Speed up CTP account display: refresh equity on fast snapshot without waiting for full position sync.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -88,8 +88,14 @@ def _cached_ctp_account(mode: str) -> dict[str, float]:
|
|||||||
|
|
||||||
snap = position_hub.get_snapshot() or {}
|
snap = position_hub.get_snapshot() or {}
|
||||||
cap = float(snap.get("capital") or 0)
|
cap = float(snap.get("capital") or 0)
|
||||||
if cap > 0:
|
avail = snap.get("account_available")
|
||||||
return {"balance": cap}
|
if cap > 0 or avail is not None:
|
||||||
|
out: dict[str, float] = {}
|
||||||
|
if cap > 0:
|
||||||
|
out["balance"] = cap
|
||||||
|
if avail is not None:
|
||||||
|
out["available"] = float(avail)
|
||||||
|
return out
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
|
|||||||
+88
-11
@@ -154,6 +154,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
"""注册交易相关路由。"""
|
"""注册交易相关路由。"""
|
||||||
_nav = require_nav
|
_nav = require_nav
|
||||||
_live_refresh_lock = threading.Lock()
|
_live_refresh_lock = threading.Lock()
|
||||||
|
_live_slow_lock = threading.Lock()
|
||||||
_ctp_status_cache: dict = {"mode": "", "status": {}, "ts": 0.0}
|
_ctp_status_cache: dict = {"mode": "", "status": {}, "ts": 0.0}
|
||||||
_ctp_status_cache_lock = threading.Lock()
|
_ctp_status_cache_lock = threading.Lock()
|
||||||
_ctp_status_refresh_flag = {"busy": False}
|
_ctp_status_refresh_flag = {"busy": False}
|
||||||
@@ -331,6 +332,52 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
except Exception:
|
except Exception:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
def _live_account_fields(mode: str) -> dict:
|
||||||
|
"""轻量读柜台资金(不跑 SQLite 对账)。"""
|
||||||
|
if not _cached_ctp_status(mode).get("connected"):
|
||||||
|
return {}
|
||||||
|
try:
|
||||||
|
acc = _ctp_account(mode)
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
balance = float(acc.get("balance") or 0)
|
||||||
|
available = acc.get("available")
|
||||||
|
out: dict = {}
|
||||||
|
if balance > 0:
|
||||||
|
out["capital"] = balance
|
||||||
|
if available is not None:
|
||||||
|
out["account_available"] = round(float(available), 2)
|
||||||
|
try:
|
||||||
|
mu = ctp_account_margin_used(mode)
|
||||||
|
if mu is not None:
|
||||||
|
out["margin_used"] = mu
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return out
|
||||||
|
|
||||||
|
def _apply_live_account(payload: dict, mode: str, fields: dict | None = None) -> dict:
|
||||||
|
merged = dict(payload)
|
||||||
|
acct = fields if fields is not None else _live_account_fields(mode)
|
||||||
|
if not acct:
|
||||||
|
return merged
|
||||||
|
if acct.get("capital"):
|
||||||
|
merged["capital"] = acct["capital"]
|
||||||
|
if acct.get("account_available") is not None:
|
||||||
|
merged["account_available"] = acct["account_available"]
|
||||||
|
if acct.get("margin_used") is not None:
|
||||||
|
merged["margin_used"] = acct["margin_used"]
|
||||||
|
return merged
|
||||||
|
|
||||||
|
def _broadcast_account_fast(mode: str) -> None:
|
||||||
|
"""CTP 连上后立刻推送权益/可用,持仓可仍在后台同步。"""
|
||||||
|
try:
|
||||||
|
snap = position_hub.get_snapshot() or {"ok": True, "rows": []}
|
||||||
|
merged = _apply_live_account(dict(snap), mode)
|
||||||
|
merged["ctp_status"] = dict(ctp_status(mode) or _cached_ctp_status(mode))
|
||||||
|
position_hub.broadcast("positions", merged)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.debug("broadcast account fast: %s", exc)
|
||||||
|
|
||||||
def _ctp_positions(
|
def _ctp_positions(
|
||||||
mode: str,
|
mode: str,
|
||||||
*,
|
*,
|
||||||
@@ -2132,7 +2179,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
equity=capital,
|
equity=capital,
|
||||||
)
|
)
|
||||||
syncing = bool(ctp_st.get("connected") or ctp_st.get("connecting"))
|
syncing = bool(ctp_st.get("connected") or ctp_st.get("connecting"))
|
||||||
return {
|
payload = {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
"rows": [],
|
"rows": [],
|
||||||
"active_orders": [],
|
"active_orders": [],
|
||||||
@@ -2148,6 +2195,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
"sync_state": "syncing" if syncing else trading_state.sync_state,
|
"sync_state": "syncing" if syncing else trading_state.sync_state,
|
||||||
"sync_label": "加载中…" if syncing else trading_state.sync_label(),
|
"sync_label": "加载中…" if syncing else trading_state.sync_label(),
|
||||||
}
|
}
|
||||||
|
return _apply_live_account(payload, mode)
|
||||||
|
|
||||||
def _normalize_live_payload(payload: dict) -> dict:
|
def _normalize_live_payload(payload: dict) -> dict:
|
||||||
if payload.get("rows"):
|
if payload.get("rows"):
|
||||||
@@ -2212,37 +2260,54 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
since_connect = time.time() - float(
|
since_connect = time.time() - float(
|
||||||
getattr(get_bridge(), "_last_connect_ok_ts", 0) or 0,
|
getattr(get_bridge(), "_last_connect_ok_ts", 0) or 0,
|
||||||
)
|
)
|
||||||
if since_connect < 180:
|
if since_connect < 45:
|
||||||
payload = dict(payload)
|
payload = dict(payload)
|
||||||
payload["sync_state"] = "syncing"
|
payload["sync_state"] = "syncing"
|
||||||
payload["sync_label"] = "持仓同步中…"
|
payload["sync_label"] = "持仓同步中…"
|
||||||
return _normalize_live_payload(payload)
|
return _apply_live_account(_normalize_live_payload(payload), mode)
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
if fast:
|
if fast:
|
||||||
|
mode = get_trading_mode(get_setting)
|
||||||
snap = position_hub.get_snapshot()
|
snap = position_hub.get_snapshot()
|
||||||
if snap:
|
if snap:
|
||||||
return snap
|
return _apply_live_account(dict(snap), mode)
|
||||||
if _live_refresh_lock.acquire(blocking=False):
|
if _live_refresh_lock.acquire(blocking=False):
|
||||||
try:
|
try:
|
||||||
return _build()
|
return _apply_live_account(_build(), mode)
|
||||||
finally:
|
finally:
|
||||||
_live_refresh_lock.release()
|
_live_refresh_lock.release()
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
try:
|
try:
|
||||||
init_strategy_tables(conn)
|
init_strategy_tables(conn)
|
||||||
return _minimal_live_payload(conn)
|
return _apply_live_account(_minimal_live_payload(conn), mode)
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
with _live_refresh_lock:
|
if not _live_slow_lock.acquire(blocking=False):
|
||||||
return _build()
|
mode = get_trading_mode(get_setting)
|
||||||
|
snap = position_hub.get_snapshot()
|
||||||
|
if snap:
|
||||||
|
return _apply_live_account(dict(snap), mode)
|
||||||
|
conn = get_db()
|
||||||
|
try:
|
||||||
|
init_strategy_tables(conn)
|
||||||
|
return _apply_live_account(_minimal_live_payload(conn), mode)
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
try:
|
||||||
|
with _live_refresh_lock:
|
||||||
|
return _apply_live_account(_build(), get_trading_mode(get_setting))
|
||||||
|
finally:
|
||||||
|
_live_slow_lock.release()
|
||||||
|
|
||||||
def _push_position_snapshot_async(*, fast: bool = True) -> None:
|
def _push_position_snapshot_async(*, fast: bool = True) -> None:
|
||||||
def _run() -> None:
|
def _run() -> None:
|
||||||
try:
|
try:
|
||||||
payload = _refresh_trading_live_snapshot(fast=fast)
|
payload = _refresh_trading_live_snapshot(fast=fast)
|
||||||
position_hub.broadcast("positions", payload)
|
position_hub.broadcast("positions", payload)
|
||||||
|
if fast:
|
||||||
|
return
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
try:
|
try:
|
||||||
rec = _recommend_payload(conn)
|
rec = _recommend_payload(conn)
|
||||||
@@ -2415,11 +2480,14 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
def _on_ctp_connected(mode: str) -> None:
|
def _on_ctp_connected(mode: str) -> None:
|
||||||
if mode != get_trading_mode(get_setting):
|
if mode != get_trading_mode(get_setting):
|
||||||
return
|
return
|
||||||
|
_broadcast_account_fast(mode)
|
||||||
_schedule_recommend_refresh()
|
_schedule_recommend_refresh()
|
||||||
_push_position_snapshot_async(fast=True)
|
_push_position_snapshot_async(fast=True)
|
||||||
|
|
||||||
def _after_connect() -> None:
|
def _after_connect() -> None:
|
||||||
try:
|
try:
|
||||||
|
_broadcast_account_fast(mode)
|
||||||
|
_push_position_snapshot_async(fast=True)
|
||||||
try:
|
try:
|
||||||
with _ctp_td_lock:
|
with _ctp_td_lock:
|
||||||
get_bridge().request_position_snapshot(force=True)
|
get_bridge().request_position_snapshot(force=True)
|
||||||
@@ -2434,7 +2502,15 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
commit_retry(conn)
|
commit_retry(conn)
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
_push_position_snapshot_async(fast=False)
|
|
||||||
|
def _slow_sync() -> None:
|
||||||
|
time.sleep(8)
|
||||||
|
try:
|
||||||
|
_push_position_snapshot_async(fast=False)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.debug("ctp connected slow sync: %s", exc)
|
||||||
|
|
||||||
|
threading.Thread(target=_slow_sync, daemon=True, name="ctp-slow-sync").start()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.debug("ctp connected monitor restore: %s", exc)
|
logger.debug("ctp connected monitor restore: %s", exc)
|
||||||
|
|
||||||
@@ -2463,11 +2539,12 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
),
|
),
|
||||||
equity=capital,
|
equity=capital,
|
||||||
)
|
)
|
||||||
ctp_acc = {}
|
ctp_acc = _ctp_account(mode) if connected else {}
|
||||||
bootstrap_live = position_hub.get_snapshot()
|
bootstrap_live = position_hub.get_snapshot()
|
||||||
if connected and bootstrap_live and bootstrap_live.get("capital") is not None:
|
if connected and bootstrap_live and bootstrap_live.get("capital") is not None:
|
||||||
cap = float(bootstrap_live.get("capital") or 0)
|
cap = float(bootstrap_live.get("capital") or 0)
|
||||||
ctp_acc = {"balance": cap, "available": cap}
|
if cap > 0 and not ctp_acc.get("balance"):
|
||||||
|
ctp_acc = {"balance": cap, "available": bootstrap_live.get("account_available", cap)}
|
||||||
active_trend = conn.execute(
|
active_trend = conn.execute(
|
||||||
"SELECT * FROM trend_pullback_plans WHERE status='active' ORDER BY id DESC LIMIT 1"
|
"SELECT * FROM trend_pullback_plans WHERE status='active' ORDER BY id DESC LIMIT 1"
|
||||||
).fetchone()
|
).fetchone()
|
||||||
|
|||||||
@@ -264,7 +264,7 @@
|
|||||||
posFastPollTimer = setInterval(function () {
|
posFastPollTimer = setInterval(function () {
|
||||||
pollPositions();
|
pollPositions();
|
||||||
posFastPollCount += 1;
|
posFastPollCount += 1;
|
||||||
if (posFastPollCount >= 90) stopPosFastPoll();
|
if (posFastPollCount >= 120) stopPosFastPoll();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,6 +272,10 @@
|
|||||||
if (!data) return;
|
if (!data) return;
|
||||||
var cap = document.getElementById('cap-display');
|
var cap = document.getElementById('cap-display');
|
||||||
if (cap && data.capital != null) cap.textContent = Number(data.capital).toFixed(2);
|
if (cap && data.capital != null) cap.textContent = Number(data.capital).toFixed(2);
|
||||||
|
var avail = document.getElementById('avail-display');
|
||||||
|
if (avail && data.account_available != null) {
|
||||||
|
avail.textContent = Number(data.account_available).toFixed(2);
|
||||||
|
}
|
||||||
var connected = data.ctp_status && data.ctp_status.connected;
|
var connected = data.ctp_status && data.ctp_status.connected;
|
||||||
var connecting = data.ctp_status && data.ctp_status.connecting;
|
var connecting = data.ctp_status && data.ctp_status.connecting;
|
||||||
var cooldownSec = (data.ctp_status && data.ctp_status.login_cooldown_sec) || 0;
|
var cooldownSec = (data.ctp_status && data.ctp_status.login_cooldown_sec) || 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user