phase-2: extract shared utils
This commit is contained in:
committed by
saikiranvella
parent
5a4b4aa6d1
commit
d5cf3fc31f
@@ -0,0 +1,73 @@
|
||||
import { YahooClient } from './YahooClient.js';
|
||||
import { REGIME } from '../config/constants.js';
|
||||
|
||||
const TTL_MS = 60 * 60 * 1000;
|
||||
|
||||
const DEFAULTS = {
|
||||
sp500Price: 5000,
|
||||
riskFreeRate: 4.5,
|
||||
vixLevel: 20,
|
||||
rateRegime: REGIME.HIGH,
|
||||
volatilityRegime: REGIME.NORMAL,
|
||||
benchmarks: { marketPE: 22, techPE: 30, reitYield: 3.5, igSpread: 1.0 },
|
||||
};
|
||||
|
||||
const rateRegime = (rate) => (rate < 2 ? REGIME.LOW : rate <= 5 ? REGIME.NORMAL : REGIME.HIGH);
|
||||
const volRegime = (vix) => (vix < 15 ? REGIME.LOW : vix <= 25 ? REGIME.NORMAL : REGIME.HIGH);
|
||||
|
||||
const pe = (summary) =>
|
||||
summary.summaryDetail?.trailingPE ?? summary.defaultKeyStatistics?.forwardPE;
|
||||
|
||||
export class BenchmarkProvider {
|
||||
// logger: object with .warn() — defaults to console so CLI behaviour is unchanged.
|
||||
constructor({ logger = console } = {}) {
|
||||
this.client = new YahooClient();
|
||||
this.cache = { data: null, expiresAt: 0 };
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
async getMarketContext() {
|
||||
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 = tn10y.price?.regularMarketPrice ?? 0;
|
||||
const sp500Price = sp500.price?.regularMarketPrice ?? 0;
|
||||
const vixLevel = vix.price?.regularMarketPrice ?? 0;
|
||||
|
||||
if (!sp500Price || !riskFreeRate) throw new Error('Invalid market data (zero values)');
|
||||
|
||||
const lqdYield = (lqd.summaryDetail?.trailingAnnualDividendYield ?? 0) * 100;
|
||||
|
||||
const context = {
|
||||
sp500Price,
|
||||
riskFreeRate,
|
||||
vixLevel,
|
||||
rateRegime: rateRegime(riskFreeRate),
|
||||
volatilityRegime: volRegime(vixLevel),
|
||||
benchmarks: {
|
||||
marketPE: pe(spy) ?? 22,
|
||||
techPE: pe(xlk) ?? 30,
|
||||
reitYield: (xlre.summaryDetail?.trailingAnnualDividendYield ?? 0.035) * 100,
|
||||
igSpread: Math.max(0.1, lqdYield - riskFreeRate),
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
this.cache = { data: context, expiresAt: Date.now() + TTL_MS };
|
||||
return context;
|
||||
} catch (err) {
|
||||
this.logger.warn('Market data fetch failed, using defaults:', err.message);
|
||||
return this.cache.data ?? DEFAULTS;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user