Implement CTP-authoritative trading UI with event-driven state.

Add in-memory order/position books fed by CTP events, split active orders above positions in the UI, tick-triggered local SL/TP, and 30-second full calibration.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-26 18:12:11 +08:00
parent 4ef33a367f
commit 3a150dd3d6
8 changed files with 1056 additions and 210 deletions
+75 -13
View File
@@ -80,6 +80,37 @@ def _find_ctp_position(positions: list[dict], sym: str, direction: str) -> Optio
return None
def _vt_order_in_active(vt_oid: str, active_orders: dict[str, dict]) -> bool:
oid = (vt_oid or "").strip()
if not oid:
return False
if oid in active_orders:
return True
tail = oid.rsplit("_", 1)[-1]
for key in active_orders:
if key == oid or key.endswith(tail) or oid.endswith(key):
return True
return False
def _symbol_open_order_active(
orders: list[dict],
sym: str,
direction: str,
match_fn: Callable[[str, str], bool],
) -> Optional[dict]:
direction = (direction or "long").strip().lower()
for o in orders or []:
offset_u = (o.get("offset") or "").upper()
if offset_u and "OPEN" not in offset_u:
continue
if (o.get("direction") or "long") != direction:
continue
if match_fn(o.get("symbol") or "", sym):
return o
return None
def reconcile_pending_orders(
conn,
mode: str,
@@ -103,13 +134,15 @@ def reconcile_pending_orders(
else []
)
try:
active_orders = {
str(o.get("order_id") or ""): o
for o in ctp_list_active_orders(mode)
if o.get("order_id")
}
active_order_list = ctp_list_active_orders(mode)
active_orders = {}
for o in active_order_list:
for key in (o.get("order_id"), o.get("vt_order_id")):
if key:
active_orders[str(key)] = o
except Exception as exc:
logger.debug("list active orders: %s", exc)
active_order_list = []
active_orders = {}
rows = conn.execute(
@@ -137,7 +170,7 @@ def reconcile_pending_orders(
stats["promoted"] += 1
continue
if vt_oid and vt_oid in active_orders:
if vt_oid and _vt_order_in_active(vt_oid, active_orders):
if age >= limit_sec and is_trading_session():
if ctp_cancel_order(mode, vt_oid):
conn.execute(
@@ -149,13 +182,42 @@ def reconcile_pending_orders(
logger.warning("pending auto-cancel failed monitor=%s order=%s", mid, vt_oid)
continue
# 委托已不在活跃列表且无持仓:拒单/撤单/过期
if age >= 8:
conn.execute(
"UPDATE trade_order_monitors SET status='closed' WHERE id=?",
(mid,),
)
stats["closed"] += 1
live_open = _symbol_open_order_active(active_order_list, sym, direction, match)
if live_open or (vt_oid and age < limit_sec):
if live_open and age >= limit_sec and is_trading_session():
cancel_oid = (
vt_oid
or live_open.get("vt_order_id")
or live_open.get("order_id")
or ""
)
if cancel_oid and ctp_cancel_order(mode, cancel_oid):
conn.execute(
"UPDATE trade_order_monitors SET status='closed' WHERE id=?",
(mid,),
)
stats["cancelled"] += 1
continue
if age >= limit_sec:
if vt_oid and is_trading_session():
if ctp_cancel_order(mode, vt_oid):
conn.execute(
"UPDATE trade_order_monitors SET status='closed' WHERE id=?",
(mid,),
)
stats["cancelled"] += 1
else:
logger.info(
"pending monitor=%s order=%s kept (cancel not confirmed)",
mid, vt_oid,
)
elif not vt_oid:
conn.execute(
"UPDATE trade_order_monitors SET status='closed' WHERE id=?",
(mid,),
)
stats["closed"] += 1
if any(stats.values()):
conn.commit()