fix: 重启后立即读库展示持仓,CTP异步重连不再阻塞

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-25 14:57:39 +08:00
parent 01de8dfb69
commit 7daed9bd3a
5 changed files with 111 additions and 38 deletions
+76 -24
View File
@@ -78,6 +78,7 @@ from vnpy_bridge import (
ctp_status,
execute_order,
get_bridge,
set_position_refresh_callback,
)
@@ -128,9 +129,18 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
except Exception:
return {}
def _ctp_positions(mode: str, *, refresh_if_empty: bool = True) -> list:
def _ctp_positions(
mode: str,
*,
refresh_if_empty: bool = True,
refresh_margin: bool = False,
) -> list:
try:
return ctp_list_positions(mode, refresh_if_empty=refresh_if_empty)
return ctp_list_positions(
mode,
refresh_if_empty=refresh_if_empty,
refresh_margin=refresh_margin,
)
except Exception:
return []
@@ -431,6 +441,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
mode: str,
capital: float,
now_iso: str,
fast: bool = False,
) -> Optional[dict]:
if not mon and not ctp:
return None
@@ -463,14 +474,16 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
holding = _holding_duration(open_time, now_iso) if open_time else ""
mark = None
if ctp_status(mode).get("connected"):
if not fast and ctp_status(mode).get("connected"):
mark = ctp_get_tick_price(mode, sym)
if (mark is None or mark <= 0) and codes:
if not fast and (mark is None or mark <= 0) and codes:
mark = fetch_price(
sym,
codes.get("market_code", ""),
codes.get("sina_code", ""),
)
if mark is None or mark <= 0:
mark = entry if entry else None
close_est = float(mark) if mark and mark > 0 else entry
if float_pnl is None and mark and entry:
pos_tmp = calc_position_metrics(
@@ -564,7 +577,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
"trailing_r_locked": int(mon.get("trailing_r_locked") or 0) if mon else 0,
}
def _build_trading_live_rows(conn) -> list[dict]:
def _build_trading_live_rows(conn, *, fast: bool = False) -> list[dict]:
from zoneinfo import ZoneInfo
tz = ZoneInfo("Asia/Shanghai")
now_iso = datetime.now(tz).strftime("%Y-%m-%dT%H:%M")
@@ -583,7 +596,10 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
if key not in monitor_by_key:
monitor_by_key[key] = mon
ctp_list: list[dict] = _ctp_positions(mode) if ctp_status(mode).get("connected") else []
ctp_list: list[dict] = (
_ctp_positions(mode, refresh_if_empty=not fast, refresh_margin=not fast)
if ctp_status(mode).get("connected") else []
)
ctp_by_key: dict[str, dict] = {}
for p in ctp_list:
if int(p.get("lots") or 0) <= 0:
@@ -617,6 +633,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
try:
row = _compose_position_row(
conn, mon=mon, ctp=ctp, mode=mode, capital=capital, now_iso=now_iso,
fast=fast,
)
if row:
rows.append(row)
@@ -647,6 +664,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
try:
row = _compose_position_row(
conn, mon=mon, ctp=ctp, mode=mode, capital=capital, now_iso=now_iso,
fast=fast,
)
if row:
rows.append(row)
@@ -663,11 +681,12 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
deduped.append(row)
return deduped
def _build_trading_live_payload(conn) -> dict:
def _build_trading_live_payload(conn, *, fast: bool = False) -> dict:
mode = get_trading_mode(get_setting)
ctp_st = ctp_status(mode)
_ensure_monitors_from_ctp(conn, mode)
rows = _build_trading_live_rows(conn)
if not fast:
_ensure_monitors_from_ctp(conn, mode)
rows = _build_trading_live_rows(conn, fast=fast)
pending_orders = _build_pending_orders(conn, mode)
capital = _capital(conn)
risk = get_risk_status(conn, active_count=_effective_active_position_count(conn, mode))
@@ -682,26 +701,54 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
"trading_session": is_trading_session(),
}
def _refresh_trading_live_snapshot() -> dict:
def _refresh_trading_live_snapshot(*, fast: bool = False) -> dict:
mode = get_trading_mode(get_setting)
if not fast and ctp_status(mode).get("connected"):
try:
get_bridge().refresh_positions()
except Exception as exc:
logger.debug("refresh positions before snapshot: %s", exc)
conn = get_db()
try:
init_strategy_tables(conn)
payload = _build_trading_live_payload(conn)
payload = _build_trading_live_payload(conn, fast=fast)
conn.commit()
return payload
finally:
conn.close()
def _push_position_snapshot_async() -> None:
def _push_position_snapshot_async(*, fast: bool = False) -> None:
def _run() -> None:
try:
payload = _refresh_trading_live_snapshot()
payload = _refresh_trading_live_snapshot(fast=fast)
position_hub.broadcast("positions", payload)
except Exception as exc:
logger.debug("push position snapshot: %s", exc)
threading.Thread(target=_run, daemon=True).start()
def _bootstrap_trading_runtime() -> None:
"""进程启动:立刻读库展示持仓,并异步连 CTP。"""
set_position_refresh_callback(
lambda: _push_position_snapshot_async(fast=False)
)
def _warm() -> None:
try:
payload = _refresh_trading_live_snapshot(fast=True)
position_hub.set_snapshot(payload)
position_hub.broadcast("positions", payload)
except Exception as exc:
logger.warning("bootstrap position snapshot: %s", exc)
threading.Thread(target=_warm, daemon=True, name="position-bootstrap").start()
try:
from vnpy_bridge import ctp_start_connect
mode = get_trading_mode(get_setting)
ctp_start_connect(mode, force=False)
except Exception as exc:
logger.debug("bootstrap ctp connect: %s", exc)
@app.route("/trade")
@login_required
def trade_page():
@@ -788,7 +835,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
conn = get_db()
try:
init_strategy_tables(conn)
payload = _build_trading_live_payload(conn)
payload = _build_trading_live_payload(conn, fast=True)
conn.commit()
position_hub.set_snapshot(payload)
return jsonify(payload)
@@ -807,7 +854,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
if snap:
yield sse_format("positions", snap)
else:
payload = _refresh_trading_live_snapshot()
payload = _refresh_trading_live_snapshot(fast=True)
position_hub.set_snapshot(payload)
yield sse_format("positions", payload)
while True:
@@ -1322,20 +1369,24 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
"status": st,
"account": acc,
})
try:
st = ctp_connect(mode, force=force)
acc = _ctp_account(mode)
return jsonify({"ok": True, "status": st, "account": acc})
except Exception as exc:
st = ctp_status(mode)
return jsonify({"ok": False, "error": str(exc), "status": st}), 400
return jsonify({
"ok": False,
"error": st.get("last_error") or "CTP 连接未启动",
"status": st,
"account": acc,
}), 400
@app.route("/api/ctp/status")
@login_required
def api_ctp_status():
mode = get_trading_mode(get_setting)
st = ctp_status(mode)
acc = _ctp_account(mode) if st.get("connected") else {}
acc = {}
if st.get("connected"):
try:
acc = _ctp_account(mode)
except Exception:
acc = {}
return jsonify({"ok": True, "status": st, "account": acc})
@app.route("/api/account_snapshot")
@@ -1777,9 +1828,10 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
interval=1,
)
start_position_worker(
refresh_fn=_refresh_trading_live_snapshot,
refresh_fn=lambda: _refresh_trading_live_snapshot(fast=False),
interval=1,
)
_bootstrap_trading_runtime()
start_ctp_fee_worker(
get_mode_fn=lambda: get_trading_mode(get_setting),
get_setting_fn=get_setting,