2026-04-17-bbsb-sampled-report-design.md 9.6 KB

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