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:
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