fix: NaN win_rate/profit_factor/expectancy crash JSON serialization

Root cause: VBT produces NaN for win_rate, profit_factor, and
expectancy when all trades have zero PnL (no winners, no losers).
JSON.dumps() rejects NaN/Inf values with 'ValueError: Out of range
float values are not JSON compliant'.

Fixes:
- Add _sanitize_nan(): recursive NaN/Inf → 0.0 for JSON safety
- Apply at end of _normalize_vbt_fields (after field fixes)
- Fix win_rate check: use 'is None' instead of 'not' (0.0 is falsy)
- Previously crashing files now work:
  grid_mm_BTC_4h_100b, hurst_vpin_BTC_1m_100b, etc.
This commit is contained in:
ramseshk
2026-08-07 16:46:14 +08:00
parent 745174f0e6
commit 2aca789581
+15 -1
View File
@@ -528,6 +528,19 @@ def _refresh_vbt_cache():
_vbt_cache_time = now _vbt_cache_time = now
def _sanitize_nan(obj):
"""Recursively replace NaN/Inf/-Inf with 0 for JSON compliance."""
import math
if isinstance(obj, dict):
return {k: _sanitize_nan(v) for k, v in obj.items()}
if isinstance(obj, list):
return [_sanitize_nan(v) for v in obj]
if isinstance(obj, float):
if math.isnan(obj) or math.isinf(obj):
return 0.0
return obj
def _normalize_vbt_fields(data: dict) -> dict: def _normalize_vbt_fields(data: dict) -> dict:
"""Normalise old/new backtest file field names to a consistent schema.""" """Normalise old/new backtest file field names to a consistent schema."""
out = dict(data) out = dict(data)
@@ -570,7 +583,7 @@ def _normalize_vbt_fields(data: dict) -> dict:
if out.get("total_trades") is None: if out.get("total_trades") is None:
out["total_trades"] = 0 out["total_trades"] = 0
if not out.get("win_rate") and "trades" in out: if out.get("win_rate") is None and "trades" in out:
trades = out.get("trades", []) trades = out.get("trades", [])
if trades: if trades:
wins = sum(1 for t in trades if (t.get("pnl_net", t.get("pnl_gross", t.get("pnl", 0))) or 0) > 0) wins = sum(1 for t in trades if (t.get("pnl_net", t.get("pnl_gross", t.get("pnl", 0))) or 0) > 0)
@@ -604,6 +617,7 @@ def _normalize_vbt_fields(data: dict) -> dict:
fee = float(t.get("fee", 0)) fee = float(t.get("fee", 0))
t["pnl_gross"] = round(pnl_net + fee, 4) t["pnl_gross"] = round(pnl_net + fee, 4)
out = _sanitize_nan(out)
return out return out