update
This commit is contained in:
parent
64c5ce606e
commit
fbdb1cf421
@ -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]:
|
||||
|
||||
@ -1087,6 +1087,12 @@
|
||||
<div class="stat-label">盈亏比</div>
|
||||
<div class="stat-value">{{ stats.profit_factor === Infinity ? '∞' : stats.profit_factor.toFixed(2) }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">最大回撤</div>
|
||||
<div class="stat-value" :class="stats.max_drawdown <= 0 ? 'negative' : 'positive'">
|
||||
{{ stats.max_drawdown ? stats.max_drawdown.toFixed(2) : '0.00' }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 实时价格 -->
|
||||
@ -1267,6 +1273,91 @@
|
||||
|
||||
<!-- 详细统计 -->
|
||||
<div v-if="activeTab === 'stats'">
|
||||
<!-- 总体指标汇总 -->
|
||||
<div class="grade-stats" style="margin-bottom: 30px;">
|
||||
<div class="grade-card">
|
||||
<div class="grade-card-header">
|
||||
<span class="grade-card-title">交易统计</span>
|
||||
</div>
|
||||
<div class="grade-card-stats">
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">总交易数</span>
|
||||
<span>{{ stats.total_trades }}</span>
|
||||
</div>
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">盈利交易</span>
|
||||
<span class="positive">{{ stats.winning_trades }}</span>
|
||||
</div>
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">亏损交易</span>
|
||||
<span class="negative">{{ stats.losing_trades }}</span>
|
||||
</div>
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">胜率</span>
|
||||
<span>{{ stats.win_rate.toFixed(1) }}%</span>
|
||||
</div>
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">平均盈利</span>
|
||||
<span class="positive">${{ stats.average_win.toFixed(2) }}</span>
|
||||
</div>
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">平均亏损</span>
|
||||
<span class="negative">${{ stats.average_loss.toFixed(2) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grade-card">
|
||||
<div class="grade-card-header">
|
||||
<span class="grade-card-title">盈亏指标</span>
|
||||
</div>
|
||||
<div class="grade-card-stats">
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">总盈亏</span>
|
||||
<span :class="stats.total_pnl >= 0 ? 'positive' : 'negative'">
|
||||
${{ stats.total_pnl.toFixed(2) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">平均盈亏</span>
|
||||
<span :class="stats.average_pnl >= 0 ? 'positive' : 'negative'">
|
||||
${{ stats.average_pnl.toFixed(2) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">盈亏比</span>
|
||||
<span>{{ stats.profit_factor === Infinity ? '∞' : stats.profit_factor.toFixed(2) }}</span>
|
||||
</div>
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">最佳交易</span>
|
||||
<span class="positive">{{ stats.best_trade.toFixed(2) }}%</span>
|
||||
</div>
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">最差交易</span>
|
||||
<span class="negative">{{ stats.worst_trade.toFixed(2) }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grade-card">
|
||||
<div class="grade-card-header">
|
||||
<span class="grade-card-title">风险指标</span>
|
||||
</div>
|
||||
<div class="grade-card-stats">
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">最大回撤</span>
|
||||
<span :class="stats.max_drawdown <= 0 ? 'negative' : 'positive'">
|
||||
{{ stats.max_drawdown ? stats.max_drawdown.toFixed(2) : '0.00' }}%
|
||||
</span>
|
||||
</div>
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">回撤金额</span>
|
||||
<span :class="stats.max_drawdown_amount <= 0 ? 'negative' : 'positive'">
|
||||
{{ stats.max_drawdown_amount ? (stats.max_drawdown_amount >= 0 ? '+$' : '-$') + Math.abs(stats.max_drawdown_amount).toFixed(2) : '$0.00' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 style="color: var(--text-primary); font-weight: 300; margin-bottom: 16px;">按信号等级统计</h3>
|
||||
<div class="grade-stats">
|
||||
<div class="grade-card" v-for="(data, grade) in stats.by_grade" :key="grade">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user