phase-7: code restructure
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { MarketRegime } from '../server/services/MarketRegime';
|
||||
import { SECTOR, ASSET_TYPE } from '../server/config/constants';
|
||||
import type { Benchmarks, RateRegime } from '../server/types';
|
||||
|
||||
const regime = (benchmarks: Partial<Benchmarks>, extra: { rateRegime?: RateRegime } = {}) =>
|
||||
new MarketRegime({ benchmarks: benchmarks as Benchmarks, ...extra });
|
||||
|
||||
test('stock inflated P/E = marketPE × 1.5', () => {
|
||||
const { gates } = regime({ marketPE: 24 }).getInflatedOverrides(ASSET_TYPE.STOCK, SECTOR.GENERAL);
|
||||
assert.equal(gates.maxPERatio, Math.round(24 * 1.5)); // 36
|
||||
});
|
||||
|
||||
test('tech inflated P/E = techPE × 1.3', () => {
|
||||
const { gates } = regime({ techPE: 40 }).getInflatedOverrides(
|
||||
ASSET_TYPE.STOCK,
|
||||
SECTOR.TECHNOLOGY,
|
||||
);
|
||||
assert.equal(gates.maxPERatio, Math.round(40 * 1.3)); // 52
|
||||
});
|
||||
|
||||
test('REIT inflated minYield = reitYield × 0.85 in NORMAL rate regime', () => {
|
||||
const { thresholds } = regime({ reitYield: 4.0 }, { rateRegime: 'NORMAL' }).getInflatedOverrides(
|
||||
ASSET_TYPE.STOCK,
|
||||
SECTOR.REIT,
|
||||
);
|
||||
assert.equal(thresholds.minYield, +(4.0 * 0.85).toFixed(2)); // 3.40
|
||||
});
|
||||
|
||||
test('REIT inflated minYield = reitYield × 0.95 in HIGH rate regime', () => {
|
||||
const { thresholds } = regime({ reitYield: 4.0 }, { rateRegime: 'HIGH' }).getInflatedOverrides(
|
||||
ASSET_TYPE.STOCK,
|
||||
SECTOR.REIT,
|
||||
);
|
||||
assert.equal(thresholds.minYield, +(4.0 * 0.95).toFixed(2)); // 3.80
|
||||
});
|
||||
|
||||
test('bond inflated minSpread = igSpread × 0.80 in NORMAL rate regime', () => {
|
||||
const { thresholds } = regime({ igSpread: 1.5 }, { rateRegime: 'NORMAL' }).getInflatedOverrides(
|
||||
ASSET_TYPE.BOND,
|
||||
SECTOR.GENERAL,
|
||||
);
|
||||
assert.equal(thresholds.minSpread, +(1.5 * 0.8).toFixed(2)); // 1.20
|
||||
});
|
||||
|
||||
test('bond inflated minSpread = igSpread × 0.90 in HIGH rate regime', () => {
|
||||
const { thresholds } = regime({ igSpread: 1.5 }, { rateRegime: 'HIGH' }).getInflatedOverrides(
|
||||
ASSET_TYPE.BOND,
|
||||
SECTOR.GENERAL,
|
||||
);
|
||||
assert.equal(thresholds.minSpread, +(1.5 * 0.9).toFixed(2)); // 1.35
|
||||
});
|
||||
|
||||
test('GENERAL stock P/E multiplier compresses to 1.2× in HIGH rate regime', () => {
|
||||
const { gates } = regime({ marketPE: 25 }, { rateRegime: 'HIGH' }).getInflatedOverrides(
|
||||
ASSET_TYPE.STOCK,
|
||||
SECTOR.GENERAL,
|
||||
);
|
||||
assert.equal(gates.maxPERatio, Math.round(25 * 1.2)); // 30
|
||||
});
|
||||
|
||||
test('ETF inflated loosens expense gate to 0.75', () => {
|
||||
const { gates } = regime({}).getInflatedOverrides(ASSET_TYPE.ETF);
|
||||
assert.equal(gates.maxExpenseRatio, 0.75);
|
||||
});
|
||||
|
||||
test('falls back to defaults when benchmarks missing', () => {
|
||||
const { gates } = new MarketRegime({}).getInflatedOverrides(ASSET_TYPE.STOCK, SECTOR.GENERAL);
|
||||
assert.equal(gates.maxPERatio, Math.round(22 * 1.5)); // default marketPE = 22
|
||||
});
|
||||
Reference in New Issue
Block a user