Add futures roll strategy with breakout monitoring and fixed-amount sizing.
Replace percent-based risk with system fixed amount, support market/breakout add modes only, allow pending submission outside trading hours, and fix short breakout geometry plus route registration. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+91
-21
@@ -32,9 +32,11 @@
|
||||
</ul>
|
||||
<p><strong>顺势加仓(滚仓)</strong></p>
|
||||
<ul>
|
||||
<li>在「下单监控」已有 <strong>active 持仓监控</strong> 上扩大仓位,统一抬高止损</li>
|
||||
<li>预览后执行;<strong>仓位上限冻结时仍可滚仓</strong>,但受滚仓保证金上限约束</li>
|
||||
<li>最多 3 腿;止盈锁定首仓逻辑</li>
|
||||
<li>仅<strong>固定金额(以损定仓)</strong>模式;<strong>移动保本</strong>持仓不可滚仓</li>
|
||||
<li>须「下单监控」有 active 持仓;与趋势回调互斥</li>
|
||||
<li>风险预算 = 系统设置<strong>固定金额</strong>;合并持仓打到新止损总亏损 ≤ B</li>
|
||||
<li>最多 3 腿(已成交);同一组最多 1 条 pending 监控腿</li>
|
||||
<li>止盈锁定首仓;突破由程序监控标记价穿越后市价加仓</li>
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
@@ -87,43 +89,111 @@
|
||||
<div class="card">
|
||||
<h2>顺势加仓(滚仓)</h2>
|
||||
<div class="card-body">
|
||||
<p class="hint" style="margin-bottom:.65rem">在已有持仓上扩大仓位,统一抬高止损;最多 3 腿,止盈锁定首仓。</p>
|
||||
{% if roll_groups %}
|
||||
{% for g in roll_groups %}
|
||||
<div class="strategy-active-roll">
|
||||
运行中 · 监控 #{{ g.order_monitor_id }} · {{ g.leg_count or 1 }} 腿 · 止损 {{ g.current_stop_loss }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<details class="module-rules" style="margin-bottom:.65rem">
|
||||
<summary>顺势加仓规则说明</summary>
|
||||
<div class="module-rules-body" style="font-size:.78rem;line-height:1.55">
|
||||
<ul>
|
||||
<li>手动提交;须实盘已有同向持仓与 active 监控单</li>
|
||||
<li>计仓模式须为<strong>固定金额</strong>;移动保本不可滚仓</li>
|
||||
<li>做多/做空各最多 3 次滚仓(仅计已成交);止盈为首仓 TP 不变</li>
|
||||
<li>风险预算 B = 系统设置中的<strong>固定金额</strong>;打到新止损 S 时合并持仓总亏损 ≤ B</li>
|
||||
<li>突破:标记价穿越触发价后按当时持仓重算手数再市价加仓</li>
|
||||
<li>pending 腿不可改,只能删除;手动平仓后滚仓组关闭</li>
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
{% if not roll_allowed %}
|
||||
<p class="hint text-muted">当前为「{{ sizing_mode_label }}」模式,滚仓不可用。请在系统设置切换为<strong>固定金额</strong>。</p>
|
||||
{% endif %}
|
||||
{% if monitors %}
|
||||
<p class="hint" id="roll-risk-hint">风险预算(固定金额):<strong id="roll-risk-budget">{{ '%.0f'|format(fixed_amount) }} 元</strong></p>
|
||||
<form id="roll-form" class="form-compact">
|
||||
<div class="field" style="margin-bottom:.5rem">
|
||||
<label class="text-label" style="font-size:.72rem">选择下单监控(开仓后生成)</label>
|
||||
<select name="monitor_id" required>
|
||||
<div class="form-line line-2">
|
||||
<select name="monitor_id" id="roll-monitor-select" required {% if not roll_allowed %}disabled{% endif %}>
|
||||
{% for m in monitors %}
|
||||
<option value="{{ m.id }}">{{ m.symbol_name or m.symbol }} {{ m.symbol }} · {{ '多' if m.direction == 'long' else '空' }} {{ m.lots }}手 · SL {{ m.stop_loss or '—' }}</option>
|
||||
<option value="{{ m.id }}"
|
||||
data-direction="{{ m.direction }}"
|
||||
data-eligible="{{ '1' if m.roll_eligible else '0' }}"
|
||||
data-block="{{ m.roll_block_reason or '' }}"
|
||||
{% if not m.roll_eligible %}disabled{% endif %}>
|
||||
{{ m.symbol_name or m.symbol }} {{ m.symbol }} · {{ '多' if m.direction == 'long' else '空' }} #{{ m.id }}
|
||||
· {{ m.lots }}手 · SL {{ m.stop_loss or '—' }}
|
||||
{% if not m.roll_eligible %} · {{ m.roll_block_reason }}{% endif %}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select name="add_mode" id="roll-add-mode" {% if not roll_allowed %}disabled{% endif %}>
|
||||
<option value="market">市价加仓</option>
|
||||
<option value="breakout">突破加仓</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-line line-2">
|
||||
<input name="new_stop_loss" type="number" step="any" placeholder="新统一止损" required>
|
||||
<input name="risk_percent" type="number" step="0.1" value="2" placeholder="总风险 %" title="总风险%">
|
||||
<input name="new_stop_loss" id="roll-new-sl" type="number" step="any" placeholder="新统一止损" required {% if not roll_allowed %}disabled{% endif %}>
|
||||
<input name="breakthrough_price" id="roll-break-price" type="number" step="any" placeholder="突破价" hidden>
|
||||
</div>
|
||||
<div class="form-line line-2">
|
||||
<input name="add_price" type="number" step="any" placeholder="加仓参考价(可选)">
|
||||
<button type="button" class="btn-primary" id="btn-roll-preview">预览滚仓</button>
|
||||
<button type="button" class="btn-primary" id="btn-roll-preview" {% if not roll_allowed %}disabled{% endif %}>预览</button>
|
||||
<button type="button" class="btn-primary" id="btn-roll-exec" hidden {% if not roll_allowed %}disabled{% endif %}>执行滚仓</button>
|
||||
</div>
|
||||
<div id="roll-preview" class="strategy-preview" hidden></div>
|
||||
<button type="button" class="btn-primary" id="btn-roll-exec" hidden style="margin-top:.65rem;width:100%">执行滚仓</button>
|
||||
<p class="hint" id="roll-exec-hint" hidden style="font-size:.75rem;margin-top:.45rem">市价加仓:须交易时段内确认,10 秒倒计时执行;突破加仓:任意时间可提交,开盘后再监控触价</p>
|
||||
</form>
|
||||
{% else %}
|
||||
<p class="empty-hint">暂无可用持仓监控</p>
|
||||
<ol class="strategy-steps">
|
||||
<li>打开 <a href="{{ url_for('positions') }}">持仓监控</a>,连接 CTP</li>
|
||||
<li>在「期货下单」填写品种、止损/止盈并<strong>开仓</strong></li>
|
||||
<li>开仓成功后会生成本页可选的监控记录,即可滚仓</li>
|
||||
<li>系统设置为<strong>固定金额</strong>,在「期货下单」开仓(勿开移动保本)</li>
|
||||
<li>开仓成功后生成本页可选监控,即可滚仓</li>
|
||||
</ol>
|
||||
{% endif %}
|
||||
<h3 style="font-size:.85rem;margin:1rem 0 .45rem">活跃滚仓组</h3>
|
||||
{% if roll_groups %}
|
||||
<table class="strategy-preview-table">
|
||||
<thead><tr>
|
||||
<th>ID</th><th>品种</th><th>方向</th><th>腿数</th><th>首仓TP</th><th>当前SL</th><th>当前均价</th><th>止盈盈利(元)</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{% for g in roll_groups %}
|
||||
<tr>
|
||||
<td>#{{ g.id }}</td>
|
||||
<td>{{ g.symbol_name or g.symbol }}</td>
|
||||
<td>{{ '多' if g.direction == 'long' else '空' }}</td>
|
||||
<td>{{ g.leg_count or 0 }}/3</td>
|
||||
<td>{{ g.initial_take_profit or '—' }}</td>
|
||||
<td>{{ g.current_stop_loss or '—' }}</td>
|
||||
<td>{{ g.avg_entry or '—' }}</td>
|
||||
<td>{{ g.reward_at_tp if g.reward_at_tp is not none else '—' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="hint text-muted">暂无</p>
|
||||
{% endif %}
|
||||
<h3 style="font-size:.85rem;margin:1rem 0 .45rem">最近滚仓腿</h3>
|
||||
{% if roll_legs %}
|
||||
<table class="strategy-preview-table">
|
||||
<thead><tr>
|
||||
<th>#</th><th>组</th><th>方式</th><th>手数</th><th>触发/限价</th><th>新SL</th><th>状态</th><th>操作</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{% for l in roll_legs %}
|
||||
<tr>
|
||||
<td>{{ l.id }}</td>
|
||||
<td>#{{ l.roll_group_id }}</td>
|
||||
<td>{{ add_mode_labels.get(l.add_mode, l.add_mode) }}</td>
|
||||
<td>{{ l.lots or '—' }}</td>
|
||||
<td>{{ l.breakthrough_price or l.limit_price or l.fill_price or '—' }}</td>
|
||||
<td>{{ l.new_stop_loss or '—' }}</td>
|
||||
<td title="{{ l.invalidated_reason or '' }}">{{ roll_leg_status_labels.get(l.status, l.status) }}{% if l.status == 'invalidated' and l.invalidated_reason %} · {{ l.invalidated_reason[:24] }}{% endif %}</td>
|
||||
<td>{% if l.status == 'pending' %}<button type="button" class="btn-link roll-cancel-leg" data-leg-id="{{ l.id }}">删除</button>{% else %}—{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="hint text-muted">暂无</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user