Restore stop-loss and take-profit monitors after restart and CTP reconnect.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+58
-5
@@ -603,6 +603,40 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
)
|
)
|
||||||
return None, None, 0, None
|
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]]:
|
def _ctp_position_keys(mode: str) -> set[tuple[str, str]]:
|
||||||
keys: set[tuple[str, str]] = set()
|
keys: set[tuple[str, str]] = set()
|
||||||
for p in _ctp_positions(mode):
|
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
|
mon = mv
|
||||||
break
|
break
|
||||||
ths = _ctp_pos_to_ths_code(p) or (p.get("symbol") or "")
|
ths = _ctp_pos_to_ths_code(p) or (p.get("symbol") or "")
|
||||||
|
direction = p.get("direction") or "long"
|
||||||
if not mon:
|
if not mon:
|
||||||
mon = _find_pending_monitor(
|
mon = _find_pending_monitor(conn, ths, direction)
|
||||||
conn, ths, p.get("direction") or "long",
|
if not mon:
|
||||||
)
|
mon = _find_or_revive_monitor(conn, ths, direction)
|
||||||
if mon:
|
if mon:
|
||||||
|
mon = _restore_monitor_sl_tp_if_missing(conn, mon, ths, direction) or mon
|
||||||
_sync_monitor_from_ctp(
|
_sync_monitor_from_ctp(
|
||||||
conn, int(mon["id"]), mon.get("symbol") or ths,
|
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,
|
mode, ctp=p, capital=capital,
|
||||||
)
|
)
|
||||||
mon = _find_active_monitor(
|
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
|
) or mon
|
||||||
|
mon = _restore_monitor_sl_tp_if_missing(conn, mon, ths, direction) or mon
|
||||||
try:
|
try:
|
||||||
row = _compose_position_row(
|
row = _compose_position_row(
|
||||||
conn, mon=mon, ctp=p, mode=mode, capital=capital,
|
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)):
|
if ctp_st.get("connected") and (not fast or _has_pending_monitors(conn)):
|
||||||
_reconcile_pending(conn, mode, capital=capital)
|
_reconcile_pending(conn, mode, capital=capital)
|
||||||
if ctp_st.get("connected") and not fast:
|
if ctp_st.get("connected") and not fast:
|
||||||
|
_ensure_monitors_from_ctp(conn, mode)
|
||||||
_sync_trade_monitors_with_ctp(conn, mode)
|
_sync_trade_monitors_with_ctp(conn, mode)
|
||||||
rows = _build_trading_live_rows(conn, fast=fast)
|
rows = _build_trading_live_rows(conn, fast=fast)
|
||||||
active_orders = _build_active_orders(
|
active_orders = _build_active_orders(
|
||||||
@@ -1797,6 +1835,21 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
return
|
return
|
||||||
_schedule_recommend_refresh()
|
_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")
|
@app.route("/trade")
|
||||||
@login_required
|
@login_required
|
||||||
def trade_page():
|
def trade_page():
|
||||||
|
|||||||
+10
-2
@@ -614,8 +614,16 @@ def reconcile_monitors_without_position(conn, mode: str, *, grace_sec: int = 120
|
|||||||
position_keys.add((sym, direction))
|
position_keys.add((sym, direction))
|
||||||
|
|
||||||
margin_used = ctp_account_margin_used(mode) or 0.0
|
margin_used = ctp_account_margin_used(mode) or 0.0
|
||||||
if not position_keys and margin_used > 300:
|
if not position_keys:
|
||||||
return 0
|
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()
|
now_ts = time.time()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user