From 3c7da47bfb96cf161a95c47fc2b0092bf9ba178e Mon Sep 17 00:00:00 2001 From: dekun Date: Fri, 3 Jul 2026 22:20:53 +0800 Subject: [PATCH] Fix monitor closed too early on manual close: keep active until CTP flat and revive for roll. Co-authored-by: Cursor --- modules/trading/install.py | 49 +++++++++++++++++++++++++++++++--- modules/trading/sl_tp_guard.py | 1 + 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/modules/trading/install.py b/modules/trading/install.py index 80d4c05..8336f84 100644 --- a/modules/trading/install.py +++ b/modules/trading/install.py @@ -1220,9 +1220,22 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se def _revive_closed_monitor(conn, symbol: str, direction: str) -> Optional[dict]: """柜台仍有持仓但本地监控被误关时,恢复最近一条同品种记录。""" - if should_skip_monitor_revive(symbol, direction): - return None direction = (direction or "long").strip().lower() + ctp_still_open = False + mode = get_trading_mode(get_setting) + if ctp_status(mode).get("connected"): + for p in list(_ctp_positions(mode) or []) + list(trading_state.get_positions() or []): + if int(p.get("lots") or 0) <= 0: + continue + if (p.get("direction") or "long") != direction: + continue + if _match_ctp_symbol(symbol, p.get("symbol") or ""): + ctp_still_open = True + break + if should_skip_monitor_revive(symbol, direction) and not ctp_still_open: + return None + if ctp_still_open and close_pending_active(symbol, direction): + clear_close_pending(symbol, direction) for r in conn.execute( "SELECT * FROM trade_order_monitors WHERE status='closed' ORDER BY id DESC LIMIT 40" ).fetchall(): @@ -3455,7 +3468,8 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se symbol_name=(mon.get("symbol_name") or "") if mon else "", market_code=(mon.get("market_code") or "") if mon else "", ) - _close_all_monitors_for_sym_dir(conn, sym, direction) + if mon: + cancel_monitor_exit_orders(conn, mon, mode=mode) conn.commit() try: on_user_initiated_close(conn, trading_day=trading_day_label()) @@ -3714,6 +3728,32 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se return val return None + def _revive_monitors_for_open_ctp(conn, mode: str) -> int: + """柜台有持仓但本地监控被误关时恢复(策略页/滚仓用,不做全量同步)。""" + if not ctp_status(mode).get("connected"): + return 0 + revived = 0 + seen: set[tuple[str, str]] = set() + for p in list(_ctp_positions(mode) or []) + list(trading_state.get_positions() or []): + lots = int(p.get("lots") or 0) + if lots <= 0: + continue + direction = (p.get("direction") or "long").strip().lower() + ths = _ctp_pos_to_ths_code(p) or (p.get("symbol") or "").strip() + if not ths: + continue + key = _canonical_position_key(ths, direction) + if key in seen: + continue + seen.add(key) + if _find_active_monitor(conn, ths, direction): + continue + if _revive_closed_monitor(conn, ths, direction): + revived += 1 + if revived: + commit_retry(conn) + return revived + def _revive_roll_monitors_light(conn) -> int: """策略页:仅恢复滚仓组关联的误关监控,不做 CTP 同步(避免阻塞页面)。""" rows = conn.execute( @@ -3740,6 +3780,9 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se try: init_strategy_tables(conn) capital = _capital(conn) + mode = get_trading_mode(get_setting) + if ctp_status(mode).get("connected"): + _revive_monitors_for_open_ctp(conn, mode) _revive_roll_monitors_light(conn) need_initial = conn.execute( "SELECT id FROM roll_groups WHERE status='active' AND COALESCE(initial_lots, 0)=0 LIMIT 1" diff --git a/modules/trading/sl_tp_guard.py b/modules/trading/sl_tp_guard.py index fb29e8e..2545dd2 100644 --- a/modules/trading/sl_tp_guard.py +++ b/modules/trading/sl_tp_guard.py @@ -797,6 +797,7 @@ def reconcile_monitors_without_position(conn, mode: str, *, grace_sec: int = 120 cancel_monitor_exit_orders(conn, mon, mode=mode) except Exception as exc: logger.warning("cancel exit orders monitor=%s: %s", mon.get("id"), exc) + clear_close_pending(ms, md) conn.execute("UPDATE trade_order_monitors SET status='closed' WHERE id=?", (mon["id"],)) closed += 1 if closed: