{
  "schema_version": "1.0.0",
  "bot_id": "3.15",
  "bot_name": "VolatilityHarvest",
  "slug": "volatilityharvest",
  "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": "Volatility monitor / Observation bus",
    "applies_to": "Polymarket binary event markets with elevated realised volatility and thick order books, where posting inside the spread earns rebates from wide-spread trading activity",
    "default_mode": "shadow_only",
    "user_visible": "Advanced details only",
    "developer_owner": "Polytraders core \u2014 Strategy pod"
  },
  "purpose": "VolatilityHarvest quotes inside the spread on volatile Polymarket event markets with thick books, collecting maker rebates while managing inventory skew. It enters when realised volatility exceeds min_realised_vol, quotes inside the current spread by quote_inside_bps, and limits total position via max_inventory_skew. After a losing quote it cools off for cool_off_after_loss seconds.",
  "why_it_matters": [
    {
      "failure": "Inventory accumulates in one direction",
      "consequence": "Sustained directional order flow leaves the bot long or short at a mispriced level; inventory risk grows until the market moves against the accumulated position."
    },
    {
      "failure": "Volatility drops after entry",
      "consequence": "When realised volatility falls, spread income shrinks but position remains; the strategy is no longer compensated for carrying risk."
    },
    {
      "failure": "Cool-off period ignored after loss",
      "consequence": "Immediately re-quoting after an adverse fill can compound losses if the adverse move is continuing."
    }
  ],
  "polymarket_inputs": [
    {
      "input": "CLOB book (bid, ask, depth, trades)",
      "source": "ws_market",
      "required": true,
      "use": "Compute realised vol from tick history; measure current spread; detect depth thickness."
    },
    {
      "input": "Market 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": "Realised volatility per market",
      "source": "internal (volatility model)",
      "required": true,
      "use": "Gate entry on min_realised_vol threshold."
    },
    {
      "input": "Builder code bytes32",
      "source": "internal config",
      "required": true,
      "use": "Injected into builder field on every signed V2 OrderIntent."
    }
  ],
  "raw_params": [
    "min_realised_vol \u00b7 float",
    "quote_inside_bps \u00b7 int",
    "max_inventory_skew \u00b7 float",
    "cool_off_after_loss \u00b7 int"
  ],
  "parameters": [
    {
      "name": "min_realised_vol",
      "default": 0.05,
      "warning": 0.03,
      "hard": 0.01,
      "controls": "Minimum 1h realised volatility (annualised) required before quoting inside the spread.",
      "why_default_matters": "0.05 ensures there is enough price movement to generate meaningful order flow and rebates.",
      "threshold_logic": [
        {
          "condition": ">= 0.05",
          "action": "Allow spread quoting"
        },
        {
          "condition": "0.03\u20130.05",
          "action": "WARN VH_LOW_VOL; halve quote size"
        },
        {
          "condition": "< 0.01",
          "action": "HARD_REJECT VH_VOL_BELOW_FLOOR"
        }
      ],
      "dev_check": "if realised_vol < params.hard: return skip('VH_VOL_BELOW_FLOOR')",
      "user_facing": "Market volatility was too low for spread harvesting."
    },
    {
      "name": "quote_inside_bps",
      "default": 50,
      "warning": 20,
      "hard": 5,
      "controls": "How many bps inside the current best bid/ask the bot posts its maker quote.",
      "why_default_matters": "50 bps inside provides meaningful price improvement while still earning the maker rebate.",
      "threshold_logic": [
        {
          "condition": ">= 50 bps inside",
          "action": "Standard inside quote"
        },
        {
          "condition": "20\u201350 bps",
          "action": "WARN VH_TIGHT_INSIDE_QUOTE"
        },
        {
          "condition": "< 5 bps",
          "action": "HARD_REJECT \u2014 too close to crossing"
        }
      ],
      "dev_check": "if params.quote_inside_bps < params.hard: return skip('VH_QUOTE_TOO_TIGHT')",
      "user_facing": "The inside quote was too close to the crossing point."
    },
    {
      "name": "max_inventory_skew",
      "default": 0.3,
      "warning": 0.5,
      "hard": 0.7,
      "controls": "Maximum fraction of total position that can be on one side (YES or NO) before the bot stops quoting that side.",
      "why_default_matters": "0.30 limits directional exposure from accumulated inventory.",
      "threshold_logic": [
        {
          "condition": "<= 0.30",
          "action": "Quote both sides normally"
        },
        {
          "condition": "0.50\u20130.70",
          "action": "WARN VH_HIGH_SKEW; stop quoting skewed side"
        },
        {
          "condition": "> 0.70",
          "action": "HARD_REJECT VH_INVENTORY_LIMIT \u2014 stop all quoting"
        }
      ],
      "dev_check": "if abs(inventory_skew) > params.hard: return skip('VH_INVENTORY_LIMIT')",
      "user_facing": "Position skew limit reached; quoting paused until inventory rebalances."
    },
    {
      "name": "cool_off_after_loss",
      "default": 60,
      "warning": 30,
      "hard": 0,
      "controls": "Seconds to pause quoting on a market after an adverse fill that moves the mark against the position.",
      "why_default_matters": "60s cool-off prevents immediately compounding losses if directional flow continues.",
      "threshold_logic": [
        {
          "condition": ">= 60s",
          "action": "Standard cool-off"
        },
        {
          "condition": "30\u201360s",
          "action": "WARN VH_SHORT_COOLOFF"
        },
        {
          "condition": "= 0s",
          "action": "No cool-off \u2014 not recommended"
        }
      ],
      "dev_check": "if in_cooloff(market_id): return skip('VH_COOLOFF_ACTIVE')",
      "user_facing": "A brief pause is in effect after an adverse fill."
    }
  ],
  "default_config": {
    "bot_id": "strat.volatilityharvest",
    "version": "0.1.0",
    "mode": "shadow_only",
    "defaults": {
      "min_realised_vol": 0.05,
      "quote_inside_bps": 50,
      "max_inventory_skew": 0.3,
      "cool_off_after_loss": 60
    },
    "locked": {
      "min_realised_vol": {
        "min": 0.01
      },
      "quote_inside_bps": {
        "min": 5
      },
      "max_inventory_skew": {
        "max": 0.7
      }
    }
  },
  "implementation_flow": [
    "Check KillSwitch; if active, emit no OrderIntents.",
    "FETCH realised vol from volatility model; if < hard (0.01), skip VH_VOL_BELOW_FLOOR.",
    "Check cool-off state; if in cool-off for market_id, skip VH_COOLOFF_ACTIVE.",
    "FETCH ws_market book; check inventory skew.",
    "IF abs(inventory_skew) > hard (0.70): skip VH_INVENTORY_LIMIT.",
    "Compute inside quote: bid = best_bid + quote_inside_bps/10000; ask = best_ask - quote_inside_bps/10000.",
    "Compute quote size = min(max_quote_size_usd, available_capital).",
    "Adjust size down 50% if realised_vol < warning (0.03).",
    "EMIT GTC post-only OrderIntent for YES (bid side) and/or NO (ask side) depending on skew.",
    "EMIT DecisionReport with intent_emitted=true, reason=VH_QUOTE_EMITTED."
  ],
  "decision_logic": {
    "approve": "realised_vol >= 0.05, not in cool-off, inventory skew within bounds, market open, KillSwitch inactive.",
    "reshape_required": "Not applicable \u2014 reshaping handled by downstream Risk guardrail.",
    "reject": "realised_vol < 0.01; inventory skew > 0.70; in cool-off; KillSwitch active.",
    "warning_only": "realised_vol 0.03\u20130.05 or skew 0.50\u20130.70 triggers warning and size/side reduction."
  },
  "decision_output_schema": "OrderIntent",
  "decision_output_example": {
    "intent_id": "oi_01HVH0000001A",
    "trace_id": "tr_01HVH000TR001",
    "market_id": "0xvh000000000000000000000000000000000000000000000000000000000000001",
    "outcome": "YES",
    "side": "buy",
    "price": "0.495",
    "size_pUSD": "200.00",
    "tif": "GTC",
    "post_only": true,
    "builder": {
      "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
      "fee_bps": 10
    },
    "negrisk_aware": false,
    "decision": {
      "realised_vol": 0.08,
      "quote_inside_bps": 50,
      "inventory_skew": 0.1,
      "reasons": [
        "VH_QUOTE_EMITTED"
      ]
    },
    "comment": "fees are operator-set at match time in V2 \u2014 feeRateBps is NOT on the signed order"
  },
  "developer_log": {
    "bot_id": "strat.volatilityharvest",
    "market_id": "0xvh000000000000000000000000000000000000000000000000000000000000001",
    "realised_vol": 0.08,
    "quote_inside_bps": 50,
    "inventory_skew": 0.1,
    "intent_emitted": true,
    "reason": "VH_QUOTE_EMITTED",
    "emitted_at_ms": 1746790800000
  },
  "user_explanations": [
    {
      "situation": "Spread-harvesting quote placed",
      "message": "Market volatility is elevated. A maker quote was placed inside the spread to earn rebates from the resulting order flow."
    },
    {
      "situation": "Cool-off active",
      "message": "A brief pause is in effect after an adverse fill. Quoting will resume automatically."
    },
    {
      "situation": "Inventory skew limit reached",
      "message": "The position is too one-sided. Quoting is paused until inventory rebalances."
    }
  ],
  "failure_modes": {
    "main_failure_mode": "Inventory accumulation from persistent directional order flow: the bot continuously fills on one side without offsetting fills, building an exposed directional position.",
    "false_positive_risk": "Low realised vol that briefly spikes triggers quotes, then vol drops below min_realised_vol, leaving a quoted position with insufficient spread income.",
    "false_negative_risk": "min_realised_vol set too high misses genuine high-vol opportunities on markets with strong but sub-threshold volatility.",
    "safe_fallback": "If ws_market feed stale or volatility model unavailable, skip without emitting any OrderIntent.",
    "required_dependencies": [
      "ws_market",
      "clob_public",
      "internal volatility model",
      "KillSwitch",
      "internal builder code"
    ]
  },
  "acceptance_tests": {
    "unit": [
      {
        "test": "Emit GTC inside quote when vol=0.08, no cool-off, skew=0.1",
        "setup": "min_realised_vol=0.05, quote_inside_bps=50",
        "expected": "GTC post_only OrderIntent; reason=VH_QUOTE_EMITTED"
      },
      {
        "test": "Skip when in cool-off period",
        "setup": "cool_off_state=active, remaining=45s",
        "expected": "No OrderIntent; reason=VH_COOLOFF_ACTIVE"
      },
      {
        "test": "Skip when inventory skew > 0.70 hard limit",
        "setup": "inventory_skew=0.75",
        "expected": "No OrderIntent; reason=VH_INVENTORY_LIMIT"
      }
    ],
    "integration": [
      {
        "test": "Full cycle: vol signal \u2192 inside quote \u2192 GTC post-only on Polygon testnet",
        "expected": "Order has builder.code, post_only=true, no feeRateBps, EIP-712 domain v2"
      }
    ],
    "property": [
      {
        "property": "Bot never quotes when inventory skew > max_inventory_skew hard limit",
        "required": "Always true"
      },
      {
        "property": "feeRateBps never present on any signed OrderIntent",
        "required": "Always true"
      }
    ]
  },
  "checklist_overrides": {},
  "legacy_goal": "Quote inside thick books on volatile event markets and let the wide spreads pay you.",
  "legacy_pm_signals": [
    "Realised intra-tick volatility per market",
    "Bid/ask spread vs. realised vol ratio",
    "Trade-imbalance and cancel-rate signatures"
  ],
  "legacy_external_feeds": [],
  "reporting_groups": [
    "strategy_decision"
  ],
  "reason_codes": [
    {
      "code": "VH_QUOTE_EMITTED",
      "severity": "INFO",
      "meaning": "Vol >= min, no cool-off, skew within bounds. GTC post-only OrderIntent emitted.",
      "action": "Emit GTC maker quote.",
      "user_message": "A spread-harvesting maker quote was placed."
    },
    {
      "code": "VH_VOL_BELOW_FLOOR",
      "severity": "INFO",
      "meaning": "Realised vol below 0.01 hard floor.",
      "action": "Skip; no quote.",
      "user_message": "Market volatility was too low for spread harvesting."
    },
    {
      "code": "VH_COOLOFF_ACTIVE",
      "severity": "INFO",
      "meaning": "Post-loss cool-off period is active for this market.",
      "action": "Skip; no quote.",
      "user_message": "Brief cool-off after adverse fill is in effect."
    },
    {
      "code": "VH_INVENTORY_LIMIT",
      "severity": "HARD_REJECT",
      "meaning": "Inventory skew exceeds 0.70 hard limit.",
      "action": "Skip all quoting on this market.",
      "user_message": "Position skew limit reached; quoting paused."
    },
    {
      "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_volatilityharvest_decisions_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "verdict",
          "reason_code"
        ],
        "meaning": "Total evaluation cycles by verdict and reason."
      },
      {
        "name": "polytraders_strat_volatilityharvest_inventory_skew",
        "type": "gauge",
        "unit": "fraction",
        "labels": [
          "market_id"
        ],
        "meaning": "Current inventory skew per tracked market."
      },
      {
        "name": "polytraders_strat_volatilityharvest_realised_vol",
        "type": "histogram",
        "unit": "annualised",
        "labels": [],
        "meaning": "Distribution of realised vol at quote emission."
      },
      {
        "name": "polytraders_strat_volatilityharvest_eval_latency_ms",
        "type": "histogram",
        "unit": "milliseconds",
        "labels": [],
        "meaning": "Latency from vol signal to OrderIntent emit."
      }
    ],
    "alerts": [
      {
        "name": "VolatilityHarvestInventoryHigh",
        "condition": "polytraders_strat_volatilityharvest_inventory_skew > 0.50",
        "severity": "warn",
        "runbook": "#runbook-vh-inventory"
      },
      {
        "name": "VolatilityHarvestKillSwitch",
        "condition": "rate(polytraders_strat_volatilityharvest_decisions_total{reason_code='KILL_SWITCH_ACTIVE'}[1m]) > 0",
        "severity": "page",
        "runbook": "#runbook-killswitch"
      },
      {
        "name": "VolatilityHarvestInventoryLimit",
        "condition": "polytraders_strat_volatilityharvest_inventory_skew > 0.70",
        "severity": "page",
        "runbook": "#runbook-vh-inventory-limit"
      }
    ]
  },
  "state": {
    "store": "redis",
    "shape": "Per market: inventory_yes_usd, inventory_no_usd, last_adverse_fill_ms (cool-off), realised_vol; keyed by market_id",
    "ttl": "Session-scoped inventory; cool-off expires per cool_off_after_loss seconds",
    "recovery": "On cold start, inventory rebuilt from exec layer fills; cool-off resets to 0.",
    "size_estimate": "~250 bytes per tracked market; < 1 MB total"
  },
  "concurrency": {
    "execution_model": "actor-per-market",
    "max_in_flight": 30,
    "idempotency_key": "intent_id",
    "timeout_ms": 200,
    "backpressure": "drop oldest vol update per market_id when queue > 3",
    "locking": "per-market_id mutex for inventory and cool-off 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": "GTC post-only OrderIntents for risk guardrail evaluation."
      },
      {
        "bot_id": "gov.builder_attribution",
        "what": "builder.code bytes32 on every OrderIntent."
      }
    ],
    "sibling": [],
    "external": [
      {
        "service": "Polymarket CLOB WebSocket (ws_market)",
        "sla": "best-effort",
        "fallback": "Skip all quote emission on disconnect; cool-off timers continue."
      },
      {
        "service": "Internal volatility model",
        "sla": "internal SLA",
        "fallback": "If vol model unavailable, skip \u2014 treat as vol_below_floor."
      }
    ]
  },
  "security_surfaces": {
    "signs_orders": true,
    "private_key_access": "signing-only",
    "abuse_vectors": [
      "Adversary floods one side to push inventory skew to limit, halting the bot",
      "Spoof high realised-vol signal to induce quoting on illiquid markets"
    ],
    "mitigations": [
      "Inventory skew limit and hard cap prevent runaway one-sided exposure",
      "Realised vol sourced from authenticated internal model; not user-configurable in real-time"
    ]
  },
  "failure_injection": [
    {
      "scenario": "INVENTORY_SKEW_BREACH",
      "how_to_inject": "Inject fills only on YES side until skew > 0.70",
      "expected_behaviour": "VH_INVENTORY_LIMIT; all quoting on that market halted",
      "recovery": "Automatic when offsetting fills reduce skew below 0.50."
    },
    {
      "scenario": "COOLOFF_ACTIVE",
      "how_to_inject": "Trigger adverse fill; confirm cool-off fires",
      "expected_behaviour": "VH_COOLOFF_ACTIVE; no quotes for cool_off_after_loss seconds",
      "recovery": "Automatic after cool-off expires."
    },
    {
      "scenario": "KILL_SWITCH_ON",
      "how_to_inject": "Set killswitch.active=true",
      "expected_behaviour": "No OrderIntents emitted",
      "recovery": "Automatic on manual KillSwitch reset."
    }
  ],
  "runbook": {
    "summary": "VolatilityHarvest incidents are typically inventory skew breaches (auto-halt) or kill-switch activations. Cool-off triggers are expected normal behavior.",
    "oncall_actions": [
      {
        "alert": "VolatilityHarvestInventoryHigh",
        "first_action": "Review inventory skew on Grafana; check which markets have directional one-sided fills.",
        "escalate_to": "Strategy pod lead if skew > 0.60 and growing."
      },
      {
        "alert": "VolatilityHarvestKillSwitch",
        "first_action": "Confirm KillSwitch activation was intentional.",
        "escalate_to": "Risk pod lead immediately."
      },
      {
        "alert": "VolatilityHarvestInventoryLimit",
        "first_action": "Halt all quoting on affected markets (automatic). Review fill history for systematic adverse selection.",
        "escalate_to": "Risk pod lead; manual reset required."
      }
    ],
    "manual_overrides": [
      {
        "name": "reset_inventory",
        "how": "polytraders bot reset-inventory strat.volatilityharvest --market <id>",
        "when": "After ops review confirms inventory skew was from temporary one-sided flow, not systematic adverse selection."
      }
    ],
    "healthcheck": "GET /internal/health/volatilityharvest -> 200 if No market inventory skew > 0.50; vol model active; KillSwitch inactive.. Red: Any market at inventory hard limit or KillSwitch active.."
  },
  "promotion_gates": {
    "to_shadow": [
      {
        "gate": "Unit tests pass including inventory-limit halt and cool-off block",
        "how_measured": "CI test run",
        "threshold": "100% pass"
      }
    ],
    "to_limited_live": [
      {
        "gate": "p99 eval latency < 200ms over 24h shadow run",
        "how_measured": "polytraders_strat_volatilityharvest_eval_latency_ms histogram",
        "threshold": "p99 < 200ms"
      }
    ],
    "to_general_live": [
      {
        "gate": "E2E: vol signal \u2192 inside quote \u2192 GTC maker on Polygon testnet with post_only=true and builder.code",
        "how_measured": "E2E test",
        "threshold": "Pass"
      }
    ]
  },
  "wire_examples": {
    "input": [
      {
        "label": "Vol signal \u2014 market at 0.08 realised vol",
        "source": "internal (volatility model)",
        "payload": {
          "market_id": "0xvh000000000000000000000000000000000000000000000000000000000000001",
          "realised_vol": "0.08",
          "best_bid": "0.490",
          "best_ask": "0.510",
          "inventory_skew": "0.10",
          "received_at_ms": 1746790800000
        }
      }
    ],
    "output": [
      {
        "label": "OrderIntent \u2014 VH GTC post-only inside bid",
        "payload": {
          "intent_id": "oi_01HVH0000001A",
          "market_id": "0xvh000000000000000000000000000000000000000000000000000000000000001",
          "outcome": "YES",
          "side": "buy",
          "price": "0.495",
          "size_pUSD": "200.00",
          "tif": "GTC",
          "post_only": true,
          "builder": {
            "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
            "fee_bps": 10
          },
          "decision": {
            "realised_vol": 0.08,
            "quote_inside_bps": 50,
            "reasons": [
              "VH_QUOTE_EMITTED"
            ]
          }
        }
      }
    ]
  },
  "reference_implementation": {
    "pseudocode": "FUNCTION onVolUpdate(market_id, volSignal):\n  ks = FETCH internal.killswitch.status\n  IF ks.active: RETURN\n\n  // Vol gate\n  realisedVol = volSignal.realised_vol\n  IF realisedVol < params.min_realised_vol_hard:  // 0.01\n    EMIT DecisionReport(intent_emitted=false, reason='VH_VOL_BELOW_FLOOR')\n    RETURN\n\n  // Cool-off check\n  IF state.inCooloff(market_id):\n    EMIT DecisionReport(intent_emitted=false, reason='VH_COOLOFF_ACTIVE')\n    RETURN\n\n  // Inventory skew check\n  inventory = FETCH state.inventory(market_id)\n  skew = computeSkew(inventory.yes_usd, inventory.no_usd)\n  IF abs(skew) > params.max_inventory_skew_hard:  // 0.70\n    EMIT DecisionReport(intent_emitted=false, reason='VH_INVENTORY_LIMIT')\n    RETURN\n\n  book = FETCH ws_market.book(market_id)\n  insideBps = params.quote_inside_bps / 10000\n  bidPrice = book.best_bid + insideBps\n  askPrice = book.best_ask - insideBps\n\n  sizeMultiplier = 0.5 IF realisedVol < params.min_realised_vol_warn ELSE 1.0\n  IF sizeMultiplier < 1.0: WARN('VH_LOW_VOL')\n  orderSize = toPusdUnits(params.max_quote_size_usd * sizeMultiplier)\n\n  // Quote both sides unless inventory skew exceeds warn threshold\n  IF skew < params.max_inventory_skew_warn OR skew >= 0:\n    EMIT OrderIntent(market=market_id, outcome='YES', side='buy', price=bidPrice,\n                     size_pUSD=orderSize, tif='GTC', post_only=true, builder=code)\n  EMIT DecisionReport(intent_emitted=true, realised_vol=realisedVol, reason='VH_QUOTE_EMITTED')",
    "sdk_calls": [
      "ws_market.subscribe('book', [market_id])",
      "fetchClobPublic('/markets/' + market_id)",
      "internal.volatilityModel.realisedVol(market_id)",
      "buildOrderTypedData(orderParams, {name:'CTFExchange', version:'2', chainId:137})"
    ],
    "complexity": "O(1) per vol update per market; O(open_quotes) for inventory tracking"
  },
  "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"
  }
}