Skip to main content

deflated-sharpe

Is your backtest real?

PyPI Python 3.10+ License Tests

You tested 1,000 parameter combinations and found a Sharpe 2.0 strategy. Is it real — or did you just test enough times to get lucky?

deflated-sharpe implements the Deflated Sharpe Ratio (Bailey & Lopez de Prado, 2014) and related statistical gates. Pure Python, zero required dependencies, designed to be the last check before you deploy a strategy.

Install

pip install deflated-sharpe

Quick Start

Deflated Sharpe Ratio

from deflated_sharpe import deflated_sharpe_ratio

dsr, p_value = deflated_sharpe_ratio(observed_sr=2.0, num_trials=1000, num_obs=252)
print(f"DSR={dsr:.2f}, p={p_value:.4f}")

A positive DSR with p < 0.05 means your strategy likely has real alpha after accounting for the number of trials you ran. A negative DSR means the expected maximum Sharpe from random chance alone exceeds your observed Sharpe.

Minimum Backtest Length

from deflated_sharpe import min_backtest_length

min_obs = min_backtest_length(target_sr=1.5, num_trials=500)
print(f"Need at least {min_obs} observations")

Before running a large search, compute how many observations you need. If your backtest window is shorter than min_obs, no strategy can pass the DSR gate regardless of performance.

Regime Decay Detection

from deflated_sharpe import RegimeDecayDetector, StrategyBaseline, TradeResult

baseline = StrategyBaseline(win_rate=0.55, trade_count=200, max_drawdown_pct=12.0)
detector = RegimeDecayDetector(baseline=baseline)
detector.fit_market_baseline(training_features)  # list of (atr_ratio, trend_pct, atr_percentile)

for trade in live_trades:
    detector.add_trade(trade)
assessment = detector.assess()
print(f"Decay confirmed: {assessment.decay_confirmed} ({assessment.signals_fired}/3 signals)")

Triple-confirmation system: Bayesian win rate decay, drawdown exceedance (1.5x backtest MDD), and Mahalanobis out-of-distribution detection. Two of three signals must fire simultaneously to confirm decay.

How DSR Saved Us

In March 2026, we ran a grid search over 19,200 parameter combinations on BTCUSDT 1H walk-forward data (23 periods, 3-month IS + 3-month OOS). Multiple strategies showed Sharpe ratios above 1.5 in-sample. The DSR gate rejected every single one — correctly preventing deployment of overfitted strategies.

The math was simple: with M=19,200 trials and only 30-50 trades per window, the expected maximum Sharpe from pure chance exceeded every observed value. We then tested LLM-guided search (M~30 per period, 640x fewer trials) and found the same result: trade count was the binding constraint, not search method. DSR saved us from deploying strategies that looked profitable but had zero statistical significance.

Full analysis: Phase 15 Case Study

Before and After DSR

Tools

deflated_sharpe_ratio(observed_sr, num_trials, num_obs, skewness, kurtosis)

Computes the Deflated Sharpe Ratio per Bailey & Lopez de Prado (2014). Adjusts the observed Sharpe for selection bias from multiple testing, accounting for return non-normality via skewness and kurtosis corrections.

The key insight: when you test M strategies, the maximum Sharpe you expect from pure luck grows as O(sqrt(ln(M))). DSR subtracts this expected maximum from your observed Sharpe and normalizes by the standard error.

Parameter Type Default Description
observed_sr float required Observed Sharpe ratio
num_trials int required Number of strategies tested (M)
num_obs int required Number of observations (T)
skewness float 0.0 Return skewness (0 = normal)
kurtosis float 3.0 Return kurtosis (3 = normal)

Returns (dsr, p_value). DSR > 0 with p < 0.05 indicates statistical significance.

Reference: Bailey, D.H. & Lopez de Prado, M. (2014), Eq. 2-4.

min_backtest_length(target_sr, num_trials, alpha, skewness, kurtosis)

Binary search for the minimum number of observations T such that DSR > 0 at the given significance level. Use this to determine if your backtest window is long enough before running a parameter search.

from deflated_sharpe import min_backtest_length

# "I want Sharpe 1.5 after testing 200 strategies. How much data do I need?"
min_obs = min_backtest_length(target_sr=1.5, num_trials=200, alpha=0.05)

