2026-04-17-multi-strategy-sampled-report-architecture-design.md 8.3 KB

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

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