2026-08-10 13:36:26 -04:00
|
|
|
|
import { test } from 'node:test';
|
|
|
|
|
|
import { strict as assert } from 'node:assert';
|
|
|
|
|
|
import {
|
|
|
|
|
|
buildDealerMap,
|
|
|
|
|
|
contractGex,
|
2026-08-18 14:10:02 -04:00
|
|
|
|
contractVex,
|
2026-08-10 13:36:26 -04:00
|
|
|
|
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);
|
|
|
|
|
|
});
|
2026-08-18 14:10:02 -04:00
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
});
|
2026-08-10 13:36:26 -04:00
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
});
|