phase-6: typescript introduction

This commit is contained in:
Sai Kiran Vella
2026-06-04 22:16:48 -04:00
committed by saikiranvella
parent 57625c27d7
commit c160e65bd6
69 changed files with 2323 additions and 1036 deletions
@@ -1,7 +1,9 @@
// Credit rating scale (S&P convention).
// Bond.js converts letter ratings to these numbers; BondScorer uses them for gate checks.
import type { Sector } from './constants.js';
// ── Credit rating scale (S&P convention) ─────────────────────────────────
// Bond.ts converts letter ratings to these numbers; BondScorer uses them for gate checks.
// Investment grade = BBB (7) and above.
export const CREDIT_RATING_SCALE = {
export const CREDIT_RATING_SCALE: Record<string, number> = {
AAA: 10,
AA: 9,
A: 8,
@@ -14,16 +16,38 @@ export const CREDIT_RATING_SCALE = {
D: 1,
};
// ── Scoring rule shape ────────────────────────────────────────────────────
interface GateSet extends Record<string, number> {}
interface WeightSet extends Record<string, number> {}
interface ThresholdSet extends Record<string, number> {}
interface RuleBlock {
gates: GateSet;
weights: WeightSet;
thresholds: ThresholdSet;
}
interface StockRules extends RuleBlock {
SECTOR_OVERRIDE: Partial<Record<Sector, Partial<RuleBlock>>>;
}
interface ScoringRulesShape {
STOCK: StockRules;
ETF: RuleBlock;
BOND: RuleBlock;
}
// ─────────────────────────────────────────────────────────────────────────────
// Fundamental baseline — Graham / value-investing style.
// MarketRegime.js overrides the valuation gates for INFLATED-mode analysis.
// MarketRegime.ts overrides the valuation gates for INFLATED-mode analysis.
// Sector overrides are structural — they apply in both modes.
// ─────────────────────────────────────────────────────────────────────────────
export const ScoringRules = {
export const ScoringRules: ScoringRulesShape = {
STOCK: {
gates: {
maxDebtToEquity: 1.5, // Graham ceiling; 3.0 was too permissive — most distress starts above 2x
minQuickRatio: 0.8, // Raised from 0.5: below 0.8 signals real liquidity stress in non-tech
maxDebtToEquity: 1.5, // Graham ceiling; most distress starts above 2x
minQuickRatio: 0.8, // below 0.8 signals real liquidity stress in non-tech
maxPERatio: 15, // Graham's actual rule: never pay more than 15x trailing earnings
maxPegGate: 1.0, // PEG > 1.0 means you're paying full price for growth (Lynch standard)
},
@@ -33,48 +57,36 @@ export const ScoringRules = {
roe: 3, // return on equity — Buffett's primary quality metric
peg: 2, // valuation relative to growth
revenue: 2, // revenue growth
fcf: 3, // raised: FCF is the most manipulation-resistant quality signal
fcf: 3, // FCF is the most manipulation-resistant quality signal
},
thresholds: {
marginHigh: 15, // lowered from 20: 15% net margin is genuinely excellent across most sectors
marginMed: 8, // lowered from 10: 8% is the realistic mid-tier for industrials/retail
marginHigh: 15, // 15% net margin is genuinely excellent across most sectors
marginMed: 8, // 8% is the realistic mid-tier for industrials/retail
opMarginHigh: 20,
opMarginMed: 10,
roeHigh: 15, // lowered from 20: sustainable 15% ROE is Buffett-quality; 20% is rare/fleeting
roeMed: 10, // kept — 10% is the cost-of-equity floor for most businesses
pegHigh: 0.75, // raised bar: PEG < 0.75 is genuinely cheap relative to growth
roeHigh: 15, // sustainable 15% ROE is Buffett-quality; 20% is rare/fleeting
roeMed: 10, // 10% is the cost-of-equity floor for most businesses
pegHigh: 0.75, // PEG < 0.75 is genuinely cheap relative to growth
pegMed: 1.0,
revHigh: 10, // lowered from 15: 10% organic revenue growth is strong for mature cos
revHigh: 10, // 10% organic revenue growth is strong for mature cos
revMed: 5,
fcfHigh: 5,
fcfMed: 2,
},
SECTOR_OVERRIDE: {
// Large-cap tech borrows to fund buybacks — D/E 2.0 is structural, not distress.
// AAPL quick ratio runs ~0.9 by design (aggressive working capital management).
// Raised maxPERatio from 30→35: mega-cap tech comps (MSFT, GOOG) trade 28-35x sustainably.
// Tightened maxPegGate from 2.0→1.5: paying >1.5x PEG for tech rarely ends well long-term.
TECHNOLOGY: {
gates: { maxDebtToEquity: 2.0, minQuickRatio: 0.8, maxPERatio: 35, maxPegGate: 1.5 },
weights: { margin: 1, opMargin: 3, roe: 3, peg: 3, revenue: 4, fcf: 3 },
thresholds: { marginHigh: 25, opMarginHigh: 25, roeHigh: 20, pegHigh: 1.0, revHigh: 20 },
},
// REITs: P/E and PEG are distorted by depreciation — score on yield and P/FFO.
// Raised minYield from 4.0→4.5: 10Y yield at 4.5%+ means REITs must clear that bar to add value.
// Tightened maxPFFO from 15→18: 15 was too tight; well-run REITs (O, VICI) trade 17-22x P/FFO.
// Explicitly zero out weights that don't apply to REITs.
REIT: {
gates: { maxDebtToEquity: 6.0, minQuickRatio: 0.1, maxPERatio: 9999, maxPegGate: 9999 },
weights: { margin: 0, opMargin: 0, roe: 0, peg: 0, revenue: 0, fcf: 0, yield: 5, pFFO: 3 },
thresholds: { minYield: 4.5, maxPFFO: 20 },
},
// Banks: P/E and PEG are distorted by loan loss provisions.
// Price-to-Book is the primary valuation metric.
// Lowered maxPriceToBook from 2.0→1.5: P/B > 1.5 for banks outside crisis recovery is expensive.
// Tightened ROE threshold: 12% is the realistic cost-of-equity for US banks; 10% is break-even.
FINANCIAL: {
gates: {
maxDebtToEquity: 9999,
@@ -87,9 +99,6 @@ export const ScoringRules = {
thresholds: { roeHigh: 15, roeMed: 12, revHigh: 10, revMed: 5 },
},
// Energy: capital-heavy, cyclical. D/E up to 1.5 is normal.
// FCF yield is the primary quality signal (replaces margin); opMargin matters for integrated cos.
// Div yield is scored because energy majors return capital via dividends.
ENERGY: {
gates: { maxDebtToEquity: 1.5, minQuickRatio: 0.6, maxPERatio: 15, maxPegGate: 1.5 },
weights: { margin: 0, opMargin: 3, roe: 2, peg: 1, revenue: 2, fcf: 4, yield: 3 },
@@ -103,8 +112,6 @@ export const ScoringRules = {
},
},
// Healthcare: high R&D burn distorts net margin; focus on revenue growth and FCF.
// P/E can be elevated for pipeline names — gate loosened slightly.
HEALTHCARE: {
gates: { maxDebtToEquity: 1.5, minQuickRatio: 1.0, maxPERatio: 25, maxPegGate: 1.5 },
weights: { margin: 1, opMargin: 2, roe: 2, peg: 2, revenue: 4, fcf: 3 },
@@ -120,11 +127,6 @@ export const ScoringRules = {
},
},
// Communication Services: META, GOOGL, NFLX, DIS, T, VZ.
// Mix of high-margin platform businesses and capital-heavy telcos/media.
// P/E gate at 25: META and GOOGL sustainably trade 20-25x; below 15 is wrong for platforms.
// High FCF weight: platform businesses are judged on FCF (ad revenue converts 35-40% to FCF).
// Revenue growth matters more than for mature industrials — network effects are the moat.
COMMUNICATION: {
gates: { maxDebtToEquity: 2.0, minQuickRatio: 0.8, maxPERatio: 25, maxPegGate: 1.5 },
weights: { margin: 2, opMargin: 3, roe: 2, peg: 2, revenue: 3, fcf: 4 },
@@ -144,10 +146,6 @@ export const ScoringRules = {
},
},
// Consumer Staples: KO, PG, WMT, COST, KR. Slow-growth, recession-resistant.
// Lower revenue growth expectations (2-5% is good for staples).
// Higher margin thresholds — pricing power is the primary moat (not growth).
// D/E tolerance is low — staples should be conservatively financed.
CONSUMER_STAPLES: {
gates: { maxDebtToEquity: 1.5, minQuickRatio: 0.5, maxPERatio: 22, maxPegGate: 2.0 },
weights: { margin: 3, opMargin: 3, roe: 3, peg: 1, revenue: 1, fcf: 3 },
@@ -167,10 +165,6 @@ export const ScoringRules = {
},
},
// Consumer Discretionary: AMZN, HD, MCD, NKE, TSLA. Cyclical, growth-oriented.
// Revenue growth is the primary signal — discretionary spending expands with the economy.
// Margins are thinner than staples (competitive markets); FCF matters for capital return.
// P/E gate relaxed slightly — quality retailers trade at 20-30x on durable FCF.
CONSUMER_DISCRETIONARY: {
gates: { maxDebtToEquity: 2.0, minQuickRatio: 0.5, maxPERatio: 25, maxPegGate: 1.5 },
weights: { margin: 2, opMargin: 2, roe: 2, peg: 2, revenue: 4, fcf: 3 },
@@ -193,24 +187,17 @@ export const ScoringRules = {
},
ETF: {
// Raised expense gate from 0.5→0.2: with so many sub-0.1% index ETFs available,
// a 0.5% expense ratio is genuinely hard to justify except for niche/active strategies.
gates: { maxExpenseRatio: 0.2 },
weights: { yield: 2, lowCost: 4, fiveYearReturn: 2 }, // cost is #1 predictive factor; 5Y return rewards consistency
weights: { yield: 2, lowCost: 4, fiveYearReturn: 2 },
thresholds: {
minYield: 1.5,
maxExpense: 0.05, // 0.05% is achievable for broad market ETFs
minVolume: 1000000, // 1M ADV is the real liquidity floor to avoid slippage
minFiveYearReturn: 8.0, // S&P 500 long-run real return ~7-10%; 8% filters underperformers
maxExpense: 0.05,
minVolume: 1_000_000,
minFiveYearReturn: 8.0,
},
},
BOND: {
// Kept investment-grade floor at BBB — still correct. Below BBB is speculative.
// Raised minSpread from 1.0→1.5: with risk-free at 4.5%, you need >1.5% spread
// to be compensated for credit risk vs just buying Treasuries.
// Tightened maxDuration from 10→7: in a HIGH rate regime, duration > 7 carries
// meaningful rate-sensitivity risk (every 1% rate rise ≈ 7% price loss).
gates: { minCreditRating: 7 }, // BBB = investment-grade floor
weights: { yieldSpread: 3, duration: 2 },
thresholds: { minSpread: 1.5, maxDuration: 7 },
@@ -1,17 +1,19 @@
import type { Signal, AssetType, RateRegime } from '../types.js';
export const SIGNAL = {
STRONG_BUY: '✅ Strong Buy',
MOMENTUM: '⚡ Momentum',
SPECULATION: '⚠️ Speculation',
NEUTRAL: '🔄 Neutral',
AVOID: '❌ Avoid',
};
STRONG_BUY: '✅ Strong Buy' as Signal,
MOMENTUM: '⚡ Momentum' as Signal,
SPECULATION: '⚠️ Speculation' as Signal,
NEUTRAL: '🔄 Neutral' as Signal,
AVOID: '❌ Avoid' as Signal,
} as const;
export const ASSET_TYPE = {
STOCK: 'STOCK',
ETF: 'ETF',
BOND: 'BOND',
STOCK: 'STOCK' as AssetType,
ETF: 'ETF' as AssetType,
BOND: 'BOND' as AssetType,
CRYPTO: 'crypto',
};
} as const;
export const SECTOR = {
TECHNOLOGY: 'TECHNOLOGY',
@@ -23,20 +25,22 @@ export const SECTOR = {
CONSUMER_STAPLES: 'CONSUMER_STAPLES',
CONSUMER_DISCRETIONARY: 'CONSUMER_DISCRETIONARY',
GENERAL: 'GENERAL',
};
} as const;
export type Sector = (typeof SECTOR)[keyof typeof SECTOR];
export const SCORE_MODE = {
FUNDAMENTAL: 'FUNDAMENTAL',
INFLATED: 'INFLATED',
};
} as const;
export const REGIME = {
LOW: 'LOW',
NORMAL: 'NORMAL',
HIGH: 'HIGH',
};
LOW: 'LOW' as RateRegime,
NORMAL: 'NORMAL' as RateRegime,
HIGH: 'HIGH' as RateRegime,
} as const;
export const YAHOO_MODULES = [
export const YAHOO_MODULES: string[] = [
'assetProfile',
'financialData',
'defaultKeyStatistics',
@@ -44,7 +48,7 @@ export const YAHOO_MODULES = [
'summaryDetail',
];
export const SIGNAL_ORDER = {
export const SIGNAL_ORDER: Record<string, number> = {
[SIGNAL.STRONG_BUY]: 0,
[SIGNAL.MOMENTUM]: 1,
[SIGNAL.NEUTRAL]: 2,