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
This commit is contained in:
ramseshk
2026-08-10 16:20:56 +08:00
parent 268fe606fa
commit 543537e33f
8 changed files with 914 additions and 1 deletions
+3 -1
View File
@@ -312,6 +312,8 @@ class VBTBacktestRunner:
interval: str = "1h",
testnet: bool = False,
limit: int = 5000,
start_ms: int | None = None,
end_ms: int | None = None,
) -> dict[str, Any] | None:
"""Fetch candles, generate signals, run VBT backtest, return metrics."""
coins = self._get_coins(strategy)
@@ -320,7 +322,7 @@ class VBTBacktestRunner:
data = {}
for coin in coins:
try:
df = provider.fetch_candles(coin, interval=interval, limit=limit)
df = provider.fetch_candles(coin, interval=interval, limit=limit, start_ms=start_ms, end_ms=end_ms)
if not df.empty:
data[coin] = df
except Exception as e: