An honest reality-check for trading strategies — Deflated Sharpe Ratio + sealed out-of-sample holdout.
Project description
OverfitGuard
An honest reality-check for trading strategies. Before you risk a dollar on a backtest, answer the one question that actually matters: is this a real edge, or did I fool myself?
Search hard enough over historical data and you will always find something that looks brilliant in-sample — by pure luck. OverfitGuard applies the discipline that separates a real edge from a data-mined mirage, and it refuses to bless anything that can't survive it:
- Deflated Sharpe Ratio (Bailey & López de Prado, 2014) — penalises your Sharpe for how many strategies you tried, how long your track record is, and how fat-tailed your returns are.
- Sealed out-of-sample holdout — the "does it still work on data it has never seen" test that catches regime-fitting the deflation math can't.
- White's Reality Check (Sullivan, Timmermann & White) — judges a whole search at once: was the best of your 500 configs real, or just the luckiest?
Two independent defenses on a single strategy, plus a family-wise test for a search. Dependency-light
(just numpy/pandas), framework-agnostic, and it never sees your data source, broker, or secret sauce.
Install
pip install overfitguard # once published to PyPI
# or from source:
pip install -e ".[test]"
Library — 30 seconds
import pandas as pd
from overfitguard import validate, screen
# One strategy — be honest about how many configs you tried:
result = validate(my_returns, n_trials=250, benchmark=spy_returns)
print(result.verdict) # LIKELY_REAL / INCONCLUSIVE / LIKELY_OVERFIT / ...
print(result.report())
# A whole search — was the best candidate real?
print(screen(all_candidate_returns).report()) # candidates = DataFrame, one column each
Command line
overfitguard validate returns.csv --trials 250 --benchmark spy.csv --html report.html
overfitguard screen candidates.csv --bootstrap 2000 --html screen.html
--html writes a self-contained, theme-aware report you can open in any browser or hand to a client.
See two real outputs without installing anything: a caught mirage
(FAILS_OUT_OF_SAMPLE) and a genuine edge (LIKELY_REAL) — proof
the verdict swings both ways. Regenerate them with --trials 500 (mirage) and --trials 300 (genuine
edge) on the matching *_returns.csv.
The verdicts
| Verdict | Meaning |
|---|---|
LIKELY_REAL |
Survives deflation for the trials you tried and holds up out-of-sample. |
SURVIVES_DEFLATION_BUT_DECAYS_OOS |
Significant, but the edge shrinks on unseen data. Caution. |
INCONCLUSIVE |
Out-of-sample positive, but too short / too modest to prove for the trials tried. |
FAILS_OUT_OF_SAMPLE |
The edge vanishes or reverses on data the search never saw — the classic mirage. |
LIKELY_OVERFIT |
A search that wide would produce a Sharpe this good by luck. |
BEST_IS_SIGNIFICANT / NO_STRATEGY_BEATS_LUCK |
(search screen) the best candidate beats — or doesn't — what that many random tries throw up. |
Why it's honest
The number that fools most people is the in-sample Sharpe — and OverfitGuard treats a high one as expected, not as evidence. If you tell it you tried one strategy when you really tried a thousand, the sealed holdout is there to catch the decay anyway.
It will not tell you that you found alpha. It will tell you whether you have earned the right to believe it — which is rarer, and worth far more.
Learn the ideas — the companion course
Fooled by Backtests is a short, free course that teaches these defenses by using this tool on real data — including a research program that rigorously tested a dozen strategy families and found buy-and-hold beat essentially all of them. It ends with hands-on labs: manufacture a mirage and watch it get caught, screen 250 candidates for the one real edge, and — the capstone — audit your own strategy in one command.
Limitations & how to read the verdict
OverfitGuard is deliberately a skeptic. Understand these before you use it (the full treatment, with formulas and calibration evidence, is in docs/METHODS.md):
INCONCLUSIVEis common, and it does not mean "fake." The engine is tuned for a low false-positive rate (≈5% at the 95% bar, confirmed by Monte-Carlo calibration), which costs it power: a genuine edge with a modest Sharpe or a short track record — especially one found after many trials — will often readINCONCLUSIVE. That is the honest answer ("promising, not proven"), not a rejection. More data is the cure.- A
LIKELY_REALatn_trials=1is still a 5%-false-positive test. If you under-report how many configurations you tried, you are weakening the deflation — the sealed holdout is the backstop, but be honest withn_trials. screen()tests each candidate's mean return against zero (cash), not against a benchmark. It answers "does the best of these have positive expected return beyond luck?" — not "does it beat buy-and-hold." Bring your own benchmark by screening excess-over-benchmark returns.- Approximations, stated plainly: the deflation's multiplicity bar uses the strategy's own Sharpe
estimator variance as a proxy for the dispersion of your trial Sharpes; and the default out-of-sample
check is a single 35% holdout, which is itself noisy — for a more robust read, cross-validate across
sub-periods with
kfold_oos_sharpe(returns, k=5)oroverfitguard validate ... --kfold 5. - It is a research / due-diligence tool, not financial advice.
LIKELY_REALmeans "you have earned the right to believe it," not "trade this."
Project layout
overfitguard/
pyproject.toml # pip-installable, exposes the `overfitguard` CLI
index.html # self-contained landing page (GitHub Pages front door)
src/overfitguard/ # core.py · screen.py · report.py · cli.py
tests/ # 27 tests (core, screener, CLI)
examples/ # quickstart.py + two sample HTML reports (a catch and a pass)
course/ # "Fooled by Backtests" — 5 chapters + hands-on labs + capstone
docs/METHODS.md # the maths, calibration evidence, and limitations in full
.github/workflows/ci.yml # tests on 3.9 / 3.11 / 3.12
License
MIT. OverfitGuard is a research/due-diligence tool — it does not give financial advice, and
LIKELY_REAL is not a recommendation to trade.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file overfitguard-0.3.2.tar.gz.
File metadata
- Download URL: overfitguard-0.3.2.tar.gz
- Upload date:
- Size: 94.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
114b20c79db6ba9cd08444559a394077285d4183b59dea9260ddbedce55eb5db
|
|
| MD5 |
2386b20db4d396d7f057fa7aa9016123
|
|
| BLAKE2b-256 |
cfdf743ae516840b05d3639e1d3069fde1a4c5059cff900a3a73e71c63de410b
|
Provenance
The following attestation bundles were made for overfitguard-0.3.2.tar.gz:
Publisher:
publish.yml on anpeterlin/overfitguard
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
overfitguard-0.3.2.tar.gz -
Subject digest:
114b20c79db6ba9cd08444559a394077285d4183b59dea9260ddbedce55eb5db - Sigstore transparency entry: 2165609529
- Sigstore integration time:
-
Permalink:
anpeterlin/overfitguard@fad3fe5c7fc67868d67cb961fae4fb4bc7575b56 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/anpeterlin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fad3fe5c7fc67868d67cb961fae4fb4bc7575b56 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file overfitguard-0.3.2-py3-none-any.whl.
File metadata
- Download URL: overfitguard-0.3.2-py3-none-any.whl
- Upload date:
- Size: 20.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1a1804766d5a53ab671434fae7a0a54c1c325f0b7696aa5bbba726f319e71e14
|
|
| MD5 |
6ed9ec57ac6d0fbbc0c0e5d052d59f55
|
|
| BLAKE2b-256 |
990aac986e3c14278faf9b802d9aadfa7e184ab0ce1e52aa11e32ddc8cb85072
|
Provenance
The following attestation bundles were made for overfitguard-0.3.2-py3-none-any.whl:
Publisher:
publish.yml on anpeterlin/overfitguard
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
overfitguard-0.3.2-py3-none-any.whl -
Subject digest:
1a1804766d5a53ab671434fae7a0a54c1c325f0b7696aa5bbba726f319e71e14 - Sigstore transparency entry: 2165609541
- Sigstore integration time:
-
Permalink:
anpeterlin/overfitguard@fad3fe5c7fc67868d67cb961fae4fb4bc7575b56 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/anpeterlin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fad3fe5c7fc67868d67cb961fae4fb4bc7575b56 -
Trigger Event:
workflow_dispatch
-
Statement type: