phase-2: extract shared utils

This commit is contained in:
Sai Kiran Vella
2026-06-04 11:06:30 -04:00
parent d87f0b8427
commit dc7ee22135
49 changed files with 299 additions and 120 deletions
+63
View File
@@ -0,0 +1,63 @@
import { SECTOR, ASSET_TYPE, REGIME } from '../config/constants.js';
export class MarketRegime {
constructor(marketContext) {
const b = marketContext?.benchmarks ?? {};
this.marketPE = b.marketPE ?? 22;
this.techPE = b.techPE ?? 30;
this.reitYield = b.reitYield ?? 3.5;
this.igSpread = b.igSpread ?? 1.0;
this.rateRegime = marketContext?.rateRegime ?? REGIME.NORMAL;
this.volatilityRegime = marketContext?.volatilityRegime ?? REGIME.NORMAL;
}
getInflatedOverrides(type, sector) {
if (type === ASSET_TYPE.STOCK) return this._stock(sector);
if (type === ASSET_TYPE.ETF) return this._etf();
if (type === ASSET_TYPE.BOND) return this._bond();
return { gates: {}, thresholds: {} };
}
_stock(sector) {
if (sector === SECTOR.REIT) {
return {
gates: {},
// In HIGH rate environment tighten REIT yield floor — REITs must compete harder with bonds.
thresholds: {
minYield: +(this.reitYield * (this.rateRegime === REGIME.HIGH ? 0.95 : 0.85)).toFixed(2),
maxPFFO: 20,
},
};
}
if (sector === SECTOR.TECHNOLOGY) {
return {
gates: {
maxPERatio: Math.round(this.techPE * 1.3),
maxPegGate: +(this.techPE / 15).toFixed(1),
},
thresholds: {},
};
}
// In HIGH rate environment, compress the P/E tolerance — higher rates mean
// future earnings are discounted more aggressively (lower DCF valuations).
const peMultiplier = this.rateRegime === REGIME.HIGH ? 1.2 : 1.5;
return {
gates: {
maxPERatio: Math.round(this.marketPE * peMultiplier),
maxPegGate: +(this.marketPE / 12).toFixed(1),
},
thresholds: {},
};
}
_etf() {
return { gates: { maxExpenseRatio: 0.75 }, thresholds: { minYield: 0.5 } };
}
_bond() {
// In HIGH rate environment demand a wider spread — the opportunity cost of holding
// corporate bonds over Treasuries is higher when risk-free rate is elevated.
const spreadMultiplier = this.rateRegime === REGIME.HIGH ? 0.9 : 0.8;
return { gates: {}, thresholds: { minSpread: +(this.igSpread * spreadMultiplier).toFixed(2) } };
}
}