Restore stop-loss and take-profit monitors after restart and CTP reconnect.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-30 09:24:37 +08:00
parent 963ed141e5
commit ec4199b82c
2 changed files with 68 additions and 7 deletions
+58 -5
View File
@@ -603,6 +603,40 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
)
return None, None, 0, None
def _restore_monitor_sl_tp_if_missing(
conn,
mon: Optional[dict],
sym: str,
direction: str,
) -> Optional[dict]:
"""活跃监控缺少止盈止损时,从最近关闭的同品种记录恢复并写回数据库。"""
if not mon:
return None
sl = mon.get("stop_loss")
tp = mon.get("take_profit")
trailing = int(mon.get("trailing_be") or 0)
if sl is not None or tp is not None or trailing:
return mon
rsl, rtp, rtrail, rinitial = _restore_sl_tp_from_closed(conn, sym, direction)
if rsl is None and rtp is None and not rtrail:
return mon
conn.execute(
"""UPDATE trade_order_monitors SET
stop_loss=?, take_profit=?, trailing_be=?, initial_stop_loss=?
WHERE id=? AND status='active'""",
(rsl, rtp, rtrail, rinitial, int(mon["id"])),
)
row = conn.execute(
"SELECT * FROM trade_order_monitors WHERE id=?", (int(mon["id"]),),
).fetchone()
if row:
logger.info(
"恢复止盈止损 monitor=%s sym=%s sl=%s tp=%s",
mon.get("id"), sym, rsl, rtp,
)
return dict(row)
return mon
def _ctp_position_keys(mode: str) -> set[tuple[str, str]]:
keys: set[tuple[str, str]] = set()
for p in _ctp_positions(mode):
@@ -1567,19 +1601,22 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
mon = mv
break
ths = _ctp_pos_to_ths_code(p) or (p.get("symbol") or "")
direction = p.get("direction") or "long"
if not mon:
mon = _find_pending_monitor(
conn, ths, p.get("direction") or "long",
)
mon = _find_pending_monitor(conn, ths, direction)
if not mon:
mon = _find_or_revive_monitor(conn, ths, direction)
if mon:
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 p.get("direction") or "long",
mon.get("direction") or direction,
mode, ctp=p, capital=capital,
)
mon = _find_active_monitor(
conn, mon.get("symbol") or ths, mon.get("direction") or "long",
conn, mon.get("symbol") or ths, mon.get("direction") or direction,
) or mon
mon = _restore_monitor_sl_tp_if_missing(conn, mon, ths, direction) or mon
try:
row = _compose_position_row(
conn, mon=mon, ctp=p, mode=mode, capital=capital,
@@ -1610,6 +1647,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
if ctp_st.get("connected") and (not fast or _has_pending_monitors(conn)):
_reconcile_pending(conn, mode, capital=capital)
if ctp_st.get("connected") and not fast:
_ensure_monitors_from_ctp(conn, mode)
_sync_trade_monitors_with_ctp(conn, mode)
rows = _build_trading_live_rows(conn, fast=fast)
active_orders = _build_active_orders(
@@ -1797,6 +1835,21 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
return
_schedule_recommend_refresh()
def _after_connect() -> None:
try:
conn = get_db()
try:
init_strategy_tables(conn)
_ensure_monitors_from_ctp(conn, mode)
conn.commit()
finally:
conn.close()
_push_position_snapshot_async(fast=False)
except Exception as exc:
logger.debug("ctp connected monitor restore: %s", exc)
threading.Thread(target=_after_connect, daemon=True, name="ctp-monitor-restore").start()
@app.route("/trade")
@login_required
def trade_page():
+10 -2
View File
@@ -614,8 +614,16 @@ def reconcile_monitors_without_position(conn, mode: str, *, grace_sec: int = 120
position_keys.add((sym, direction))
margin_used = ctp_account_margin_used(mode) or 0.0
if not position_keys and margin_used > 300:
return 0
if not position_keys:
if margin_used > 100:
return 0
try:
bridge = get_bridge()
since_connect = time.time() - float(getattr(bridge, "_last_connect_ok_ts", 0) or 0)
if since_connect < 180:
return 0
except Exception:
return 0
now_ts = time.time()