顺势加仓 v2:程序监控滚仓、文档页与平仓同步

重写滚仓计仓与四种加仓方式(市价/斐波/突破),程序盯 mark 触价成交;风险读监控单;pending 可删不可改;手动平仓同步结束滚仓。新增 /strategy/roll/docs 说明页与顺势加仓滚仓说明.md。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-26 22:03:23 +08:00
parent 4aebe70611
commit d467760d5c
17 changed files with 1506 additions and 630 deletions
+3 -108
View File
@@ -5,119 +5,14 @@
<script src="/static/instance_theme.js?v=4"></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>顺势加仓 · {{ exchange_display }}</title>
<style>
body{font-family:system-ui,sans-serif;background:#0f1117;color:#e6e8ef;margin:0;padding:16px}
.container{max-width:1100px;margin:0 auto}
.top-nav{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px}
.top-nav a{padding:6px 10px;border:1px solid #304164;border-radius:8px;background:#151a2a;color:#8fc8ff;text-decoration:none}
.top-nav a.active{background:#2a3f6c;color:#dbe4ff}
.card{background:#151a2a;border:1px solid #2a3150;border-radius:10px;padding:14px;margin-bottom:12px}
.form-row{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:8px}
.form-row input,.form-row select{padding:8px 10px;border-radius:6px;border:1px solid #3a4a66;background:#0f1420;color:#eee;min-width:120px}
.form-row button{padding:8px 14px;border-radius:8px;border:none;background:#2d6a4f;color:#fff;cursor:pointer}
.rule-tip{font-size:.8rem;color:#8892b0;line-height:1.5;margin-bottom:10px}
.flash{background:#1f2a44;border:1px solid #3a5a8a;padding:10px;border-radius:8px;margin-bottom:12px}
table{width:100%;border-collapse:collapse;font-size:.82rem}
th,td{border-bottom:1px solid #2a3150;padding:6px 8px;text-align:left}
</style>
<link rel="stylesheet" href="/static/instance_theme.css?v=4">
<meta name="theme-color" content="#0b0d14">
</head>
<body>
<div class="container">
<div class="header-row" style="justify-content:space-between;margin-bottom:8px">
<h1 style="margin:0">策略交易 · 顺势加仓 <span style="font-size:.85rem;color:#8fc8ff">{{ exchange_display }}</span></h1>
<div class="theme-toggle instance-theme-toggle" role="group" aria-label="界面主题">
<button type="button" class="theme-toggle-btn is-active" data-theme-value="dark" aria-pressed="true" title="暗色主题">
<svg class="theme-icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M12.1 3a9 9 0 1 0 8.9 11 6.5 6.5 0 1 1-8.9-11z"/></svg>
</button>
<button type="button" class="theme-toggle-btn" data-theme-value="light" aria-pressed="false" title="亮色主题">
<svg class="theme-icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></svg>
</button>
</div>
</div>
<div class="top-nav">
<a href="/trade">实盘下单</a>
<a href="/strategy/trend">趋势回调</a>
<a href="/strategy/roll" class="active">顺势加仓</a>
<a href="/records">交易复盘</a>
</div>
<div class="container" style="max-width:1100px;margin:0 auto;padding:16px">
{% with messages = get_flashed_messages() %}{% if messages %}<div class="flash">{{ messages[0] }}</div>{% endif %}{% endwith %}
<div class="card">
<h2 style="margin:0 0 8px">规则说明</h2>
<div class="rule-tip">
<strong>仅人工加仓</strong>,程序不会自动触发。须先在「实盘下单」有同向持仓。<br>
做多最多滚仓 <strong>3</strong> 次;止盈<strong>锁定首仓</strong>不变;每次填写<strong>止损偏移%</strong>(相对合并均价,默认 1%),总风险%按「合并持仓打到新止损≈账户风险」反推张数。<br>
斐波限价:上沿 H、下沿 L 仅用于算 0.618/0.786 加仓价(多:下沿=止损侧;空:上沿=止损侧)。<br>
{% if trend_active %}<span style="color:#ff8f8f">当前有运行中的趋势回调计划,请先结束后再滚仓。</span>{% endif %}
</div>
<form action="{{ url_for('strategy_roll_execute') }}" method="post" class="form-row">
<select name="symbol" required>
<option value="">选择持仓币种</option>
{% for o in monitors %}
<option value="{{ o.symbol }}">{{ o.symbol }} {{ '多' if o.direction=='long' else '空' }} #{{ o.id }}</option>
{% endfor %}
</select>
<select name="direction">
<option value="long">做多</option>
<option value="short">做空</option>
</select>
<select name="add_mode">
<option value="market">市价加仓</option>
<option value="fib_618">限价 斐波0.618</option>
<option value="fib_786">限价 斐波0.786</option>
</select>
<input name="fib_upper" step="any" placeholder="上沿 H">
<input name="fib_lower" step="any" placeholder="下沿 L">
<input name="stop_offset_pct" type="number" min="0.01" step="0.01" value="1" placeholder="止损偏移%(合并均价)" required>
<input name="risk_percent" type="number" min="0.1" step="0.1" value="{{ default_risk_percent }}" placeholder="总风险%">
<button type="submit" {% if trend_active %}disabled{% endif %} onclick="return confirm('确认按预览逻辑实盘加仓并更新止损?')">执行滚仓</button>
</form>
<p class="rule-tip">建议执行前用浏览器开发者工具 POST <code>/strategy/roll/preview</code> 查看 JSON 预览(或将加入页面内预览按钮)。</p>
</div>
<div class="card">
<h3>活跃滚仓组</h3>
<table>
<tr><th>ID</th><th>币种</th><th>方向</th><th>腿数</th><th>首仓TP</th><th>当前SL</th><th>当前均价</th><th>止盈盈利U</th></tr>
{% for g in roll_groups %}
<tr>
<td>{{ g.id }}</td>
<td>{{ g.symbol }}</td>
<td>{{ g.direction }}</td>
<td>{{ g.leg_count }}</td>
<td>{% if price_fmt %}{{ price_fmt(g.symbol, g.initial_take_profit) }}{% else %}{{ g.initial_take_profit }}{% endif %}</td>
<td>{% if price_fmt %}{{ price_fmt(g.symbol, g.current_stop_loss) }}{% else %}{{ g.current_stop_loss }}{% endif %}</td>
<td>{% if g.avg_entry_display %}{{ g.avg_entry_display }}{% elif g.avg_entry is not none %}{{ g.avg_entry }}{% else %}—{% endif %}</td>
<td>{% if g.reward_at_tp_usdt is not none %}{{ '%.2f'|format(g.reward_at_tp_usdt) }}{% else %}—{% endif %}</td>
</tr>
{% else %}
<tr><td colspan="8" style="color:#8892b0">暂无</td></tr>
{% endfor %}
</table>
</div>
<div class="card">
<h3>最近滚仓腿</h3>
<table>
<tr><th>#</th><th></th><th>方式</th><th>张数</th><th>新SL</th><th>当前均价</th><th>止盈盈利U</th><th>状态</th></tr>
{% for leg in roll_legs %}
<tr>
<td>{{ leg.leg_index }}</td>
<td>{{ leg.roll_group_id }}</td>
<td>{{ leg.add_mode }}</td>
<td>{{ leg.amount }}</td>
<td>{{ leg.new_stop_loss }}</td>
<td>{% if leg.avg_entry_display %}{{ leg.avg_entry_display }}{% elif leg.avg_entry_after is not none %}{{ leg.avg_entry_after }}{% else %}—{% endif %}</td>
<td>{% if leg.reward_at_tp_usdt is not none %}{{ '%.2f'|format(leg.reward_at_tp_usdt) }}{% else %}—{% endif %}</td>
<td>{{ leg.status_label or leg.status }}</td>
</tr>
{% else %}
<tr><td colspan="8" style="color:#8892b0">暂无</td></tr>
{% endfor %}
</table>
</div>
{% include 'strategy_roll_panel.html' %}
<p class="rule-tip" style="margin-top:12px"><a href="/strategy/roll/docs" style="color:#8fc8ff">顺势加仓完整逻辑说明</a></p>
</div>
</body>
</html>
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="zh-CN" data-theme="dark">
<head>
<meta charset="UTF-8">
<script src="/static/instance_theme.js?v=4"></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>顺势加仓 · 详细说明 · {{ exchange_display }}</title>
<link rel="stylesheet" href="/static/instance_theme.css?v=4">
<meta name="theme-color" content="#0b0d14">
<style>
body{font-family:system-ui,sans-serif;background:#0f1117;color:#e6e8ef;margin:0;padding:16px}
.container{max-width:920px;margin:0 auto}
.doc-nav{margin-bottom:14px}
.doc-nav a{color:#8fc8ff;text-decoration:none}
.doc-body{background:#151a2a;border:1px solid #2a3150;border-radius:10px;padding:18px 20px;line-height:1.65;font-size:.92rem}
.doc-body h1{font-size:1.35rem;margin:0 0 12px;color:#f0f2ff}
.doc-body h2{font-size:1.08rem;margin:22px 0 10px;color:#b8c4ff;border-bottom:1px solid #2a3150;padding-bottom:6px}
.doc-body h3{font-size:.98rem;margin:16px 0 8px;color:#c9d4ff}
.doc-body p,.doc-body li{color:#dde2ff}
.doc-body ul,.doc-body ol{margin:8px 0 12px 1.25em}
.doc-body code{background:#252538;padding:1px 5px;border-radius:4px;font-size:.88em}
.doc-body pre{background:#0f1420;border:1px solid #2a3150;border-radius:8px;padding:12px;overflow:auto;font-size:.84rem;line-height:1.5}
.doc-body table{width:100%;border-collapse:collapse;margin:10px 0;font-size:.86rem}
.doc-body th,.doc-body td{border:1px solid #2a3150;padding:6px 8px;text-align:left}
.doc-body th{background:#1a2030;color:#b8c4ff}
.doc-body hr{border:none;border-top:1px solid #2a3150;margin:20px 0}
</style>
</head>
<body>
<div class="container">
<div class="doc-nav">
<a href="/strategy">← 返回策略交易</a>
&nbsp;·&nbsp;
<a href="/strategy/roll">顺势加仓</a>
</div>
<article class="doc-body">
{{ doc_html|safe }}
</article>
</div>
</body>
</html>
+52 -29
View File
@@ -1,41 +1,57 @@
<div class="strategy-panel-inner">
<div class="strategy-panel-inner" id="strategy-roll-panel">
<h2 style="margin:0 0 8px">顺势加仓</h2>
<details class="tip-collapse strategy-roll-rule-collapse">
<details class="tip-collapse strategy-roll-rule-collapse" open>
<summary class="tip-collapse-summary">顺势加仓规则说明{% if roll_trend_active %} · 当前有趋势回调计划{% endif %}</summary>
<div class="tip-collapse-body rule-tip">
<strong>仅人工加仓</strong>,程序不会自动触发。须先在「实盘下单」有同向持仓。<br>
做多最多滚仓 <strong>3</strong> 次;止盈<strong>锁定首仓</strong>不变;每次填写<strong>止损偏移%</strong>(相对合并均价,默认 1%),总风险%按「合并持仓打到新止损≈账户风险」反推张数<br>
斐波限价:上沿 H、下沿 L 仅用于算 0.618/0.786 加仓价(多:下沿=止损侧;空:上沿=止损侧)。<br>
<strong>仓位上限冻结时仍可顺势加仓</strong>(在已有同向监控持仓上操作,不占用新仓名额)<br>
<strong>仅人工提交</strong>须先在「实盘下单」有同向持仓。<strong>以损定仓</strong>模式可用。<br>
做多/做空各最多滚仓 <strong>3</strong>(仅计已成交腿);止盈<strong>锁定首仓</strong>不变。<br>
风险比例读取所选监控单,<strong>不可手改</strong>;打到新止损时合并持仓亏损 ≈ 1 个风险单位(当前基数 × 监控 risk%)。<br>
斐波/突破为<strong>程序监控</strong>(mark 价穿越触发),触价后市价加仓;同时仅允许 <strong>1</strong> 条监控中腿,提交后<strong>不可修改</strong>,可删除<br>
手动平仓后滚仓监控自动结束;<strong>已成交腿历史保留</strong>供复盘。<br>
<a href="/strategy/roll/docs" target="_blank" rel="noopener" style="color:#8fc8ff">→ 顺势加仓完整逻辑说明</a><br>
{% if roll_trend_active %}<span style="color:#ff8f8f">当前有运行中的趋势回调计划,请先结束后再滚仓。</span>{% endif %}
</div>
</details>
<form action="{{ url_for('strategy_roll_execute') }}" method="post" class="form-row">
<select name="symbol" required>
<div id="roll-risk-banner" class="rule-tip" style="margin-bottom:8px;color:#8fc8ff">
当前风险:请选择持仓币种
</div>
<form id="roll-form" action="{{ url_for('strategy_roll_execute') }}" method="post" class="form-row">
<select name="symbol" id="roll-symbol" required>
<option value="">选择持仓币种</option>
{% for o in roll_monitors %}
<option value="{{ o.symbol }}">{{ o.symbol }} {{ '多' if o.direction=='long' else '空' }} #{{ o.id }}</option>
<option value="{{ o.symbol }}"
data-direction="{{ o.direction }}"
data-monitor-id="{{ o.id }}"
data-risk-percent="{{ o.risk_percent or default_risk_percent }}">
{{ o.symbol }} {{ '多' if o.direction=='long' else '空' }} #{{ o.id }} · 风险{{ o.risk_percent or default_risk_percent }}%
</option>
{% endfor %}
</select>
<select name="direction">
<option value="long">做多</option>
<option value="short">做空</option>
</select>
<select name="add_mode">
<input type="hidden" name="direction" id="roll-direction" value="long">
<select name="add_mode" id="roll-add-mode">
<option value="market">市价加仓</option>
<option value="fib_618">限价 斐波0.618</option>
<option value="fib_786">限价 斐波0.786</option>
<option value="fib_618">斐波 0.618</option>
<option value="fib_786">斐波 0.786</option>
<option value="breakout">突破加仓</option>
</select>
<input name="fib_upper" step="any" placeholder="上沿 H">
<input name="fib_lower" step="any" placeholder="沿 L">
<input name="stop_offset_pct" type="number" min="0.01" step="0.01" value="1" placeholder="止损偏移%(合并均价)" required>
<input name="risk_percent" type="number" min="0.1" step="0.1" value="{{ default_risk_percent }}" placeholder="总风险%">
<button type="submit" {% if roll_trend_active %}disabled style="opacity:.5"{% endif %} onclick="return confirm('确认按预览逻辑实盘加仓并更新止损?')">执行滚仓</button>
<span class="roll-field roll-field-fib">
<input name="fib_upper" id="roll-fib-upper" step="any" placeholder="沿 H">
<input name="fib_lower" id="roll-fib-lower" step="any" placeholder="下沿 L">
</span>
<span class="roll-field roll-field-breakout">
<input name="breakthrough_price" id="roll-breakout" step="any" placeholder="突破价">
</span>
<input name="new_stop_loss" id="roll-stop-loss" type="number" min="0" step="any" placeholder="新止损价" required>
<button type="button" id="roll-preview-btn" {% if roll_trend_active %}disabled{% endif %}>预览</button>
<button type="submit" id="roll-submit-btn" {% if roll_trend_active %}disabled style="opacity:.5"{% endif %} disabled>执行滚仓</button>
</form>
<details class="tip-collapse strategy-roll-preview-tip">
<summary class="tip-collapse-summary">滚仓预览接口说明</summary>
<div class="tip-collapse-body rule-tip">执行前可用开发者工具 POST <code>/strategy/roll/preview</code> 查看 JSON 预览。</div>
</details>
<div id="roll-preview-box" class="rule-tip" style="display:none;margin:8px 0;padding:10px;border:1px solid #3a5a8a;border-radius:8px;background:#141a28">
<div id="roll-preview-text"></div>
<div id="roll-countdown" style="margin-top:6px;color:#ffb347;display:none"></div>
</div>
<h3 style="margin:14px 0 8px;font-size:.95rem;color:#b8c4ff">活跃滚仓组</h3>
<div class="table-wrap">
@@ -61,17 +77,23 @@
<h3 style="margin:14px 0 8px;font-size:.95rem;color:#b8c4ff">最近滚仓腿</h3>
<div class="table-wrap">
<table>
<tr><th>#</th><th></th><th>方式</th><th>张数</th><th>新SL</th><th>当前均价</th><th>止盈盈利U</th><th>状态</th></tr>
<tr><th>#</th><th></th><th>方式</th><th>张数</th><th>触发/限价</th><th>新SL</th><th>状态</th><th>操作</th></tr>
{% for leg in roll_legs %}
<tr>
<td>{{ leg.leg_index }}</td>
<td>{{ leg.roll_group_id }}</td>
<td>{{ leg.add_mode }}</td>
<td>{{ leg.amount }}</td>
<td>{% if leg.amount %}{{ leg.amount }}{% else %}—{% endif %}</td>
<td>{% if leg.limit_price %}{{ leg.limit_price }}{% elif leg.breakthrough_price %}{{ leg.breakthrough_price }}{% elif leg.fill_price %}{{ leg.fill_price }}{% else %}—{% endif %}</td>
<td>{{ leg.new_stop_loss }}</td>
<td>{% if leg.avg_entry_display %}{{ leg.avg_entry_display }}{% elif leg.avg_entry_after is not none %}{{ leg.avg_entry_after }}{% else %}—{% endif %}</td>
<td>{% if leg.reward_at_tp_usdt is not none %}{{ '%.2f'|format(leg.reward_at_tp_usdt) }}{% else %}—{% endif %}</td>
<td>{{ leg.status_label or leg.status }}</td>
<td>
{% if leg.status == 'pending' %}
<form action="{{ url_for('strategy_roll_cancel_leg', leg_id=leg.id) }}" method="post" style="margin:0" onsubmit="return confirm('确认删除本条滚仓监控?')">
<button type="submit" style="padding:2px 8px;font-size:.75rem">删除</button>
</form>
{% else %}—{% endif %}
</td>
</tr>
{% else %}
<tr><td colspan="8" style="color:#8892b0">暂无</td></tr>
@@ -79,3 +101,4 @@
</table>
</div>
</div>
<script src="/static/strategy_roll.js?v=1"></script>