a
This commit is contained in:
parent
8612b6cb21
commit
e3b6dfb65d
|
|
@ -4,6 +4,7 @@
|
|||
from fastapi import APIRouter, Query, HTTPException
|
||||
from typing import Optional
|
||||
from datetime import datetime, timedelta
|
||||
from collections import Counter
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import logging
|
||||
|
|
@ -226,6 +227,23 @@ async def get_trade_stats(
|
|||
sum(abs(float(t["pnl"])) for t in loss_trades) / len(loss_trades) if loss_trades else 0.0
|
||||
)
|
||||
win_loss_ratio = (avg_win_pnl / avg_loss_pnl_abs) if avg_loss_pnl_abs > 0 else None
|
||||
|
||||
# 平仓原因分布(用来快速定位胜率低的主要来源:止损/止盈/同步等)
|
||||
exit_reason_counts = Counter((t.get("exit_reason") or "unknown") for t in meaningful_trades)
|
||||
|
||||
# 平均持仓时长(分钟):优先使用 duration_minutes 字段(若为空则跳过)
|
||||
durations = []
|
||||
for t in meaningful_trades:
|
||||
dm = t.get("duration_minutes")
|
||||
try:
|
||||
if dm is None:
|
||||
continue
|
||||
dm_f = float(dm)
|
||||
if dm_f >= 0:
|
||||
durations.append(dm_f)
|
||||
except Exception:
|
||||
continue
|
||||
avg_duration_minutes = (sum(durations) / len(durations)) if durations else None
|
||||
|
||||
stats = {
|
||||
"total_trades": len(trades),
|
||||
|
|
@ -243,6 +261,8 @@ async def get_trade_stats(
|
|||
"avg_loss_pnl_abs": avg_loss_pnl_abs,
|
||||
"avg_win_loss_ratio": win_loss_ratio,
|
||||
"avg_win_loss_ratio_target": 3.0,
|
||||
"exit_reason_counts": dict(exit_reason_counts),
|
||||
"avg_duration_minutes": avg_duration_minutes,
|
||||
# 总交易量(名义下单量口径):优先使用 notional_usdt(新字段),否则回退 entry_price * quantity
|
||||
"total_notional_usdt": sum(
|
||||
float(t.get('notional_usdt') or (float(t.get('entry_price', 0)) * float(t.get('quantity', 0))))
|
||||
|
|
|
|||
|
|
@ -266,6 +266,39 @@ const TradeList = () => {
|
|||
{stats.avg_pnl.toFixed(2)} USDT
|
||||
</div>
|
||||
</div>
|
||||
{"avg_duration_minutes" in stats && stats.avg_duration_minutes !== null && stats.avg_duration_minutes !== undefined && (
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">平均持仓时长(分钟)</div>
|
||||
<div className="stat-value">{Number(stats.avg_duration_minutes || 0).toFixed(0)}</div>
|
||||
<div style={{ fontSize: '12px', color: '#999', marginTop: '4px' }}>
|
||||
(仅统计“有意义交易”,且需要 duration_minutes 字段)
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{"exit_reason_counts" in stats && stats.exit_reason_counts && (
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">平仓原因(有意义交易)</div>
|
||||
<div className="stat-value" style={{ fontSize: '1.1rem' }}>
|
||||
{(() => {
|
||||
const m = stats.exit_reason_counts || {}
|
||||
const stopLoss = Number(m.stop_loss || 0)
|
||||
const takeProfit = Number(m.take_profit || 0)
|
||||
const trailing = Number(m.trailing_stop || 0)
|
||||
const manual = Number(m.manual || 0)
|
||||
const sync = Number(m.sync || 0)
|
||||
const other = Number(m.unknown || 0)
|
||||
const parts = []
|
||||
if (stopLoss) parts.push(`止损 ${stopLoss}`)
|
||||
if (takeProfit) parts.push(`止盈 ${takeProfit}`)
|
||||
if (trailing) parts.push(`移动止损 ${trailing}`)
|
||||
if (manual) parts.push(`手动 ${manual}`)
|
||||
if (sync) parts.push(`同步 ${sync}`)
|
||||
if (other) parts.push(`其他 ${other}`)
|
||||
return parts.length ? parts.join(' / ') : '—'
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{"avg_win_pnl" in stats && "avg_loss_pnl_abs" in stats && Number(stats.total_pnl || 0) > 0 && (
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">平均盈利 / 平均亏损(期望 3:1)</div>
|
||||
|
|
|
|||
|
|
@ -412,6 +412,13 @@ class TradingStrategy:
|
|||
# 判断是否应该交易(信号强度 >= 7 才交易,提高胜率)
|
||||
min_signal_strength = config.TRADING_CONFIG.get('MIN_SIGNAL_STRENGTH', 7)
|
||||
should_trade = signal_strength >= min_signal_strength and direction is not None
|
||||
|
||||
# 提升胜率:4H趋势中性时不做自动交易(只保留推荐/观察)
|
||||
# 经验上,中性趋势下“趋势跟踪”信号更容易被来回扫损,导致胜率显著降低与交易次数激增。
|
||||
if trend_4h == 'neutral':
|
||||
if should_trade:
|
||||
reasons.append("❌ 4H趋势中性(为提升胜率:仅生成推荐,不自动交易)")
|
||||
should_trade = False
|
||||
|
||||
# 如果信号方向与4H趋势相反,直接拒绝交易
|
||||
if direction and trend_4h:
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user