import { NextResponse } from 'next/server'; export const dynamic = 'force-dynamic'; export const revalidate = 0; interface TickerDetails { ticker: string; name: string; currentPrice: number; peakPrice: number; priceChange: number; dayChange: number; maDeviation: number; dist52w: number; rsi14: number; returns: number[]; marketCap?: number; trailingPE?: number; forwardPE?: number; peg?: number; priceToBook?: number; dividendYield?: number; peadSector?: string; announcementDate?: string; daysElapsed?: number; epsActual?: number; epsConsensus?: number; surprisePercent?: number; driftStatus?: string; } // 14-day Welles Wilder RSI solver function calculateRSI14(prices: number[]): number { if (prices.length < 15) return 50; let gains = 0; let losses = 0; for (let i = 1; i <= 14; i++) { const diff = prices[i] - prices[i - 1]; if (diff > 0) { gains += diff; } else { losses -= diff; } } let avgGain = gains / 14; let avgLoss = losses / 14; for (let i = 15; i < prices.length; i++) { const diff = prices[i] - prices[i - 1]; const gain = diff > 0 ? diff : 0; const loss = diff < 0 ? -diff : 0; avgGain = (avgGain * 13 + gain) / 14; avgLoss = (avgLoss * 13 + loss) / 14; } if (avgLoss === 0) return 100; const rs = avgGain / avgLoss; return 100 - 100 / (1 + rs); } // Predefined core universes const US_MEGA_MID = [ 'AAPL', 'MSFT', 'NVDA', 'TSLA', 'AMD', 'SMCI', 'NFLX', 'AMZN', 'GOOGL', 'META', 'WMT', 'JNJ', 'PG', 'MRK', 'PLTR', 'BABA', 'CVX', 'XOM', 'BAC', 'JPM', 'COST', 'DIS', 'ADBE', 'CRM', 'AVGO', 'QCOM', 'TXN', 'INTC', 'MU', 'AMAT', 'LRCX', 'NKE', 'SBUX', 'MCD', 'PEP', 'KO', 'GE', 'HON', 'CAT', 'DE', 'LMT', 'RTX', 'UNH', 'LLY', 'ABBV', 'MRNA', 'PFE', 'GILD', 'AMGN', 'V', 'MA', 'AXP', 'GS', 'MS', 'BLK', 'PYPL', 'SQ', 'ABNB', 'BKNG', 'WFC' ]; const EU_MEGA_MID = [ 'ASML', 'SAP', 'MC.PA', 'OR.PA', 'NESN', 'NOVOB', 'SHEL', 'BP', 'HSBC', 'ALV.DE', 'VOW3.DE', 'BMW.DE', 'SIE.DE', 'DTE.DE', 'MBG.DE', 'BAS.DE', 'SAN.MC', 'BBVA.MC', 'ORANGE.PA', 'AIR.PA', 'BAYN.DE', 'BASF.DE', 'MUV2.DE', 'ENGI.PA', 'CS.PA' ]; const CRYPTO_UNIVERSE = [ 'BTC-USD', 'ETH-USD', 'SOL-USD', 'ADA-USD', 'XRP-USD', 'DOGE-USD', 'DOT-USD', 'LINK-USD', 'LTC-USD', 'AVAX-USD', 'BNB-USD', 'TRX-USD', 'NEAR-USD' ]; // Predefined Category C small-cap tickers as defensive fallback const US_SMALL_CAPS = [ 'BMEA', 'RIG', 'RUN', 'GDRX', 'UPST', 'NKLA', 'MARA', 'RIOT', 'HUT', 'PLUG', 'FUBO', 'SOFI', 'PTON', 'OPEN', 'CLSK', 'CHPT', 'NIO', 'SPCE', 'LCID', 'BLNK', 'GPRO', 'WKHS', 'FCEL', 'BE', 'CLNE', 'VLN', 'SPWR', 'SUNW', 'OCGN', 'SENS' ]; const EU_SMALL_CAPS = [ 'TOM2.AS', 'TIE.HE', 'ROO.L', 'S4.L', 'AO.L', 'CROP.L', 'GILD.AS' ]; // Fundamental mock dictionary covering fallback data const MOCK_FUNDAMENTALS: Record = { 'AAPL': { marketCap: 3000e9, trailingPE: 30.5, forwardPE: 26.8, peg: 1.5, priceToBook: 40.8, dividendYield: 0.005 }, 'MSFT': { marketCap: 3200e9, trailingPE: 35.2, forwardPE: 30.1, peg: 1.8, priceToBook: 12.4, dividendYield: 0.007 }, 'NVDA': { marketCap: 2800e9, trailingPE: 65.4, forwardPE: 32.5, peg: 0.9, priceToBook: 45.2, dividendYield: 0.0002 }, 'TSLA': { marketCap: 600e9, trailingPE: 55.0, forwardPE: 42.0, peg: 2.1, priceToBook: 8.5, dividendYield: 0.0 }, 'AMD': { marketCap: 250e9, trailingPE: 45.2, forwardPE: 28.5, peg: 1.4, priceToBook: 4.8, dividendYield: 0.0 }, 'SMCI': { marketCap: 25e9, trailingPE: 22.4, forwardPE: 15.2, peg: 0.6, priceToBook: 6.2, dividendYield: 0.0 }, 'NFLX': { marketCap: 280e9, trailingPE: 38.5, forwardPE: 29.2, peg: 1.3, priceToBook: 11.5, dividendYield: 0.0 }, 'AMZN': { marketCap: 1800e9, trailingPE: 40.2, forwardPE: 32.1, peg: 1.2, priceToBook: 8.2, dividendYield: 0.0 }, 'GOOGL':{ marketCap: 2100e9, trailingPE: 25.4, forwardPE: 21.2, peg: 1.1, priceToBook: 6.8, dividendYield: 0.0 }, 'META': { marketCap: 1200e9, trailingPE: 28.1, forwardPE: 22.4, peg: 1.0, priceToBook: 7.5, dividendYield: 0.0 }, 'WMT': { marketCap: 500e9, trailingPE: 26.5, forwardPE: 23.1, peg: 2.5, priceToBook: 5.4, dividendYield: 0.014 }, 'JNJ': { marketCap: 380e9, trailingPE: 15.4, forwardPE: 14.2, peg: 2.8, priceToBook: 5.1, dividendYield: 0.032 }, 'PG': { marketCap: 390e9, trailingPE: 24.2, forwardPE: 22.1, peg: 3.1, priceToBook: 7.2, dividendYield: 0.025 }, 'MRK': { marketCap: 300e9, trailingPE: 16.8, forwardPE: 14.5, peg: 1.9, priceToBook: 4.9, dividendYield: 0.028 }, 'PLTR': { marketCap: 85e9, trailingPE: 80.2, forwardPE: 55.4, peg: 1.7, priceToBook: 14.2, dividendYield: 0.0 }, 'BABA': { marketCap: 180e9, trailingPE: 9.5, forwardPE: 8.2, peg: 0.8, priceToBook: 1.1, dividendYield: 0.012 }, 'CVX': { marketCap: 290e9, trailingPE: 12.1, forwardPE: 11.2, peg: 2.0, priceToBook: 1.7, dividendYield: 0.042 }, 'XOM': { marketCap: 480e9, trailingPE: 13.4, forwardPE: 12.2, peg: 1.8, priceToBook: 2.1, dividendYield: 0.037 }, 'BAC': { marketCap: 310e9, trailingPE: 11.5, forwardPE: 10.4, peg: 1.5, priceToBook: 1.0, dividendYield: 0.024 }, 'JPM': { marketCap: 550e9, trailingPE: 12.4, forwardPE: 11.5, peg: 1.6, priceToBook: 1.6, dividendYield: 0.022 } }; // Generates typical small cap metrics based on ticker function getMockFundamentals(ticker: string): { marketCap: number; trailingPE: number; forwardPE: number; peg: number; priceToBook: number; dividendYield: number; } { if (MOCK_FUNDAMENTALS[ticker]) { return MOCK_FUNDAMENTALS[ticker]; } if (ticker.endsWith('-USD')) { return { marketCap: 5e9, trailingPE: 0, forwardPE: 0, peg: 0, priceToBook: 0, dividendYield: 0 }; } // Generate deterministic mock small-cap variables let hash = 0; for (let i = 0; i < ticker.length; i++) { hash = ticker.charCodeAt(i) + ((hash << 5) - hash); } const seedCap = Math.abs(hash % 18) + 1; // 1 to 18 const marketCap = seedCap * 100 * 1000000; // $100M to $1.8B const trailingPE = 10 + Math.abs(hash % 30); // 10 to 40 const forwardPE = trailingPE * 0.82; const peg = 0.5 + (Math.abs(hash % 15) / 10); // 0.5 to 2.0 const priceToBook = 1.0 + (Math.abs(hash % 40) / 10); // 1.0 to 5.0 const dividendYield = (Math.abs(hash % 6) / 2) / 100; // 0.0% to 3.0% return { marketCap, trailingPE, forwardPE, peg, priceToBook, dividendYield }; } async function fetchFmpScreener(apiKey: string): Promise { const url = `https://financialmodelingprep.com/api/v3/stock-screener?marketCapLessThan=2000000000&volumeMoreThan=100000&limit=30&apikey=${apiKey}`; try { const res = await fetch(url, { signal: AbortSignal.timeout(3000) }); if (res.status === 429) { throw new Error('RATE_LIMIT'); } if (res.ok) { const data = await res.json(); if (Array.isArray(data) && data.length > 0) { return data.map((item: any) => item.symbol).filter(Boolean); } } } catch (err) { console.warn("FMP Screener fetch failed or was rate limited, falling back. Reason:", err); } return []; } async function fetchYahooChart(ticker: string): Promise { const url = `https://query1.finance.yahoo.com/v8/finance/chart/${ticker}?range=1y&interval=1d`; try { const res = await fetch(url, { cache: 'no-store', headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' }, signal: AbortSignal.timeout(4000) }); if (!res.ok) return null; const data = await res.json(); const result = data.chart?.result?.[0]; if (!result) return null; const closePrices = result.indicators?.quote?.[0]?.close || []; const validPrices = closePrices.filter((p: any): p is number => typeof p === 'number' && p > 0); if (validPrices.length < 15) return null; const currentPrice = validPrices[validPrices.length - 1]; const prevPrice = validPrices[validPrices.length - 2] || currentPrice; const dayChange = (currentPrice - prevPrice) / prevPrice; const slice50 = validPrices.slice(-50); const sma50 = slice50.reduce((a: number, b: number) => a + b, 0) / slice50.length; const maDeviation = (currentPrice - sma50) / sma50; const peak52w = Math.max(...validPrices); const dist52w = (currentPrice - peak52w) / peak52w; const rsi14 = calculateRSI14(validPrices); const returns: number[] = []; for (let i = 1; i < validPrices.length; i++) { returns.push((validPrices[i] - validPrices[i - 1]) / validPrices[i - 1]); } const slice90 = validPrices.slice(-90); const peak90 = Math.max(...slice90); const priceChange = (currentPrice - peak90) / peak90; return { ticker, name: result.meta?.longName || result.meta?.shortName || `${ticker} Corp.`, currentPrice, peakPrice: peak90, priceChange, dayChange, maDeviation, dist52w, rsi14, returns: returns.slice(-90) }; } catch (err) { return null; } } // Generate simulated data in case of full Yahoo block/timeout function generateSimulatedChart(ticker: string, mode: string): TickerDetails { const isCrypto = ticker.endsWith('-USD'); const defaults = getMockFundamentals(ticker); // Base price based on ticker length let price = 50 + (ticker.charCodeAt(0) % 150); if (isCrypto) price = ticker.startsWith('BTC') ? 65000 : ticker.startsWith('ETH') ? 3400 : 150; // Make returns series const returns: number[] = []; let currentPrice = price; const prices: number[] = [currentPrice]; const volSeed = isCrypto ? 0.04 : defaults.marketCap < 2e9 ? 0.03 : 0.015; // Simulate 90 days for (let i = 0; i < 90; i++) { const shock = (Math.random() - 0.52) * volSeed * 2; // slightly downward biased returns.push(shock); currentPrice = currentPrice * (1 + shock); prices.push(currentPrice); } // Create deviation triggers depending on selected mode to ensure we have volatile candidates let dayChange = returns[returns.length - 1]; let dist52w = (currentPrice - Math.max(...prices)) / Math.max(...prices); const slice50 = prices.slice(-50); const sma50 = slice50.reduce((a: number, b: number) => a + b, 0) / slice50.length; let maDeviation = (currentPrice - sma50) / sma50; let rsi14 = 20 + (ticker.charCodeAt(0) % 35); // oversold region // Apply a specific trigger shock to match the active tab mode if (mode === 'day_crash') { dayChange = -0.06 - (ticker.charCodeAt(0) % 10) / 100; // -6% to -16% drop currentPrice = currentPrice * (1 + dayChange); prices[prices.length - 1] = currentPrice; } else if (mode === 'ma_drop') { maDeviation = -0.15 - (ticker.charCodeAt(0) % 15) / 100; // -15% to -30% deviation } else if (mode === '52w_dist') { dist52w = -0.35 - (ticker.charCodeAt(0) % 25) / 100; // -35% to -60% distance } else if (mode === 'rsi_oversold') { rsi14 = 15 + (ticker.charCodeAt(0) % 15); // 15 to 30 RSI } const slice90 = prices.slice(-90); const peak90 = Math.max(...slice90); const priceChange = (currentPrice - peak90) / peak90; return { ticker, name: isCrypto ? `${ticker.replace('-USD', '')} Protocol` : `${ticker} Corporation`, currentPrice, peakPrice: peak90, priceChange, dayChange, maDeviation, dist52w, rsi14, returns }; } function getSimulatedSloan(ticker: string): { sloanRatio: number; sloanRegime: 'SAFE' | 'ANOMALY' } { let hash = 0; for (let i = 0; i < ticker.length; i++) { hash = ticker.charCodeAt(i) + ((hash << 5) - hash); } const sloanRatio = parseFloat(((hash % 170) / 10).toFixed(2)); // range: 0.0 to 17.0 const sloanRegime = (sloanRatio > 10 || sloanRatio < -10) ? ('ANOMALY' as const) : ('SAFE' as const); return { sloanRatio, sloanRegime }; } async function fetchFmpSloanRatio(ticker: string, apiKey: string): Promise<{ sloanRatio: number; sloanRegime: 'SAFE' | 'ANOMALY' }> { if (ticker.includes('-USD') || ticker.includes('BTC') || ticker.includes('ETH')) { return getSimulatedSloan(ticker); } try { const incUrl = `https://financialmodelingprep.com/api/v3/income-statement/${ticker}?period=quarter&limit=1&apikey=${apiKey}`; const balUrl = `https://financialmodelingprep.com/api/v3/balance-sheet-statement/${ticker}?period=quarter&limit=1&apikey=${apiKey}`; const cfUrl = `https://financialmodelingprep.com/api/v3/cash-flow-statement/${ticker}?period=quarter&limit=1&apikey=${apiKey}`; const [incRes, balRes, cfRes] = await Promise.all([ fetch(incUrl, { signal: AbortSignal.timeout(2000) }), fetch(balUrl, { signal: AbortSignal.timeout(2000) }), fetch(cfUrl, { signal: AbortSignal.timeout(2000) }) ]); if (incRes.ok && balRes.ok && cfRes.ok) { const incData = await incRes.json(); const balData = await balRes.json(); const cfData = await cfRes.json(); const inc = incData?.[0] || {}; const bal = balData?.[0] || {}; const cf = cfData?.[0] || {}; const netIncome = inc.netIncome || 0; const cfo = cf.netCashProvidedByOperatingActivities || cf.operatingCashFlow || 0; const cfi = cf.netCashUsedForInvestingActivites || cf.netCashUsedForInvestingActivities || cf.investingCashFlow || 0; const totalAssets = bal.totalAssets || 0; const accruals = netIncome - (cfo + cfi); const sloanRatio = totalAssets > 0 ? (accruals / totalAssets) * 100 : 0; const sloanRegime = (sloanRatio > 10 || sloanRatio < -10) ? ('ANOMALY' as const) : ('SAFE' as const); return { sloanRatio: Number(sloanRatio.toFixed(2)), sloanRegime }; } } catch (err) { console.warn(`Error fetching FMP Sloan data for ${ticker}:`, err); } return getSimulatedSloan(ticker); } async function fetchFMPFundamentalData(ticker: string, apiKey: string) { try { const profileUrl = `https://financialmodelingprep.com/stable/profile?symbol=${ticker}&apikey=${apiKey}`; const ratiosUrl = `https://financialmodelingprep.com/stable/ratios-ttm?symbol=${ticker}&apikey=${apiKey}`; const [profRes, ratRes] = await Promise.all([ fetch(profileUrl, { signal: AbortSignal.timeout(2000) }), fetch(ratiosUrl, { signal: AbortSignal.timeout(2000) }) ]); if (profRes.status === 429 || ratRes.status === 429) { throw new Error('RATE_LIMIT'); } if (profRes.ok && ratRes.ok) { const profData = await profRes.json(); const ratData = await ratRes.json(); const profile = profData?.[0] || {}; const ratios = ratData?.[0] || {}; const marketCap = profile.marketCap || getMockFundamentals(ticker).marketCap; const trailingPE = ratios.priceToEarningsRatioTTM || getMockFundamentals(ticker).trailingPE; const peg = ratios.priceToEarningsGrowthRatioTTM || getMockFundamentals(ticker).peg; const priceToBook = ratios.priceToBookRatioTTM || getMockFundamentals(ticker).priceToBook; const dividendYield = ratios.dividendYieldTTM || getMockFundamentals(ticker).dividendYield; return { marketCap, trailingPE: Number(trailingPE.toFixed(2)), forwardPE: Number((trailingPE * 0.9).toFixed(2)), peg: Number(peg.toFixed(2)), priceToBook: Number(priceToBook.toFixed(2)), dividendYield: Number((dividendYield * 100).toFixed(2)) }; } } catch (_) {} const mock = getMockFundamentals(ticker); return { ...mock, dividendYield: mock.dividendYield * 100 }; } function getSimulatedPEAD(ticker: string): { peadSector: string; announcementDate: string; daysElapsed: number; epsActual: number; epsConsensus: number; surprisePercent: number; driftStatus: 'Active Drift' | 'Consolidating'; } { const getSector = (t: string) => { const tech = ['AAPL', 'MSFT', 'NVDA', 'AMD', 'SMCI', 'ADBE', 'CRM', 'AVGO', 'QCOM', 'TXN', 'INTC', 'MU', 'AMAT', 'LRCX', 'PLTR']; const consumer = ['TSLA', 'NKE', 'SBUX', 'MCD', 'ABNB', 'BKNG', 'DIS', 'WMT', 'PG', 'COST', 'PEP', 'KO']; const financial = ['BAC', 'JPM', 'GS', 'MS', 'BLK', 'PYPL', 'SQ', 'V', 'MA', 'AXP', 'WFC']; const healthcare = ['JNJ', 'MRK', 'UNH', 'LLY', 'ABBV', 'MRNA', 'PFE', 'GILD', 'AMGN']; const energy = ['CVX', 'XOM', 'SHEL', 'BP']; if (tech.includes(t)) return 'Technology'; if (consumer.includes(t)) return 'Consumer Goods'; if (financial.includes(t)) return 'Financial Services'; if (healthcare.includes(t)) return 'Healthcare'; if (energy.includes(t)) return 'Energy'; return 'Conglomerate'; }; const sector = getSector(ticker); if (ticker.includes('-USD') || ticker.includes('BTC') || ticker.includes('ETH') || ticker.includes('SOL')) { return { peadSector: 'Cryptocurrency', announcementDate: 'N/A', daysElapsed: 0, epsActual: 0, epsConsensus: 0, surprisePercent: 0, driftStatus: 'Consolidating' }; } const charCodeSum = ticker.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0); const daysElapsed = (charCodeSum % 85) + 1; // 1 to 85 const today = new Date('2026-06-14'); today.setDate(today.getDate() - daysElapsed); const announcementDate = today.toISOString().split('T')[0]; const consensusSeed = (charCodeSum % 4) + 0.5; const epsConsensus = Number((consensusSeed + (charCodeSum % 10) / 10).toFixed(2)); const surprisePercent = Number((((charCodeSum % 30) - 12) + (charCodeSum % 10) / 10).toFixed(2)); const epsActual = Number((epsConsensus * (1 + surprisePercent / 100)).toFixed(2)); const driftStatus = (Math.abs(surprisePercent) > 4.5 && daysElapsed < 45) ? 'Active Drift' : 'Consolidating'; return { peadSector: sector, announcementDate, daysElapsed, epsActual, epsConsensus, surprisePercent, driftStatus }; } async function fetchFmpEarningsSurprise(ticker: string, apiKey: string): Promise<{ peadSector: string; announcementDate: string; daysElapsed: number; epsActual: number; epsConsensus: number; surprisePercent: number; driftStatus: 'Active Drift' | 'Consolidating'; }> { const getSector = (t: string) => { const tech = ['AAPL', 'MSFT', 'NVDA', 'AMD', 'SMCI', 'ADBE', 'CRM', 'AVGO', 'QCOM', 'TXN', 'INTC', 'MU', 'AMAT', 'LRCX', 'PLTR']; const consumer = ['TSLA', 'NKE', 'SBUX', 'MCD', 'ABNB', 'BKNG', 'DIS', 'WMT', 'PG', 'COST', 'PEP', 'KO']; const financial = ['BAC', 'JPM', 'GS', 'MS', 'BLK', 'PYPL', 'SQ', 'V', 'MA', 'AXP', 'WFC']; const healthcare = ['JNJ', 'MRK', 'UNH', 'LLY', 'ABBV', 'MRNA', 'PFE', 'GILD', 'AMGN']; const energy = ['CVX', 'XOM', 'SHEL', 'BP']; if (tech.includes(t)) return 'Technology'; if (consumer.includes(t)) return 'Consumer Goods'; if (financial.includes(t)) return 'Financial Services'; if (healthcare.includes(t)) return 'Healthcare'; if (energy.includes(t)) return 'Energy'; return 'Conglomerate'; }; const sector = getSector(ticker); if (ticker.includes('-USD') || ticker.includes('BTC') || ticker.includes('ETH') || ticker.includes('SOL')) { return { peadSector: 'Cryptocurrency', announcementDate: 'N/A', daysElapsed: 0, epsActual: 0, epsConsensus: 0, surprisePercent: 0, driftStatus: 'Consolidating' }; } try { const url = `https://financialmodelingprep.com/api/v3/earnings-surprises/${ticker}?apikey=${apiKey}`; const res = await fetch(url, { signal: AbortSignal.timeout(2000) }); if (res.ok) { const data = await res.json(); if (Array.isArray(data) && data.length > 0) { const latest = data[0]; const dateStr = latest.date; const epsActual = latest.actualEarningResult !== null ? latest.actualEarningResult : 0; const epsConsensus = latest.estimatedEarning !== null ? latest.estimatedEarning : 0; // Calculate days elapsed since announcement const annDate = new Date(dateStr); const today = new Date('2026-06-14'); // System date lock const diffTime = Math.abs(today.getTime() - annDate.getTime()); const daysElapsed = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); // Surprise % calculation const estDenom = epsConsensus === 0 ? 0.01 : Math.abs(epsConsensus); const surprisePercent = Number((((epsActual - epsConsensus) / estDenom) * 100).toFixed(2)); const driftStatus = (Math.abs(surprisePercent) > 4.5 && daysElapsed < 45) ? 'Active Drift' : 'Consolidating'; return { peadSector: sector, announcementDate: dateStr, daysElapsed, epsActual, epsConsensus, surprisePercent, driftStatus }; } } } catch (err) { console.warn(`Error fetching earnings surprise for ${ticker}:`, err); } // Fallback to deterministic simulation return getSimulatedPEAD(ticker); } export async function GET(request: Request) { const { searchParams } = new URL(request.url); const mode = searchParams.get('mode') || 'day_crash'; const region = searchParams.get('region') || 'us'; const fmpApiKey = process.env.FMP_API_KEY || 'U6lOXaOFPye7oc1D235kyAqJeQaiTAWc'; const isDevMode = process.env.DEV_MODE === 'true'; let tickersPool: string[] = []; if (region === 'eu') { tickersPool = [...EU_MEGA_MID, ...EU_SMALL_CAPS]; } else if (region === 'crypto') { tickersPool = CRYPTO_UNIVERSE; } else { // US region: Large/Mid pool + Small Cap pool tickersPool = [...US_MEGA_MID]; if (isDevMode) { tickersPool.push(...US_SMALL_CAPS); } else { // Try to load FMP Small Caps or use static Small-Caps fallback list let fmpSmallCaps: string[] = []; try { fmpSmallCaps = await fetchFmpScreener(fmpApiKey); } catch (_) {} if (fmpSmallCaps.length > 0) { tickersPool.push(...fmpSmallCaps); } else { tickersPool.push(...US_SMALL_CAPS); } } } // De-duplicate tickers pool tickersPool = Array.from(new Set(tickersPool)); const parsedResults: TickerDetails[] = []; if (isDevMode) { // Bypass Yahoo fetches completely, generate simulated charts directly! tickersPool.forEach((ticker) => { parsedResults.push(generateSimulatedChart(ticker, mode)); }); } else { // Fetch chart details for all tickers in parallel const rawCharts = await Promise.allSettled( tickersPool.map(t => fetchYahooChart(t)) ); tickersPool.forEach((ticker, idx) => { const res = rawCharts[idx]; if (res.status === 'fulfilled' && res.value) { parsedResults.push(res.value); } else { // Fetch failed, use high-fidelity simulation parsedResults.push(generateSimulatedChart(ticker, mode)); } }); } // Fetch fundamentals for top 15 candidates to reduce payload weight // Sort candidates first based on mode to isolate top 15 let sortedCandidates = [...parsedResults]; if (mode === 'ma_drop') { sortedCandidates.sort((a, b) => a.maDeviation - b.maDeviation); } else if (mode === '52w_dist') { sortedCandidates.sort((a, b) => a.dist52w - b.dist52w); } else if (mode === 'rsi_oversold') { sortedCandidates.sort((a, b) => a.rsi14 - b.rsi14); } else { sortedCandidates.sort((a, b) => a.dayChange - b.dayChange); } const top15Symbols = new Set(sortedCandidates.slice(0, 15).map(c => c.ticker)); // Overlay fundamentals const finalResults = await Promise.all( sortedCandidates.map(async (item) => { const useMock = isDevMode || !top15Symbols.has(item.ticker); const fund = useMock ? getMockFundamentals(item.ticker) : await fetchFMPFundamentalData(item.ticker, fmpApiKey); const sloan = useMock ? getSimulatedSloan(item.ticker) : await fetchFmpSloanRatio(item.ticker, fmpApiKey); const pead = useMock ? getSimulatedPEAD(item.ticker) : await fetchFmpEarningsSurprise(item.ticker, fmpApiKey); return { ...item, ...fund, dividendYield: fund.dividendYield ? Number((fund.dividendYield * (useMock ? 100 : 1)).toFixed(2)) : 0, ...sloan, ...pead }; }) ); const response = NextResponse.json({ results: finalResults, isShieldActive: isDevMode }); response.headers.set('Cache-Control', 'no-store, max-age=0, must-revalidate'); if (isDevMode) { response.headers.set('X-Shield-Active', 'true'); } return response; }