SPB Git

Repository Public

test_dsr.py 4.4 KB 81 lines python

1"""Deflated Sharpe Ratio against the worked example of Bailey & López de Prado (2014)."""2from __future__ import annotations34import math56import numpy as np7import pytest8from scipy import stats910from spq.dsr import (EULER_GAMMA, deflated_sharpe, deflated_sharpe_from_returns, expected_max_sharpe,11                     probabilistic_sharpe)121314def test_worked_example_of_the_paper():15    """Section 'A numerical example' of the paper: the best of N = 100 independent trials has an annualised16    Sharpe ratio of 2.5 on T = 1250 daily returns (250 per year) with skewness -3 and kurtosis 10; the variance of17    the trials' annualised Sharpe ratios is 1/2. The paper obtains SR0 = 0.1132 (daily) and DSR = 0.9004."""18    sr = 2.5 / math.sqrt(250)19    var_trials = 0.5 / 25020    sr0 = expected_max_sharpe(100, var_trials)21    assert sr0 == pytest.approx(0.1132, abs=5e-5)22    assert sr0 * math.sqrt(250) == pytest.approx(1.7895, abs=1e-3)       # the same threshold, annualised23    dsr = deflated_sharpe(sr, 1250, -3.0, 10.0, 100, var_trials)24    assert dsr == pytest.approx(0.9004, abs=5e-4)25    # the paper's point: the same track record looks certain if the 100 trials are ignored26    assert probabilistic_sharpe(sr, 0.0, 1250, -3.0, 10.0) > 0.9999272829def test_expected_maximum_by_hand():30    # N = 10, unit variance: (1 - g) * z(0.9) + g * z(1 - 1/(10 e))31    z1, z2 = stats.norm.ppf(0.9), stats.norm.ppf(1 - 1 / (10 * math.e))32    assert z1 == pytest.approx(1.2815516) and z2 == pytest.approx(1.7884, abs=1e-3)33    assert expected_max_sharpe(10, 1.0) == pytest.approx((1 - EULER_GAMMA) * z1 + EULER_GAMMA * z2)34    assert expected_max_sharpe(10, 1.0) == pytest.approx(1.574, abs=1e-3)35    assert expected_max_sharpe(10, 4.0) == pytest.approx(2 * expected_max_sharpe(10, 1.0))36    assert expected_max_sharpe(1, 1.0) == 0.0 and expected_max_sharpe(50, 0.0) == 0.037    with pytest.raises(ValueError):38        expected_max_sharpe(0, 1.0)39    # the simulated maximum of N standard normal Sharpe ratios agrees with the approximation40    rng = np.random.default_rng(0)41    simulated = rng.standard_normal((20000, 100)).max(axis=1).mean()42    assert expected_max_sharpe(100, 1.0) == pytest.approx(simulated, abs=0.03)434445def test_probabilistic_sharpe_by_hand():46    # normal returns (skew 0, kurtosis 3), SR 0.1 per period, 101 observations:47    # Phi(0.1 * sqrt(100) / sqrt(1 + 0.5 * 0.01)) = Phi(0.997509) = 0.8407448    assert probabilistic_sharpe(0.1, 0.0, 101) == pytest.approx(stats.norm.cdf(1 / math.sqrt(1.005)))49    assert probabilistic_sharpe(0.1, 0.0, 101) == pytest.approx(0.84074, abs=1e-4)50    assert probabilistic_sharpe(0.1, 0.1, 101) == pytest.approx(0.5)51    # negative skew and fat tails lower the confidence in a positive Sharpe ratio52    assert probabilistic_sharpe(0.1, 0.0, 101, skew=-2.0, kurtosis=12.0) < probabilistic_sharpe(0.1, 0.0, 101)53    assert math.isnan(probabilistic_sharpe(0.1, 0.0, 1))54    assert math.isnan(probabilistic_sharpe(2.0, 0.0, 100, skew=3.0, kurtosis=1.0))   # impossible moments555657def test_more_trials_deflate_more():58    values = [deflated_sharpe(0.08, 2000, -0.5, 6.0, n, 0.001) for n in (1, 2, 10, 100, 1000)]59    assert values == sorted(values, reverse=True) and values[0] > 0.99 and values[-1] < 0.260    assert deflated_sharpe(0.08, 2000, -0.5, 6.0, 1, 0.001) == probabilistic_sharpe(0.08, 0.0, 2000, -0.5, 6.0)616263def test_from_returns_reports_its_inputs():64    rng = np.random.default_rng(1)65    r = rng.normal(0.001, 0.01, 3000)66    trials = list(rng.normal(0.0, 0.03, 40))67    out = deflated_sharpe_from_returns(r, trials + [None, float("nan")])68    sr = r.mean() / r.std(ddof=1)69    assert out["n_trials"] == 40 and out["n_obs"] == 300070    assert out["sharpe_per_bar"] == pytest.approx(sr)71    assert out["var_trial_sharpes"] == pytest.approx(np.var(trials, ddof=1))72    assert out["kurtosis"] == pytest.approx(stats.kurtosis(r, fisher=False))73    assert out["expected_max_sharpe_per_bar"] == pytest.approx(expected_max_sharpe(40, np.var(trials, ddof=1)))74    assert out["deflated_sharpe"] == pytest.approx(75        probabilistic_sharpe(sr, out["expected_max_sharpe_per_bar"], 3000, out["skew"], out["kurtosis"]))76    assert out["deflated_sharpe"] < out["probabilistic_sharpe_vs_zero"]77    single = deflated_sharpe_from_returns(r, [0.05])78    assert single["n_trials"] == 1 and single["deflated_sharpe"] == pytest.approx(single["probabilistic_sharpe_vs_zero"])79    flat = deflated_sharpe_from_returns(np.zeros(100), trials)80    assert flat["deflated_sharpe"] is None81
Files main
Menu

Theme