BacktestBuddy
Backtest sports-betting strategies in Python: a sequential bankroll simulation over your historical odds, with staking strategies, risk metrics, closing line value, and interactive Plotly charts.
Works with any data source: you pass a pandas DataFrame and tell BacktestBuddy which columns hold the odds, the outcome, the date, and your model output.
Python >= 3.9 · MIT license
Features
- Two ways to backtest:
PredictionBacktestfor predictions you already have,ModelBacktestto train an sklearn-style model walk-forward. - Strategies: fixed stake, Kelly criterion, value betting, unit staking, edge-proportional staking, odds-band filter, and risk limits (stake caps, daily limits, loss pauses, drawdown stop).
- Strategy toolkit: combine any outcome selector with any stake sizer, e.g. value-bet selection with Kelly sizing, or write your own block in a few lines.
- Simultaneous matches: size same-day bets from the day's opening bankroll and cap their total Kelly stake.
- Metrics: ROI, Yield, CAGR, Sharpe, Sortino, Calmar, drawdown, calibration (Brier, log loss, ECE), and a market baseline.
- Closing line value: compare the odds you took with any later or sharper line.
- Margin removal: multiplicative, additive, power, and Shin methods.
- Odds stress test: re-run with 1%, 2%, 3% worse odds to see how robust the edge is.
- Hyperparameter tuning:
evaluate()returns only the metrics an optimizer like Optuna needs, several times faster than a full run, plus a holdout split helper. - Charts: bankroll and drawdown, reliability diagram, odds distribution.
Installation
pip install backtestbuddy
Quick example
import numpy as np
import pandas as pd
import backtestbuddy as btb
# One row per match: date, decimal odds per outcome, actual outcome (0-based), model output
rng = np.random.default_rng(0)
n = 500
p_home = rng.uniform(0.3, 0.7, n)
data = pd.DataFrame({
"date": pd.date_range("2023-01-01", periods=n, freq="D"),
"odds_home": np.round(0.95 / p_home, 2),
"odds_away": np.round(0.95 / (1 - p_home), 2),
"winner": (rng.uniform(size=n) > p_home).astype(int), # 0 = home, 1 = away
})
model_p = np.clip(p_home + rng.normal(0, 0.05, n), 0.01, 0.99)
data["prob_home"] = model_p
data["prob_away"] = 1 - model_p
data["prediction"] = (model_p < 0.5).astype(int)
backtest = btb.PredictionBacktest(
data=data,
odds_columns=["odds_home", "odds_away"],
outcome_column="winner",
date_column="date",
prediction_column="prediction",
model_prob_columns=["prob_home", "prob_away"],
strategy=btb.KellyCriterion(downscaling=0.5),
initial_bankroll=1000,
)
backtest.run()
metrics = backtest.calculate_metrics()
print(metrics["ROI [%]"], metrics["Yield [%]"], metrics["Max Drawdown [%]"])
# This toy model is only noise around the true odds, so the 5% margin wins: expect a loss.
backtest.plot() # interactive Plotly chart
Documentation
- Getting Started: data format and a first backtest, step by step
- Core Concepts: how the simulation works
- Backtest Module: classes, parameters, results columns, stress test
- Strategies Module
- Metrics Module: every metric with its formula
- Odds Module: margin removal
- Hyperparameter Tuning: fast
evaluate(), holdout split, Optuna recipe - Plots Module
- Examples
- Changelog
Browse the docs locally with pip install backtestbuddy[docs] and mkdocs serve.
Development
The project uses uv. From a clone of the repository:
uv run --extra test pytest # full test suite, including every docs example
uv run --extra docs mkdocs serve # docs at http://127.0.0.1:8000
GitHub Actions runs the tests on Python 3.9 to 3.13 and builds the docs for every pull request.
Metadata
Release files for backtestbuddy 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| backtestbuddy-0.3.0.tar.gz | 121.4 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| backtestbuddy-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 176.8 kB
Release files / backtestbuddy-0.3.0.tar.gz
| Download URL | backtestbuddy-0.3.0.tar.gz |
|---|---|
| Size | 121.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5c125c966531f33ae0f6aa4c4d27f996ac721da184e4121b3f958309fb52345e
|
|
BLAKE2b-256 checksum How to use checksums |
6bd296e35635998711a85d42e469efedc55cac443d8f2086886d59a4926784d8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.2
|
Release files / backtestbuddy-0.3.0-py3-none-any.whl
| Download URL | backtestbuddy-0.3.0-py3-none-any.whl |
|---|---|
| Size | 55.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
401ea7519eb0097367216dfe2cc8de848ed1db11b439bce60601d45c6abadd5d
|
|
BLAKE2b-256 checksum How to use checksums |
6fc1c59c65172939a9a5cde4ef140c481639c1de328fbd074afb6d679293f2a9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.2
|
Security for backtestbuddy 0.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.