Files
investor-flow/app/server/src/analysis/__tests__/dealerExposureEngine.test.ts
T
Investor Flow Build 90e1829d39
CI / Test (push) Canceled after 0s
CI / Build and push (push) Canceled after 0s
feat: Unraid deploy, dealer-flow heatmap, confluence zones, 13F capture
Ship Node production images, Unraid compose, and Gitea CI/CD (test then
push registry images; cron script if no runner). Rebuild dealer flow as a
heatmap-first map with integrity gates and chart helpers. Add confluence
zone rules, session clock, capture evidence, and tighter 13F/queue/options
paths, plus the matching UI and tests.
2026-08-18 14:10:02 -04:00

429 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import {
buildDealerMap,
contractGex,
contractVex,
isDegradedDealerMap,
isHealthyDealerMap,
isSaneIv,
layer0Educator,
resolveGreeks,
type DealerMap,
} from '../dealerExposureEngine.ts';
import type { NormalizedOptionSurface, NormalizedOptionRow } from '../../options/types.ts';
function row(partial: Partial<NormalizedOptionRow> & Pick<NormalizedOptionRow, 'strike' | 'expiry' | 'right'>): NormalizedOptionRow {
return {
openInterest: 1000,
volume: 100,
bid: 1,
ask: 1.1,
mid: 1.05,
iv: 0.25,
greeks: { gamma: 0.02, vanna: 0.1, delta: 0.5 },
...partial,
};
}
function surface(rows: NormalizedOptionRow[], spot = 100): NormalizedOptionSurface {
return {
symbol: 'TEST',
spot,
asOf: '2026-08-06T15:00:00.000Z',
providerId: 'synthetic',
delaySeconds: 900,
oiFreshness: 'prior_session',
rows,
provenance: {
fetchedAt: '2026-08-06T15:00:00.000Z',
sourceKind: 'yfinance',
rawSourceId: 'synthetic:TEST',
},
};
}
test('contractGex: call positive, put negative', () => {
const call = contractGex(0.01, 100, 100, 'call');
const put = contractGex(0.01, 100, 100, 'put');
assert.ok(call > 0);
assert.ok(put < 0);
assert.equal(call, -put);
});
test('contractVex uses the same S² notional as GEX', () => {
const gex = contractGex(0.02, 1000, 44, 'call');
const vex = contractVex(0.02, 1000, 44, 'call');
assert.equal(vex, gex);
assert.equal(contractVex(0.02, 1000, 44, 'put'), -vex);
});
test('buildDealerMap: expiries sorted nearest-left and capped', () => {
const rows: NormalizedOptionRow[] = [];
for (const exp of ['2026-08-08', '2026-08-15', '2026-08-22', '2026-09-19', '2026-10-17', '2026-11-21', '2026-12-19', '2027-01-16']) {
rows.push(row({ strike: 100, expiry: exp, right: 'call', openInterest: 500 }));
rows.push(row({ strike: 100, expiry: exp, right: 'put', openInterest: 400, greeks: { gamma: 0.015, vanna: -0.05 } }));
}
const map = buildDealerMap(surface(rows), { maxExpiries: 4 });
assert.equal(map.expiries.length, 4);
assert.equal(map.expiries[0], '2026-08-08');
assert.ok(map.expiries.every((e, i) => i === 0 || e >= map.expiries[i - 1]));
assert.equal(map.providerId, 'synthetic');
assert.equal(map.convention, 'classic_call_pos_put_neg');
assert.match(map.delayNote, /prior session/i);
});
test('buildDealerMap: king is max absolute GEX cell', () => {
const map = buildDealerMap(
surface([
row({ strike: 95, expiry: '2026-08-15', right: 'put', openInterest: 2000, greeks: { gamma: 0.03, vanna: 0.05 } }),
row({ strike: 100, expiry: '2026-08-15', right: 'call', openInterest: 500, greeks: { gamma: 0.02, vanna: 0.05 } }),
row({ strike: 105, expiry: '2026-08-15', right: 'call', openInterest: 5000, greeks: { gamma: 0.04, vanna: 0.05 } }),
]),
);
assert.ok(map.levels.king);
assert.equal(map.levels.king!.strike, 105);
assert.ok(map.cells.some((c) => c.isKing && c.strike === 105));
});
test('buildDealerMap: skips rows without OI', () => {
const map = buildDealerMap(
surface([
row({ strike: 100, expiry: '2026-08-15', right: 'call', openInterest: null }),
row({ strike: 100, expiry: '2026-08-15', right: 'put', openInterest: 0 }),
row({ strike: 101, expiry: '2026-08-15', right: 'call', openInterest: 1000 }),
]),
);
assert.ok(map.byStrike.some((s) => s.strike === 101));
assert.ok(!map.byStrike.some((s) => s.strike === 100));
assert.ok(map.coveragePct < 100);
});
test('buildDealerMap: BSM fill when gamma missing but IV present', () => {
// Asymmetric OI so call/put GEX does not cancel to zero at the strike.
const map = buildDealerMap(
surface([
row({
strike: 100,
expiry: '2026-09-15',
right: 'call',
openInterest: 5000,
iv: 0.3,
greeks: null,
}),
row({
strike: 100,
expiry: '2026-09-15',
right: 'put',
openInterest: 1000,
iv: 0.3,
greeks: null,
}),
]),
);
assert.ok(map.cells.length >= 1);
assert.ok(map.bsmFillPct > 0);
assert.ok(map.byStrike.some((s) => s.absGex > 0), `byStrike=${JSON.stringify(map.byStrike)}`);
});
test('withExposureConvention: dealer_inventory negates classic GEX and keeps king strike', async () => {
const { withExposureConvention } = await import('../dealerExposureEngine.ts');
const classic = buildDealerMap(
surface([
row({ strike: 100, expiry: '2026-08-15', right: 'call', openInterest: 5000, greeks: { gamma: 0.04, vanna: 0.05 } }),
row({ strike: 95, expiry: '2026-08-15', right: 'put', openInterest: 2000, greeks: { gamma: 0.03, vanna: 0.05 } }),
], 100),
);
assert.equal(classic.convention, 'classic_call_pos_put_neg');
const dealer = withExposureConvention(classic, 'dealer_inventory');
assert.equal(dealer.convention, 'dealer_inventory');
assert.ok(classic.levels.king && dealer.levels.king);
assert.equal(classic.levels.king!.strike, dealer.levels.king!.strike);
assert.ok(
Math.abs(classic.levels.king!.gex + dealer.levels.king!.gex) < 1e-6,
'king gex should flip sign',
);
for (const c of classic.cells) {
const d = dealer.cells.find((x) => x.strike === c.strike && x.expiry === c.expiry);
assert.ok(d);
assert.ok(Math.abs(c.gex + d!.gex) < 1e-6, `cell ${c.strike} gex should flip`);
}
// Round-trip
const back = withExposureConvention(dealer, 'classic_call_pos_put_neg');
assert.equal(back.convention, 'classic_call_pos_put_neg');
assert.ok(Math.abs(back.levels.king!.gex - classic.levels.king!.gex) < 1e-6);
});
test('buildDealerMap: flip between +GEX and −GEX strikes', () => {
// Strong + below and − above (or vice versa) to create a sign change
const map = buildDealerMap(
surface([
row({ strike: 90, expiry: '2026-08-15', right: 'call', openInterest: 8000, greeks: { gamma: 0.03, vanna: 0.1 } }),
row({ strike: 95, expiry: '2026-08-15', right: 'call', openInterest: 6000, greeks: { gamma: 0.03, vanna: 0.1 } }),
row({ strike: 105, expiry: '2026-08-15', right: 'put', openInterest: 8000, greeks: { gamma: 0.03, vanna: 0.1 } }),
row({ strike: 110, expiry: '2026-08-15', right: 'put', openInterest: 6000, greeks: { gamma: 0.03, vanna: 0.1 } }),
], 100),
);
// puts contribute negative GEX, calls positive → flip exists between clusters
assert.ok(map.levels.flip != null, 'expected a polarity flip');
});
test('buildDealerMap: detects rug_setup pattern', () => {
// +GEX above spot, large −GEX pivot below, more −GEX stack
const map = buildDealerMap(
surface([
row({ strike: 110, expiry: '2026-08-15', right: 'call', openInterest: 10000, greeks: { gamma: 0.05, vanna: 0.1 } }),
row({ strike: 95, expiry: '2026-08-15', right: 'put', openInterest: 12000, greeks: { gamma: 0.05, vanna: 0.1 } }),
row({ strike: 90, expiry: '2026-08-15', right: 'put', openInterest: 9000, greeks: { gamma: 0.04, vanna: 0.1 } }),
row({ strike: 85, expiry: '2026-08-15', right: 'put', openInterest: 8000, greeks: { gamma: 0.04, vanna: 0.1 } }),
row({ strike: 100, expiry: '2026-08-15', right: 'call', openInterest: 500, greeks: { gamma: 0.01, vanna: 0.05 } }),
], 100),
);
assert.ok(
map.patterns.some((p) => p.id === 'rug_setup'),
`patterns=${JSON.stringify(map.patterns)}`,
);
});
test('buildDealerMap: detects whipsaw with air pocket between edges', () => {
// Strong nodes at 90 and 110, near-zero middle
const rows: NormalizedOptionRow[] = [
row({ strike: 90, expiry: '2026-08-15', right: 'call', openInterest: 15000, greeks: { gamma: 0.05, vanna: 0.1 } }),
row({ strike: 110, expiry: '2026-08-15', right: 'call', openInterest: 15000, greeks: { gamma: 0.05, vanna: 0.1 } }),
];
for (const k of [95, 98, 100, 102, 105]) {
rows.push(
row({
strike: k,
expiry: '2026-08-15',
right: 'call',
openInterest: 10,
greeks: { gamma: 0.0001, vanna: 0.001 },
}),
);
}
const map = buildDealerMap(surface(rows, 100));
assert.ok(map.levels.airPockets.length >= 1, 'expected air pocket');
assert.ok(
map.patterns.some((p) => p.id === 'whipsaw'),
`patterns=${JSON.stringify(map.patterns)} scenario=${map.scenario}`,
);
});
test('buildDealerMap: velocity growing near air pocket', () => {
const curr = buildDealerMap(
surface([
row({ strike: 90, expiry: '2026-08-15', right: 'call', openInterest: 5000, greeks: { gamma: 0.04, vanna: 0.1 } }),
row({ strike: 100, expiry: '2026-08-15', right: 'call', openInterest: 50, greeks: { gamma: 0.001, vanna: 0.01 } }),
row({ strike: 110, expiry: '2026-08-15', right: 'call', openInterest: 5000, greeks: { gamma: 0.04, vanna: 0.1 } }),
]),
);
const prior: Pick<DealerMap, 'asOf' | 'byStrike' | 'levels'> = {
asOf: '2026-08-05T15:00:00.000Z',
byStrike: curr.byStrike.map((s) =>
s.strike === 90 ? { ...s, absGex: s.absGex * 0.3, gex: s.gex * 0.3 } : { ...s, absGex: s.absGex * 0.9, gex: s.gex * 0.9 },
),
levels: curr.levels,
};
const withVel = buildDealerMap(
surface([
row({ strike: 90, expiry: '2026-08-15', right: 'call', openInterest: 5000, greeks: { gamma: 0.04, vanna: 0.1 } }),
row({ strike: 100, expiry: '2026-08-15', right: 'call', openInterest: 50, greeks: { gamma: 0.001, vanna: 0.01 } }),
row({ strike: 110, expiry: '2026-08-15', right: 'call', openInterest: 5000, greeks: { gamma: 0.04, vanna: 0.1 } }),
]),
{ prior },
);
assert.ok(withVel.velocity);
assert.ok(withVel.velocity!.nodes.some((n) => n.growing));
});
test('buildDealerMap: OPEX Friday banner within 5 days', () => {
// 2026-08-07 is a Friday
const map = buildDealerMap(
surface([
row({ strike: 100, expiry: '2026-08-07', right: 'call', openInterest: 1000 }),
]),
);
assert.equal(map.opexHints.isNearOpex, true);
assert.match(map.opexHints.note, /OPEX|Near-term/i);
});
test('layer0Educator: no buy/sell imperatives', () => {
const map = buildDealerMap(
surface([
row({ strike: 100, expiry: '2026-08-15', right: 'call', openInterest: 3000 }),
row({ strike: 95, expiry: '2026-08-15', right: 'put', openInterest: 3000 }),
]),
);
const copy = layer0Educator(map);
const blob = JSON.stringify(copy).toLowerCase();
assert.equal(copy.footer, 'Educational observation only.');
assert.ok(!/\bbuy\b/.test(blob) || blob.includes('buying pressure') === false);
// Primary rule: no "you should buy/sell"
assert.ok(!/you should (buy|sell)/i.test(blob));
assert.ok(!/\b(buy|sell) (calls|puts|shares|stock)\b/i.test(blob));
assert.ok(copy.questions.length >= 3);
assert.ok(copy.caveats.some((c) => /prior session|delay/i.test(c)));
});
test('buildDealerMap: engine has no vendor coupling (synthetic only path)', () => {
// Sanity: providerId synthetic preserved; computation local_engine
const map = buildDealerMap(
surface([row({ strike: 50, expiry: '2026-08-20', right: 'call', openInterest: 100 })]),
);
assert.equal(map.computation, 'local_engine');
assert.equal(map.unit, 'usd_delta_adj_gamma_notional');
assert.ok(map.quality);
assert.ok(['ok', 'degraded', 'poor'].includes(map.quality.greekQuality));
});
test('isSaneIv: rejects premarket junk and absurd IV', () => {
assert.equal(isSaneIv(1e-5), false);
assert.equal(isSaneIv(0.02), false);
assert.equal(isSaneIv(0.25), true);
assert.equal(isSaneIv(4.0), false);
assert.equal(isSaneIv(null), false);
});
test('resolveGreeks: skips junk IV without vendor gamma', () => {
const r = resolveGreeks(
row({
strike: 100,
expiry: '2026-09-15',
right: 'call',
openInterest: 1000,
iv: 1e-5,
greeks: null,
}),
100,
'2026-08-07T13:14:00.000Z',
0.04,
0,
);
assert.equal(r.skippedBadIv, true);
assert.equal(r.gamma, 0);
});
test('resolveGreeks: uses fallback IV hint when current IV is junk', () => {
const r = resolveGreeks(
row({
strike: 100,
expiry: '2026-09-15',
right: 'call',
openInterest: 5000,
iv: 1e-5,
greeks: null,
}),
100,
'2026-08-07T13:14:00.000Z',
0.04,
0,
{ fallbackIv: 0.35 },
);
assert.equal(r.skippedBadIv, false);
assert.ok(r.gamma > 0);
assert.equal(r.usedFallbackIv, true);
assert.equal(r.usedBsm, true);
});
test('buildDealerMap: junk IV rows skipped; quality poor when most OI unusable', () => {
// No sane IV on the surface → no ATM proxy → skip
const map = buildDealerMap(
surface([
row({
strike: 100,
expiry: '2026-09-15',
right: 'call',
openInterest: 9000,
iv: 1e-5,
greeks: null,
}),
row({
strike: 100,
expiry: '2026-09-15',
right: 'put',
openInterest: 8000,
iv: 0.01,
greeks: null,
}),
row({
strike: 105,
expiry: '2026-09-15',
right: 'call',
openInterest: 100,
iv: 0.02,
greeks: null,
}),
]),
);
assert.ok(map.quality.skippedBadIv >= 2, `skipped=${map.quality.skippedBadIv}`);
assert.equal(map.quality.greekQuality, 'poor');
assert.ok(isDegradedDealerMap(map));
assert.equal(isHealthyDealerMap(map), false);
});
test('buildDealerMap: ATM IV proxy fills junk rows when some sane IV exists', () => {
const map = buildDealerMap(
surface([
row({
strike: 100,
expiry: '2026-09-15',
right: 'call',
openInterest: 5000,
iv: 1e-5,
greeks: null,
}),
row({
strike: 100,
expiry: '2026-09-15',
right: 'put',
openInterest: 1000,
iv: 1e-5,
greeks: null,
}),
row({
strike: 101,
expiry: '2026-09-15',
right: 'call',
openInterest: 200,
iv: 0.28,
greeks: null,
}),
]),
);
assert.ok(map.byStrike.some((s) => s.absGex > 0));
assert.ok(map.quality.fallbackIvUses >= 1 || map.quality.included >= 1);
});
test('buildDealerMap: ivHints restore structure when live IV is junk', () => {
const map = buildDealerMap(
surface([
row({
strike: 100,
expiry: '2026-09-15',
right: 'call',
openInterest: 5000,
iv: 1e-5,
greeks: null,
}),
row({
strike: 100,
expiry: '2026-09-15',
right: 'put',
openInterest: 1000,
iv: 1e-5,
greeks: null,
}),
]),
{
ivHints: {
'2026-09-15|100|call': 0.3,
'2026-09-15|100|put': 0.3,
},
},
);
assert.ok(map.byStrike.some((s) => s.absGex > 0));
assert.ok(map.quality.fallbackIvUses >= 1);
assert.ok(map.quality.greekQuality !== 'poor' || map.cells.length > 0);
});