From 3c53b2063fa357192b54f69ad1c3f3dc33cfc083 Mon Sep 17 00:00:00 2001 From: dekun Date: Thu, 2 Jul 2026 21:55:50 +0800 Subject: [PATCH] Sync CTP monitors on strategy page load for roll trading. Revive and sync active monitors from live positions before listing roll candidates, show ineligible monitors with reasons, and validate eligibility on preview. Co-authored-by: Cursor --- modules/trading/install.py | 41 ++++++++++++++++++++++++++++- modules/web/static/js/strategy.js | 13 +++++++++ modules/web/templates/strategy.html | 8 ++++-- 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/modules/trading/install.py b/modules/trading/install.py index 2427538..c68af75 100644 --- a/modules/trading/install.py +++ b/modules/trading/install.py @@ -3190,6 +3190,44 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se return val return None + def _ensure_strategy_monitors(conn, mode: str) -> int: + """策略页加载前:恢复误关监控并同步柜台,与持仓页逻辑一致。""" + if not _cached_ctp_status(mode).get("connected"): + return 0 + capital = _capital(conn) + synced = 0 + seen: set[tuple[str, str]] = set() + positions = list(trading_state.get_positions() or []) + if not positions: + positions = list(_ctp_positions(mode, refresh_if_empty=False) or []) + for p in positions: + lots = int(p.get("lots") or 0) + if lots <= 0: + continue + ths = _ctp_pos_to_ths_code(p) or (p.get("symbol") or "") + direction = (p.get("direction") or "long").strip().lower() + key = (ths.lower(), direction) + if key in seen: + continue + seen.add(key) + mon = _find_or_revive_monitor(conn, ths, direction) + if not mon: + continue + mon = _restore_monitor_sl_tp_if_missing(conn, mon, ths, direction) or mon + _sync_monitor_from_ctp( + conn, + int(mon["id"]), + mon.get("symbol") or ths, + mon.get("direction") or direction, + mode, + ctp=p, + capital=capital, + ) + synced += 1 + if synced: + commit_retry(conn) + return synced + @app.route("/strategy") @login_required @_nav("strategy") @@ -3199,13 +3237,14 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se init_strategy_tables(conn) ensure_monitor_order_columns(conn) capital = _capital(conn) + mode = get_trading_mode(get_setting) + _ensure_strategy_monitors(conn, mode) active_trend = conn.execute( "SELECT * FROM trend_pullback_plans WHERE status='active' ORDER BY id DESC LIMIT 1" ).fetchone() monitors_raw = conn.execute( "SELECT * FROM trade_order_monitors WHERE status='active' ORDER BY id DESC" ).fetchall() - mode = get_trading_mode(get_setting) roll_ctx = _build_roll_context(conn) roll_groups = conn.execute( """SELECT g.*, m.symbol_name, m.lots AS mon_lots, m.entry_price AS mon_entry, diff --git a/modules/web/static/js/strategy.js b/modules/web/static/js/strategy.js index 5b82237..9fa5a9a 100644 --- a/modules/web/static/js/strategy.js +++ b/modules/web/static/js/strategy.js @@ -221,6 +221,19 @@ if (btnRollE) btnRollE.hidden = true; return; } + if (rollMonitorSel) { + var opt = rollMonitorSel.options[rollMonitorSel.selectedIndex]; + if (opt && opt.getAttribute('data-eligible') === '0') { + showPreview( + rollPrev, + opt.getAttribute('data-block') || '当前监控不可滚仓', + false, + false + ); + if (btnRollE) btnRollE.hidden = true; + return; + } + } btnRollP.disabled = true; jsonPost('/api/strategy/roll/preview', rollPayload).then(function (d) { if (!d.ok) { diff --git a/modules/web/templates/strategy.html b/modules/web/templates/strategy.html index bc885a4..476b9d5 100644 --- a/modules/web/templates/strategy.html +++ b/modules/web/templates/strategy.html @@ -106,6 +106,10 @@

当前为「{{ sizing_mode_label }}」模式,滚仓不可用。请在系统设置切换为固定金额

{% endif %} {% if monitors %} + {% set roll_eligible_count = monitors|selectattr('roll_eligible')|list|length %} + {% if roll_eligible_count == 0 %} +

检测到 {{ monitors|length }} 条持仓监控,但当前均不可滚仓(见下拉项说明)。

+ {% endif %}

风险预算(固定金额):{{ '%.0f'|format(fixed_amount) }} 元

@@ -114,8 +118,7 @@ {% else %}

暂无可用持仓监控

+

若「委托与持仓」中已有持仓,请先在持仓页连接 CTP 并刷新,再回到本页。

  1. 打开 持仓监控,连接 CTP
  2. 系统设置为固定金额,在「期货下单」开仓(勿开移动保本)