232 lines
6.8 KiB
TypeScript
232 lines
6.8 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import type { OptionPrint } from "@islandflow/types";
|
|
import { buildSmartMoneyEventFromPacket } from "../../compute/src/parent-events";
|
|
import {
|
|
buildSyntheticBurstForTest,
|
|
buildSyntheticFlowPacketForTest,
|
|
createSyntheticOptionsAdapter,
|
|
listSyntheticSmartMoneyScenariosForTest,
|
|
updateSyntheticIvForTest
|
|
} from "../src/adapters/synthetic";
|
|
|
|
const totalBurstNotional = (burst: {
|
|
legs: Array<{
|
|
basePrice: number;
|
|
baseSize: number;
|
|
}>;
|
|
cycles: number;
|
|
}): number =>
|
|
burst.legs.reduce((sum, leg) => sum + leg.basePrice * leg.baseSize * burst.cycles * 100, 0);
|
|
|
|
const findBurst = (
|
|
mode: "realistic" | "active",
|
|
scenarioId: string,
|
|
now = Date.UTC(2026, 0, 2)
|
|
) => {
|
|
for (let i = 1; i <= 360; i += 1) {
|
|
const burst = buildSyntheticBurstForTest(i, now + i * 1_000, mode);
|
|
if (burst.scenarioId === scenarioId) {
|
|
return burst;
|
|
}
|
|
}
|
|
throw new Error(`Unable to find synthetic scenario ${scenarioId} in mode ${mode}`);
|
|
};
|
|
|
|
describe("synthetic options burst sizing", () => {
|
|
it("keeps realistic-mode ask-lift accumulation inside the configured notional band", () => {
|
|
const burst = findBurst("realistic", "ask_lift_accumulation");
|
|
|
|
expect(burst.scenarioId).toBe("ask_lift_accumulation");
|
|
expect(totalBurstNotional(burst)).toBeGreaterThanOrEqual(12_000);
|
|
expect(totalBurstNotional(burst)).toBeLessThanOrEqual(90_000);
|
|
});
|
|
|
|
it("keeps active-mode call sweeps inside the configured notional band", () => {
|
|
const burst = findBurst("active", "call_sweep");
|
|
|
|
expect(burst.scenarioId).toBe("call_sweep");
|
|
expect(totalBurstNotional(burst)).toBeGreaterThanOrEqual(70_000);
|
|
expect(totalBurstNotional(burst)).toBeLessThanOrEqual(420_000);
|
|
});
|
|
});
|
|
|
|
describe("synthetic options IV model", () => {
|
|
it("increases under repeated same-contract ask buying", () => {
|
|
let state = updateSyntheticIvForTest(undefined, {
|
|
ts: 1_000,
|
|
placement: "A",
|
|
size: 100,
|
|
notional: 20_000,
|
|
dteDays: 1,
|
|
moneyness: 1.02
|
|
});
|
|
const firstIv = state.iv;
|
|
|
|
state = updateSyntheticIvForTest(state, {
|
|
ts: 1_100,
|
|
placement: "AA",
|
|
size: 300,
|
|
notional: 80_000,
|
|
dteDays: 1,
|
|
moneyness: 1.02
|
|
});
|
|
|
|
expect(state.iv).toBeGreaterThan(firstIv);
|
|
});
|
|
|
|
it("decays after inactivity", () => {
|
|
const active = updateSyntheticIvForTest(undefined, {
|
|
ts: 1_000,
|
|
placement: "AA",
|
|
size: 500,
|
|
notional: 120_000,
|
|
dteDays: 7,
|
|
moneyness: 1.1
|
|
});
|
|
const decayed = updateSyntheticIvForTest(active, {
|
|
ts: 181_000,
|
|
placement: "MID",
|
|
size: 10,
|
|
notional: 1_000,
|
|
dteDays: 7,
|
|
moneyness: 1.1
|
|
});
|
|
|
|
expect(decayed.iv).toBeLessThan(active.iv);
|
|
});
|
|
|
|
it("keeps IV within clamps", () => {
|
|
let state = undefined;
|
|
for (let i = 0; i < 80; i += 1) {
|
|
state = updateSyntheticIvForTest(state, {
|
|
ts: 1_000 + i * 10,
|
|
placement: "AA",
|
|
size: 10_000,
|
|
notional: 5_000_000,
|
|
dteDays: 0,
|
|
moneyness: 1.8
|
|
});
|
|
}
|
|
|
|
expect(state.iv).toBeGreaterThanOrEqual(0.05);
|
|
expect(state.iv).toBeLessThanOrEqual(2.5);
|
|
});
|
|
});
|
|
|
|
describe("synthetic smart-money scenarios", () => {
|
|
it("provides deterministic labeled parent-event templates for all core profiles plus noise", () => {
|
|
const scenarios = listSyntheticSmartMoneyScenariosForTest();
|
|
|
|
expect(scenarios.map((scenario) => scenario.id)).toEqual([
|
|
"institutional_directional",
|
|
"retail_whale",
|
|
"event_driven",
|
|
"vol_seller",
|
|
"arbitrage",
|
|
"hedge_reactive",
|
|
"neutral_noise"
|
|
]);
|
|
});
|
|
|
|
it("scores each labeled scenario as its intended primary profile", () => {
|
|
const now = Date.parse("2026-01-02T15:00:00Z");
|
|
const scenarios = listSyntheticSmartMoneyScenariosForTest().filter(
|
|
(scenario) => scenario.label !== "neutral_noise"
|
|
);
|
|
|
|
for (const scenario of scenarios) {
|
|
const { packet, hiddenLabel } = buildSyntheticFlowPacketForTest(scenario.id, now);
|
|
const event = buildSmartMoneyEventFromPacket(packet);
|
|
const winningScore = event.profile_scores[0];
|
|
const nearbyWrongScores = event.profile_scores.filter(
|
|
(score) => score.profile_id !== scenario.label && score.probability >= 0.5
|
|
);
|
|
|
|
expect(event.abstained, scenario.id).toBe(false);
|
|
expect(event.primary_profile_id, scenario.id).toBe(scenario.label);
|
|
expect(winningScore?.profile_id, scenario.id).toBe(scenario.label);
|
|
expect(winningScore?.probability ?? 0, scenario.id).toBeGreaterThanOrEqual(0.5);
|
|
expect(hiddenLabel.length, scenario.id).toBeGreaterThan(0);
|
|
expect(nearbyWrongScores, scenario.id).toEqual([]);
|
|
}
|
|
});
|
|
|
|
it("covers every smart-money label in active runtime mode over a deterministic sample", () => {
|
|
const seen = new Set<string>();
|
|
const now = Date.parse("2026-01-02T15:00:00Z");
|
|
|
|
for (let i = 1; i <= 120; i += 1) {
|
|
const burst = buildSyntheticBurstForTest(i, now + i * 1_000, "active");
|
|
seen.add(burst.label);
|
|
}
|
|
|
|
expect(seen).toEqual(
|
|
new Set([
|
|
"institutional_directional",
|
|
"retail_whale",
|
|
"event_driven",
|
|
"vol_seller",
|
|
"arbitrage",
|
|
"hedge_reactive",
|
|
"neutral_noise"
|
|
])
|
|
);
|
|
});
|
|
|
|
it("covers every smart-money label in realistic mode within a default twenty-minute window", () => {
|
|
const seen = new Set<string>();
|
|
const now = Date.parse("2026-01-02T15:00:00Z");
|
|
|
|
for (let i = 1; i <= 120; i += 1) {
|
|
const burst = buildSyntheticBurstForTest(i, now + i * 10_000, "realistic");
|
|
seen.add(burst.label);
|
|
}
|
|
|
|
expect(seen).toEqual(
|
|
new Set([
|
|
"institutional_directional",
|
|
"retail_whale",
|
|
"event_driven",
|
|
"vol_seller",
|
|
"arbitrage",
|
|
"hedge_reactive",
|
|
"neutral_noise"
|
|
])
|
|
);
|
|
});
|
|
|
|
it("keeps neutral background noise below the emission threshold", () => {
|
|
const { packet } = buildSyntheticFlowPacketForTest(
|
|
"neutral_noise",
|
|
Date.parse("2026-01-02T15:00:00Z")
|
|
);
|
|
|
|
const event = buildSmartMoneyEventFromPacket(packet);
|
|
|
|
expect(event.abstained).toBe(true);
|
|
expect(event.primary_profile_id).toBeNull();
|
|
expect(event.profile_scores[0]?.probability ?? 1).toBeLessThan(0.42);
|
|
});
|
|
|
|
it("does not expose hidden labels on emitted option prints", async () => {
|
|
const adapter = createSyntheticOptionsAdapter({
|
|
emitIntervalMs: 1,
|
|
mode: "active"
|
|
});
|
|
const trades: OptionPrint[] = [];
|
|
const stop = adapter.start({
|
|
onTrade: (trade) => {
|
|
trades.push(trade);
|
|
}
|
|
});
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
stop();
|
|
|
|
expect(trades.length).toBeGreaterThan(0);
|
|
for (const trade of trades) {
|
|
expect("hiddenLabel" in trade).toBe(false);
|
|
expect("label" in trade).toBe(false);
|
|
}
|
|
});
|
|
});
|