e2bf58cfd3
保本后结束趋势计划,持仓转入下单监控(备注趋势回调),交易所同时挂保本止损与计划止盈;中控或交易所平仓均经下单监控写入交易记录(trend_plan_id、开仓类型),四所共用 strategy_trend_register。 Co-authored-by: Cursor <cursoragent@cursor.com>
180 lines
12 KiB
HTML
180 lines
12 KiB
HTML
{% 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>
|
||
确认执行时若当前可用余额与预览快照相对偏差 > <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.stop_loss) }} | {{ 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) }} | 风险比例 {{ trend_preview.risk_percent }}%
|
||
</div>
|
||
<div class="table-wrap" style="margin-bottom:10px">
|
||
<table>
|
||
<tr><th>#</th><th>补仓触发价</th><th>该档张数</th></tr>
|
||
{% for row in trend_preview_levels %}
|
||
<tr><td>{{ row.i }}</td><td>{{ price_fmt(trend_preview.symbol, row.price) }}</td><td>{{ amt_disp(trend_preview.symbol, row.contracts) }}</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">
|
||
<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>
|
||
<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>
|