Backtesting
The Whale.BackTest module provides a replay engine for evaluating betting
strategies against historical outcome data. It applies the full Whale stack
(sizing, bankroll, risk, policy, guards) on each simulated decision point
and collects performance metrics.
Defining a strategy
Section titled “Defining a strategy”A strategy is a function from market state to a list of candidate bets:
import Whale.BackTest exposing (Strategy, Candidate)import Whale.Types exposing (Probability, Odds)
let my_strategy : Strategy = fun state -> state.opportunities |> List.filter (fun o -> o.edge > 0.02) |> List.map (fun o -> Candidate.new o.prob o.odds)The backtest engine calls this function at each time step, feeds candidates through the sizing and guard pipeline, and records outcomes.
Running a backtest
Section titled “Running a backtest”import Whale.BackTest exposing (run, Config)
let config = Config.new { initial_bankroll = Money.usd 10_000.00 , sizing = Sizing.fractional_kelly 0.5 , policies = my_policies , guards = my_guards }
let results = BackTest.run config my_strategy historical_datahistorical_data is a time-ordered sequence of market snapshots, each
containing the opportunities visible at that point and the realized
outcomes.
Results
Section titled “Results”BackTest.run returns a BackTest.Results record:
| Field | Type | Meaning |
|---|---|---|
final_bankroll | Money | Ending balance |
peak_bankroll | Money | High-water mark reached |
max_drawdown | Float | Largest peak-to-trough decline as a fraction |
total_bets | Int | Number of bets placed |
win_rate | Float | Fraction of bets that returned a profit |
total_return | Float | (final - initial) / initial |
sharpe | Float | Annualized Sharpe ratio of per-period returns |
log_growth | Float | Realized geometric growth rate |
Comparing strategies
Section titled “Comparing strategies”Run multiple configurations over the same historical data and compare on the metrics that matter to your use case:
let results_full = BackTest.run config_full strategy datalet results_half = BackTest.run config_half strategy data
let better = if results_half.sharpe > results_full.sharpe then "half Kelly" else "full Kelly"The backtesting surface is deterministic: identical inputs produce identical
outputs. For Monte Carlo exploration (shuffled outcome order, synthetic
data), generate the input sequences upstream and feed them through
BackTest.run as separate historical datasets.