{
  "schema_version": "1.0.0",
  "bot_id": "3.13",
  "bot_name": "Breakout-Follower",
  "slug": "breakout-follower",
  "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": "News materiality feed / Observation bus",
    "applies_to": "Polymarket binary markets where a high-confidence news event has just landed and the price has broken out beyond the Bollinger Band threshold, indicating a sustained directional move",
    "default_mode": "shadow_only",
    "user_visible": "Advanced details only",
    "developer_owner": "Polytraders core \u2014 Strategy pod"
  },
  "purpose": "Breakout-Follower is a momentum strategy that enters Polymarket binary markets when a high-confidence news event causes a price breakout beyond configurable Bollinger Band thresholds. It scales in across multiple steps and trails a stop using an ATR multiple, acting as the directional complement to Mean-Reversion Sniper.",
  "why_it_matters": [
    {
      "failure": "Low-confidence news signal",
      "consequence": "Entering on weak or ambiguous news causes the trade to mean-revert rather than sustain, resulting in a loss on each scale-in step."
    },
    {
      "failure": "Trail stop too tight",
      "consequence": "A trailing stop set too close to the current price gets triggered by normal volatility before the trend matures, cutting a profitable position prematurely."
    },
    {
      "failure": "No exit discipline on reversal",
      "consequence": "Without an ATR-based stop, a breakout that fails can become a runaway adverse position as the market reverts toward its pre-news price."
    }
  ],
  "polymarket_inputs": [
    {
      "input": "CLOB price and depth",
      "source": "ws_market",
      "required": true,
      "use": "Detect Bollinger Band breakout; compute entry price and available depth."
    },
    {
      "input": "Market status (open, resolved)",
      "source": "clob_public",
      "required": true,
      "use": "Skip resolved or closed markets."
    }
  ],
  "internal_inputs": [
    {
      "input": "KillSwitch active flag",
      "source": "KillSwitch",
      "required": true,
      "use": "Abort all intent emission if KillSwitch active."
    },
    {
      "input": "News materiality signal (confidence, direction)",
      "source": "internal (news feed adapter)",
      "required": true,
      "use": "Gate entry on news_confidence_min; determine breakout direction."
    },
    {
      "input": "ATR (average true range) per market",
      "source": "internal (volatility model)",
      "required": true,
      "use": "Compute trailing stop distance as trail_atr_multiple * ATR."
    },
    {
      "input": "Builder code bytes32",
      "source": "internal config",
      "required": true,
      "use": "Injected into builder field on every signed V2 OrderIntent."
    }
  ],
  "raw_params": [
    "news_confidence_min \u00b7 0\u20131",
    "bollinger_stdev \u00b7 float",
    "scale_in_steps \u00b7 int",
    "trail_atr_multiple \u00b7 float"
  ],
  "parameters": [
    {
      "name": "news_confidence_min",
      "default": 0.8,
      "warning": 0.65,
      "hard": 0.5,
      "controls": "Minimum news materiality confidence score (0\u20131) required before the bot will consider a breakout trade.",
      "why_default_matters": "0.80 ensures the news signal is high-quality before committing capital to a trend-following strategy.",
      "threshold_logic": [
        {
          "condition": ">= 0.80",
          "action": "Allow breakout evaluation"
        },
        {
          "condition": "0.65\u20130.80",
          "action": "WARN BREAKOUT_LOW_CONFIDENCE; halve scale-in size"
        },
        {
          "condition": "< 0.50",
          "action": "HARD_REJECT BREAKOUT_CONFIDENCE_BELOW_FLOOR"
        }
      ],
      "dev_check": "if news_confidence < params.hard: return skip('BREAKOUT_CONFIDENCE_BELOW_FLOOR')",
      "user_facing": "The news signal was not confident enough to justify a breakout trade."
    },
    {
      "name": "bollinger_stdev",
      "default": 2.0,
      "warning": 1.5,
      "hard": 1.0,
      "controls": "Number of standard deviations for the Bollinger Band; price must close beyond this band to trigger a breakout signal.",
      "why_default_matters": "2.0 standard deviations filters routine price noise and only signals genuine breakouts.",
      "threshold_logic": [
        {
          "condition": ">= 2.0",
          "action": "Strong breakout signal"
        },
        {
          "condition": "1.5\u20132.0",
          "action": "WARN BREAKOUT_WEAK_BAND; reduce size"
        },
        {
          "condition": "< 1.0",
          "action": "Reject config \u2014 too many false breakouts"
        }
      ],
      "dev_check": "if params.bollinger_stdev < params.hard: raise ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL')",
      "user_facing": "The price movement did not clearly exceed the breakout threshold."
    },
    {
      "name": "scale_in_steps",
      "default": 3,
      "warning": 4,
      "hard": 5,
      "controls": "Number of equal-sized scale-in orders spread across the breakout move. Limits per-step size.",
      "why_default_matters": "3 steps provides staged entry without over-committing on the initial signal.",
      "threshold_logic": [
        {
          "condition": "<= 3 steps",
          "action": "Standard scale-in"
        },
        {
          "condition": "> 5 steps",
          "action": "Reject config \u2014 PARAMETER_CHANGE_REQUIRES_APPROVAL"
        }
      ],
      "dev_check": "if params.scale_in_steps > params.hard: raise ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL')",
      "user_facing": "The trade is entered in equal stages to limit risk on each step."
    },
    {
      "name": "trail_atr_multiple",
      "default": 1.5,
      "warning": 1.0,
      "hard": 0.5,
      "controls": "Trailing stop distance as a multiple of the market's ATR. Stops below this level trigger an exit OrderIntent.",
      "why_default_matters": "1.5\u00d7 ATR provides a stop wide enough to survive normal volatility while protecting against trend reversals.",
      "threshold_logic": [
        {
          "condition": ">= 1.5\u00d7",
          "action": "Standard trail stop"
        },
        {
          "condition": "1.0\u20131.5\u00d7",
          "action": "WARN BREAKOUT_TIGHT_TRAIL"
        },
        {
          "condition": "< 0.5\u00d7",
          "action": "Reject config \u2014 stop too tight"
        }
      ],
      "dev_check": "if params.trail_atr_multiple < params.hard: raise ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL')",
      "user_facing": "A trailing stop will exit the position if the price reverses by more than the configured amount."
    }
  ],
  "default_config": {
    "bot_id": "strat.breakout_follower",
    "version": "0.1.0",
    "mode": "shadow_only",
    "defaults": {
      "news_confidence_min": 0.8,
      "bollinger_stdev": 2.0,
      "scale_in_steps": 3,
      "trail_atr_multiple": 1.5
    },
    "locked": {
      "news_confidence_min": {
        "min": 0.5
      },
      "bollinger_stdev": {
        "min": 1.0
      },
      "scale_in_steps": {
        "max": 5
      },
      "trail_atr_multiple": {
        "min": 0.5
      }
    }
  },
  "implementation_flow": [
    "Check KillSwitch; if active, emit no OrderIntents.",
    "FETCH news materiality signal; if confidence < hard (0.50), skip BREAKOUT_CONFIDENCE_BELOW_FLOOR.",
    "FETCH ws_market book; compute Bollinger Band from recent price history.",
    "IF price NOT outside bollinger_stdev bands: skip (no breakout).",
    "Determine breakout direction from news signal.",
    "Compute per-step size = max_position_pUSD / scale_in_steps.",
    "For step 1: EMIT IOC OrderIntent in breakout direction.",
    "Store entry price and current ATR in state; compute initial trail_stop = entry - trail_atr_multiple * ATR.",
    "On each subsequent tick: check if trail_stop triggered; if so, EMIT exit IOC OrderIntent.",
    "On new ticks in trend direction: update trail_stop upward (ratchet-only).",
    "EMIT DecisionReport with intent_emitted=true, reason=BREAKOUT_ENTRY or BREAKOUT_TRAIL_STOP."
  ],
  "decision_logic": {
    "approve": "news_confidence >= 0.80, price outside Bollinger Bands, market open, KillSwitch inactive.",
    "reshape_required": "Not applicable \u2014 reshaping handled by downstream Risk guardrail.",
    "reject": "news_confidence < 0.50; no breakout signal; market closed; KillSwitch active.",
    "warning_only": "news_confidence 0.65\u20130.80 triggers warning and halved scale-in size."
  },
  "decision_output_schema": "OrderIntent",
  "decision_output_example": {
    "intent_id": "oi_01HBF0000001A",
    "trace_id": "tr_01HBF000TR001",
    "market_id": "0xbf00000000000000000000000000000000000000000000000000000000000001",
    "outcome": "YES",
    "side": "buy",
    "price": "0.720",
    "size_pUSD": "150.00",
    "tif": "IOC",
    "post_only": false,
    "builder": {
      "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
      "fee_bps": 25
    },
    "negrisk_aware": false,
    "decision": {
      "news_confidence": 0.88,
      "bollinger_stdev_actual": 2.3,
      "step": 1,
      "trail_stop": 0.68,
      "reasons": [
        "BREAKOUT_ENTRY"
      ]
    },
    "comment": "fees are operator-set at match time in V2 \u2014 feeRateBps is NOT on the signed order"
  },
  "developer_log": {
    "bot_id": "strat.breakout_follower",
    "market_id": "0xbf00000000000000000000000000000000000000000000000000000000000001",
    "news_confidence": 0.88,
    "bollinger_stdev_actual": 2.3,
    "step": 1,
    "trail_stop": 0.68,
    "intent_emitted": true,
    "reason": "BREAKOUT_ENTRY",
    "emitted_at_ms": 1746790800000
  },
  "user_explanations": [
    {
      "situation": "Breakout trade entered",
      "message": "A high-confidence news event caused the market to break out of its normal range. The strategy entered in the direction of the move."
    },
    {
      "situation": "Trail stop triggered",
      "message": "The market reversed beyond the trailing stop level. The position was closed to protect against further loss."
    },
    {
      "situation": "News confidence too low",
      "message": "The news signal was not strong enough to justify a breakout trade."
    }
  ],
  "failure_modes": {
    "main_failure_mode": "False breakout: news signal causes a short-lived price spike that reverts before all scale-in steps execute, trapping the position at an unfavorable average entry.",
    "false_positive_risk": "Low-quality news signal misidentified as high-confidence produces a breakout entry on a market that subsequently mean-reverts.",
    "false_negative_risk": "news_confidence_min set too high filters out genuine breakouts on medium-confidence events.",
    "safe_fallback": "If ws_market feed is stale or news signal unavailable, skip without emitting any OrderIntent.",
    "required_dependencies": [
      "ws_market",
      "clob_public",
      "internal news feed adapter",
      "internal volatility model (ATR)",
      "KillSwitch",
      "internal builder code"
    ]
  },
  "acceptance_tests": {
    "unit": [
      {
        "test": "Enter step 1 when news_confidence=0.88, price beyond 2.0 Bollinger bands",
        "setup": "bollinger_stdev=2.0, scale_in_steps=3",
        "expected": "IOC OrderIntent step=1; reason=BREAKOUT_ENTRY"
      },
      {
        "test": "Skip when news_confidence=0.40 (< hard 0.50)",
        "setup": "news_confidence=0.40",
        "expected": "No OrderIntent; reason=BREAKOUT_CONFIDENCE_BELOW_FLOOR"
      },
      {
        "test": "Trail stop triggers exit when price reverts beyond ATR multiple",
        "setup": "trail_atr_multiple=1.5, ATR=0.03",
        "expected": "Exit IOC OrderIntent emitted; reason=BREAKOUT_TRAIL_STOP"
      }
    ],
    "integration": [
      {
        "test": "Full cycle: news signal \u2192 breakout \u2192 scale-in on Polygon testnet",
        "expected": "Order has builder.code, no feeRateBps, EIP-712 domain v2"
      }
    ],
    "property": [
      {
        "property": "Trail stop only ratchets in trend direction, never loosens",
        "required": "Always true"
      },
      {
        "property": "feeRateBps never present on any signed OrderIntent",
        "required": "Always true"
      }
    ]
  },
  "checklist_overrides": {},
  "legacy_goal": "Momentum-follow when an asymmetric news event hits \u2014 opposite-shaped twin of Mean-Reversion.",
  "legacy_pm_signals": [
    "Price breaking 30-min Bollinger band",
    "Order-flow strongly one-sided"
  ],
  "legacy_external_feeds": [
    "NewsIngest confidence score"
  ],
  "reporting_groups": [
    "strategy_decision"
  ],
  "reason_codes": [
    {
      "code": "BREAKOUT_ENTRY",
      "severity": "INFO",
      "meaning": "News confidence >= min, price beyond Bollinger Band. Scale-in step 1 OrderIntent emitted.",
      "action": "Emit IOC OrderIntent step 1.",
      "user_message": "A breakout was detected and a trade was entered."
    },
    {
      "code": "BREAKOUT_TRAIL_STOP",
      "severity": "INFO",
      "meaning": "Price reversed beyond trail_atr_multiple * ATR from peak. Exit OrderIntent emitted.",
      "action": "Emit exit IOC OrderIntent.",
      "user_message": "The trailing stop was triggered and the position was closed."
    },
    {
      "code": "BREAKOUT_LOW_CONFIDENCE",
      "severity": "WARN",
      "meaning": "News confidence between 0.65 and 0.80; scale-in size halved.",
      "action": "Emit at 50% size; log warning.",
      "user_message": "The news signal was moderate; a reduced-size trade was placed."
    },
    {
      "code": "BREAKOUT_CONFIDENCE_BELOW_FLOOR",
      "severity": "HARD_REJECT",
      "meaning": "News confidence below 0.50 hard floor.",
      "action": "Skip; no OrderIntent.",
      "user_message": "The news signal was too weak for a breakout trade."
    },
    {
      "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_breakoutfollower_decisions_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "verdict",
          "reason_code"
        ],
        "meaning": "Total evaluation cycles by verdict and reason."
      },
      {
        "name": "polytraders_strat_breakoutfollower_intents_emitted_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "step",
          "outcome"
        ],
        "meaning": "Total OrderIntents by scale-in step and outcome."
      },
      {
        "name": "polytraders_strat_breakoutfollower_news_confidence",
        "type": "histogram",
        "unit": "score",
        "labels": [],
        "meaning": "Distribution of news confidence scores at entry."
      },
      {
        "name": "polytraders_strat_breakoutfollower_eval_latency_ms",
        "type": "histogram",
        "unit": "milliseconds",
        "labels": [],
        "meaning": "Latency from news signal to OrderIntent emit."
      }
    ],
    "alerts": [
      {
        "name": "BreakoutFollowerStaleFeed",
        "condition": "rate(polytraders_strat_breakoutfollower_decisions_total{reason_code='STALE_MARKET_DATA'}[5m]) > 0.1",
        "severity": "warn",
        "runbook": "#runbook-breakout-stale"
      },
      {
        "name": "BreakoutFollowerKillSwitch",
        "condition": "rate(polytraders_strat_breakoutfollower_decisions_total{reason_code='KILL_SWITCH_ACTIVE'}[1m]) > 0",
        "severity": "page",
        "runbook": "#runbook-killswitch"
      },
      {
        "name": "BreakoutFollowerLowConfidence",
        "condition": "rate(polytraders_strat_breakoutfollower_decisions_total{reason_code='BREAKOUT_CONFIDENCE_BELOW_FLOOR'}[10m]) > 0.5",
        "severity": "warn",
        "runbook": "#runbook-breakout-confidence"
      }
    ]
  },
  "state": {
    "store": "redis",
    "shape": "Per active position: entry_price, current_trail_stop, steps_executed, atr; keyed by (market_id, direction)",
    "ttl": "Position state live until exit; 24h max TTL",
    "recovery": "On cold start, active positions re-read from exec layer; trail stop recomputed from current ATR.",
    "size_estimate": "~300 bytes per position; < 1 MB total"
  },
  "concurrency": {
    "execution_model": "actor-per-market",
    "max_in_flight": 25,
    "idempotency_key": "intent_id",
    "timeout_ms": 250,
    "backpressure": "drop oldest tick per market_id when queue > 3",
    "locking": "per-market_id mutex for trail-stop 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": "Polymarket CLOB WebSocket (ws_market)",
        "sla": "best-effort",
        "fallback": "Halt entries on disconnect; existing trail stops still evaluated from cached price."
      },
      {
        "service": "Internal news feed adapter",
        "sla": "internal SLA",
        "fallback": "If news signal unavailable, skip new entries; do not affect existing trail stops."
      }
    ]
  },
  "security_surfaces": {
    "signs_orders": true,
    "private_key_access": "signing-only",
    "abuse_vectors": [
      "News signal injection to induce false breakout trades",
      "ATR manipulation to force trail-stop triggers"
    ],
    "mitigations": [
      "News signals sourced from authenticated internal adapter only",
      "ATR computed from internal volatility model using CLOB tick history",
      "Trail stop ratchets only in trend direction \u2014 cannot be loosened programmatically"
    ]
  },
  "failure_injection": [
    {
      "scenario": "NEWS_SIGNAL_UNAVAILABLE",
      "how_to_inject": "Cut news feed adapter connection",
      "expected_behaviour": "No new entries; existing trail stops continue; DecisionReport emitted",
      "recovery": "Automatic when adapter reconnects."
    },
    {
      "scenario": "CONFIDENCE_BELOW_FLOOR",
      "how_to_inject": "Inject news signal with confidence=0.30",
      "expected_behaviour": "BREAKOUT_CONFIDENCE_BELOW_FLOOR; no OrderIntent",
      "recovery": "Automatic on next high-confidence signal."
    },
    {
      "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."
    }
  ],
  "runbook": {
    "summary": "Breakout-Follower incidents are typically stale news feeds (blocking new entries) or kill-switch activations. Trail-stop triggers are expected normal behavior and do not require escalation.",
    "oncall_actions": [
      {
        "alert": "BreakoutFollowerStaleFeed",
        "first_action": "Check news feed adapter connectivity and internal volatility model health.",
        "escalate_to": "Infra on-call if news adapter down > 5 min."
      },
      {
        "alert": "BreakoutFollowerKillSwitch",
        "first_action": "Confirm KillSwitch activation was intentional.",
        "escalate_to": "Risk pod lead immediately."
      },
      {
        "alert": "BreakoutFollowerLowConfidence",
        "first_action": "Review news adapter quality; check if confidence calibration has drifted.",
        "escalate_to": "Strategy pod lead."
      }
    ],
    "manual_overrides": [
      {
        "name": "exclude_market",
        "how": "Add market_id to config.excluded_markets",
        "when": "Market is showing anomalous price action unrelated to news events."
      }
    ],
    "healthcheck": "GET /internal/health/breakout-follower -> 200 if News feed age < 5s; ATR model active; KillSwitch inactive.. Red: News adapter down or KillSwitch active.."
  },
  "promotion_gates": {
    "to_shadow": [
      {
        "gate": "Unit tests pass including trail-stop trigger and low-confidence block",
        "how_measured": "CI test run",
        "threshold": "100% pass"
      }
    ],
    "to_limited_live": [
      {
        "gate": "p99 eval latency < 250ms over 24h shadow run",
        "how_measured": "polytraders_strat_breakoutfollower_eval_latency_ms histogram",
        "threshold": "p99 < 250ms"
      }
    ],
    "to_general_live": [
      {
        "gate": "E2E: news signal \u2192 breakout \u2192 scale-in OrderIntent on Polygon testnet with builder.code and no feeRateBps",
        "how_measured": "E2E test",
        "threshold": "Pass"
      }
    ]
  },
  "wire_examples": {
    "input": [
      {
        "label": "News signal \u2014 high confidence breakout",
        "source": "internal (news adapter)",
        "payload": {
          "market_id": "0xbf00000000000000000000000000000000000000000000000000000000000001",
          "news_confidence": 0.88,
          "direction": "YES",
          "bollinger_stdev_actual": 2.3,
          "atr": 0.03,
          "received_at_ms": 1746790800000
        }
      }
    ],
    "output": [
      {
        "label": "OrderIntent \u2014 breakout step 1 IOC buy YES",
        "payload": {
          "intent_id": "oi_01HBF0000001A",
          "market_id": "0xbf00000000000000000000000000000000000000000000000000000000000001",
          "outcome": "YES",
          "side": "buy",
          "price": "0.720",
          "size_pUSD": "150.00",
          "tif": "IOC",
          "builder": {
            "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
            "fee_bps": 25
          },
          "decision": {
            "step": 1,
            "reasons": [
              "BREAKOUT_ENTRY"
            ]
          }
        }
      }
    ]
  },
  "reference_implementation": {
    "pseudocode": "FUNCTION onNewsTick(market_id, newsSignal):\n  ks = FETCH internal.killswitch.status\n  IF ks.active: RETURN\n\n  // Gate on news confidence\n  IF newsSignal.confidence < params.news_confidence_min_hard:  // 0.50\n    EMIT DecisionReport(intent_emitted=false, reason='BREAKOUT_CONFIDENCE_BELOW_FLOOR')\n    RETURN\n\n  mkt = FETCH clob_public.GET('/markets/' + market_id)\n  IF mkt.closed OR mkt.resolved: RETURN\n\n  // Check Bollinger Band breakout\n  book = FETCH ws_market.book(market_id)\n  price = book.last_trade_price\n  bbUpper, bbLower = computeBollingerBands(market_id, params.bollinger_stdev)\n  IF price <= bbUpper AND price >= bbLower: RETURN  // No breakout\n\n  // Size computation\n  sizeMultiplier = 0.5 IF newsSignal.confidence < params.news_confidence_min_warn ELSE 1.0\n  IF sizeMultiplier < 1.0: WARN('BREAKOUT_LOW_CONFIDENCE')\n  stepSize = toPusdUnits(params.max_position_usd / params.scale_in_steps * sizeMultiplier)\n\n  // Emit step 1 entry\n  direction = newsSignal.direction\n  EMIT OrderIntent(market=market_id, outcome=direction, side='buy', price=book.best_ask,\n                   size_pUSD=stepSize, tif='IOC', builder=internalBuilderCode)\n\n  atr = FETCH internal.volatilityModel.ATR(market_id)\n  trailStop = book.last_trade_price - params.trail_atr_multiple * atr\n  STORE state.position(market_id, {step:1, trail_stop:trailStop, entry_price:price})\n  EMIT DecisionReport(intent_emitted=true, reason='BREAKOUT_ENTRY', step=1)",
    "sdk_calls": [
      "ws_market.subscribe('book', [market_id])",
      "fetchClobPublic('/markets/' + market_id)",
      "internal.newsAdapter.signal(market_id)",
      "internal.volatilityModel.ATR(market_id)",
      "buildOrderTypedData(orderParams, {name:'CTFExchange', version:'2', chainId:137})"
    ],
    "complexity": "O(1) per news tick per market; O(open_positions) per book tick for trail-stop evaluation"
  },
  "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"
  }
}