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:
+15
-1
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user