{
  "schema_version": "2.0.0",
  "bot_id": "4.3",
  "bot_name": "OrderFlowAnalyzer",
  "slug": "orderflowanalyzer",
  "layer": "Intelligence",
  "layer_key": "intel",
  "bot_class": "Signal Service",
  "authority": [
    "Read-only"
  ],
  "status": "live",
  "readiness": "General live",
  "flagship": false,
  "is_reference": false,
  "public_export": false,
  "identity": {
    "layer": "Intelligence",
    "bot_class": "Signal Service",
    "authority": "Read-only",
    "runs_before": "liquidity-aware strategy layer, LiquidityGuard",
    "runs_after": "ws_market subscription established; clob_public book snapshot loaded",
    "applies_to": "All markets with active ws_market subscriptions",
    "default_mode": "general_live",
    "user_visible": "Advanced details only",
    "developer_owner": "Polytraders core \u2014 Intelligence pod"
  },
  "purpose": "OrderFlowAnalyzer subscribes to ws_market for real-time trade prints and clob_public for order-book snapshots, then classifies each flow event as toxic (informed, likely to precede adverse price movement), benign (noise/retail), or informed (structural, correlated with neg-risk rebalancing). It emits an ObservationReport for every toxic flip and samples 1/10 routine ticks. Output feeds liquidity-aware strategies and the LiquidityGuard risk bot. OrderFlowAnalyzer is strictly read-only \u2014 it never submits or signs orders.",
  "why_it_matters": [
    {
      "failure": "Toxic flow not detected before entry",
      "consequence": "Strategy enters a market being targeted by informed traders; adverse selection erodes edge within seconds of fill."
    },
    {
      "failure": "Order-book imbalance not propagated to LiquidityGuard",
      "consequence": "LiquidityGuard uses stale depth figures; permits entry at sizes that would incur >2\u00d7 expected slippage."
    },
    {
      "failure": "Informed neg-risk flow misclassified as benign",
      "consequence": "Neg-risk rebalancing activity (bulk outcome swaps) treated as noise; strategy takes wrong side of a structural price shift."
    },
    {
      "failure": "Microstructure features computed from stale snapshot",
      "consequence": "Stale imbalance and micro-volatility signals produce incorrect flow classification, causing either over-trading or missed entries."
    }
  ],
  "polymarket_inputs": [
    {
      "input": "Real-time trade prints (taker side, size, price, timestamp_ms)",
      "source": "ws_market",
      "required": true,
      "use": "Classify each fill as toxic / benign / informed based on size, speed, and direction patterns."
    },
    {
      "input": "Order-book snapshots (top-50 bid/ask levels, sizes in pUSD)",
      "source": "clob_public",
      "required": true,
      "use": "Compute bid-ask imbalance, visible depth, and queue-position features for flow classification."
    },
    {
      "input": "Market neg-risk flag",
      "source": "Gamma API (via internal cache)",
      "required": false,
      "use": "Amplify informed-flow scoring for neg-risk markets where bulk outcome swaps are common."
    }
  ],
  "internal_inputs": [
    {
      "input": "KillSwitch active flag",
      "source": "KillSwitch",
      "required": true,
      "use": "Continue computing microstructure features but suppress ObservationReport emissions when KillSwitch is active."
    },
    {
      "input": "LiquidityGuard depth thresholds",
      "source": "LiquidityGuard config",
      "required": false,
      "use": "Calibrate minimum imbalance threshold against current depth guard parameters."
    }
  ],
  "raw_params": [
    "imbalance_window_s \u00b7 int",
    "queue_position_method \u00b7 enum",
    "micro_vol_window_s \u00b7 int",
    "publish_rate_hz \u00b7 int"
  ],
  "parameters": [
    {
      "name": "imbalance_window_s",
      "default": 30,
      "warning": 10,
      "hard": 5,
      "controls": "Rolling window in seconds over which order-book imbalance is computed.",
      "why_default_matters": "30 s captures meaningful directional pressure without over-reacting to single large prints.",
      "threshold_logic": [
        {
          "condition": "window \u2265 30 s",
          "action": "Normal \u2014 stable imbalance signal"
        },
        {
          "condition": "10\u201330 s",
          "action": "WARN \u2014 signal noisier; may increase false toxic classifications"
        },
        {
          "condition": "< 5 s",
          "action": "Reject \u2014 PARAMETER_CHANGE_REQUIRES_APPROVAL"
        }
      ],
      "dev_check": "if (p.imbalance_window_s < p.hard) throw ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL');",
      "user_facing": "Order-book pressure is measured over a rolling window to distinguish sustained trends from one-off prints."
    },
    {
      "name": "micro_vol_window_s",
      "default": 60,
      "warning": 20,
      "hard": 10,
      "controls": "Window in seconds for micro-volatility estimation (std-dev of trade prices).",
      "why_default_matters": "60 s provides a stable micro-vol baseline; shorter windows produce noisy estimates.",
      "threshold_logic": [
        {
          "condition": "window \u2265 60 s",
          "action": "Normal"
        },
        {
          "condition": "20\u201360 s",
          "action": "WARN \u2014 noisier micro-vol estimate"
        },
        {
          "condition": "< 10 s",
          "action": "Reject \u2014 PARAMETER_CHANGE_REQUIRES_APPROVAL"
        }
      ],
      "dev_check": "if (p.micro_vol_window_s < p.hard) throw ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL');",
      "user_facing": "Short-term price volatility is tracked to detect unusual market stress."
    },
    {
      "name": "publish_rate_hz",
      "default": 1,
      "warning": 5,
      "hard": 10,
      "controls": "Maximum ObservationReport emission rate per market in Hertz (reports/second).",
      "why_default_matters": "1 Hz prevents bus flooding while ensuring fresh flow classification for liquidity strategies.",
      "threshold_logic": [
        {
          "condition": "rate \u2264 1 Hz",
          "action": "Normal"
        },
        {
          "condition": "1\u20135 Hz",
          "action": "WARN \u2014 higher bus load"
        },
        {
          "condition": "> 10 Hz",
          "action": "Hard cap \u2014 PARAMETER_CHANGE_REQUIRES_APPROVAL"
        }
      ],
      "dev_check": "if (p.publish_rate_hz > p.hard) throw ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL');",
      "user_facing": "Flow classification updates are rate-limited to prevent overloading downstream systems."
    },
    {
      "name": "queue_position_method",
      "default": "pro_rata",
      "warning": null,
      "hard": null,
      "controls": "Method used to estimate queue position for maker orders. Options: pro_rata | fifo | none.",
      "why_default_matters": "Polymarket uses pro-rata queue mechanics; pro_rata provides the most accurate toxic-flow detection.",
      "threshold_logic": [],
      "dev_check": "",
      "user_facing": "Queue position is estimated using the correct mechanics for this exchange type."
    }
  ],
  "default_config": {
    "bot_id": "intel.orderflowanalyzer",
    "version": "2.1.0",
    "mode": "general_live",
    "defaults": {
      "imbalance_window_s": 30,
      "micro_vol_window_s": 60,
      "publish_rate_hz": 1,
      "queue_position_method": "pro_rata"
    },
    "locked": {
      "imbalance_window_s": {
        "min": 5
      },
      "micro_vol_window_s": {
        "min": 10
      },
      "publish_rate_hz": {
        "max": 10
      }
    }
  },
  "implementation_flow": [
    "Subscribe to ws_market for trade prints (taker_side, size_pusd, price, timestamp_ms) for all tracked markets.",
    "Subscribe to clob_public for order-book snapshots (top-50 bid/ask levels) per market at publish_rate_hz.",
    "On each trade print: append to rolling imbalance buffer (imbalance_window_s). Compute bid_ask_imbalance = (bid_depth - ask_depth) / total_depth.",
    "Compute micro_vol: std-dev of last N trade prices within micro_vol_window_s.",
    "Classify flow_class: TOXIC if (taker_side==BUY AND imbalance < -0.3) OR (taker_side==SELL AND imbalance > 0.3) OR print_size_pusd > 2\u00d7 avg_print_size; INFORMED if neg_risk_flag AND abs(imbalance) > 0.5; BENIGN otherwise.",
    "Check KillSwitch; if active, continue computing but suppress emissions.",
    "Apply sampling: if flow_class == TOXIC or last ObservationReport for this market > 1/publish_rate_hz s ago \u2192 emit-every. Otherwise sample-1/10.",
    "Emit ObservationReport with: report_id, trace_id, condition_id, flow_class, bid_ask_imbalance, micro_vol, print_size_pusd, taker_side, book_depth_pusd, neg_risk_flag, warnings.",
    "Log per-market cycle: flow_class distribution, imbalance, micro_vol, prints_received, emitted."
  ],
  "decision_logic": {
    "approve": "Not applicable \u2014 OrderFlowAnalyzer is read-only; it never approves or submits orders.",
    "reshape_required": "Not applicable.",
    "reject": "ObservationReport emissions are suppressed only when KillSwitch is active (KILL_SWITCH_ACTIVE). Routine ticks sampled out are not emitted but are still computed.",
    "warning_only": "ORDERFLOW_THIN_BOOK is included as a warning when visible book depth < 2\u00d7 LiquidityGuard minimum; downstream strategies apply additional size restrictions."
  },
  "decision_output_schema": "ObservationReport",
  "decision_output_example": {
    "report_id": "rep_ofa_0xbcd2_1746701000000",
    "trace_id": "trc_0xcafe010203040506",
    "bot_id": "intel.orderflowanalyzer",
    "kind": "ObservationReport",
    "condition_id": "0xbcd2340000000000000000000000000000000000000000000000000000000000",
    "flow_class": "TOXIC",
    "bid_ask_imbalance": -0.41,
    "micro_vol": 0.018,
    "print_size_pusd": 4200,
    "taker_side": "BUY",
    "book_depth_pusd": 8900,
    "neg_risk_flag": true,
    "sampling_applied": false,
    "warnings": [
      "ORDERFLOW_THIN_BOOK"
    ],
    "emitted_at_ms": 1746701000085
  },
  "developer_log": {
    "bot_id": "intel.orderflowanalyzer",
    "cycle_ts_ms": 1746701000000,
    "markets_tracked": 47,
    "prints_received": 312,
    "toxic_flips": 3,
    "informed_events": 1,
    "benign_ticks": 308,
    "emitted": 14,
    "sampled_out": 298,
    "killswitch_active": false
  },
  "user_explanations": [
    {
      "situation": "Strategy entry blocked \u2014 toxic flow detected",
      "message": "The order book shows signs of informed selling pressure on this market. Entry has been paused to avoid adverse selection."
    },
    {
      "situation": "Lower-than-expected fill rate on a market",
      "message": "Recent order-flow analysis detected directional imbalance. Liquidity-aware sizing reduced the order size to protect against unfavourable execution."
    },
    {
      "situation": "Neg-risk market flagged as informed flow",
      "message": "Bulk rebalancing activity typical of multi-outcome markets was detected. This is structural, not predatory, but strategies will treat it as an informed signal."
    }
  ],
  "failure_modes": {
    "main_failure_mode": "ws_market disconnection causes OrderFlowAnalyzer to miss trade prints during a high-activity period, allowing strategies to enter during undetected toxic flow.",
    "false_positive_risk": "A single large benign print (e.g. a market maker hedging) classified as TOXIC based on size alone, causing unnecessary strategy pausing.",
    "false_negative_risk": "Slow-drip toxic flow spread across many small prints falls below per-print size threshold; imbalance accumulates but classification update is delayed by the rolling window.",
    "safe_fallback": "If ws_market is disconnected for > 2\u00d7 imbalance_window_s, emit STALE_DATA and set flow_class=UNKNOWN for all affected markets. Downstream strategies treat UNKNOWN as conservative (do not enter). Reconnect with exponential back-off.",
    "required_dependencies": [
      "ws_market trade-print subscription",
      "clob_public order-book snapshot endpoint",
      "KillSwitch active flag readable"
    ]
  },
  "acceptance_tests": {
    "unit": [
      {
        "test": "Large BUY print with negative imbalance classified as TOXIC",
        "setup": "print_size=4000 pUSD, imbalance=-0.35, taker_side=BUY",
        "expected": "ObservationReport emitted with flow_class=TOXIC, sampling_applied=false"
      },
      {
        "test": "Routine small print classified as BENIGN and sampled 1/10",
        "setup": "print_size=50 pUSD, imbalance=0.05, taker_side=BUY; run 10 prints",
        "expected": "Approximately 1 ObservationReport emitted with flow_class=BENIGN"
      },
      {
        "test": "Neg-risk bulk rebalance classified as INFORMED",
        "setup": "neg_risk_flag=true, abs(imbalance)=0.55, print_size=1500 pUSD",
        "expected": "ObservationReport with flow_class=INFORMED"
      },
      {
        "test": "ws_market outage sets flow_class=UNKNOWN",
        "setup": "Disconnect ws_market for 65 s (> 2\u00d7 imbalance_window_s=30)",
        "expected": "ObservationReport with flow_class=UNKNOWN, warnings=['STALE_DATA']"
      },
      {
        "test": "KillSwitch suppresses emissions",
        "setup": "killswitch.active=true; TOXIC print arrives",
        "expected": "Flow classified as TOXIC; no ObservationReport emitted; KILL_SWITCH_ACTIVE logged"
      },
      {
        "test": "publish_rate_hz hard cap enforced",
        "setup": "100 TOXIC prints in 1 s, publish_rate_hz=1",
        "expected": "At most 1 ObservationReport emitted per second per market"
      }
    ],
    "integration": [
      {
        "test": "TOXIC ObservationReport reaches LiquidityGuard and triggers size reduction",
        "expected": "LiquidityGuard receives flow_class=TOXIC and applies additional size restriction on next OrderIntent"
      },
      {
        "test": "ws_market reconnection resumes correct flow classification",
        "expected": "After reconnect, first ObservationReport has flow_class determined by fresh prints, not stale UNKNOWN state"
      },
      {
        "test": "Neg-risk market informed flow correctly annotated end-to-end",
        "expected": "Liquidity-aware strategy receives ObservationReport with neg_risk_flag=true and flow_class=INFORMED"
      }
    ],
    "property": [
      {
        "property": "OrderFlowAnalyzer never submits, signs, or modifies any order",
        "required": "Always true"
      },
      {
        "property": "No ObservationReport emitted when KillSwitch is active",
        "required": "Always true"
      },
      {
        "property": "flow_class=UNKNOWN set for all markets when ws_market is stale",
        "required": "Always true \u2014 stale data must never produce a BENIGN or TOXIC classification"
      }
    ]
  },
  "checklist_overrides": {},
  "legacy_goal": "Compute book microstructure features in real time.",
  "legacy_pm_signals": [
    "WSS book updates per market",
    "Trade prints + cancellations"
  ],
  "legacy_external_feeds": [],
  "reporting_groups": [
    "pretrade_intel"
  ],
  "network": [
    "polygon"
  ],
  "api_surface": [
    "clob_public",
    "ws_market",
    "internal"
  ],
  "version": {
    "spec": "2.0.0",
    "implementation": "2.1.0",
    "schema": "2",
    "released": "2026-04-28"
  },
  "migration_history": [
    {
      "date": "2026-04-28",
      "from": "v1",
      "to": "v2",
      "reason": "CLOB V2 cutover \u2014 pUSD denomination and new book/trade-print field names",
      "action_taken": "Trade-print size and book depth figures updated from USDC.e to pUSD denomination. ws_market subscription updated to CLOB V2 message format (timestamp_ms field; removed legacy nonce from trade payload). No feeRateBps plumbing in this bot."
    }
  ],
  "polymarket_v2_compat": {
    "clob_version": "v2",
    "collateral": "pUSD",
    "eip712_domain_version": "2",
    "builder_code_aware": false,
    "negrisk_aware": true,
    "multichain_ready": false,
    "sdk_used": "py-clob-client-v2",
    "settlement_contract": "CTFExchangeV2",
    "notes": "OrderFlowAnalyzer consumes CLOB V2 ws_market trade prints (timestamp_ms format) and clob_public book snapshots with all sizes denominated in pUSD. Neg-risk flag from Gamma API amplifies the informed-flow classification threshold for multi-outcome markets where bulk outcome swaps are structural."
  },
  "reference_implementation": {
    "summary": "Subscribes to ws_market for trade prints and clob_public for book snapshots, computes rolling imbalance and micro-volatility features, classifies each flow event as TOXIC/BENIGN/INFORMED, and emits ObservationReports with per-market sampling (emit-every for TOXIC flips, sample-1/10 for routine ticks).",
    "language_note": "Pseudocode is language-agnostic. FETCH = read input. EMIT = produce output.",
    "pseudocode": "// --- Initialisation ---\nFOR market IN tracked_markets:\n  ws_market.subscribe(market.condition_id, handler=onTradePrint)\n  imbalance_buffer[market.condition_id] = RollingWindow(imbalance_window_s)\n  microvol_buffer[market.condition_id]  = RollingWindow(micro_vol_window_s)\n\nFUNCTION onTradePrint(event):\n  cid = event.condition_id\n\n  // --- 1. Staleness gate ---\n  IF (now_ms() - event.timestamp_ms) > imbalance_window_s * 1000:\n    LOG WARN 'STALE_DATA \u2014 skipping stale print'\n    RETURN\n\n  // --- 2. Update rolling windows ---\n  imbalance_buffer[cid].add(event)\n  microvol_buffer[cid].add(event.price)\n\n  // --- 3. Fetch book snapshot ---\n  book = fetchClobPublic('/book?market=' + cid + '&depth=50')\n  IF book IS NULL:\n    flow_class = 'UNKNOWN'; warnings = ['STALE_DATA']\n    GOTO emit_check\n\n  bid_depth  = SUM(level.size * level.price FOR level IN book.bids[:50])\n  ask_depth  = SUM(level.size * level.price FOR level IN book.asks[:50])\n  total_depth = bid_depth + ask_depth\n  imbalance   = (bid_depth - ask_depth) / total_depth IF total_depth > 0 ELSE 0\n\n  micro_vol   = STDEV(microvol_buffer[cid].prices())\n\n  // --- 4. Flow classification ---\n  avg_print = imbalance_buffer[cid].avg_print_size_pusd()\n  neg_risk  = internal.market_cache[cid].neg_risk OR false\n\n  IF (event.taker_side == 'BUY'  AND imbalance < -0.3)   OR (event.taker_side == 'SELL' AND imbalance >  0.3)   OR event.size_pusd > 2 * avg_print:\n    flow_class = 'TOXIC'\n  ELSE IF neg_risk AND ABS(imbalance) > 0.5:\n    flow_class = 'INFORMED'\n  ELSE:\n    flow_class = 'BENIGN'\n\n  // --- 5. Warnings ---\n  warnings = []\n  IF total_depth < 2 * liquidity_guard.min_depth_pusd:\n    warnings.append('ORDERFLOW_THIN_BOOK')\n\n  :emit_check\n  // --- 6. KillSwitch ---\n  ks = FETCH internal.killswitch.status\n  IF ks.active:\n    LOG INFO 'KILL_SWITCH_ACTIVE \u2014 suppressing ObservationReport'\n    RETURN\n\n  // --- 7. Rate-limit + sampling ---\n  IF flow_class == 'TOXIC' OR time_since_last_emit[cid] >= 1/params.publish_rate_hz:\n    sampling_applied = false\n  ELSE:\n    IF random() > 0.1: RETURN     // sample-1/10\n    sampling_applied = true\n\n  // --- 8. Emit ---\n  report = ObservationReport(\n    report_id         = 'rep_ofa_' + cid[:6] + '_' + now_ms(),\n    trace_id          = newTraceId(),\n    bot_id            = 'intel.orderflowanalyzer',\n    kind              = 'ObservationReport',\n    condition_id      = cid,\n    flow_class        = flow_class,\n    bid_ask_imbalance = imbalance,\n    micro_vol         = micro_vol,\n    print_size_pusd   = event.size_pusd,\n    taker_side        = event.taker_side,\n    book_depth_pusd   = total_depth,\n    neg_risk_flag     = neg_risk,\n    sampling_applied  = sampling_applied,\n    warnings          = warnings,\n    emitted_at_ms     = now_ms()\n  )\n  EMIT internal.bus.observations <- report\n  time_since_last_emit[cid] = now_ms()\n",
    "sdk_calls": [
      "ws_market.subscribe(condition_id, handler)",
      "fetchClobPublic('/book?market=<condition_id>&depth=50')"
    ],
    "complexity": "O(1) per trade print; O(N) per book snapshot where N = book levels (\u226450)"
  },
  "wire_examples": {
    "input": {
      "label": "ws_market trade print",
      "source": "ws_market",
      "payload": {
        "event_type": "trade",
        "condition_id": "0xbcd2340000000000000000000000000000000000000000000000000000000000",
        "taker_side": "BUY",
        "size_pusd": 4200,
        "price": 0.72,
        "timestamp_ms": 1746701000000,
        "maker_order_id": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab"
      }
    },
    "output": {
      "label": "ObservationReport \u2014 TOXIC flow detected",
      "payload": {
        "report_id": "rep_ofa_0xbcd2_1746701000000",
        "trace_id": "trc_0xcafe010203040506070809",
        "bot_id": "intel.orderflowanalyzer",
        "kind": "ObservationReport",
        "condition_id": "0xbcd2340000000000000000000000000000000000000000000000000000000000",
        "flow_class": "TOXIC",
        "bid_ask_imbalance": -0.41,
        "micro_vol": 0.018,
        "print_size_pusd": 4200,
        "taker_side": "BUY",
        "book_depth_pusd": 8900,
        "neg_risk_flag": true,
        "sampling_applied": false,
        "warnings": [
          "ORDERFLOW_THIN_BOOK"
        ],
        "emitted_at_ms": 1746701000085
      }
    }
  },
  "reason_codes": [
    {
      "code": "ORDERFLOW_TOXIC_FLIP",
      "severity": "WARN",
      "meaning": "Flow classified as TOXIC: large print or directional imbalance signals informed order flow.",
      "action": "Emit ObservationReport with flow_class=TOXIC emit-every; downstream strategies apply adverse-selection guard.",
      "user_message": "Unusual buying or selling pressure detected on this market. Entry paused temporarily."
    },
    {
      "code": "ORDERFLOW_INFORMED",
      "severity": "WARN",
      "meaning": "Neg-risk market shows structural bulk-rebalancing flow classified as INFORMED.",
      "action": "Emit ObservationReport with flow_class=INFORMED; strategies treat as structural directional signal.",
      "user_message": ""
    },
    {
      "code": "ORDERFLOW_THIN_BOOK",
      "severity": "WARN",
      "meaning": "Visible book depth below 2\u00d7 LiquidityGuard minimum during flow classification.",
      "action": "Include in warnings on ObservationReport; LiquidityGuard applies tighter size restrictions.",
      "user_message": "This market has unusually thin liquidity. Order sizes are being restricted."
    },
    {
      "code": "STALE_DATA",
      "severity": "WARN",
      "meaning": "ws_market disconnected or book snapshot stale for > 2\u00d7 imbalance_window_s.",
      "action": "Set flow_class=UNKNOWN for affected markets; halt emissions; downstream strategies do not enter.",
      "user_message": ""
    },
    {
      "code": "KILL_SWITCH_ACTIVE",
      "severity": "HARD_REJECT",
      "meaning": "KillSwitch active; ObservationReport emissions suppressed.",
      "action": "Continue computing features but suppress all emissions.",
      "user_message": "Flow analysis is paused while trading is suspended system-wide."
    },
    {
      "code": "MARKET_CLOSED",
      "severity": "EXPLAIN",
      "meaning": "Trade print received for a market that is closed or resolved.",
      "action": "Ignore print; unsubscribe from ws_market for this condition_id.",
      "user_message": ""
    },
    {
      "code": "ORDERFLOW_RATE_CAPPED",
      "severity": "INFO",
      "meaning": "ObservationReport emission rate-capped at publish_rate_hz for a benign market.",
      "action": "Subsequent prints sampled; next scheduled emit will carry accumulated imbalance.",
      "user_message": ""
    },
    {
      "code": "PARAMETER_CHANGE_REQUIRES_APPROVAL",
      "severity": "HARD_REJECT",
      "meaning": "A parameter change violates a locked bound (e.g. imbalance_window_s < 5).",
      "action": "Reject config change; do not apply.",
      "user_message": ""
    }
  ],
  "metrics": {
    "emitted": [
      {
        "name": "polytraders_intel_orderflowanalyzer_prints_received_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "condition_id"
        ],
        "meaning": "Total trade prints received from ws_market per market."
      },
      {
        "name": "polytraders_intel_orderflowanalyzer_observations_emitted_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "flow_class",
          "sampling_applied"
        ],
        "meaning": "ObservationReports emitted broken down by flow class and sampling status."
      },
      {
        "name": "polytraders_intel_orderflowanalyzer_bid_ask_imbalance",
        "type": "gauge",
        "unit": "ratio",
        "labels": [
          "condition_id"
        ],
        "meaning": "Current bid-ask imbalance per market (range -1 to 1)."
      },
      {
        "name": "polytraders_intel_orderflowanalyzer_micro_vol",
        "type": "gauge",
        "unit": "ratio",
        "labels": [
          "condition_id"
        ],
        "meaning": "Current micro-volatility (std-dev of recent trade prices) per market."
      },
      {
        "name": "polytraders_intel_orderflowanalyzer_toxic_flips_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "condition_id"
        ],
        "meaning": "Total TOXIC flow classifications, per market."
      },
      {
        "name": "polytraders_intel_orderflowanalyzer_ws_reconnects_total",
        "type": "counter",
        "unit": "count",
        "labels": [],
        "meaning": "Total ws_market reconnection events."
      }
    ],
    "alerts": [
      {
        "name": "OrderFlowAnalyzerWSDisconnect",
        "condition": "rate(polytraders_intel_orderflowanalyzer_ws_reconnects_total[5m]) > 3",
        "severity": "page",
        "runbook": "#runbook-orderflowanalyzer-ws-disconnect"
      },
      {
        "name": "OrderFlowAnalyzerToxicFloodSpike",
        "condition": "rate(polytraders_intel_orderflowanalyzer_toxic_flips_total[5m]) > 20",
        "severity": "warn",
        "runbook": "#runbook-orderflowanalyzer-toxic-spike"
      },
      {
        "name": "OrderFlowAnalyzerStaleData",
        "condition": "polytraders_intel_orderflowanalyzer_prints_received_total rate == 0 for 5m",
        "severity": "page",
        "runbook": "#runbook-orderflowanalyzer-stale-data"
      },
      {
        "name": "OrderFlowAnalyzerHighImbalance",
        "condition": "abs(polytraders_intel_orderflowanalyzer_bid_ask_imbalance) > 0.7 for any condition_id",
        "severity": "warn",
        "runbook": "#runbook-orderflowanalyzer-high-imbalance"
      }
    ],
    "dashboards": [
      "Grafana \u2014 Intelligence / OrderFlowAnalyzer flow classification distribution",
      "Grafana \u2014 Intelligence / per-market imbalance and micro-vol heatmap"
    ],
    "log_level": "info"
  },
  "state": {
    "store": "in-memory",
    "shape": "Per-market rolling windows: imbalance_buffer (trade prints \u00d7 imbalance_window_s), microvol_buffer (prices \u00d7 micro_vol_window_s), last_emit_ts. No durable state between restarts.",
    "ttl": "imbalance_window_s (30 s default) for imbalance buffer; micro_vol_window_s (60 s) for vol buffer",
    "recovery": "On cold start, rolling windows are empty. First imbalance_window_s of operation produces no TOXIC classifications until buffers warm up. flow_class=UNKNOWN emitted during warm-up.",
    "size_estimate": "~20 KB per market for 30-s rolling buffer at 10 prints/s; ~1 MB for 50 markets"
  },
  "concurrency": {
    "execution_model": "single-threaded event loop",
    "max_in_flight": 50,
    "idempotency_key": "condition_id + timestamp_ms",
    "timeout_ms": 500,
    "backpressure": "drop-after-buffer \u2014 excess prints dropped when per-market queue > 100; STALE_DATA warned",
    "locking": "none \u2014 per-market state accessed only from the single event loop"
  },
  "dependencies": {
    "depends_on": [
      {
        "bot_id": "risk.kill_switch",
        "why": "KillSwitch gate suppresses ObservationReport emissions."
      }
    ],
    "emits_to": [
      {
        "bot_id": "risk.liquidity_guard",
        "what": "ObservationReport with flow_class, bid_ask_imbalance, and book_depth_pusd for depth calibration"
      },
      {
        "bot_id": "strat.liquidity_aware_strategies",
        "what": "ObservationReport with TOXIC / INFORMED / BENIGN classification for entry gating"
      }
    ],
    "sibling": [],
    "external": [
      {
        "service": "Polymarket ws_market",
        "endpoint": "wss://ws-subscriptions-clob.polymarket.com/ws/market",
        "sla": "best-effort",
        "fallback": "Set flow_class=UNKNOWN; emit STALE_DATA; reconnect with exponential back-off"
      },
      {
        "service": "CLOB public API (book snapshots)",
        "endpoint": "https://clob.polymarket.com",
        "sla": "99.95% / 200 ms p99",
        "fallback": "Skip book snapshot for that tick; use last-known depth with STALE_DATA warning"
      }
    ]
  },
  "security_surfaces": {
    "signs_orders": false,
    "private_key_access": "none",
    "abuse_vectors": [
      "Adversary floods ws_market with wash-trade prints to trigger false TOXIC classifications, causing strategies to stand down",
      "Manipulated clob_public book snapshot (e.g. spoofed depth) causing incorrect imbalance computation"
    ],
    "mitigations": [
      "Minimum print_size_pusd threshold prevents single 1-lot prints from triggering TOXIC classification",
      "Imbalance computed over rolling window \u2014 single snapshot manipulation insufficient to flip classification",
      "All ObservationReports are recommendations only \u2014 LiquidityGuard and strategies independently validate depth"
    ],
    "contract_calls": []
  },
  "failure_injection": [
    {
      "scenario": "WS_DISCONNECT",
      "how_to_inject": "Drop TCP connection to ws_market for 65 s (> 2\u00d7 imbalance_window_s=30)",
      "expected_behaviour": "flow_class=UNKNOWN set for all affected markets; STALE_DATA WARN logged; OrderFlowAnalyzerWSDisconnect alert fires if > 3 reconnects in 5 min",
      "recovery": "Automatic reconnect with exponential back-off; buffer warms up over next imbalance_window_s"
    },
    {
      "scenario": "TOXIC_FLOOD",
      "how_to_inject": "Inject 25 TOXIC prints/s across 3 markets for 10 s",
      "expected_behaviour": "flow_class=TOXIC emitted emit-every at publish_rate_hz cap; OrderFlowAnalyzerToxicFloodSpike alert fires",
      "recovery": "Automatic when toxic print rate subsides"
    },
    {
      "scenario": "STALE_BOOK_SNAPSHOT",
      "how_to_inject": "Return 503 from clob_public for 30 s",
      "expected_behaviour": "Book depth unavailable; flow classification falls back to last-known depth with STALE_DATA warning",
      "recovery": "Automatic when clob_public recovers"
    },
    {
      "scenario": "KILL_SWITCH_ON",
      "how_to_inject": "Set killswitch.active=true; inject TOXIC print",
      "expected_behaviour": "Flow classified; no ObservationReport emitted; KILL_SWITCH_ACTIVE logged",
      "recovery": "Emissions resume on first print after KillSwitch reset"
    },
    {
      "scenario": "BUFFER_OVERFLOW",
      "how_to_inject": "Inject 200 prints/s per market for 5 s (> per-market queue=100)",
      "expected_behaviour": "Excess prints dropped; STALE_DATA WARN logged; no crash; rate normalises",
      "recovery": "Automatic when ingest rate drops below drain rate"
    }
  ],
  "runbook": {
    "summary": "OrderFlowAnalyzer incidents are usually ws_market disconnections or unusual toxic flow spikes. Disconnections affect all downstream liquidity-aware strategies; page immediately.",
    "oncall_actions": [
      {
        "alert": "OrderFlowAnalyzerWSDisconnect",
        "first_action": "Check ws_reconnects_total and ws_market endpoint health. Verify Polymarket WebSocket status page.",
        "escalate_to": "Infra on-call immediately if reconnect fails > 3 attempts"
      },
      {
        "alert": "OrderFlowAnalyzerToxicFloodSpike",
        "first_action": "Identify markets generating toxic flips (toxic_flips_total by condition_id). Check if this is wash trading or genuine informed flow.",
        "escalate_to": "Intelligence pod lead within 10 minutes"
      },
      {
        "alert": "OrderFlowAnalyzerStaleData",
        "first_action": "Check prints_received_total rate. Verify ws_market subscription is active. Check clob_public availability.",
        "escalate_to": "Infra on-call within 5 minutes"
      },
      {
        "alert": "OrderFlowAnalyzerHighImbalance",
        "first_action": "Identify the condition_id. Check if imbalance is correlated with a news event (see NewsIngest). Notify trading pod.",
        "escalate_to": "Trading pod lead if imbalance > 0.8 for > 5 min"
      }
    ],
    "manual_overrides": [
      {
        "name": "force_benign",
        "how": "Set config.override_flow_class[condition_id]=BENIGN for up to 60 s",
        "when": "False-positive TOXIC classification confirmed; short-term override while root cause investigated"
      }
    ],
    "healthcheck": "GET /internal/health/orderflowanalyzer -> 200 if ws_market connected AND prints_received in last 10 s AND no STALE_DATA for any market. RED if ws_market disconnected > 30 s OR zero prints for > 60 s OR STALE_DATA on > 20% of tracked markets."
  },
  "promotion_gates": {
    "to_shadow": [
      {
        "gate": "Unit tests pass for TOXIC, BENIGN, INFORMED classifications and KillSwitch suppression",
        "how_measured": "CI test run",
        "threshold": "100% pass"
      },
      {
        "gate": "ws_market integration test: trade print received and classified correctly",
        "how_measured": "Integration test against Polymarket staging WebSocket",
        "threshold": "Pass"
      }
    ],
    "to_limited_live": [
      {
        "gate": "p99 end-to-end latency (print received \u2192 ObservationReport emitted) < 100 ms over 24 h",
        "how_measured": "emitted_at_ms - event.timestamp_ms histogram p99",
        "threshold": "p99 < 100 ms"
      },
      {
        "gate": "Neg-risk INFORMED flow classification correct for known neg-risk market",
        "how_measured": "Integration test with live neg-risk market",
        "threshold": "Pass"
      }
    ],
    "to_general_live": [
      {
        "gate": "Zero false STALE_DATA warnings during normal ws_market connectivity over 7 days",
        "how_measured": "Grafana stale_data reason_code counter",
        "threshold": "0 false positives"
      },
      {
        "gate": "Toxic-flow recall \u2265 80% on labelled back-test dataset of known informed-flow episodes",
        "how_measured": "Back-test against labelled trade history",
        "threshold": "\u2265 80% recall"
      },
      {
        "gate": "KillSwitch suppression: zero ObservationReports when KillSwitch active",
        "how_measured": "Integration test",
        "threshold": "Pass"
      }
    ]
  },
  "reporting": {
    "emits_kinds": [
      "ObservationReport"
    ],
    "topics": [
      "polytraders.reports.intel"
    ],
    "cadence": "every-event",
    "retention_class": "30d",
    "retention_notes": "Full fidelity for 30 d; rolled-up summary retained for 1 y",
    "sampling_rule": "emit-every for TOXIC flips; sample-1/10 for routine BENIGN ticks",
    "bus_failure_action": "drop-after-buffer",
    "user_visible": "no",
    "consumes_kinds": []
  },
  "capital_impact": "Indirect",
  "v3_status": {
    "phase": 2,
    "phase_name": "Data normalisation",
    "docs": {
      "done": 27,
      "total": 27,
      "state": "done"
    },
    "impl": {
      "done": 0,
      "total": 15,
      "state": "pending"
    },
    "runtime": {
      "done": 0,
      "total": 8,
      "state": "pending"
    },
    "overall": "pending"
  }
}