{
  "schema_version": "1.0.0",
  "bot_id": "3.6",
  "bot_name": "Cross-Market Arb",
  "slug": "cross-market-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": "Manually reviewed pairs of binary markets with logical price constraints (e.g. nominee \u2264 winner, team advances \u2264 tournament winner) where the constraint is violated by more than tolerance_bps",
    "default_mode": "limited_live",
    "user_visible": "Advanced details only",
    "developer_owner": "Polytraders core \u2014 Strategy pod"
  },
  "purpose": "Cross-Market Arb detects logically constrained relationships between pairs of Polymarket binary markets \u2014 e.g. 'Candidate A wins primary' should price at or below 'Candidate A wins general', or 'Team X advances to semi-final' should price at or below 'Team X wins tournament'. When the logical constraint is violated by more than tolerance_bps (after fee buffer), the bot emits a pair of OrderIntents (one per market) to exploit the mispricing. Market pairs must pass manual review before the bot will trade them (require_manual_pair_review=true). This is a user-controlled execution tool; it does not copy trades or make performance claims.",
  "why_it_matters": [
    {
      "failure": "Logically distinct markets paired incorrectly",
      "consequence": "A pair that looks logically constrained but has different resolution conditions may diverge legitimately. Trading a false constraint produces losses when both markets resolve consistently with their actual rules."
    },
    {
      "failure": "One market resolves while the paired order is still resting",
      "consequence": "If market A resolves YES and the paired market B order has not filled, the bot holds a one-sided position with no offsetting settlement path."
    },
    {
      "failure": "Tolerance_bps too tight on high-spread markets",
      "consequence": "The bot may fire on transient spread noise rather than genuine constraint violations, incurring fee costs with no structural edge."
    },
    {
      "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": "Best bid/ask for each market in registered pairs",
      "source": "ws_market (CLOB WebSocket)",
      "required": true,
      "use": "Monitor for constraint violations: p_market_A > p_market_B + tolerance_bps when A implies B."
    },
    {
      "input": "Market metadata: condition ID, outcome token IDs, resolution rules, category",
      "source": "gamma",
      "required": true,
      "use": "Validate that pair logic and resolution conditions align before registering a market pair."
    },
    {
      "input": "Market open/closed/resolved status",
      "source": "clob_public",
      "required": true,
      "use": "Skip pairs where either market is closed or resolved; cancel resting legs immediately."
    },
    {
      "input": "Top-of-book depth on both markets",
      "source": "clob_public",
      "required": true,
      "use": "Size each leg to min(depth, per-leg budget from liquidity budget)."
    },
    {
      "input": "Embedding-based similarity score (e5-large-v2 or similar)",
      "source": "internal",
      "required": false,
      "use": "Cross-check that proposed pair markets are semantically related; flag low-similarity pairs for human review."
    }
  ],
  "internal_inputs": [
    {
      "input": "KillSwitch active flag",
      "source": "KillSwitch",
      "required": true,
      "use": "Abort all intent emission immediately if KillSwitch is active."
    },
    {
      "input": "Registered market-pair registry (manually reviewed)",
      "source": "internal config",
      "required": true,
      "use": "Bot only evaluates pairs in the approved registry. Pairs require human review before entry."
    },
    {
      "input": "Builder code bytes32",
      "source": "internal config",
      "required": true,
      "use": "Injected into builder field on every signed V2 OrderIntent."
    }
  ],
  "raw_params": [
    "tolerance_bps \u00b7 int",
    "min_similarity_score \u00b7 0\u20131",
    "require_manual_pair_review \u00b7 bool",
    "max_legs \u00b7 int"
  ],
  "parameters": [
    {
      "name": "tolerance_bps",
      "default": 30,
      "warning": 15,
      "hard": 5,
      "controls": "Minimum basis-point violation of the logical constraint (after fee buffer) required before emitting OrderIntents.",
      "why_default_matters": "30 bps covers fee drag and expected slippage on a typical Polymarket binary pair. Below 15 bps the violation may be transient noise; below 5 bps the bot will not fire.",
      "threshold_logic": [
        {
          "condition": ">= 30 bps",
          "action": "EMIT pair OrderIntents"
        },
        {
          "condition": "15\u201330 bps",
          "action": "WARN CROSS_MARKET_ARB_VIOLATION_MARGINAL; emit at 50% size"
        },
        {
          "condition": "< 5 bps (hard floor)",
          "action": "SKIP \u2014 CROSS_MARKET_ARB_NO_EDGE"
        }
      ],
      "dev_check": "if violation_bps < params.hard: return skip('CROSS_MARKET_ARB_NO_EDGE')",
      "user_facing": "The pricing inconsistency between linked markets was too small to trade profitably after fees."
    },
    {
      "name": "min_similarity_score",
      "default": 0.82,
      "warning": 0.7,
      "hard": 0.6,
      "controls": "Minimum embedding-based similarity score (0\u20131) between the two market questions before the pair is eligible for auto-registration.",
      "why_default_matters": "0.82 ensures markets are semantically related enough to have a meaningful logical constraint. Below 0.70 pairs are likely comparing unrelated events.",
      "threshold_logic": [
        {
          "condition": ">= 0.82",
          "action": "Pair eligible; still requires manual review"
        },
        {
          "condition": "0.70\u20130.82",
          "action": "WARN CROSS_MARKET_ARB_LOW_SIMILARITY; manual review mandatory before any trade"
        },
        {
          "condition": "< 0.60",
          "action": "Hard floor; pair rejected from registry"
        }
      ],
      "dev_check": "if similarity < params.hard: raise ConfigError('CROSS_MARKET_ARB_LOW_SIMILARITY')",
      "user_facing": ""
    },
    {
      "name": "require_manual_pair_review",
      "default": true,
      "warning": null,
      "hard": null,
      "controls": "When true, every new market pair must be manually reviewed and approved before the bot will trade it. Cannot be set to false in production.",
      "why_default_matters": "Prevents the bot from auto-discovering and trading logically incorrect pairs that could produce losses.",
      "threshold_logic": [
        {
          "condition": "true",
          "action": "Normal; all pairs require manual approval"
        },
        {
          "condition": "false",
          "action": "Not permitted in production; config rejected"
        }
      ],
      "dev_check": "assert params.require_manual_pair_review == True",
      "user_facing": ""
    },
    {
      "name": "max_legs",
      "default": 2,
      "warning": 3,
      "hard": 4,
      "controls": "Maximum number of simultaneous legs per detected constraint violation. Standard cross-market arb requires exactly 2 legs.",
      "why_default_matters": "2 legs covers the standard binary pair. Values > 2 are only valid for chained logical constraints (A implies B implies C).",
      "threshold_logic": [
        {
          "condition": "== 2",
          "action": "Normal paired trade"
        },
        {
          "condition": "3\u20134",
          "action": "WARN; chained constraint mode; each additional leg increases execution risk"
        },
        {
          "condition": "> 4",
          "action": "Reject config \u2014 PARAMETER_CHANGE_REQUIRES_APPROVAL"
        }
      ],
      "dev_check": "if params.max_legs > params.hard: raise ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL')",
      "user_facing": "The trade was placed across the two linked markets simultaneously."
    }
  ],
  "default_config": {
    "bot_id": "strat.cross_market_arb",
    "version": "2.1.0",
    "mode": "limited_live",
    "defaults": {
      "tolerance_bps": 30,
      "min_similarity_score": 0.82,
      "require_manual_pair_review": true,
      "max_legs": 2
    },
    "locked": {
      "tolerance_bps": {
        "min": 5
      },
      "min_similarity_score": {
        "min": 0.6
      },
      "require_manual_pair_review": {
        "value": true
      },
      "max_legs": {
        "max": 4
      }
    }
  },
  "implementation_flow": [
    "Check KillSwitch active flag; if active, emit no OrderIntents.",
    "Load registered market-pair registry (manually reviewed pairs only).",
    "Subscribe to ws_market book updates for all markets in active pairs.",
    "On each book tick: retrieve the latest best-ask for each market in the pair.",
    "Validate book freshness (last_seen < 5s); if stale, emit STALE_MARKET_DATA and skip.",
    "Confirm both markets in the pair are open and not resolved (clob_public).",
    "Compute violation_bps = (p_market_A - p_market_B) * 10000 for pairs where A logically implies B.",
    "If violation_bps < tolerance_bps hard floor (5 bps), emit sampled DecisionReport CROSS_MARKET_ARB_NO_EDGE and skip.",
    "If violation_bps < warning threshold (30 bps), WARN CROSS_MARKET_ARB_VIOLATION_MARGINAL; reduce leg sizes by 50%.",
    "Fetch top-of-book depth for both markets; set legSize = min(depth_A, depth_B, per-leg budget).",
    "Emit OrderIntent for market A (sell YES / buy NO \u2014 the overpriced side).",
    "Emit OrderIntent for market B (buy YES \u2014 the underpriced side).",
    "Note: fees are operator-set at match time in V2 \u2014 feeRateBps is NOT on any signed order.",
    "Emit DecisionReport with intent_emitted=true, violation_bps, pair_id, reason CROSS_MARKET_ARB_EDGE_DETECTED."
  ],
  "decision_logic": {
    "approve": "violation_bps >= tolerance_bps, both markets open, both legs have depth, pair is manually approved, KillSwitch inactive. Emit two paired OrderIntents (FOK).",
    "reshape_required": "Not applicable \u2014 strat bots emit OrderIntents; reshaping is handled by the downstream Risk guardrail pipeline.",
    "reject": "violation_bps < 5 bps hard floor; either market closed/resolved; pair not in approved registry; KillSwitch active; stale feed. Emit DecisionReport intent_emitted=false.",
    "warning_only": "violation_bps between 5 and 30 bps triggers CROSS_MARKET_ARB_VIOLATION_MARGINAL and 50% size reduction."
  },
  "decision_output_schema": "OrderIntent",
  "decision_output_example": {
    "intent_id": "oi_01HXCMA00001AA",
    "trace_id": "tr_01HXCMA0000TR1",
    "market_id": "0xcrossma000000000000000000000000000000000000000000000000000000001a",
    "outcome": "YES",
    "side": "buy",
    "price": "0.410",
    "size_pUSD": "250.00",
    "tif": "FOK",
    "post_only": false,
    "builder": {
      "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
      "fee_bps": 25
    },
    "negrisk_aware": false,
    "decision": {
      "pair_id": "pair_nominee_vs_winner_0042",
      "violation_bps": 45.0,
      "leg": "underpriced_B",
      "reasons": [
        "CROSS_MARKET_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.cross_market_arb",
    "pair_id": "pair_nominee_vs_winner_0042",
    "market_A": "0xcrossma000000000000000000000000000000000000000000000000000000001b",
    "market_B": "0xcrossma000000000000000000000000000000000000000000000000000000001a",
    "p_A": 0.455,
    "p_B": 0.41,
    "violation_bps": 45.0,
    "leg_size_pusd": 250.0,
    "intent_emitted": true,
    "reason": "CROSS_MARKET_ARB_EDGE_DETECTED",
    "emitted_at_ms": 1746790200000
  },
  "user_explanations": [
    {
      "situation": "Cross-market arb trade initiated",
      "message": "Two related markets were priced inconsistently: one was priced higher than the other in a way that contradicts their logical relationship. Orders were placed to exploit the pricing gap."
    },
    {
      "situation": "No violation \u2014 no trade",
      "message": "The pricing between the two linked markets was consistent after accounting for fees. No order was placed."
    },
    {
      "situation": "Violation marginal \u2014 reduced size",
      "message": "A small pricing inconsistency between related markets was detected. Order sizes were halved to limit exposure given the thin margin."
    },
    {
      "situation": "Market resolved \u2014 pair deactivated",
      "message": "One of the two linked markets has resolved. The pair has been deactivated and no new orders will be placed."
    }
  ],
  "failure_modes": {
    "main_failure_mode": "One market in the pair resolves while the second leg order is resting, leaving a one-sided position with no offsetting settlement path.",
    "false_positive_risk": "Stale WebSocket book data shows a violation that has already corrected, causing the bot to attempt an arb that no longer exists and incurring fee costs with no edge.",
    "false_negative_risk": "tolerance_bps set too conservatively on low-fee market pairs causes valid constraint violations to be skipped.",
    "safe_fallback": "If either market's book data is stale (last_seen > 5s) or either market resolves, emit STALE_MARKET_DATA DecisionReport and skip. Cancel any resting legs on resolved markets immediately.",
    "required_dependencies": [
      "ws_market book stream (both market IDs in each active pair)",
      "clob_public market endpoint (status + depth)",
      "internal market-pair registry (manually reviewed)",
      "KillSwitch active flag",
      "internal builder code"
    ]
  },
  "acceptance_tests": {
    "unit": [
      {
        "test": "Emit pair when violation_bps=45 (above threshold 30)",
        "setup": "p_A=0.455, p_B=0.410, tolerance_bps=30, pair manually approved",
        "expected": "Two OrderIntents emitted; DecisionReport intent_emitted=true, reason=CROSS_MARKET_ARB_EDGE_DETECTED"
      },
      {
        "test": "Skip when violation_bps=3 (below hard floor 5)",
        "setup": "p_A=0.502, p_B=0.499",
        "expected": "No OrderIntents; sampled DecisionReport reason=CROSS_MARKET_ARB_NO_EDGE"
      },
      {
        "test": "Reduce sizes 50% when violation is marginal (18 bps)",
        "setup": "violation_bps=18, tolerance_bps=30",
        "expected": "OrderIntents emitted at 50% size; WARN CROSS_MARKET_ARB_VIOLATION_MARGINAL"
      },
      {
        "test": "Reject unreviewed pair",
        "setup": "pair not in approved registry",
        "expected": "No OrderIntents; CROSS_MARKET_ARB_PAIR_NOT_APPROVED"
      },
      {
        "test": "Skip when either market closed",
        "setup": "market_B.status=resolved",
        "expected": "No OrderIntents; reason=MARKET_CLOSED; resting leg on market_A cancelled"
      },
      {
        "test": "Skip when KillSwitch active",
        "setup": "killswitch.active=true",
        "expected": "No OrderIntents emitted"
      }
    ],
    "integration": [
      {
        "test": "Full cycle: ws_market pair tick \u2192 violation detected \u2192 two signed V2 FOK OrderIntents submitted",
        "expected": "Both orders have builder.code (bytes32), no feeRateBps, EIP-712 domain version '2'"
      },
      {
        "test": "Market resolution mid-trade triggers immediate leg cancellation",
        "expected": "Resting leg cancelled; MARKET_CLOSED DecisionReport emitted; no new intents for pair"
      }
    ],
    "property": [
      {
        "property": "Bot never trades a pair not in the manually reviewed approved registry",
        "required": "Always true"
      },
      {
        "property": "feeRateBps is never present on any signed OrderIntent",
        "required": "Always true \u2014 V2 fees are operator-set at match time"
      },
      {
        "property": "require_manual_pair_review is always true in production config",
        "required": "Always true"
      }
    ]
  },
  "checklist_overrides": {},
  "legacy_goal": "Exploit logical relationships between different Polymarket markets.",
  "legacy_pm_signals": [
    "Books on each linked market (e.g., \"wins primary\" vs. \"is nominee\")",
    "Embedding-based similarity score (e5-large-v2 / similar)",
    "Monotonicity violations on over/under markets"
  ],
  "legacy_external_feeds": [],
  "reporting_groups": [
    "strategy_decision"
  ],
  "network": [
    "polygon"
  ],
  "api_surface": [
    "gamma",
    "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 all signed multi-market order construction. Updated collateral denomination to pUSD. Injected builder field (bytes32) on every OrderIntent. EIP-712 Exchange domain version updated from '1' to '2'. Market-pair registry updated to use Gamma API for metadata and condition-ID resolution."
    }
  ],
  "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": "Operates on linked binary market pairs. feeRateBps is not present on any signed order; fees are operator-set at match time. Market-pair relationships are reviewed and registered manually before deployment."
  },
  "reference_implementation": {
    "summary": "Subscribes to CLOB WebSocket book updates for registered market pairs, checks for logical-constraint violations above tolerance_bps, and emits FOK OrderIntents on both legs when a violation is confirmed.",
    "language_note": "Pseudocode is language-agnostic. FETCH = read input. EMIT = produce output. Translate to TS/Python/Go/Rust.",
    "pseudocode": "FUNCTION evaluatePair(pair_id, bookTick):\n  // --- 0. KillSwitch gate ---\n  ks = FETCH internal.killswitch.status\n  IF ks.active: RETURN\n\n  // --- 1. Registry check ---\n  pair = FETCH internal.pairRegistry.get(pair_id)\n  IF NOT pair OR NOT pair.manually_approved:\n    EMIT DecisionReport(intent_emitted=false, reason='CROSS_MARKET_ARB_PAIR_NOT_APPROVED')\n    RETURN\n\n  // --- 2. Staleness check ---\n  IF isStale(bookTick, maxAgeS=5):\n    EMIT DecisionReport(intent_emitted=false, reason='STALE_MARKET_DATA')\n    RETURN\n\n  // --- 3. Market status check ---\n  mktA = FETCH clob_public.GET('/markets/' + pair.market_id_A)\n  mktB = FETCH clob_public.GET('/markets/' + pair.market_id_B)\n  IF mktA.closed OR mktA.resolved OR mktB.closed OR mktB.resolved:\n    CANCEL_RESTING_LEGS(pair_id)\n    EMIT DecisionReport(intent_emitted=false, reason='MARKET_CLOSED')\n    RETURN\n\n  // --- 4. Constraint violation check (A implies B, so p_A should <= p_B) ---\n  p_A = bookTick.market_A.best_ask\n  p_B = bookTick.market_B.best_ask\n  violation_bps = (p_A - p_B) * 10000\n\n  // --- 5. Hard floor ---\n  IF violation_bps < params.tolerance_bps_hard:  // 5 bps\n    IF random() < 0.01:\n      EMIT DecisionReport(intent_emitted=false, reason='CROSS_MARKET_ARB_NO_EDGE', violation_bps=violation_bps)\n    RETURN\n\n  // --- 6. Warning threshold ---\n  sizeMultiplier = 1.0\n  IF violation_bps < params.tolerance_bps:  // 30 bps default\n    WARN('CROSS_MARKET_ARB_VIOLATION_MARGINAL')\n    sizeMultiplier = 0.5\n\n  // --- 7. Size and emit legs ---\n  depthA = FETCH clob_public.depth(pair.market_id_A)\n  depthB = FETCH clob_public.depth(pair.market_id_B)\n  legSize = toPusdUnits(min(depthA, depthB, legBudget) * sizeMultiplier)\n\n  // Sell the overpriced side (market A)\n  EMIT OrderIntent(market_id=pair.market_id_A, outcome='YES', side='sell',\n                   price=p_A, size_pUSD=legSize, tif='FOK',\n                   builder={code: internal.builder_code, fee_bps: 25})\n  // Buy the underpriced side (market B)\n  EMIT OrderIntent(market_id=pair.market_id_B, outcome='YES', side='buy',\n                   price=p_B, size_pUSD=legSize, tif='FOK',\n                   builder={code: internal.builder_code, fee_bps: 25})\n\n  EMIT DecisionReport(intent_emitted=true, violation_bps=violation_bps,\n                      pair_id=pair_id, reason='CROSS_MARKET_ARB_EDGE_DETECTED')",
    "sdk_calls": [
      "fetchClobPublic('/markets/' + market_id)",
      "ws_market.subscribe('book', [market_id_A, market_id_B])",
      "toPusdUnits(rawFloat)",
      "buildOrderTypedData(orderParams, { name: 'CTFExchange', version: '2', chainId: 137 })",
      "internal.killswitch.status()",
      "internal.pairRegistry.get(pair_id)",
      "internal.builder_code"
    ],
    "complexity": "O(1) per market pair per book tick"
  },
  "wire_examples": {
    "input": [
      {
        "label": "Pair book tick \u2014 constraint violation detected (45 bps)",
        "source": "ws_market",
        "payload": {
          "pair_id": "pair_nominee_vs_winner_0042",
          "market_id_A": "0xcrossma000000000000000000000000000000000000000000000000000000001b",
          "market_id_B": "0xcrossma000000000000000000000000000000000000000000000000000000001a",
          "p_A_best_ask": "0.455",
          "p_B_best_ask": "0.410",
          "violation_bps": "45.0",
          "depth_A_pusd": "380.00",
          "depth_B_pusd": "290.00",
          "received_at_ms": 1746790200000
        }
      }
    ],
    "output": [
      {
        "label": "OrderIntent \u2014 market B leg (buy, FOK, builder-attributed)",
        "payload": {
          "intent_id": "oi_01HXCMA00001AA",
          "trace_id": "tr_01HXCMA0000TR1",
          "market_id": "0xcrossma000000000000000000000000000000000000000000000000000000001a",
          "outcome": "YES",
          "side": "buy",
          "price": "0.410",
          "size_pUSD": "250.00",
          "tif": "FOK",
          "post_only": false,
          "builder": {
            "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
            "fee_bps": 25
          },
          "negrisk_aware": false,
          "decision": {
            "pair_id": "pair_nominee_vs_winner_0042",
            "violation_bps": 45.0,
            "leg": "underpriced_B",
            "reasons": [
              "CROSS_MARKET_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_01HXCMA999ZZZ",
          "bot_id": "strat.cross_market_arb",
          "pair_id": "pair_nominee_vs_winner_0042",
          "intent_emitted": false,
          "violation_bps": 2.1,
          "reasons": [
            "CROSS_MARKET_ARB_NO_EDGE"
          ],
          "sampled": true,
          "evaluated_at_ms": 1746790201000
        }
      }
    ]
  },
  "reason_codes": [
    {
      "code": "CROSS_MARKET_ARB_EDGE_DETECTED",
      "severity": "INFO",
      "meaning": "Logical constraint violation exceeds tolerance_bps after fee buffer. Paired OrderIntents emitted.",
      "action": "Emit two FOK OrderIntents (one per market in the pair).",
      "user_message": "A pricing inconsistency between two related markets was detected and orders were placed to capture it."
    },
    {
      "code": "CROSS_MARKET_ARB_NO_EDGE",
      "severity": "INFO",
      "meaning": "Violation is below the 5 bps hard floor. No trade opportunity exists after fees.",
      "action": "Skip; emit sampled DecisionReport.",
      "user_message": "The pricing gap between linked markets was too small to trade profitably after fees."
    },
    {
      "code": "CROSS_MARKET_ARB_VIOLATION_MARGINAL",
      "severity": "WARN",
      "meaning": "Violation is between 5 and 30 bps (warning threshold). Trade is marginal.",
      "action": "Emit OrderIntents at 50% leg size; log warning.",
      "user_message": "A small pricing inconsistency was detected. Order sizes were reduced."
    },
    {
      "code": "CROSS_MARKET_ARB_PAIR_NOT_APPROVED",
      "severity": "HARD_REJECT",
      "meaning": "Market pair is not in the manually reviewed approved registry.",
      "action": "Skip; no OrderIntents emitted.",
      "user_message": ""
    },
    {
      "code": "CROSS_MARKET_ARB_LOW_SIMILARITY",
      "severity": "WARN",
      "meaning": "Embedding similarity score for the proposed pair is below min_similarity_score warning threshold.",
      "action": "Block pair auto-registration; require additional manual review.",
      "user_message": ""
    },
    {
      "code": "STALE_MARKET_DATA",
      "severity": "HARD_REJECT",
      "meaning": "Book snapshot older than 5s for either market in the pair.",
      "action": "Skip; no OrderIntents; cancel resting legs if any.",
      "user_message": "Market data was too old to act on safely."
    },
    {
      "code": "MARKET_CLOSED",
      "severity": "HARD_REJECT",
      "meaning": "Either market in the pair is closed or resolved.",
      "action": "Skip; cancel resting legs; deactivate pair until re-reviewed.",
      "user_message": "One of the linked markets has closed. No new orders will be placed."
    },
    {
      "code": "KILL_SWITCH_ACTIVE",
      "severity": "HARD_REJECT",
      "meaning": "Global kill switch is active.",
      "action": "Skip all pairs; 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_crossmarketarb_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_crossmarketarb_violation_bps",
        "type": "histogram",
        "unit": "basis_points",
        "labels": [
          "pair_id"
        ],
        "meaning": "Distribution of measured constraint violations in bps per pair."
      },
      {
        "name": "polytraders_strat_crossmarketarb_intents_emitted_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "pair_id",
          "leg"
        ],
        "meaning": "Total OrderIntents emitted by pair and leg (A/B)."
      },
      {
        "name": "polytraders_strat_crossmarketarb_eval_latency_ms",
        "type": "histogram",
        "unit": "milliseconds",
        "labels": [],
        "meaning": "Wall-clock time from pair book tick to OrderIntent emit."
      },
      {
        "name": "polytraders_strat_crossmarketarb_stale_feed_total",
        "type": "counter",
        "unit": "count",
        "labels": [],
        "meaning": "Evaluation cycles skipped due to stale feed on either market in a pair."
      },
      {
        "name": "polytraders_strat_crossmarketarb_active_pairs",
        "type": "gauge",
        "unit": "count",
        "labels": [],
        "meaning": "Number of currently active (manually approved) market pairs."
      }
    ],
    "alerts": [
      {
        "name": "CrossMarketArbHighStaleFeed",
        "condition": "rate(polytraders_strat_crossmarketarb_stale_feed_total[5m]) > 0.1",
        "severity": "warn",
        "runbook": "#runbook-crossmarketarb-stale-feed"
      },
      {
        "name": "CrossMarketArbNoEdgeStreak",
        "condition": "rate(polytraders_strat_crossmarketarb_decisions_total{verdict='false'}[15m]) / rate(polytraders_strat_crossmarketarb_decisions_total[15m]) > 0.99",
        "severity": "warn",
        "runbook": "#runbook-crossmarketarb-no-edge"
      },
      {
        "name": "CrossMarketArbHighLatency",
        "condition": "histogram_quantile(0.99, rate(polytraders_strat_crossmarketarb_eval_latency_ms_bucket[5m])) > 200",
        "severity": "warn",
        "runbook": "#runbook-crossmarketarb-latency"
      },
      {
        "name": "CrossMarketArbKillSwitchBlocking",
        "condition": "rate(polytraders_strat_crossmarketarb_decisions_total{reason_code='KILL_SWITCH_ACTIVE'}[1m]) > 0",
        "severity": "page",
        "runbook": "#runbook-killswitch"
      }
    ],
    "dashboards": [
      "Grafana \u2014 Strategy / CrossMarketArb violation distribution per pair",
      "Grafana \u2014 Strategy / CrossMarketArb active pairs and intent throughput"
    ],
    "log_level": "info"
  },
  "state": {
    "store": "redis",
    "shape": "Per-pair last book tick (p_A, p_B, violation_bps, timestamp_ms), resting leg IDs, and last DecisionReport; keyed by pair_id",
    "ttl": "30s for book state; resting leg IDs persistent until fill/cancel",
    "recovery": "On cold start, resting leg IDs re-fetched from clob_auth. Book state rebuilt from first ws_market tick per market.",
    "size_estimate": "~400 bytes per active pair; typically < 1 MB total"
  },
  "concurrency": {
    "execution_model": "actor-per-market",
    "max_in_flight": 20,
    "idempotency_key": "intent_id",
    "timeout_ms": 200,
    "backpressure": "drop oldest pending tick per pair_id when queue depth > 5",
    "locking": "per-pair_id mutex for Redis state write and resting leg tracking"
  },
  "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": "Paired FOK OrderIntents for risk guardrail evaluation before SmartRouter."
      },
      {
        "bot_id": "gov.builder_attribution",
        "what": "builder.code bytes32 on every 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 1s interval; emit STALE_MARKET_DATA if gap > 5s."
      },
      {
        "service": "Polymarket Gamma API",
        "sla": "99.9% (Polymarket-published)",
        "fallback": "Use cached market metadata; if cache > 5min old, flag pair for re-validation."
      }
    ]
  },
  "security_surfaces": {
    "signs_orders": true,
    "private_key_access": "signing-only",
    "abuse_vectors": [
      "Adversary manufactures a pair violation by moving one market via a large spoof order",
      "Replaying a signed OrderIntent after the constraint violation has closed",
      "Injecting a new pair into the registry by bypassing the manual review gate"
    ],
    "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",
      "Pair registry mutations require operator authentication and manual approval flag",
      "Book staleness check (5s) prevents acting on adversarially manipulated prices"
    ],
    "contract_calls": [
      {
        "contract": "CTFExchangeV2",
        "function": "matchOrders",
        "purpose": "Settlement of YES token purchases on both legs of the cross-market pair."
      }
    ]
  },
  "failure_injection": [
    {
      "scenario": "STALE_WS_FEED",
      "how_to_inject": "Pause ws_market WebSocket for market_A; let last_seen age beyond 5s",
      "expected_behaviour": "STALE_MARKET_DATA DecisionReport; no OrderIntents for the affected pair",
      "recovery": "Automatic on WebSocket reconnect."
    },
    {
      "scenario": "MARKET_RESOLVES_MID_TRADE",
      "how_to_inject": "Set mock market_B.resolved=true after market_A leg is emitted",
      "expected_behaviour": "MARKET_CLOSED; resting market_A leg cancelled; pair deactivated; ops alert",
      "recovery": "Manual pair re-review required before re-activation."
    },
    {
      "scenario": "VIOLATION_BELOW_HARD_FLOOR",
      "how_to_inject": "Set p_A=0.413, p_B=0.411 (violation=2 bps)",
      "expected_behaviour": "CROSS_MARKET_ARB_NO_EDGE; no OrderIntents; sampled DecisionReport",
      "recovery": "Automatic when violation widens."
    },
    {
      "scenario": "KILL_SWITCH_ON",
      "how_to_inject": "Set killswitch.active=true",
      "expected_behaviour": "No OrderIntents emitted for any pair",
      "recovery": "Automatic on manual KillSwitch reset."
    },
    {
      "scenario": "UNAPPROVED_PAIR_INJECTION",
      "how_to_inject": "Add a pair directly to registry without setting manually_approved=true",
      "expected_behaviour": "CROSS_MARKET_ARB_PAIR_NOT_APPROVED; no OrderIntents; ops log entry",
      "recovery": "Manual approval required."
    }
  ],
  "runbook": {
    "summary": "Cross-Market Arb incidents are typically stale feeds, market resolutions mid-trade (requiring manual pair review), or false constraint violations from thin-book manipulation. Stale feed incidents resolve automatically; market resolution events require ops review.",
    "oncall_actions": [
      {
        "alert": "CrossMarketArbHighStaleFeed",
        "first_action": "Check ws_market WebSocket connectivity for affected markets.",
        "escalate_to": "Infra on-call if disconnected > 2 minutes."
      },
      {
        "alert": "CrossMarketArbNoEdgeStreak",
        "first_action": "Review active pair price histories; confirm markets are not abnormally correlated.",
        "escalate_to": "Strategy pod lead if streak persists > 30 minutes."
      },
      {
        "alert": "CrossMarketArbHighLatency",
        "first_action": "Check Redis state write latency and clob_public REST call times for pair depth fetches.",
        "escalate_to": "Exec pod lead if p99 > 400ms sustained."
      },
      {
        "alert": "CrossMarketArbKillSwitchBlocking",
        "first_action": "Confirm KillSwitch activation was intentional.",
        "escalate_to": "Risk pod lead immediately."
      }
    ],
    "manual_overrides": [
      {
        "name": "deactivate_pair",
        "how": "Set pair.active=false in pairRegistry; requires operator auth",
        "when": "Either market in pair resolves, enters UMA dispute, or shows anomalous thin-book manipulation."
      },
      {
        "name": "pause_bot",
        "how": "polytraders bot pause strat.cross_market_arb",
        "when": "Multiple pairs showing simultaneous false violations (possible coordinated manipulation)."
      }
    ],
    "healthcheck": "GET /internal/health/cross-market-arb -> 200 if ws_market feed last_seen < 5s for all active pair markets, Redis reachable, KillSwitch inactive, at least one pair evaluated in last 60s."
  },
  "promotion_gates": {
    "to_shadow": [
      {
        "gate": "All unit tests pass including pair-approval invariant and FOK-only property",
        "how_measured": "CI test run",
        "threshold": "100% pass"
      },
      {
        "gate": "feeRateBps absence verified; require_manual_pair_review=true enforced 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_crossmarketarb_eval_latency_ms histogram",
        "threshold": "p99 < 200ms"
      },
      {
        "gate": "Zero unreviewed pair trades in 48h shadow run",
        "how_measured": "Pair approval audit log",
        "threshold": "0 incidents"
      }
    ],
    "to_general_live": [
      {
        "gate": "E2E: pair violation detected \u2192 two signed V2 FOK OrderIntents submitted on Polygon testnet",
        "how_measured": "E2E test",
        "threshold": "Pass"
      },
      {
        "gate": "Market resolution mid-trade correctly triggers leg cancellation 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",
    "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"
  }
}