benjamini_hochberg(p_values, alpha)

Benjamini-Hochberg FDR correction for evaluating multiple strategies simultaneously. When you have N candidate strategies each with a DSR p-value, BH controls the false discovery rate at level alpha.

from deflated_sharpe import deflated_sharpe_ratio, benjamini_hochberg

p_values = [
    deflated_sharpe_ratio(sr, num_trials=50, num_obs=500)[1]
    for sr in [1.2, 0.8, 1.5, 0.3]
]
results = benjamini_hochberg(p_values, alpha=0.05)
for idx, p, sig in results:
    print(f"Strategy {idx}: p={p:.4f}, significant={sig}")

RegimeDecayDetector

Live monitoring for strategy regime decay. Three independent signals with 2/3 majority vote:

  • S1 Win Rate Decay: Bayesian Beta updating with backtest prior. Fires when P(win_rate < breakeven) exceeds threshold.
  • S2 Drawdown Exceedance: Fires when current drawdown exceeds dd_multiplier (default 1.5x) times backtest maximum drawdown.
  • S3 Market OOD: Mahalanobis distance on market features (ATR ratio, trend, ATR percentile). Fires when recent trades are beyond the training distribution's 95th percentile.

Anti-false-positive measures: minimum 20 trades before assessment, cooling period of 5 trades after trigger, Bonferroni correction for multiple strategies.

Config Parameter Default Description
min_trades 20 Minimum trades before assessment
cooling_period 5 Trades to skip after trigger
win_rate_decay_prob_threshold 0.80 P(wr < breakeven) threshold
dd_multiplier 1.5 Drawdown exceedance multiplier
ood_percentile 95.0 Mahalanobis percentile threshold
num_strategies 1 N for Bonferroni correction

Paper Verification

The DSR implementation is verified against the original paper's mathematics: Gumbel approximation for E[Z_max], standard error with non-normality correction, and the full DSR test statistic. See tests/test_paper_verification.py for numerical checks against known values from Bailey & Lopez de Prado (2014).

Zero Dependencies

The core library uses only Python standard library (math, dataclasses). The _math.py module implements norm_cdf, matrix inversion, and Mahalanobis distance from scratch to avoid pulling in NumPy/SciPy for basic usage.

For the regime detector's Bayesian win rate signal (S1), scipy.stats.beta is used if available; otherwise a point-estimate fallback is used. Install the optional dependency:

pip install "deflated-sharpe[scipy]"

References

Bailey, D. H., & Lopez de Prado, M. (2014). "The Deflated Sharpe Ratio: Correcting for Selection Bias, Backtest Overfitting, and Non-Normality." Journal of Portfolio Management, 40(5), 94-107. DOI: 10.3905/jpm.2014.40.5.094

License

Apache-2.0

Metadata

Release files for deflated-sharpe 0.1.0

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

Source distribution (sdist)

Source distribution for deflated-sharpe 0.1.0
File Size Uploaded
deflated_sharpe-0.1.0.tar.gz 71.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for deflated-sharpe 0.1.0
File Interpreter ABI Platform
deflated_sharpe-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 86.4 kB

Release files / deflated_sharpe-0.1.0.tar.gz

Download URL deflated_sharpe-0.1.0.tar.gz
Size 71.4 kB
Tags Source
SHA-256 checksum
How to use checksums
4da61e76e5acd4222bf10e18c682423d983a6c204cb2ff6fdecce64d7d1c12d9
BLAKE2b-256 checksum
How to use checksums
900610eb6cd87bf5ec61ee691fa4f8e9b10ced4a2545f45721af3f1b68d2f772
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Mar 21, 2026.

Transparency log

Release files / deflated_sharpe-0.1.0-py3-none-any.whl

Download URL deflated_sharpe-0.1.0-py3-none-any.whl
Size 14.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f6c323ea3e84c2861dd35deb5f5126ff1476b7b181fee946ebf5c99fa32a251e
BLAKE2b-256 checksum
How to use checksums
bb8009bcbae3a54fe98ff0fbf15995681f834bc8fe954f3dbe52192d82a44ea0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Mar 21, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

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