Stream real-time position quotes via tick-driven SSE with incremental UI updates.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -138,6 +138,7 @@ from vnpy_bridge import (
|
||||
get_bridge,
|
||||
set_position_refresh_callback,
|
||||
set_tick_sl_tp_callback,
|
||||
set_tick_quote_callback,
|
||||
set_ctp_connected_callback,
|
||||
)
|
||||
|
||||
@@ -1622,6 +1623,61 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
|
||||
threading.Thread(target=_run, daemon=True).start()
|
||||
|
||||
def _build_position_quotes_payload(mode: str) -> dict:
|
||||
"""轻量现价/浮盈(仅读 tick 缓存,不走 SQLite)。"""
|
||||
if not ctp_status(mode).get("connected"):
|
||||
return {"ok": True, "quotes": []}
|
||||
from contract_specs import get_contract_spec
|
||||
|
||||
positions = trading_state.get_positions()
|
||||
if not positions:
|
||||
positions = _ctp_positions(mode, refresh_if_empty=False)
|
||||
quotes: list[dict] = []
|
||||
for p in positions:
|
||||
lots = int(p.get("lots") or 0)
|
||||
if lots <= 0:
|
||||
continue
|
||||
ths = _ctp_pos_to_ths_code(p) or (p.get("symbol") or "")
|
||||
if not ths:
|
||||
continue
|
||||
entry = float(p.get("avg_price") or 0)
|
||||
if entry <= 0:
|
||||
continue
|
||||
direction = (p.get("direction") or "long").strip().lower()
|
||||
mark = ctp_get_tick_price(mode, ths)
|
||||
if not mark or mark <= 0:
|
||||
continue
|
||||
mult = float(get_contract_spec(ths).get("mult") or 10)
|
||||
if direction == "long":
|
||||
float_pnl = round((mark - entry) * mult * lots, 2)
|
||||
else:
|
||||
float_pnl = round((entry - mark) * mult * lots, 2)
|
||||
row_key = _canonical_position_key(
|
||||
ths, direction, (p.get("exchange") or ""),
|
||||
)
|
||||
quotes.append({
|
||||
"key": row_key,
|
||||
"position_key": row_key,
|
||||
"mark_price": mark,
|
||||
"current_price": mark,
|
||||
"float_pnl": float_pnl,
|
||||
})
|
||||
return {"ok": True, "quotes": quotes}
|
||||
|
||||
def _push_position_quotes_async() -> None:
|
||||
def _run() -> None:
|
||||
try:
|
||||
if not is_trading_session():
|
||||
return
|
||||
mode = get_trading_mode(get_setting)
|
||||
payload = _build_position_quotes_payload(mode)
|
||||
if payload.get("quotes"):
|
||||
position_hub.push_event("position_quotes", payload)
|
||||
except Exception as exc:
|
||||
logger.debug("push position quotes: %s", exc)
|
||||
|
||||
threading.Thread(target=_run, daemon=True, name="position-quotes").start()
|
||||
|
||||
def _on_tick_sl_tp(exchange: str, symbol: str, price: float) -> None:
|
||||
from sl_tp_guard import check_sl_tp_on_tick
|
||||
from db_conn import DB_PATH, connect_db
|
||||
@@ -1651,6 +1707,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
set_position_refresh_callback(
|
||||
lambda: _push_position_snapshot_async(fast=True)
|
||||
)
|
||||
set_tick_quote_callback(_push_position_quotes_async)
|
||||
set_tick_sl_tp_callback(_on_tick_sl_tp)
|
||||
set_ctp_connected_callback(_on_ctp_connected)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user