import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { gradeStudySetup, proposeStudySetups, type CandleBar, } from '../dealerStudyEngine.ts'; import type { DealerMap } from '../dealerExposureEngine.ts'; function baseMap(over: Partial = {}): DealerMap { return { symbol: 'TEST', spot: 100, asOf: '2026-08-01T00:00:00.000Z', delayNote: 'delayed', delaySeconds: 900, oiFreshness: 'prior_session', providerId: 'yfinance', convention: 'classic_retail', unit: 'usd', computation: 'settled_oi', expiries: ['2026-08-15'], cells: [], byStrike: [], netVexByStrike: [], levels: { king: { strike: 105, expiry: '2026-08-15', gex: 1e6 }, gatekeepers: [], flip: 98, callWall: 108, putWall: 95, airPockets: [], clusters: [], }, regimeAtSpot: 'positive', scenario: 'gex_dominates', patterns: [], opexHints: { isNearOpex: false, note: '' }, provenance: { source: 'test' } as DealerMap['provenance'], coveragePct: 90, bsmFillPct: 80, ...over, }; } describe('proposeStudySetups', () => { it('proposes cushion pin when positive regime and king away from spot', () => { const props = proposeStudySetups(baseMap()); assert.ok(props.some((p) => p.hypothesisId === 'cushion_pin_toward_king')); assert.ok(props.every((p) => p.disclaimer === 'Educational observation only.')); }); it('proposes spring setup when negative regime', () => { const props = proposeStudySetups(baseMap({ regimeAtSpot: 'negative' })); assert.ok(props.some((p) => p.hypothesisId === 'spring_accelerate_from_spot')); }); }); describe('gradeStudySetup', () => { const loggedAt = '2026-08-01T00:00:00.000Z'; it('grades long win when target before invalidation', () => { const bars: CandleBar[] = [ { ts: '2026-08-02', o: 100, h: 101, l: 99, c: 100.5 }, { ts: '2026-08-03', o: 100.5, h: 106, l: 100, c: 105 }, ]; const r = gradeStudySetup( { bias: 'educational_long', entry: 100, invalidation: 95, target: 105, horizonDays: 5, loggedAt, }, bars, new Date('2026-08-10'), ); assert.equal(r.grade, 'win'); assert.ok(r.score >= 55); }); it('grades long loss when invalidation first', () => { const bars: CandleBar[] = [ { ts: '2026-08-02', o: 100, h: 100.5, l: 94, c: 95 }, { ts: '2026-08-03', o: 95, h: 110, l: 94, c: 108 }, ]; const r = gradeStudySetup( { bias: 'educational_long', entry: 100, invalidation: 95, target: 105, horizonDays: 5, loggedAt, }, bars, new Date('2026-08-10'), ); assert.equal(r.grade, 'loss'); }); it('grades range win if held inside edges', () => { const bars: CandleBar[] = [ { ts: '2026-08-02', o: 100, h: 102, l: 98, c: 100 }, { ts: '2026-08-03', o: 100, h: 103, l: 97, c: 101 }, { ts: '2026-08-04', o: 101, h: 102, l: 99, c: 100 }, { ts: '2026-08-05', o: 100, h: 101, l: 99, c: 100 }, { ts: '2026-08-06', o: 100, h: 102, l: 98, c: 100 }, ]; const r = gradeStudySetup( { bias: 'educational_range', entry: 100, invalidation: 95, target: 105, horizonDays: 5, loggedAt, }, bars, new Date('2026-08-10'), ); assert.equal(r.grade, 'win'); }); it('returns incomplete when no bars', () => { const r = gradeStudySetup( { bias: 'educational_long', entry: 100, invalidation: 95, target: 105, horizonDays: 5, loggedAt, }, [], new Date('2026-08-10'), ); assert.equal(r.grade, 'incomplete'); }); });