Files
ftdt-quant-lab/tests/test_quant_walkforward.py
T
ramseshk 543537e33f feat: quant validation framework — DSR, PSR, Haircut, regimes, walk-forward
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
2026-08-10 16:20:56 +08:00

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"