islandflow/services/compute/tests/structure-packets.test.ts
dirtydishes 9076d3b395 Add synthetic print and structure features
- Export synthetic market types
- Track special print conditions and derived cluster features
- Add same-size leg symmetry to structure packets
2026-05-13 22:36:13 -04:00

177 lines
5.3 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { summarizeStructure } from "../src/structures";
import {
buildStructureFlowPacket,
planStructurePacket,
shouldEmitStructurePacket,
type LegEvidence
} from "../src/structure-packets";
const placements = (overrides?: Partial<LegEvidence["placements"]>): LegEvidence["placements"] => ({
aa: 0,
a: 0,
b: 0,
bb: 0,
mid: 0,
missing: 0,
stale: 0,
...overrides
});
const leg = (input: Partial<LegEvidence> & Pick<LegEvidence, "contractId" | "right" | "strike">): LegEvidence => {
return {
contractId: input.contractId,
root: "SPY",
expiry: input.expiry ?? "2025-01-17",
right: input.right,
strike: input.strike,
startTs: input.startTs ?? 1000,
endTs: input.endTs ?? 1100,
members: input.members ?? [input.contractId],
totalSize: input.totalSize ?? 100,
totalPremium: input.totalPremium ?? 1000,
placements: input.placements ?? placements(),
source_ts: input.source_ts ?? 1000,
ingest_ts: input.ingest_ts ?? 1200,
seq: input.seq ?? 1
};
};
describe("structure packet planning", () => {
test("emits only on latest leg endTs", () => {
const call = leg({
contractId: "SPY-2025-01-17-450-C",
right: "C",
strike: 450,
endTs: 1100
});
const put = leg({
contractId: "SPY-2025-01-17-450-P",
right: "P",
strike: 450,
endTs: 1125
});
const legs: LegEvidence[] = [call, put];
expect(shouldEmitStructurePacket(legs, call.contractId)).toBe(false);
expect(shouldEmitStructurePacket(legs, put.contractId)).toBe(true);
});
test("plans deterministic id + members across legs", () => {
const call = leg({
contractId: "SPY-2025-01-17-450-C",
right: "C",
strike: 450,
members: ["p2", "p1"],
totalSize: 20,
totalPremium: 4000,
placements: placements({ aa: 1, mid: 1 })
});
const put = leg({
contractId: "SPY-2025-01-17-450-P",
right: "P",
strike: 450,
startTs: 1005,
endTs: 1120,
members: ["p3"],
totalSize: 10,
totalPremium: 1500,
placements: placements({ bb: 1 })
});
const legs = [call, put];
const summary = summarizeStructure(legs);
expect(summary?.type).toBe("straddle");
const plan = planStructurePacket(legs, summary!, 500);
expect(plan).not.toBeNull();
expect(plan!.pseudoContractId).toBe("SPY-2025-01-17-STRUCT-straddle");
expect(plan!.id.startsWith("flowpacket:SPY-2025-01-17-STRUCT-straddle:")).toBe(true);
expect(plan!.members).toEqual(["p1", "p2", "p3"]);
expect(plan!.totalSize).toBe(30);
expect(plan!.totalPremium).toBe(5500);
expect(plan!.count).toBe(3);
const swappedPlan = planStructurePacket([put, call], summary!, 500);
expect(swappedPlan).not.toBeNull();
expect(swappedPlan!.id).toBe(plan!.id);
expect(swappedPlan!.members).toEqual(plan!.members);
});
test("builds structure FlowPacket with aggregate aggressor ratios", () => {
const call = leg({
contractId: "SPY-2025-01-17-450-C",
right: "C",
strike: 450,
members: ["p1", "p2"],
totalSize: 20,
totalPremium: 4000,
placements: placements({ aa: 1, mid: 1 })
});
const put = leg({
contractId: "SPY-2025-01-17-450-P",
right: "P",
strike: 450,
members: ["p3"],
totalSize: 10,
totalPremium: 1500,
placements: placements({ bb: 1 })
});
const legs = [call, put];
const summary = summarizeStructure(legs);
const plan = planStructurePacket(legs, summary!, 500);
const packet = buildStructureFlowPacket(plan!, summary!);
expect(packet.features.packet_kind).toBe("structure");
expect(packet.features.underlying_id).toBe("SPY");
expect(packet.features.nbbo_aa_count).toBe(1);
expect(packet.features.nbbo_bb_count).toBe(1);
expect(packet.features.nbbo_mid_count).toBe(1);
expect(packet.features.nbbo_coverage_ratio).toBeCloseTo(1, 6);
expect(packet.features.same_size_leg_symmetry).toBeCloseTo(0.5, 4);
// 2 aggressive (AA + BB) out of 3 classified (AA + BB + MID)
expect(packet.features.nbbo_aggressive_ratio).toBeCloseTo(2 / 3, 4);
});
test("includes roll metadata when structure type is roll", () => {
const near = leg({
contractId: "SPY-2025-01-17-450-C",
right: "C",
strike: 450,
expiry: "2025-01-17",
members: ["p1"],
totalSize: 10,
totalPremium: 2000,
placements: placements({ aa: 1 })
});
const far = leg({
contractId: "SPY-2025-02-21-455-C",
right: "C",
strike: 455,
expiry: "2025-02-21",
startTs: 1010,
endTs: 1120,
members: ["p2"],
totalSize: 12,
totalPremium: 2500,
placements: placements({ bb: 1 })
});
const legs = [near, far];
const summary = summarizeStructure(legs);
expect(summary?.type).toBe("roll");
const plan = planStructurePacket(legs, summary!, 500);
const packet = buildStructureFlowPacket(plan!, summary!);
expect(packet.features.structure_expiries_count).toBe(2);
expect(packet.features.roll_from_expiry).toBe("2025-01-17");
expect(packet.features.roll_to_expiry).toBe("2025-02-21");
expect(packet.features.roll_from_strike).toBe(450);
expect(packet.features.roll_to_strike).toBe(455);
expect(packet.features.roll_strike_delta).toBe(5);
});
});