bot统计
This commit is contained in:
@@ -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},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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(()=>{});
|
||||
|
||||
Reference in New Issue
Block a user