Sfoglia il codice sorgente

docs: add ultrashort validation specs

lxy 1 mese fa
parent
commit
62c4fa1f12

+ 668 - 0
docs/superpowers/plans/2026-04-16-okx-codex-trader.md

@@ -0,0 +1,668 @@
+# OKX Codex Trader Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Build a minimal Python CLI that fetches OKX swap candles, backtests a fixed `10/20` SMA strategy, asks local Codex CLI for a validated trading signal, and sends OKX demo orders for `BTC-USDT-SWAP` and `ETH-USDT-SWAP`.
+
+**Architecture:** The project is a small package rooted at `okx_codex_trader/`. `cli.py` owns argument parsing and command dispatch, `okx_client.py` owns OKX HTTP signing and REST calls, `backtest.py` owns deterministic SMA backtesting, and `codex_analyzer.py` owns subprocess execution of `codex` plus signal validation. Shared dataclasses live in `models.py`, and `config.py` enforces the runtime contract at the boundary.
+
+**Tech Stack:** Python 3.11+, `pytest`, `requests`, `argparse`, `dataclasses`, `subprocess`, standard library JSON and HMAC utilities
+
+---
+
+## File Structure
+
+- Create: `/home/lxy/okx-codex-trader/pyproject.toml`
+  - project metadata and dependencies
+- Create: `/home/lxy/okx-codex-trader/README.md`
+  - setup, environment variables, and command examples
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/__init__.py`
+  - package marker
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/config.py`
+  - required environment variables and runtime checks
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/models.py`
+  - normalized dataclasses for candles, signals, orders, positions, and backtest results
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/strategy.py`
+  - signal validation helpers and SMA crossover helpers
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/backtest.py`
+  - deterministic `10/20` SMA backtest engine with `10_000 USDT` initial equity
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/okx_client.py`
+  - signed demo REST client, instrument lookup, candle fetch, position mode check, leverage set, order placement, positions fetch
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/codex_analyzer.py`
+  - local `codex` subprocess call and JSON validation
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/cli.py`
+  - command entrypoint and JSON stdout formatting
+- Create: `/home/lxy/okx-codex-trader/tests/test_config.py`
+  - config boundary tests
+- Create: `/home/lxy/okx-codex-trader/tests/test_strategy.py`
+  - signal validation tests
+- Create: `/home/lxy/okx-codex-trader/tests/test_backtest.py`
+  - fixed-series backtest tests
+- Create: `/home/lxy/okx-codex-trader/tests/test_okx_client.py`
+  - signed request, demo header, sizing, and hedge-mode tests
+- Create: `/home/lxy/okx-codex-trader/tests/test_codex_analyzer.py`
+  - subprocess and JSON parsing tests
+- Create: `/home/lxy/okx-codex-trader/tests/test_cli.py`
+  - command parsing and dispatch tests
+
+### Task 1: Project Skeleton
+
+**Files:**
+- Create: `/home/lxy/okx-codex-trader/pyproject.toml`
+- Create: `/home/lxy/okx-codex-trader/README.md`
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/__init__.py`
+
+- [ ] **Step 1: Write the failing packaging smoke test**
+
+```python
+import okx_codex_trader
+
+
+def test_package_exports_version():
+    assert okx_codex_trader.__version__ == "0.1.0"
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `pytest /home/lxy/okx-codex-trader/tests/test_config.py -k package_exports_version -v`
+Expected: FAIL because the package module does not exist yet
+
+- [ ] **Step 3: Write minimal package metadata**
+
+```toml
+[project]
+name = "okx-codex-trader"
+version = "0.1.0"
+dependencies = ["requests>=2.32,<3"]
+
+[project.scripts]
+okx-codex-trader = "okx_codex_trader.cli:main"
+```
+
+```python
+__version__ = "0.1.0"
+```
+
+```bash
+git -C /home/lxy/okx-codex-trader init
+```
+
+- [ ] **Step 4: Run test to verify it passes**
+
+Run: `pytest /home/lxy/okx-codex-trader/tests/test_config.py -k package_exports_version -v`
+Expected: PASS
+
+- [ ] **Step 5: Commit**
+
+```bash
+git -C /home/lxy/okx-codex-trader add pyproject.toml README.md okx_codex_trader/__init__.py tests/test_config.py
+git -C /home/lxy/okx-codex-trader commit -m "chore: scaffold okx codex trader project"
+```
+
+### Task 2: Config And Models
+
+**Files:**
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/config.py`
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/models.py`
+- Test: `/home/lxy/okx-codex-trader/tests/test_config.py`
+- Test: `/home/lxy/okx-codex-trader/tests/test_strategy.py`
+
+- [ ] **Step 1: Write the failing config and signal validation tests**
+
+```python
+def test_load_config_requires_okx_credentials(monkeypatch):
+    monkeypatch.delenv("OKX_API_KEY", raising=False)
+    monkeypatch.delenv("OKX_API_SECRET", raising=False)
+    monkeypatch.delenv("OKX_API_PASSPHRASE", raising=False)
+
+    with pytest.raises(ValueError):
+        load_config()
+
+
+def test_validate_signal_rejects_leverage_out_of_range():
+    with pytest.raises(ValueError):
+        validate_signal({"action": "long", "confidence": 0.9, "leverage": 4, "entry_price": None, "take_profit_price": None, "stop_loss_price": None, "reason": "x"})
+
+
+def test_validate_signal_rejects_unknown_action():
+    with pytest.raises(ValueError):
+        validate_signal({"action": "hold", "confidence": 0.9, "leverage": 2, "entry_price": None, "take_profit_price": None, "stop_loss_price": None, "reason": "x"})
+
+
+def test_validate_signal_rejects_confidence_out_of_range():
+    with pytest.raises(ValueError):
+        validate_signal({"action": "long", "confidence": 1.2, "leverage": 2, "entry_price": None, "take_profit_price": None, "stop_loss_price": None, "reason": "x"})
+
+
+def test_validate_signal_requires_full_shape():
+    with pytest.raises(ValueError):
+        validate_signal({"action": "long", "confidence": 0.9, "leverage": 2})
+```
+
+- [ ] **Step 2: Run tests to verify they fail**
+
+Run: `pytest /home/lxy/okx-codex-trader/tests/test_config.py /home/lxy/okx-codex-trader/tests/test_strategy.py -v`
+Expected: FAIL because `load_config` and `validate_signal` are not implemented
+
+- [ ] **Step 3: Write minimal config and model code**
+
+```python
+@dataclass(frozen=True)
+class Candle:
+    symbol: str
+    ts: int
+    open: float
+    high: float
+    low: float
+    close: float
+    volume: float
+
+
+@dataclass(frozen=True)
+class TradeSignal:
+    action: Literal["long", "short", "flat"]
+    confidence: float
+    leverage: int
+    entry_price: float | None
+    take_profit_price: float | None
+    stop_loss_price: float | None
+    reason: str
+
+
+@dataclass(frozen=True)
+class InstrumentMeta:
+    ct_val: float
+    lot_sz: float
+    min_sz: float
+
+
+@dataclass(frozen=True)
+class Position:
+    symbol: str
+    pos_side: str
+    size: float
+    avg_price: float
+
+
+@dataclass(frozen=True)
+class OrderResult:
+    status: str
+    order_id: str | None
+    symbol: str
+    side: str | None
+    pos_side: str | None
+    order_type: str | None
+    size: float | None
+
+
+@dataclass(frozen=True)
+class BacktestTrade:
+    direction: str
+    entry_price: float
+    exit_price: float
+    margin_used: float
+    ending_equity: float
+
+
+@dataclass(frozen=True)
+class BacktestResult:
+    initial_equity: float
+    ending_equity: float
+    total_return: float
+    max_drawdown: float
+    win_rate: float
+    trade_count: int
+    trades: list[BacktestTrade]
+
+    def to_dict(self) -> dict[str, object]:
+        ...
+
+
+@dataclass(frozen=True)
+class Config:
+    api_key: str
+    api_secret: str
+    api_passphrase: str
+
+
+def load_config(env: Mapping[str, str] | None = None) -> Config:
+    source = os.environ if env is None else env
+    ...
+```
+
+- [ ] **Step 4: Run tests to verify they pass**
+
+Run: `pytest /home/lxy/okx-codex-trader/tests/test_config.py /home/lxy/okx-codex-trader/tests/test_strategy.py -v`
+Expected: PASS
+
+- [ ] **Step 5: Commit**
+
+```bash
+git -C /home/lxy/okx-codex-trader add okx_codex_trader/config.py okx_codex_trader/models.py okx_codex_trader/strategy.py tests/test_config.py tests/test_strategy.py
+git -C /home/lxy/okx-codex-trader commit -m "feat: add config and signal validation models"
+```
+
+### Task 3: Backtest Engine
+
+**Files:**
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/backtest.py`
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/strategy.py`
+- Test: `/home/lxy/okx-codex-trader/tests/test_backtest.py`
+
+- [ ] **Step 1: Write the failing SMA backtest test**
+
+```python
+def test_backtest_runs_fixed_sma_crossover_series():
+    candles = build_crossing_series()
+
+    result = run_backtest(candles=candles, leverage=2)
+
+    assert result.initial_equity == 10_000
+    assert result.trade_count == 2
+    assert result.trades[0].entry_price == candles[21].open
+    assert result.trades[0].exit_price == candles[30].open
+    assert result.trades[0].margin_used == 10_000
+    assert result.trades[1].margin_used == result.trades[0].ending_equity
+    assert result.ending_equity == 4_888.888888888889
+    assert result.total_return == -0.5111111111111112
+    assert "total_return" in result.to_dict()
+    assert "max_drawdown" in result.to_dict()
+    assert result.win_rate == 0.5
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `pytest /home/lxy/okx-codex-trader/tests/test_backtest.py -v`
+Expected: FAIL because `run_backtest` does not exist
+
+- [ ] **Step 3: Write minimal backtest implementation**
+
+```python
+def run_backtest(candles: list[Candle], leverage: int) -> BacktestResult:
+    fast = simple_moving_average(candles, 10)
+    slow = simple_moving_average(candles, 20)
+    ...
+```
+
+- [ ] **Step 4: Run test to verify it passes**
+
+Run: `pytest /home/lxy/okx-codex-trader/tests/test_backtest.py -v`
+Expected: PASS
+
+- [ ] **Step 5: Commit**
+
+```bash
+git -C /home/lxy/okx-codex-trader add okx_codex_trader/backtest.py okx_codex_trader/strategy.py tests/test_backtest.py
+git -C /home/lxy/okx-codex-trader commit -m "feat: add deterministic sma backtest engine"
+```
+
+### Task 4: OKX Demo Client
+
+**Files:**
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/okx_client.py`
+- Test: `/home/lxy/okx-codex-trader/tests/test_okx_client.py`
+
+- [ ] **Step 1: Write the failing client tests**
+
+```python
+def test_signed_demo_request_attaches_headers():
+    session = DummySession()
+    client = OkxClient(config=sample_config(), session=session)
+
+    client.get_candles(symbol="BTC-USDT-SWAP", bar="1H", limit=20)
+
+    request = session.last_request
+    assert request.headers["x-simulated-trading"] == "1"
+    assert request.headers["OK-ACCESS-KEY"] == "key"
+
+
+def test_build_contract_size_rounds_down_to_lot_size():
+    metadata = InstrumentMeta(ct_val=0.01, lot_sz=0.1, min_sz=0.1)
+    assert build_contract_size(notional=251, price=25_000, metadata=metadata) == 1.0
+
+
+def test_build_contract_size_fails_below_min_size():
+    metadata = InstrumentMeta(ct_val=0.01, lot_sz=1, min_sz=5)
+    with pytest.raises(ValueError):
+        build_contract_size(notional=250, price=25_100, metadata=metadata)
+
+
+def test_market_order_fetches_latest_price_before_sizing():
+    session = DummySession([
+        instrument_response(),
+        ticker_response(last="25000"),
+        account_config_response(pos_mode="long_short_mode"),
+        leverage_response(),
+        place_order_response(),
+    ])
+    client = OkxClient(config=sample_config(), session=session)
+
+    client.place_demo_order(symbol="BTC-USDT-SWAP", signal=market_long_signal(), margin_usdt=100)
+
+    assert session.request_paths == [
+        "/api/v5/public/instruments",
+        "/api/v5/market/ticker",
+        "/api/v5/account/config",
+        "/api/v5/account/set-leverage",
+        "/api/v5/trade/order",
+    ]
+
+
+def test_place_demo_order_fails_when_not_hedge_mode():
+    session = DummySession([
+        instrument_response(),
+        ticker_response(last="25000"),
+        account_config_response(pos_mode="net_mode"),
+    ])
+    client = OkxClient(config=sample_config(), session=session)
+
+    with pytest.raises(ValueError):
+        client.place_demo_order(symbol="BTC-USDT-SWAP", signal=market_long_signal(), margin_usdt=100)
+
+
+def test_limit_short_order_uses_sell_and_short_pos_side():
+    session = DummySession([
+        instrument_response(),
+        account_config_response(pos_mode="long_short_mode"),
+        leverage_response(),
+        place_order_response(),
+    ])
+    client = OkxClient(config=sample_config(), session=session)
+
+    client.place_demo_order(symbol="ETH-USDT-SWAP", signal=limit_short_signal(), margin_usdt=100)
+
+    order_request = session.last_json_body
+    assert order_request["ordType"] == "limit"
+    assert order_request["side"] == "sell"
+    assert order_request["posSide"] == "short"
+    assert order_request["px"] == "25000"
+    assert session.request_bodies[2]["lever"] == "2"
+    assert session.request_bodies[2]["mgnMode"] == "isolated"
+
+
+def test_flat_signal_returns_noop_without_order_submission():
+    session = DummySession([])
+    client = OkxClient(config=sample_config(), session=session)
+
+    result = client.place_demo_order(symbol="BTC-USDT-SWAP", signal=flat_signal(), margin_usdt=100)
+
+    assert result.status == "noop"
+    assert session.request_paths == []
+
+
+def test_place_demo_order_sends_computed_sz_and_ignores_tp_sl_fields():
+    session = DummySession([
+        instrument_response(),
+        ticker_response(last="25000"),
+        account_config_response(pos_mode="long_short_mode"),
+        leverage_response(),
+        place_order_response(),
+    ])
+    client = OkxClient(config=sample_config(), session=session)
+
+    client.place_demo_order(symbol="BTC-USDT-SWAP", signal=market_long_signal(), margin_usdt=100)
+
+    order_request = session.last_json_body
+    assert order_request["sz"] == "8"
+    assert "tpTriggerPx" not in order_request
+    assert "slTriggerPx" not in order_request
+
+
+def test_okx_error_payload_raises_value_error():
+    session = DummySession([error_response(code="51000", msg="parameter error")])
+    client = OkxClient(config=sample_config(), session=session)
+
+    with pytest.raises(ValueError):
+        client.get_candles(symbol="BTC-USDT-SWAP", bar="1H", limit=20)
+
+
+def test_get_positions_returns_normalized_positions():
+    session = DummySession([positions_response()])
+    client = OkxClient(config=sample_config(), session=session)
+
+    positions = client.get_positions(symbol="BTC-USDT-SWAP")
+
+    assert positions[0].symbol == "BTC-USDT-SWAP"
+```
+
+- [ ] **Step 2: Run tests to verify they fail**
+
+Run: `pytest /home/lxy/okx-codex-trader/tests/test_okx_client.py -v`
+Expected: FAIL because `OkxClient` and sizing helpers do not exist
+
+- [ ] **Step 3: Write minimal client implementation**
+
+```python
+class OkxClient:
+    base_url = "https://www.okx.com"
+
+    def get_candles(self, symbol: str, bar: str, limit: int) -> list[Candle]: ...
+    def get_instrument_meta(self, symbol: str) -> InstrumentMeta: ...
+    def get_last_price(self, symbol: str) -> float: ...
+    def ensure_hedge_mode(self) -> None: ...
+    def set_leverage(self, symbol: str, leverage: int, pos_side: str) -> None: ...
+    def place_demo_order(self, symbol: str, signal: TradeSignal, margin_usdt: float) -> OrderResult: ...
+    def get_positions(self, symbol: str) -> list[Position]: ...
+```
+
+- [ ] **Step 4: Run tests to verify they pass**
+
+Run: `pytest /home/lxy/okx-codex-trader/tests/test_okx_client.py -v`
+Expected: PASS
+
+- [ ] **Step 5: Commit**
+
+```bash
+git -C /home/lxy/okx-codex-trader add okx_codex_trader/okx_client.py tests/test_okx_client.py
+git -C /home/lxy/okx-codex-trader commit -m "feat: add okx demo client and contract sizing"
+```
+
+### Task 5: Codex Analyzer
+
+**Files:**
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/codex_analyzer.py`
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/strategy.py`
+- Test: `/home/lxy/okx-codex-trader/tests/test_codex_analyzer.py`
+
+- [ ] **Step 1: Write the failing analyzer tests**
+
+```python
+def test_analyzer_fails_when_codex_is_missing():
+    with pytest.raises(FileNotFoundError):
+        analyze_with_codex(candles=sample_candles(), symbol="BTC-USDT-SWAP", bar="1H", which=missing_which)
+
+
+def test_analyzer_rejects_non_json_output():
+    runner = fake_runner(stdout="not json")
+    with pytest.raises(ValueError):
+        analyze_with_codex(candles=sample_candles(), symbol="BTC-USDT-SWAP", bar="1H", runner=runner)
+
+
+def test_analyzer_rejects_json_leverage_out_of_range():
+    runner = fake_runner(stdout='{"action":"long","confidence":0.8,"leverage":4,"entry_price":null,"take_profit_price":null,"stop_loss_price":null,"reason":"x"}')
+    with pytest.raises(ValueError):
+        analyze_with_codex(candles=sample_candles(), symbol="BTC-USDT-SWAP", bar="1H", runner=runner)
+```
+
+- [ ] **Step 2: Run tests to verify they fail**
+
+Run: `pytest /home/lxy/okx-codex-trader/tests/test_codex_analyzer.py -v`
+Expected: FAIL because `analyze_with_codex` does not exist
+
+- [ ] **Step 3: Write minimal analyzer implementation**
+
+```python
+def analyze_with_codex(...):
+    command = ["codex", "exec", prompt]
+    completed = runner(command, capture_output=True, text=True, check=False)
+    ...
+```
+
+- [ ] **Step 4: Run tests to verify they pass**
+
+Run: `pytest /home/lxy/okx-codex-trader/tests/test_codex_analyzer.py -v`
+Expected: PASS
+
+- [ ] **Step 5: Commit**
+
+```bash
+git -C /home/lxy/okx-codex-trader add okx_codex_trader/codex_analyzer.py okx_codex_trader/strategy.py tests/test_codex_analyzer.py
+git -C /home/lxy/okx-codex-trader commit -m "feat: add codex analyzer integration"
+```
+
+### Task 6: CLI Commands
+
+**Files:**
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/cli.py`
+- Modify: `/home/lxy/okx-codex-trader/README.md`
+- Test: `/home/lxy/okx-codex-trader/tests/test_cli.py`
+
+- [ ] **Step 1: Write the failing CLI tests**
+
+```python
+def build_main_with_stubs():
+    client = fake_client()
+    main = main_factory(
+        load_config=lambda: sample_config(),
+        client_factory=lambda config: client,
+        analyze_fn=fake_analyze_with_codex,
+        write_text=real_write_text,
+    )
+    return main, client
+
+
+def test_fetch_history_prints_candle_json(capsys):
+    main, client = build_main_with_stubs()
+    exit_code = main(["fetch-history", "--symbol", "BTC-USDT-SWAP", "--bar", "1H", "--limit", "20"])
+    assert exit_code == 0
+    assert client.get_candles_called_with == ("BTC-USDT-SWAP", "1H", 20)
+    assert '"symbol": "BTC-USDT-SWAP"' in capsys.readouterr().out
+
+
+def test_backtest_prints_summary_json(capsys):
+    main, client = build_main_with_stubs()
+    exit_code = main(["backtest", "--symbol", "BTC-USDT-SWAP", "--bar", "1H", "--limit", "50", "--leverage", "2"])
+    assert exit_code == 0
+    assert client.get_candles_called_with == ("BTC-USDT-SWAP", "1H", 50)
+    assert '"trade_count"' in capsys.readouterr().out
+
+
+def test_analyze_writes_output_file_and_stdout(tmp_path, capsys):
+    main, client = build_main_with_stubs()
+    output_file = tmp_path / "signal.json"
+    exit_code = main([
+        "analyze",
+        "--symbol", "BTC-USDT-SWAP",
+        "--bar", "1H",
+        "--limit", "20",
+        "--output-file", str(output_file),
+    ])
+    assert exit_code == 0
+    assert output_file.exists()
+    assert client.get_candles_called_with == ("BTC-USDT-SWAP", "1H", 20)
+    assert '"action"' in capsys.readouterr().out
+
+
+def test_paper_order_reads_signal_file_and_outputs_order_json(tmp_path, capsys):
+    main, client = build_main_with_stubs()
+    signal_file = tmp_path / "signal.json"
+    signal_file.write_text(json.dumps(valid_signal()))
+
+    exit_code = main([
+        "paper-order",
+        "--symbol", "BTC-USDT-SWAP",
+        "--signal-file", str(signal_file),
+        "--margin-usdt", "100",
+    ])
+
+    assert exit_code == 0
+    assert client.place_demo_order_called
+    assert '"status"' in capsys.readouterr().out
+
+
+def test_positions_prints_position_json(capsys):
+    main, client = build_main_with_stubs()
+    exit_code = main(["positions", "--symbol", "BTC-USDT-SWAP"])
+    assert exit_code == 0
+    assert client.get_positions_called_with == "BTC-USDT-SWAP"
+    assert '"symbol": "BTC-USDT-SWAP"' in capsys.readouterr().out
+
+
+def test_cli_rejects_unsupported_symbol():
+    main, _ = build_main_with_stubs()
+    with pytest.raises(SystemExit):
+        main(["fetch-history", "--symbol", "SOL-USDT-SWAP", "--bar", "1H", "--limit", "20"])
+
+
+def test_cli_rejects_leverage_out_of_range():
+    main, _ = build_main_with_stubs()
+    with pytest.raises(SystemExit):
+        main(["backtest", "--symbol", "BTC-USDT-SWAP", "--bar", "1H", "--limit", "50", "--leverage", "4"])
+```
+
+- [ ] **Step 2: Run tests to verify they fail**
+
+Run: `pytest /home/lxy/okx-codex-trader/tests/test_cli.py -v`
+Expected: FAIL because `main` and command dispatch do not exist
+
+- [ ] **Step 3: Write minimal CLI implementation**
+
+```python
+def main(argv: Sequence[str] | None = None) -> int:
+    parser = build_parser()
+    args = parser.parse_args(argv)
+    ...
+```
+
+- [ ] **Step 4: Run tests to verify they pass**
+
+Run: `pytest /home/lxy/okx-codex-trader/tests/test_cli.py -v`
+Expected: PASS
+
+- [ ] **Step 5: Run the full test suite and commit**
+
+Run: `pytest /home/lxy/okx-codex-trader/tests -v`
+Expected: PASS
+
+```bash
+git -C /home/lxy/okx-codex-trader add okx_codex_trader/cli.py README.md tests/test_cli.py
+git -C /home/lxy/okx-codex-trader commit -m "feat: add cli commands for okx codex trader"
+```
+
+### Task 7: End-To-End Local Verification
+
+**Files:**
+- Modify: `/home/lxy/okx-codex-trader/README.md`
+
+- [ ] **Step 1: Document the exact runtime commands**
+
+```md
+python -m okx_codex_trader.cli fetch-history --symbol BTC-USDT-SWAP --bar 1H --limit 50
+python -m okx_codex_trader.cli backtest --symbol BTC-USDT-SWAP --bar 1H --limit 200 --leverage 2
+python -m okx_codex_trader.cli analyze --symbol BTC-USDT-SWAP --bar 1H --limit 50 --output-file signal.json
+python -m okx_codex_trader.cli paper-order --symbol BTC-USDT-SWAP --signal-file signal.json --margin-usdt 100
+python -m okx_codex_trader.cli positions --symbol BTC-USDT-SWAP
+```
+
+- [ ] **Step 2: Run the test suite one more time**
+
+Run: `pytest /home/lxy/okx-codex-trader/tests -v`
+Expected: PASS
+
+- [ ] **Step 3: Run one dry local command that does not require OKX credentials**
+
+Run: `python -m okx_codex_trader.cli --help`
+Expected: usage text printed successfully
+
+- [ ] **Step 4: Record any unverified external dependencies**
+
+Expected note:
+- OKX demo credentials were not exercised in automated tests
+- local `codex` runtime behavior outside mocked subprocess tests still requires manual verification
+
+- [ ] **Step 5: Commit**
+
+```bash
+git -C /home/lxy/okx-codex-trader add README.md
+git -C /home/lxy/okx-codex-trader commit -m "docs: add local verification commands"
+```

+ 450 - 0
docs/superpowers/plans/2026-04-17-bbmr-sampled-report.md

@@ -0,0 +1,450 @@
+# BBMR Sampled Report Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Add a `backtest-bbmr-report` command that samples multiple non-overlapping historical windows, runs a fixed Bollinger Band mean-reversion strategy on each window, and outputs one single-page HTML report with client-side segment switching.
+
+**Architecture:** Extend the current report pipeline instead of replacing it. Add a dedicated BBMR sampler/runner module that fetches one public candle pool, derives deterministic sampled windows with warm-up context, runs one backtest per sampled window, and feeds a single wrapper HTML that switches per-segment journals and plots client-side.
+
+**Tech Stack:** Python 3.11+, `backtesting.py`, `pandas`, existing OKX public candle client, existing CLI test harness, plain HTML/CSS/JS
+
+---
+
+## File Structure
+
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/cli.py`
+  - add `backtest-bbmr-report` CLI command and dispatch
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/bbmr_report.py`
+  - deterministic non-overlapping sampler
+  - local dataclasses for sampled segment definitions/results
+  - BBMR strategy runner
+  - aggregate and per-segment result shaping
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/report.py`
+  - add single-page sampled report HTML renderer
+- Modify: `/home/lxy/okx-codex-trader/tests/test_cli.py`
+  - CLI contract for new command
+- Create: `/home/lxy/okx-codex-trader/tests/test_bbmr_report.py`
+  - sampler, strategy, and single-page HTML tests
+- Modify: `/home/lxy/okx-codex-trader/README.md`
+  - add new command example after implementation is stable
+
+### Task 1: CLI Contract For `backtest-bbmr-report`
+
+**Files:**
+- Modify: `/home/lxy/okx-codex-trader/tests/test_cli.py`
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/cli.py`
+
+- [ ] **Step 1: Write the failing CLI test**
+
+```python
+def test_backtest_bbmr_report_generates_single_page_report(capsys, tmp_path):
+    main, client, bbmr_calls = build_main_with_stubs()
+    output_file = tmp_path / "bbmr.html"
+
+    exit_code = main(
+        [
+            "backtest-bbmr-report",
+            "--symbol",
+            "BTC-USDT-SWAP",
+            "--bar",
+            "3m",
+            "--history-limit",
+            "5000",
+            "--leverage",
+            "2",
+            "--segments",
+            "8",
+            "--window-size",
+            "300",
+            "--output-file",
+            str(output_file),
+        ]
+    )
+
+    assert exit_code == 0
+    assert client.get_candles_called_with == ("BTC-USDT-SWAP", "3m", 5000)
+    assert bbmr_calls[0]["segments"] == 8
+    assert bbmr_calls[0]["window_size"] == 300
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_cli.py -k bbmr -q`
+Expected: FAIL because `backtest-bbmr-report` is not implemented
+
+- [ ] **Step 3: Write minimal CLI implementation**
+
+```python
+bbmr_report = subparsers.add_parser("backtest-bbmr-report")
+bbmr_report.add_argument("--symbol", choices=SUPPORTED_SYMBOLS, required=True)
+bbmr_report.add_argument("--bar", required=True)
+bbmr_report.add_argument("--history-limit", type=int, required=True)
+bbmr_report.add_argument("--leverage", type=int, choices=(1, 2, 3), required=True)
+bbmr_report.add_argument("--segments", type=int, required=True)
+bbmr_report.add_argument("--window-size", type=int, required=True)
+bbmr_report.add_argument("--output-file", required=True)
+```
+
+```python
+if args.command == "backtest-bbmr-report":
+    candles = client.get_candles(args.symbol, args.bar, args.history_limit)
+    report = bbmr_report_fn(
+        candles=candles,
+        leverage=args.leverage,
+        output_file=Path(args.output_file),
+        symbol=args.symbol,
+        bar=args.bar,
+        segments=args.segments,
+        window_size=args.window_size,
+    )
+    print(_dump_json(report))
+    return 0
+```
+
+```python
+def main_factory(
+    *,
+    ...,
+    report_fn: Callable = generate_backtest_report,
+    bbmr_report_fn: Callable = generate_bbmr_sampled_report,
+):
+    ...
+```
+
+```python
+def build_main_with_stubs(...):
+    ...
+    def fake_bbmr_report(...):
+        ...
+    main = main_factory(..., bbmr_report_fn=fake_bbmr_report)
+```
+
+- [ ] **Step 4: Run test to verify it passes**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_cli.py -k bbmr -q`
+Expected: PASS
+
+- [ ] **Step 5: Commit**
+
+```bash
+git -C /home/lxy/okx-codex-trader add okx_codex_trader/cli.py tests/test_cli.py
+git -C /home/lxy/okx-codex-trader commit -m "feat: add bbmr report cli contract"
+```
+
+### Task 2: Deterministic Sampler Contract
+
+**Files:**
+- Create: `/home/lxy/okx-codex-trader/tests/test_bbmr_report.py`
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/bbmr_report.py`
+
+- [ ] **Step 1: Write the failing sampler tests**
+
+```python
+def test_sample_segments_rejects_history_pool_too_small():
+    candles = build_candles(count=1000)
+
+    with pytest.raises(ValueError, match="history pool is too small"):
+        sample_segments(candles=candles, segments=8, window_size=300, warmup_bars=69, seed=7)
+
+
+def test_sample_segments_returns_non_overlapping_ranges():
+    candles = build_candles(count=5000)
+
+    sampled = sample_segments(candles=candles, segments=4, window_size=300, warmup_bars=69, seed=7)
+
+    ranges = [(segment.context_start, segment.report_end) for segment in sampled]
+    assert len(ranges) == len(set(ranges))
+    for left, right in zip(ranges, ranges[1:]):
+        assert left[1] < right[0] or right[1] < left[0]
+
+
+def test_sample_segments_is_deterministic_for_same_seed():
+    candles = build_candles(count=5000)
+
+    a = sample_segments(candles=candles, segments=4, window_size=300, warmup_bars=69, seed=7)
+    b = sample_segments(candles=candles, segments=4, window_size=300, warmup_bars=69, seed=7)
+
+    assert a == b
+
+
+def test_generate_bbmr_sampled_report_rejects_insufficient_history_pool(tmp_path):
+    candles = build_candles(count=1000)
+
+    with pytest.raises(ValueError, match="history pool is too small"):
+        generate_bbmr_sampled_report(
+            candles=candles,
+            leverage=2,
+            output_file=tmp_path / "bbmr.html",
+            symbol="BTC-USDT-SWAP",
+            bar="3m",
+            segments=8,
+            window_size=300,
+        )
+```
+
+- [ ] **Step 2: Run tests to verify they fail**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_bbmr_report.py -k sample_segments -q`
+Expected: FAIL because sampler functions do not exist
+
+- [ ] **Step 3: Write minimal deterministic sampler**
+
+```python
+WARMUP_BARS = 69
+SAMPLER_SEED = 7
+```
+
+```python
+def sample_segments(...):
+    required = segments * (window_size + warmup_bars)
+    if len(candles) < required:
+        raise ValueError("history pool is too small")
+    ...
+```
+
+- [ ] **Step 4: Run tests to verify they pass**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_bbmr_report.py -k sample_segments -q`
+Expected: PASS
+
+- [ ] **Step 5: Commit**
+
+```bash
+git -C /home/lxy/okx-codex-trader add okx_codex_trader/bbmr_report.py tests/test_bbmr_report.py
+git -C /home/lxy/okx-codex-trader commit -m "feat: add deterministic bbmr segment sampler"
+```
+
+### Task 3: BBMR Strategy Rules
+
+**Files:**
+- Modify: `/home/lxy/okx-codex-trader/tests/test_bbmr_report.py`
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/bbmr_report.py`
+
+- [ ] **Step 1: Write the failing strategy tests**
+
+```python
+def test_run_bbmr_segment_produces_long_trade_on_lower_band_reversion():
+    candles = build_lower_band_reversion_fixture()
+
+    result = run_bbmr_segment(candles=candles, leverage=2)
+
+    assert result.trade_count == 1
+    assert result.trades[0]["side"] == "Long"
+
+
+def test_run_bbmr_segment_produces_short_trade_on_upper_band_reversion():
+    candles = build_upper_band_reversion_fixture()
+
+    result = run_bbmr_segment(candles=candles, leverage=2)
+
+    assert result.trade_count == 1
+    assert result.trades[0]["side"] == "Short"
+
+
+def test_run_bbmr_segment_marks_open_position_to_market_but_keeps_journal_realized_only():
+    candles = build_open_tail_fixture()
+
+    result = run_bbmr_segment(candles=candles, leverage=2)
+
+    assert result.trade_count == 0
+    assert result.trades == []
+    assert result.total_return != 0
+```
+
+- [ ] **Step 2: Run tests to verify they fail**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_bbmr_report.py -k run_bbmr_segment -q`
+Expected: FAIL because BBMR runner does not exist
+
+- [ ] **Step 3: Write minimal BBMR segment runner**
+
+Implement fixed rules from the spec and lock them with explicit tests:
+- Bollinger `20/2`
+- bandwidth `(upper-lower)/middle`
+- filter against previous `50` completed bandwidth values
+- stop-loss intrabar at stop price
+- same-bar sequencing: stop first, then close-based exit, then entry only if flat and no exit occurred
+- final reported candle cannot generate next-open entry or mean-reversion exit
+- ending return and drawdown use final-close mark-to-market if trade remains open
+
+Add failing tests for:
+
+```python
+def test_run_bbmr_segment_excludes_warmup_from_reported_trade_times():
+    ...
+
+
+def test_run_bbmr_segment_does_not_generate_entry_from_final_reported_candle():
+    ...
+
+
+def test_run_bbmr_segment_stop_loss_takes_precedence_over_close_exit():
+    ...
+
+
+def test_run_bbmr_segment_does_not_reverse_on_exit_bar():
+    ...
+```
+
+- [ ] **Step 4: Run tests to verify they pass**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_bbmr_report.py -k run_bbmr_segment -q`
+Expected: PASS
+
+- [ ] **Step 5: Commit**
+
+```bash
+git -C /home/lxy/okx-codex-trader add okx_codex_trader/bbmr_report.py tests/test_bbmr_report.py
+git -C /home/lxy/okx-codex-trader commit -m "feat: add bbmr segment backtest rules"
+```
+
+### Task 4: Single-Page HTML Wrapper
+
+**Files:**
+- Modify: `/home/lxy/okx-codex-trader/tests/test_bbmr_report.py`
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/report.py`
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/bbmr_report.py`
+
+- [ ] **Step 1: Write the failing HTML wrapper tests**
+
+```python
+def test_render_bbmr_sampled_report_contains_summary_and_segment_switcher():
+    html = render_bbmr_sampled_report(...)
+    assert "history limit" in html.lower()
+    assert "segment count" in html.lower()
+    assert "window size" in html.lower()
+    assert "average return across segments" in html.lower()
+    assert "median return across segments" in html.lower()
+    assert "best segment return" in html.lower()
+    assert "worst segment return" in html.lower()
+    assert "segment-selector" in html
+    assert "data-segment-index=\"0\"" in html
+
+
+def test_render_bbmr_sampled_report_contains_trade_journal_and_plot_reference():
+    html = render_bbmr_sampled_report(...)
+    assert "Trade Journal" in html
+    assert "/files/segment-0.plot.html" in html
+    assert "sampled range start time" in html.lower()
+    assert "sampled range end time" in html.lower()
+    assert "trade count" in html.lower()
+    assert "total return" in html.lower()
+    assert "win rate" in html.lower()
+    assert "max drawdown" in html.lower()
+```
+
+- [ ] **Step 2: Run tests to verify they fail**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_bbmr_report.py -k render_bbmr -q`
+Expected: FAIL because sampled report wrapper does not exist
+
+- [ ] **Step 3: Write minimal single-page wrapper**
+
+Render:
+- top-level aggregate metrics
+- strategy description
+- sampled ranges table/list
+- segment buttons/select element
+- one active segment panel at a time
+- client-side JS to switch visible panel and iframe
+
+- [ ] **Step 4: Run tests to verify they pass**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_bbmr_report.py -k render_bbmr -q`
+Expected: PASS
+
+- [ ] **Step 5: Commit**
+
+```bash
+git -C /home/lxy/okx-codex-trader add okx_codex_trader/report.py okx_codex_trader/bbmr_report.py tests/test_bbmr_report.py
+git -C /home/lxy/okx-codex-trader commit -m "feat: add bbmr sampled single-page html report"
+```
+
+### Task 5: End-To-End Report Generation
+
+**Files:**
+- Modify: `/home/lxy/okx-codex-trader/tests/test_cli.py`
+- Modify: `/home/lxy/okx-codex-trader/tests/test_bbmr_report.py`
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/bbmr_report.py`
+- Modify: `/home/lxy/okx-codex-trader/README.md`
+
+- [ ] **Step 1: Write the failing end-to-end generator test**
+
+```python
+def test_generate_bbmr_sampled_report_writes_wrapper_and_plot_files(tmp_path):
+    candles = build_candles(count=5000)
+
+    result = generate_bbmr_sampled_report(
+        candles=candles,
+        leverage=2,
+        output_file=tmp_path / "bbmr.html",
+        symbol="BTC-USDT-SWAP",
+        bar="3m",
+        segments=3,
+        window_size=300,
+    )
+
+    assert Path(result["report_file"]).exists()
+    assert result["segment_count"] == 3
+    assert (tmp_path / "bbmr.segment-0.plot.html").exists()
+```
+
+- [ ] **Step 2: Run tests to verify they fail**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_bbmr_report.py -k generate_bbmr -q`
+Expected: FAIL because the assembled generator does not exist
+
+- [ ] **Step 3: Write minimal generator and docs**
+
+Implement:
+- one wrapper HTML file
+- one plot file per segment
+- aggregate summary JSON return to CLI
+- README command example
+
+- [ ] **Step 4: Run focused tests to verify they pass**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_cli.py tests/test_bbmr_report.py -q`
+Expected: PASS
+
+- [ ] **Step 5: Commit**
+
+```bash
+git -C /home/lxy/okx-codex-trader add README.md okx_codex_trader/bbmr_report.py okx_codex_trader/report.py tests/test_bbmr_report.py tests/test_cli.py
+git -C /home/lxy/okx-codex-trader commit -m "feat: add sampled bbmr backtest report"
+```
+
+### Task 6: Full Verification And Real Report
+
+**Files:**
+- Verify only: `/home/lxy/okx-codex-trader`
+
+- [ ] **Step 1: Run full automated tests**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest -q`
+Expected: PASS
+
+- [ ] **Step 2: Generate a real sampled report**
+
+Run:
+
+```bash
+cd /home/lxy/okx-codex-trader
+.venv/bin/python -m okx_codex_trader.cli backtest-bbmr-report \
+  --symbol BTC-USDT-SWAP \
+  --bar 3m \
+  --history-limit 5000 \
+  --leverage 2 \
+  --segments 8 \
+  --window-size 300 \
+  --output-file bbmr-sampled-report.html
+```
+
+Expected: one wrapper HTML plus segment plot files written next to it
+
+- [ ] **Step 3: Commit**
+
+```bash
+git -C /home/lxy/okx-codex-trader add README.md okx_codex_trader/cli.py okx_codex_trader/bbmr_report.py okx_codex_trader/report.py tests/test_bbmr_report.py tests/test_cli.py
+git -C /home/lxy/okx-codex-trader commit -m "feat: ship sampled bbmr report flow"
+```

