Skip to main content

overfitlab: backtest overfitting statistics

The statistics that tell a real trading signal from a lucky one, packaged as a library and turned on real research.

overfitlab deflates a backtest for the search behind it. Run 1,000 strategies on pure noise and the best shows a median annual Sharpe ratio of 1.43. It looks publishable, and it's nothing. This project implements the tests that catch that, checks them against the papers and simulations, and applies them to public data and to a real research project's trial log.

The ten-minute memo gives the argument. The docs site has the API, the theory and a calculator.

Quickstart

pip install overfitlab
import overfitlab as ol

# A 2.5 annual Sharpe ratio over five years, the best of 100 trials.
sr = 2.5 / 250**0.5
result = ol.deflated_sharpe_ratio(
    sr, 1250, n_trials=100, var_trials=0.5 / 250, skew=-3.0, kurtosis=10.0
)
print(round(result.dsr, 4))  # 0.9004

That Sharpe ratio misses the 0.95 bar once its 100 trials and fat left tail are counted. Getting started covers trial logs, cross-validation and the verdict gate.

The problem

Researchers run many backtests and report the best. The best of many noisy trials has a high Sharpe ratio even when no trial has an edge, and a backtest report doesn't show how many trials stood behind it. The hypothesis here is that a small set of published corrections, applied with an honest trial count, separates luck from signal, and that each has failure modes worth measuring.

Data

Raw and licensed data live in data/, which is git-ignored. Only download scripts, aggregates and small synthetic fixtures are committed.

Dataset Cost What it gives
Synthetic returns (GBM, block bootstrap) Free Ground truth for validating every test.
Stooq bulk daily data and Sharadar Stooq is free, Sharadar is a paid subscription Daily prices for 12 ETFs, 2006 to 2026.
Ken French data library and AQR datasets Free 56 long factor histories for multiple-testing case studies.
Open Source Asset Pricing Free A real "many signals" universe of 212 predictors, release 2026.10.

AQR data: Century of Factor Premia, Time Series Momentum, Betting Against Beta and Quality Minus Junk, from AQR Capital Management, used with AQR's written permission. This repository publishes aggregate statistics and charts only, not AQR's files or series.

Method

Method Question it answers
PSR and MinTRL Is this Sharpe ratio real, given its length and return shape?
DSR and MinBTL Is it real, given how many trials produced it?
PBO via CSCV Does in-sample selection carry over out of sample?
Reality Check, SPA, StepM Does anything in this set beat the benchmark, and which?
Harvey–Liu haircut What is the Sharpe ratio worth after multiple testing?
Purged k-fold and CPCV How do I cross-validate overlapping labels without leaking?

A verdict gate requires the DSR, PBO, SPA, StepM and the haircut to pass together. Trial logs follow a frozen contract, so N and V come from a record and not from memory.

Each method is checked three ways: against the papers' printed examples, against closed-form power, and against independent implementations. Validation has the details.

Results

Experiment Result
Best of 1,000 on noise, 100 seeds Median best Sharpe 1.43. Naive PSR significant in 100 seeds, DSR in 0. Median PBO 0.50.
1,034 factory variants on noise Median best Sharpe 1.13. DSR significant in 0 seeds. Median PBO 0.52.
Leakage on noise, 20-day labels Shuffled k-fold accuracy 0.703, purged 0.495. Gap 0.208.
Factory on 12 ETFs, 1,034 trials Sharpe 0.76 against a bar of 0.83. DSR 0.39, PBO 0.31, verdict FAIL. Holdout Sharpe 0.68.
56 factors from Ken French and AQR 30 survive StepM.
212 OSAP predictors, full history 173 at t > 1.96, 134 at t > 3, 149 after BHY, 96 after StepM.
208 OSAP predictors, after publication 54 at t > 1.96, 24 at t > 3, 9 after BHY, 8 after StepM.

The best of many noise strategies against its DSR

Detection takes a long record. With a true Sharpe ratio of 1 among 100 trials, the Reality Check, SPA and StepM need 20 years for 80% power, and the DSR doesn't reach it within the 20-year grid. See the power curves.

A real trial log

On a real research project's trial log, three strategy families pass when each is judged on its own trials, and none passes at the project's whole count of 613 trials. Trial logs in practice has the table.

What failed

  • DSR power. The DSR rejects less often than its reference: 0.036 against 0.168, 0.149 against 0.207, and 0.003 against 0.056 at the three cells that miss. V is estimated from the same trials as the planted winner, which raises its own bar. A version given the true V matches. This is kept as a finding, and no limit was changed.
  • One paper example. One of the 40 paper examples misses its tolerance by 5e-6, and is kept as a documented exception.
  • Studentised SPA check. Against bsds.m, 19 of 20 cases agree within 0.013, and StepM agrees in all 20. Case 2 has no positive mean, and bsds.m has no floor there, so it reports p = 1.
  • Holm and BHY on the calculator. Deferred. The page gives the exact Bonferroni haircut.

Limitations

  • A trial log records what was run, not what was thought about, so N is a lower bound.
  • The tests here compare with zero. A long-only ETF strategy can beat zero on the equity premium alone, which is why the factory case passes SPA and StepM and still fails the DSR.
  • Fewer predictors survive after publication partly because those samples are shorter, so that drop isn't all decay.
  • PBO measures whether rankings persist, not whether money is made. An edge that died halfway through scores near 0. See when PBO misleads.
  • Power curves assume independent strategies with one planted signal.

How to reproduce

The study runs from a clone of the GitHub repository, because the olab-study commands aren't part of the PyPI package.

uv sync
uv run olab-study all

The full run takes about an hour and regenerates every table and figure from the data in data/. Without that data, the real-data steps are skipped with a message. --quick runs a reduced version in about two minutes. Each run adds rows to results/experiment_log.csv.

Further reading

Metadata

Release files for overfitlab 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for overfitlab 0.1.1
File Size Uploaded Provenance Actions
overfitlab-0.1.1.tar.gz 51.2 kB With provenance Details for overfitlab-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for overfitlab 0.1.1
File Interpreter ABI Platform Actions
overfitlab-0.1.1-py3-none-any.whl Python 3 none any Details for overfitlab-0.1.1-py3-none-any.whl

Total release size: 111.5 kB

Release files / overfitlab-0.1.1.tar.gz

Download URL overfitlab-0.1.1.tar.gz
Size 51.2 kB
Tags Source
SHA-256 checksum
How to use checksums
b1ff89478ae6ff0fe216f4f7271ee4ce38bceb9ceaaf31dd2fa431dff993e735
BLAKE2b-256 checksum
How to use checksums
84ad491625cbbaf1540b7817aac4e33c236c803d0640983bdf63dc4e9de2687c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.

Transparency log

Release files / overfitlab-0.1.1-py3-none-any.whl

Download URL overfitlab-0.1.1-py3-none-any.whl
Size 60.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
260548e37710837a598054c2ce9089badf9657ac60cff315b44930883b1e5a55
BLAKE2b-256 checksum
How to use checksums
47dcf39055adfa7dbce0b215ea7ea5ede1ec529e7758f9683f9dc8946315315a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.

Transparency log

Security for overfitlab 0.1.1

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for overfitlab-0.1.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.

Transparency log

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.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page