Isolate CTP in worker process and improve strategy roll UX.

Split vn.py into qihuo-ctp worker with IPC client bridge, keep CTP connected during breaks with cached account fallback, speed up strategy page loads, and allow off-session breakout roll submissions.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-01 12:35:47 +08:00
parent 08d55411aa
commit 9cd81a3ea7
17 changed files with 2214 additions and 227 deletions
+10 -4
View File
@@ -136,7 +136,10 @@
<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>
<p class="hint" id="roll-exec-hint" hidden style="font-size:.75rem;margin-top:.45rem">市价加仓:须交易时段内确认,10 秒倒计时执行;突破加仓:任意时间可提交,开盘后再监控触价</p>
<p class="hint" id="roll-exec-hint" hidden style="font-size:.75rem;margin-top:.45rem">市价加仓:须交易时段内确认,10 秒倒计时执行;突破加仓:休盘也可提交,开盘后再监控触价</p>
{% if not trading_session %}
<p class="hint text-muted" id="roll-off-session-hint" style="font-size:.75rem;margin-top:.35rem">当前{{ session_clock.status_label or '休盘' }}:请选「突破加仓」填写突破价后预览并提交监控。</p>
{% endif %}
</form>
{% else %}
<p class="empty-hint">暂无可用持仓监控</p>
@@ -151,7 +154,7 @@
<div class="table-responsive">
<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>
<th>ID</th><th>品种</th><th>方向</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 %}
@@ -160,6 +163,8 @@
<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.first_lots if g.first_lots is not none else '—' }}</td>
<td>{{ g.total_lots if g.total_lots is not none else '—' }}</td>
<td>{{ g.initial_take_profit or '—' }}</td>
<td>{{ g.current_stop_loss or '—' }}</td>
<td>{{ g.avg_entry or '—' }}</td>
@@ -172,12 +177,12 @@
{% else %}
<p class="hint text-muted">暂无</p>
{% endif %}
<h3 style="font-size:.85rem;margin:1rem 0 .45rem">最近滚仓</h3>
<h3 style="font-size:.85rem;margin:1rem 0 .45rem">正在滚仓</h3>
{% if roll_legs %}
<div class="table-responsive">
<table class="strategy-preview-table">
<thead><tr>
<th>#</th><th></th><th>方式</th><th>手数</th><th>触发/限价</th><th>新SL</th><th>状态</th><th>操作</th>
<th>#</th><th></th><th>方式</th><th>手数</th><th>触发/限价</th><th>新SL</th><th>当前价</th><th>状态</th><th>操作</th>
</tr></thead>
<tbody>
{% for l in roll_legs %}
@@ -188,6 +193,7 @@
<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>{{ l.current_price if l.current_price is not none else '—' }}</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>
+36 -1
View File
@@ -1,6 +1,15 @@
{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #}
{% extends "base.html" %}
{% block title %}策略记录 - 国内期货 · 交易复盘系统{% endblock %}
{% block extra_css %}
<style>
.strategy-preview{background:var(--card-inner);border:1px solid var(--card-border);border-radius:8px;padding:.65rem .85rem;font-size:.78rem;line-height:1.5}
.strategy-preview-table{width:100%;border-collapse:collapse;font-size:.72rem;min-width:520px}
.strategy-preview-table th,.strategy-preview-table td{padding:.35rem .4rem;border-bottom:1px solid var(--table-border);text-align:right;white-space:nowrap}
.strategy-preview-table th:first-child,.strategy-preview-table td:first-child{text-align:left}
.strategy-preview-table thead th{color:var(--text-muted);font-weight:600;background:var(--list-item-bg)}
</style>
{% endblock %}
{% block content %}
<div class="split-grid">
<div class="card card-scroll">
@@ -15,7 +24,33 @@
<h2>顺势加仓</h2>
{% if roll_rows %}
<ul class="list">{% for r in roll_rows %}
<li class="list-item"><span>{{ r.symbol }} {{ r.result_label }} · {{ r.closed_at or r.created_at }}</span></li>
<li class="list-item">
<details style="width:100%">
<summary>{{ r.symbol }} {{ r.result_label }} · {{ r.closed_at or r.created_at }}</summary>
<div class="strategy-preview" style="margin-top:.55rem">
<p>方向:{{ '多' if r.direction == 'long' else '空' }}</p>
<p>首仓手数:{{ r.detail.first_lots or '—' }} · 加仓次数:{{ r.detail.add_count or 0 }} · 加仓手数:{{ r.detail.add_lots or 0 }} · 当前总手数:{{ r.detail.total_lots or '—' }}</p>
<p>最新止损:{{ r.detail.latest_stop_loss or '—' }} · 平仓价格:{{ r.detail.close_price or '—' }} · 盈利情况:{{ r.detail.pnl if r.detail.pnl is not none else '—' }}</p>
{% if r.detail.legs %}
<table class="strategy-preview-table">
<thead><tr><th></th><th>方式</th><th>手数</th><th>成交/触发价</th><th>新SL</th><th>时间</th></tr></thead>
<tbody>
{% for l in r.detail.legs %}
<tr>
<td>{{ l.leg_index or loop.index }}</td>
<td>{{ l.add_mode }}</td>
<td>{{ l.lots or '—' }}</td>
<td>{{ l.fill_price or l.breakthrough_price or l.limit_price or '—' }}</td>
<td>{{ l.new_stop_loss or '—' }}</td>
<td>{{ l.created_at or '—' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
</details>
</li>
{% endfor %}</ul>
{% else %}<p class="empty-hint">暂无记录</p>{% endif %}
</div>