+ 443 - 0
docs/superpowers/plans/2026-04-17-bbsb-sampled-report.md

@@ -0,0 +1,443 @@
+# BBSB Sampled Report Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Add a `backtest-bbsb-report` command that samples multiple non-overlapping historical windows, runs a fixed Bollinger Band squeeze-breakout strategy on each window, and outputs one single-page HTML report with client-side segment switching.
+
+**Architecture:** Keep the existing report paths intact. Add one parallel `bbsb_report.py` module that follows the same public-candle pool and deterministic block-sampler shape as `bbmr_report.py`, but swaps in breakout entry rules plus fixed intrabar TP/SL exits. The CLI gets one new command and the HTML shell stays journal-first.
+
+**Tech Stack:** Python 3.11+, `pandas`, `bokeh`, existing OKX public candle client, existing CLI test harness, plain HTML/CSS/JS
+
+---
+
+## File Structure
+
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/cli.py`
+  - add `backtest-bbsb-report` CLI command and dispatch
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/bbsb_report.py`
+  - deterministic sampler
+  - local dataclasses for sampled segment definitions/results
+  - BBSB strategy runner
+  - aggregate and per-segment result shaping
+- Modify: `/home/lxy/okx-codex-trader/tests/test_cli.py`
+  - CLI contract for new command
+- Create: `/home/lxy/okx-codex-trader/tests/test_bbsb_report.py`
+  - sampler, strategy, generator, and single-page HTML tests
+- Modify: `/home/lxy/okx-codex-trader/README.md`
+  - add new command example after implementation is stable
+
+### Task 1: CLI Contract For `backtest-bbsb-report`
+
+**Files:**
+- Modify: `/home/lxy/okx-codex-trader/tests/test_cli.py`
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/cli.py`
+
+- [ ] **Step 1: Write the failing CLI test**
+
+```python
+def test_backtest_bbsb_report_generates_single_page_report(capsys, tmp_path):
+    main, client, _, _, bbsb_calls = build_main_with_stubs()
+    output_file = tmp_path / "bbsb.html"
+
+    exit_code = main(
+        [
+            "backtest-bbsb-report",
+            "--symbol",
+            "BTC-USDT-SWAP",
+            "--bar",
+            "3m",
+            "--history-limit",
+            "5000",
+            "--leverage",
+            "2",
+            "--segments",
+            "8",
+            "--window-size",
+            "300",
+            "--output-file",
+            str(output_file),
+        ]
+    )
+
+    assert exit_code == 0
+    assert client.get_candles_called_with == ("BTC-USDT-SWAP", "3m", 5000)
+    assert bbsb_calls[0]["segments"] == 8
+    assert bbsb_calls[0]["window_size"] == 300
+    parser = build_parser()
+    assert tuple(parser._option_string_actions["--symbol"].choices) == ("BTC-USDT-SWAP", "ETH-USDT-SWAP")
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_cli.py -k bbsb -q`
+Expected: FAIL because `backtest-bbsb-report` is not implemented
+
+- [ ] **Step 3: Write minimal CLI implementation**
+
+```python
+bbsb_report = subparsers.add_parser("backtest-bbsb-report")
+bbsb_report.add_argument("--symbol", choices=("BTC-USDT-SWAP", "ETH-USDT-SWAP"), required=True)
+bbsb_report.add_argument("--bar", required=True)
+bbsb_report.add_argument("--history-limit", type=int, required=True)
+bbsb_report.add_argument("--leverage", type=int, choices=(1, 2, 3), required=True)
+bbsb_report.add_argument("--segments", type=int, required=True)
+bbsb_report.add_argument("--window-size", type=int, required=True)
+bbsb_report.add_argument("--output-file", required=True)
+```
+
+```python
+if args.command == "backtest-bbsb-report":
+    candles = client.get_candles(args.symbol, args.bar, args.history_limit)
+    report = bbsb_report_fn(
+        candles=candles,
+        leverage=args.leverage,
+        output_file=Path(args.output_file),
+        symbol=args.symbol,
+        bar=args.bar,
+        segments=args.segments,
+        window_size=args.window_size,
+    )
+    print(_dump_json(report))
+    return 0
+```
+
+- [ ] **Step 4: Run test to verify it passes**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_cli.py -k bbsb -q`
+Expected: PASS
+
+### Task 2: Deterministic Sampler Contract
+
+**Files:**
+- Create: `/home/lxy/okx-codex-trader/tests/test_bbsb_report.py`
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/bbsb_report.py`
+
+- [ ] **Step 1: Write the failing sampler tests**
+
+```python
+def test_sample_segments_rejects_history_pool_too_small():
+    candles = build_linear_candles(1_000)
+
+    with pytest.raises(ValueError, match="history pool is too small"):
+        sample_segments(candles=candles, segments=8, window_size=300, warmup_bars=69, seed=7)
+
+
+def test_sample_segments_returns_non_overlapping_ranges():
+    candles = build_linear_candles(5_000)
+
+    sampled = sample_segments(candles=candles, segments=4, window_size=300, warmup_bars=69, seed=7)
+
+    for left_index, left in enumerate(sampled):
+        for right in sampled[left_index + 1 :]:
+            assert left.report_end <= right.context_start or right.report_end <= left.context_start
+
+
+def test_sample_segments_is_deterministic_for_same_seed():
+    candles = build_linear_candles(5_000)
+
+    first = sample_segments(candles=candles, segments=4, window_size=300, warmup_bars=69, seed=7)
+    second = sample_segments(candles=candles, segments=4, window_size=300, warmup_bars=69, seed=7)
+
+    assert first == second
+
+
+def test_sample_segments_uses_exact_block_candidates_and_drops_partial_tail():
+    candles = build_linear_candles(1_300)
+
+    sampled = sample_segments(candles=candles, segments=3, window_size=300, warmup_bars=69, seed=7)
+
+    assert [segment.context_start for segment in sampled] == sorted(segment.context_start for segment in sampled)
+    assert all(segment.context_start % (300 + 69) == 0 for segment in sampled)
+    assert all(segment.report_end <= 1_300 for segment in sampled)
+
+
+def test_generate_bbsb_sampled_report_rejects_invalid_sampling_result(tmp_path, monkeypatch):
+    monkeypatch.setattr("okx_codex_trader.bbsb_report.sample_segments", lambda **_: [])
+
+    with pytest.raises(ValueError, match="invalid sampling result"):
+        generate_bbsb_sampled_report(
+            candles=build_linear_candles(5_000),
+            leverage=2,
+            output_file=tmp_path / "bbsb.html",
+            symbol="BTC-USDT-SWAP",
+            bar="3m",
+            segments=2,
+            window_size=300,
+        )
+```
+
+- [ ] **Step 2: Run tests to verify they fail**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_bbsb_report.py -k "sample_segments or invalid_sampling" -q`
+Expected: FAIL because sampler functions do not exist
+
+- [ ] **Step 3: Write minimal deterministic sampler**
+
+```python
+WARMUP_BARS = 69
+SAMPLER_SEED = 7
+```
+
+```python
+def sample_segments(...):
+    block_size = window_size + warmup_bars
+    if len(candles) < segments * block_size:
+        raise ValueError("history pool is too small")
+    context_starts = list(range(0, len(candles) - block_size + 1, block_size))
+    ...
+```
+
+- [ ] **Step 4: Run tests to verify they pass**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_bbsb_report.py -k "sample_segments or invalid_sampling" -q`
+Expected: PASS
+
+### Task 3: BBSB Strategy Rules
+
+**Files:**
+- Modify: `/home/lxy/okx-codex-trader/tests/test_bbsb_report.py`
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/bbsb_report.py`
+
+- [ ] **Step 1: Write the failing strategy tests**
+
+```python
+def test_run_bbsb_segment_produces_long_breakout_trade():
+    result = run_bbsb_segment(candles=build_long_breakout_fixture(), leverage=2, warmup_bars=69)
+
+    assert result.trade_count == 1
+    assert result.trades[0]["side"] == "Long"
+
+
+def test_run_bbsb_segment_produces_short_breakout_trade():
+    result = run_bbsb_segment(candles=build_short_breakout_fixture(), leverage=2, warmup_bars=69)
+
+    assert result.trade_count == 1
+    assert result.trades[0]["side"] == "Short"
+
+
+def test_run_bbsb_segment_stop_loss_takes_precedence_over_take_profit():
+    result = run_bbsb_segment(candles=build_ambiguous_exit_fixture(), leverage=2, warmup_bars=69)
+
+    assert result.trades[0]["exit_price"] == pytest.approx(expected_stop_price)
+
+
+def test_run_bbsb_segment_does_not_generate_entry_from_final_reported_candle():
+    result = run_bbsb_segment(candles=build_final_bar_breakout_fixture(), leverage=2, warmup_bars=69)
+
+    assert result.trade_count == 0
+
+
+def test_run_bbsb_segment_marks_open_position_to_market_but_keeps_journal_realized_only():
+    result = run_bbsb_segment(candles=build_open_tail_fixture(), leverage=2, warmup_bars=69)
+
+    assert result.trade_count == 0
+    assert result.trades == []
+    assert result.total_return != 0
+    assert result.open_position is not None
+
+
+def test_run_bbsb_segment_uses_population_std_and_previous_50_completed_bandwidths_only():
+    result = run_bbsb_segment(candles=build_indicator_contract_fixture(), leverage=2, warmup_bars=69)
+
+    assert result.trade_count == 1
+    assert result.trades[0]["entry_time"] == "expected timestamp from the first bar that only passes with ddof=0 and previous-50 median"
+
+
+def test_run_bbsb_segment_does_not_allow_tp_or_sl_on_entry_candle():
+    result = run_bbsb_segment(candles=build_entry_bar_tp_sl_fixture(), leverage=2, warmup_bars=69)
+
+    assert result.trade_count == 0
+    assert result.open_position is not None
+
+
+def test_run_bbsb_segment_exit_exhausts_bar_without_same_bar_reentry():
+    result = run_bbsb_segment(candles=build_same_bar_reentry_fixture(), leverage=2, warmup_bars=69)
+
+    assert result.trade_count == 1
+    assert len(result.entries) == 1
+```
+
+- [ ] **Step 2: Run tests to verify they fail**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_bbsb_report.py -k run_bbsb_segment -q`
+Expected: FAIL because BBSB runner does not exist
+
+- [ ] **Step 3: Write minimal BBSB segment runner**
+
+```python
+def run_bbsb_segment(...):
+    ...
+    if position is not None and index > int(position["entry_index"]):
+        if stop_hit:
+            ...
+            continue
+        if take_profit_hit:
+            ...
+            continue
+    ...
+```
+
+- [ ] **Step 4: Run tests to verify they pass**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_bbsb_report.py -k run_bbsb_segment -q`
+Expected: PASS
+
+### Task 4: Generator And Single-Page HTML Report
+
+**Files:**
+- Modify: `/home/lxy/okx-codex-trader/tests/test_bbsb_report.py`
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/bbsb_report.py`
+
+- [ ] **Step 1: Write the failing generator and HTML tests**
+
+```python
+def test_generate_bbsb_sampled_report_rejects_insufficient_history_pool(tmp_path):
+    candles = build_linear_candles(1_000)
+
+    with pytest.raises(ValueError, match="history pool is too small"):
+        generate_bbsb_sampled_report(
+            candles=candles,
+            leverage=2,
+            output_file=tmp_path / "bbsb.html",
+            symbol="BTC-USDT-SWAP",
+            bar="3m",
+            segments=8,
+            window_size=300,
+        )
+
+
+def test_render_bbsb_sampled_report_contains_summary_and_segment_switcher():
+    html = render_bbsb_sampled_report(...)
+
+    assert "BBSB sampled report" in html
+    assert "Average Return Across Segments" in html
+    assert "Median Return Across Segments" in html
+    assert "Best Segment Return" in html
+    assert "Worst Segment Return" in html
+    assert "segment-selector" in html
+
+
+def test_render_bbsb_sampled_report_contains_trade_journal_and_plot_payload():
+    html = render_bbsb_sampled_report(...)
+
+    assert "Trade Journal" in html
+    assert "Sampled Range Start Time" in html
+    assert "plot0" in html
+
+
+def test_generate_bbsb_sampled_report_hides_warmup_labels_and_reports_ranges(tmp_path):
+    report = generate_bbsb_sampled_report(...)
+
+    assert report["segment_count"] == 2
+    html = Path(report["report_file"]).read_text()
+    assert "expected reported start time" in html
+    assert "expected reported end time" in html
+    assert "warmup timestamp" not in html
+
+
+def test_render_bbsb_sampled_report_embeds_exact_metric_values():
+    html = render_bbsb_sampled_report(
+        ...,
+        aggregate_summary={
+            "aggregate_trade_count": 12,
+            "average_return": 0.125,
+            "median_return": 0.05,
+            "best_segment_return": 0.3,
+            "worst_segment_return": -0.2,
+        },
+        segment_results=[
+            {
+                "index": 0,
+                "start_time": "2026-04-01 00:00",
+                "end_time": "2026-04-01 15:00",
+                "trade_count": 3,
+                "total_return": 0.1,
+                "win_rate": 0.6667,
+                "max_drawdown": 0.05,
+                "trades": [...],
+                "plot_div": "<div>plot0</div>",
+            }
+        ],
+        ...,
+    )
+
+    assert "0.125" in html
+    assert "0.05" in html
+    assert "0.3" in html
+    assert "-0.2" in html
+    assert "0.6667" in html
+```
+
+- [ ] **Step 2: Run tests to verify they fail**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_bbsb_report.py -k "generate_bbsb or render_bbsb" -q`
+Expected: FAIL because generator and renderer do not exist
+
+- [ ] **Step 3: Write minimal renderer and generator**
+
+```python
+def render_bbsb_sampled_report(...):
+    return f"""<!DOCTYPE html>..."""
+```
+
+```python
+def generate_bbsb_sampled_report(...):
+    sampled = sample_segments(...)
+    ...
+    return {
+        "report_file": str(output_file),
+        "segment_count": segments,
+        "window_size": window_size,
+        "aggregate_trade_count": aggregate_trade_count,
+        "average_return": round(average_return, 6),
+    }
+```
+
+- [ ] **Step 4: Run tests to verify they pass**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_bbsb_report.py -q`
+Expected: PASS
+
+### Task 5: Docs And End-To-End Verification
+
+**Files:**
+- Modify: `/home/lxy/okx-codex-trader/README.md`
+
+- [ ] **Step 1: Write the failing README expectation test only if one already exists**
+
+No new README-only test is needed because this repo does not enforce README snapshots.
+
+- [ ] **Step 2: Update README with the new command**
+
+```bash
+python -m okx_codex_trader.cli backtest-bbsb-report --symbol BTC-USDT-SWAP --bar 3m --history-limit 5000 --leverage 2 --segments 8 --window-size 300 --output-file bbsb-sampled-report.html
+```
+
+- [ ] **Step 3: Run the focused and full verification**
+
+Run:
+
+```bash
+cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_cli.py -k bbsb -q
+cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_bbsb_report.py -q
+cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest -q
+```
+
+Expected:
+
+- new BBSB tests pass
+- full suite stays green
+
+- [ ] **Step 4: Generate one real report**
+
+Run:
+
+```bash
+cd /home/lxy/okx-codex-trader && .venv/bin/python -m okx_codex_trader.cli backtest-bbsb-report --symbol BTC-USDT-SWAP --bar 3m --history-limit 5000 --leverage 2 --segments 8 --window-size 300 --output-file bbsb-sampled-report.html
+```
+
+Expected:
+
+- command exits `0`
+- `bbsb-sampled-report.html` exists
+- JSON summary prints `report_file`, `segment_count`, `window_size`, `aggregate_trade_count`, `average_return`

+ 357 - 0
docs/superpowers/plans/2026-04-17-multi-strategy-sampled-report-architecture.md

@@ -0,0 +1,357 @@
+# Multi-Strategy Sampled Report Architecture Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Extract one shared sampled-report shell, migrate `BBMR` and `BBSB` onto it without behavior change, and add three new sampled-report strategies with explicit CLI parameters.
+
+**Architecture:** Keep each strategy’s segment loop local to its own module. Extract only the duplicated sampled-report shell into one shared module that owns sampling, aggregate metrics, plotting, HTML rendering, and file writing. Each new strategy then adds only its own segment runner, thin wrapper, tests, and explicit CLI flags.
+
+**Tech Stack:** Python 3.11+, `pandas`, `bokeh`, existing OKX public candle client, existing CLI test harness, plain HTML/CSS/JS
+
+---
+
+## File Structure
+
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/sampled_report.py`
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/bbmr_report.py`
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/bbsb_report.py`
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/donchian_report.py`
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/rsi2_report.py`
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/ema_pullback_report.py`
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/cli.py`
+- Create: `/home/lxy/okx-codex-trader/tests/test_sampled_report.py`
+- Modify: `/home/lxy/okx-codex-trader/tests/test_bbmr_report.py`
+- Modify: `/home/lxy/okx-codex-trader/tests/test_bbsb_report.py`
+- Modify: `/home/lxy/okx-codex-trader/tests/test_cli.py`
+- Create: `/home/lxy/okx-codex-trader/tests/test_donchian_report.py`
+- Create: `/home/lxy/okx-codex-trader/tests/test_rsi2_report.py`
+- Create: `/home/lxy/okx-codex-trader/tests/test_ema_pullback_report.py`
+
+### Task 1: Shared Sampled Report Shell
+
+**Files:**
+- Create: `/home/lxy/okx-codex-trader/tests/test_sampled_report.py`
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/sampled_report.py`
+
+- [ ] **Step 1: Write the failing shared-shell tests**
+
+```python
+def test_sample_segments_is_deterministic():
+    ...
+
+
+def test_sample_segments_rejects_invalid_sampling_result():
+    ...
+
+
+def test_generate_sampled_report_aggregates_metrics():
+    ...
+
+
+def test_render_sampled_report_includes_strategy_params():
+    ...
+
+
+def test_build_segment_plot_embeds_entry_exit_markers():
+    ...
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_sampled_report.py -q`
+Expected: FAIL on missing shared-shell behavior, not import/setup failure
+
+- [ ] **Step 3: Write minimal shared shell**
+
+```python
+@dataclass(frozen=True)
+class SampledSegment: ...
+
+
+@dataclass(frozen=True)
+class SegmentResult: ...
+```
+
+```python
+def sample_segments(...): ...
+def trade_equity(...): ...
+def mark_to_market(...): ...
+def build_segment_plot(...): ...
+def render_sampled_report(...): ...
+def generate_sampled_report(...): ...
+```
+
+- [ ] **Step 4: Run test to verify it passes**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_sampled_report.py -q`
+Expected: PASS
+
+### Task 2: Migrate BBMR To Shared Shell
+
+**Files:**
+- Modify: `/home/lxy/okx-codex-trader/tests/test_bbmr_report.py`
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/bbmr_report.py`
+
+- [ ] **Step 1: Add one failing wrapper-contract test**
+
+```python
+def test_generate_bbmr_sampled_report_uses_shared_shell(monkeypatch, tmp_path):
+    ...
+```
+
+- [ ] **Step 2: Run BBMR tests to verify red**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_bbmr_report.py -q`
+Expected: FAIL on the new wrapper contract test while current behavior tests still define the expected contract
+
+- [ ] **Step 3: Replace local BBMR shell with a thin wrapper**
+
+```python
+def generate_bbmr_sampled_report(...):
+    return generate_sampled_report(
+        ...,
+        strategy_label="BBMR",
+        strategy_description="...",
+        strategy_params={...},
+        run_segment=run_bbmr_segment,
+    )
+```
+
+- [ ] **Step 4: Run BBMR tests to verify green**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_bbmr_report.py -q`
+Expected: PASS
+
+### Task 3: Migrate BBSB To Shared Shell
+
+**Files:**
+- Modify: `/home/lxy/okx-codex-trader/tests/test_bbsb_report.py`
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/bbsb_report.py`
+
+- [ ] **Step 1: Add one failing wrapper-contract test**
+
+```python
+def test_generate_bbsb_sampled_report_uses_shared_shell(monkeypatch, tmp_path):
+    ...
+```
+
+- [ ] **Step 2: Run BBSB tests to verify red**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_bbsb_report.py -q`
+Expected: FAIL on the new wrapper contract test while current behavior tests still define the expected contract
+
+- [ ] **Step 3: Replace local BBSB shell with a thin wrapper**
+
+```python
+def generate_bbsb_sampled_report(...):
+    return generate_sampled_report(
+        ...,
+        strategy_label="BBSB",
+        strategy_description="...",
+        strategy_params={...},
+        run_segment=run_bbsb_segment,
+    )
+```
+
+- [ ] **Step 4: Run BBSB tests to verify green**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_bbsb_report.py -q`
+Expected: PASS
+
+### Task 4: Preservation Checks For Migrated Commands
+
+**Files:**
+- Modify: `/home/lxy/okx-codex-trader/tests/test_cli.py`
+
+- [ ] **Step 1: Add explicit unchanged-summary assertions**
+
+```python
+def test_backtest_bbmr_report_summary_shape_is_unchanged(): ...
+def test_backtest_bbsb_report_summary_shape_is_unchanged(): ...
+```
+
+- [ ] **Step 2: Run tests to verify they pass**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_cli.py -k "bbmr or bbsb" -q`
+Expected: PASS
+
+### Task 5: Donchian Strategy And CLI
+
+**Files:**
+- Create: `/home/lxy/okx-codex-trader/tests/test_donchian_report.py`
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/donchian_report.py`
+- Modify: `/home/lxy/okx-codex-trader/tests/test_cli.py`
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/cli.py`
+
+- [ ] **Step 1: Write the failing Donchian strategy and CLI tests**
+
+```python
+def test_run_donchian_segment_produces_long_trade(): ...
+def test_run_donchian_segment_produces_short_trade(): ...
+def test_run_donchian_segment_stop_loss_takes_precedence(): ...
+def test_run_donchian_segment_does_not_generate_entry_from_final_bar(): ...
+def test_run_donchian_segment_marks_open_position_to_market(): ...
+def test_backtest_donchian_report_dispatches_generator(): ...
+```
+
+- [ ] **Step 2: Run tests to verify red**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_donchian_report.py tests/test_cli.py -k donchian -q`
+Expected: FAIL on missing Donchian behavior and missing CLI dispatch, not import/setup failure
+
+- [ ] **Step 3: Write minimal Donchian module and CLI entry**
+
+```python
+@dataclass(frozen=True)
+class DonchianConfig:
+    entry_window: int = 20
+    exit_window: int = 10
+    stop_loss_pct: float = 0.01
+```
+
+```python
+def run_donchian_segment(...): ...
+def generate_donchian_sampled_report(...): ...
+def _add_sampled_report_parser(...): ...
+SAMPLED_REPORT_COMMANDS = {
+    "backtest-donchian-report": {
+        "generator": generate_donchian_sampled_report,
+        "parser_args": (...),
+        "strategy_params": (...),
+    },
+}
+```
+
+- [ ] **Step 4: Run tests to verify green**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_donchian_report.py tests/test_cli.py -k donchian -q`
+Expected: PASS
+
+### Task 6: RSI2 Strategy And CLI
+
+**Files:**
+- Create: `/home/lxy/okx-codex-trader/tests/test_rsi2_report.py`
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/rsi2_report.py`
+- Modify: `/home/lxy/okx-codex-trader/tests/test_cli.py`
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/cli.py`
+
+- [ ] **Step 1: Write the failing RSI2 strategy and CLI tests**
+
+```python
+def test_run_rsi2_segment_produces_long_trade(): ...
+def test_run_rsi2_segment_produces_short_trade(): ...
+def test_run_rsi2_segment_exit_priority_is_correct(): ...
+def test_run_rsi2_segment_does_not_generate_entry_from_final_bar(): ...
+def test_run_rsi2_segment_marks_open_position_to_market(): ...
+def test_backtest_rsi2_report_dispatches_generator(): ...
+```
+
+- [ ] **Step 2: Run tests to verify red**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_rsi2_report.py tests/test_cli.py -k rsi2 -q`
+Expected: FAIL on missing RSI2 behavior and missing CLI dispatch, not import/setup failure
+
+- [ ] **Step 3: Write minimal RSI2 module and CLI entry**
+
+```python
+@dataclass(frozen=True)
+class RSI2Config:
+    trend_sma: int = 50
+    rsi_length: int = 2
+    rsi_long_threshold: float = 10.0
+    rsi_short_threshold: float = 90.0
+    exit_rsi: float = 50.0
+```
+
+```python
+def run_rsi2_segment(...): ...
+def generate_rsi2_sampled_report(...): ...
+```
+
+- [ ] **Step 4: Run tests to verify green**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_rsi2_report.py tests/test_cli.py -k rsi2 -q`
+Expected: PASS
+
+### Task 7: EMA Pullback Strategy And CLI
+
+**Files:**
+- Create: `/home/lxy/okx-codex-trader/tests/test_ema_pullback_report.py`
+- Create: `/home/lxy/okx-codex-trader/okx_codex_trader/ema_pullback_report.py`
+- Modify: `/home/lxy/okx-codex-trader/tests/test_cli.py`
+- Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/cli.py`
+
+- [ ] **Step 1: Write the failing EMA pullback strategy and CLI tests**
+
+```python
+def test_run_ema_pullback_segment_produces_long_trade(): ...
+def test_run_ema_pullback_segment_produces_short_trade(): ...
+def test_run_ema_pullback_segment_stop_priority_is_correct(): ...
+def test_run_ema_pullback_segment_does_not_generate_entry_from_final_bar(): ...
+def test_run_ema_pullback_segment_marks_open_position_to_market(): ...
+def test_backtest_ema_pullback_report_dispatches_generator(): ...
+```
+
+- [ ] **Step 2: Run tests to verify red**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_ema_pullback_report.py tests/test_cli.py -k ema_pullback -q`
+Expected: FAIL on missing EMA pullback behavior and missing CLI dispatch, not import/setup failure
+
+- [ ] **Step 3: Write minimal EMA pullback module and CLI entry**
+
+```python
+@dataclass(frozen=True)
+class EMAPullbackConfig:
+    fast_ema: int = 20
+    slow_ema: int = 50
+    stop_buffer_pct: float = 0.005
+```
+
+```python
+def run_ema_pullback_segment(...): ...
+def generate_ema_pullback_sampled_report(...): ...
+```
+
+- [ ] **Step 4: Run tests to verify green**
+
+Run: `cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_ema_pullback_report.py tests/test_cli.py -k ema_pullback -q`
+Expected: PASS
+
+### Task 8: Full Verification
+
+**Files:**
+- None
+
+- [ ] **Step 1: Run focused and full verification**
+
+Run:
+
+```bash
+cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_sampled_report.py -q
+cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_bbmr_report.py tests/test_bbsb_report.py -q
+cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_donchian_report.py tests/test_rsi2_report.py tests/test_ema_pullback_report.py -q
+cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest tests/test_cli.py -k "bbmr or bbsb or donchian or rsi2 or ema_pullback" -q
+cd /home/lxy/okx-codex-trader && .venv/bin/python -m pytest -q
+```
+
+Expected:
+
+- all new focused suites pass
+- migrated `BBMR/BBSB` suites stay green
+- full suite stays green
+
+- [ ] **Step 2: Generate one real report per new strategy**
+
+Run:
+
+```bash
+cd /home/lxy/okx-codex-trader && .venv/bin/python -m okx_codex_trader.cli backtest-donchian-report --symbol BTC-USDT-SWAP --bar 3m --history-limit 5000 --leverage 2 --segments 8 --window-size 300 --output-file donchian-sampled-report.html
+cd /home/lxy/okx-codex-trader && .venv/bin/python -m okx_codex_trader.cli backtest-rsi2-report --symbol BTC-USDT-SWAP --bar 3m --history-limit 5000 --leverage 2 --segments 8 --window-size 300 --output-file rsi2-sampled-report.html
+cd /home/lxy/okx-codex-trader && .venv/bin/python -m okx_codex_trader.cli backtest-ema-pullback-report --symbol BTC-USDT-SWAP --bar 3m --history-limit 5000 --leverage 2 --segments 8 --window-size 300 --output-file ema-pullback-sampled-report.html
+```
+
+Expected:
+
+- each command exits `0`
+- each report file exists
+- each JSON summary prints `report_file`, `segment_count`, `window_size`, `aggregate_trade_count`, `average_return`

+ 302 - 0
docs/superpowers/specs/2026-04-16-okx-codex-trader-design.md

@@ -0,0 +1,302 @@
+# OKX Codex Trader Design
+
+## Goal
+
+Build a minimal local CLI project that:
+
+- fetches OKX perpetual futures market data
+- backtests a strategy on historical candles
+- asks local Codex CLI to analyze recent market data and return a trading decision
+- sends simulated orders to OKX Demo Trading
+
+The first version is limited to:
+
+- `BTC-USDT-SWAP`
+- `ETH-USDT-SWAP`
+- isolated margin only
+- leverage range `1x` to `3x`
+- OKX demo trading only
+
+## Non-Goals
+
+This version does not include:
+
+- spot trading
+- cross margin
+- live trading
+- multi-exchange support
+- portfolio management across symbols
+- order book level backtesting
+- automatic daemonized trading loops
+- web UI
+
+## Project Shape
+
+The project will live at `/home/lxy/okx-codex-trader`.
+
+The codebase will contain these modules:
+
+- `okx_codex_trader/cli.py`
+  - command entrypoint
+- `okx_codex_trader/config.py`
+  - loads required environment variables
+- `okx_codex_trader/models.py`
+  - dataclasses for candles, signals, orders, positions, and backtest results
+- `okx_codex_trader/okx_client.py`
+  - OKX REST client for market data, account reads, leverage setup, and demo orders
+- `okx_codex_trader/backtest.py`
+  - local candle-based backtest engine
+- `okx_codex_trader/strategy.py`
+  - deterministic backtest strategy and signal validation rules
+- `okx_codex_trader/codex_analyzer.py`
+  - invokes local `codex` command and parses the returned JSON signal
+
+## Commands
+
+The first version exposes exactly these commands:
+
+- `fetch-history`
+  - fetch candles for a symbol and timeframe
+  - required arguments:
+    - `--symbol` with value `BTC-USDT-SWAP` or `ETH-USDT-SWAP`
+    - `--bar` for OKX candle timeframe
+    - `--limit` for candle count
+  - output:
+    - JSON array of normalized candles to stdout
+- `backtest`
+  - run a local candle-based backtest with the built-in dual moving average strategy
+  - required arguments:
+    - `--symbol` with value `BTC-USDT-SWAP` or `ETH-USDT-SWAP`
+    - `--bar` for OKX candle timeframe
+    - `--limit` for candle count
+    - `--leverage` in `1..3`
+  - output:
+    - one JSON object with summary metrics and per-trade results
+- `analyze`
+  - send recent candles to Codex CLI and return a structured decision
+  - required arguments:
+    - `--symbol` with value `BTC-USDT-SWAP` or `ETH-USDT-SWAP`
+    - `--bar` for OKX candle timeframe
+    - `--limit` for candle count
+    - `--output-file` for the validated signal JSON
+  - output:
+    - validated signal JSON written to the target file
+    - the same signal JSON printed to stdout
+- `paper-order`
+  - read a structured signal from `--signal-file` and place a demo order on OKX
+  - required arguments:
+    - `--symbol` with value `BTC-USDT-SWAP` or `ETH-USDT-SWAP`
+    - `--signal-file <path>`
+    - `--margin-usdt <number>`
+  - output:
+    - one JSON object describing the no-op result or submitted OKX order
+- `positions`
+  - show current demo positions
+  - required arguments:
+    - `--symbol` with value `BTC-USDT-SWAP` or `ETH-USDT-SWAP`
+  - output:
+    - JSON array of normalized current positions
+
+## Data Flow
+
+### Historical Backtest
+
+The v1 backtest strategy is fixed:
+
+- indicator family: simple moving averages
+- fast window: `10`
+- slow window: `20`
+- signal source: candle close
+- long entry: fast SMA crosses above slow SMA
+- short entry: fast SMA crosses below slow SMA
+- entry price: next candle open after a crossover signal
+- exit price: next candle open after the opposite crossover signal
+- position sizing: use `100%` of current backtest equity as isolated margin for each trade
+- leverage: CLI input, limited to integer `1..3`
+- fees: `0`
+- slippage: `0`
+- take profit / stop loss in backtest: not used in v1
+- initial equity: `10,000 USDT`
+- if a position remains open at the end of the candle range:
+  - do not create a synthetic closing trade
+  - keep `trade_count` and `trades` limited to realized exits only
+  - compute summary `ending_equity` and `total_return` from final-candle-close mark-to-market equity
+  - compute `max_drawdown` using the same mark-to-market equity path
+
+There are no separate configurable risk parameters in v1. The backtest input is fully defined by:
+
+- symbol
+- timeframe
+- candle range
+- leverage
+
+1. CLI requests historical candles from OKX.
+2. The OKX client returns normalized candle objects.
+3. The backtest engine iterates through candles in time order.
+4. The strategy input is reduced to:
+   - symbol
+   - timeframe
+   - recent candles
+   - leverage
+   - candle range
+5. The engine simulates entry and exit using candle OHLC values.
+6. The result reports:
+   - total return
+   - win rate
+   - max drawdown
+   - trade count
+   - per-trade summary
+
+### Codex Analysis
+
+1. CLI fetches recent candles.
+2. The analyzer builds a strict prompt telling Codex to return JSON only.
+3. Codex returns a structured trading decision.
+4. The analyzer validates that response before any order path can use it.
+
+The allowed signal shape is:
+
+- `action`: `long`, `short`, or `flat`
+- `confidence`: `0` to `1`
+- `leverage`: integer `1` to `3`
+- `entry_price`: number or `null`
+- `take_profit_price`: number or `null`
+- `stop_loss_price`: number or `null`
+- `reason`: short string
+
+If the JSON is invalid, the command fails. No fallback logic is added.
+
+### Demo Order Path
+
+The order translation contract is fixed in v1:
+
+- `paper-order` input
+  - requires `--signal-file <path>`
+  - reads one JSON signal object from that file
+  - requires `--margin-usdt <number>`
+- `action = flat`
+  - `paper-order` does not send any OKX order
+  - the command returns a no-op result
+- `entry_price = null`
+  - submit a market order
+- `entry_price = number`
+  - submit a limit order at that price
+- `take_profit_price`
+  - accepted in validated signal output
+  - not submitted to OKX in v1
+- `stop_loss_price`
+  - accepted in validated signal output
+  - not submitted to OKX in v1
+
+The v1 `paper-order` command places only the primary entry order. It does not create linked TP/SL algo orders.
+
+The v1 sizing rule is fixed:
+
+- `margin_usdt` is provided explicitly by the user
+- order notional is `margin_usdt * leverage`
+- price basis is:
+  - `entry_price` when a limit order is requested
+  - latest market price when a market order is requested
+- the client must fetch OKX instrument metadata for the symbol before sizing
+- the client uses `ctVal`, `lotSz`, and `minSz` from the OKX instrument response
+- for linear swaps, contract count is computed from order notional and price basis using contract value
+- contract count is rounded down to the nearest valid multiple of `lotSz`
+- if the rounded contract count is smaller than `minSz`, the command fails
+
+The OKX mapping is fixed to hedge mode semantics:
+
+- `action = long`
+  - `side = buy`
+  - `posSide = long`
+- `action = short`
+  - `side = sell`
+  - `posSide = short`
+- `tdMode = isolated`
+- position mode must be hedge mode for v1 short and long symmetry
+- the client checks current position mode before order submission
+- if the account is not already in hedge mode, the command fails
+
+1. CLI receives a validated order request.
+2. The OKX client sets isolated leverage for the symbol and side.
+3. The OKX client submits an OKX demo order.
+4. The response is returned as normalized order output.
+
+### Runtime Contract
+
+The project requires these environment variables:
+
+- `OKX_API_KEY`
+- `OKX_API_SECRET`
+- `OKX_API_PASSPHRASE`
+
+The project also requires a local `codex` executable to be available on `PATH`.
+
+There is no required environment variable for Codex in v1.
+
+## Constraints
+
+- All authenticated OKX requests must use demo trading mode.
+- All order requests must target swap instruments only.
+- All leverage values outside `1..3` are rejected immediately.
+- Margin mode is fixed to isolated.
+- Backtest strategy is fixed to the built-in `10/20` SMA crossover.
+- Position mode is required to already be hedge mode.
+- The project accepts user input and OKX API responses as system boundaries. Validation is limited to those boundaries.
+
+## Error Handling
+
+Only boundary failures are handled:
+
+- missing environment variables
+- missing `codex` executable on `PATH`
+- invalid user CLI arguments
+- invalid Codex JSON output
+- OKX HTTP or API errors
+- account position mode not set to hedge mode
+- computed contract size below OKX minimum size
+
+There is no internal fallback branch for malformed intermediate state. Failures stop the command directly.
+
+## Testing
+
+The implementation will follow test-first for the minimal behaviors:
+
+- config loading fails when required keys are missing
+- analyzer fails when `codex` is not on `PATH`
+- Codex analyzer rejects non-JSON or out-of-range leverage
+- backtest computes expected results for the fixed `10/20` SMA crossover on a known candle series
+- OKX request signing and demo headers are attached correctly
+- `paper-order` maps `entry_price = null` to market and `entry_price = number` to limit
+- `paper-order` returns no-op for `action = flat`
+- `paper-order` reads the signal from `--signal-file`
+- `paper-order` sizes the order from explicit `margin_usdt`
+- `paper-order` maps `long` and `short` to the fixed OKX `side` and `posSide` fields
+- `paper-order` fails when account mode is not hedge mode
+- contract sizing rounds down by `lotSz` and fails below `minSz`
+- CLI command parsing dispatches the expected workflow
+
+External OKX calls will not be required in unit tests. HTTP interactions will be mocked at the client boundary.
+
+## Implementation Order
+
+1. Create project skeleton and Python package metadata.
+2. Add tests for config loading and signal validation.
+3. Implement config and models.
+4. Add tests for backtest behavior.
+5. Implement candle-based backtest engine.
+6. Add tests for OKX request construction.
+7. Implement OKX client.
+8. Add tests for Codex analyzer parsing.
+9. Implement Codex analyzer.
+10. Add CLI tests.
+11. Implement CLI commands.
+
+## Success Criteria
+
+The first version is complete when:
+
+- `fetch-history` prints normalized historical candles from OKX
+- `backtest` runs locally on OKX candle data
+- `analyze` returns validated JSON from local Codex CLI
+- `paper-order` sends an OKX demo swap order in isolated mode with leverage `1..3`
+- `positions` returns current demo positions

+ 171 - 0
docs/superpowers/specs/2026-04-16-okx-local-paper-engine-design.md

@@ -0,0 +1,171 @@
+# OKX Local Paper Engine Design
+
+## Goal
+
+Keep OKX as a market data source only, and move simulated trading fully local.
+
+The revised project must:
+
+- fetch public OKX market candles
+- backtest locally on historical candles
+- ask local Codex CLI for a validated signal
+- simulate order execution, positions, and equity locally without calling OKX trading endpoints
+
+## Non-Goals
+
+This revision does not include:
+
+- OKX demo order placement
+- OKX account reads for positions or balances
+- live trading
+- spot support
+- order book simulation
+- partial fills
+- fee modeling
+- slippage modeling
+
+## Runtime Contract
+
+- `fetch-history` uses public OKX market data and must not require API credentials
+- `backtest` uses public OKX market data and must not require API credentials
+- `analyze` uses public OKX market data and local `codex`
+- `paper-order` uses local state only
+- `positions` uses local state only
+
+## Local State
+
+The local paper engine stores one JSON file at:
+
+- `/home/lxy/okx-codex-trader/paper_state.json`
+
+The file contains:
+
+- cash balance in USDT
+- open positions keyed by symbol and side
+- realized pnl
+- last updated timestamp
+
+Initial state rules:
+
+- if the file does not exist, `paper-order` and `positions` initialize it implicitly
+- initial cash balance is `10_000 USDT`
+- initial positions are empty
+- realized pnl starts at `0`
+
+## Commands
+
+### `fetch-history`
+
+Unchanged behavior:
+
+- fetch public OKX candles for `BTC-USDT-SWAP` or `ETH-USDT-SWAP`
+- print normalized candles
+
+### `backtest`
+
+Unchanged behavior:
+
+- fetch public OKX candles
+- run local `10/20` SMA backtest
+
+### `analyze`
+
+Unchanged behavior:
+
+- fetch public OKX candles
+- call local `codex`
+- validate JSON signal
+
+### `paper-order`
+
+Changed behavior:
+
+- read validated signal from `--signal-file`
+- never call OKX trading endpoints
+- update `paper_state.json`
+
+Rules:
+
+- `action = flat`
+  - no order sent
+  - no position change
+  - return noop result
+- `action = long`
+  - open or add to long position for symbol
+- `action = short`
+  - open or add to short position for symbol
+- order price basis:
+  - `entry_price` if provided
+  - otherwise latest OKX public market price
+- margin used:
+  - explicit `--margin-usdt`
+- notional:
+  - `margin_usdt * leverage`
+- position quantity:
+  - `notional / price`
+- required cash:
+  - exactly `margin_usdt`
+- if cash balance is smaller than `margin_usdt`, fail
+
+State updates:
+
+- reduce cash by `margin_usdt`
+- add position quantity to matching symbol/side bucket
+- recompute average entry price for that bucket
+
+This v1 paper engine does not:
+
+- net long and short
+- auto-close opposite positions
+- reserve maintenance margin
+- apply fees or slippage
+- apply TP/SL automatically
+
+### `positions`
+
+Changed behavior:
+
+- read local `paper_state.json`
+- print current local positions only
+
+## Data Model
+
+Add local paper engine models:
+
+- `PaperPosition`
+  - `symbol`
+  - `side`
+  - `quantity`
+  - `avg_entry_price`
+  - `margin_used`
+- `PaperState`
+  - `cash_usdt`
+  - `realized_pnl`
+  - `positions`
+  - `updated_at`
+
+## Error Handling
+
+Allowed failures:
+
+- invalid signal file
+- insufficient local cash
+- invalid local state file shape
+- public OKX market data failure
+- missing `codex` executable for `analyze`
+
+No fallback logic.
+
+## Testing
+
+Add tests for:
+
+- public candle fetch works without credentials
+- `paper-order` initializes missing `paper_state.json`
+- `paper-order` uses explicit `entry_price` when present
+- `paper-order` uses latest public market price when `entry_price = null`
+- `paper-order` reduces cash by exact `margin_usdt`
+- `paper-order` increases matching local position
+- `paper-order` recomputes average entry price when adding to same side
+- `paper-order` rejects when local cash is insufficient
+- `positions` reads local state instead of OKX account positions

+ 216 - 0
docs/superpowers/specs/2026-04-17-bbmr-sampled-report-design.md

@@ -0,0 +1,216 @@
+# BBMR Sampled Report Design
+
+## Goal
+
+Add a new real backtest report path for short-horizon Bollinger Band mean reversion.
+
+The feature must:
+
+- fetch one larger pool of recent public OKX candles
+- randomly sample multiple non-overlapping time windows from that pool
+- run the same Bollinger mean reversion strategy on each sampled window
+- output one HTML report file with a segment switcher instead of many separate report pages
+
+## Non-Goals
+
+This feature does not:
+
+- replace the existing `10/20` SMA backtest path
+- replace the existing `backtest-report` command
+- add fees or slippage
+- add TP/SL trailing logic
+- auto-optimize parameters
+- support spot symbols
+
+## Command
+
+Add one new CLI command:
+
+- `backtest-bbmr-report`
+
+Required arguments:
+
+- `--symbol` with value `BTC-USDT-SWAP` or `ETH-USDT-SWAP`
+- `--bar` for candle timeframe
+- `--history-limit` for the total candle pool
+- `--leverage` in `1..3`
+- `--segments` for the number of sampled windows
+- `--window-size` for bars per sampled window
+- `--output-file` for the final single-page HTML report
+
+Optional arguments are not added in v1. Parameters are fixed except where the user explicitly asked for configurability.
+
+## Strategy Contract
+
+This v1 strategy is fixed:
+
+- strategy family: Bollinger Band mean reversion
+- indicator length: `20`
+- standard deviation multiplier: `2`
+- bandwidth filter lookback: `50`
+- stop loss: `0.5%` relative to entry
+- initial equity per sampled segment: `10_000 USDT`
+- position sizing: use `100%` of current segment equity as isolated margin for each trade
+- leverage: CLI input, limited to integer `1..3`
+- fees: `0`
+- slippage: `0`
+
+### Entry Rules
+
+- if candle close is below lower band, enter `long` at next candle open
+- if candle close is above upper band, enter `short` at next candle open
+- entry signals are only evaluated on reported candles that still have a next reported candle inside the same sampled window
+- the final reported candle of a sampled window cannot generate a new entry
+
+### Filter Rule
+
+- Bollinger bandwidth is defined as `(upper_band - lower_band) / middle_band`
+- only allow entries when current Bollinger bandwidth is less than or equal to the rolling median of the previous `50` completed bandwidth values
+- the current bar is excluded from the median window
+
+### Warm-Up Rule
+
+- each sampled report window must have `69` warm-up candles immediately before its first reported candle
+- warm-up candles are used only to compute:
+  - Bollinger bands with `length = 20`
+  - previous `50` completed bandwidth values for the filter
+- warm-up candles are never shown in the sampled range labels, trade journal, or per-segment metrics
+- sampling must reserve these `69` warm-up candles per segment
+- sampled segments must be non-overlapping after including their warm-up context
+
+### Exit Rules
+
+- mean-reversion exits are close-based:
+  - if a `long` position is open and candle close is greater than or equal to the middle band, exit at next candle open
+  - if a `short` position is open and candle close is less than or equal to the middle band, exit at next candle open
+- mean-reversion exit signals are only evaluated on reported candles that still have a next reported candle inside the same sampled window
+- the final reported candle of a sampled window cannot generate a new mean-reversion exit
+- stop-loss exits are intrabar:
+  - if a `long` position is open and a later candle low is less than or equal to the stop price, exit on that bar at the stop price
+  - if a `short` position is open and a later candle high is greater than or equal to the stop price, exit on that bar at the stop price
+- if both a stop-loss condition and a mean-reversion exit condition would occur on the same bar, stop-loss takes precedence
+- same-bar sequencing is fixed:
+  - first evaluate intrabar stop-loss for an already open position
+  - if stop-loss exits the position on that bar, the bar is exhausted and no new entry or reversal may be generated from that same bar
+  - if no stop-loss exit occurs, evaluate close-based mean-reversion exit for an already open position
+  - if a close-based exit signal is generated on that bar, do not also generate an opposite-side entry from that same bar
+  - entry signals are only allowed when the strategy is flat at the close of that bar and no exit occurred on that bar
+
+### End-Of-Window Rule
+
+- keep current project behavior
+- do not force-close an open trade at the final bar
+- report realized trades only in the trade journal
+- if a position remains open at the end of a sampled window:
+  - per-segment `trade_count` and journal rows remain realized-only
+  - per-segment `total return` and `max drawdown` use final-bar-close mark-to-market equity
+  - aggregate return statistics use those per-segment mark-to-market totals
+
+## Sampling Contract
+
+Sampling must avoid concentrating on one market regime.
+
+Rules:
+
+- fetch `history-limit` candles first
+- sample exactly `segments` non-overlapping windows
+- each sampled window must have exactly `window-size` candles
+- windows are chosen randomly from the fetched pool
+- sampling must fail if `history-limit` is too small to fit the requested non-overlapping windows plus `69` warm-up candles per segment
+
+The random process must be reproducible:
+
+- use a deterministic RNG seed
+- expose the chosen sampled ranges in the output report
+
+## Output Shape
+
+The command outputs one HTML file and supporting plot files.
+
+### Main HTML
+
+The main report page is a single journal-first page with:
+
+- summary header
+- strategy description
+- sampling summary
+- segment switcher
+- current segment metrics
+- current segment trade journal
+- current segment interactive plot
+
+### Segment Switching
+
+The single HTML file must allow switching between sampled windows without opening separate top-level pages.
+
+Implementation rule for v1:
+
+- generate one plot file per sampled window if needed
+- keep one main wrapper HTML
+- the wrapper switches the visible segment content client-side
+
+### Summary Metrics
+
+Top-level page must include:
+
+- symbol
+- timeframe
+- history limit
+- segment count
+- window size
+- strategy parameters
+- aggregate trade count
+- average return across segments
+- median return across segments
+- best segment return
+- worst segment return
+
+### Per-Segment Metrics
+
+Each segment view must show:
+
+- sampled range start time
+- sampled range end time
+- trade count
+- total return
+- win rate
+- max drawdown
+
+## Data Model
+
+Add report-side models for:
+
+- sampled segment definition
+  - start index
+  - end index
+  - start timestamp
+  - end timestamp
+- sampled segment result
+  - summary metrics
+  - trade journal rows
+  - plot filename
+
+These models are local to report generation and do not affect existing paper state files.
+
+## Error Handling
+
+Allowed failures:
+
+- `history-limit` too small for requested `segments * window-size`
+- invalid sampling result
+- public OKX market data failure
+- report generation failure
+
+No fallback logic.
+
+## Testing
+
+Add tests for:
+
+- CLI parses `backtest-bbmr-report`
+- report generator rejects insufficient history pool
+- sampler returns non-overlapping windows
+- sampler is deterministic for the same seed
+- report HTML contains aggregate summary and segment switcher
+- report HTML contains per-segment trade journal and plot reference
+- strategy generates long and short trades on controlled fixtures

+ 257 - 0
docs/superpowers/specs/2026-04-17-bbsb-sampled-report-design.md

@@ -0,0 +1,257 @@
+# BBSB Sampled Report Design
+
+## Goal
+
+Add a second real backtest report path for short-horizon Bollinger Band squeeze breakout.
+
+The feature must:
+
+- fetch one larger pool of recent public OKX candles
+- randomly sample multiple non-overlapping time windows from that pool
+- run the same Bollinger squeeze breakout strategy on each sampled window
+- output one HTML report file with a segment switcher instead of many separate report pages
+
+## Non-Goals
+
+This feature does not:
+
+- replace the existing `10/20` SMA backtest path
+- replace the existing `backtest-report` command
+- replace the existing `backtest-bbmr-report` command
+- add fees or slippage
+- add trailing stop logic
+- add extra regime filters beyond squeeze detection
+- support spot symbols
+
+## Command
+
+Add one new CLI command:
+
+- `backtest-bbsb-report`
+
+Required arguments:
+
+- `--symbol` with value `BTC-USDT-SWAP` or `ETH-USDT-SWAP`
+- `--bar` for candle timeframe
+- `--history-limit` for the total candle pool
+- `--leverage` in `1..3`
+- `--segments` for the number of sampled windows
+- `--window-size` for bars per sampled window
+- `--output-file` for the final single-page HTML report
+
+Optional arguments are not added in v1.
+
+## Strategy Contract
+
+This v1 strategy is fixed:
+
+- strategy family: Bollinger Band squeeze breakout
+- indicator length: `20`
+- standard deviation multiplier: `2`
+- bandwidth lookback: `50`
+- take profit: `1.0%` relative to entry
+- stop loss: `0.5%` relative to entry
+- initial equity per sampled segment: `10_000 USDT`
+- position sizing: use `100%` of current segment equity as isolated margin for each trade
+- leverage: CLI input, limited to integer `1..3`
+- fees: `0`
+- slippage: `0`
+
+Equity math is fixed:
+
+- on entry, `margin_used = current_equity`
+- no contract multiplier is modeled
+- no size rounding is modeled
+- for a `long` trade, `price_return = (exit_price - entry_price) / entry_price`
+- for a `short` trade, `price_return = (entry_price - exit_price) / entry_price`
+- `exit_equity = margin_used + (margin_used * leverage * price_return)`
+- mark-to-market uses the same formula with final close as `exit_price`
+
+### Entry Rules
+
+- if current candle is in squeeze state and candle close is above upper band, enter `long` at next candle open
+- if current candle is in squeeze state and candle close is below lower band, enter `short` at next candle open
+- entry signals are only evaluated on reported candles that still have a next reported candle inside the same sampled window
+- the final reported candle of a sampled window cannot generate a new entry
+
+### Squeeze Rule
+
+- middle band is `SMA(20)` of close prices
+- standard deviation uses the same `20` close prices with population semantics (`ddof = 0`)
+- upper band = `middle_band + 2 * std`
+- lower band = `middle_band - 2 * std`
+- Bollinger bandwidth is defined as `(upper_band - lower_band) / middle_band`
+- current candle is in squeeze state only when current Bollinger bandwidth is less than or equal to the rolling median of the previous `50` completed bandwidth values
+- the current bar is excluded from the median window
+- for an even-size sorted list of `50` bandwidth values, median is the arithmetic mean of the two middle values
+
+### Warm-Up Rule
+
+- each sampled report window must have `69` warm-up candles immediately before its first reported candle
+- warm-up candles are used only to compute:
+  - Bollinger bands with `length = 20`
+  - previous `50` completed bandwidth values for the squeeze rule
+- warm-up candles are never shown in the sampled range labels, trade journal, or per-segment metrics
+- sampling must reserve these `69` warm-up candles per segment
+- sampled segments must be non-overlapping after including their warm-up context
+
+### Exit Rules
+
+- breakout exits are price-based only
+- take-profit exits are intrabar:
+  - if a `long` position is open and a later candle high is greater than or equal to the take-profit price, exit on that bar at the take-profit price
+  - if a `short` position is open and a later candle low is less than or equal to the take-profit price, exit on that bar at the take-profit price
+- stop-loss exits are intrabar:
+  - if a `long` position is open and a later candle low is less than or equal to the stop price, exit on that bar at the stop price
+  - if a `short` position is open and a later candle high is greater than or equal to the stop price, exit on that bar at the stop price
+- `later candle` means only candles with `index > entry_index`
+- take-profit and stop-loss can never fire on the entry candle
+- if both take-profit and stop-loss conditions would occur on the same bar, stop-loss takes precedence
+- same-bar sequencing is fixed:
+  - first evaluate intrabar stop-loss for an already open position
+  - if stop-loss exits the position on that bar, the bar is exhausted and no new entry or reversal may be generated from that same bar
+  - if no stop-loss exit occurs, evaluate intrabar take-profit for an already open position
+  - if take-profit exits the position on that bar, the bar is exhausted and no new entry or reversal may be generated from that same bar
+  - entry signals are only allowed when the strategy is flat at the close of that bar and no exit occurred on that bar
+
+### End-Of-Window Rule
+
+- do not force-close an open trade at the final bar
+- report realized trades only in the trade journal
+- if a position remains open at the end of a sampled window:
+  - per-segment `trade_count` and journal rows remain realized-only
+  - per-segment `total return` and `max drawdown` use final-bar-close mark-to-market equity
+  - aggregate return statistics use those per-segment mark-to-market totals
+
+## Sampling Contract
+
+- fetch `history-limit` candles first
+- sample exactly `segments` non-overlapping windows
+- each sampled window must have exactly `window-size` candles
+- sampling uses deterministic block sampling:
+  - `block_size = window_size + 69`
+  - candidate `context_start` values are `range(0, history_limit - block_size + 1, block_size)`
+  - partial tail blocks are never candidates
+  - shuffle candidate starts with RNG seed `7`
+  - take the first `segments` shuffled starts
+  - sort selected starts ascending before report generation
+- sampling must fail if `history-limit < segments * (window_size + 69)`
+
+The random process must be reproducible:
+
+- use RNG seed `7`
+- expose the chosen sampled ranges in the output report
+
+## Output Shape
+
+The command outputs one HTML file.
+
+### Main HTML
+
+The main report page is a single journal-first page with:
+
+- summary header
+- strategy description
+- sampling summary
+- segment switcher
+- current segment metrics
+- current segment trade journal
+- current segment interactive plot
+
+### Segment Switching
+
+The single HTML file must allow switching between sampled windows without opening separate top-level pages.
+
+Implementation rule for v1:
+
+- embed all segment plots into one wrapper HTML
+- the wrapper switches the visible segment content client-side
+
+### Summary Metrics
+
+Top-level page must include:
+
+- symbol
+- timeframe
+- history limit
+- segment count
+- window size
+- strategy parameters
+- aggregate trade count
+- average return across segments
+- median return across segments
+- best segment return
+- worst segment return
+
+Metric formulas are fixed:
+
+- segment `trade_count` = number of realized journal rows
+- segment `win_rate` = realized winning trades divided by `trade_count`; if `trade_count == 0`, `win_rate = 0`
+- segment `total_return` = `(ending_equity - 10_000) / 10_000`
+- segment `ending_equity` = realized exit equity if flat at end, otherwise final-close mark-to-market equity
+- per-bar equity is computed on every reported candle:
+  - if flat after that candle's exit processing, equity for that bar is current realized equity
+  - if a position remains open after that candle's exit processing, equity for that bar is mark-to-market equity using that candle's close
+- segment `max_drawdown` = maximum drop from prior peak equity on that per-bar equity curve
+- aggregate `trade_count` = sum of all segment `trade_count`
+- aggregate `average_return` = arithmetic mean of segment `total_return`
+- aggregate `median_return` = median of segment `total_return`
+- aggregate `best_segment_return` = max of segment `total_return`
+- aggregate `worst_segment_return` = min of segment `total_return`
+
+### Per-Segment Metrics
+
+Each segment view must show:
+
+- sampled range start time
+- sampled range end time
+- trade count
+- total return
+- win rate
+- max drawdown
+
+## Data Model
+
+Add report-side models for:
+
+- sampled segment definition
+  - context start index
+  - reported start index
+  - reported end index
+  - reported start timestamp
+  - reported end timestamp
+- sampled segment result
+  - summary metrics
+  - trade journal rows
+  - plot payload
+
+These models are local to report generation and do not affect existing paper state files.
+
+All output labels and timestamps refer to the reported window only. The `69` warm-up bars are internal and never exposed as segment start/end labels.
+
+## Error Handling
+
+Allowed failures:
+
+- `history-limit` too small for requested `segments * (window-size + 69)`
+- invalid sampling result
+- public OKX market data failure
+- report generation failure
+
+No fallback logic.
+
+## Testing
+
+Add tests for:
+
+- CLI parses `backtest-bbsb-report`
+- report generator rejects insufficient history pool
+- sampler returns non-overlapping windows
+- sampler is deterministic for the same seed
+- strategy generates a long breakout trade on a controlled fixture
+- strategy generates a short breakout trade on a controlled fixture
+- stop-loss takes precedence over take-profit on an ambiguous bar
+- final reported candle cannot create a new entry
+- open tail is marked to market while the journal stays realized-only
+- report HTML contains aggregate summary and segment switcher
+- report HTML contains per-segment trade journal and plot payload

+ 333 - 0
docs/superpowers/specs/2026-04-17-multi-strategy-sampled-report-architecture-design.md

@@ -0,0 +1,333 @@
+# Multi-Strategy Sampled Report Architecture Design
+
+## Goal
+
+Refactor the current sampled-report path so new sampled backtest strategies can be added without copying the full sampler, plot builder, HTML shell, and CLI plumbing each time.
+
+The design must also make strategy parameters easy to tune from the CLI without turning the repo into a generic framework.
+
+This spec covers the shared architecture seam first. Strategy additions listed later are intended consumers of that seam, not part of the seam itself.
+
+## Non-Goals
+
+This design does not:
+
+- replace the existing simple `backtest` SMA path in `backtest.py`
+- unify all strategy loops into one generic execution engine
+- add optimization, grid search, or auto-tuning
+- add spot support
+- add fees, slippage, or exchange execution realism
+- add ML, order book, or news-based strategies
+
+## Current Problem
+
+The repo now has two sampled-report strategies:
+
+- `BBMR` in `bbmr_report.py`
+- `BBSB` in `bbsb_report.py`
+
+Their strategy loops are meaningfully different, but the surrounding report shell is almost identical:
+
+- sampled window selection
+- shared segment result shape
+- equity plot construction
+- journal-first HTML shell
+- aggregate metric calculation
+- file writing
+- CLI parser and dispatch shape
+
+Adding each new strategy by copying one more `*_report.py` file will keep duplicating those same pieces.
+
+## Design Principle
+
+Do not abstract the strategy loop itself.
+
+The clean seam is outside the strategy loop:
+
+- keep each strategy’s segment runner local to its own module
+- extract only the sampled-report shell that is already duplicated
+
+This preserves correctness because:
+
+- `BBMR` has deferred next-open exits
+- `BBSB` has intrabar TP/SL exits
+- future strategies will likely have different exit timing again
+
+Trying to force those into one generic per-bar lifecycle now would be over-abstraction.
+
+## Target Module Layout
+
+```text
+okx_codex_trader/
+  sampled_report.py
+    SampledSegment
+    SegmentResult
+    sample_segments()
+    trade_equity()
+    mark_to_market()
+    build_segment_plot()
+    render_sampled_report()
+    generate_sampled_report()
+
+  bbmr_report.py
+    BBMRConfig
+    run_bbmr_segment()
+    generate_bbmr_sampled_report()  # thin wrapper
+
+  bbsb_report.py
+    BBSBConfig
+    run_bbsb_segment()
+    generate_bbsb_sampled_report()  # thin wrapper
+
+  <future_strategy>_report.py
+    <Strategy>Config
+    run_<strategy>_segment()
+    generate_<strategy>_sampled_report()  # thin wrapper
+```
+
+## Shared Layer Contract
+
+Create one shared sampled-report module, responsible only for the duplicated shell.
+
+### `sampled_report.py` owns
+
+- `SampledSegment`
+- `SegmentResult`
+- deterministic block sampler
+- trade equity helpers
+- mark-to-market helpers
+- shared plot builder
+- shared single-page HTML renderer
+- shared report generator orchestration
+
+### `sampled_report.py` does not own
+
+- strategy entry rules
+- strategy exit rules
+- strategy-specific indicators
+- strategy-specific config dataclasses
+
+## Shared Generator Contract
+
+The shared generator takes:
+
+- `candles`
+- `leverage`
+- `output_file`
+- `symbol`
+- `bar`
+- `segments`
+- `window_size`
+- `report_title`
+- `strategy_label`
+- `strategy_description`
+- `strategy_params`
+- `run_segment`
+- optional `warmup_bars`
+
+The shared generator must:
+
+1. sample deterministic non-overlapping windows
+2. run `run_segment` once per sampled window
+3. collect per-segment results
+4. build shared plots
+5. compute aggregate metrics
+6. render one single-page HTML report, including `strategy_params`
+7. write the report file
+8. return the same JSON summary shape used by existing sampled-report commands
+
+## Segment Runner Contract
+
+Each strategy module exports one local `run_*_segment()` function.
+
+That function takes:
+
+- `candles`
+- `leverage`
+- `warmup_bars`
+- optional strategy config
+
+It returns one shared `SegmentResult`.
+
+This is the only required boundary between a strategy and the sampled-report shell.
+
+## CLI Design
+
+Do not create a generic `--strategy key=value,key=value` parser.
+
+Keep the CLI explicit and readable.
+
+### Command shape
+
+Use one sampled-report command family per strategy:
+
+- `backtest-bbmr-report`
+- `backtest-bbsb-report`
+- `backtest-donchian-report`
+- `backtest-rsi2-report`
+- `backtest-ema-pullback-report`
+
+Each command keeps the existing common arguments:
+
+- `--symbol`
+- `--bar`
+- `--history-limit`
+- `--leverage`
+- `--segments`
+- `--window-size`
+- `--output-file`
+
+Each command may add only its own strategy-specific arguments.
+
+### Parser plumbing
+
+`cli.py` should stop repeating the same sampled-report parser block.
+
+Extract one tiny helper:
+
+- `_add_sampled_report_parser(...)`
+
+And one handler map:
+
+- command name -> a small entry containing:
+  - generator function
+  - strategy-specific parser args
+  - strategy-specific argument extraction into `strategy_params`
+
+This is enough. No registry system, no plugin architecture.
+
+## Parameterization Rules
+
+Parameters must be explicit CLI flags, not hidden in code.
+
+Rules:
+
+- common sampled-report parameters stay common
+- strategy-specific parameters are flat flags
+- every parameter must have a deterministic default
+- report header must print the effective strategy parameters
+
+### Example
+
+`Donchian` might expose:
+
+- `--entry-window`
+- `--exit-window`
+- `--stop-loss-pct`
+
+`RSI2 trend pullback` might expose:
+
+- `--trend-sma`
+- `--rsi-length`
+- `--rsi-long-threshold`
+- `--rsi-short-threshold`
+- `--exit-rsi`
+
+`EMA pullback reclaim` might expose:
+
+- `--fast-ema`
+- `--slow-ema`
+- `--stop-buffer-pct`
+
+No nested config syntax.
+
+## First Additional Strategies
+
+After the shared seam exists, add three more OHLCV-only strategies because they are materially different from the current sampled-report pair plus the separate SMA backtest path.
+
+### 1. Donchian Channel Breakout
+
+Core rule shape:
+
+- long when close breaks above the highest high of the previous `N` bars
+- short when close breaks below the lowest low of the previous `N` bars
+- enter at next open
+- exit on opposite `M`-bar Donchian break or a fixed percent stop
+
+Why add it:
+
+- pure price-structure breakout
+- no moving-average crossover logic
+- no Bollinger/bandwidth dependency
+
+### 2. RSI(2) Pullback In Trend
+
+Core rule shape:
+
+- long regime when `close > SMA(trend_window)`
+- short regime when `close < SMA(trend_window)`
+- within long regime, long when `RSI(2)` drops below a low threshold
+- within short regime, short when `RSI(2)` rises above a high threshold
+- enter at next open
+- exit when RSI mean-reverts past a fixed center threshold
+
+Why add it:
+
+- short-horizon oscillator exhaustion inside trend
+- different from both SMA crossover and Bollinger mean reversion
+
+### 3. EMA Pullback Reclaim
+
+Core rule shape:
+
+- long bias when `EMA(fast) > EMA(slow)`
+- short bias when `EMA(fast) < EMA(slow)`
+- in-trend, long when price trades through the fast EMA but closes back above it
+- in-trend, short when price trades through the fast EMA but closes back below it
+- enter at next open
+- exit on opposite close through the fast EMA or a stop beyond the signal candle
+
+Why add it:
+
+- continuation-after-pullback structure
+- distinct from breakout and oscillator logic
+
+## Recommended Delivery Order
+
+To minimize risk and keep each addition testable:
+
+1. extract shared sampled-report shell
+2. migrate `BBMR` and `BBSB` onto that shell with no behavior change
+3. optionally add `Donchian` strategy
+4. optionally add `RSI2 trend pullback` strategy
+5. optionally add `EMA pullback reclaim` strategy
+
+## Testing Requirements
+
+### Shared shell tests
+
+Add tests for:
+
+- deterministic block sampling
+- invalid sampling result failure
+- shared aggregate metric calculation
+- shared single-page HTML rendering
+- shared plot embedding
+
+### Migration tests
+
+For `BBMR` and `BBSB`:
+
+- keep current behavior tests
+- keep current CLI tests
+- ensure generated report summary shape does not change
+
+### New strategy tests
+
+Each new strategy must have:
+
+- one long fixture
+- one short fixture
+- one stop/exit precedence fixture
+- one final-bar no-new-entry fixture
+- one open-tail mark-to-market fixture
+
+## Success Criteria
+
+This design is complete when:
+
+- adding a new sampled-report strategy no longer requires copying the whole report shell
+- strategy-specific logic lives only in one strategy module
+- common metrics and HTML layout come from one shared path
+- strategy parameters are tunable from explicit CLI flags
+- the repo still does not contain a generic framework or plugin system

+ 2 - 0
openspec/changes/add-robust-ultrashort-validation/.openspec.yaml

@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-04-27

+ 3 - 0
openspec/changes/add-robust-ultrashort-validation/README.md

@@ -0,0 +1,3 @@
+# add-robust-ultrashort-validation
+
+Add statistically robust ultra-short strategy validation and promote only candidates with positive confidence intervals.

+ 48 - 0
openspec/changes/add-robust-ultrashort-validation/design.md

@@ -0,0 +1,48 @@
+## Context
+
+The current project has several sampled-report strategies and an exploratory script that tested ultra-short candidates. The first pass used a small random sample and produced a VWAP candidate that did not survive longer validation. The robust validation workflow must make that failure visible and provide a direct path for accepting only statistically supported candidates.
+
+The existing strategy segment runners already return the metrics needed for per-window analysis. The change can reuse those runners and add a research-only validation path that computes all required statistics from deterministic, non-overlapping windows.
+
+## Goals / Non-Goals
+
+**Goals:**
+
+- Validate candidate strategies over a long historical range.
+- Use all available non-overlapping windows for the selected window size.
+- Report confidence intervals and distribution metrics needed to judge statistical support.
+- Identify RSI2 parameter sets whose 95% confidence interval lower bound remains positive.
+- Preserve the conclusion that the earlier VWAP candidate is not statistically supported.
+
+**Non-Goals:**
+
+- No live trading or paper trading behavior changes.
+- No new order execution logic.
+- No strategy auto-selection for production trading.
+- No fee, slippage, or funding-rate model in this change.
+- No generic backtest framework rewrite.
+
+## Decisions
+
+1. Use deterministic non-overlapping windows.
+
+   Random sampled windows are useful for quick exploration, but they leave the result dependent on the sample seed. Non-overlapping windows provide a direct count of independent evaluation slices for the chosen window size and make the reported sample count auditable.
+
+2. Keep robust validation in a research script.
+
+   The requirement is to validate and incorporate the strategy conclusion, not to change trading execution. Keeping the workflow outside the trading CLI avoids expanding the system boundary while still making the result repeatable.
+
+3. Rank candidates by 95% confidence interval lower bound before average return.
+
+   Average return alone allowed small-sample winners to rank highly. The lower confidence bound better matches the objective: only promote candidates whose positive result survives statistical uncertainty.
+
+4. Promote RSI2 as a candidate family, not a live strategy.
+
+   The current robust run shows RSI2 candidates with positive confidence interval lower bounds on 3m/5m data. That supports further candidate work, but it does not justify live deployment because transaction costs are not included.
+
+## Risks / Trade-offs
+
+- Transaction costs are excluded → The robust output must label results as gross returns and must not claim live profitability.
+- Window returns can still be regime-dependent → The output includes worst return, p10, p90, median, and positive-window rate so the distribution is visible.
+- Non-overlapping windows reduce sample count versus overlapping windows → The chosen sample count is more conservative and easier to reason about.
+- Candidate ranking can overfit the tested historical period → The change promotes only candidates with positive confidence interval lower bounds and leaves cost-aware validation as a separate required step before trading.

+ 27 - 0
openspec/changes/add-robust-ultrashort-validation/proposal.md

@@ -0,0 +1,27 @@
+## Why
+
+The initial ultra-short strategy search used too few sampled windows to justify a trading conclusion. The project needs a repeatable validation path that rejects small-sample winners and only promotes strategies whose long-range window statistics remain positive.
+
+## What Changes
+
+- Add a robust ultra-short validation capability that evaluates candidate strategies across long historical ranges and all non-overlapping windows.
+- Record statistical outputs required for candidate acceptance: sample count, average return, 95% confidence interval, median return, positive-window rate, tail returns, trade count, win rate, and max drawdown.
+- Promote RSI2 as the current candidate family only when its long-sample confidence interval lower bound remains positive.
+- Reject the earlier ETH 3m VWAP candidate because long-sample validation does not show a positive confidence interval.
+- Keep validation as a research/backtest workflow; do not change live or paper trading behavior in this change.
+
+## Capabilities
+
+### New Capabilities
+
+- `robust-strategy-validation`: Defines the long-sample validation contract for ultra-short candidate strategies and the acceptance criteria for promoting a strategy candidate.
+
+### Modified Capabilities
+
+None.
+
+## Impact
+
+- Adds OpenSpec artifacts for strategy validation requirements.
+- Affects research/backtest scripts and generated CSV outputs.
+- Does not change OKX order placement, paper positions, authenticated APIs, or the existing CLI trading commands.

+ 43 - 0
openspec/changes/add-robust-ultrashort-validation/specs/robust-strategy-validation/spec.md

@@ -0,0 +1,43 @@
+## ADDED Requirements
+
+### Requirement: Long-sample all-window validation
+The system SHALL validate ultra-short candidate strategies over a long historical candle range using every non-overlapping evaluation window available for the configured window size.
+
+#### Scenario: Build deterministic validation windows
+- **WHEN** robust validation is run for a symbol, bar, history limit, window size, and candidate warmup size
+- **THEN** the system MUST evaluate each complete non-overlapping window exactly once
+- **THEN** the system MUST report the resulting sample count
+
+### Requirement: Statistical validation metrics
+The system SHALL report the statistics needed to evaluate whether a candidate has credible positive gross return.
+
+#### Scenario: Report candidate distribution
+- **WHEN** robust validation completes for a candidate
+- **THEN** the output MUST include average return, 95% confidence interval lower bound, 95% confidence interval upper bound, median return, positive-window rate, worst return, p10 return, p90 return, best return, trade count, average trades per window, win rate, and max drawdown
+
+### Requirement: Candidate acceptance criterion
+The system SHALL treat a candidate as statistically supported only when its 95% confidence interval lower bound is greater than zero on the robust validation run.
+
+#### Scenario: Accept supported candidate
+- **WHEN** a candidate's robust validation output has `ci95_low` greater than zero
+- **THEN** the candidate MAY be listed as a supported gross-return candidate
+
+#### Scenario: Reject unsupported candidate
+- **WHEN** a candidate's robust validation output has `ci95_low` less than or equal to zero
+- **THEN** the candidate MUST NOT be listed as a supported gross-return candidate
+
+### Requirement: Gross-return labeling
+The system SHALL identify robust validation outputs as gross-return backtest results that exclude fees, slippage, and funding rates.
+
+#### Scenario: Prevent live-profitability claim
+- **WHEN** robust validation results are summarized
+- **THEN** the summary MUST state that the results do not include fees, slippage, or funding rates
+- **THEN** the summary MUST NOT present the candidate as live-trading ready
+
+### Requirement: Current strategy conclusion
+The system SHALL preserve the validated conclusion that RSI2 has the strongest current gross-return support and the earlier VWAP candidate is not statistically supported.
+
+#### Scenario: Summarize current robust run
+- **WHEN** the robust validation result set includes the latest 50,000-candle BTC/ETH 1m/3m/5m run
+- **THEN** the summary MUST identify the top supported RSI2 candidates by confidence interval lower bound
+- **THEN** the summary MUST identify VWAP as rejected when its confidence interval lower bound is not positive

+ 18 - 0
openspec/changes/add-robust-ultrashort-validation/tasks.md

@@ -0,0 +1,18 @@
+## 1. Robust Validation Workflow
+
+- [x] 1.1 Keep a research-only robust validation command path that evaluates all complete non-overlapping windows for a candidate.
+- [x] 1.2 Ensure robust validation reports sample count, average return, 95% confidence interval, median return, positive-window rate, worst return, p10 return, p90 return, best return, trade count, average trades per window, win rate, and max drawdown.
+- [x] 1.3 Sort robust candidate output by confidence interval lower bound before average return.
+
+## 2. Strategy Candidate Results
+
+- [x] 2.1 Run BTC/ETH 1m/3m/5m robust validation over 50,000 candles with 240-bar windows.
+- [x] 2.2 Save the all-candidate robust result CSV.
+- [x] 2.3 Record that RSI2 is the current supported gross-return candidate family when its confidence interval lower bound is positive.
+- [x] 2.4 Record that VWAP is rejected when its confidence interval lower bound is not positive.
+
+## 3. Verification
+
+- [x] 3.1 Add or update tests for deterministic all-window metric calculation.
+- [x] 3.2 Verify the workflow does not alter paper trading, OKX order placement, or existing trading CLI behavior.
+- [x] 3.3 Run the existing test suite.

+ 54 - 0
openspec/changes/add-robust-ultrashort-validation/validation-results.md

@@ -0,0 +1,54 @@
+# Robust Ultra-Short Validation Results
+
+Command:
+
+```bash
+rtk uv run python scripts/explore_ultrashort.py --robust-all --history-limit 50000 --window-size 240
+```
+
+Scope:
+
+- Symbols: `BTC-USDT-SWAP`, `ETH-USDT-SWAP`
+- Bars: `1m`, `3m`, `5m`
+- History: `50,000` candles per symbol/bar
+- Evaluation windows: `240` report candles per window
+- Windowing: complete non-overlapping windows
+- Result file: `ultrashort-robust-all.csv`
+
+Gross-return limitation:
+
+These results exclude fees, slippage, and funding rates. They are not live-trading-ready profitability claims.
+
+## Supported Current Candidate Family
+
+RSI2 is the strongest current gross-return candidate family because multiple RSI2 parameter sets have positive `ci95_low` in the 50,000-candle robust run.
+
+Top supported row by confidence interval lower bound:
+
+- Symbol/bar: `BTC-USDT-SWAP 3m`
+- Candidate: `rsi2-t50-l18.0-s82.0`
+- Sample count: `208`
+- Average return: `0.004964944867230226`
+- 95% CI: `[0.0021465031908155787, 0.007783386543644873]`
+- Median return: `0.005718299303763979`
+- Positive-window rate: `0.6442307692307693`
+- Worst return: `-0.09055481231134109`
+- P10 return: `-0.019867937019411638`
+- P90 return: `0.030006449668830256`
+- Trade count: `3142`
+- Average trades per window: `15.10576923076923`
+- Win rate: `0.6750833353564286`
+- Max drawdown: `0.10233260066528144`
+
+## Rejected Prior Candidate
+
+VWAP reversion is rejected as a supported gross-return candidate because robust validation did not produce a positive 95% confidence interval lower bound for the tested VWAP parameter set family.
+
+Top VWAP robust row by confidence interval lower bound:
+
+- Symbol/bar: `BTC-USDT-SWAP 3m`
+- Candidate: `vwap-revert-w56-z2.4-sl0.006`
+- Sample count: `207`
+- Average return: `0.000949`
+- 95% CI lower bound: `-0.003921`
+- 95% CI upper bound: `0.005819`