Emit structure flow packets with full evidence
This commit is contained in:
parent
f08abec68a
commit
fe6aef5fbc
6 changed files with 538 additions and 54 deletions
137
services/compute/tests/structure-packets.test.ts
Normal file
137
services/compute/tests/structure-packets.test.ts
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
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: "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);
|
||||
|
||||
// 2 aggressive (AA + BB) out of 3 classified (AA + BB + MID)
|
||||
expect(packet.features.nbbo_aggressive_ratio).toBeCloseTo(2 / 3, 4);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue