Spaces:
Sleeping
Sleeping
| import { MungerEngine } from '@/lib/munger-engine'; | |
| import { JsonStore } from '@/lib/store'; | |
| import fs from 'fs'; | |
| import path from 'path'; | |
| import Alpaca from '@alpacahq/alpaca-trade-api'; | |
| // Load initial symbols (same logic as scripts/find_forever_stocks.ts) | |
| const loadSymbols = () => { | |
| try { | |
| const jsonPath = path.resolve(process.cwd(), 'scripts/sp500_symbols.json'); | |
| if (fs.existsSync(jsonPath)) { | |
| return JSON.parse(fs.readFileSync(jsonPath, 'utf-8')) as string[]; | |
| } | |
| } catch { | |
| console.warn("Failed to load symbols from file, using fallback"); | |
| } | |
| return ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'META', 'BRK-B', 'V', 'JNJ', 'WMT', 'PG']; | |
| }; | |
| // Fetch portfolio symbols from Alpaca | |
| const getPortfolioSymbols = async (): Promise<string[]> => { | |
| try { | |
| if (!process.env.ALPACA_KEY || !process.env.ALPACA_SECRET) { | |
| logToFile('getPortfolioSymbols: No Alpaca credentials found'); | |
| return []; | |
| } | |
| const alpaca = new (Alpaca as any)({ | |
| keyId: process.env.ALPACA_KEY, | |
| secretKey: process.env.ALPACA_SECRET, | |
| paper: true, | |
| }); | |
| const positions = await alpaca.getPositions(); | |
| const portfolioSymbols = positions.map((p: any) => p.symbol as string); | |
| logToFile(`getPortfolioSymbols: Found ${portfolioSymbols.length} symbols in portfolio: ${portfolioSymbols.join(', ')}`); | |
| return portfolioSymbols; | |
| } catch (error) { | |
| logToFile(`getPortfolioSymbols: Failed to fetch - ${(error as Error).message}`); | |
| console.warn('Failed to fetch portfolio symbols:', error); | |
| return []; | |
| } | |
| }; | |
| const logToFile = (msg: string) => { | |
| try { | |
| const logPath = path.resolve(process.cwd(), 'debug_sync.log'); | |
| fs.appendFileSync(logPath, `${new Date().toISOString()}: ${msg}\n`); | |
| } catch { } | |
| }; | |
| export async function runSync(options?: { force?: boolean }) { | |
| logToFile("runSync: Started"); | |
| const currentStatus = JsonStore.getStatus(); | |
| if (currentStatus.status === 'SYNCING') { | |
| logToFile("runSync: Already syncing, aborting"); | |
| throw new Error('Sync already in progress'); | |
| } | |
| // Get S&P 500 symbols | |
| const sp500Symbols = loadSymbols(); | |
| logToFile(`runSync: Loaded ${sp500Symbols.length} S&P 500 symbols`); | |
| // Get portfolio symbols from Alpaca | |
| const portfolioSymbols = await getPortfolioSymbols(); | |
| // Combine symbols (unique set) | |
| const allSymbols = [...new Set([...sp500Symbols, ...portfolioSymbols])]; | |
| logToFile(`runSync: Total unique symbols to analyze: ${allSymbols.length} (S&P500: ${sp500Symbols.length}, Portfolio: ${portfolioSymbols.length})`); | |
| JsonStore.updateStatus({ | |
| status: 'SYNCING', | |
| totalAssets: allSymbols.length, | |
| assetsAnalyzed: 0, | |
| lastSyncTimestamp: new Date().toISOString() | |
| }); | |
| logToFile("runSync: Status set to SYNCING. Starting background process."); | |
| (async () => { | |
| try { | |
| logToFile('runSync: Async block started'); | |
| const results = await MungerEngine.processBatch(allSymbols, portfolioSymbols, (analyzed, total) => { | |
| // Log every 10 items or so to avoid huge IO | |
| if (analyzed % 10 === 0) logToFile(`runSync: Progress ${analyzed}/${total}`); | |
| JsonStore.updateStatus({ | |
| assetsAnalyzed: analyzed, | |
| totalAssets: total | |
| }); | |
| }); | |
| logToFile(`runSync: Finished processing. Saving ${results.length} results.`); | |
| JsonStore.saveStocks(results); | |
| logToFile("runSync: Updating status to IDLE"); | |
| JsonStore.updateStatus({ | |
| status: 'IDLE', | |
| lastSyncTimestamp: new Date().toISOString(), | |
| buyTriggers: results.filter(s => s.signal === 'BUY_TRIGGER').length | |
| }); | |
| } catch (error) { | |
| logToFile(`runSync: FAILED with error: ${(error as Error).message}`); | |
| console.error('Sync failed:', error); | |
| JsonStore.updateStatus({ | |
| status: 'ERROR', | |
| errors: [(error as Error).message] | |
| }); | |
| } | |
| })(); | |
| logToFile("runSync: Returning success to caller"); | |
| return { success: true, totalAssets: allSymbols.length }; | |
| } | |