Files
crypto_monitor/strategy_templates/strategy_trend_panel.html
T

208 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% set mf = money_fmt|default(funds_fmt) %}
{% macro amt_disp(sym, val) %}{% if amt_fmt is defined %}{{ amt_fmt(sym, val) }}{% else %}{{ val }}{% endif %}{% endmacro %}
<div class="strategy-panel-inner trend-card">
<h2 style="margin-bottom:8px">趋势回调</h2>
<div class="rule-tip">
<strong>生成预览</strong>:读取合约 USDT <strong>可用余额快照</strong>并计算计划(不下单)。预览有效期 <strong>{{ trend_pullback_preview_ttl }} 秒</strong><br>
<strong>确认执行</strong>:市价首仓 50% + 挂交易所止损;首仓后可<strong>手动保本</strong>(默认均价+{{ trend_manual_breakeven_offset_pct }}%);剩余 50% 在止损与补仓区间之间共 {{ trend_pullback_dca_legs }} 档(做多为<strong>上沿</strong>、做空为<strong>下沿</strong>;程序可能因最小张数自动减档)市价补仓;<strong>止盈由程序监控</strong><br>
确认执行时若当前可用余额与预览快照相对偏差 &gt; <strong>{{ trend_preview_max_drift_pct }}%</strong> 会拒绝并要求重新预览。
</div>
<form id="trend-pullback-form" action="{{ url_for('preview_trend_pullback') }}" method="post" class="form-row">
<input name="symbol" placeholder="BTC 或 ETH/USDT" required>
<select name="direction" id="trend-direction" required>
<option value="">方向</option>
<option value="long">做多</option>
<option value="short">做空</option>
</select>
<input name="leverage" type="number" min="1" step="1" placeholder="杠杆(必填)" required>
<input name="risk_percent" type="number" min="0.1" step="0.1" value="5" placeholder="风险%相对可用快照" title="默认5:最坏亏损约≤可用余额×5%">
<input name="sl" step="any" placeholder="止损价" required>
<input name="add_upper" id="trend-add-upper" step="any" placeholder="补仓上沿价" required>
<input name="take_profit" step="any" placeholder="止盈价(固定)" required>
<button type="submit" {% if can_trade_trend is defined %}{% if not can_trade_trend %}disabled style="opacity:.5;cursor:not-allowed"{% endif %}{% elif not can_trade %}disabled style="opacity:.5;cursor:not-allowed"{% endif %}>生成预览</button>
</form>
<script>
(function(){
const dirSel = document.getElementById("trend-direction");
const addInp = document.getElementById("trend-add-upper");
function syncAddUpperPlaceholder(){
if(!addInp || !dirSel) return;
const d = (dirSel.value || "long").toLowerCase();
addInp.placeholder = d === "short" ? "补仓下沿价" : "补仓上沿价";
}
if(dirSel){
dirSel.addEventListener("change", syncAddUpperPlaceholder);
syncAddUpperPlaceholder();
}
})();
</script>
{% if trend_preview %}
<div style="margin-top:14px;padding:12px;background:#141a2e;border:1px solid #2a3150;border-radius:8px">
<div style="display:flex;flex-wrap:wrap;justify-content:space-between;gap:8px;margin-bottom:8px">
<strong style="color:#dbe4ff">当前预览(剩余 <span id="trend-preview-ttl">{{ trend_pullback_preview_ttl }}</span>s</strong>
<span style="font-size:.8rem;color:#9aa" data-expires-ms="{{ preview_expires_ms }}">倒计时加载中…</span>
</div>
<div style="font-size:.82rem;color:#cfd3ef;line-height:1.55;margin-bottom:10px">
{{ trend_preview.symbol }} {{ '做多' if trend_preview.direction == 'long' else '做空' }} {{ trend_preview.leverage }}x
预览可用快照 <strong>{{ mf(trend_preview.snapshot_available_usdt) }}</strong> U 参考价 {{ price_fmt(trend_preview.symbol, trend_preview.live_price_ref) }}
计划保证金≈{{ mf(trend_preview.plan_margin_capital) }} U 总张≈{{ amt_disp(trend_preview.symbol, trend_preview.target_order_amount) }}(首仓 {{ amt_disp(trend_preview.symbol, trend_preview.first_order_amount) }} + 补仓 {{ amt_disp(trend_preview.symbol, trend_preview.remainder_total) }}<br>
止损价 {{ price_fmt(trend_preview.symbol, trend_preview.preview_unified_stop_loss or trend_preview.stop_loss) }} 止损金额 {% if trend_preview.preview_risk_amount_u is not none %}{{ mf(trend_preview.preview_risk_amount_u) }}U{% else %}—{% endif %}(快照×风险{{ trend_preview.risk_percent }}%)| {{ trend_add_zone_label(trend_preview.direction) }} {{ price_fmt(trend_preview.symbol, trend_preview.add_upper) }} 止盈价 {{ price_fmt(trend_preview.symbol, trend_preview.take_profit) }} 首仓盈亏比 {% if trend_preview.preview_target_rr is not none %}{{ '%.2f'|format(trend_preview.preview_target_rr) }}{% else %}—{% endif %}
</div>
<div class="table-wrap" style="margin-bottom:10px">
<table>
<tr><th>档位</th><th>触发/参考价</th><th>张数</th><th>加仓后均价</th><th>止盈盈利(U)</th><th>止损(U)</th><th>盈亏比</th></tr>
{% for row in trend_preview_levels %}
<tr>
<td>{{ row.label or row.i }}</td>
<td>{{ price_fmt(trend_preview.symbol, row.price) }}</td>
<td>{{ amt_disp(trend_preview.symbol, row.contracts) }}</td>
<td>{% if row.avg_entry is not none %}{{ price_fmt(trend_preview.symbol, row.avg_entry) }}{% else %}—{% endif %}</td>
<td>{% if row.profit_u is not none %}{{ mf(row.profit_u) }}{% else %}—{% endif %}</td>
<td>{% if row.risk_u is not none %}{{ mf(row.risk_u) }}{% else %}—{% endif %}</td>
<td>{% if row.rr is not none %}{{ '%.2f'|format(row.rr) }}{% else %}—{% endif %}</td>
</tr>
{% endfor %}
</table>
</div>
<div class="form-row" style="gap:10px;align-items:center">
<form action="{{ url_for('execute_trend_pullback') }}" method="post" style="display:inline">
<input type="hidden" name="preview_id" value="{{ trend_preview.id }}">
<button type="submit" onclick="return confirm('确认按预览参数实盘下单?')">确认执行(实盘)</button>
</form>
<form action="{{ url_for('cancel_trend_pullback_preview') }}" method="post" style="display:inline">
<input type="hidden" name="preview_id" value="{{ trend_preview.id }}">
<button type="submit" style="background:#2f2134;color:#ffb2b2">取消预览</button>
</form>
</div>
</div>
<script>
(function(){
const el = document.querySelector("[data-expires-ms]");
if(!el) return;
const exp = parseInt(el.getAttribute("data-expires-ms")||"0",10);
function tick(){
const left = Math.max(0, Math.floor((exp - Date.now()) / 1000));
el.innerText = left > 0 ? ("剩余 " + left + " 秒") : "已过期,请重新生成预览";
const span = document.getElementById("trend-preview-ttl");
if(span) span.innerText = String(left);
if(left <= 0) return;
setTimeout(tick, 1000);
}
tick();
})();
</script>
{% elif trend_preview_expired %}
<div class="rule-tip" style="margin-top:12px;color:#ff8f8f">该预览已过期(超过 {{ trend_pullback_preview_ttl }} 秒),请重新点击「生成预览」。</div>
{% endif %}
<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 %}
{% 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">{{ trend_add_zone_label(t.direction) }} {{ price_fmt(sym, t.add_upper) }}</span>
| 已补仓 <strong>{{ t.legs_done }}/{{ t.dca_legs }}</strong>
</div>
<div class="plan-card-grid plan-card-grid--metrics">
<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 %}
{{ mf(t.floating_pnl) }}U{% if calc.pnlpct is not none %} ({{ '%+.2f'|format(calc.pnlpct) }}%){% endif %}
{% else %}—{% endif %}
</span>
</div>
</div>
{% if t.dca_levels %}
<div class="plan-dca-block">
<div class="plan-dca-title">补仓计划明细</div>
<table class="plan-dca-table">
<tr><th>档位</th><th>触发价</th><th>张数</th><th>加仓后均价</th><th>止盈盈利(U)</th><th>止损(U)</th><th>盈亏比</th><th>状态</th></tr>
{% for lv in t.dca_levels %}
<tr>
<td>{{ lv.label }}</td>
<td>{% if lv.price is not none %}{{ price_fmt(sym, lv.price) }}{% else %}—{% endif %}</td>
<td>{% if lv.contracts is not none %}{{ amt_disp(sym, lv.contracts) }}{% else %}—{% endif %}</td>
<td>{% if lv.avg_entry is not none %}{{ price_fmt(sym, lv.avg_entry) }}{% else %}—{% endif %}</td>
<td>{% if lv.profit_u is not none %}{{ mf(lv.profit_u) }}{% else %}—{% endif %}</td>
<td>{% if lv.risk_u is not none %}{{ mf(lv.risk_u) }}{% else %}—{% endif %}</td>
<td>{% if lv.rr is not none %}{{ '%.2f'|format(lv.rr) }}{% else %}—{% endif %}</td>
<td class="{% if lv.status == 'done' %}st-done{% else %}st-pending{% endif %}">{{ lv.status_label }}</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %}
<div class="plan-card-meta" style="margin-top:8px">
<form action="{{ url_for('trend_pullback_breakeven', pid=t.id) }}" method="post" class="form-row" style="margin:0;align-items:center" onsubmit="return confirm('确认保本?将结束本趋势计划,持仓移交「下单监控」(备注趋势回调计划),并在交易所同时挂保本止损与计划止盈;后续平仓会写入交易记录。');">
<label style="font-size:.78rem;color:#cfd3ef;display:flex;align-items:center;gap:6px">
保本移交 偏移%
<input name="breakeven_offset_pct" type="number" min="0" step="0.01" value="{{ trend_manual_breakeven_offset_pct }}" style="width:72px;padding:4px 8px">
</label>
<button type="submit" style="padding:6px 12px;background:#1f4a3a;color:#8fc8ff">保本移交下单监控</button>
{% if t.breakeven_applied %}<span style="color:#6ab88a;font-size:.75rem">已保本 {{ (t.breakeven_applied_at or '')[:16] }}</span>{% endif %}
</form>
</div>
<div class="plan-card-meta" style="margin-bottom:0">
快照可用: {% if t.snapshot_available_usdt is not none %}{{ mf(t.snapshot_available_usdt) }}U{% else %}—{% endif %}
计划保证金≈{% if t.plan_margin_capital is not none %}{{ mf(t.plan_margin_capital) }}U{% else %}—{% endif %}
杠杆: {{ t.leverage }}x
</div>
</div>
{% else %}
<div class="plan-position-card" style="color:#8892b0;text-align:center;padding:16px">暂无运行中的趋势回调计划</div>
{% endfor %}
</div>
</div>
</div>