543537e33f
Three-module quant framework replacing 'sort by Sharpe' with proper statistical validation: quant/significance.py (15 tests): - deflated_sharpe_ratio(): adjusts for N trials (Harvey & Liu 2015) - probabilistic_sharpe_ratio(): P(True SR > benchmark) given T, skew, kurt - sharpe_haircut(): expected OOS Sharpe after selection bias deflation - QuantVerdict: DEPLOY / SIMULATE / DISCARD with 5-point scoring - validate_strategy(): one-shot validation function quant/regimes.py (8 tests): - classify_regime(): trending_up/down, ranging, volatile - RegimeClassifier: stateful rolling-window classifier - conditional_performance(): per-regime trade statistics quant/walkforward.py (5 tests): - WalkForwardRunner: sequential IS/OOS window optimization - WFWindow/WFReport: structured walk-forward results - consistency score, performance decay, concatenated OOS equity - significance_report() integration Walk-forward results (real HL data with date-sliced windows): grid_mm 1h: 2/4 pos, OOS S=-0.45, 74t, haircut=-22.66 → DISCARD momentum 4h: 2/4 pos, OOS S=-1.47, 116t, haircut=-45.35 → DISCARD composite_mm 1h: 2/4 pos, OOS S=+2.97, 6t, haircut=+43.25 → SIMULATE 28 tests total
52 lines
2.0 KiB
Python
52 lines
2.0 KiB
Python
"""
|
|
Tests for quant/walkforward.py.
|
|
"""
|
|
from quant.walkforward import WalkForwardRunner, WFReport, WFWindow
|
|
|
|
|
|
class TestWFReport:
|
|
def test_empty_report(self):
|
|
report = WFReport(strategy="test", interval="1h", n_windows=5)
|
|
assert report.consistency == 0.0
|
|
assert report.oos_sharpe == 0.0
|
|
assert report.performance_decay == 0.0
|
|
assert report.total_oos_trades == 0
|
|
|
|
def test_single_window_positive(self):
|
|
report = WFReport(strategy="test", interval="1h", n_windows=5)
|
|
report.windows.append(WFWindow(
|
|
window_idx=0, is_start="2026-01-01", is_end="2026-02-01",
|
|
oos_start="2026-02-01", oos_end="2026-03-01",
|
|
is_sharpe=2.0, oos_sharpe=1.5, is_return_pct=5.0,
|
|
oos_return_pct=3.0, oos_trades=10,
|
|
))
|
|
assert report.consistency == 1.0
|
|
assert report.avg_oos_sharpe == 1.5
|
|
assert report.performance_decay == 1.5 / 2.0
|
|
assert report.total_oos_trades == 10
|
|
|
|
def test_mixed_windows(self):
|
|
report = WFReport(strategy="test", interval="1h", n_windows=3)
|
|
report.windows = [
|
|
WFWindow(0, "A", "B", "B", "C", 2.0, 1.0, 5.0, 2.0, 5),
|
|
WFWindow(1, "B", "C", "C", "D", 1.0, -0.5, 2.0, -1.0, 8),
|
|
WFWindow(2, "C", "D", "D", "E", 1.5, 0.3, 3.0, 0.5, 6),
|
|
]
|
|
assert report.consistency == 2 / 3 # 2 of 3 windows positive OOS
|
|
assert report.avg_oos_sharpe == (1.0 - 0.5 + 0.3) / 3
|
|
|
|
def test_significance_discard_weak(self):
|
|
report = WFReport(strategy="test", interval="1h", n_windows=5)
|
|
report.windows.append(WFWindow(
|
|
0, "A", "B", "B", "C", 8.0, -2.0, 2.5, -5.0, 4,
|
|
))
|
|
report.oos_equity_curve = [{"t": 0, "v": 10000}, {"t": 1, "v": 9500}]
|
|
sig = report.significance_report(n_trials=639)
|
|
assert sig["verdict"] == "DISCARD"
|
|
|
|
def test_summary(self):
|
|
report = WFReport(strategy="momentum", interval="4h", n_windows=3)
|
|
s = report.summary()
|
|
assert s["strategy"] == "momentum"
|
|
assert s["interval"] == "4h"
|