{
  "schema_version": "1.0.0",
  "bot_id": "3.5",
  "bot_name": "Bregman-Projection Arb",
  "slug": "bregman-projection-arb",
  "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": "Negative-risk multi-outcome CLOB markets where the joint order-book distribution deviates from the nearest Bregman-projection-valid simplex distribution by more than kl_divergence_threshold",
    "default_mode": "limited_live",
    "user_visible": "Advanced details only",
    "developer_owner": "Polytraders core \u2014 Strategy pod"
  },
  "purpose": "Bregman-Projection Arb detects when the joint probability distribution implied by a neg-risk multi-outcome market's order book diverges from the closest valid (simplex-constrained) distribution by more than kl_divergence_threshold nats. It uses a Frank-Wolfe iterative projection to find the nearest valid distribution, measures the KL-divergence of the observed book from that projection, and emits a set of up to max_legs_per_trade OrderIntents to exploit the divergence. This is a user-controlled execution tool for multi-outcome neg-risk markets. It never trades standard 2-outcome binary markets (those are handled by Sum-to-One Arb). No performance claims are made; the tool automates the mechanical detection and sizing of Bregman-projection violations.",
  "why_it_matters": [
    {
      "failure": "KL-divergence computed on stale snapshots",
      "consequence": "The divergence measurement reflects an order book that no longer exists. Emitting OrderIntents based on stale data incurs slippage on all legs and may produce a net loss."
    },
    {
      "failure": "Frank-Wolfe projection does not converge within frank_wolfe_iters",
      "consequence": "The nearest valid distribution is not found; the measured divergence is an upper bound, not the true value. The bot may under-size or skip genuinely profitable trades."
    },
    {
      "failure": "Leg count exceeds max_legs_per_trade while liquidity is thin",
      "consequence": "In a 10-outcome market, all legs may be needed to fully capture the divergence. Capping legs leaves residual exposure that does not hedge cleanly."
    },
    {
      "failure": "NegRisk market settled via DVM while legs are open",
      "consequence": "If a disputed NegRisk outcome enters a 24-48h UMA DVM vote, open positions cannot be settled immediately. The bot must detect the dispute flag and halt new legs on affected markets."
    },
    {
      "failure": "feeRateBps present on signed order (V1 pattern)",
      "consequence": "CTFExchangeV2 rejects orders with feeRateBps. Fees are operator-set at match time. No leg intent may contain this field."
    }
  ],
  "polymarket_inputs": [
    {
      "input": "Full order book for all outcome tokens in the neg-risk market",
      "source": "ws_market (CLOB WebSocket)",
      "required": true,
      "use": "Build observed joint distribution from best-ask prices across all outcome legs."
    },
    {
      "input": "Market condition ID, outcome token IDs, negRisk flag, NegRiskAdapter address",
      "source": "clob_public / onchain",
      "required": true,
      "use": "Confirm market uses NegRiskAdapter and identify all outcome token IDs for the projection."
    },
    {
      "input": "Top-of-book depth for each outcome leg",
      "source": "clob_public",
      "required": true,
      "use": "Size each leg to min(depth_available, liquidity_cap_usd / n_legs)."
    },
    {
      "input": "Market open/closed/resolved/dispute status",
      "source": "clob_public",
      "required": true,
      "use": "Skip markets in UMA DVM dispute or resolution; halt new legs immediately."
    },
    {
      "input": "Historical co-movement matrix (internal feed)",
      "source": "internal",
      "required": false,
      "use": "Optional prior for initialising the Frank-Wolfe projection; speeds up convergence."
    }
  ],
  "internal_inputs": [
    {
      "input": "KillSwitch active flag",
      "source": "KillSwitch",
      "required": true,
      "use": "Abort all intent emission immediately if KillSwitch is active."
    },
    {
      "input": "Builder code bytes32",
      "source": "internal config",
      "required": true,
      "use": "Injected into builder field on every signed V2 OrderIntent for attribution."
    }
  ],
  "raw_params": [
    "kl_divergence_threshold \u00b7 float",
    "frank_wolfe_iters \u00b7 int",
    "max_legs_per_trade \u00b7 int",
    "liquidity_cap_usd \u00b7 int"
  ],
  "parameters": [
    {
      "name": "kl_divergence_threshold",
      "default": 0.015,
      "warning": 0.008,
      "hard": 0.003,
      "controls": "Minimum KL-divergence (in nats) from the nearest valid simplex distribution required before emitting OrderIntents.",
      "why_default_matters": "0.015 nats provides meaningful edge after fee drag on typical neg-risk books. Below 0.008 the divergence is marginal; below 0.003 the bot will not fire regardless of config.",
      "threshold_logic": [
        {
          "condition": "kl_div >= 0.015",
          "action": "EMIT multi-leg OrderIntents"
        },
        {
          "condition": "0.008 <= kl_div < 0.015",
          "action": "WARN BREGMAN_ARB_DIVERGENCE_MARGINAL; emit at 50% leg size"
        },
        {
          "condition": "kl_div < 0.003 (hard floor)",
          "action": "SKIP \u2014 BREGMAN_ARB_NO_EDGE; do not emit"
        }
      ],
      "dev_check": "if kl_div < params.hard: return skip('BREGMAN_ARB_NO_EDGE')",
      "user_facing": "The multi-outcome pricing distribution was not far enough from the valid range to justify a trade after fees."
    },
    {
      "name": "frank_wolfe_iters",
      "default": 200,
      "warning": 80,
      "hard": 30,
      "controls": "Maximum iterations for the Frank-Wolfe simplex projection algorithm. Higher values give a more precise projection but increase latency.",
      "why_default_matters": "200 iterations converges to within 1e-6 nats on markets with up to 20 outcomes. Below 80 the projection may not converge on large markets.",
      "threshold_logic": [
        {
          "condition": ">= 200",
          "action": "Full convergence expected"
        },
        {
          "condition": "80\u2013200",
          "action": "WARN BREGMAN_ARB_PROJECTION_MARGINAL; acceptable for small markets (\u2264 5 outcomes)"
        },
        {
          "condition": "< 30",
          "action": "Hard floor; config rejected"
        }
      ],
      "dev_check": "assert params.frank_wolfe_iters >= params.hard",
      "user_facing": ""
    },
    {
      "name": "max_legs_per_trade",
      "default": 6,
      "warning": 9,
      "hard": 12,
      "controls": "Maximum number of outcome-token legs emitted in a single trade cycle. Limits exposure on high-outcome-count markets.",
      "why_default_matters": "6 legs covers most neg-risk markets cleanly. Beyond 9 legs the tail positions have tiny notional and the incremental edge is smaller than combined fee drag.",
      "threshold_logic": [
        {
          "condition": "<= 6",
          "action": "Normal; all legs emitted"
        },
        {
          "condition": "7\u201312",
          "action": "WARN; each additional leg has diminishing edge"
        },
        {
          "condition": "> 12",
          "action": "Reject config \u2014 PARAMETER_CHANGE_REQUIRES_APPROVAL"
        }
      ],
      "dev_check": "if params.max_legs_per_trade > params.hard: raise ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL')",
      "user_facing": "Trade was sized across the most mispriced outcome legs."
    },
    {
      "name": "liquidity_cap_usd",
      "default": 400,
      "warning": 600,
      "hard": 800,
      "controls": "Total pUSD budget divided evenly across emitted legs. Each leg is capped at liquidity_cap_usd / n_legs, further bounded by available depth.",
      "why_default_matters": "400 pUSD total keeps per-leg sizes modest (< 70 pUSD each for 6 legs) and within typical neg-risk top-of-book depth.",
      "threshold_logic": [
        {
          "condition": "<= 400 pUSD",
          "action": "Normal leg sizing"
        },
        {
          "condition": "400\u2013800 pUSD",
          "action": "WARN; confirm depth covers each leg"
        },
        {
          "condition": "> 800 pUSD",
          "action": "Reject config \u2014 PARAMETER_CHANGE_REQUIRES_APPROVAL"
        }
      ],
      "dev_check": "legSize = min(depthAvail, liquidity_cap_usd / n_legs)",
      "user_facing": "Each leg of the multi-outcome trade was sized to fit within available market liquidity."
    }
  ],
  "default_config": {
    "bot_id": "strat.bregman_projection_arb",
    "version": "2.1.0",
    "mode": "limited_live",
    "defaults": {
      "kl_divergence_threshold": 0.015,
      "frank_wolfe_iters": 200,
      "max_legs_per_trade": 6,
      "liquidity_cap_usd": 400
    },
    "locked": {
      "kl_divergence_threshold": {
        "min": 0.003
      },
      "frank_wolfe_iters": {
        "min": 30
      },
      "max_legs_per_trade": {
        "max": 12
      },
      "liquidity_cap_usd": {
        "max": 800
      }
    }
  },
  "implementation_flow": [
    "Check KillSwitch active flag; if active, emit no OrderIntents.",
    "Subscribe to ws_market book updates for all active neg-risk multi-outcome markets.",
    "On each book snapshot: validate freshness (last_seen < 3s); else emit STALE_MARKET_DATA and skip.",
    "Confirm market negRisk=true and not closed/resolved/in-dispute via clob_public.",
    "Build observed probability vector p_obs from best-ask prices across all outcome token IDs (normalise to sum-to-one).",
    "Run Frank-Wolfe projection: initialise q = uniform; iterate up to frank_wolfe_iters; converge to nearest simplex distribution q*.",
    "Compute KL(p_obs || q*). If < kl_divergence_threshold hard floor (0.003), emit DecisionReport intent_emitted=false BREGMAN_ARB_NO_EDGE (sampled 1/100); RETURN.",
    "If kl_div < warning threshold (0.015), WARN BREGMAN_ARB_DIVERGENCE_MARGINAL; reduce all leg sizes by 50%.",
    "Sort outcome legs by |p_obs_i - q*_i| descending; select top min(max_legs_per_trade, n_outcomes) legs.",
    "Fetch top-of-book depth for each selected leg from clob_public; set legSize = min(depth, liquidity_cap_usd / n_legs).",
    "Emit OrderIntent for each selected leg: market_id, outcome_token_id, side=buy (if p_obs_i > q*_i: NO/short; else YES/long), price=best_ask, size_pUSD=legSize, tif=FOK, builder={code, fee_bps:25}.",
    "Note: fees are operator-set at match time in V2 \u2014 feeRateBps is NOT on any signed order.",
    "Emit DecisionReport with intent_emitted=true, kl_divergence, n_legs, reason BREGMAN_ARB_EDGE_DETECTED."
  ],
  "decision_logic": {
    "approve": "kl_div >= kl_divergence_threshold, market is neg-risk and open, all legs have depth, KillSwitch inactive. Emit per-leg OrderIntents (FOK).",
    "reshape_required": "Not applicable \u2014 strat bots emit OrderIntents; reshaping is handled by the downstream Risk guardrail pipeline.",
    "reject": "kl_div < 0.003 hard floor; market closed/resolved/disputed; KillSwitch active; stale snapshot. Emit DecisionReport intent_emitted=false.",
    "warning_only": "kl_div between 0.003 and 0.015 triggers BREGMAN_ARB_DIVERGENCE_MARGINAL warn and 50% size reduction."
  },
  "decision_output_schema": "OrderIntent",
  "decision_output_example": {
    "intent_id": "oi_01HXB9PARZ0001A",
    "trace_id": "tr_01HXB9PARZ000TR",
    "market_id": "0xbregman0000000000000000000000000000000000000000000000000000000001",
    "outcome_token_id": "0xbregman_tk_outcome_3",
    "side": "buy",
    "price": "0.112",
    "size_pUSD": "66.00",
    "tif": "FOK",
    "post_only": false,
    "builder": {
      "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
      "fee_bps": 25
    },
    "negrisk_aware": true,
    "decision": {
      "kl_divergence": 0.022,
      "n_legs": 6,
      "leg_index": 2,
      "reasons": [
        "BREGMAN_ARB_EDGE_DETECTED"
      ]
    },
    "comment": "fees are operator-set at match time in V2 \u2014 feeRateBps is NOT on the signed order"
  },
  "developer_log": {
    "bot_id": "strat.bregman_projection_arb",
    "market_id": "0xbregman0000000000000000000000000000000000000000000000000000000001",
    "n_outcomes": 8,
    "kl_divergence": 0.022,
    "frank_wolfe_iters_used": 147,
    "n_legs_selected": 6,
    "liquidity_cap_usd": 400,
    "per_leg_size_pusd": 66.0,
    "intent_emitted": true,
    "reason": "BREGMAN_ARB_EDGE_DETECTED",
    "emitted_at_ms": 1746790000000
  },
  "user_explanations": [
    {
      "situation": "Multi-outcome arb trade initiated",
      "message": "The combined pricing across several outcomes of this multi-choice market deviated from a mathematically consistent distribution. Orders were placed across the mispriced outcomes to capture the discrepancy."
    },
    {
      "situation": "No divergence \u2014 no trade",
      "message": "The multi-outcome pricing was within normal bounds after fees. No order was placed."
    },
    {
      "situation": "Divergence marginal \u2014 reduced size",
      "message": "A small pricing inconsistency was detected. Leg sizes were halved to limit exposure given the thin margin."
    },
    {
      "situation": "Market in dispute",
      "message": "This market's outcome is under review. No new orders were placed until the dispute resolves."
    }
  ],
  "failure_modes": {
    "main_failure_mode": "Frank-Wolfe projection does not converge within frank_wolfe_iters: the KL-divergence measurement is imprecise, and the bot may misidentify which legs to trade or over-size positions.",
    "false_positive_risk": "Stale or illiquid book data produces a spurious divergence; the bot emits legs that no longer have edge by the time they reach the CLOB.",
    "false_negative_risk": "kl_divergence_threshold set too conservatively misses genuine divergences on smaller-outcome-count markets where even 0.010 nats represents meaningful pricing error.",
    "safe_fallback": "If book snapshot is stale (last_seen > 3s) or market is in UMA DVM dispute, emit STALE_MARKET_DATA DecisionReport and skip without emitting any OrderIntent.",
    "required_dependencies": [
      "ws_market book stream (all outcome token IDs for neg-risk market)",
      "clob_public market endpoint (status + depth + negRisk flag)",
      "KillSwitch active flag",
      "internal builder code",
      "Historical co-movement matrix (optional; improves projection init)"
    ]
  },
  "acceptance_tests": {
    "unit": [
      {
        "test": "Emit 6 legs when kl_div=0.022 (above threshold 0.015)",
        "setup": "8-outcome market; p_obs deviates from projection by 0.022 nats; depth=200 pUSD each",
        "expected": "Six OrderIntents emitted; DecisionReport intent_emitted=true, reason=BREGMAN_ARB_EDGE_DETECTED"
      },
      {
        "test": "Skip when kl_div=0.002 (below hard floor 0.003)",
        "setup": "p_obs near-valid distribution; kl_div=0.002",
        "expected": "No OrderIntents; sampled DecisionReport reason=BREGMAN_ARB_NO_EDGE"
      },
      {
        "test": "Reduce leg sizes 50% when kl_div marginal (0.010)",
        "setup": "kl_div=0.010, liquidity_cap_usd=400, n_legs=6",
        "expected": "Six OrderIntents at 33 pUSD each (50% of 66); WARN BREGMAN_ARB_DIVERGENCE_MARGINAL"
      },
      {
        "test": "Skip when market in UMA DVM dispute",
        "setup": "market.dispute_active=true",
        "expected": "No OrderIntents; reason=MARKET_CLOSED"
      },
      {
        "test": "Skip when KillSwitch active",
        "setup": "killswitch.active=true",
        "expected": "No OrderIntents emitted"
      },
      {
        "test": "Frank-Wolfe iteration cap enforced",
        "setup": "frank_wolfe_iters=200; convergence at iter 147",
        "expected": "Projection completes; developer_log.frank_wolfe_iters_used <= 200"
      }
    ],
    "integration": [
      {
        "test": "Full cycle: neg-risk ws_market snapshot \u2192 KL computed \u2192 6 signed V2 FOK OrderIntents submitted",
        "expected": "All intents have builder.code (bytes32), no feeRateBps, negrisk_aware=true, EIP-712 domain version '2'"
      },
      {
        "test": "Stale ws_market feed triggers STALE_MARKET_DATA skip",
        "expected": "DecisionReport intent_emitted=false, reason=STALE_MARKET_DATA after 3s feed gap"
      }
    ],
    "property": [
      {
        "property": "All emitted leg OrderIntents are FOK; bot never posts resting maker orders",
        "required": "Always true"
      },
      {
        "property": "n_legs emitted never exceeds max_legs_per_trade",
        "required": "Always true"
      },
      {
        "property": "feeRateBps never present on any signed OrderIntent",
        "required": "Always true \u2014 V2 fees are operator-set at match time"
      }
    ]
  },
  "checklist_overrides": {},
  "legacy_goal": "Multi-outcome arb that doesn't require strict sum-violation; trades the divergence from the closest valid distribution.",
  "legacy_pm_signals": [
    "Full neg-risk event book (all outcomes)",
    "Real-time KL-divergence vs. nearest valid distribution"
  ],
  "legacy_external_feeds": [
    "Historical co-movement matrix (internal)"
  ],
  "reporting_groups": [
    "strategy_decision"
  ],
  "network": [
    "polygon"
  ],
  "api_surface": [
    "clob_public",
    "clob_auth",
    "ws_market",
    "onchain",
    "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 all signed multi-leg order construction. Updated collateral denomination to pUSD. Injected builder field (bytes32) on every OrderIntent. EIP-712 Exchange domain version updated from '1' to '2'. NegRisk market handling now uses NegRiskAdapter for multi-outcome CLOB V2 settlement."
    }
  ],
  "polymarket_v2_compat": {
    "clob_version": "v2",
    "collateral": "pUSD",
    "eip712_domain_version": "2",
    "builder_code_aware": true,
    "negrisk_aware": true,
    "multichain_ready": false,
    "sdk_used": "py-clob-client-v2",
    "settlement_contract": "CTFExchangeV2",
    "notes": "Operates exclusively on negative-risk multi-outcome markets via NegRiskAdapter. Builder code is injected on each leg intent. feeRateBps is not present on any signed order."
  },
  "reference_implementation": {
    "summary": "Subscribes to CLOB WebSocket book snapshots for neg-risk markets, runs Frank-Wolfe simplex projection to find the nearest valid distribution, measures KL-divergence, and emits per-leg FOK OrderIntents when divergence exceeds threshold.",
    "language_note": "Pseudocode is language-agnostic. FETCH = read input. EMIT = produce output. Translate to TS/Python/Go/Rust.",
    "pseudocode": "FUNCTION evaluateNegRiskMarket(market_id, bookSnapshot):\n  // --- 0. KillSwitch gate ---\n  ks = FETCH internal.killswitch.status\n  IF ks.active: RETURN\n\n  // --- 1. Staleness check ---\n  IF isStale(bookSnapshot, maxAgeS=3):\n    EMIT DecisionReport(intent_emitted=false, reason='STALE_MARKET_DATA')\n    RETURN\n\n  // --- 2. NegRisk market validation ---\n  market = FETCH clob_public.GET('/markets/' + market_id)\n  IF NOT market.neg_risk OR market.closed OR market.resolved OR market.dispute_active:\n    EMIT DecisionReport(intent_emitted=false, reason='MARKET_CLOSED')\n    RETURN\n\n  // --- 3. Build observed probability vector ---\n  outcomes = bookSnapshot.outcomes  // list of {token_id, best_ask, depth_pusd}\n  p_obs = [o.best_ask for o in outcomes]\n  p_obs = normalise(p_obs)  // sum-to-one on simplex\n\n  // --- 4. Frank-Wolfe projection to nearest valid distribution ---\n  q = uniform(len(outcomes))  // init\n  FOR iter IN range(params.frank_wolfe_iters):\n    grad = KL_gradient(p_obs, q)  // \u2207KL(p||q) = -p/q\n    s = argmin_simplex(grad)      // Frank-Wolfe linear minimisation step\n    gamma = 2.0 / (iter + 2)     // step size\n    q_new = (1 - gamma) * q + gamma * s\n    IF norm(q_new - q) < 1e-8: BREAK\n    q = q_new\n  q_star = q\n\n  // --- 5. KL-divergence check ---\n  kl_div = KL(p_obs, q_star)\n  IF kl_div < params.kl_divergence_threshold_hard:  // 0.003\n    IF random() < 0.01:\n      EMIT DecisionReport(intent_emitted=false, reason='BREGMAN_ARB_NO_EDGE', kl_div=kl_div)\n    RETURN\n\n  // --- 6. Warning threshold ---\n  sizeMultiplier = 1.0\n  IF kl_div < params.kl_divergence_threshold:  // 0.015\n    WARN('BREGMAN_ARB_DIVERGENCE_MARGINAL')\n    sizeMultiplier = 0.5\n\n  // --- 7. Select legs by divergence magnitude ---\n  divergences = [(i, abs(p_obs[i] - q_star[i])) for i in range(len(outcomes))]\n  divergences.sort(by=magnitude, descending=true)\n  selected = divergences[:params.max_legs_per_trade]\n\n  // --- 8. Size and emit legs ---\n  legBudget = params.liquidity_cap_usd / len(selected)\n  FOR (i, _) IN selected:\n    legSize = toPusdUnits(min(outcomes[i].depth_pusd, legBudget) * sizeMultiplier)\n    side = 'buy' IF p_obs[i] < q_star[i] ELSE 'sell'\n    EMIT OrderIntent(\n      market_id        = market_id,\n      outcome_token_id = outcomes[i].token_id,\n      side             = side,\n      price            = outcomes[i].best_ask,\n      size_pUSD        = legSize,\n      tif              = 'FOK',\n      post_only        = false,\n      negrisk_aware    = true,\n      builder          = {code: internal.builder_code, fee_bps: 25}\n      // V2: no feeRateBps on signed order\n    )\n\n  EMIT DecisionReport(intent_emitted=true, kl_divergence=kl_div,\n                      n_legs=len(selected), reason='BREGMAN_ARB_EDGE_DETECTED')",
    "sdk_calls": [
      "fetchClobPublic('/markets/' + market_id)",
      "ws_market.subscribe('book', outcome_token_ids)",
      "toPusdUnits(rawFloat)",
      "buildOrderTypedData(orderParams, { name: 'CTFExchange', version: '2', chainId: 137 })",
      "internal.killswitch.status()",
      "internal.builder_code"
    ],
    "complexity": "O(k * frank_wolfe_iters) per book snapshot, where k = number of outcomes"
  },
  "wire_examples": {
    "input": [
      {
        "label": "NegRisk market book snapshot \u2014 8 outcomes, divergence detected",
        "source": "ws_market",
        "payload": {
          "market_id": "0xbregman0000000000000000000000000000000000000000000000000000000001",
          "neg_risk": true,
          "outcomes": [
            {
              "token_id": "0xbrg_tk_0",
              "best_ask": "0.310",
              "depth_pusd": "180.00"
            },
            {
              "token_id": "0xbrg_tk_1",
              "best_ask": "0.185",
              "depth_pusd": "220.00"
            },
            {
              "token_id": "0xbrg_tk_2",
              "best_ask": "0.112",
              "depth_pusd": "90.00"
            },
            {
              "token_id": "0xbrg_tk_3",
              "best_ask": "0.098",
              "depth_pusd": "75.00"
            },
            {
              "token_id": "0xbrg_tk_4",
              "best_ask": "0.095",
              "depth_pusd": "60.00"
            },
            {
              "token_id": "0xbrg_tk_5",
              "best_ask": "0.074",
              "depth_pusd": "50.00"
            },
            {
              "token_id": "0xbrg_tk_6",
              "best_ask": "0.068",
              "depth_pusd": "45.00"
            },
            {
              "token_id": "0xbrg_tk_7",
              "best_ask": "0.058",
              "depth_pusd": "40.00"
            }
          ],
          "received_at_ms": 1746790000000
        }
      }
    ],
    "output": [
      {
        "label": "OrderIntent \u2014 leg 2 (buy outcome 2, FOK, builder-attributed)",
        "payload": {
          "intent_id": "oi_01HXB9PARZ0001A",
          "trace_id": "tr_01HXB9PARZ000TR",
          "market_id": "0xbregman0000000000000000000000000000000000000000000000000000000001",
          "outcome_token_id": "0xbrg_tk_2",
          "side": "buy",
          "price": "0.112",
          "size_pUSD": "66.00",
          "tif": "FOK",
          "post_only": false,
          "builder": {
            "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
            "fee_bps": 25
          },
          "negrisk_aware": true,
          "decision": {
            "kl_divergence": 0.022,
            "n_legs": 6,
            "leg_index": 2,
            "reasons": [
              "BREGMAN_ARB_EDGE_DETECTED"
            ]
          },
          "comment": "fees are operator-set at match time in V2 \u2014 feeRateBps is NOT on the signed order"
        }
      },
      {
        "label": "DecisionReport \u2014 skipped (no edge), sampled 1/100",
        "payload": {
          "report_id": "dr_01HXB9PARZ999Z",
          "bot_id": "strat.bregman_projection_arb",
          "market_id": "0xbregman0000000000000000000000000000000000000000000000000000000001",
          "intent_emitted": false,
          "kl_divergence": 0.001,
          "reasons": [
            "BREGMAN_ARB_NO_EDGE"
          ],
          "sampled": true,
          "evaluated_at_ms": 1746790001000
        }
      }
    ]
  },
  "reason_codes": [
    {
      "code": "BREGMAN_ARB_EDGE_DETECTED",
      "severity": "INFO",
      "meaning": "KL-divergence from the nearest valid simplex distribution exceeds kl_divergence_threshold. Multi-leg OrderIntents emitted.",
      "action": "Emit per-leg FOK OrderIntents.",
      "user_message": "A pricing inconsistency across multiple outcomes was detected and orders were placed to capture it."
    },
    {
      "code": "BREGMAN_ARB_NO_EDGE",
      "severity": "INFO",
      "meaning": "KL-divergence is below the 0.003 nats absolute hard floor. No trade opportunity exists after fees.",
      "action": "Skip; emit DecisionReport intent_emitted=false (sampled 1/100).",
      "user_message": "The multi-outcome pricing was within normal bounds. No order was placed."
    },
    {
      "code": "BREGMAN_ARB_DIVERGENCE_MARGINAL",
      "severity": "WARN",
      "meaning": "KL-divergence is between the hard floor (0.003) and the warning threshold (0.015). Trade is marginal.",
      "action": "Emit OrderIntents at 50% leg size; log warning.",
      "user_message": "A small pricing inconsistency was detected. Leg sizes were reduced."
    },
    {
      "code": "BREGMAN_ARB_PROJECTION_MARGINAL",
      "severity": "WARN",
      "meaning": "Frank-Wolfe iteration count is below the warning threshold; projection may not have fully converged.",
      "action": "Emit with caution; log warning for ops review.",
      "user_message": ""
    },
    {
      "code": "BREGMAN_ARB_DEPTH_INSUFFICIENT",
      "severity": "WARN",
      "meaning": "Top-of-book depth on one or more selected legs is below minimum viable trade size (5 pUSD).",
      "action": "Skip affected leg; reduce n_legs; emit DecisionReport.",
      "user_message": "Not enough liquidity on some outcome legs to place the full trade."
    },
    {
      "code": "STALE_MARKET_DATA",
      "severity": "HARD_REJECT",
      "meaning": "Book snapshot is older than 3 seconds or market is in UMA DVM dispute.",
      "action": "Skip; no OrderIntent emitted.",
      "user_message": "Market data was too old to act on safely."
    },
    {
      "code": "MARKET_CLOSED",
      "severity": "HARD_REJECT",
      "meaning": "Market is closed, resolved, disputed, or is not a neg-risk market.",
      "action": "Skip immediately; no OrderIntent emitted.",
      "user_message": "This market is no longer open for trading."
    },
    {
      "code": "KILL_SWITCH_ACTIVE",
      "severity": "HARD_REJECT",
      "meaning": "Global kill switch is active.",
      "action": "Skip all markets; no OrderIntents emitted.",
      "user_message": "Trading is currently paused."
    },
    {
      "code": "PARAMETER_CHANGE_REQUIRES_APPROVAL",
      "severity": "HARD_REJECT",
      "meaning": "A config change would push a parameter past its locked hard limit.",
      "action": "Reject config change; do not apply.",
      "user_message": ""
    }
  ],
  "metrics": {
    "emitted": [
      {
        "name": "polytraders_strat_bregmanarb_decisions_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "verdict",
          "reason_code"
        ],
        "meaning": "Total evaluation cycles by intent_emitted (true/false) and reason code."
      },
      {
        "name": "polytraders_strat_bregmanarb_kl_divergence",
        "type": "histogram",
        "unit": "nats",
        "labels": [],
        "meaning": "Distribution of measured KL-divergence across all evaluated neg-risk markets."
      },
      {
        "name": "polytraders_strat_bregmanarb_legs_emitted_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "outcome_index"
        ],
        "meaning": "Total OrderIntents emitted per outcome leg index."
      },
      {
        "name": "polytraders_strat_bregmanarb_projection_iters",
        "type": "histogram",
        "unit": "iterations",
        "labels": [],
        "meaning": "Frank-Wolfe iteration count at convergence per evaluation."
      },
      {
        "name": "polytraders_strat_bregmanarb_eval_latency_ms",
        "type": "histogram",
        "unit": "milliseconds",
        "labels": [],
        "meaning": "Wall-clock time from book snapshot receipt to last OrderIntent emit."
      },
      {
        "name": "polytraders_strat_bregmanarb_stale_feed_total",
        "type": "counter",
        "unit": "count",
        "labels": [],
        "meaning": "Evaluation cycles skipped due to stale ws_market feed."
      }
    ],
    "alerts": [
      {
        "name": "BregmanArbHighStaleFeed",
        "condition": "rate(polytraders_strat_bregmanarb_stale_feed_total[5m]) > 0.1",
        "severity": "warn",
        "runbook": "#runbook-bregmanarb-stale-feed"
      },
      {
        "name": "BregmanArbHighLatency",
        "condition": "histogram_quantile(0.99, rate(polytraders_strat_bregmanarb_eval_latency_ms_bucket[5m])) > 300",
        "severity": "warn",
        "runbook": "#runbook-bregmanarb-latency"
      },
      {
        "name": "BregmanArbNoEdgeStreak",
        "condition": "rate(polytraders_strat_bregmanarb_decisions_total{verdict='false'}[15m]) / rate(polytraders_strat_bregmanarb_decisions_total[15m]) > 0.99",
        "severity": "warn",
        "runbook": "#runbook-bregmanarb-no-edge"
      },
      {
        "name": "BregmanArbKillSwitchBlocking",
        "condition": "rate(polytraders_strat_bregmanarb_decisions_total{reason_code='KILL_SWITCH_ACTIVE'}[1m]) > 0",
        "severity": "page",
        "runbook": "#runbook-killswitch"
      }
    ],
    "dashboards": [
      "Grafana \u2014 Strategy / BregmanArb KL-divergence distribution",
      "Grafana \u2014 Strategy / BregmanArb leg throughput and projection convergence"
    ],
    "log_level": "info"
  },
  "state": {
    "store": "redis",
    "shape": "Per-market last book snapshot (p_obs vector, q_star vector, kl_div, timestamp_ms) and last DecisionReport outcome; keyed by market_id",
    "ttl": "10s",
    "recovery": "On cold start, state is empty; first book snapshot triggers fresh Frank-Wolfe projection without stale-state comparison.",
    "size_estimate": "~1 KB per active neg-risk market (vectors scale with n_outcomes); typically < 2 MB total"
  },
  "concurrency": {
    "execution_model": "single-threaded event loop",
    "max_in_flight": 30,
    "idempotency_key": "intent_id",
    "timeout_ms": 400,
    "backpressure": "drop oldest pending snapshot per market_id when queue depth > 3",
    "locking": "per-market_id mutex for Redis state write during projection"
  },
  "dependencies": {
    "depends_on": [
      {
        "bot_id": "risk.kill_switch",
        "why": "Checked first; blocks all intent emission when active."
      }
    ],
    "emits_to": [
      {
        "bot_id": "risk.portfolio_guard",
        "what": "Multi-leg FOK OrderIntents for risk guardrail evaluation before SmartRouter."
      },
      {
        "bot_id": "gov.builder_attribution",
        "what": "builder.code bytes32 on every leg OrderIntent for attribution tracking."
      }
    ],
    "sibling": [],
    "external": [
      {
        "service": "Polymarket CLOB v2 (public)",
        "sla": "99.9% (Polymarket-published)",
        "fallback": "Skip evaluation cycle; emit STALE_MARKET_DATA DecisionReport."
      },
      {
        "service": "Polymarket CLOB WebSocket (ws_market)",
        "sla": "best-effort",
        "fallback": "On disconnect, fall back to REST polling at 2s interval; emit STALE_MARKET_DATA if gap > 3s."
      }
    ]
  },
  "security_surfaces": {
    "signs_orders": true,
    "private_key_access": "signing-only",
    "abuse_vectors": [
      "Adversary spams thin neg-risk legs to force spurious KL-divergence signals",
      "Replaying a signed OrderIntent after the projected divergence closes",
      "Injecting a modified builder.code to redirect attribution fees"
    ],
    "mitigations": [
      "All legs use FOK; partial fills are rejected rather than leaving naked positions",
      "V2 order timestamp(ms) invalidates replays outside the exchange acceptance window",
      "Builder code is read from immutable internal config; not user-supplied",
      "Book snapshot staleness check (3s) prevents acting on stale divergence signals"
    ],
    "contract_calls": [
      {
        "contract": "CTFExchangeV2",
        "function": "matchOrders",
        "purpose": "Settlement of multi-outcome NegRisk token purchases via NegRiskAdapter."
      }
    ]
  },
  "failure_injection": [
    {
      "scenario": "STALE_WS_FEED",
      "how_to_inject": "Pause ws_market WebSocket; let last_seen age beyond 3s",
      "expected_behaviour": "Bot emits STALE_MARKET_DATA DecisionReport and skips all evaluations until feed recovers",
      "recovery": "Automatic on WebSocket reconnect within one evaluation cycle."
    },
    {
      "scenario": "PROJECTION_NON_CONVERGENCE",
      "how_to_inject": "Set frank_wolfe_iters=1 on a 15-outcome market",
      "expected_behaviour": "BREGMAN_ARB_PROJECTION_MARGINAL WARN; projection completes with imprecise q_star; ops alert fires",
      "recovery": "Increase frank_wolfe_iters in config; ops review required."
    },
    {
      "scenario": "DISPUTE_ACTIVE",
      "how_to_inject": "Set market.dispute_active=true on the test market",
      "expected_behaviour": "MARKET_CLOSED DecisionReport; no OrderIntents emitted for disputed market",
      "recovery": "Automatic when dispute resolves and market returns to open status."
    },
    {
      "scenario": "KILL_SWITCH_ON",
      "how_to_inject": "Set killswitch.active=true",
      "expected_behaviour": "No OrderIntents emitted for any market",
      "recovery": "Automatic on manual KillSwitch reset."
    },
    {
      "scenario": "DEPTH_INSUFFICIENT_ON_LEG",
      "how_to_inject": "Set mock depth on outcome leg 3 to 2 pUSD (below 5 pUSD floor)",
      "expected_behaviour": "BREGMAN_ARB_DEPTH_INSUFFICIENT; leg 3 skipped; remaining 5 legs emitted",
      "recovery": "Automatic when depth replenishes."
    }
  ],
  "runbook": {
    "summary": "Bregman-Projection Arb incidents are usually stale feed, Frank-Wolfe non-convergence on high-outcome markets, or UMA DVM dispute halts. Non-convergence requires a config fix; dispute halts resolve automatically.",
    "oncall_actions": [
      {
        "alert": "BregmanArbHighStaleFeed",
        "first_action": "Check ws_market WebSocket connectivity and reconnect loop logs.",
        "escalate_to": "Infra on-call if disconnected > 2 minutes."
      },
      {
        "alert": "BregmanArbHighLatency",
        "first_action": "Check frank_wolfe_iters config and number of active neg-risk markets being evaluated.",
        "escalate_to": "Strategy pod lead if p99 latency > 600ms sustained."
      },
      {
        "alert": "BregmanArbNoEdgeStreak",
        "first_action": "Review neg-risk market conditions; confirm books are not unusually tight.",
        "escalate_to": "Strategy pod lead if streak persists > 30 minutes."
      },
      {
        "alert": "BregmanArbKillSwitchBlocking",
        "first_action": "Confirm KillSwitch activation was intentional. Check KillSwitch runbook.",
        "escalate_to": "Risk pod lead immediately."
      }
    ],
    "manual_overrides": [
      {
        "name": "exclude_market",
        "how": "Add market_id to config.excluded_markets list",
        "when": "Market is in UMA DVM dispute or showing anomalous multi-outcome pricing."
      },
      {
        "name": "pause_bot",
        "how": "polytraders bot pause strat.bregman_projection_arb",
        "when": "Elevated leg misfire rate or projection convergence failures across multiple markets."
      }
    ],
    "healthcheck": "GET /internal/health/bregman-projection-arb -> 200 if ws_market feed last_seen < 3s, Redis reachable, KillSwitch inactive, at least one market evaluated in last 60s."
  },
  "promotion_gates": {
    "to_shadow": [
      {
        "gate": "All unit tests pass including FOK-only invariant and projection convergence",
        "how_measured": "CI test run",
        "threshold": "100% pass"
      },
      {
        "gate": "feeRateBps absence verified; negrisk_aware=true on all OrderIntents",
        "how_measured": "Integration test asserting V2 order schema",
        "threshold": "Pass"
      }
    ],
    "to_limited_live": [
      {
        "gate": "p99 eval latency < 400ms over 24h (includes Frank-Wolfe projection time)",
        "how_measured": "polytraders_strat_bregmanarb_eval_latency_ms histogram",
        "threshold": "p99 < 400ms"
      },
      {
        "gate": "Zero partial-leg fills (all FOK either fully fill or reject) in 48h shadow run",
        "how_measured": "Fill reconciliation report",
        "threshold": "0 partial fills"
      }
    ],
    "to_general_live": [
      {
        "gate": "E2E: neg-risk book snapshot \u2192 KL computed \u2192 N signed V2 FOK OrderIntents submitted on Polygon testnet",
        "how_measured": "E2E test",
        "threshold": "Pass"
      },
      {
        "gate": "Frank-Wolfe projection convergence verified across 10 synthetic market distributions",
        "how_measured": "Unit test suite",
        "threshold": "All converge within frank_wolfe_iters"
      }
    ]
  },
  "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 considered-but-skipped",
    "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"
  }
}