import type { ClassifierHit, FlowPacket } from "@islandflow/types"; import { parseContractId, type ParsedContract } from "./contracts"; export type ClassifierConfig = { sweepMinPremium: number; sweepMinCount: number; sweepMinPremiumZ: number; spikeMinPremium: number; spikeMinSize: number; spikeMinPremiumZ: number; spikeMinSizeZ: number; zMinSamples: number; minNbboCoverage: number; minAggressorRatio: number; zeroDteMaxAtmPct: number; zeroDteMinPremium: number; zeroDteMinSize: number; }; const MS_PER_DAY = 86_400_000; const clamp = (value: number, min = 0, max = 1): number => { if (!Number.isFinite(value)) { return min; } return Math.max(min, Math.min(max, value)); }; const formatUsd = (value: number): string => { if (!Number.isFinite(value)) { return "$0"; } return `$${value.toFixed(2)}`; }; const getNumberFeature = (packet: FlowPacket, key: string): number => { const value = packet.features[key]; return typeof value === "number" && Number.isFinite(value) ? value : 0; }; const getStringFeature = (packet: FlowPacket, key: string): string => { const value = packet.features[key]; return typeof value === "string" ? value : ""; }; const formatPct = (value: number): string => `${Math.round(value * 100)}%`; const formatPctPrecise = (value: number, digits = 2): string => { if (!Number.isFinite(value)) { return "0%"; } return `${(value * 100).toFixed(digits)}%`; }; const getAggressorContext = ( packet: FlowPacket ): { coverage: number; aggressiveBuyRatio: number; aggressiveSellRatio: number; aggressiveRatio: number; } => { return { coverage: getNumberFeature(packet, "nbbo_coverage_ratio"), aggressiveBuyRatio: getNumberFeature(packet, "nbbo_aggressive_buy_ratio"), aggressiveSellRatio: getNumberFeature(packet, "nbbo_aggressive_sell_ratio"), aggressiveRatio: getNumberFeature(packet, "nbbo_aggressive_ratio") }; }; const applyAggressorAdjustment = ( confidence: number, coverage: number, aggressiveRatio: number, config: ClassifierConfig ): { confidence: number; note: string } => { if (!Number.isFinite(coverage) || coverage <= 0) { return { confidence, note: "Aggressor mix unavailable (no NBBO coverage)." }; } let adjusted = confidence; if (coverage >= config.minNbboCoverage) { if (aggressiveRatio >= config.minAggressorRatio) { adjusted += 0.05; } else { adjusted -= 0.1; } } const note = `Aggressor mix ${formatPct(aggressiveRatio)} aggressive, NBBO coverage ${formatPct( coverage )}.`; return { confidence: adjusted, note }; }; type LargeActivity = { count: number; totalPremium: number; totalSize: number; windowMs: number; premiumZ: number; sizeZ: number; premiumBaselineReady: boolean; sizeBaselineReady: boolean; passesAbsolute: boolean; passesZ: boolean; baselineNote: string; }; const getLargeActivity = (packet: FlowPacket, config: ClassifierConfig): LargeActivity => { const count = getNumberFeature(packet, "count"); const totalPremium = getNumberFeature(packet, "total_premium"); const totalSize = getNumberFeature(packet, "total_size"); const windowMs = getNumberFeature(packet, "window_ms"); const premiumZ = getNumberFeature(packet, "total_premium_z"); const sizeZ = getNumberFeature(packet, "total_size_z"); const premiumBaseline = getNumberFeature(packet, "total_premium_baseline_n"); const sizeBaseline = getNumberFeature(packet, "total_size_baseline_n"); const premiumBaselineReady = premiumBaseline >= config.zMinSamples; const sizeBaselineReady = sizeBaseline >= config.zMinSamples; const passesAbsolute = totalSize >= config.spikeMinSize && totalPremium >= config.spikeMinPremium; const passesZ = (premiumBaselineReady && premiumZ >= config.spikeMinPremiumZ) || (sizeBaselineReady && sizeZ >= config.spikeMinSizeZ); const baselineNote = premiumBaselineReady || sizeBaselineReady ? `Baseline z-scores: premium ${premiumZ.toFixed(2)}, size ${sizeZ.toFixed(2)}.` : "Baseline z-scores unavailable."; return { count, totalPremium, totalSize, windowMs, premiumZ, sizeZ, premiumBaselineReady, sizeBaselineReady, passesAbsolute, passesZ, baselineNote }; }; const applySideAggressorAdjustment = ( confidence: number, coverage: number, ratio: number, config: ClassifierConfig, label: string ): { confidence: number; note: string } => { const normalizedCoverage = clamp(coverage, 0, 1); const normalizedRatio = clamp(ratio, 0, 1); let adjusted = confidence; if (normalizedCoverage <= 0) { return { confidence: adjusted - 0.15, note: "Aggressor mix unavailable (no NBBO coverage)." }; } if (normalizedCoverage < config.minNbboCoverage) { adjusted -= 0.1; } if (normalizedRatio >= config.minAggressorRatio) { adjusted += 0.05; } else { adjusted -= 0.1; } const note = `Aggressor mix ${formatPct(normalizedRatio)} ${label}, NBBO coverage ${formatPct( normalizedCoverage )}.`; return { confidence: adjusted, note }; }; const getReferenceTs = (packet: FlowPacket): number | null => { const endTs = getNumberFeature(packet, "end_ts"); if (endTs > 0) { return endTs; } if (Number.isFinite(packet.source_ts) && packet.source_ts > 0) { return packet.source_ts; } return null; }; const getReferenceDay = (packet: FlowPacket): string | null => { const referenceTs = getReferenceTs(packet); if (!referenceTs) { return null; } return new Date(referenceTs).toISOString().slice(0, 10); }; const getDteDays = (packet: FlowPacket, contract: ParsedContract): number | null => { const expiryTs = Date.parse(`${contract.expiry}T00:00:00Z`); if (!Number.isFinite(expiryTs)) { return null; } const referenceTs = getReferenceTs(packet); if (!referenceTs) { return null; } const diffMs = expiryTs - referenceTs; if (diffMs < 0) { return null; } return Math.ceil(diffMs / MS_PER_DAY); }; const buildSweepHit = ( packet: FlowPacket, contract: ParsedContract, direction: "bullish" | "bearish", config: ClassifierConfig ): ClassifierHit | null => { const count = getNumberFeature(packet, "count"); const totalPremium = getNumberFeature(packet, "total_premium"); const totalSize = getNumberFeature(packet, "total_size"); const firstPrice = getNumberFeature(packet, "first_price"); const lastPrice = getNumberFeature(packet, "last_price"); const windowMs = getNumberFeature(packet, "window_ms"); const premiumZ = getNumberFeature(packet, "total_premium_z"); const premiumBaseline = getNumberFeature(packet, "total_premium_baseline_n"); const coverage = getNumberFeature(packet, "nbbo_coverage_ratio"); const aggressiveBuyRatio = getNumberFeature(packet, "nbbo_aggressive_buy_ratio"); const aggressiveSellRatio = getNumberFeature(packet, "nbbo_aggressive_sell_ratio"); const aggressiveRatio = Math.max(aggressiveBuyRatio, aggressiveSellRatio); const baselineReady = premiumBaseline >= config.zMinSamples; const passesAbsolute = totalPremium >= config.sweepMinPremium; const passesZ = baselineReady && premiumZ >= config.sweepMinPremiumZ; if (count < config.sweepMinCount || (!passesAbsolute && !passesZ)) { return null; } const priceDelta = lastPrice - firstPrice; const priceTrend = priceDelta >= 0 ? "up" : "down"; let confidence = 0.55; if (priceDelta >= 0) { confidence += 0.1; } if (count >= config.sweepMinCount + 2) { confidence += 0.1; } if (totalPremium >= config.sweepMinPremium * 2) { confidence += 0.15; } if (passesZ) { confidence += 0.1; if (premiumZ >= config.sweepMinPremiumZ + 1) { confidence += 0.05; } } const aggressor = applyAggressorAdjustment(confidence, coverage, aggressiveRatio, config); confidence = clamp(aggressor.confidence, 0, 0.95); const baselineNote = baselineReady ? `Baseline premium z-score ${premiumZ.toFixed(2)} over ${Math.round(premiumBaseline)} samples.` : "Baseline premium z-score unavailable."; return { classifier_id: direction === "bullish" ? "large_bullish_call_sweep" : "large_bearish_put_sweep", confidence, direction, explanations: [ `Likely ${direction === "bullish" ? "call" : "put"} sweep: ${count} prints in ${Math.round(windowMs)}ms for ${packet.features.option_contract_id ?? packet.id}.`, `Premium ${formatUsd(totalPremium)} across ${Math.round(totalSize)} contracts; price ${priceTrend}.`, `Thresholds: >=${config.sweepMinCount} prints and >=${formatUsd(config.sweepMinPremium)} premium or z>=${config.sweepMinPremiumZ.toFixed(1)}.`, baselineNote, aggressor.note ] }; }; const buildSpikeHit = (packet: FlowPacket, config: ClassifierConfig): ClassifierHit | null => { const activity = getLargeActivity(packet, config); const { coverage, aggressiveBuyRatio, aggressiveSellRatio } = getAggressorContext(packet); const aggressiveRatio = Math.max(aggressiveBuyRatio, aggressiveSellRatio); if (!activity.passesAbsolute && !activity.passesZ) { return null; } let confidence = 0.5; if (activity.totalSize >= config.spikeMinSize * 2) { confidence += 0.15; } if (activity.totalPremium >= config.spikeMinPremium * 2) { confidence += 0.15; } if (activity.count >= 3) { confidence += 0.1; } if (activity.passesZ) { confidence += 0.1; if ( activity.premiumZ >= config.spikeMinPremiumZ + 1 || activity.sizeZ >= config.spikeMinSizeZ + 1 ) { confidence += 0.05; } } const aggressor = applyAggressorAdjustment(confidence, coverage, aggressiveRatio, config); confidence = clamp(aggressor.confidence, 0, 0.9); return { classifier_id: "unusual_contract_spike", confidence, direction: "neutral", explanations: [ `Unusual contract spike: ${activity.count} prints in ${Math.round(activity.windowMs)}ms for ${packet.features.option_contract_id ?? packet.id}.`, `Premium ${formatUsd(activity.totalPremium)} across ${Math.round(activity.totalSize)} contracts.`, `Thresholds: >=${config.spikeMinSize} contracts and >=${formatUsd(config.spikeMinPremium)} premium or z>=${config.spikeMinPremiumZ.toFixed(1)}.`, activity.baselineNote, aggressor.note ] }; }; const buildOverwriteHit = ( packet: FlowPacket, contract: ParsedContract, config: ClassifierConfig ): ClassifierHit | null => { if (contract.right !== "C") { return null; } const activity = getLargeActivity(packet, config); if (!activity.passesAbsolute && !activity.passesZ) { return null; } const { coverage, aggressiveSellRatio } = getAggressorContext(packet); let confidence = 0.45; if (activity.totalPremium >= config.spikeMinPremium * 2) { confidence += 0.15; } if (activity.totalSize >= config.spikeMinSize * 2) { confidence += 0.1; } if (activity.count >= 3) { confidence += 0.05; } if (activity.passesZ) { confidence += 0.1; } const aggressor = applySideAggressorAdjustment( confidence, coverage, aggressiveSellRatio, config, "sell-side" ); confidence = clamp(aggressor.confidence, 0, 0.9); return { classifier_id: "large_call_sell_overwrite", confidence, direction: "bearish", explanations: [ `Likely call overwrite: ${activity.count} prints in ${Math.round(activity.windowMs)}ms for ${packet.features.option_contract_id ?? packet.id}.`, `Premium ${formatUsd(activity.totalPremium)} across ${Math.round(activity.totalSize)} contracts.`, `Thresholds: >=${config.spikeMinSize} contracts and >=${formatUsd(config.spikeMinPremium)} premium or z>=${config.spikeMinPremiumZ.toFixed(1)}.`, "Direction inferred from sell-side aggressor mix.", activity.baselineNote, aggressor.note ] }; }; const buildPutWriteHit = ( packet: FlowPacket, contract: ParsedContract, config: ClassifierConfig ): ClassifierHit | null => { if (contract.right !== "P") { return null; } const activity = getLargeActivity(packet, config); if (!activity.passesAbsolute && !activity.passesZ) { return null; } const { coverage, aggressiveSellRatio } = getAggressorContext(packet); let confidence = 0.45; if (activity.totalPremium >= config.spikeMinPremium * 2) { confidence += 0.15; } if (activity.totalSize >= config.spikeMinSize * 2) { confidence += 0.1; } if (activity.count >= 3) { confidence += 0.05; } if (activity.passesZ) { confidence += 0.1; } const aggressor = applySideAggressorAdjustment( confidence, coverage, aggressiveSellRatio, config, "sell-side" ); confidence = clamp(aggressor.confidence, 0, 0.9); return { classifier_id: "large_put_sell_write", confidence, direction: "bullish", explanations: [ `Likely put write: ${activity.count} prints in ${Math.round(activity.windowMs)}ms for ${packet.features.option_contract_id ?? packet.id}.`, `Premium ${formatUsd(activity.totalPremium)} across ${Math.round(activity.totalSize)} contracts.`, `Thresholds: >=${config.spikeMinSize} contracts and >=${formatUsd(config.spikeMinPremium)} premium or z>=${config.spikeMinPremiumZ.toFixed(1)}.`, "Direction inferred from sell-side aggressor mix.", activity.baselineNote, aggressor.note ] }; }; const buildStraddleStrangleHit = ( packet: FlowPacket, config: ClassifierConfig ): ClassifierHit | null => { const structureType = getStringFeature(packet, "structure_type"); if (structureType !== "straddle" && structureType !== "strangle") { return null; } const activity = getLargeActivity(packet, config); const { coverage, aggressiveBuyRatio, aggressiveSellRatio, aggressiveRatio } = getAggressorContext(packet); const structureLegs = getNumberFeature(packet, "structure_legs"); const structureStrikes = getNumberFeature(packet, "structure_strikes"); const strikeSpan = getNumberFeature(packet, "structure_strike_span"); let confidence = 0.45; if (activity.totalPremium >= config.spikeMinPremium) { confidence += 0.1; } if (activity.totalSize >= config.spikeMinSize) { confidence += 0.05; } if (structureLegs >= 4) { confidence += 0.05; } const aggressor = applyAggressorAdjustment(confidence, coverage, aggressiveRatio, config); confidence = clamp(aggressor.confidence, 0, 0.85); let volBias = "mixed aggressor skew"; if (aggressiveBuyRatio >= aggressiveSellRatio + 0.1) { volBias = "buy-side skew suggests long volatility"; } else if (aggressiveSellRatio >= aggressiveBuyRatio + 0.1) { volBias = "sell-side skew suggests short volatility"; } const skewNote = `Aggressor skew: buy ${formatPct(aggressiveBuyRatio)}, sell ${formatPct( aggressiveSellRatio )}; ${volBias}.`; return { classifier_id: structureType === "straddle" ? "straddle" : "strangle", confidence, direction: "neutral", explanations: [ `Likely ${structureType}: ${structureLegs} legs across ${structureStrikes} strikes (span ${strikeSpan}).`, `Premium ${formatUsd(activity.totalPremium)} across ${Math.round(activity.totalSize)} contracts.`, skewNote, aggressor.note ] }; }; const buildVerticalSpreadHit = ( packet: FlowPacket, config: ClassifierConfig ): ClassifierHit | null => { const structureType = getStringFeature(packet, "structure_type"); if (structureType !== "vertical") { return null; } const structureRights = getStringFeature(packet, "structure_rights"); if (structureRights !== "C" && structureRights !== "P") { return null; } const activity = getLargeActivity(packet, config); const { coverage, aggressiveBuyRatio, aggressiveSellRatio } = getAggressorContext(packet); const structureLegs = getNumberFeature(packet, "structure_legs"); const structureStrikes = getNumberFeature(packet, "structure_strikes"); const strikeSpan = getNumberFeature(packet, "structure_strike_span"); let confidence = 0.5; if (activity.totalPremium >= config.spikeMinPremium) { confidence += 0.1; } if (activity.totalSize >= config.spikeMinSize) { confidence += 0.05; } if (structureLegs >= 3) { confidence += 0.05; } let direction: "bullish" | "bearish" | "neutral" = "neutral"; let biasNote = "Debit/credit bias unclear (insufficient aggressor data)."; let aggressorNote = "Aggressor mix unavailable (no NBBO coverage)."; const hasAggressor = coverage > 0 && aggressiveBuyRatio + aggressiveSellRatio > 0; if (hasAggressor) { const buyDominant = aggressiveBuyRatio >= aggressiveSellRatio; const dominantRatio = buyDominant ? aggressiveBuyRatio : aggressiveSellRatio; const label = buyDominant ? "buy-side" : "sell-side"; const aggressor = applySideAggressorAdjustment( confidence, coverage, dominantRatio, config, label ); confidence = aggressor.confidence; aggressorNote = aggressor.note; const spreadBias = buyDominant ? "debit" : "credit"; biasNote = `Aggressor skew: buy ${formatPct(aggressiveBuyRatio)}, sell ${formatPct( aggressiveSellRatio )}; suggests ${spreadBias} ${structureRights === "C" ? "call" : "put"} vertical.`; if (structureRights === "C") { direction = buyDominant ? "bullish" : "bearish"; } else { direction = buyDominant ? "bearish" : "bullish"; } } else { confidence -= 0.1; } confidence = clamp(confidence, 0, 0.85); return { classifier_id: "vertical_spread", confidence, direction, explanations: [ `Likely vertical spread: ${structureLegs} legs across ${structureStrikes} strikes (span ${strikeSpan}).`, `Premium ${formatUsd(activity.totalPremium)} across ${Math.round(activity.totalSize)} contracts.`, biasNote, aggressorNote, "Direction inferred from debit/credit bias." ] }; }; const buildLadderHit = ( packet: FlowPacket, config: ClassifierConfig ): ClassifierHit | null => { const structureType = getStringFeature(packet, "structure_type"); if (structureType !== "ladder") { return null; } const activity = getLargeActivity(packet, config); const { coverage, aggressiveRatio } = getAggressorContext(packet); const structureRights = getStringFeature(packet, "structure_rights"); const structureLegs = getNumberFeature(packet, "structure_legs"); const structureStrikes = getNumberFeature(packet, "structure_strikes"); const strikeSpan = getNumberFeature(packet, "structure_strike_span"); const qualifies = activity.totalPremium >= config.spikeMinPremium || activity.totalSize >= config.spikeMinSize || activity.passesZ; if (!qualifies) { return null; } let confidence = 0.45; if (activity.totalPremium >= config.spikeMinPremium * 2) { confidence += 0.1; } if (activity.totalSize >= config.spikeMinSize * 2) { confidence += 0.1; } if (structureStrikes >= 4) { confidence += 0.05; } if (activity.passesZ) { confidence += 0.05; } const aggressor = applyAggressorAdjustment(confidence, coverage, aggressiveRatio, config); confidence = clamp(aggressor.confidence, 0, 0.85); let direction: "bullish" | "bearish" | "neutral" = "neutral"; if (structureRights === "C") { direction = "bullish"; } else if (structureRights === "P") { direction = "bearish"; } return { classifier_id: "ladder_accumulation", confidence, direction, explanations: [ `Likely multi-strike ladder accumulation: ${structureLegs} legs across ${structureStrikes} strikes (span ${strikeSpan}).`, `Premium ${formatUsd(activity.totalPremium)} across ${Math.round(activity.totalSize)} contracts.`, `Thresholds: ladder structure plus >=${config.spikeMinSize} contracts or >=${formatUsd(config.spikeMinPremium)} premium.`, "Direction inferred from call/put ladder.", activity.baselineNote, aggressor.note ] }; }; const buildRollHit = (packet: FlowPacket, config: ClassifierConfig): ClassifierHit | null => { const structureType = getStringFeature(packet, "structure_type"); if (structureType !== "roll") { return null; } const structureRights = getStringFeature(packet, "structure_rights"); if (structureRights !== "C" && structureRights !== "P") { return null; } const activity = getLargeActivity(packet, config); const qualifies = activity.totalPremium >= config.spikeMinPremium || activity.totalSize >= config.spikeMinSize; if (!qualifies) { return null; } const { coverage, aggressiveBuyRatio, aggressiveSellRatio, aggressiveRatio } = getAggressorContext(packet); const fromExpiry = getStringFeature(packet, "roll_from_expiry") || ""; const toExpiry = getStringFeature(packet, "roll_to_expiry") || ""; const getOptionalNumber = (key: string): number | null => { const value = packet.features[key]; return typeof value === "number" && Number.isFinite(value) ? value : null; }; const fromStrike = getOptionalNumber("roll_from_strike"); const toStrike = getOptionalNumber("roll_to_strike"); const strikeDelta = getOptionalNumber("roll_strike_delta") ?? 0; const expiryDaysDelta = getOptionalNumber("roll_expiry_days_delta"); const hasStrikePair = fromStrike !== null && toStrike !== null; const hasExpiryPair = Boolean(fromExpiry) && Boolean(toExpiry); let rollFlavor = "roll out"; if (hasStrikePair) { if (strikeDelta > 0.0001) { rollFlavor = "roll out and up"; } else if (strikeDelta < -0.0001) { rollFlavor = "roll out and down"; } } let direction: "bullish" | "bearish" | "neutral" = "neutral"; if (hasStrikePair) { if (structureRights === "C") { direction = strikeDelta > 0.0001 ? "bullish" : strikeDelta < -0.0001 ? "bearish" : "neutral"; } else { direction = strikeDelta > 0.0001 ? "bearish" : strikeDelta < -0.0001 ? "bullish" : "neutral"; } } let confidence = 0.5; if (activity.totalPremium >= config.spikeMinPremium * 2) { confidence += 0.1; } if (activity.totalSize >= config.spikeMinSize * 2) { confidence += 0.05; } if (hasStrikePair && Math.abs(strikeDelta) > 0.0001) { confidence += 0.05; } if (hasExpiryPair && expiryDaysDelta !== null && expiryDaysDelta >= 7) { confidence += 0.05; } const aggressor = applyAggressorAdjustment(confidence, coverage, aggressiveRatio, config); confidence = clamp(aggressor.confidence, 0, 0.85); const expiryNote = hasExpiryPair ? `Expiries: ${fromExpiry} -> ${toExpiry}${ expiryDaysDelta !== null && expiryDaysDelta !== 0 ? ` (${Math.round(expiryDaysDelta)}d)` : "" }.` : "Expiry pairing unavailable."; const strikeNote = hasStrikePair ? `Strikes: ${fromStrike} -> ${toStrike} (delta ${strikeDelta}).` : "Strike pairing unavailable."; const skewNote = `Aggressor skew: buy ${formatPct(aggressiveBuyRatio)}, sell ${formatPct( aggressiveSellRatio )}.`; return { classifier_id: "roll_up_down_out", confidence, direction, explanations: [ `Consistent with ${rollFlavor}: ${activity.count} prints in ${Math.round(activity.windowMs)}ms for ${packet.features.underlying_id ?? packet.id}.`, expiryNote, strikeNote, `Premium ${formatUsd(activity.totalPremium)} across ${Math.round(activity.totalSize)} contracts.`, `Thresholds: >=${config.spikeMinSize} contracts or >=${formatUsd(config.spikeMinPremium)} premium.`, skewNote, aggressor.note ] }; }; const buildFarDatedHit = ( packet: FlowPacket, contract: ParsedContract, config: ClassifierConfig ): ClassifierHit | null => { const dteDays = getDteDays(packet, contract); if (!dteDays || dteDays < 60) { return null; } const activity = getLargeActivity(packet, config); if (!activity.passesAbsolute && !activity.passesZ) { return null; } const { coverage, aggressiveRatio } = getAggressorContext(packet); let confidence = 0.5; if (dteDays >= 90) { confidence += 0.05; } if (activity.totalPremium >= config.spikeMinPremium * 2) { confidence += 0.1; } if (activity.totalSize >= config.spikeMinSize * 2) { confidence += 0.05; } if (activity.passesZ) { confidence += 0.1; } const aggressor = applyAggressorAdjustment(confidence, coverage, aggressiveRatio, config); confidence = clamp(aggressor.confidence, 0, 0.85); return { classifier_id: "far_dated_conviction", confidence, direction: contract.right === "C" ? "bullish" : "bearish", explanations: [ `Likely far-dated ${contract.right === "C" ? "call" : "put"} positioning: ${dteDays} DTE for ${packet.features.option_contract_id ?? packet.id}.`, `Premium ${formatUsd(activity.totalPremium)} across ${Math.round(activity.totalSize)} contracts.`, `Thresholds: DTE >=60 and >=${config.spikeMinSize} contracts or >=${formatUsd(config.spikeMinPremium)} premium (or z-scores).`, "Direction inferred from call/put right.", activity.baselineNote, aggressor.note ] }; }; const buildZeroDteGammaPunchHit = ( packet: FlowPacket, contract: ParsedContract, config: ClassifierConfig ): ClassifierHit | null => { const referenceDay = getReferenceDay(packet); if (!referenceDay || contract.expiry !== referenceDay) { return null; } const activity = getLargeActivity(packet, config); if ( activity.totalPremium < config.zeroDteMinPremium || activity.totalSize < config.zeroDteMinSize ) { return null; } const underlyingMid = getNumberFeature(packet, "underlying_mid"); if (!Number.isFinite(underlyingMid) || underlyingMid <= 0) { return null; } const strike = contract.strike; const atmPct = Math.abs(strike - underlyingMid) / underlyingMid; if (atmPct > config.zeroDteMaxAtmPct) { return null; } const { coverage, aggressiveRatio } = getAggressorContext(packet); let confidence = 0.55; if (atmPct <= config.zeroDteMaxAtmPct * 0.5) { confidence += 0.05; } if (activity.totalPremium >= config.zeroDteMinPremium * 2) { confidence += 0.1; } if (activity.totalSize >= config.zeroDteMinSize * 2) { confidence += 0.05; } const aggressor = applyAggressorAdjustment(confidence, coverage, aggressiveRatio, config); confidence = clamp(aggressor.confidence, 0, 0.9); return { classifier_id: "zero_dte_gamma_punch", confidence, direction: contract.right === "C" ? "bullish" : "bearish", explanations: [ `Likely 0DTE gamma punch: ${packet.features.option_contract_id ?? packet.id} near ATM.`, `Underlying mid ${formatUsd(underlyingMid)}, strike ${formatUsd(strike)} (${formatPctPrecise(atmPct)} from ATM).`, `Premium ${formatUsd(activity.totalPremium)} across ${Math.round(activity.totalSize)} contracts.`, `Thresholds: DTE=0, ATM <=${formatPctPrecise(config.zeroDteMaxAtmPct)}, >=${formatUsd( config.zeroDteMinPremium )} premium, >=${config.zeroDteMinSize} contracts.`, activity.baselineNote, aggressor.note ] }; }; export const evaluateClassifiers = ( packet: FlowPacket, config: ClassifierConfig ): ClassifierHit[] => { const packetKind = getStringFeature(packet, "packet_kind"); const structureOnly = packetKind === "structure"; const contractId = typeof packet.features.option_contract_id === "string" ? packet.features.option_contract_id : ""; const contract = structureOnly ? null : parseContractId(contractId); const hits: ClassifierHit[] = []; if (structureOnly) { const structureHit = buildStraddleStrangleHit(packet, config); if (structureHit) { hits.push(structureHit); } const verticalHit = buildVerticalSpreadHit(packet, config); if (verticalHit) { hits.push(verticalHit); } const ladderHit = buildLadderHit(packet, config); if (ladderHit) { hits.push(ladderHit); } const rollHit = buildRollHit(packet, config); if (rollHit) { hits.push(rollHit); } return hits; } if (!structureOnly) { if (contract?.right === "C") { const hit = buildSweepHit(packet, contract, "bullish", config); if (hit) { hits.push(hit); } } if (contract?.right === "P") { const hit = buildSweepHit(packet, contract, "bearish", config); if (hit) { hits.push(hit); } } const spikeHit = buildSpikeHit(packet, config); if (spikeHit) { hits.push(spikeHit); } if (contract) { const overwriteHit = buildOverwriteHit(packet, contract, config); if (overwriteHit) { hits.push(overwriteHit); } const putWriteHit = buildPutWriteHit(packet, contract, config); if (putWriteHit) { hits.push(putWriteHit); } const farDatedHit = buildFarDatedHit(packet, contract, config); if (farDatedHit) { hits.push(farDatedHit); } const zeroDteHit = buildZeroDteGammaPunchHit(packet, contract, config); if (zeroDteHit) { hits.push(zeroDteHit); } } } return hits; };