Detect option rolls and emit roll classifier
This commit is contained in:
parent
fe6aef5fbc
commit
a82db56ab6
7 changed files with 378 additions and 16 deletions
|
|
@ -194,6 +194,32 @@ describe("classifier structure and positioning signals", () => {
|
|||
expect(hits.some((hit) => hit.classifier_id === "ladder_accumulation")).toBe(true);
|
||||
});
|
||||
|
||||
test("roll classifier triggers on cross-expiry structure packets", () => {
|
||||
const packet = buildPacket({
|
||||
packet_kind: "structure",
|
||||
structure_type: "roll",
|
||||
structure_legs: 2,
|
||||
structure_strikes: 2,
|
||||
structure_rights: "C",
|
||||
structure_strike_span: 5,
|
||||
total_premium: 70_000,
|
||||
total_size: 800,
|
||||
nbbo_coverage_ratio: 0.85,
|
||||
nbbo_aggressive_buy_ratio: 0.7,
|
||||
nbbo_aggressive_sell_ratio: 0.3,
|
||||
roll_from_expiry: "2025-01-17",
|
||||
roll_to_expiry: "2025-02-21",
|
||||
roll_from_strike: 450,
|
||||
roll_to_strike: 455,
|
||||
roll_strike_delta: 5,
|
||||
roll_expiry_days_delta: 35
|
||||
});
|
||||
const hits = evaluateClassifiers(packet, baseConfig);
|
||||
const hit = hits.find((candidate) => candidate.classifier_id === "roll_up_down_out");
|
||||
expect(hit).toBeTruthy();
|
||||
expect(hit?.direction).toBe("bullish");
|
||||
});
|
||||
|
||||
test("far-dated conviction triggers on 60DTE threshold", () => {
|
||||
const packet = buildPacket({
|
||||
option_contract_id: "SPY-2024-04-19-450-C",
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ const leg = (input: Partial<LegEvidence> & Pick<LegEvidence, "contractId" | "rig
|
|||
return {
|
||||
contractId: input.contractId,
|
||||
root: "SPY",
|
||||
expiry: "2025-01-17",
|
||||
expiry: input.expiry ?? "2025-01-17",
|
||||
right: input.right,
|
||||
strike: input.strike,
|
||||
startTs: input.startTs ?? 1000,
|
||||
|
|
@ -134,4 +134,43 @@ describe("structure packet planning", () => {
|
|||
// 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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -40,4 +40,20 @@ describe("structure summaries", () => {
|
|||
expect(summary?.type).toBe("strangle");
|
||||
expect(summary?.strikes).toBe(2);
|
||||
});
|
||||
|
||||
test("detects rolls across expiries", () => {
|
||||
const summary = summarizeStructure([
|
||||
{
|
||||
...leg("c1", "C", 450),
|
||||
expiry: "2025-01-17"
|
||||
},
|
||||
{
|
||||
...leg("c2", "C", 455),
|
||||
expiry: "2025-02-21"
|
||||
}
|
||||
]);
|
||||
expect(summary?.type).toBe("roll");
|
||||
expect(summary?.rights).toBe("C");
|
||||
expect(summary?.strikes).toBe(2);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue