phase-7: code restructure
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { RuleMerger } from '../server/services/RuleMerger';
|
||||
import { SCORE_MODE } from '../server/config/constants';
|
||||
import type { MarketContext } from '../server/types';
|
||||
|
||||
const ctx = {
|
||||
benchmarks: { marketPE: 25, techPE: 32, reitYield: 3.8, igSpread: 1.2 },
|
||||
} as Partial<MarketContext>;
|
||||
|
||||
test('FUNDAMENTAL mode returns Graham-style P/E gate', () => {
|
||||
const rules = RuleMerger.getRulesForAsset(
|
||||
'STOCK',
|
||||
{ sector: 'GENERAL' },
|
||||
ctx as MarketContext,
|
||||
SCORE_MODE.FUNDAMENTAL,
|
||||
);
|
||||
assert.equal(rules.gates.maxPERatio, 15); // updated: Graham's real rule is 15x
|
||||
assert.equal(rules.gates.maxPegGate, 1.0); // updated: Lynch PEG standard
|
||||
});
|
||||
|
||||
test('INFLATED mode loosens P/E gate from live SPY data', () => {
|
||||
const rules = RuleMerger.getRulesForAsset(
|
||||
'STOCK',
|
||||
{ sector: 'GENERAL' },
|
||||
ctx as MarketContext,
|
||||
SCORE_MODE.INFLATED,
|
||||
);
|
||||
assert.equal(rules.gates.maxPERatio, Math.round(25 * 1.5)); // 37
|
||||
assert(rules.gates.maxPERatio > 15, 'Inflated P/E should exceed fundamental 15x');
|
||||
});
|
||||
|
||||
test('INFLATED tech P/E gate uses XLK benchmark', () => {
|
||||
const rules = RuleMerger.getRulesForAsset(
|
||||
'STOCK',
|
||||
{ sector: 'TECHNOLOGY' },
|
||||
ctx as MarketContext,
|
||||
SCORE_MODE.INFLATED,
|
||||
);
|
||||
assert.equal(rules.gates.maxPERatio, Math.round(32 * 1.3)); // 42
|
||||
});
|
||||
|
||||
test('Sector override applied before inflated overrides', () => {
|
||||
const rules = RuleMerger.getRulesForAsset(
|
||||
'STOCK',
|
||||
{ sector: 'REIT' },
|
||||
ctx as MarketContext,
|
||||
SCORE_MODE.FUNDAMENTAL,
|
||||
);
|
||||
assert.equal(rules.gates.maxPERatio, 9999);
|
||||
assert.equal(rules.weights.yield, 5);
|
||||
assert.equal(rules.weights.margin, 0);
|
||||
});
|
||||
|
||||
test('SECTOR_OVERRIDE is deleted from returned rules', () => {
|
||||
const rules = RuleMerger.getRulesForAsset(
|
||||
'STOCK',
|
||||
{ sector: 'GENERAL' },
|
||||
ctx as MarketContext,
|
||||
SCORE_MODE.FUNDAMENTAL,
|
||||
) as unknown as Record<string, unknown>;
|
||||
assert.equal(rules.SECTOR_OVERRIDE, undefined);
|
||||
});
|
||||
|
||||
test('throws for unknown asset type', () => {
|
||||
assert.throws(
|
||||
() => RuleMerger.getRulesForAsset('CRYPTO' as never, {}, ctx as MarketContext),
|
||||
/No rules configured/,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user