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 => { 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 }; }