bot统计

This commit is contained in:
dekun
2026-05-15 22:04:00 +08:00
parent 1f4f571210
commit a86d1a44dd
2 changed files with 253 additions and 68 deletions
+60 -24
View File
@@ -1450,20 +1450,41 @@ def _count_opens_between(conn, start_td, end_td):
).fetchone()[0]
def _load_completed_live_pnls(conn):
def _count_trend_plan_opens_between(conn, start_td, end_td):
"""趋势回调:按计划在库里的 session_date(开仓所属北京交易日)计数。"""
return conn.execute(
"SELECT COUNT(*) FROM trend_pullback_plans WHERE session_date IS NOT NULL AND TRIM(session_date) != '' "
"AND session_date >= ? AND session_date <= ?",
(start_td, end_td),
).fetchone()[0]
def _load_completed_trade_pnls(conn, monitor_type: str):
"""已平仓实盘记录:按 monitor_type 过滤;趋势回调优先用交易所同步盈亏。"""
q = """SELECT pnl_amount, reviewed_pnl_amount, closed_at, reviewed_closed_at, created_at,
result, reviewed_result
result, reviewed_result, exchange_realized_pnl
FROM trade_records
WHERE monitor_type = '下单监控'
WHERE monitor_type = ?
ORDER BY COALESCE(closed_at, created_at, opened_at) ASC, id ASC"""
rows = conn.execute(q).fetchall()
rows = conn.execute(q, (monitor_type,)).fetchall()
out = []
for r in rows:
effective_result = (r["reviewed_result"] or r["result"] or "").strip()
if effective_result not in TRADE_COMPLETED_RESULTS:
continue
try:
p = float(r["reviewed_pnl_amount"] if r["reviewed_pnl_amount"] is not None else (r["pnl_amount"] or 0))
if monitor_type == MONITOR_TYPE_TREND:
ex = None
try:
ex = r["exchange_realized_pnl"]
except (KeyError, IndexError, TypeError):
ex = None
if ex is not None and str(ex).strip() != "":
p = float(ex)
else:
p = float(r["reviewed_pnl_amount"] if r["reviewed_pnl_amount"] is not None else (r["pnl_amount"] or 0))
else:
p = float(r["reviewed_pnl_amount"] if r["reviewed_pnl_amount"] is not None else (r["pnl_amount"] or 0))
except (TypeError, ValueError):
p = 0.0
t = parse_dt_for_trading_day(r["reviewed_closed_at"]) or parse_dt_for_trading_day(r["closed_at"]) or parse_dt_for_trading_day(r["created_at"])
@@ -1537,10 +1558,12 @@ def _compute_period_metrics(trades):
def compute_stats_bundle(conn, trading_day, now_dt=None):
"""日 / 周 / 月 统计:平仓按平仓时间所在交易日计入。"""
"""日 / 周 / 月 统计:平仓按平仓时间所在交易日计入;下单监控与趋势回调分列"""
now_dt = now_dt or app_now()
pnls = _load_completed_live_pnls(conn)
total_opens_all = conn.execute("SELECT COUNT(*) FROM order_monitors").fetchone()[0]
pnls_order = _load_completed_trade_pnls(conn, "下单监控")
pnls_trend = _load_completed_trade_pnls(conn, MONITOR_TYPE_TREND)
total_opens_order = conn.execute("SELECT COUNT(*) FROM order_monitors").fetchone()[0]
total_opens_trend = conn.execute("SELECT COUNT(*) FROM trend_pullback_plans").fetchone()[0]
w_start, w_end = _session_week_bounds(trading_day)
m_start, m_end = _calendar_month_bounds(now_dt)
@@ -1552,26 +1575,39 @@ def compute_stats_bundle(conn, trading_day, now_dt=None):
_p, _t, td = tr
return td and m_start <= td <= m_end
day_trades = [tr for tr in pnls if tr[2] == trading_day]
week_trades = [tr for tr in pnls if in_week(tr)]
month_trades = [tr for tr in pnls if in_month(tr)]
day_range = f"北京时间交易日 {trading_day}"
week_range = f"{w_start} ~ {w_end}(北京日期,近7天窗口)"
month_range = f"{m_start} ~ {m_end}(北京时间自然月)"
dm = _compute_period_metrics(day_trades)
wm = _compute_period_metrics(week_trades)
mm = _compute_period_metrics(month_trades)
dm["opens_count"] = _count_opens_between(conn, trading_day, trading_day)
wm["opens_count"] = _count_opens_between(conn, w_start, w_end)
mm["opens_count"] = _count_opens_between(conn, m_start, m_end)
dm["range_label"] = f"北京时间交易日 {trading_day}"
wm["range_label"] = f"{w_start} ~ {w_end}(北京日期,近7天窗口)"
mm["range_label"] = f"{m_start} ~ {m_end}(北京时间自然月)"
day_o = [tr for tr in pnls_order if tr[2] == trading_day]
day_t = [tr for tr in pnls_trend if tr[2] == trading_day]
dm_o = _compute_period_metrics(day_o)
dm_t = _compute_period_metrics(day_t)
dm_o["opens_count"] = _count_opens_between(conn, trading_day, trading_day)
dm_t["opens_count"] = _count_trend_plan_opens_between(conn, trading_day, trading_day)
week_o = [tr for tr in pnls_order if in_week(tr)]
week_t = [tr for tr in pnls_trend if in_week(tr)]
wm_o = _compute_period_metrics(week_o)
wm_t = _compute_period_metrics(week_t)
wm_o["opens_count"] = _count_opens_between(conn, w_start, w_end)
wm_t["opens_count"] = _count_trend_plan_opens_between(conn, w_start, w_end)
month_o = [tr for tr in pnls_order if in_month(tr)]
month_t = [tr for tr in pnls_trend if in_month(tr)]
mm_o = _compute_period_metrics(month_o)
mm_t = _compute_period_metrics(month_t)
mm_o["opens_count"] = _count_opens_between(conn, m_start, m_end)
mm_t["opens_count"] = _count_trend_plan_opens_between(conn, m_start, m_end)
return {
"trading_day": trading_day,
"total_opens_all": total_opens_all,
"day": dm,
"week": wm,
"month": mm,
"total_opens_order": total_opens_order,
"total_opens_trend": total_opens_trend,
"total_opens_all": int(total_opens_order) + int(total_opens_trend),
"day": {"range_label": day_range, "order": dm_o, "trend": dm_t},
"week": {"range_label": week_range, "order": wm_o, "trend": wm_t},
"month": {"range_label": month_range, "order": mm_o, "trend": mm_t},
}
+193 -44
View File
@@ -58,6 +58,7 @@
.direction-short{background:#331e24;color:#ff6666}
.pnl-profit{color:#4cd97f;font-weight:600}
.pnl-loss{color:#ff6666;font-weight:600}
.pnl-neutral{color:#cfd3ef;font-weight:600}
.flash{padding:10px;background:#1e2533;color:#4cc2ff;border-radius:10px;margin-bottom:12px;text-align:center;border:1px solid #304164}
.ai-result{background:#1a1a29;border:1px solid #2e2e45;border-radius:8px;padding:10px;white-space:pre-wrap;max-height:220px;overflow:auto;font-size:.84rem;line-height:1.45;margin-top:8px}
.price-up{color:#4cd97f}
@@ -81,21 +82,48 @@
.detail-modal .panel-body{white-space:pre-wrap;line-height:1.5;font-size:.86rem;color:#e5e9ff}
.detail-modal .panel-image{margin-top:10px;max-width:min(100%,680px);border-radius:8px;cursor:pointer;border:1px solid #2a3150}
.table-wrap{overflow-x:auto}
.order-card{grid-column:1/-1}
.trend-card{grid-column:1/-1}
.trade-dashboard{grid-column:1/-1;display:flex;flex-direction:column;gap:14px}
.trade-panels-row{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;align-items:stretch}
.trade-panels-row > .card{min-height:0;height:100%;display:flex;flex-direction:column;box-sizing:border-box}
.trade-panels-row > .trend-card{gap:12px}
.trade-panels-row > .order-card .order-live-positions{margin-top:auto;flex:0 1 auto;min-height:0}
.order-live-positions{display:flex;flex-direction:column;gap:10px;margin-top:12px;padding-top:14px;border-top:1px solid #2a3150;max-height:240px;overflow:auto}
.order-live-positions .running-plans-stack{margin-top:0}
.trade-panels-row > .trend-card .trend-running-plans{margin-top:auto}
.trend-running-plans{padding-top:14px;border-top:1px solid #2a3150}
.running-plans-stack{display:flex;flex-direction:column;gap:12px;margin-top:10px}
.plan-position-card{background:#141a2a;border:1px solid #2a3150;border-radius:12px;padding:12px 14px}
.plan-card-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;flex-wrap:wrap;margin-bottom:8px}
.plan-card-title{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:1rem;font-weight:700;color:#f0f2ff}
.plan-card-meta{font-size:.76rem;color:#8892b0;line-height:1.55;margin-bottom:10px}
.plan-card-meta .accent{color:#6ab8ff}
.plan-card-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px 14px;margin-bottom:10px}
@media (max-width:720px){
.plan-card-grid{grid-template-columns:1fr}
}
.plan-cell{display:flex;flex-direction:column;gap:3px}
.plan-cell .lbl{font-size:.72rem;color:#8b95b8}
.plan-cell .val.pnl-profit,.plan-cell .val .pnl-profit{color:#4cd97f}
.plan-cell .val.pnl-loss,.plan-cell .val .pnl-loss{color:#ff6666}
.plan-cell .val.pnl-neutral,.plan-cell .val .pnl-neutral{color:#cfd3ef}
.btn-close-plan{padding:7px 14px;background:#5c1e2a;color:#ffb4b4;border:none;border-radius:8px;cursor:pointer;font-size:.82rem;font-weight:600;text-decoration:none;white-space:nowrap}
.btn-close-plan:hover{filter:brightness(1.08)}
.records-card{grid-column:1/-1}
.review-card{grid-column:1/-1}
@media (min-width: 1900px){
.container{max-width:2100px}
.order-card .list{max-height:420px}
.order-card .order-live-positions{max-height:420px}
.records-card .table-wrap{max-height:620px;overflow:auto}
}
@media (max-width: 1400px){
.container{width:min(99vw,1600px)}
.grid{grid-template-columns:1fr}
.order-card,.records-card,.review-card{grid-column:auto}
.trade-dashboard,.records-card,.review-card{grid-column:auto}
.panel-list{grid-template-columns:1fr}
}
@media (max-width:1200px){
.trade-panels-row{grid-template-columns:1fr}
}
@media (max-width: 960px){
body{padding:10px}
.form-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
@@ -119,14 +147,16 @@
.stats-period-block:last-child{border-bottom:none;margin-bottom:0;padding-bottom:0}
.stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px}
.stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4}
.stats-split-row{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start}
.stats-split-col{min-width:0;background:#101522;border:1px solid #252a45;border-radius:10px;padding:10px 12px}
.stats-split-head{font-size:.88rem;font-weight:600;color:#b8c4ff;margin-bottom:8px;padding-bottom:6px;border-bottom:1px solid #2a3150}
@media (max-width:900px){
.stats-split-row{grid-template-columns:1fr}
}
</style>
</head>
<body>
{% macro period_stats(title, s) %}
<div class="stats-period-block">
<h3>{{ title }}</h3>
<div class="sub">{{ s.range_label }}</div>
<div class="stats-detail">
{% macro period_metrics_cells(s) %}
<div class="stat-item"><div class="label">开单次数</div><div class="value">{{ s.opens_count }}</div></div>
<div class="stat-item"><div class="label">平仓笔数</div><div class="value">{{ s.closed_count }}</div></div>
<div class="stat-item"><div class="label">胜率</div><div class="value">{% if s.win_rate_pct is not none %}{{ s.win_rate_pct }}%{% else %}-{% endif %}</div></div>
@@ -138,6 +168,20 @@
<div class="stat-item"><div class="label">当前连续亏损笔数</div><div class="value">{{ s.consecutive_losses }}</div></div>
<div class="stat-item"><div class="label">最长连续亏损(交易日)</div><div class="value">{{ s.max_loss_streak_days }} 天</div></div>
<div class="stat-item"><div class="label">期内最大亏损日</div><div class="value">{% if s.worst_day %}{{ s.worst_day }}{{ money_fmt(s.worst_day_pnl) }}U{% else %}-{% endif %}</div></div>
{% endmacro %}
{% macro period_stats_dual(title, pair) %}
<div class="stats-period-block">
<h3>{{ title }}</h3>
<div class="sub">{{ pair.range_label }}</div>
<div class="stats-split-row">
<div class="stats-split-col">
<div class="stats-split-head">机器人下单监控</div>
<div class="stats-detail">{{ period_metrics_cells(pair.order) }}</div>
</div>
<div class="stats-split-col">
<div class="stats-split-head">趋势回调策略</div>
<div class="stats-detail">{{ period_metrics_cells(pair.trend) }}</div>
</div>
</div>
</div>
{% endmacro %}
@@ -171,6 +215,8 @@
<div class="grid">
{% if page == 'trade' %}
<div class="trade-dashboard">
<div class="trade-panels-row">
<div class="card order-card">
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap;margin-bottom:8px">
<h2 style="margin-bottom:0">机器人下单监控(单仓)</h2>
@@ -232,24 +278,60 @@
<input id="order-tp-pct" name="tp_pct" type="number" min="0.01" step="0.01" placeholder="止盈%" style="display:none">
<button type="submit">开仓(以损定仓)</button>
</form>
<div class="list">
<div class="order-live-positions">
<h3 style="margin:0 0 2px;font-size:.95rem;color:#b8c4ff">实时持仓</h3>
<div class="running-plans-stack">
{% for o in order %}
<div class="list-item">
<div><strong>{{ o.symbol }}</strong> | <span class="badge direction">{{ '做多' if o.direction == 'long' else '做空' }}</span></div>
<div>
风格:{{ o.trade_style or 'trend' }} | 风险:{{ o.risk_percent or '-' }}%≈{{ money_fmt(o.risk_amount) }}U
| {% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %}
<br>
成交:{{ price_fmt(o.symbol, o.trigger_price) }} 止损:{{ price_fmt(o.symbol, o.stop_loss) }} 止盈:{{ price_fmt(o.symbol, o.take_profit) }}
| 盈亏比:<span id="order-rr-{{ o.id }}">{% if o.rr_ratio is not none %}1:{{ '%.2f'|format(o.rr_ratio) }}{% else %}-{% endif %}</span>
| 现价:<span id="order-price-{{ o.id }}">-</span>
| 浮盈亏:<span id="order-pnl-{{ o.id }}">-</span>
| 计划基数:{{ money_fmt(o.margin_capital) }}U | 所保证金:<span id="order-ex-margin-{{ o.id }}">-</span>
| 杠杆:{{ o.leverage }}x | 仓位占比:{{ o.position_ratio }}%
{% set osym = o.exchange_symbol or o.symbol %}
<div class="plan-position-card">
<div class="plan-card-head">
<div class="plan-card-title">
<span>{{ osym }}</span>
<span class="badge {{ 'direction-long' if o.direction == 'long' else 'direction-short' }}">{{ '做多' if o.direction == 'long' else '做空' }}</span>
</div>
<a href="/del_order/{{ o.id }}" class="btn-close-plan" onclick="return confirm('删除会触发手动平仓,继续?')">平仓</a>
</div>
<div class="plan-card-meta">
来源: 下单监控 风格: {{ o.trade_style or 'trend' }} 风险: {% if o.risk_percent is not none %}{{ o.risk_percent }}%{% else %}—{% endif %}≈{{ money_fmt(o.risk_amount) }}U
{% if o.breakeven_enabled %}<span class="accent">移动保本: 开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(osym, o.breakeven_price) }}</span>{% else %}移动保本: 关{% endif %}
</div>
<div class="plan-card-grid">
<div class="plan-cell">
<span class="lbl">成交价</span>
<span class="val">{{ price_fmt(osym, o.trigger_price) }}</span>
</div>
<div class="plan-cell">
<span class="lbl">止损</span>
<span class="val">{{ price_fmt(osym, o.stop_loss) }}</span>
</div>
<div class="plan-cell">
<span class="lbl">止盈</span>
<span class="val">{{ price_fmt(osym, o.take_profit) }}</span>
</div>
<div class="plan-cell">
<span class="lbl">盈亏比</span>
<span class="val"><span id="order-rr-{{ o.id }}">{% if o.rr_ratio is not none %}{{ '%.2f'|format(o.rr_ratio) }}:1{% else %}—{% endif %}</span></span>
</div>
<div class="plan-cell">
<span class="lbl">标记价</span>
<span class="val"><span id="order-price-{{ o.id }}">-</span></span>
</div>
<div class="plan-cell">
<span class="lbl">浮盈亏</span>
<span class="val"><span id="order-pnl-{{ o.id }}">-</span></span>
</div>
</div>
<div class="plan-card-meta" style="margin-bottom:0">
保证金: <span id="order-ex-margin-{{ o.id }}">-</span>
计划基数: {{ money_fmt(o.margin_capital) }}U
杠杆: {{ o.leverage }}x
仓位占比: {{ o.position_ratio }}%
</div>
<a href="/del_order/{{ o.id }}" class="btn-del" onclick="return confirm('删除会触发手动平仓,继续?')">平仓</a>
</div>
{% else %}
<div class="plan-position-card" style="color:#8892b0;text-align:center;padding:16px">暂无机器人持仓</div>
{% endfor %}
</div>
</div>
</div>
@@ -326,23 +408,87 @@
<div class="rule-tip" style="margin-top:12px;color:#ff8f8f">该预览已过期(超过 {{ trend_pullback_preview_ttl }} 秒),请重新点击「生成预览」。</div>
{% endif %}
<h3 style="margin-top:16px;font-size:.95rem;color:#b8c4ff">运行中的计划</h3>
<div class="list" style="margin-top:8px">
<div class="trend-running-plans">
<h3 style="margin:0 0 10px;font-size:.95rem;color:#b8c4ff">运行中的计划</h3>
<div class="running-plans-stack">
{% for t in trend_plans %}
<div class="list-item">
<div><strong>#{{ t.id }} {{ t.symbol }}</strong> | {{ '做多' if t.direction == 'long' else '做空' }} | {{ t.leverage }}x</div>
<div style="font-size:.82rem;color:#cfd3ef">
可用快照:{{ money_fmt(t.snapshot_available_usdt) }}U | 计划保证金≈{{ money_fmt(t.plan_margin_capital) }}U | 总张≈{{ amt_fmt(t.symbol, t.target_order_amount) }} 首仓{{ amt_fmt(t.symbol, t.first_order_amount) }} 补仓档{{ t.dca_legs }}
<br>止损:{{ price_fmt(t.symbol, t.stop_loss) }} 补仓上沿:{{ price_fmt(t.symbol, t.add_upper) }} 止盈:{{ price_fmt(t.symbol, t.take_profit) }}
<br>均价:{{ price_fmt(t.symbol, t.avg_entry_price) }} 已补仓:{{ t.legs_done }}/{{ t.dca_legs }}
<br>浮盈亏(交易所): {% if t.floating_pnl is not none %}<span class="{{ 'pnl-profit' if t.floating_pnl > 0 else ('pnl-loss' if t.floating_pnl < 0 else '') }}">{{ money_fmt(t.floating_pnl) }} U</span>{% else %}—{% endif %}{% if t.floating_mark is not none %} 标记价: {{ price_fmt(t.symbol, t.floating_mark) }}{% endif %}
{% set sym = t.exchange_symbol or t.symbol %}
{% set calc = namespace(rr=None, pnlpct=None) %}
{% if t.avg_entry_price is not none and t.stop_loss is not none and t.take_profit is not none %}
{% set e = t.avg_entry_price|float %}
{% set sl = t.stop_loss|float %}
{% set tp = t.take_profit|float %}
{% if t.direction == 'long' %}
{% set risk = e - sl %}
{% set reward = tp - e %}
{% else %}
{% set risk = sl - e %}
{% set reward = e - tp %}
{% endif %}
{% if risk > 0 %}
{% set calc.rr = reward / risk %}
{% endif %}
{% endif %}
{% if t.floating_pnl is not none and t.plan_margin_capital is not none and t.plan_margin_capital|float > 0 %}
{% set calc.pnlpct = (t.floating_pnl|float) / (t.plan_margin_capital|float) * 100 %}
{% endif %}
<div class="plan-position-card">
<div class="plan-card-head">
<div class="plan-card-title">
<span>#{{ t.id }} {{ sym }}</span>
<span class="badge {{ 'direction-long' if t.direction == 'long' else 'direction-short' }}">{{ '做多' if t.direction == 'long' else '做空' }}</span>
</div>
<a href="/stop_trend_pullback/{{ t.id }}" class="btn-close-plan" onclick="return confirm('结束计划:市价平仓并撤掉该合约全部挂单,确定?')">结束计划</a>
</div>
<div class="plan-card-meta">
来源: 趋势回调计划 风险: {% if t.risk_percent is not none %}{{ t.risk_percent }}%{% else %}—{% endif %}
<span class="accent">补仓上沿 {{ price_fmt(sym, t.add_upper) }}</span>
| 已补仓 <strong>{{ t.legs_done }}/{{ t.dca_legs }}</strong>
</div>
<div class="plan-card-grid">
<div class="plan-cell">
<span class="lbl">均价</span>
<span class="val">{% if t.avg_entry_price is not none %}{{ price_fmt(sym, t.avg_entry_price) }}{% else %}—{% endif %}</span>
</div>
<div class="plan-cell">
<span class="lbl">止损</span>
<span class="val">{{ price_fmt(sym, t.stop_loss) }}</span>
</div>
<div class="plan-cell">
<span class="lbl">止盈</span>
<span class="val">{{ price_fmt(sym, t.take_profit) }}</span>
</div>
<div class="plan-cell">
<span class="lbl">盈亏比</span>
<span class="val">{% if calc.rr is not none %}{{ '%.2f'|format(calc.rr) }}:1{% else %}—{% endif %}</span>
</div>
<div class="plan-cell">
<span class="lbl">标记价</span>
<span class="val">{% if t.floating_mark is not none %}{{ price_fmt(sym, t.floating_mark) }}{% else %}—{% endif %}</span>
</div>
<div class="plan-cell">
<span class="lbl">浮盈亏</span>
<span class="val {% if t.floating_pnl is not none %}{% if t.floating_pnl > 0 %}pnl-profit{% elif t.floating_pnl < 0 %}pnl-loss{% else %}pnl-neutral{% endif %}{% endif %}">
{% if t.floating_pnl is not none %}
{{ money_fmt(t.floating_pnl) }}U{% if calc.pnlpct is not none %} ({{ '%+.2f'|format(calc.pnlpct) }}%){% endif %}
{% else %}—{% endif %}
</span>
</div>
</div>
<div class="plan-card-meta" style="margin-bottom:0">
快照可用: {% if t.snapshot_available_usdt is not none %}{{ money_fmt(t.snapshot_available_usdt) }}U{% else %}—{% endif %}
计划保证金≈{% if t.plan_margin_capital is not none %}{{ money_fmt(t.plan_margin_capital) }}U{% else %}—{% endif %}
总张≈{{ amt_fmt(sym, t.target_order_amount) }}(首{{ amt_fmt(sym, t.first_order_amount) }} + 补{{ amt_fmt(sym, t.remainder_total) }}
杠杆: {{ t.leverage }}x
</div>
<a href="/stop_trend_pullback/{{ t.id }}" class="btn-del" onclick="return confirm('结束计划:市价平仓并撤掉该合约全部挂单,确定?')">结束计划</a>
</div>
{% else %}
<div class="list-item" style="color:#8892b0">暂无运行中的趋势回调计划</div>
<div class="plan-position-card" style="color:#8892b0;text-align:center;padding:16px">暂无运行中的趋势回调计划</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
{% endif %}
@@ -477,12 +623,14 @@
<div class="stat-item"><div class="label">持仓占用导致错过(累计)</div><div class="value">{{ occupied_miss_total }}</div></div>
</div>
<div class="sub" style="margin-bottom:12px;color:#8892b0;font-size:.82rem">
已平仓「机器人下单 / 趋势回调」按平仓时间归入<strong>北京时间</strong>下的交易日;胜率按盈笔数/(盈+亏)。历史总开仓(累计):
<strong style="color:#cfd3ef">{{ stats_bundle.total_opens_all }}</strong>
已平仓记录按平仓时间归入<strong>北京时间</strong>交易日;胜率按盈笔数/(盈+亏)。各策略分列统计。<br>
历史总开仓(累计):下单监控 <strong style="color:#cfd3ef">{{ stats_bundle.total_opens_order }}</strong>
| 趋势回调 <strong style="color:#cfd3ef">{{ stats_bundle.total_opens_trend }}</strong>
(合计 <strong style="color:#cfd3ef">{{ stats_bundle.total_opens_all }}</strong> 次)
</div>
{{ period_stats("日统计", stats_bundle.day) }}
{{ period_stats("周统计", stats_bundle.week) }}
{{ period_stats("月统计", stats_bundle.month) }}
{{ period_stats_dual("日统计", stats_bundle.day) }}
{{ period_stats_dual("周统计", stats_bundle.week) }}
{{ period_stats_dual("月统计", stats_bundle.month) }}
</div>
</div>
{% endif %}
@@ -1163,14 +1311,15 @@ function refreshPriceSnapshot(){
const pnlEl = document.getElementById(`order-pnl-${o.id}`);
if(pnlEl){
pnlEl.innerText = `${formatSigned(o.float_pnl, 2)}U (${formatSigned(o.float_pct, 2)}%)`;
pnlEl.classList.remove("price-up","price-down","price-flat");
if(Number(o.float_pnl) > 0) pnlEl.classList.add("price-up");
else if(Number(o.float_pnl) < 0) pnlEl.classList.add("price-down");
else pnlEl.classList.add("price-flat");
pnlEl.classList.remove("pnl-profit","pnl-loss","pnl-neutral");
const fp = Number(o.float_pnl);
if(fp > 0) pnlEl.classList.add("pnl-profit");
else if(fp < 0) pnlEl.classList.add("pnl-loss");
else pnlEl.classList.add("pnl-neutral");
}
const rrEl = document.getElementById(`order-rr-${o.id}`);
if(rrEl){
rrEl.innerText = (typeof o.rr_ratio !== "undefined" && o.rr_ratio !== null) ? `1:${Number(o.rr_ratio).toFixed(2)}` : "-";
rrEl.innerText = (typeof o.rr_ratio !== "undefined" && o.rr_ratio !== null) ? `${Number(o.rr_ratio).toFixed(2)}:1` : "-";
}
});
}).catch(()=>{});