{
  "schema_version": "1.0.0",
  "bot_id": "3.2",
  "bot_name": "Maker-Wide",
  "slug": "maker-wide",
  "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",
    "applies_to": "Thin-book binary markets where 24h volume < 250,000 pUSD, spread > 2 * edge_bps, and no stale-book condition is detected (time since last fill < stale_book_minutes)",
    "default_mode": "limited_live",
    "user_visible": "Advanced details only",
    "developer_owner": "Polytraders core \u2014 Strategy pod"
  },
  "purpose": "Maker-Wide is a passive market-making strategy designed for thin, illiquid binary markets where the natural spread is wider than Maker-Tight's target. It posts resting maker quotes at a wider edge (80\u2013200 bps from mid), a smaller clip size, and with a longer expected holding period. The stale-book detector (time since last fill > stale_book_minutes) prevents continuous requoting on markets with no real two-way flow. Volatility of mid over the last hour determines the spread width dynamically: higher vol \u2192 wider edge. This is a user-controlled liquidity-provision tool. Maker rebates (20\u201325% of platform fees, paid in pUSD) contribute to the return profile. No performance claims are made.",
  "why_it_matters": [
    {
      "failure": "Quoting on a stale book (no fills for > stale_book_minutes)",
      "consequence": "Resting orders sit on a market with no real two-way activity. If news arrives and moves the market, the stale quotes are adversely selected at a wide loss."
    },
    {
      "failure": "Edge too narrow for wide-spread illiquid markets",
      "consequence": "In a thin book, the spread can move by 100\u2013200 bps in a single fill. A narrow edge does not cover this adverse-selection risk."
    },
    {
      "failure": "Inventory accumulates without rebalancing",
      "consequence": "On a thin market with one-sided flow, the bot can accumulate a large directional inventory before the inventory skew mechanism has time to work. Hard inventory cap prevents runaway accumulation."
    },
    {
      "failure": "feeRateBps present on signed maker order (V1 pattern)",
      "consequence": "CTFExchangeV2 rejects orders with feeRateBps. Maker fees are operator-set at match time; the signed order must not contain this field. Maker fee_bps is capped at 50 bps."
    }
  ],
  "polymarket_inputs": [
    {
      "input": "Live order book \u2014 best bid, best ask, mid-price",
      "source": "ws_market (CLOB WebSocket)",
      "required": true,
      "use": "Compute current spread and mid-price; derive dynamic edge width from mid-volatility."
    },
    {
      "input": "Time since last fill on the market",
      "source": "clob_public (fills endpoint)",
      "required": true,
      "use": "Stale-book detector: if time since last fill > stale_book_minutes, skip requoting."
    },
    {
      "input": "Hourly mid-price volatility",
      "source": "ws_market (trade tape, 60-min rolling)",
      "required": true,
      "use": "Set dynamic edge: edge_bps = base_edge_bps * (1 + vol_multiplier * hourly_vol)."
    },
    {
      "input": "Running inventory position on each market",
      "source": "clob_auth (open positions)",
      "required": true,
      "use": "Compute inventory ratio; apply hard inventory cap; skew quotes to reduce open inventory."
    }
  ],
  "internal_inputs": [
    {
      "input": "KillSwitch active flag",
      "source": "KillSwitch",
      "required": true,
      "use": "Cancel all open maker quotes and emit no new OrderIntents if KillSwitch is active."
    },
    {
      "input": "Builder code bytes32",
      "source": "internal config",
      "required": true,
      "use": "Injected into builder field on every maker OrderIntent. Maker fee_bps <= 50."
    }
  ],
  "raw_params": [
    "edge_bps \u00b7 int (80\u2013200)",
    "clip_size_usd \u00b7 int (small)",
    "hard_inventory_cap_usd \u00b7 int",
    "stale_book_minutes \u00b7 int"
  ],
  "parameters": [
    {
      "name": "edge_bps",
      "default": 120,
      "warning": 60,
      "hard": 30,
      "controls": "Base edge in basis points from mid-price at which the bot will post a wide-spread quote. Actual edge is scaled upward by hourly mid volatility.",
      "why_default_matters": "120 bps covers expected adverse selection on thin illiquid markets where the spread can move 100+ bps on a single fill. Below 60 bps the edge is unlikely to persist; below 30 bps the bot will not quote regardless.",
      "threshold_logic": [
        {
          "condition": ">= 120 bps",
          "action": "Post wide quotes at mid +/- edge_bps/2"
        },
        {
          "condition": "60\u2013120 bps",
          "action": "WARN MAKER_WIDE_EDGE_MARGINAL; post at 50% clip size"
        },
        {
          "condition": "< 30 bps (hard floor)",
          "action": "SKIP \u2014 MAKER_WIDE_SPREAD_TOO_TIGHT"
        }
      ],
      "dev_check": "if edge_bps < params.hard: return skip('MAKER_WIDE_SPREAD_TOO_TIGHT')",
      "user_facing": "The market spread is too narrow for wide-spread maker quoting. Quoting was skipped for this cycle."
    },
    {
      "name": "clip_size_usd",
      "default": 50,
      "warning": 100,
      "hard": 200,
      "controls": "Size in pUSD of each individual wide maker quote (bid or ask). Kept small to limit adverse-selection exposure on thin books.",
      "why_default_matters": "50 pUSD per side is modest for thin markets where a single fill can move the book significantly. 200 pUSD is the hard cap to prevent over-concentration in illiquid positions.",
      "threshold_logic": [
        {
          "condition": "<= 50 pUSD",
          "action": "Normal small clip quote"
        },
        {
          "condition": "50\u2013200 pUSD",
          "action": "WARN; confirm thin-book depth supports this size"
        },
        {
          "condition": "> 200 pUSD",
          "action": "Reject config \u2014 PARAMETER_CHANGE_REQUIRES_APPROVAL"
        }
      ],
      "dev_check": "if params.clip_size_usd > params.hard: raise ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL')",
      "user_facing": "Quote was sized to keep risk small on this low-activity market."
    },
    {
      "name": "hard_inventory_cap_usd",
      "default": 300,
      "warning": 200,
      "hard": 500,
      "controls": "Maximum total pUSD inventory on a single market. If inventory exceeds this, the bot stops quoting until inventory reduces via natural fill.",
      "why_default_matters": "300 pUSD on a thin market represents meaningful one-sided exposure. The hard cap prevents runaway accumulation in the absence of two-way flow.",
      "threshold_logic": [
        {
          "condition": "<= 300 pUSD",
          "action": "Normal quoting"
        },
        {
          "condition": "200\u2013300 pUSD",
          "action": "WARN MAKER_WIDE_HIGH_INVENTORY; reduce clip to minimum"
        },
        {
          "condition": "> 500 pUSD",
          "action": "Hard cap; stop quoting on market until inventory falls below warning threshold"
        }
      ],
      "dev_check": "if inventory_usd >= params.hard_inventory_cap_usd: return skip('MAKER_WIDE_INVENTORY_CAP_HIT')",
      "user_facing": "This market's current position is at the maximum allowed size. No new quotes were placed until the position reduces."
    },
    {
      "name": "stale_book_minutes",
      "default": 15,
      "warning": 30,
      "hard": 60,
      "controls": "Maximum time in minutes since the last fill before the book is considered stale and quoting is paused.",
      "why_default_matters": "15 minutes without a fill on a thin market indicates negligible real two-way interest. Quoting into a completely inactive book has no maker rebate benefit and high adverse-selection risk when interest returns.",
      "threshold_logic": [
        {
          "condition": "< 15 min since last fill",
          "action": "Active book; quote normally"
        },
        {
          "condition": "15\u201360 min",
          "action": "WARN MAKER_WIDE_STALE_BOOK; continue quoting with reduced clip"
        },
        {
          "condition": "> 60 min (hard floor)",
          "action": "SKIP \u2014 MAKER_WIDE_BOOK_TOO_STALE; cancel open quotes"
        }
      ],
      "dev_check": "if time_since_last_fill_min > params.hard: return skip('MAKER_WIDE_BOOK_TOO_STALE')",
      "user_facing": "This market hasn't had any recent trading activity. Quoting was paused to avoid posting into a stale book."
    }
  ],
  "default_config": {
    "bot_id": "strat.maker_wide",
    "version": "2.1.0",
    "mode": "limited_live",
    "defaults": {
      "edge_bps": 120,
      "clip_size_usd": 50,
      "hard_inventory_cap_usd": 300,
      "stale_book_minutes": 15
    },
    "locked": {
      "edge_bps": {
        "min": 30
      },
      "clip_size_usd": {
        "max": 200
      },
      "hard_inventory_cap_usd": {
        "max": 500
      },
      "stale_book_minutes": {
        "max": 60
      }
    }
  },
  "implementation_flow": [
    "Check KillSwitch active flag; if active, cancel all open maker quotes and emit no new OrderIntents.",
    "Subscribe to ws_market book and trade-tape streams for all eligible thin-book markets.",
    "Filter markets: 24h volume < 250,000 pUSD (thin-book regime). Markets with > 250K volume are handled by Maker-Tight.",
    "On each book tick: compute mid = (best_bid + best_ask) / 2; spread_bps = (best_ask - best_bid) / mid * 10000.",
    "Check stale-book condition: fetch time since last fill from clob_public. If > stale_book_minutes hard (60 min), skip \u2014 MAKER_WIDE_BOOK_TOO_STALE; cancel open quotes.",
    "If time_since_last_fill > 15 min (warning), WARN MAKER_WIDE_STALE_BOOK; reduce clip to 50%.",
    "Compute hourly mid volatility from trade tape. Set dynamic_edge_bps = edge_bps * (1 + 0.5 * hourly_vol / 0.01).",
    "If spread_bps < 2 * dynamic_edge_bps hard floor (30 bps): skip \u2014 MAKER_WIDE_SPREAD_TOO_TIGHT (sampled 1/100).",
    "Fetch running inventory from clob_auth. If inventory_usd >= hard_inventory_cap_usd: skip \u2014 MAKER_WIDE_INVENTORY_CAP_HIT.",
    "Compute inventory skew: bidPrice = mid - dynamic_edge_bps/2/10000 - skew * inventory_ratio; askPrice symmetric.",
    "Emit OrderIntent YES (post_only=true, side=buy, price=bidPrice, tif=GTC, builder={code, fee_bps:30}, size_pUSD=clip_size_usd).",
    "Emit OrderIntent NO  (post_only=true, side=buy, price=1-askPrice, tif=GTC, builder={code, fee_bps:30}, size_pUSD=clip_size_usd).",
    "Note: fees are operator-set at match time in V2 \u2014 feeRateBps is NOT on the signed order. Maker fee_bps <= 50.",
    "Emit DecisionReport with intent_emitted=true, dynamic_edge_bps, time_since_last_fill_min, reason MAKER_WIDE_QUOTING."
  ],
  "decision_logic": {
    "approve": "spread_bps > 2 * dynamic_edge_bps, time_since_last_fill < stale_book_minutes, inventory_usd < hard_inventory_cap_usd, KillSwitch inactive. Emit bid + ask OrderIntents as GTC post_only maker orders.",
    "reshape_required": "Not applicable \u2014 strat bots emit OrderIntents; reshaping is handled downstream by the Risk guardrail pipeline.",
    "reject": "spread too tight; stale book (> 60 min); inventory cap hit; KillSwitch active; stale feed. Cancel open quotes and emit DecisionReport intent_emitted=false.",
    "warning_only": "edge_bps between 30 and 60 bps, or time_since_last_fill between 15 and 60 min, triggers warning and 50% size reduction."
  },
  "decision_output_schema": "OrderIntent",
  "decision_output_example": {
    "intent_id": "oi_01HXMKW0000W01A",
    "trace_id": "tr_01HXMKW0000WTR1",
    "market_id": "0xmakerwide000000000000000000000000000000000000000000000000000001a",
    "outcome": "YES",
    "side": "buy",
    "price": "0.582",
    "size_pUSD": "50.00",
    "tif": "GTC",
    "post_only": true,
    "builder": {
      "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
      "fee_bps": 30
    },
    "negrisk_aware": false,
    "decision": {
      "dynamic_edge_bps": 132.0,
      "time_since_last_fill_min": 4.2,
      "inventory_ratio": 0.08,
      "skew_applied": true,
      "reasons": [
        "MAKER_WIDE_QUOTING"
      ]
    },
    "comment": "fees are operator-set at match time in V2 \u2014 feeRateBps is NOT on the signed order"
  },
  "developer_log": {
    "bot_id": "strat.maker_wide",
    "market_id": "0xmakerwide000000000000000000000000000000000000000000000000000001a",
    "mid": 0.588,
    "best_bid": 0.57,
    "best_ask": 0.606,
    "spread_bps": 61.2,
    "base_edge_bps": 120,
    "hourly_vol": 0.018,
    "dynamic_edge_bps": 132.0,
    "time_since_last_fill_min": 4.2,
    "inventory_usd": 24.0,
    "inventory_ratio": 0.08,
    "skew_applied": true,
    "clip_size_pusd": 50.0,
    "intent_emitted": true,
    "reason": "MAKER_WIDE_QUOTING",
    "emitted_at_ms": 1746790400000
  },
  "user_explanations": [
    {
      "situation": "Wide maker quote posted",
      "message": "A resting order was placed at a wider spread on this low-activity market to provide liquidity. The quote will fill if a taker crosses the price."
    },
    {
      "situation": "Quote skipped \u2014 stale book",
      "message": "This market hasn't had any trades recently. Quoting was paused to avoid posting into a potentially stale book."
    },
    {
      "situation": "Quote skipped \u2014 inventory cap reached",
      "message": "The current position on this market is at the maximum allowed size. No new quotes were placed until the position reduces via natural trading."
    },
    {
      "situation": "Spread dynamically widened",
      "message": "Recent price volatility on this market caused the quote spread to widen automatically to provide more protection against adverse selection."
    }
  ],
  "failure_modes": {
    "main_failure_mode": "Adverse selection on news arrival: stale wide-spread quotes get hit by informed takers when an event moves the market sharply, producing a loss larger than the accumulated maker rebates.",
    "false_positive_risk": "Stale-book detector is too sensitive, blocking quoting on thin markets that do have valid two-way interest at infrequent intervals.",
    "false_negative_risk": "stale_book_minutes set too high causes the bot to continue quoting on truly inactive markets, accumulating positions without offsetting natural flow.",
    "safe_fallback": "If book data is stale (last_seen > 5s) or clob_auth position read fails, cancel all open quotes and emit no new OrderIntents. Hard inventory cap prevents runaway accumulation.",
    "required_dependencies": [
      "ws_market book and trade-tape stream",
      "clob_public fills endpoint (time since last fill)",
      "clob_auth open positions",
      "KillSwitch active flag",
      "internal builder code"
    ]
  },
  "acceptance_tests": {
    "unit": [
      {
        "test": "Post bid+ask when spread > 2*dynamic_edge_bps and book is fresh",
        "setup": "spread=250bps, base_edge=120bps, time_since_fill=4min, inventory=0",
        "expected": "Two OrderIntents (bid + ask) emitted with post_only=true, GTC"
      },
      {
        "test": "Skip when time_since_last_fill > hard limit (60 min)",
        "setup": "time_since_last_fill_min=75",
        "expected": "No OrderIntents; open quotes cancelled; reason=MAKER_WIDE_BOOK_TOO_STALE"
      },
      {
        "test": "Skip when inventory_usd >= hard_inventory_cap_usd",
        "setup": "inventory_usd=310, hard_inventory_cap_usd=300",
        "expected": "No OrderIntents; reason=MAKER_WIDE_INVENTORY_CAP_HIT"
      },
      {
        "test": "Dynamic edge widens with high volatility",
        "setup": "base_edge_bps=120, hourly_vol=0.03",
        "expected": "dynamic_edge_bps > 120; quotes placed at wider spread"
      },
      {
        "test": "Reduce clip 50% when edge_bps marginal (50 bps)",
        "setup": "edge_bps=50, clip_size_usd=50",
        "expected": "OrderIntents emitted with size=25; WARN MAKER_WIDE_EDGE_MARGINAL"
      },
      {
        "test": "Skip when KillSwitch active; cancel open quotes",
        "setup": "killswitch.active=true",
        "expected": "No new OrderIntents; cancellation of open quotes emitted"
      }
    ],
    "integration": [
      {
        "test": "Full cycle: ws_market tick \u2192 stale-book check \u2192 dynamic edge \u2192 two signed V2 post_only GTC OrderIntents",
        "expected": "Both orders have builder.code (bytes32), no feeRateBps, post_only=true, EIP-712 domain version '2'"
      },
      {
        "test": "Stale book detection cancels open quotes and emits MAKER_WIDE_BOOK_TOO_STALE",
        "expected": "Open quote IDs cancelled; DecisionReport intent_emitted=false"
      }
    ],
    "property": [
      {
        "property": "post_only=true on every maker OrderIntent; bot never submits taker orders",
        "required": "Always true"
      },
      {
        "property": "feeRateBps never present on any signed OrderIntent",
        "required": "Always true \u2014 V2 fees are operator-set at match time"
      },
      {
        "property": "Inventory never exceeds hard_inventory_cap_usd on any single market",
        "required": "Always true"
      }
    ]
  },
  "checklist_overrides": {},
  "legacy_goal": "Same shape as Maker-Tight, but for thin books \u2014 wider spreads, smaller size, longer rests.",
  "legacy_pm_signals": [
    "Stale-book detector: time since last fill",
    "Volatility-of-mid in last hour (sets spread width)"
  ],
  "legacy_external_feeds": [],
  "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, maker fee_bps <= 50)",
      "reason": "CLOB V2 cutover",
      "action_taken": "Switched to py-clob-client-v2. Removed feeRateBps from all signed maker order construction. Updated collateral denomination to pUSD. Injected builder field (bytes32) on every OrderIntent. Confirmed maker fee_bps cap of 50 bps. EIP-712 Exchange domain version updated from '1' to '2'. Stale-book detection extended to use minute-level resolution for thin books."
    }
  ],
  "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": "All maker orders use post_only=true to qualify for maker rebates (20-25% of platform fees, paid in pUSD). builder.fee_bps is capped at 30 bps, within the V2 maker maximum of 50 bps. feeRateBps is not present on any signed order \u2014 operator-set at match time."
  },
  "reference_implementation": {
    "summary": "Subscribes to CLOB WebSocket book and trade-tape streams for thin-book markets, checks spread, stale-book condition, and inventory cap, then emits GTC post_only maker OrderIntents inside the touch at dynamically widened edge.",
    "language_note": "Pseudocode is language-agnostic. FETCH = read input. EMIT = produce output. Translate to TS/Python/Go/Rust.",
    "pseudocode": "FUNCTION onBookTick(market_id, bookTick):\n  // --- 0. KillSwitch gate ---\n  ks = FETCH internal.killswitch.status\n  IF ks.active:\n    CANCEL_OPEN_QUOTES(market_id)\n    RETURN\n\n  // --- 1. Stale-book check ---\n  fills = FETCH clob_public.GET('/markets/' + market_id + '/fills?limit=1')\n  timeSinceFillMin = (now_ms() - fills[0].timestamp_ms) / 60000\n  IF timeSinceFillMin > params.stale_book_minutes_hard:  // 60 min\n    CANCEL_OPEN_QUOTES(market_id)\n    EMIT DecisionReport(intent_emitted=false, reason='MAKER_WIDE_BOOK_TOO_STALE')\n    RETURN\n\n  // --- 2. Inventory cap check ---\n  position = FETCH clob_auth.GET('/positions?market=' + market_id)\n  IF position.notional_usd >= params.hard_inventory_cap_usd:\n    EMIT DecisionReport(intent_emitted=false, reason='MAKER_WIDE_INVENTORY_CAP_HIT')\n    RETURN\n\n  // --- 3. Dynamic edge from volatility ---\n  tape = FETCH ws_market.tradeTape(market_id, window_min=60)\n  hourlyVol = stddev(tape.mid_prices)\n  dynamicEdgeBps = params.edge_bps * (1 + 0.5 * hourlyVol / 0.01)\n  IF dynamicEdgeBps < params.edge_bps_hard:  // 30 bps\n    IF random() < 0.01:\n      EMIT DecisionReport(intent_emitted=false, reason='MAKER_WIDE_SPREAD_TOO_TIGHT')\n    RETURN\n\n  // --- 4. Warning thresholds ---\n  clipSize = toPusdUnits(params.clip_size_usd)\n  IF dynamicEdgeBps < 60 OR timeSinceFillMin > 15:\n    WARN('MAKER_WIDE_EDGE_MARGINAL' OR 'MAKER_WIDE_STALE_BOOK')\n    clipSize = toPusdUnits(params.clip_size_usd * 0.5)\n\n  // --- 5. Inventory skew ---\n  inventoryRatio = position.notional_usd / params.hard_inventory_cap_usd\n  skew = 0.3 * inventoryRatio\n  mid = (bookTick.best_bid + bookTick.best_ask) / 2\n  edgeHalf = dynamicEdgeBps / 2 / 10000\n  bidPrice = round(mid - edgeHalf - skew, 4)\n  askPrice = round(mid + edgeHalf - skew, 4)\n\n  // --- 6. Emit wide maker OrderIntents (V2: no feeRateBps; post_only=true) ---\n  EMIT OrderIntent(\n    market_id = market_id, outcome = 'YES', side = 'buy',\n    price = bidPrice, size_pUSD = clipSize, tif = 'GTC', post_only = true,\n    builder = {code: internal.builder_code, fee_bps: 30}\n  )\n  EMIT OrderIntent(\n    market_id = market_id, outcome = 'NO', side = 'buy',\n    price = round(1 - askPrice, 4), size_pUSD = clipSize, tif = 'GTC', post_only = true,\n    builder = {code: internal.builder_code, fee_bps: 30}\n  )\n  EMIT DecisionReport(intent_emitted=true, dynamic_edge_bps=dynamicEdgeBps,\n                      time_since_last_fill_min=timeSinceFillMin, reason='MAKER_WIDE_QUOTING')",
    "sdk_calls": [
      "ws_market.subscribe('book', [market_id])",
      "ws_market.subscribe('trade_tape', [market_id])",
      "fetchClobPublic('/markets/' + market_id + '/fills?limit=1')",
      "clob_auth.GET('/positions?market=' + market_id)",
      "toPusdUnits(rawFloat)",
      "buildOrderTypedData(orderParams, { name: 'CTFExchange', version: '2', chainId: 137 })",
      "internal.killswitch.status()",
      "internal.builder_code"
    ],
    "complexity": "O(1) per market book tick"
  },
  "wire_examples": {
    "input": [
      {
        "label": "WebSocket book tick \u2014 thin market, spread 250 bps, fresh book",
        "source": "ws_market",
        "payload": {
          "market_id": "0xmakerwide000000000000000000000000000000000000000000000000000001a",
          "best_bid": "0.570",
          "best_ask": "0.606",
          "mid": "0.588",
          "spread_bps": "61.2",
          "volume_24h_pusd": "42000",
          "time_since_last_fill_min": "4.2",
          "hourly_vol": "0.018",
          "received_at_ms": 1746790400000
        }
      }
    ],
    "output": [
      {
        "label": "OrderIntent \u2014 wide maker bid (YES buy, post_only, GTC)",
        "payload": {
          "intent_id": "oi_01HXMKW0000W01A",
          "trace_id": "tr_01HXMKW0000WTR1",
          "market_id": "0xmakerwide000000000000000000000000000000000000000000000000000001a",
          "outcome": "YES",
          "side": "buy",
          "price": "0.582",
          "size_pUSD": "50.00",
          "tif": "GTC",
          "post_only": true,
          "builder": {
            "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
            "fee_bps": 30
          },
          "negrisk_aware": false,
          "decision": {
            "dynamic_edge_bps": 132.0,
            "time_since_last_fill_min": 4.2,
            "inventory_ratio": 0.08,
            "skew_applied": true,
            "reasons": [
              "MAKER_WIDE_QUOTING"
            ]
          },
          "comment": "fees are operator-set at match time in V2 \u2014 feeRateBps is NOT on the signed order"
        }
      },
      {
        "label": "DecisionReport \u2014 skipped (book too stale), sampled 1/100",
        "payload": {
          "report_id": "dr_01HXMKW999ZZZZ",
          "bot_id": "strat.maker_wide",
          "market_id": "0xmakerwide000000000000000000000000000000000000000000000000000001a",
          "intent_emitted": false,
          "time_since_last_fill_min": 82.0,
          "reasons": [
            "MAKER_WIDE_BOOK_TOO_STALE"
          ],
          "sampled": true,
          "evaluated_at_ms": 1746790401000
        }
      }
    ]
  },
  "reason_codes": [
    {
      "code": "MAKER_WIDE_QUOTING",
      "severity": "INFO",
      "meaning": "Spread is adequate, book is fresh, inventory below cap. Wide maker bid+ask OrderIntents emitted.",
      "action": "Emit two GTC post_only OrderIntents.",
      "user_message": "Wide maker quotes were posted inside the market spread."
    },
    {
      "code": "MAKER_WIDE_SPREAD_TOO_TIGHT",
      "severity": "INFO",
      "meaning": "Market spread is narrower than 2 * dynamic_edge_bps hard floor. Wide quoting is not viable.",
      "action": "Skip; emit sampled DecisionReport.",
      "user_message": "The market spread was too tight for wide maker quoting."
    },
    {
      "code": "MAKER_WIDE_BOOK_TOO_STALE",
      "severity": "WARN",
      "meaning": "Time since last fill exceeds stale_book_minutes hard limit (60 min). Book is considered inactive.",
      "action": "Cancel open quotes; emit DecisionReport intent_emitted=false.",
      "user_message": "This market has had no recent trading activity. Quotes were paused."
    },
    {
      "code": "MAKER_WIDE_STALE_BOOK",
      "severity": "WARN",
      "meaning": "Time since last fill is between 15 and 60 minutes (warning threshold). Book activity is low.",
      "action": "Reduce clip to 50%; continue quoting; log warning.",
      "user_message": "This market has had limited recent activity. Quote sizes were reduced."
    },
    {
      "code": "MAKER_WIDE_INVENTORY_CAP_HIT",
      "severity": "WARN",
      "meaning": "Inventory on this market has reached hard_inventory_cap_usd. Quoting is suspended.",
      "action": "Skip; no new OrderIntents; emit DecisionReport.",
      "user_message": "The maximum position size for this market has been reached. No new quotes were placed."
    },
    {
      "code": "MAKER_WIDE_EDGE_MARGINAL",
      "severity": "WARN",
      "meaning": "Dynamic edge is between 30 and 60 bps (warning threshold). Quoting at reduced clip size.",
      "action": "Emit OrderIntents at 50% clip size; log warning.",
      "user_message": "Market conditions are borderline. Maker quotes were posted at reduced size."
    },
    {
      "code": "MAKER_WIDE_HIGH_INVENTORY",
      "severity": "WARN",
      "meaning": "Inventory is between the warning (200 pUSD) and hard cap (300 pUSD). Accumulation risk elevated.",
      "action": "Continue quoting; reduce clip to minimum; log warning.",
      "user_message": "The current position on this market is large. Quote sizes were reduced."
    },
    {
      "code": "STALE_MARKET_DATA",
      "severity": "HARD_REJECT",
      "meaning": "Book snapshot older than 5s or position data unavailable.",
      "action": "Cancel open quotes; no new OrderIntents.",
      "user_message": "Market data was too old. Quotes were cancelled."
    },
    {
      "code": "KILL_SWITCH_ACTIVE",
      "severity": "HARD_REJECT",
      "meaning": "Global kill switch is active.",
      "action": "Cancel all open quotes; no new OrderIntents.",
      "user_message": "Trading is currently paused."
    }
  ],
  "metrics": {
    "emitted": [
      {
        "name": "polytraders_strat_makerwide_decisions_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "verdict",
          "reason_code"
        ],
        "meaning": "Total evaluation cycles by intent_emitted and reason code."
      },
      {
        "name": "polytraders_strat_makerwide_dynamic_edge_bps",
        "type": "histogram",
        "unit": "basis_points",
        "labels": [
          "market_id"
        ],
        "meaning": "Distribution of dynamic edge in bps at each quoting cycle."
      },
      {
        "name": "polytraders_strat_makerwide_inventory_usd",
        "type": "gauge",
        "unit": "pusd",
        "labels": [
          "market_id"
        ],
        "meaning": "Current inventory in pUSD per quoted market."
      },
      {
        "name": "polytraders_strat_makerwide_intents_emitted_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "side"
        ],
        "meaning": "Total wide maker OrderIntents emitted by side (bid/ask)."
      },
      {
        "name": "polytraders_strat_makerwide_stale_book_skips_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "market_id"
        ],
        "meaning": "Quoting cycles skipped due to stale book per market."
      },
      {
        "name": "polytraders_strat_makerwide_eval_latency_ms",
        "type": "histogram",
        "unit": "milliseconds",
        "labels": [],
        "meaning": "Wall-clock latency from book tick to OrderIntent emit."
      }
    ],
    "alerts": [
      {
        "name": "MakerWideHighInventory",
        "condition": "polytraders_strat_makerwide_inventory_usd > 250",
        "severity": "warn",
        "runbook": "#runbook-makerwide-inventory"
      },
      {
        "name": "MakerWideHighStaleBookRate",
        "condition": "rate(polytraders_strat_makerwide_stale_book_skips_total[10m]) > 5",
        "severity": "warn",
        "runbook": "#runbook-makerwide-stale-book"
      },
      {
        "name": "MakerWideStaleFeed",
        "condition": "rate(polytraders_strat_makerwide_decisions_total{reason_code='STALE_MARKET_DATA'}[5m]) > 0.1",
        "severity": "warn",
        "runbook": "#runbook-makerwide-stale-feed"
      },
      {
        "name": "MakerWideKillSwitchBlocking",
        "condition": "rate(polytraders_strat_makerwide_decisions_total{reason_code='KILL_SWITCH_ACTIVE'}[1m]) > 0",
        "severity": "page",
        "runbook": "#runbook-killswitch"
      }
    ],
    "dashboards": [
      "Grafana \u2014 Strategy / MakerWide dynamic edge and stale-book rate",
      "Grafana \u2014 Strategy / MakerWide inventory per market"
    ],
    "log_level": "info"
  },
  "state": {
    "store": "redis",
    "shape": "Per-market last book tick (mid, spread_bps, dynamic_edge_bps, timestamp_ms), time_since_last_fill_min, open quote IDs, and current inventory_usd; keyed by market_id",
    "ttl": "5m for book state; open quote IDs persistent until fill or cancel",
    "recovery": "On cold start, open quote IDs re-fetched from clob_auth. Inventory re-read from clob_auth. Book state rebuilt from first ws_market tick.",
    "size_estimate": "~350 bytes per quoted market; typically < 2 MB total for thin-book universe"
  },
  "concurrency": {
    "execution_model": "actor-per-market",
    "max_in_flight": 30,
    "idempotency_key": "intent_id",
    "timeout_ms": 200,
    "backpressure": "drop oldest pending tick per market_id when queue depth > 3",
    "locking": "per-market_id mutex for inventory state read/write and open quote tracking"
  },
  "dependencies": {
    "depends_on": [
      {
        "bot_id": "risk.kill_switch",
        "why": "Checked first; cancels all open quotes and blocks new intent emission when active."
      }
    ],
    "emits_to": [
      {
        "bot_id": "risk.portfolio_guard",
        "what": "GTC post_only wide maker OrderIntents for risk guardrail evaluation before SmartRouter."
      },
      {
        "bot_id": "gov.builder_attribution",
        "what": "builder.code bytes32 on every OrderIntent for attribution and rebate tracking."
      }
    ],
    "sibling": [
      {
        "bot_id": "strat.maker_tight",
        "note": "Maker-Tight handles liquid markets (volume >= 250K pUSD); Maker-Wide handles thin markets (volume < 250K pUSD). They should not post on the same market simultaneously."
      }
    ],
    "external": [
      {
        "service": "Polymarket CLOB WebSocket (ws_market)",
        "sla": "best-effort",
        "fallback": "On disconnect, cancel open quotes; no new OrderIntents until feed recovers."
      },
      {
        "service": "Polymarket CLOB auth API (positions)",
        "sla": "99.95%",
        "fallback": "If position read fails, cancel open quotes and skip cycle (fail-closed on inventory state)."
      },
      {
        "service": "Polymarket CLOB public API (fills)",
        "sla": "99.9%",
        "fallback": "Use cached last-fill timestamp; if cache > 2 min old, treat as stale-book condition."
      }
    ]
  },
  "security_surfaces": {
    "signs_orders": true,
    "private_key_access": "signing-only",
    "abuse_vectors": [
      "Adversary floods thin book with cancels to advance stale_book timer, then exploits resumed quoting with informed order",
      "Quote stuffing on thin market to repeatedly trigger stale-book detection and prevent Maker-Wide from providing liquidity",
      "Inventory manipulation via spoofed position to pin inventory near hard cap and halt quoting"
    ],
    "mitigations": [
      "Stale-book timer is based on last actual fill timestamp, not last quote or cancel",
      "Inventory read from clob_auth (authenticated), not from ws_market (public feed)",
      "KillSwitch cancels all open quotes immediately on activation",
      "V2 order timestamp(ms) prevents replay of old signed maker orders",
      "post_only=true prevents inadvertent taker fills on latency spikes"
    ],
    "contract_calls": [
      {
        "contract": "CTFExchangeV2",
        "function": "matchOrders",
        "purpose": "Settlement of wide maker YES token purchases/sales when a taker crosses the posted quote."
      }
    ]
  },
  "failure_injection": [
    {
      "scenario": "STALE_WS_FEED",
      "how_to_inject": "Pause ws_market WebSocket; let last_seen age beyond 5s",
      "expected_behaviour": "Open quotes cancelled; no new OrderIntents; STALE_MARKET_DATA DecisionReport",
      "recovery": "Automatic on WebSocket reconnect; quotes re-posted on next clean tick."
    },
    {
      "scenario": "STALE_BOOK_HARD_LIMIT",
      "how_to_inject": "Set mock last fill timestamp to now() - 75 minutes",
      "expected_behaviour": "MAKER_WIDE_BOOK_TOO_STALE; open quotes cancelled; no new OrderIntents",
      "recovery": "Automatic when a fill occurs on the market and stale timer resets."
    },
    {
      "scenario": "INVENTORY_CAP_HIT",
      "how_to_inject": "Set mock position.notional_usd=310 on target market (> hard cap 300)",
      "expected_behaviour": "MAKER_WIDE_INVENTORY_CAP_HIT; no new OrderIntents for that market",
      "recovery": "Automatic when inventory falls below warning threshold via natural fills."
    },
    {
      "scenario": "KILL_SWITCH_ON",
      "how_to_inject": "Set killswitch.active=true",
      "expected_behaviour": "All open quotes cancelled; no new OrderIntents",
      "recovery": "Automatic on manual KillSwitch reset."
    },
    {
      "scenario": "DYNAMIC_EDGE_COLLAPSE",
      "how_to_inject": "Set mock hourly_vol=0 and spread_bps=50bps (below 2*30bps hard floor edge)",
      "expected_behaviour": "MAKER_WIDE_SPREAD_TOO_TIGHT; no OrderIntents; sampled DecisionReport",
      "recovery": "Automatic when spread widens above threshold."
    }
  ],
  "runbook": {
    "summary": "Maker-Wide incidents are typically stale-book triggers (no fills for 60+ min), inventory cap breaches on thin markets, or stale feeds. Stale feeds resolve automatically; inventory cap breaches require monitoring until natural fills reduce the position.",
    "oncall_actions": [
      {
        "alert": "MakerWideHighInventory",
        "first_action": "Check inventory_usd on Grafana for the flagged market. Confirm bot is skewing quotes to reduce inventory.",
        "escalate_to": "Risk pod lead if inventory_usd > 400 pUSD and not decreasing."
      },
      {
        "alert": "MakerWideHighStaleBookRate",
        "first_action": "Review last-fill timestamps on flagged markets. Confirm markets have genuine lack of activity vs. a fills API issue.",
        "escalate_to": "Strategy pod lead if multiple markets simultaneously trigger stale-book during expected active hours."
      },
      {
        "alert": "MakerWideStaleFeed",
        "first_action": "Check ws_market WebSocket connectivity.",
        "escalate_to": "Infra on-call if disconnected > 2 minutes."
      },
      {
        "alert": "MakerWideKillSwitchBlocking",
        "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 showing anomalous thin-book conditions or is under oracle dispute."
      },
      {
        "name": "cancel_all_quotes",
        "how": "polytraders bot cancel-quotes strat.maker_wide --all",
        "when": "Emergency adverse-selection event in progress on a thin market."
      }
    ],
    "healthcheck": "GET /internal/health/maker-wide -> 200 if ws_market feed last_seen < 5s, clob_auth reachable, KillSwitch inactive, and at least one thin-book market quoted in last 5 min."
  },
  "promotion_gates": {
    "to_shadow": [
      {
        "gate": "All unit tests pass including post_only invariant, stale-book detection, and inventory cap",
        "how_measured": "CI test run",
        "threshold": "100% pass"
      },
      {
        "gate": "feeRateBps absence verified; maker fee_bps <= 50 verified in integration test",
        "how_measured": "Integration test asserting V2 order schema",
        "threshold": "Pass"
      }
    ],
    "to_limited_live": [
      {
        "gate": "p99 eval latency < 200ms over 24h",
        "how_measured": "polytraders_strat_makerwide_eval_latency_ms histogram",
        "threshold": "p99 < 200ms"
      },
      {
        "gate": "Inventory stays below hard_inventory_cap_usd on all markets over 48h shadow run",
        "how_measured": "polytraders_strat_makerwide_inventory_usd gauge",
        "threshold": "max < 300 pUSD"
      }
    ],
    "to_general_live": [
      {
        "gate": "E2E: book tick \u2192 stale-book check \u2192 two signed V2 post_only GTC OrderIntents submitted and resting on CLOB testnet",
        "how_measured": "E2E test",
        "threshold": "Pass"
      },
      {
        "gate": "Stale-book cancellation verified: open quotes cancelled on hard-limit trigger",
        "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 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"
  }
}