Fix post-close UI: persist closing state in DB, defer trade log until CTP fill.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-03 22:58:14 +08:00
parent d6cfeeac75
commit e3b640b35c
2 changed files with 109 additions and 41 deletions
+95 -10
View File
@@ -60,6 +60,7 @@ MONITOR_ORDER_COLUMNS = (
"ALTER TABLE trade_order_monitors ADD COLUMN vt_order_id TEXT",
"ALTER TABLE trade_order_monitors ADD COLUMN order_price REAL",
"ALTER TABLE trade_order_monitors ADD COLUMN open_fee REAL",
"ALTER TABLE trade_order_monitors ADD COLUMN close_pending_until REAL",
)
TRADE_RESULTS = ("止损", "止盈", "移动止盈", "保本止盈", "手动平仓")
@@ -77,7 +78,7 @@ def _monitor_columns_exist(conn) -> bool:
cols.add(r.get("name") or "")
else:
cols.add(r[1])
return "open_fee" in cols
return "close_pending_until" in cols
except Exception:
return False
@@ -181,29 +182,111 @@ def _position_key(sym: str, direction: str) -> str:
return f"{(sym or '').strip().lower()}|{(direction or 'long').strip().lower()}"
def mark_close_pending(sym: str, direction: str, *, secs: int = CLOSE_PENDING_SEC) -> None:
def mark_close_pending(
sym: str,
direction: str,
*,
secs: int = CLOSE_PENDING_SEC,
conn=None,
monitor_id: int = 0,
) -> None:
key = _position_key(sym, direction)
until = time.time() + max(30, int(secs))
with _closing_lock:
_close_pending_until[key] = time.time() + max(30, int(secs))
_close_pending_until[key] = until
if conn is None:
return
try:
mid = int(monitor_id or 0)
if mid > 0:
conn.execute(
"UPDATE trade_order_monitors SET close_pending_until=? WHERE id=?",
(until, mid),
)
return
direction = (direction or "long").strip().lower()
for r in conn.execute(
"SELECT id, symbol, direction FROM trade_order_monitors "
"WHERE status IN ('active', 'closed')"
):
if (r["direction"] or "long").strip().lower() != direction:
continue
if _match_symbol(sym, r["symbol"] or ""):
conn.execute(
"UPDATE trade_order_monitors SET close_pending_until=? WHERE id=?",
(until, int(r["id"])),
)
except Exception as exc:
logger.debug("persist close_pending: %s", exc)
def clear_close_pending(sym: str, direction: str) -> None:
def clear_close_pending(sym: str, direction: str, conn=None) -> None:
key = _position_key(sym, direction)
with _closing_lock:
_close_pending_until.pop(key, None)
if conn is None:
return
try:
direction = (direction or "long").strip().lower()
for r in conn.execute(
"SELECT id, symbol, direction FROM trade_order_monitors "
"WHERE close_pending_until IS NOT NULL"
):
if (r["direction"] or "long").strip().lower() != direction:
continue
if _match_symbol(sym, r["symbol"] or ""):
conn.execute(
"UPDATE trade_order_monitors SET close_pending_until=NULL WHERE id=?",
(int(r["id"]),),
)
except Exception as exc:
logger.debug("clear close_pending db: %s", exc)
def close_pending_active(sym: str, direction: str) -> bool:
def close_pending_active(sym: str, direction: str, conn=None) -> bool:
key = _position_key(sym, direction)
now = time.time()
with _closing_lock:
until = float(_close_pending_until.get(key) or 0)
if until > time.time():
if until > now:
return True
if until:
_close_pending_until.pop(key, None)
if conn is None:
return False
try:
direction = (direction or "long").strip().lower()
for r in conn.execute(
"SELECT id, symbol, direction, close_pending_until FROM trade_order_monitors "
"WHERE close_pending_until IS NOT NULL AND close_pending_until > ?",
(now,),
):
if (r["direction"] or "long").strip().lower() != direction:
continue
if not _match_symbol(sym, r["symbol"] or ""):
continue
db_until = float(r["close_pending_until"] or 0)
if db_until > now:
with _closing_lock:
_close_pending_until[key] = db_until
return True
except Exception as exc:
logger.debug("read close_pending db: %s", exc)
return False
def position_close_in_progress(
conn,
mode: str,
sym: str,
direction: str,
) -> bool:
"""平仓委托已提交或柜台仍有平仓挂单(含进程重启后)。"""
if close_pending_active(sym, direction, conn=conn):
return True
return _has_pending_close_order(mode, sym, direction)
def should_skip_monitor_revive(sym: str, direction: str) -> bool:
return close_pending_active(sym, direction)
@@ -804,7 +887,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)
clear_close_pending(ms, md, conn=conn)
conn.execute("UPDATE trade_order_monitors SET status='closed' WHERE id=?", (mon["id"],))
closed += 1
if closed:
@@ -843,12 +926,12 @@ def _execute_local_close(
float(margin_raw),
)
return
clear_close_pending(sym, direction)
clear_close_pending(sym, direction, conn=conn)
_close_all_monitors_for_symbol(conn, sym, direction)
reconcile_monitors_without_position(conn, mode)
return
if _has_pending_close_order(mode, sym, direction):
mark_close_pending(sym, direction)
mark_close_pending(sym, direction, conn=conn, monitor_id=int(mon.get("id") or 0))
cancel_monitor_exit_orders(conn, mon, mode=mode)
return
lots = int(pos.get("lots") or mon.get("lots") or 1)
@@ -865,7 +948,9 @@ def _execute_local_close(
order_type="market",
urgency="stop_loss",
)
mark_close_pending(sym, direction)
mark_close_pending(
sym, direction, conn=conn, monitor_id=int(mon.get("id") or 0),
)
_close_all_monitors_for_symbol(conn, sym, direction)
conn.commit()
result_label = _result_for_close(mon, reason)