update
This commit is contained in:
parent
64c5ce606e
commit
fbdb1cf421
@ -1099,7 +1099,7 @@ class PaperTradingService:
|
|||||||
if end_date:
|
if end_date:
|
||||||
query = query.filter(PaperOrder.closed_at <= 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:
|
if not orders:
|
||||||
return self._empty_statistics()
|
return self._empty_statistics()
|
||||||
@ -1118,6 +1118,9 @@ class PaperTradingService:
|
|||||||
gross_profit = sum(wins) if wins else 0
|
gross_profit = sum(wins) if wins else 0
|
||||||
gross_loss = sum(losses) if losses else 0
|
gross_loss = sum(losses) if losses else 0
|
||||||
|
|
||||||
|
# 计算账户最大回撤
|
||||||
|
account_max_drawdown = self._calculate_account_max_drawdown(orders)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'total_trades': total_trades,
|
'total_trades': total_trades,
|
||||||
'winning_trades': winning_trades,
|
'winning_trades': winning_trades,
|
||||||
@ -1129,7 +1132,8 @@ class PaperTradingService:
|
|||||||
'average_win': round(sum(wins) / len(wins), 2) if wins else 0,
|
'average_win': round(sum(wins) / len(wins), 2) if wins else 0,
|
||||||
'average_loss': round(sum(losses) / len(losses), 2) if losses 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'),
|
'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,
|
'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,
|
'worst_trade': min(o.pnl_percent for o in orders) if orders else 0,
|
||||||
'by_grade': self._calculate_grade_statistics(orders),
|
'by_grade': self._calculate_grade_statistics(orders),
|
||||||
@ -1140,6 +1144,49 @@ class PaperTradingService:
|
|||||||
finally:
|
finally:
|
||||||
db.close()
|
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]:
|
def _empty_statistics(self) -> Dict[str, Any]:
|
||||||
"""返回空统计结构"""
|
"""返回空统计结构"""
|
||||||
return {
|
return {
|
||||||
@ -1154,6 +1201,7 @@ class PaperTradingService:
|
|||||||
'average_loss': 0,
|
'average_loss': 0,
|
||||||
'profit_factor': 0,
|
'profit_factor': 0,
|
||||||
'max_drawdown': 0,
|
'max_drawdown': 0,
|
||||||
|
'max_drawdown_amount': 0,
|
||||||
'best_trade': 0,
|
'best_trade': 0,
|
||||||
'worst_trade': 0,
|
'worst_trade': 0,
|
||||||
'by_grade': {},
|
'by_grade': {},
|
||||||
@ -1185,6 +1233,10 @@ class PaperTradingService:
|
|||||||
).all()
|
).all()
|
||||||
|
|
||||||
realized_pnl = sum(o.pnl_amount for o in closed_orders)
|
realized_pnl = sum(o.pnl_amount for o in closed_orders)
|
||||||
|
|
||||||
|
# 计算账户最大回撤
|
||||||
|
account_max_drawdown = self._calculate_account_max_drawdown(closed_orders)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
@ -1210,7 +1262,10 @@ class PaperTradingService:
|
|||||||
'max_orders': self.max_orders,
|
'max_orders': self.max_orders,
|
||||||
'available_orders': available_orders,
|
'available_orders': available_orders,
|
||||||
'total_position_value': round(used_margin * self.leverage, 2),
|
'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]:
|
def _calculate_grade_statistics(self, orders: List[PaperOrder]) -> Dict[str, Any]:
|
||||||
|
|||||||
@ -1087,6 +1087,12 @@
|
|||||||
<div class="stat-label">盈亏比</div>
|
<div class="stat-label">盈亏比</div>
|
||||||
<div class="stat-value">{{ stats.profit_factor === Infinity ? '∞' : stats.profit_factor.toFixed(2) }}</div>
|
<div class="stat-value">{{ stats.profit_factor === Infinity ? '∞' : stats.profit_factor.toFixed(2) }}</div>
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
<!-- 实时价格 -->
|
<!-- 实时价格 -->
|
||||||
@ -1267,6 +1273,91 @@
|
|||||||
|
|
||||||
<!-- 详细统计 -->
|
<!-- 详细统计 -->
|
||||||
<div v-if="activeTab === 'stats'">
|
<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>
|
<h3 style="color: var(--text-primary); font-weight: 300; margin-bottom: 16px;">按信号等级统计</h3>
|
||||||
<div class="grade-stats">
|
<div class="grade-stats">
|
||||||
<div class="grade-card" v-for="(data, grade) in stats.by_grade" :key="grade">
|
<div class="grade-card" v-for="(data, grade) in stats.by_grade" :key="grade">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user