{
  "schema_version": "1.0.0",
  "bot_id": "3.7",
  "bot_name": "Cross-Venue Arb",
  "slug": "cross-venue-arb",
  "layer": "Strategy",
  "layer_key": "strat",
  "bot_class": "Alpha Strategy",
  "authority": [
    "Trade"
  ],
  "status": "planned",
  "readiness": "Spec started",
  "flagship": false,
  "is_reference": false,
  "public_export": false,
  "identity": {
    "layer": "Strategy",
    "bot_class": "Alpha Strategy",
    "authority": "Trade",
    "runs_before": "Risk guardrail pipeline",
    "runs_after": "Cross-venue price-feed adapter",
    "applies_to": "Polymarket binary markets where an identical resolution source matches a live Kalshi or PredictIt contract and the price gap exceeds min_gap_bps_after_fees",
    "default_mode": "shadow_only",
    "user_visible": "Advanced details only",
    "developer_owner": "Polytraders core \u2014 Strategy pod"
  },
  "purpose": "Cross-Venue Arb identifies price divergences between Polymarket and external prediction markets (Kalshi, PredictIt) that share an identical resolution source, then emits an OrderIntent to trade the cheaper leg on Polymarket. Both venues must resolve via the same authority; any mismatch blocks the trade.",
  "why_it_matters": [
    {
      "failure": "Resolution source mismatch",
      "consequence": "Two venues may look identical but resolve differently. Trading on a false parity creates an unhedged directional position rather than an arbitrage."
    },
    {
      "failure": "Stale external venue data",
      "consequence": "Cross-venue prices update asynchronously. Stale Kalshi or PredictIt snapshots produce phantom gaps that evaporate before the order lands."
    },
    {
      "failure": "Fee drag understated",
      "consequence": "Both venues charge fees; if min_gap_bps_after_fees is set too low, ostensible arb edges are consumed by combined fee drag leaving no profit."
    }
  ],
  "polymarket_inputs": [
    {
      "input": "Polymarket CLOB book (mid, spread, depth)",
      "source": "ws_market",
      "required": true,
      "use": "Compute Polymarket mid-price for gap calculation."
    },
    {
      "input": "Market metadata (resolution source, status)",
      "source": "clob_public",
      "required": true,
      "use": "Verify resolution source matches external venue before acting."
    },
    {
      "input": "Market open/closed status",
      "source": "clob_public",
      "required": true,
      "use": "Skip closed or resolved markets."
    }
  ],
  "internal_inputs": [
    {
      "input": "KillSwitch active flag",
      "source": "KillSwitch",
      "required": true,
      "use": "Abort all intent emission if KillSwitch active."
    },
    {
      "input": "External venue price snapshots (Kalshi, PredictIt)",
      "source": "internal (cross-venue adapter)",
      "required": true,
      "use": "Compute price gap between venues for the same event."
    },
    {
      "input": "Builder code bytes32",
      "source": "internal config",
      "required": true,
      "use": "Injected into builder field on every signed V2 OrderIntent."
    }
  ],
  "raw_params": [
    "min_gap_bps_after_fees \u00b7 int",
    "require_resolution_source_match \u00b7 bool (locked true)",
    "allowed_venues \u00b7 list",
    "manual_approval_required \u00b7 bool"
  ],
  "parameters": [
    {
      "name": "min_gap_bps_after_fees",
      "default": 150,
      "warning": 80,
      "hard": 30,
      "controls": "Minimum price gap in basis points between Polymarket and external venue, after deducting both venues' fees, required to emit an OrderIntent.",
      "why_default_matters": "150 bps provides a comfortable margin after combined fees (~50 bps each venue at p=0.5) and execution slippage.",
      "threshold_logic": [
        {
          "condition": ">= 150 bps",
          "action": "EMIT IOC OrderIntent"
        },
        {
          "condition": "80\u2013150 bps",
          "action": "WARN CROSS_VENUE_EDGE_MARGINAL; emit at 50% size"
        },
        {
          "condition": "< 30 bps (hard floor)",
          "action": "SKIP \u2014 CROSS_VENUE_NO_EDGE"
        }
      ],
      "dev_check": "if gap_bps < params.hard: return skip('CROSS_VENUE_NO_EDGE')",
      "user_facing": "The price gap between venues was too small after fees to justify a trade."
    },
    {
      "name": "require_resolution_source_match",
      "default": true,
      "warning": null,
      "hard": null,
      "controls": "If true (locked), both Polymarket and the external venue must resolve via the same official source. Cannot be disabled.",
      "why_default_matters": "Prevents trading on false parity that would create an unhedged directional position.",
      "threshold_logic": [
        {
          "condition": "mismatch detected",
          "action": "HARD_REJECT CROSS_VENUE_SOURCE_MISMATCH"
        }
      ],
      "dev_check": "if not sources_match(pm_market, ext_market): return skip('CROSS_VENUE_SOURCE_MISMATCH')",
      "user_facing": "The two venues resolve this question from different official sources \u2014 arb is not safe."
    },
    {
      "name": "allowed_venues",
      "default": [
        "kalshi",
        "predictit"
      ],
      "warning": null,
      "hard": null,
      "controls": "Whitelist of external prediction market venues the bot is permitted to reference for gap calculation.",
      "why_default_matters": "Restricts the bot to venues with known API contracts and fee schedules.",
      "threshold_logic": [
        {
          "condition": "venue not in list",
          "action": "SKIP \u2014 venue not whitelisted"
        }
      ],
      "dev_check": "if venue not in params.allowed_venues: skip()",
      "user_facing": "This venue is not in the approved list."
    },
    {
      "name": "manual_approval_required",
      "default": true,
      "warning": null,
      "hard": null,
      "controls": "If true, each new arb pair must receive one-time human approval before the bot trades it automatically.",
      "why_default_matters": "Prevents rogue pairing on newly created markets with ambiguous resolution matching.",
      "threshold_logic": [
        {
          "condition": "pair not approved",
          "action": "HARD_REJECT CROSS_VENUE_PAIR_NOT_APPROVED"
        }
      ],
      "dev_check": "if not pair_approved(pm_id, ext_id): return skip('CROSS_VENUE_PAIR_NOT_APPROVED')",
      "user_facing": "This venue pair has not been manually approved yet."
    }
  ],
  "default_config": {
    "bot_id": "strat.cross_venue_arb",
    "version": "0.1.0",
    "mode": "shadow_only",
    "defaults": {
      "min_gap_bps_after_fees": 150,
      "require_resolution_source_match": true,
      "manual_approval_required": true
    },
    "locked": {
      "require_resolution_source_match": {
        "value": true
      },
      "min_gap_bps_after_fees": {
        "min": 30
      }
    }
  },
  "implementation_flow": [
    "Check KillSwitch; if active, emit no OrderIntents.",
    "For each approved (pm_market, ext_market) pair: verify resolution sources match.",
    "FETCH clob_public market status; skip if closed or resolved.",
    "FETCH ws_market book for pm_market; FETCH external venue snapshot for ext_market.",
    "Compute gap_bps = (ext_price - pm_mid) * 10000 (or reverse if ext cheaper).",
    "Deduct estimated fees from both venues; compute net_gap_bps.",
    "IF net_gap_bps < hard (30 bps): SKIP, emit sampled DecisionReport CROSS_VENUE_NO_EDGE.",
    "IF net_gap_bps < warning (80 bps): WARN CROSS_VENUE_EDGE_MARGINAL; reduce size 50%.",
    "Compute order size = min(max_position_per_pair_usd, available_depth_usd).",
    "EMIT IOC OrderIntent: side=buy, price=best_ask_pm, size_pUSD=orderSize, builder=code.",
    "EMIT DecisionReport with intent_emitted=true, gap_bps, venues, reason CROSS_VENUE_EDGE_TRADE."
  ],
  "decision_logic": {
    "approve": "net_gap_bps >= min_gap_bps_after_fees, sources match, pair approved, market open, KillSwitch inactive.",
    "reshape_required": "Not applicable \u2014 reshaping handled by downstream Risk guardrail.",
    "reject": "net_gap_bps < 30 bps; source mismatch; pair not approved; market closed; KillSwitch active.",
    "warning_only": "net_gap_bps between 30 and 80 bps triggers warning and 50% size reduction."
  },
  "decision_output_schema": "OrderIntent",
  "decision_output_example": {
    "intent_id": "oi_01HCVA0000001A",
    "trace_id": "tr_01HCVA000TR001",
    "market_id": "0xcva0000000000000000000000000000000000000000000000000000000000001",
    "outcome": "YES",
    "side": "buy",
    "price": "0.610",
    "size_pUSD": "200.00",
    "tif": "IOC",
    "post_only": false,
    "builder": {
      "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
      "fee_bps": 25
    },
    "negrisk_aware": false,
    "decision": {
      "gap_bps": 165.0,
      "pm_mid": 0.61,
      "ext_price": 0.627,
      "venues": [
        "polymarket",
        "kalshi"
      ],
      "reasons": [
        "CROSS_VENUE_EDGE_TRADE"
      ]
    },
    "comment": "fees are operator-set at match time in V2 \u2014 feeRateBps is NOT on the signed order"
  },
  "developer_log": {
    "bot_id": "strat.cross_venue_arb",
    "market_id": "0xcva0000000000000000000000000000000000000000000000000000000000001",
    "pm_mid": 0.61,
    "ext_price": 0.627,
    "gap_bps": 165.0,
    "net_gap_bps": 155.0,
    "intent_emitted": true,
    "reason": "CROSS_VENUE_EDGE_TRADE",
    "emitted_at_ms": 1746790800000
  },
  "user_explanations": [
    {
      "situation": "Arb trade placed",
      "message": "A price difference was detected between Polymarket and another prediction market. An order was placed on Polymarket to capture the gap."
    },
    {
      "situation": "No edge after fees",
      "message": "The price gap between venues was too small after fees to justify a trade."
    },
    {
      "situation": "Source mismatch",
      "message": "The two venues resolve differently \u2014 no trade was placed."
    }
  ],
  "failure_modes": {
    "main_failure_mode": "Stale external venue snapshot: the external price ages faster than the Polymarket book, creating phantom gaps that disappear before the order lands.",
    "false_positive_risk": "Resolution source descriptions match textually but differ in practice, leading to a trade that is not a true arb.",
    "false_negative_risk": "min_gap_bps_after_fees set too high misses genuine arb opportunities on tighter markets.",
    "safe_fallback": "If external venue snapshot is stale (> 10s) or clob_public unavailable, emit STALE_MARKET_DATA and skip.",
    "required_dependencies": [
      "ws_market",
      "clob_public",
      "internal cross-venue adapter",
      "KillSwitch",
      "internal builder code"
    ]
  },
  "acceptance_tests": {
    "unit": [
      {
        "test": "Emit IOC when gap=165 bps, sources match, pair approved",
        "setup": "min_gap_bps_after_fees=150",
        "expected": "IOC OrderIntent emitted; reason=CROSS_VENUE_EDGE_TRADE"
      },
      {
        "test": "Skip when source mismatch",
        "setup": "pm_source='AP', ext_source='Reuters'",
        "expected": "No OrderIntent; reason=CROSS_VENUE_SOURCE_MISMATCH"
      },
      {
        "test": "Skip when KillSwitch active",
        "setup": "killswitch.active=true",
        "expected": "No OrderIntents emitted"
      }
    ],
    "integration": [
      {
        "test": "Full cycle: external snapshot \u2192 gap computed \u2192 IOC OrderIntent on Polygon testnet",
        "expected": "Order has builder.code bytes32, no feeRateBps, tif=IOC, EIP-712 domain v2"
      }
    ],
    "property": [
      {
        "property": "Bot never trades when require_resolution_source_match=true and sources differ",
        "required": "Always true"
      },
      {
        "property": "feeRateBps never present on any signed OrderIntent",
        "required": "Always true \u2014 V2 fees operator-set at match time"
      }
    ]
  },
  "checklist_overrides": {},
  "legacy_goal": "Trade Polymarket against Kalshi / PredictIt / sportsbooks when resolution sources align.",
  "legacy_pm_signals": [
    "Polymarket book + market metadata for resolution-source match"
  ],
  "legacy_external_feeds": [
    "Kalshi public API",
    "PredictIt market data",
    "Sportsbook odds-feed providers (semi-automated for sportsbook legs)"
  ],
  "reporting_groups": [
    "strategy_decision"
  ],
  "reason_codes": [
    {
      "code": "CROSS_VENUE_EDGE_TRADE",
      "severity": "INFO",
      "meaning": "Gap >= min_gap_bps_after_fees, sources match, pair approved. IOC OrderIntent emitted.",
      "action": "Emit IOC OrderIntent.",
      "user_message": "A price difference between venues was found and a trade was placed."
    },
    {
      "code": "CROSS_VENUE_NO_EDGE",
      "severity": "INFO",
      "meaning": "Net gap_bps below 30 bps hard floor after fee deduction.",
      "action": "Skip; emit sampled DecisionReport.",
      "user_message": "The price gap was too small after fees."
    },
    {
      "code": "CROSS_VENUE_EDGE_MARGINAL",
      "severity": "WARN",
      "meaning": "Gap between 30 and 80 bps; trade marginal; size reduced 50%.",
      "action": "Emit at 50% size; log warning.",
      "user_message": "A small gap was found; a reduced-size trade was placed."
    },
    {
      "code": "CROSS_VENUE_SOURCE_MISMATCH",
      "severity": "HARD_REJECT",
      "meaning": "Resolution sources differ between Polymarket and external venue.",
      "action": "Skip; no OrderIntent.",
      "user_message": "The venues resolve differently \u2014 no trade placed."
    },
    {
      "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."
    }
  ],
  "metrics": {
    "emitted": [
      {
        "name": "polytraders_strat_crossvenuearb_decisions_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "verdict",
          "reason_code",
          "ext_venue"
        ],
        "meaning": "Total evaluation cycles by verdict, reason code, and external venue."
      },
      {
        "name": "polytraders_strat_crossvenuearb_gap_bps",
        "type": "histogram",
        "unit": "basis_points",
        "labels": [
          "ext_venue"
        ],
        "meaning": "Distribution of net cross-venue gap in bps."
      },
      {
        "name": "polytraders_strat_crossvenuearb_intents_emitted_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "ext_venue"
        ],
        "meaning": "Total IOC OrderIntents emitted by external venue."
      },
      {
        "name": "polytraders_strat_crossvenuearb_eval_latency_ms",
        "type": "histogram",
        "unit": "milliseconds",
        "labels": [],
        "meaning": "Latency from snapshot receipt to OrderIntent emit."
      }
    ],
    "alerts": [
      {
        "name": "CrossVenueArbStaleFeed",
        "condition": "rate(polytraders_strat_crossvenuearb_decisions_total{reason_code='STALE_MARKET_DATA'}[5m]) > 0.1",
        "severity": "warn",
        "runbook": "#runbook-crossvenuearb-stale"
      },
      {
        "name": "CrossVenueArbKillSwitch",
        "condition": "rate(polytraders_strat_crossvenuearb_decisions_total{reason_code='KILL_SWITCH_ACTIVE'}[1m]) > 0",
        "severity": "page",
        "runbook": "#runbook-killswitch"
      },
      {
        "name": "CrossVenueArbNoEdge",
        "condition": "rate(polytraders_strat_crossvenuearb_decisions_total{verdict='skip',reason_code='CROSS_VENUE_NO_EDGE'}[10m]) / rate(polytraders_strat_crossvenuearb_decisions_total[10m]) > 0.95",
        "severity": "warn",
        "runbook": "#runbook-crossvenuearb-edge"
      }
    ]
  },
  "state": {
    "store": "redis",
    "shape": "Per approved (pm_market, ext_market) pair: last external snapshot price and timestamp; keyed by pair_id",
    "ttl": "30s per snapshot; pair approval state is persistent (manual reset only)",
    "recovery": "On cold start, external snapshots rebuilt from next polling cycle; pair approval loaded from config.",
    "size_estimate": "~150 bytes per pair; < 500 KB total"
  },
  "concurrency": {
    "execution_model": "actor-per-pair",
    "max_in_flight": 20,
    "idempotency_key": "intent_id",
    "timeout_ms": 300,
    "backpressure": "drop oldest snapshot per pair_id when queue depth > 2",
    "locking": "per-pair_id mutex for snapshot state"
  },
  "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": "IOC OrderIntents for risk guardrail evaluation."
      },
      {
        "bot_id": "gov.builder_attribution",
        "what": "builder.code bytes32 on every OrderIntent."
      }
    ],
    "sibling": [],
    "external": [
      {
        "service": "Kalshi public API",
        "sla": "best-effort",
        "fallback": "If snapshot > 10s, emit STALE_MARKET_DATA; skip pair."
      },
      {
        "service": "PredictIt market data feed",
        "sla": "best-effort",
        "fallback": "Same stale-data fallback as Kalshi."
      },
      {
        "service": "Polymarket CLOB WebSocket (ws_market)",
        "sla": "best-effort",
        "fallback": "Halt new entries on feed disconnect."
      }
    ]
  },
  "security_surfaces": {
    "signs_orders": true,
    "private_key_access": "signing-only",
    "abuse_vectors": [
      "Adversary injects stale cross-venue snapshots to create phantom arb gaps",
      "Resolution source metadata spoofing to bypass source-match gate"
    ],
    "mitigations": [
      "Cross-venue snapshots have a 10s staleness threshold",
      "Resolution source comparison uses Polymarket's authoritative clob_public metadata, not user input",
      "Manual pair approval required before bot trades any new pair"
    ]
  },
  "failure_injection": [
    {
      "scenario": "STALE_EXT_SNAPSHOT",
      "how_to_inject": "Freeze cross-venue adapter updates for > 10s",
      "expected_behaviour": "STALE_MARKET_DATA; no OrderIntents for affected pairs",
      "recovery": "Automatic when adapter resumes."
    },
    {
      "scenario": "SOURCE_MISMATCH",
      "how_to_inject": "Override test pair with mismatched resolution sources",
      "expected_behaviour": "CROSS_VENUE_SOURCE_MISMATCH; no OrderIntent",
      "recovery": "Automatic when correct pair is reloaded."
    },
    {
      "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."
    }
  ],
  "runbook": {
    "summary": "Cross-Venue Arb incidents are typically stale external feed snapshots (blocking all trades for affected pairs) or kill-switch activations. Source mismatches are expected and logged; escalate only if a previously approved pair suddenly mismatches.",
    "oncall_actions": [
      {
        "alert": "CrossVenueArbStaleFeed",
        "first_action": "Check cross-venue adapter connectivity; verify Kalshi/PredictIt API health.",
        "escalate_to": "Infra on-call if feed lag > 60s."
      },
      {
        "alert": "CrossVenueArbKillSwitch",
        "first_action": "Confirm KillSwitch activation was intentional.",
        "escalate_to": "Risk pod lead immediately."
      },
      {
        "alert": "CrossVenueArbNoEdge",
        "first_action": "Review whether venue fees have changed; verify min_gap_bps_after_fees config.",
        "escalate_to": "Strategy pod lead if prolonged."
      }
    ],
    "manual_overrides": [
      {
        "name": "revoke_pair_approval",
        "how": "Remove pair from config.approved_pairs",
        "when": "Resolution source match is no longer valid or venue policy has changed."
      }
    ],
    "healthcheck": "GET /internal/health/cross-venue-arb -> 200 if External snapshots age < 10s; \u22651 approved pair active; KillSwitch inactive.. Red: All external feeds stale or KillSwitch active.."
  },
  "promotion_gates": {
    "to_shadow": [
      {
        "gate": "All unit tests pass including source-mismatch block and stale-data skip",
        "how_measured": "CI test run",
        "threshold": "100% pass"
      }
    ],
    "to_limited_live": [
      {
        "gate": "p99 eval latency < 300ms over 24h shadow run",
        "how_measured": "polytraders_strat_crossvenuearb_eval_latency_ms histogram",
        "threshold": "p99 < 300ms"
      }
    ],
    "to_general_live": [
      {
        "gate": "E2E: snapshot received \u2192 gap computed \u2192 IOC OrderIntent on Polygon testnet with builder.code and no feeRateBps",
        "how_measured": "E2E test",
        "threshold": "Pass"
      }
    ]
  },
  "wire_examples": {
    "input": [
      {
        "label": "Cross-venue snapshot \u2014 Kalshi YES at 0.627, PM mid at 0.610",
        "source": "internal (cross-venue adapter)",
        "payload": {
          "pair_id": "cvp_kalshi_001",
          "pm_market_id": "0xcva0000000000000000000000000000000000000000000000000000000000001",
          "pm_mid": "0.610",
          "ext_venue": "kalshi",
          "ext_price": "0.627",
          "snapshot_age_ms": 800,
          "received_at_ms": 1746790800000
        }
      }
    ],
    "output": [
      {
        "label": "OrderIntent \u2014 cross-venue IOC buy YES",
        "payload": {
          "intent_id": "oi_01HCVA0000001A",
          "market_id": "0xcva0000000000000000000000000000000000000000000000000000000000001",
          "outcome": "YES",
          "side": "buy",
          "price": "0.610",
          "size_pUSD": "200.00",
          "tif": "IOC",
          "builder": {
            "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
            "fee_bps": 25
          },
          "decision": {
            "gap_bps": 165.0,
            "reasons": [
              "CROSS_VENUE_EDGE_TRADE"
            ]
          }
        }
      }
    ]
  },
  "reference_implementation": {
    "pseudocode": "FUNCTION onSnapshot(pair_id, pmMarket, extMarket, pmMid, extPrice):\n  ks = FETCH internal.killswitch.status\n  IF ks.active: RETURN\n\n  // Verify resolution source match\n  pmMeta = FETCH clob_public.GET('/markets/' + pmMarket)\n  IF pmMeta.resolution_source != extMarket.resolution_source:\n    EMIT DecisionReport(intent_emitted=false, reason='CROSS_VENUE_SOURCE_MISMATCH')\n    RETURN\n\n  IF pmMeta.closed OR pmMeta.resolved: RETURN\n\n  // Check snapshot freshness\n  IF snapshotAge(extMarket) > 10000:  // ms\n    EMIT DecisionReport(intent_emitted=false, reason='STALE_MARKET_DATA')\n    RETURN\n\n  // Compute gap\n  rawGap = (extPrice - pmMid) * 10000\n  fees = estimatedFees(pmMid)\n  netGap = rawGap - fees\n\n  IF netGap < params.min_gap_bps_after_fees_hard:  // 30 bps\n    IF random() < 0.01:\n      EMIT DecisionReport(intent_emitted=false, reason='CROSS_VENUE_NO_EDGE')\n    RETURN\n\n  sizeMultiplier = 0.5 IF netGap < params.min_gap_bps_after_fees_warn ELSE 1.0\n  IF sizeMultiplier < 1.0: WARN('CROSS_VENUE_EDGE_MARGINAL')\n\n  depth = FETCH clob_public.depth(pmMarket)\n  orderSize = toPusdUnits(min(params.max_position_per_pair_usd * sizeMultiplier, depth.available))\n\n  EMIT OrderIntent(market=pmMarket, outcome='YES', side='buy', price=pmMid,\n                   size_pUSD=orderSize, tif='IOC', builder=internalBuilderCode)\n  EMIT DecisionReport(intent_emitted=true, gap_bps=netGap, reason='CROSS_VENUE_EDGE_TRADE')",
    "sdk_calls": [
      "ws_market.subscribe('book', [pm_market_id])",
      "fetchClobPublic('/markets/' + pm_market_id)",
      "internal.crossVenueAdapter.getSnapshot(pair_id)",
      "buildOrderTypedData(orderParams, {name:'CTFExchange', version:'2', chainId:137})",
      "internal.builder_code"
    ],
    "complexity": "O(1) per snapshot per approved pair"
  },
  "api_surface": [
    "clob_public",
    "clob_auth",
    "ws_market",
    "internal"
  ],
  "network": [
    "polygon"
  ],
  "version": {
    "spec": "2.0.0",
    "implementation": "0.1.0",
    "schema": "2",
    "released": null,
    "planned_release": "Q3-2026"
  },
  "migration_history": [
    {
      "date": "2026-04-28",
      "from": "n/a",
      "to": "v2-spec",
      "reason": "Spec drafted post-CLOB-V2 cutover; bot not yet implemented",
      "action_taken": "Designed against V2 schema (pUSD, builder codes, V2 EIP-712 domain)"
    }
  ],
  "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": "Bot not yet implemented; designed against V2 schema (pUSD, builder codes, V2 EIP-712 domain). feeRateBps not present on any signed OrderIntent."
  },
  "reporting": {
    "emits_kinds": [
      "DecisionReport"
    ],
    "topics": [
      "polytraders.reports.decision"
    ],
    "cadence": "every-event",
    "retention_class": "2y",
    "sampling_rule": "emit-every",
    "bus_failure_action": "fail-closed",
    "user_visible": "yes",
    "consumes_kinds": [
      "ObservationReport",
      "RiskVote"
    ]
  },
  "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"
  }
}