{
  "schema_version": "1.0.0",
  "bot_id": "3.12",
  "bot_name": "Mean-Reversion Sniper",
  "slug": "mean-reversion-sniper",
  "layer": "Strategy",
  "layer_key": "strat",
  "bot_class": "Alpha Strategy",
  "authority": [
    "Trade"
  ],
  "status": "beta",
  "readiness": "Limited live",
  "flagship": false,
  "is_reference": false,
  "public_export": false,
  "identity": {
    "layer": "Strategy",
    "bot_class": "Alpha Strategy",
    "authority": "Trade",
    "runs_before": "Risk guardrail pipeline",
    "runs_after": "Market scanner / opportunity feed + NewsIngest event-density feed",
    "applies_to": "Standard binary markets where price has moved above price_threshold (default 0.80) and z-score of last-N price change exceeds z_score_min, with no active news cycle on the market",
    "default_mode": "limited_live",
    "user_visible": "Advanced details only",
    "developer_owner": "Polytraders core \u2014 Strategy pod"
  },
  "purpose": "Mean-Reversion Sniper fades price momentum in markets that have overreacted to recent information. It monitors markets where the best-ask for YES tokens exceeds price_threshold (default 0.80), computes a rolling z-score of the price change over a lookback window, and checks for order-book signals of a momentum reversal (aggressor side flips and large cancels on the heavy side). When z_score >= z_score_min and no active news cycle is detected by the NewsIngest event-density feed, the bot emits an IOC OrderIntent to sell YES tokens at or above price_threshold. An automatic time exit (time_exit_s) and stop-loss (stop_bps) control downside risk. This is a user-controlled execution tool; it does not predict outcomes or make performance claims.",
  "why_it_matters": [
    {
      "failure": "Fading during an active news cycle",
      "consequence": "If a material news event is causing the price to move permanently higher, fading it produces a directional loss rather than a mean-reversion gain. NewsIngest event-density gate must be respected."
    },
    {
      "failure": "z-score computed on too short a lookback",
      "consequence": "Short lookbacks produce noisy z-scores that trigger on normal intraday price variation, leading to repeated small losses from false fades."
    },
    {
      "failure": "Stop-loss not configured or too wide",
      "consequence": "Without a stop-loss, an overreaction that continues (e.g., genuine resolution signal) produces an uncapped loss on the short YES position."
    },
    {
      "failure": "feeRateBps present on signed order (V1 pattern)",
      "consequence": "CTFExchangeV2 rejects orders with feeRateBps. Fees are operator-set at match time. The signed order must not contain this field."
    }
  ],
  "polymarket_inputs": [
    {
      "input": "Price and z-score of last-N price change on target market",
      "source": "ws_market (CLOB WebSocket trade tape)",
      "required": true,
      "use": "Confirm price >= price_threshold and z_score >= z_score_min before emitting fade intent."
    },
    {
      "input": "Aggressor side flips and large cancel events",
      "source": "ws_market (order book updates)",
      "required": true,
      "use": "Detect reversal signals: momentum fading when aggressor flips from buy-dominant to sell-dominant."
    },
    {
      "input": "Top-of-book depth on YES side",
      "source": "clob_public",
      "required": true,
      "use": "Size the IOC order to min(available_depth, max_position_usd)."
    },
    {
      "input": "Market open/closed/resolved status",
      "source": "clob_public",
      "required": true,
      "use": "Skip markets that are closed, resolved, or approaching resolution (< 2h to close)."
    }
  ],
  "internal_inputs": [
    {
      "input": "KillSwitch active flag",
      "source": "KillSwitch",
      "required": true,
      "use": "Abort all intent emission immediately if KillSwitch is active."
    },
    {
      "input": "NewsIngest event-density window (don't fade during active news cycles)",
      "source": "internal (NewsIngest feed)",
      "required": true,
      "use": "Block fade intent if NewsIngest signals an active material news cycle on the target market's entity."
    },
    {
      "input": "Builder code bytes32",
      "source": "internal config",
      "required": true,
      "use": "Injected into builder field on every signed V2 OrderIntent."
    }
  ],
  "raw_params": [
    "price_threshold \u00b7 0\u20131 (default 0.80)",
    "z_score_min \u00b7 float (default 2.5)",
    "stop_bps \u00b7 int",
    "time_exit_s \u00b7 int"
  ],
  "parameters": [
    {
      "name": "price_threshold",
      "default": 0.8,
      "warning": 0.9,
      "hard": 0.95,
      "controls": "Minimum YES token price (0\u20131 pUSD) above which the bot considers a fade. Below this level the market is not overextended enough to fade.",
      "why_default_matters": "0.80 captures markets where the crowd has pushed probability high enough that a reversion is plausible. Above 0.90 prices reflect near-certain resolution; fading near 0.95 hard cap is almost never correct.",
      "threshold_logic": [
        {
          "condition": ">= 0.80",
          "action": "Eligible for fade evaluation; check z_score"
        },
        {
          "condition": "0.90\u20130.95",
          "action": "WARN MEAN_REVERSION_HIGH_PRICE_THRESHOLD; market near resolution; fade only with high z_score"
        },
        {
          "condition": ">= 0.95 (hard floor)",
          "action": "SKIP \u2014 MEAN_REVERSION_PRICE_TOO_HIGH; do not fade near resolution"
        }
      ],
      "dev_check": "if price >= params.hard: return skip('MEAN_REVERSION_PRICE_TOO_HIGH')",
      "user_facing": "The market price was too close to certain resolution to fade safely."
    },
    {
      "name": "z_score_min",
      "default": 2.5,
      "warning": 1.5,
      "hard": 1.0,
      "controls": "Minimum z-score of the recent price move (normalised against rolling price volatility) required before emitting a fade intent.",
      "why_default_matters": "2.5 sigma corresponds to a statistically unusual price move that is likely to partially revert. Below 1.5 sigma the move may be within normal daily variation; below 1.0 the bot will not fire.",
      "threshold_logic": [
        {
          "condition": ">= 2.5",
          "action": "EMIT fade OrderIntent"
        },
        {
          "condition": "1.5\u20132.5",
          "action": "WARN MEAN_REVERSION_Z_MARGINAL; emit at 50% size"
        },
        {
          "condition": "< 1.0 (hard floor)",
          "action": "SKIP \u2014 MEAN_REVERSION_Z_TOO_LOW"
        }
      ],
      "dev_check": "if z_score < params.hard: return skip('MEAN_REVERSION_Z_TOO_LOW')",
      "user_facing": "The recent price move was not statistically extreme enough to fade."
    },
    {
      "name": "stop_bps",
      "default": 150,
      "warning": 250,
      "hard": 400,
      "controls": "Basis points above the fade entry price at which the bot automatically closes the position (stop-loss). If price continues up by stop_bps, the bot buys to cover.",
      "why_default_matters": "150 bps provides meaningful downside protection while allowing normal post-fade price noise. Above 250 bps the stop is too wide for a reversion trade; above 400 bps it is rejected.",
      "threshold_logic": [
        {
          "condition": "<= 150 bps",
          "action": "Normal stop-loss"
        },
        {
          "condition": "150\u2013400 bps",
          "action": "WARN MEAN_REVERSION_WIDE_STOP; elevated risk per trade"
        },
        {
          "condition": "> 400 bps",
          "action": "Reject config \u2014 PARAMETER_CHANGE_REQUIRES_APPROVAL"
        }
      ],
      "dev_check": "if params.stop_bps > params.hard: raise ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL')",
      "user_facing": "An automatic stop-loss is in place to limit losses if the price continues rising."
    },
    {
      "name": "time_exit_s",
      "default": 120,
      "warning": 200,
      "hard": 300,
      "controls": "Maximum seconds the bot will hold a fade position before exiting regardless of price. Acts as a time-based stop to prevent open positions persisting into market resolution.",
      "why_default_matters": "120 seconds limits exposure to a 2-minute window. Reversion trades either work quickly or not at all; holding longer on a market near resolution adds settlement risk.",
      "threshold_logic": [
        {
          "condition": "<= 120s",
          "action": "Normal time exit"
        },
        {
          "condition": "120\u2013300s",
          "action": "WARN MEAN_REVERSION_LONG_TIME_EXIT; risk of open position at resolution"
        },
        {
          "condition": "> 300s (hard ceiling)",
          "action": "Reject config \u2014 PARAMETER_CHANGE_REQUIRES_APPROVAL"
        }
      ],
      "dev_check": "if params.time_exit_s > params.hard: raise ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL')",
      "user_facing": "The position was automatically closed after the maximum holding time to avoid any unresolved exposure."
    }
  ],
  "default_config": {
    "bot_id": "strat.mean_reversion_sniper",
    "version": "2.1.0",
    "mode": "limited_live",
    "defaults": {
      "price_threshold": 0.8,
      "z_score_min": 2.5,
      "stop_bps": 150,
      "time_exit_s": 120
    },
    "locked": {
      "price_threshold": {
        "max": 0.95
      },
      "z_score_min": {
        "min": 1.0
      },
      "stop_bps": {
        "max": 400
      },
      "time_exit_s": {
        "max": 300
      }
    }
  },
  "implementation_flow": [
    "Check KillSwitch active flag; if active, emit no OrderIntents; close any open fade positions.",
    "Subscribe to ws_market book and trade-tape streams for markets with best_ask_YES >= price_threshold.",
    "On each price tick: compute rolling z-score of price change over lookback window (default 20 ticks).",
    "Gate 1 \u2014 Price threshold: if best_ask_YES >= 0.95 (hard): skip MEAN_REVERSION_PRICE_TOO_HIGH.",
    "Gate 2 \u2014 NewsIngest: if active news cycle on market entity: skip MEAN_REVERSION_NEWS_ACTIVE.",
    "Gate 3 \u2014 Z-score: if z_score < 1.0 (hard): skip MEAN_REVERSION_Z_TOO_LOW (sampled 1/100).",
    "Gate 4 \u2014 Reversal signal: check aggressor side flip (taker-sell ratio rising) and large cancels on bid side.",
    "If z_score < 2.5 (warning): WARN MEAN_REVERSION_Z_MARGINAL; reduce size to 50% of max_position_usd.",
    "Fetch top-of-book YES ask depth; set orderSize = min(depth, max_position_usd) * sizeMultiplier.",
    "Emit OrderIntent: outcome=YES, side=sell, price=best_ask_YES, size_pUSD=orderSize, tif=IOC, builder={code, fee_bps:25}.",
    "Note: fees are operator-set at match time in V2 \u2014 feeRateBps is NOT on the signed order.",
    "Register position in state: entry_price, entry_time_ms, stop_price=entry_price+stop_bps/10000, exit_deadline_ms=now_ms()+time_exit_s*1000.",
    "Monitor position: if price rises above stop_price \u2192 emit buy IOC to close; if time > exit_deadline \u2192 emit buy IOC to close.",
    "Emit DecisionReport with intent_emitted=true, z_score, price_at_entry, reason MEAN_REVERSION_FADE_INITIATED."
  ],
  "decision_logic": {
    "approve": "price >= price_threshold, z_score >= z_score_min, no active news cycle, reversal signal present, KillSwitch inactive. Emit sell IOC OrderIntent.",
    "reshape_required": "Not applicable \u2014 strat bots emit OrderIntents; reshaping is handled by the downstream Risk guardrail pipeline.",
    "reject": "price >= 0.95 (hard floor); z_score < 1.0; active news cycle; KillSwitch active; stale feed. Emit DecisionReport intent_emitted=false.",
    "warning_only": "z_score between 1.0 and 2.5 triggers MEAN_REVERSION_Z_MARGINAL warn and 50% size reduction."
  },
  "decision_output_schema": "OrderIntent",
  "decision_output_example": {
    "intent_id": "oi_01HXMRS0000001A",
    "trace_id": "tr_01HXMRS000TR001",
    "market_id": "0xmrsniper00000000000000000000000000000000000000000000000000000001",
    "outcome": "YES",
    "side": "sell",
    "price": "0.847",
    "size_pUSD": "300.00",
    "tif": "IOC",
    "post_only": false,
    "builder": {
      "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
      "fee_bps": 25
    },
    "negrisk_aware": false,
    "decision": {
      "z_score": 3.1,
      "price_at_entry": 0.847,
      "stop_price": 0.862,
      "exit_deadline_ms": 1746790320000,
      "reasons": [
        "MEAN_REVERSION_FADE_INITIATED"
      ]
    },
    "comment": "fees are operator-set at match time in V2 \u2014 feeRateBps is NOT on the signed order"
  },
  "developer_log": {
    "bot_id": "strat.mean_reversion_sniper",
    "market_id": "0xmrsniper00000000000000000000000000000000000000000000000000000001",
    "price_at_entry": 0.847,
    "z_score": 3.1,
    "price_threshold": 0.8,
    "news_gate_passed": true,
    "reversal_signal": true,
    "order_size_pusd": 300.0,
    "stop_price": 0.862,
    "exit_deadline_ms": 1746790320000,
    "intent_emitted": true,
    "reason": "MEAN_REVERSION_FADE_INITIATED",
    "emitted_at_ms": 1746790200000
  },
  "user_explanations": [
    {
      "situation": "Mean-reversion fade initiated",
      "message": "The market price spiked unusually high with no active news to justify it. A short sell was placed to capture the expected return toward a more typical price level."
    },
    {
      "situation": "No fade \u2014 news cycle active",
      "message": "A material news event is currently affecting this market. Fading the price movement during active news would be risky. No order was placed."
    },
    {
      "situation": "No fade \u2014 z-score too low",
      "message": "The recent price move was not statistically unusual enough to justify a fade trade. No order was placed."
    },
    {
      "situation": "Position closed \u2014 stop-loss triggered",
      "message": "The market price continued to rise after the fade was placed. The stop-loss automatically closed the position to limit losses."
    },
    {
      "situation": "Position closed \u2014 time exit",
      "message": "The maximum holding time elapsed. The position was closed automatically to avoid any unresolved exposure."
    }
  ],
  "failure_modes": {
    "main_failure_mode": "Fading a genuine resolution signal: the market moves to 0.90+ because the underlying event has materially resolved (not overreaction), and the short YES position incurs a growing loss until the stop-loss triggers.",
    "false_positive_risk": "NewsIngest feed is delayed; the bot fades a genuine news-driven price move because the event-density signal has not yet registered, resulting in a loss.",
    "false_negative_risk": "z_score_min set too conservatively misses genuine overreaction episodes, especially on illiquid markets where z-scores are noisier.",
    "safe_fallback": "If ws_market feed is stale (last_seen > 5s) or NewsIngest feed is unavailable, emit STALE_MARKET_DATA and skip without emitting any OrderIntent. If NewsIngest is unavailable, treat as news-active (conservative gate).",
    "required_dependencies": [
      "ws_market book and trade-tape stream",
      "clob_public market endpoint (status + depth)",
      "NewsIngest event-density feed (internal)",
      "KillSwitch active flag",
      "internal builder code"
    ]
  },
  "acceptance_tests": {
    "unit": [
      {
        "test": "Emit fade when z_score=3.1, price=0.847, no news cycle, reversal signal present",
        "setup": "price_threshold=0.80, z_score_min=2.5, stop_bps=150, time_exit_s=120",
        "expected": "Sell IOC OrderIntent emitted; DecisionReport intent_emitted=true, reason=MEAN_REVERSION_FADE_INITIATED"
      },
      {
        "test": "Skip when price >= hard floor (0.96)",
        "setup": "price=0.96",
        "expected": "No OrderIntent; reason=MEAN_REVERSION_PRICE_TOO_HIGH"
      },
      {
        "test": "Skip when z_score < 1.0 hard floor",
        "setup": "z_score=0.8",
        "expected": "No OrderIntent; sampled DecisionReport reason=MEAN_REVERSION_Z_TOO_LOW"
      },
      {
        "test": "Skip when NewsIngest signals active news cycle",
        "setup": "newsingest.active=true for market entity",
        "expected": "No OrderIntent; reason=MEAN_REVERSION_NEWS_ACTIVE"
      },
      {
        "test": "Reduce size 50% when z_score marginal (1.8)",
        "setup": "z_score=1.8, z_score_min=2.5, max_position_usd=300",
        "expected": "OrderIntent emitted with size=150; WARN MEAN_REVERSION_Z_MARGINAL"
      },
      {
        "test": "Stop-loss triggers when price rises stop_bps above entry",
        "setup": "entry_price=0.847, stop_bps=150; mock price moves to 0.862",
        "expected": "Buy IOC OrderIntent emitted to close position; DecisionReport reason=MEAN_REVERSION_STOP_LOSS"
      }
    ],
    "integration": [
      {
        "test": "Full cycle: price spike detected \u2192 z_score computed \u2192 news gate passed \u2192 sell IOC OrderIntent submitted",
        "expected": "Order has builder.code (bytes32), no feeRateBps, side=sell, tif=IOC, EIP-712 domain version '2'"
      },
      {
        "test": "Time exit: position auto-closed after time_exit_s elapses with no stop-loss trigger",
        "expected": "Buy IOC OrderIntent emitted at time_exit_s; DecisionReport reason=MEAN_REVERSION_TIME_EXIT"
      }
    ],
    "property": [
      {
        "property": "Bot never holds a fade position beyond time_exit_s seconds",
        "required": "Always true"
      },
      {
        "property": "feeRateBps never present on any signed OrderIntent",
        "required": "Always true \u2014 V2 fees are operator-set at match time"
      },
      {
        "property": "Bot never fades when NewsIngest feed is unavailable (fail-closed on news gate)",
        "required": "Always true"
      }
    ]
  },
  "checklist_overrides": {},
  "legacy_goal": "Fade momentum in markets known to overreact above 80% probability.",
  "legacy_pm_signals": [
    "Price + z-score of last-N price change",
    "Aggressor side flips and large cancels on the heavy side"
  ],
  "legacy_external_feeds": [
    "NewsIngest event-density window (don't fade during active news cycles)"
  ],
  "reporting_groups": [
    "strategy_decision"
  ],
  "network": [
    "polygon"
  ],
  "api_surface": [
    "clob_public",
    "clob_auth",
    "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 (USDC.e, feeRateBps on signed order)",
      "to": "v2 (pUSD, fees operator-set at match time)",
      "reason": "CLOB V2 cutover",
      "action_taken": "Switched to py-clob-client-v2. Removed feeRateBps from signed order construction. Updated collateral denomination to pUSD. Injected builder field (bytes32). EIP-712 Exchange domain version updated '1' to '2'. NewsIngest event-density feed integration updated to V2 internal bus schema."
    }
  ],
  "polymarket_v2_compat": {
    "clob_version": "v2",
    "collateral": "pUSD",
    "eip712_domain_version": "2",
    "builder_code_aware": true,
    "negrisk_aware": false,
    "multichain_ready": false,
    "sdk_used": "py-clob-client-v2",
    "settlement_contract": "CTFExchangeV2",
    "notes": "Executes taker IOC orders to fade price momentum on standard binary markets. feeRateBps is not present on any signed order. Builder code injected on every intent."
  },
  "reference_implementation": {
    "summary": "Monitors markets for statistically unusual price spikes above price_threshold, checks NewsIngest event-density gate, and emits IOC sell OrderIntents when z_score >= z_score_min with reversal signals present. Manages open positions with stop-loss and time exit.",
    "language_note": "Pseudocode is language-agnostic. FETCH = read input. EMIT = produce output. Translate to TS/Python/Go/Rust.",
    "pseudocode": "FUNCTION onPriceTick(market_id, priceTick):\n  // --- 0. KillSwitch gate ---\n  ks = FETCH internal.killswitch.status\n  IF ks.active: CLOSE_OPEN_POSITIONS(); RETURN\n\n  // --- 1. Manage open positions (stop-loss + time exit) ---\n  pos = FETCH state.openPosition(market_id)\n  IF pos:\n    IF priceTick.best_ask >= pos.stop_price:\n      EMIT OrderIntent(side='buy', tif='IOC', size=pos.size, reason='MEAN_REVERSION_STOP_LOSS')\n      CLEAR state.openPosition(market_id)\n      RETURN\n    IF now_ms() >= pos.exit_deadline_ms:\n      EMIT OrderIntent(side='buy', tif='IOC', size=pos.size, reason='MEAN_REVERSION_TIME_EXIT')\n      CLEAR state.openPosition(market_id)\n      RETURN\n\n  // --- 2. Price threshold gate ---\n  price = priceTick.best_ask\n  IF price >= params.price_threshold_hard:  // 0.95\n    EMIT DecisionReport(intent_emitted=false, reason='MEAN_REVERSION_PRICE_TOO_HIGH')\n    RETURN\n  IF price < params.price_threshold:  // 0.80\n    RETURN  // not in fade zone\n\n  // --- 3. NewsIngest gate ---\n  newsState = FETCH internal.newsingest.eventDensity(market_id)\n  IF newsState.active OR newsState.unavailable:  // fail-closed\n    EMIT DecisionReport(intent_emitted=false, reason='MEAN_REVERSION_NEWS_ACTIVE')\n    RETURN\n\n  // --- 4. Z-score computation ---\n  tape = FETCH ws_market.tradeTape(market_id, lookback_ticks=20)\n  zScore = zScoreOfPriceChange(tape)\n  IF zScore < params.z_score_min_hard:  // 1.0\n    IF random() < 0.01:\n      EMIT DecisionReport(intent_emitted=false, reason='MEAN_REVERSION_Z_TOO_LOW', z_score=zScore)\n    RETURN\n\n  // --- 5. Reversal signal check ---\n  IF NOT reversalSignalPresent(tape):  // aggressor flip + large cancel on bid\n    RETURN\n\n  // --- 6. Size determination ---\n  sizeMultiplier = 0.5 IF zScore < params.z_score_min ELSE 1.0  // 2.5 warning\n  IF sizeMultiplier < 1.0: WARN('MEAN_REVERSION_Z_MARGINAL')\n  depth = FETCH clob_public.depth(market_id, side='YES')\n  orderSize = toPusdUnits(min(depth, params.max_position_usd) * sizeMultiplier)\n\n  // --- 7. Emit fade OrderIntent (sell YES, IOC, V2: no feeRateBps) ---\n  EMIT OrderIntent(\n    market_id = market_id, outcome = 'YES', side = 'sell',\n    price = price, size_pUSD = orderSize, tif = 'IOC', post_only = false,\n    builder = {code: internal.builder_code, fee_bps: 25}\n  )\n\n  // --- 8. Register position for stop/time-exit monitoring ---\n  SET state.openPosition(market_id, {\n    entry_price: price, size: orderSize,\n    stop_price: price + params.stop_bps / 10000,\n    exit_deadline_ms: now_ms() + params.time_exit_s * 1000\n  })\n\n  EMIT DecisionReport(intent_emitted=true, z_score=zScore,\n                      price_at_entry=price, reason='MEAN_REVERSION_FADE_INITIATED')",
    "sdk_calls": [
      "ws_market.subscribe('book', [market_id])",
      "ws_market.subscribe('trade_tape', [market_id])",
      "fetchClobPublic('/markets/' + market_id + '/orderbook')",
      "internal.newsingest.eventDensity(market_id)",
      "internal.killswitch.status()",
      "toPusdUnits(rawFloat)",
      "buildOrderTypedData(orderParams, { name: 'CTFExchange', version: '2', chainId: 137 })",
      "internal.builder_code"
    ],
    "complexity": "O(1) per price tick; O(lookback_ticks) for z-score computation"
  },
  "wire_examples": {
    "input": [
      {
        "label": "Price tick \u2014 YES spike to 0.847, z_score=3.1, no news cycle",
        "source": "ws_market",
        "payload": {
          "market_id": "0xmrsniper00000000000000000000000000000000000000000000000000000001",
          "best_ask_YES": "0.847",
          "z_score_20tick": "3.1",
          "taker_sell_ratio_5s": "0.65",
          "news_active": false,
          "depth_YES_pusd": "410.00",
          "received_at_ms": 1746790200000
        }
      }
    ],
    "output": [
      {
        "label": "OrderIntent \u2014 fade sell YES (IOC, builder-attributed)",
        "payload": {
          "intent_id": "oi_01HXMRS0000001A",
          "trace_id": "tr_01HXMRS000TR001",
          "market_id": "0xmrsniper00000000000000000000000000000000000000000000000000000001",
          "outcome": "YES",
          "side": "sell",
          "price": "0.847",
          "size_pUSD": "300.00",
          "tif": "IOC",
          "post_only": false,
          "builder": {
            "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
            "fee_bps": 25
          },
          "negrisk_aware": false,
          "decision": {
            "z_score": 3.1,
            "price_at_entry": 0.847,
            "stop_price": 0.862,
            "exit_deadline_ms": 1746790320000,
            "reasons": [
              "MEAN_REVERSION_FADE_INITIATED"
            ]
          },
          "comment": "fees are operator-set at match time in V2 \u2014 feeRateBps is NOT on the signed order"
        }
      },
      {
        "label": "DecisionReport \u2014 skipped (news cycle active)",
        "payload": {
          "report_id": "dr_01HXMRS999ZZZZ",
          "bot_id": "strat.mean_reversion_sniper",
          "market_id": "0xmrsniper00000000000000000000000000000000000000000000000000000001",
          "intent_emitted": false,
          "z_score": 2.8,
          "reasons": [
            "MEAN_REVERSION_NEWS_ACTIVE"
          ],
          "sampled": false,
          "evaluated_at_ms": 1746790201000
        }
      }
    ]
  },
  "reason_codes": [
    {
      "code": "MEAN_REVERSION_FADE_INITIATED",
      "severity": "INFO",
      "meaning": "z_score >= z_score_min, price >= price_threshold, news gate clear, reversal signal present. Fade sell IOC emitted.",
      "action": "Emit sell IOC OrderIntent; register position for stop/time-exit monitoring.",
      "user_message": "A short sell was placed to capture an expected price reversal."
    },
    {
      "code": "MEAN_REVERSION_Z_TOO_LOW",
      "severity": "INFO",
      "meaning": "z_score is below the 1.0 hard floor. Price move is within normal variation; no fade warranted.",
      "action": "Skip; emit sampled DecisionReport.",
      "user_message": "The recent price move was not extreme enough to fade."
    },
    {
      "code": "MEAN_REVERSION_Z_MARGINAL",
      "severity": "WARN",
      "meaning": "z_score is between 1.0 and 2.5. Fade is marginal; size reduced to 50%.",
      "action": "Emit IOC at 50% size; log warning.",
      "user_message": "A moderate price spike was detected. A smaller fade order was placed."
    },
    {
      "code": "MEAN_REVERSION_PRICE_TOO_HIGH",
      "severity": "HARD_REJECT",
      "meaning": "Price >= 0.95 hard floor. Market is near certain resolution; fading is prohibited.",
      "action": "Skip; no OrderIntent; emit DecisionReport.",
      "user_message": "The market price is too close to certain resolution to fade safely."
    },
    {
      "code": "MEAN_REVERSION_NEWS_ACTIVE",
      "severity": "HARD_REJECT",
      "meaning": "NewsIngest signals an active material news cycle on this market's entity. Fading is blocked.",
      "action": "Skip; no OrderIntent; emit DecisionReport.",
      "user_message": "An active news event is affecting this market. No fade order was placed."
    },
    {
      "code": "MEAN_REVERSION_STOP_LOSS",
      "severity": "WARN",
      "meaning": "Price rose above stop_price (entry + stop_bps/10000). Stop-loss triggered; position closed.",
      "action": "Emit buy IOC to close; emit DecisionReport.",
      "user_message": "The price continued to rise. The stop-loss automatically closed the position."
    },
    {
      "code": "MEAN_REVERSION_TIME_EXIT",
      "severity": "INFO",
      "meaning": "Holding period exceeded time_exit_s. Time exit triggered; position closed.",
      "action": "Emit buy IOC to close; emit DecisionReport.",
      "user_message": "The maximum holding time elapsed. The position was automatically closed."
    },
    {
      "code": "MEAN_REVERSION_HIGH_PRICE_THRESHOLD",
      "severity": "WARN",
      "meaning": "price_threshold config is above 0.90 (warning). Fading near-resolution markets carries elevated risk.",
      "action": "Allow but log warning; require higher z_score.",
      "user_message": ""
    },
    {
      "code": "STALE_MARKET_DATA",
      "severity": "HARD_REJECT",
      "meaning": "ws_market feed or NewsIngest feed is stale or unavailable.",
      "action": "Skip; no OrderIntent emitted.",
      "user_message": "Market data was too old to act on safely."
    },
    {
      "code": "KILL_SWITCH_ACTIVE",
      "severity": "HARD_REJECT",
      "meaning": "Global kill switch is active.",
      "action": "Close open positions; no new OrderIntents.",
      "user_message": "Trading is currently paused."
    }
  ],
  "metrics": {
    "emitted": [
      {
        "name": "polytraders_strat_mrsniper_decisions_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "verdict",
          "reason_code"
        ],
        "meaning": "Total evaluation cycles by intent_emitted and reason code."
      },
      {
        "name": "polytraders_strat_mrsniper_z_score",
        "type": "histogram",
        "unit": "sigma",
        "labels": [],
        "meaning": "Distribution of z-scores at evaluation time across all checked markets."
      },
      {
        "name": "polytraders_strat_mrsniper_position_hold_s",
        "type": "histogram",
        "unit": "seconds",
        "labels": [
          "exit_reason"
        ],
        "meaning": "Duration of open fade positions by exit reason (stop_loss, time_exit, profit_take)."
      },
      {
        "name": "polytraders_strat_mrsniper_news_gate_blocks_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "market_id"
        ],
        "meaning": "Number of fade attempts blocked by NewsIngest gate."
      },
      {
        "name": "polytraders_strat_mrsniper_intents_emitted_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "side"
        ],
        "meaning": "Total OrderIntents emitted by side (sell=fade open, buy=close)."
      },
      {
        "name": "polytraders_strat_mrsniper_eval_latency_ms",
        "type": "histogram",
        "unit": "milliseconds",
        "labels": [],
        "meaning": "Wall-clock time from price tick to OrderIntent emit."
      }
    ],
    "alerts": [
      {
        "name": "MrSniperHighNewsGateBlockRate",
        "condition": "rate(polytraders_strat_mrsniper_news_gate_blocks_total[10m]) > 10",
        "severity": "warn",
        "runbook": "#runbook-mrsniper-news-gate"
      },
      {
        "name": "MrSniperHighStopLossRate",
        "condition": "rate(polytraders_strat_mrsniper_position_hold_s_bucket{exit_reason='stop_loss'}[15m]) / rate(polytraders_strat_mrsniper_position_hold_s_count[15m]) > 0.5",
        "severity": "warn",
        "runbook": "#runbook-mrsniper-stop-loss"
      },
      {
        "name": "MrSniperStaleFeed",
        "condition": "rate(polytraders_strat_mrsniper_decisions_total{reason_code='STALE_MARKET_DATA'}[5m]) > 0.1",
        "severity": "warn",
        "runbook": "#runbook-mrsniper-stale-feed"
      },
      {
        "name": "MrSniperKillSwitchBlocking",
        "condition": "rate(polytraders_strat_mrsniper_decisions_total{reason_code='KILL_SWITCH_ACTIVE'}[1m]) > 0",
        "severity": "page",
        "runbook": "#runbook-killswitch"
      }
    ],
    "dashboards": [
      "Grafana \u2014 Strategy / MeanReversionSniper z-score distribution and fade frequency",
      "Grafana \u2014 Strategy / MeanReversionSniper position hold times and exit reasons"
    ],
    "log_level": "info"
  },
  "state": {
    "store": "redis",
    "shape": "Per-market open position (entry_price, size_pusd, stop_price, exit_deadline_ms) and last z-score reading; keyed by market_id",
    "ttl": "5m for z-score cache; position records persistent until closed",
    "recovery": "On cold start, open positions are re-read from clob_auth. z-score cache is rebuilt from first ws_market tape ticks.",
    "size_estimate": "~200 bytes per active fade position; typically < 500 KB total"
  },
  "concurrency": {
    "execution_model": "single-threaded event loop",
    "max_in_flight": 20,
    "idempotency_key": "intent_id",
    "timeout_ms": 150,
    "backpressure": "drop oldest pending price tick per market_id when queue depth > 5",
    "locking": "per-market_id mutex for open position state read/write"
  },
  "dependencies": {
    "depends_on": [
      {
        "bot_id": "risk.kill_switch",
        "why": "Checked first; closes open positions and blocks new intent emission when active."
      }
    ],
    "emits_to": [
      {
        "bot_id": "risk.portfolio_guard",
        "what": "Sell/buy IOC OrderIntents for risk guardrail evaluation before SmartRouter."
      },
      {
        "bot_id": "gov.builder_attribution",
        "what": "builder.code bytes32 on every OrderIntent."
      }
    ],
    "sibling": [],
    "external": [
      {
        "service": "Polymarket CLOB WebSocket (ws_market)",
        "sla": "best-effort",
        "fallback": "On disconnect, close open positions immediately (fail-closed); no new fade intents until feed recovers."
      },
      {
        "service": "NewsIngest (internal feed)",
        "sla": "internal SLA",
        "fallback": "If unavailable, treat as news-active; block all fade intents until feed is confirmed live."
      },
      {
        "service": "Polymarket CLOB public API (depth)",
        "sla": "99.9%",
        "fallback": "Use cached depth; if cache > 5s, skip fade intent."
      }
    ]
  },
  "security_surfaces": {
    "signs_orders": true,
    "private_key_access": "signing-only",
    "abuse_vectors": [
      "Adversary manufactures a price spike to trigger the fade, then reverses to profit from the short position",
      "Delayed NewsIngest feed allows fading during a genuine news event",
      "Open fade position exploited if stop-loss level is known"
    ],
    "mitigations": [
      "z_score threshold filters out manufactured spikes lacking sustained momentum",
      "NewsIngest unavailability treated as active-news (fail-closed); no fade without confirmed clear news gate",
      "Stop-loss level computed from entry price + stop_bps; not exposed externally",
      "V2 order timestamp(ms) invalidates replayed signed orders",
      "IOC orders do not rest on the book; not visible to adversaries post-emission"
    ],
    "contract_calls": [
      {
        "contract": "CTFExchangeV2",
        "function": "matchOrders",
        "purpose": "Settlement of YES token sales (fade open) and purchases (close/stop-loss)."
      }
    ]
  },
  "failure_injection": [
    {
      "scenario": "STALE_WS_FEED",
      "how_to_inject": "Pause ws_market WebSocket; let last_seen age beyond 5s",
      "expected_behaviour": "STALE_MARKET_DATA; open positions closed via emergency IOC; no new intents",
      "recovery": "Automatic on WebSocket reconnect."
    },
    {
      "scenario": "NEWS_GATE_UNAVAILABLE",
      "how_to_inject": "Disable NewsIngest internal feed",
      "expected_behaviour": "MEAN_REVERSION_NEWS_ACTIVE on all markets (fail-closed); no fade intents until feed restored",
      "recovery": "Automatic when NewsIngest feed is restored."
    },
    {
      "scenario": "STOP_LOSS_TRIGGER",
      "how_to_inject": "Set mock price to entry_price + stop_bps/10000 + 0.001 after fade is placed",
      "expected_behaviour": "MEAN_REVERSION_STOP_LOSS; buy IOC emitted immediately to close; position cleared from state",
      "recovery": "Automatic; position closed."
    },
    {
      "scenario": "TIME_EXIT_TRIGGER",
      "how_to_inject": "Advance system clock past exit_deadline_ms with open position",
      "expected_behaviour": "MEAN_REVERSION_TIME_EXIT; buy IOC emitted; position cleared",
      "recovery": "Automatic; position closed."
    },
    {
      "scenario": "KILL_SWITCH_ON",
      "how_to_inject": "Set killswitch.active=true with open fade position",
      "expected_behaviour": "All open positions closed via buy IOC; no new fades",
      "recovery": "Automatic on manual KillSwitch reset."
    }
  ],
  "runbook": {
    "summary": "Mean-Reversion Sniper incidents are typically high stop-loss rates (genuine resolution signals being faded), NewsIngest feed outages (causing no-fade lockout), or stale ws_market feeds. High stop-loss rates require immediate review of market conditions.",
    "oncall_actions": [
      {
        "alert": "MrSniperHighNewsGateBlockRate",
        "first_action": "Check NewsIngest feed health and event-density signal latency.",
        "escalate_to": "Strategy pod lead if gate is blocking on stale news events."
      },
      {
        "alert": "MrSniperHighStopLossRate",
        "first_action": "Review the markets triggering stop-losses; confirm they are not entering genuine resolution.",
        "escalate_to": "Risk pod lead if stop-loss rate > 50% over 15 minutes on active markets."
      },
      {
        "alert": "MrSniperStaleFeed",
        "first_action": "Check ws_market WebSocket connectivity.",
        "escalate_to": "Infra on-call if disconnected > 2 minutes."
      },
      {
        "alert": "MrSniperKillSwitchBlocking",
        "first_action": "Confirm KillSwitch activation was intentional.",
        "escalate_to": "Risk pod lead immediately."
      }
    ],
    "manual_overrides": [
      {
        "name": "exclude_market",
        "how": "Add market_id to config.excluded_markets",
        "when": "Market is near resolution or showing genuine directional information flow rather than overreaction."
      },
      {
        "name": "pause_bot",
        "how": "polytraders bot pause strat.mean_reversion_sniper",
        "when": "High stop-loss rate across multiple markets suggesting systematic genuine resolution signals."
      }
    ],
    "healthcheck": "GET /internal/health/mean-reversion-sniper -> 200 if ws_market feed last_seen < 5s, NewsIngest feed live, KillSwitch inactive, Redis reachable."
  },
  "promotion_gates": {
    "to_shadow": [
      {
        "gate": "All unit tests pass including news gate fail-closed and stop-loss trigger",
        "how_measured": "CI test run",
        "threshold": "100% pass"
      },
      {
        "gate": "feeRateBps absence verified; tif=IOC verified in integration test",
        "how_measured": "Integration test asserting V2 order schema",
        "threshold": "Pass"
      }
    ],
    "to_limited_live": [
      {
        "gate": "p99 eval latency < 150ms over 24h",
        "how_measured": "polytraders_strat_mrsniper_eval_latency_ms histogram",
        "threshold": "p99 < 150ms"
      },
      {
        "gate": "Stop-loss rate < 30% of all closed positions over 48h shadow run",
        "how_measured": "polytraders_strat_mrsniper_position_hold_s_bucket exit_reason histogram",
        "threshold": "< 30%"
      }
    ],
    "to_general_live": [
      {
        "gate": "E2E: price spike detected \u2192 z_score computed \u2192 news gate clear \u2192 sell IOC OrderIntent submitted on Polygon testnet",
        "how_measured": "E2E test",
        "threshold": "Pass"
      },
      {
        "gate": "Time exit and stop-loss scenarios verified in integration test",
        "how_measured": "Integration test",
        "threshold": "Pass"
      }
    ]
  },
  "reporting": {
    "emits_kinds": [
      "DecisionReport"
    ],
    "topics": [
      "polytraders.reports.decision"
    ],
    "cadence": "every-event",
    "retention_class": "2y",
    "sampling_rule": "emit-every for emitted intents; sample-1/100 for skipped no-edge cycles",
    "bus_failure_action": "fail-closed",
    "user_visible": "summary-only",
    "consumes_kinds": [
      "ObservationReport",
      "RiskVote"
    ],
    "reporting_groups": [
      "strategy_decision"
    ]
  },
  "capital_impact": "Direct",
  "v3_status": {
    "phase": 8,
    "phase_name": "Additional strategies",
    "docs": {
      "done": 27,
      "total": 27,
      "state": "done"
    },
    "impl": {
      "done": 0,
      "total": 15,
      "state": "pending"
    },
    "runtime": {
      "done": 0,
      "total": 8,
      "state": "pending"
    },
    "overall": "pending"
  }
}