From fbdb1cf4211333240ef0c60af4419481bfcc4d5b Mon Sep 17 00:00:00 2001 From: aaron <> Date: Wed, 25 Feb 2026 21:06:07 +0800 Subject: [PATCH] update --- backend/app/services/paper_trading_service.py | 61 ++++++++++++- frontend/paper-trading.html | 91 +++++++++++++++++++ 2 files changed, 149 insertions(+), 3 deletions(-) diff --git a/backend/app/services/paper_trading_service.py b/backend/app/services/paper_trading_service.py index 94e5363..2b72ae3 100644 --- a/backend/app/services/paper_trading_service.py +++ b/backend/app/services/paper_trading_service.py @@ -1099,7 +1099,7 @@ class PaperTradingService: if end_date: query = query.filter(PaperOrder.closed_at <= end_date) - orders = query.all() + orders = query.order_by(PaperOrder.closed_at.asc()).all() if not orders: return self._empty_statistics() @@ -1118,6 +1118,9 @@ class PaperTradingService: gross_profit = sum(wins) if wins else 0 gross_loss = sum(losses) if losses else 0 + # 计算账户最大回撤 + account_max_drawdown = self._calculate_account_max_drawdown(orders) + return { 'total_trades': total_trades, 'winning_trades': winning_trades, @@ -1129,7 +1132,8 @@ class PaperTradingService: 'average_win': round(sum(wins) / len(wins), 2) if wins else 0, 'average_loss': round(sum(losses) / len(losses), 2) if losses else 0, 'profit_factor': round(gross_profit / gross_loss, 2) if gross_loss > 0 else float('inf'), - 'max_drawdown': min(o.max_drawdown for o in orders) if orders else 0, + 'max_drawdown': round(account_max_drawdown, 2), + 'max_drawdown_amount': round(account_max_drawdown * self.initial_balance / 100, 2) if account_max_drawdown != 0 else 0, 'best_trade': max(o.pnl_percent for o in orders) if orders else 0, 'worst_trade': min(o.pnl_percent for o in orders) if orders else 0, 'by_grade': self._calculate_grade_statistics(orders), @@ -1140,6 +1144,49 @@ class PaperTradingService: finally: db.close() + def _calculate_account_max_drawdown(self, orders: List[PaperOrder]) -> float: + """ + 计算账户最大回撤 + + 账户最大回撤是指在统计周期内,账户权益从最高点跌落的最大百分比。 + 这是通过模拟每笔交易平仓后的账户余额来计算的。 + + Args: + orders: 已平仓订单列表(应按平仓时间排序) + + Returns: + 账户最大回撤百分比(负数,如 -5.23 表示最大回撤 5.23%) + """ + if not orders: + return 0.0 + + # 确保订单按平仓时间排序 + orders = sorted(orders, key=lambda o: o.closed_at or datetime.utcnow()) + + # 模拟账户权益曲线 + initial_balance = self.initial_balance + running_balance = initial_balance + peak_balance = initial_balance + max_drawdown = 0.0 + + for order in orders: + if order.closed_at and order.pnl_amount is not None: + # 更新账户余额 + running_balance += order.pnl_amount + + # 检查是否创新高 + if running_balance > peak_balance: + peak_balance = running_balance + + # 计算从峰值到当前值的回撤 + if peak_balance > 0: + drawdown = (running_balance - peak_balance) / peak_balance * 100 + # 记录最大回撤(最负的值) + if drawdown < max_drawdown: + max_drawdown = drawdown + + return max_drawdown + def _empty_statistics(self) -> Dict[str, Any]: """返回空统计结构""" return { @@ -1154,6 +1201,7 @@ class PaperTradingService: 'average_loss': 0, 'profit_factor': 0, 'max_drawdown': 0, + 'max_drawdown_amount': 0, 'best_trade': 0, 'worst_trade': 0, 'by_grade': {}, @@ -1185,6 +1233,10 @@ class PaperTradingService: ).all() realized_pnl = sum(o.pnl_amount for o in closed_orders) + + # 计算账户最大回撤 + account_max_drawdown = self._calculate_account_max_drawdown(closed_orders) + finally: db.close() @@ -1210,7 +1262,10 @@ class PaperTradingService: 'max_orders': self.max_orders, 'available_orders': available_orders, 'total_position_value': round(used_margin * self.leverage, 2), - 'margin_ratio': round((used_margin / current_balance * 100), 2) if current_balance > 0 else 0 + 'margin_ratio': round((used_margin / current_balance * 100), 2) if current_balance > 0 else 0, + 'max_drawdown': round(account_max_drawdown, 2), # 账户最大回撤百分比 + 'max_drawdown_amount': round(account_max_drawdown * self.initial_balance / 100, 2) if account_max_drawdown != 0 else 0, # 账户最大回撤金额 + 'total_pnl_percent': round((realized_pnl / self.initial_balance * 100), 2) if self.initial_balance > 0 else 0 } def _calculate_grade_statistics(self, orders: List[PaperOrder]) -> Dict[str, Any]: diff --git a/frontend/paper-trading.html b/frontend/paper-trading.html index a2fd93f..333bf4a 100644 --- a/frontend/paper-trading.html +++ b/frontend/paper-trading.html @@ -1087,6 +1087,12 @@
盈亏比
{{ stats.profit_factor === Infinity ? '∞' : stats.profit_factor.toFixed(2) }}
+
+
最大回撤
+
+ {{ stats.max_drawdown ? stats.max_drawdown.toFixed(2) : '0.00' }}% +
+
@@ -1267,6 +1273,91 @@
+ +
+
+
+ 交易统计 +
+
+
+ 总交易数 + {{ stats.total_trades }} +
+
+ 盈利交易 + {{ stats.winning_trades }} +
+
+ 亏损交易 + {{ stats.losing_trades }} +
+
+ 胜率 + {{ stats.win_rate.toFixed(1) }}% +
+
+ 平均盈利 + ${{ stats.average_win.toFixed(2) }} +
+
+ 平均亏损 + ${{ stats.average_loss.toFixed(2) }} +
+
+
+
+
+ 盈亏指标 +
+
+
+ 总盈亏 + + ${{ stats.total_pnl.toFixed(2) }} + +
+
+ 平均盈亏 + + ${{ stats.average_pnl.toFixed(2) }} + +
+
+ 盈亏比 + {{ stats.profit_factor === Infinity ? '∞' : stats.profit_factor.toFixed(2) }} +
+
+ 最佳交易 + {{ stats.best_trade.toFixed(2) }}% +
+
+ 最差交易 + {{ stats.worst_trade.toFixed(2) }}% +
+
+
+
+
+ 风险指标 +
+
+
+ 最大回撤 + + {{ stats.max_drawdown ? stats.max_drawdown.toFixed(2) : '0.00' }}% + +
+
+ 回撤金额 + + {{ stats.max_drawdown_amount ? (stats.max_drawdown_amount >= 0 ? '+$' : '-$') + Math.abs(stats.max_drawdown_amount).toFixed(2) : '$0.00' }} + +
+
+
+
+

按信号等级统计