fix: Fix max drawdown calculation and pandas fillna compatibility
- Fix max drawdown calculation in dashboard to use equity curve instead of cumulative profit - Add pandas fillna(method=...) compatibility fix for older indicator code - Cap drawdown percentage at 10000% to avoid display issues
This commit is contained in:
@@ -113,9 +113,12 @@ def _calc_pnl_percent(entry_price: float, size: float, pnl: float, leverage: flo
|
||||
return 0.0
|
||||
|
||||
|
||||
def _compute_performance_stats(trades: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
def _compute_performance_stats(trades: List[Dict[str, Any]], initial_capital: float = 0.0) -> Dict[str, Any]:
|
||||
"""
|
||||
Compute performance statistics from trade history.
|
||||
Args:
|
||||
trades: List of trade records
|
||||
initial_capital: Initial capital for calculating equity curve (default: 0.0, will use cumulative profit peak as baseline)
|
||||
Returns: {
|
||||
total_trades, winning_trades, losing_trades, win_rate,
|
||||
total_profit, total_loss, profit_factor,
|
||||
@@ -163,23 +166,49 @@ def _compute_performance_stats(trades: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
max_win = max(profits) if profits else 0.0
|
||||
max_loss = min(profits) if profits else 0.0
|
||||
|
||||
# Calculate max drawdown from cumulative equity
|
||||
cumulative = []
|
||||
acc = 0.0
|
||||
# Calculate max drawdown from equity curve (initial_capital + cumulative profit)
|
||||
# This ensures proper percentage calculation even when cumulative profit is negative
|
||||
cumulative_profit = 0.0
|
||||
equity_curve = []
|
||||
for p in profits:
|
||||
acc += p
|
||||
cumulative.append(acc)
|
||||
cumulative_profit += p
|
||||
equity = initial_capital + cumulative_profit
|
||||
equity_curve.append(equity)
|
||||
|
||||
peak = 0.0
|
||||
# Calculate max drawdown from equity curve
|
||||
peak_equity = initial_capital if initial_capital > 0 else (equity_curve[0] if equity_curve else 0.0)
|
||||
max_drawdown = 0.0
|
||||
for val in cumulative:
|
||||
if val > peak:
|
||||
peak = val
|
||||
dd = peak - val
|
||||
if dd > max_drawdown:
|
||||
max_drawdown = dd
|
||||
for equity in equity_curve:
|
||||
if equity > peak_equity:
|
||||
peak_equity = equity
|
||||
# Drawdown is the drop from peak
|
||||
drawdown = peak_equity - equity
|
||||
if drawdown > max_drawdown:
|
||||
max_drawdown = drawdown
|
||||
|
||||
max_drawdown_pct = (max_drawdown / peak * 100) if peak > 0 else 0.0
|
||||
# Calculate drawdown percentage: drawdown / peak_equity * 100
|
||||
# If peak_equity is 0 or very small, use a fallback calculation
|
||||
if peak_equity > 0:
|
||||
max_drawdown_pct = (max_drawdown / peak_equity * 100)
|
||||
elif initial_capital > 0:
|
||||
# Fallback: use initial capital as baseline
|
||||
max_drawdown_pct = (max_drawdown / initial_capital * 100) if initial_capital > 0 else 0.0
|
||||
else:
|
||||
# Last resort: if no initial capital and peak is 0, calculate from cumulative profit peak
|
||||
cumulative = []
|
||||
acc = 0.0
|
||||
for p in profits:
|
||||
acc += p
|
||||
cumulative.append(acc)
|
||||
peak_profit = max(cumulative) if cumulative else 0.0
|
||||
if peak_profit > 0:
|
||||
max_drawdown_pct = (max_drawdown / peak_profit * 100)
|
||||
else:
|
||||
max_drawdown_pct = 0.0
|
||||
|
||||
# Cap drawdown percentage at reasonable maximum (e.g., 10000%) to avoid display issues
|
||||
if max_drawdown_pct > 10000:
|
||||
max_drawdown_pct = 10000.0
|
||||
|
||||
# Best/worst day
|
||||
day_profits: Dict[str, float] = {}
|
||||
@@ -243,9 +272,9 @@ def _compute_strategy_stats(trades: List[Dict[str, Any]], strategies: List[Dict[
|
||||
|
||||
result = []
|
||||
for sid, strades in sid_to_trades.items():
|
||||
stats = _compute_performance_stats(strades)
|
||||
total_pnl = sum(_safe_float(t.get("profit"), 0.0) for t in strades)
|
||||
capital = sid_to_capital.get(sid, 0.0)
|
||||
stats = _compute_performance_stats(strades, initial_capital=capital)
|
||||
total_pnl = sum(_safe_float(t.get("profit"), 0.0) for t in strades)
|
||||
roi = (total_pnl / capital * 100) if capital > 0 else 0.0
|
||||
|
||||
result.append({
|
||||
@@ -374,13 +403,7 @@ def summary():
|
||||
trade['created_at'] = int(trade['created_at'].timestamp())
|
||||
recent_trades.append(trade)
|
||||
|
||||
# Compute performance statistics
|
||||
perf_stats = _compute_performance_stats(recent_trades)
|
||||
|
||||
# Compute per-strategy statistics
|
||||
strategy_stats = _compute_strategy_stats(recent_trades, strategies)
|
||||
|
||||
# Total equity/pnl (best-effort)
|
||||
# Total equity/pnl (best-effort) - calculate before performance stats for drawdown calculation
|
||||
total_initial_capital = 0.0
|
||||
for s in strategies:
|
||||
try:
|
||||
@@ -388,6 +411,12 @@ def summary():
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Compute performance statistics with initial capital for proper drawdown calculation
|
||||
perf_stats = _compute_performance_stats(recent_trades, initial_capital=total_initial_capital)
|
||||
|
||||
# Compute per-strategy statistics
|
||||
strategy_stats = _compute_strategy_stats(recent_trades, strategies)
|
||||
|
||||
# Include realized PnL from trades
|
||||
total_realized_pnl = sum(_safe_float(t.get("profit"), 0.0) for t in recent_trades)
|
||||
total_pnl = float(total_unrealized_pnl + total_realized_pnl)
|
||||
|
||||
@@ -1888,8 +1888,27 @@ class TradingExecutor:
|
||||
pre_import_code = "import numpy as np\nimport pandas as pd\n"
|
||||
exec(pre_import_code, exec_env)
|
||||
|
||||
# 兼容性修复:将旧版pandas的fillna(method=...)语法转换为新版语法
|
||||
# pandas 2.0+ 移除了 fillna() 的 method 参数,需要使用 ffill() 或 bfill()
|
||||
# 旧语法: df.fillna(method='ffill') 或 df.fillna(method="ffill")
|
||||
# 新语法: df.ffill()
|
||||
import re
|
||||
compatibility_fixed_code = indicator_code
|
||||
# 替换 fillna(method='ffill') 或 fillna(method="ffill") 为 ffill()
|
||||
compatibility_fixed_code = re.sub(
|
||||
r'\.fillna\(\s*method\s*=\s*["\']ffill["\']\s*\)',
|
||||
'.ffill()',
|
||||
compatibility_fixed_code
|
||||
)
|
||||
# 替换 fillna(method='bfill') 或 fillna(method="bfill") 为 bfill()
|
||||
compatibility_fixed_code = re.sub(
|
||||
r'\.fillna\(\s*method\s*=\s*["\']bfill["\']\s*\)',
|
||||
'.bfill()',
|
||||
compatibility_fixed_code
|
||||
)
|
||||
|
||||
# 这里的 safe_exec_code 假设已存在
|
||||
exec(indicator_code, exec_env)
|
||||
exec(compatibility_fixed_code, exec_env)
|
||||
|
||||
executed_df = exec_env.get('df', df)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user