feat: dealer flow, mirror portfolio (M21), options convexity, FINRA short interest, alert producers, vendor gate
CI / Test & Type-Check (push) Canceled after 0s
CI / Test & Type-Check (push) Canceled after 0s
Snapshot of in-progress module work across multiple slices: - Dealer Flow: dealerExposureEngine, dealerMapService, dealerMapExplain, dealerMapIntegrity, dealerMapReplay, dealerStudyEngine, hanStyleLevels - Mirror Portfolio (M21): fundRepository, captureIngest, mirrorAlertProducers, fund holdings strip, live book, position capture ingest - Options: BSM, NormalizedOptionSurface types, OptionsChainRouter, ConvexityGate, option legs panel - Alert producers: vixLevel, rotation, thesis, unlock, portfolioRisk, mirror (fund_capture, fund_13f, mirror_diff) - FINRA short interest adapter + queue integration - SEC company tickers adapter + ingest (symbol search index seed) - Vendor gate (rate-limit-first data plane, ADR-0009) - CUSIP registry, reverse 13F refresh, stock float service - LRU cache, portfolio backtest engine - Frontend: dealer-flow, funds, journal, lab, monitor, plan, portfolio, reports, screener, strategies, theses, guided-start, exits, more pages - Volume profile, workspace profile, visibility-aware poll - ADRs 0010 (mirror math not advice), 0011 (symbol search index) - VENDOR_INTEGRATIONS.md, END_USER_TEST.md - .gitignore: exclude DBs, .DS_Store, local config, agent scratch
This commit is contained in:
@@ -0,0 +1,420 @@
|
||||
import { test } from 'node:test';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import {
|
||||
buildDealerMap,
|
||||
contractGex,
|
||||
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('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);
|
||||
});
|
||||
Reference in New Issue
Block a user