import { YahooClient } from './YahooClient.js'; import { REGIME } from '../config/constants.js'; import type { MarketContext, Logger } from '../types.js'; const TTL_MS = 60 * 60 * 1000; const DEFAULTS: MarketContext = { sp500Price: 5000, riskFreeRate: 4.5, vixLevel: 20, rateRegime: 'HIGH', volatilityRegime: 'NORMAL', benchmarks: { marketPE: 22, techPE: 30, reitYield: 3.5, igSpread: 1.0 }, }; const rateRegime = (rate: number): MarketContext['rateRegime'] => rate < 2 ? REGIME.LOW : rate <= 5 ? REGIME.NORMAL : REGIME.HIGH; const volRegime = (vix: number): MarketContext['volatilityRegime'] => vix < 15 ? REGIME.LOW : vix <= 25 ? REGIME.NORMAL : REGIME.HIGH; // eslint-disable-next-line @typescript-eslint/no-explicit-any const pe = (summary: any): number | null => summary?.summaryDetail?.trailingPE ?? summary?.defaultKeyStatistics?.forwardPE ?? null; interface BenchmarkProviderOptions { logger?: Logger; } export class BenchmarkProvider { private client: YahooClient; private cache: { data: MarketContext | null; expiresAt: number }; private logger: Logger; constructor({ logger }: BenchmarkProviderOptions = {}) { this.client = new YahooClient(); this.cache = { data: null, expiresAt: 0 }; this.logger = logger ?? (console as unknown as Logger); } async getMarketContext(): Promise { if (this.cache.data && Date.now() < this.cache.expiresAt) return this.cache.data; try { const [sp500, tn10y, vix, spy, xlk, xlre, lqd] = await Promise.all([ this.client.fetchSummary('^GSPC'), this.client.fetchSummary('^TNX'), this.client.fetchSummary('^VIX'), this.client.fetchSummary('SPY'), this.client.fetchSummary('XLK'), this.client.fetchSummary('XLRE'), this.client.fetchSummary('LQD'), ]); const riskFreeRate = (sp500 as any)?.price?.regularMarketPrice !== undefined ? ((tn10y as any)?.price?.regularMarketPrice ?? 0) : 0; const sp500Price = (sp500 as any)?.price?.regularMarketPrice ?? 0; const vixLevel = (vix as any)?.price?.regularMarketPrice ?? 0; if (!sp500Price || !riskFreeRate) throw new Error('Invalid market data (zero values)'); const lqdYield = ((lqd as any)?.summaryDetail?.trailingAnnualDividendYield ?? 0) * 100; const context: MarketContext = { sp500Price, riskFreeRate, vixLevel, rateRegime: rateRegime(riskFreeRate), volatilityRegime: volRegime(vixLevel), benchmarks: { marketPE: pe(spy) ?? 22, techPE: pe(xlk) ?? 30, reitYield: ((xlre as any)?.summaryDetail?.trailingAnnualDividendYield ?? 0.035) * 100, igSpread: Math.max(0.1, lqdYield - riskFreeRate), }, }; this.cache = { data: context, expiresAt: Date.now() + TTL_MS }; return context; } catch (err) { this.logger.warn('Market data fetch failed, using defaults:', (err as Error).message); return this.cache.data ?? DEFAULTS; } } }