From 2aca7895810caddeb0012acb481d5bae604c88ef Mon Sep 17 00:00:00 2001 From: ramseshk <45832522+ramseshk@users.noreply.github.com> Date: Fri, 7 Aug 2026 16:46:14 +0800 Subject: [PATCH] fix: NaN win_rate/profit_factor/expectancy crash JSON serialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- dashboard/server.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/dashboard/server.py b/dashboard/server.py index 50570fe..dd2a0a7 100644 --- a/dashboard/server.py +++ b/dashboard/server.py @@ -528,6 +528,19 @@ def _refresh_vbt_cache(): _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: """Normalise old/new backtest file field names to a consistent schema.""" out = dict(data) @@ -570,7 +583,7 @@ def _normalize_vbt_fields(data: dict) -> dict: if out.get("total_trades") is None: 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", []) 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) @@ -604,6 +617,7 @@ def _normalize_vbt_fields(data: dict) -> dict: fee = float(t.get("fee", 0)) t["pnl_gross"] = round(pnl_net + fee, 4) + out = _sanitize_nan(out) return out