import { SmartMoneyEventSchema, type ClassifierHit, type FlowPacket, type SmartMoneyDirection, type SmartMoneyEvent, type SmartMoneyFeatures, type SmartMoneyProfileId, type SmartMoneyProfileScore } from "@islandflow/types"; import type { EventCalendarMatch } from "@islandflow/refdata/event-calendar"; import { parseContractId } from "./contracts"; const MS_PER_DAY = 86_400_000; const SPECIAL_CONDITIONS = new Set(["AUCTION", "CROSS", "OPENING", "CLOSING", "COMPLEX", "SPREAD"]); const clamp = (value: number, min = 0, max = 1): number => { if (!Number.isFinite(value)) { return min; } return Math.max(min, Math.min(max, value)); }; const numberFeature = (packet: FlowPacket, key: string): number => { const value = packet.features[key]; return typeof value === "number" && Number.isFinite(value) ? value : 0; }; const stringFeature = (packet: FlowPacket, key: string): string => { const value = packet.features[key]; return typeof value === "string" ? value : ""; }; const boolFeature = (packet: FlowPacket, key: string): boolean | null => { const value = packet.features[key]; return typeof value === "boolean" ? value : null; }; const confidenceBand = (probability: number): SmartMoneyProfileScore["confidence_band"] => { if (probability >= 0.72) { return "high"; } if (probability >= 0.52) { return "medium"; } return "low"; }; const score = ( profile_id: SmartMoneyProfileId, probability: number, direction: SmartMoneyDirection, reasons: string[] ): SmartMoneyProfileScore => ({ profile_id, probability: clamp(probability), confidence_band: confidenceBand(probability), direction, reasons }); const getReferenceTs = (packet: FlowPacket): number => { return numberFeature(packet, "end_ts") || packet.source_ts; }; const getDteDays = (packet: FlowPacket): number | null => { const contract = parseContractId(stringFeature(packet, "option_contract_id")); if (!contract) { return null; } const expiryTs = Date.parse(`${contract.expiry}T00:00:00Z`); if (!Number.isFinite(expiryTs)) { return null; } const diff = expiryTs - getReferenceTs(packet); return diff >= 0 ? Math.ceil(diff / MS_PER_DAY) : null; }; const inferDirection = (packet: FlowPacket): SmartMoneyDirection => { const structureRights = stringFeature(packet, "structure_rights"); const optionType = stringFeature(packet, "option_type") || parseContractId(stringFeature(packet, "option_contract_id"))?.right; const buy = numberFeature(packet, "nbbo_aggressive_buy_ratio"); const sell = numberFeature(packet, "nbbo_aggressive_sell_ratio"); const sellDominant = sell >= buy + 0.12; if (structureRights === "C") { return sellDominant ? "bearish" : "bullish"; } if (structureRights === "P") { return sellDominant ? "bullish" : "bearish"; } if (optionType === "C") { return sellDominant ? "bearish" : "bullish"; } if (optionType === "P") { return sellDominant ? "bullish" : "bearish"; } return "neutral"; }; export type SmartMoneyParentEventOptions = { eventCalendarMatch?: EventCalendarMatch | null; }; const buildFeatures = (packet: FlowPacket, options: SmartMoneyParentEventOptions = {}): SmartMoneyFeatures => { const contractId = stringFeature(packet, "option_contract_id"); const contract = parseContractId(contractId); const underlyingMid = numberFeature(packet, "underlying_mid"); const quoteAge = numberFeature(packet, "nbbo_age_ms") || numberFeature(packet, "underlying_quote_age_ms"); const printCount = Math.max(0, Math.round(numberFeature(packet, "count") || packet.members.length)); const staleCount = numberFeature(packet, "nbbo_stale_count"); const missingCount = numberFeature(packet, "nbbo_missing_count"); const structureLegs = Math.max(0, Math.round(numberFeature(packet, "structure_legs"))); const strikeCount = Math.max(1, Math.round(numberFeature(packet, "structure_strikes") || (contract ? 1 : 0))); const specialCount = numberFeature(packet, "special_print_count"); const calendarEventTs = options.eventCalendarMatch?.event_ts ?? null; const eventTs = calendarEventTs ?? numberFeature(packet, "corporate_event_ts"); const referenceTs = getReferenceTs(packet); const expiryTs = contract ? Date.parse(`${contract.expiry}T00:00:00Z`) : Number.NaN; const atmProximity = contract && underlyingMid > 0 ? Math.abs(contract.strike - underlyingMid) / underlyingMid : null; return { contract_count: Math.max(1, structureLegs || 1), print_count: printCount, total_size: numberFeature(packet, "total_size"), total_premium: numberFeature(packet, "total_premium"), total_notional: numberFeature(packet, "total_notional"), start_ts: numberFeature(packet, "start_ts") || packet.source_ts, end_ts: numberFeature(packet, "end_ts") || packet.source_ts, window_ms: Math.max(0, Math.round(numberFeature(packet, "window_ms"))), ...(contractId ? { option_contract_id: contractId } : {}), ...(contract?.right === "C" || contract?.right === "P" ? { option_type: contract.right } : {}), dte_days: getDteDays(packet), moneyness: contract && underlyingMid > 0 ? contract.strike / underlyingMid : null, atm_proximity: atmProximity, aggressor_buy_ratio: clamp(numberFeature(packet, "nbbo_aggressive_buy_ratio")), aggressor_sell_ratio: clamp(numberFeature(packet, "nbbo_aggressive_sell_ratio")), aggressor_ratio: clamp(numberFeature(packet, "nbbo_aggressive_ratio")), nbbo_coverage_ratio: clamp(numberFeature(packet, "nbbo_coverage_ratio")), nbbo_inside_ratio: clamp(numberFeature(packet, "nbbo_inside_ratio")), nbbo_stale_ratio: printCount > 0 ? clamp((staleCount + missingCount) / printCount) : 0, quote_age_ms: quoteAge > 0 ? quoteAge : null, venue_count: Math.max(1, Math.round(numberFeature(packet, "venue_count") || 1)), inter_fill_ms_mean: printCount > 1 ? numberFeature(packet, "window_ms") / Math.max(1, printCount - 1) : null, strike_count: strikeCount, strike_concentration: strikeCount > 0 ? clamp(1 / strikeCount) : 0, ...(stringFeature(packet, "structure_type") ? { structure_type: stringFeature(packet, "structure_type") } : {}), structure_legs: structureLegs, same_size_leg_symmetry: clamp(numberFeature(packet, "same_size_leg_symmetry")), net_directional_bias: clamp( numberFeature(packet, "nbbo_aggressive_buy_ratio") - numberFeature(packet, "nbbo_aggressive_sell_ratio"), -1, 1 ), synthetic_iv_shock: numberFeature(packet, "execution_iv_shock") || null, spread_widening: numberFeature(packet, "nbbo_spread_z") || null, underlying_move_bps: numberFeature(packet, "underlying_move_bps") || null, days_to_event: eventTs > 0 ? (eventTs - referenceTs) / MS_PER_DAY : null, expiry_after_event: eventTs > 0 && Number.isFinite(expiryTs) ? expiryTs >= eventTs : null, pre_event_concentration: eventTs > 0 && eventTs >= referenceTs ? clamp(1 - (eventTs - referenceTs) / (21 * MS_PER_DAY)) : null, special_print_ratio: printCount > 0 ? clamp(specialCount / printCount) : 0 }; }; const detectSuppression = (packet: FlowPacket, features: SmartMoneyFeatures): string[] => { const reasons: string[] = []; const conditions = String(packet.features.conditions ?? "") .split(",") .map((item) => item.trim().toUpperCase()) .filter(Boolean); if (conditions.some((condition) => SPECIAL_CONDITIONS.has(condition)) || features.special_print_ratio >= 0.34) { reasons.push("special_print_or_complex_context"); } if (features.nbbo_coverage_ratio < 0.35 || features.nbbo_stale_ratio >= 0.5) { reasons.push("stale_or_missing_quote_context"); } if (features.nbbo_inside_ratio >= 0.7 && features.aggressor_ratio < 0.35) { reasons.push("inside_market_or_cross_like_execution"); } return reasons; }; const evaluateProfiles = ( packet: FlowPacket, features: SmartMoneyFeatures, suppressed: string[] ): SmartMoneyProfileScore[] => { const direction = inferDirection(packet); const dte = features.dte_days ?? 999; const structure = features.structure_type ?? ""; const isStructure = features.structure_legs >= 2 || Boolean(structure); const buy = features.aggressor_buy_ratio; const sell = features.aggressor_sell_ratio; const premiumFactor = clamp(features.total_premium / 120_000); const sizeFactor = clamp(features.total_size / 1800); const burstFactor = clamp(features.print_count / 8); const quality = clamp(features.nbbo_coverage_ratio - features.nbbo_stale_ratio); const shortDatedOtm = dte <= 7 && features.atm_proximity !== null && features.atm_proximity >= 0.05 && features.option_type === "C"; const nearAtm = features.atm_proximity !== null && features.atm_proximity <= 0.015; const preEvent = features.days_to_event !== null && features.days_to_event >= 0 && features.days_to_event <= 21 && features.expiry_after_event === true; const scores = [ score( "institutional_directional", suppressed.length > 0 || shortDatedOtm ? 0.18 : 0.2 + premiumFactor * 0.25 + burstFactor * 0.18 + quality * 0.16 + (buy >= 0.58 || sell >= 0.58 ? 0.12 : 0), direction, [ "large_parent_event", "directional_aggressor_mix", ...(shortDatedOtm ? ["retail_frenzy_guard"] : []), ...suppressed ] ), score( "retail_whale", 0.12 + (shortDatedOtm ? 0.28 : 0) + burstFactor * 0.18 + clamp(features.synthetic_iv_shock ?? 0, 0, 0.2) + (features.total_premium < 100_000 ? 0.1 : 0), direction, ["short_dated_otm_attention_flow", "burst_print_pattern"] ), score( "event_driven", 0.12 + (preEvent ? 0.32 : 0) + premiumFactor * 0.14 + clamp(features.spread_widening ?? 0, 0, 0.16), direction === "unknown" ? "neutral" : direction, ["event_calendar_alignment", "expiry_after_event", "pre_event_concentration"] ), score( "vol_seller", 0.12 + (sell >= 0.58 ? 0.24 : 0) + (structure === "straddle" || structure === "strangle" ? 0.2 : 0) + premiumFactor * 0.14, "neutral", ["sell_side_premium", "short_vol_structure_evidence"] ), score( "arbitrage", 0.08 + (isStructure ? 0.18 : 0) + (features.same_size_leg_symmetry >= 0.7 ? 0.24 : 0) + (Math.abs(features.net_directional_bias) <= 0.15 ? 0.18 : 0), "neutral", ["matched_leg_symmetry", "near_flat_directional_exposure"] ), score( "hedge_reactive", 0.1 + (dte <= 2 && nearAtm ? 0.32 : 0) + clamp(Math.abs(features.underlying_move_bps ?? 0) / 80, 0, 0.18) + sizeFactor * 0.12, direction, ["short_dated_atm_gamma_context", "underlying_move_linkage"] ) ]; return scores.sort((a, b) => b.probability - a.probability); }; export const buildSmartMoneyEventFromPacket = ( packet: FlowPacket, options: SmartMoneyParentEventOptions = {} ): SmartMoneyEvent => { const features = buildFeatures(packet, options); const suppressed = detectSuppression(packet, features); const profileScores = evaluateProfiles(packet, features, suppressed); const primary = profileScores[0] ?? null; const abstained = !primary || primary.probability < 0.42 || suppressed.includes("stale_or_missing_quote_context"); const underlying = stringFeature(packet, "underlying_id") || parseContractId(features.option_contract_id ?? "")?.root || "UNKNOWN"; const eventKind = features.structure_legs >= 2 || stringFeature(packet, "packet_kind") === "structure" ? "multi_leg_event" : "single_leg_event"; return SmartMoneyEventSchema.parse({ source_ts: packet.source_ts, ingest_ts: packet.ingest_ts, seq: packet.seq, trace_id: `smartmoney:${packet.id}`, event_id: `smartmoney:${eventKind}:${packet.id}`, packet_ids: [packet.id], member_print_ids: packet.members, underlying_id: underlying, event_kind: eventKind, event_window_ms: features.window_ms, features, profile_scores: profileScores, primary_profile_id: abstained ? null : primary?.profile_id ?? null, primary_direction: abstained ? "unknown" : primary?.direction ?? "unknown", abstained, suppressed_reasons: suppressed }); }; const LEGACY_PROFILE_MAP: Record = { institutional_directional: "smart_money_institutional_directional", retail_whale: "smart_money_retail_whale", event_driven: "smart_money_event_driven", vol_seller: "smart_money_vol_seller", arbitrage: "smart_money_arbitrage", hedge_reactive: "smart_money_hedge_reactive" }; export const deriveClassifierHitsFromSmartMoneyEvent = (event: SmartMoneyEvent): ClassifierHit[] => { if (event.abstained || !event.primary_profile_id) { return []; } return event.profile_scores .filter((entry) => entry.profile_id === event.primary_profile_id || entry.probability >= 0.5) .slice(0, 3) .map((entry) => ({ classifier_id: LEGACY_PROFILE_MAP[entry.profile_id], confidence: entry.probability, direction: entry.direction, explanations: [ `Profile ${entry.profile_id} probability ${(entry.probability * 100).toFixed(0)}%.`, ...entry.reasons, ...event.suppressed_reasons.map((reason) => `Suppression guard: ${reason}.`) ] })); };