Fix ghost positions: stop showing local monitor when CTP confirms flat account.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -617,11 +617,16 @@ class CtpBridge:
|
||||
preserve_margin = 0.0
|
||||
if self._connected_mode and not positions:
|
||||
try:
|
||||
preserve_margin = float(
|
||||
ctp_account_margin_used(self._connected_mode) or 0,
|
||||
)
|
||||
since_ok = time.time() - float(self._last_connect_ok_ts or 0)
|
||||
except Exception:
|
||||
preserve_margin = 0.0
|
||||
since_ok = 9999.0
|
||||
if since_ok < 180:
|
||||
try:
|
||||
preserve_margin = float(
|
||||
ctp_account_margin_used(self._connected_mode) or 0,
|
||||
)
|
||||
except Exception:
|
||||
preserve_margin = 0.0
|
||||
trading_state.calibrate_from_lists(
|
||||
orders,
|
||||
positions,
|
||||
|
||||
+34
-48
@@ -2416,13 +2416,21 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
|
||||
ctp_list: list[dict] = []
|
||||
if ctp_status(mode).get("connected"):
|
||||
ctp_raw = list(_ctp_positions(mode, refresh_if_empty=False) or [])
|
||||
merged: dict[str, dict] = {}
|
||||
for p in list(_ctp_positions(mode) or []) + list(trading_state.get_positions() or []):
|
||||
for p in ctp_raw:
|
||||
lots = int(p.get("lots") or 0)
|
||||
if lots <= 0:
|
||||
continue
|
||||
pk = p.get("position_key") or _position_key_from_ctp(p)
|
||||
merged[pk] = p
|
||||
if not merged and _allow_monitor_position_fallback(mode):
|
||||
for p in list(trading_state.get_positions() or []):
|
||||
lots = int(p.get("lots") or 0)
|
||||
if lots <= 0:
|
||||
continue
|
||||
pk = p.get("position_key") or _position_key_from_ctp(p)
|
||||
merged[pk] = p
|
||||
ctp_list = list(merged.values())
|
||||
|
||||
ensure_monitor_order_columns(conn)
|
||||
@@ -2490,21 +2498,12 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
seen.add(rk)
|
||||
deduped.append(row)
|
||||
|
||||
if not deduped and ctp_status(mode).get("connected"):
|
||||
if not deduped and ctp_status(mode).get("connected") and _allow_monitor_position_fallback(mode):
|
||||
margin_raw = ctp_account_margin_used(mode)
|
||||
margin_used = float(margin_raw or 0) if margin_raw is not None else 0.0
|
||||
has_margin_hint = margin_raw is not None and margin_used > 0
|
||||
has_active_mon = any(
|
||||
int(m.get("lots") or 0) > 0 for m in monitor_by_pk.values()
|
||||
)
|
||||
since_connect = 9999.0
|
||||
try:
|
||||
since_connect = time.time() - float(
|
||||
getattr(get_bridge(), "_last_connect_ok_ts", 0) or 0,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
if has_margin_hint or has_active_mon or since_connect < 300:
|
||||
since_connect = _ctp_since_connect_sec(mode)
|
||||
if has_margin_hint or since_connect < 120:
|
||||
if not monitor_by_pk and has_margin_hint:
|
||||
_ensure_monitors_from_sticky_state(conn, mode)
|
||||
monitor_by_pk = _monitors_by_position_key(conn)
|
||||
@@ -2542,41 +2541,6 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
except Exception as exc:
|
||||
logger.warning("compose monitor fallback row failed: %s", exc)
|
||||
|
||||
if not deduped and ctp_status(mode).get("connected"):
|
||||
for r in conn.execute(
|
||||
"SELECT * FROM trade_order_monitors WHERE status='active' ORDER BY id DESC"
|
||||
).fetchall():
|
||||
mon = dict(r)
|
||||
lots = int(mon.get("lots") or 0)
|
||||
if lots <= 0:
|
||||
continue
|
||||
sym = (mon.get("symbol") or "").strip()
|
||||
direction = (mon.get("direction") or "long").strip().lower()
|
||||
rk = _monitor_position_key(mon)
|
||||
if rk in seen:
|
||||
continue
|
||||
if fast:
|
||||
mon = _overlay_sl_tp_readonly(conn, mon, sym, direction) or mon
|
||||
try:
|
||||
row = _compose_position_row(
|
||||
conn,
|
||||
mon=mon,
|
||||
ctp=None,
|
||||
mode=mode,
|
||||
capital=capital,
|
||||
now_iso=now_iso,
|
||||
fast=fast,
|
||||
)
|
||||
if not row:
|
||||
continue
|
||||
row_key = row.get("key") or row.get("position_key") or rk
|
||||
if row_key in seen:
|
||||
continue
|
||||
seen.add(row_key)
|
||||
deduped.append(row)
|
||||
except Exception as exc:
|
||||
logger.warning("compose active monitor row failed: %s", exc)
|
||||
|
||||
return deduped
|
||||
|
||||
def _build_trading_live_payload(conn, *, fast: bool = False) -> dict:
|
||||
@@ -2593,6 +2557,12 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
if not fast:
|
||||
_ensure_monitors_from_ctp(conn, mode)
|
||||
_sync_trade_monitors_with_ctp(conn, mode)
|
||||
elif (
|
||||
not _ctp_positions(mode, refresh_if_empty=False)
|
||||
and not _account_has_margin_in_use(mode)
|
||||
and _ctp_since_connect_sec(mode) >= 120
|
||||
):
|
||||
_sync_trade_monitors_with_ctp(conn, mode)
|
||||
elif count_active_trade_monitors(conn) == 0:
|
||||
margin_raw = ctp_account_margin_used(mode)
|
||||
if margin_raw is not None and float(margin_raw) > 0:
|
||||
@@ -3640,6 +3610,22 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
(now_s, gid),
|
||||
)
|
||||
|
||||
def _ctp_since_connect_sec(mode: str) -> float:
|
||||
try:
|
||||
return time.time() - float(
|
||||
getattr(get_bridge(), "_last_connect_ok_ts", 0) or 0,
|
||||
)
|
||||
except Exception:
|
||||
return 9999.0
|
||||
|
||||
def _allow_monitor_position_fallback(mode: str) -> bool:
|
||||
"""CTP 已连接且确认空仓后,禁止用本地监控拼「幽灵持仓」。"""
|
||||
if not ctp_status(mode).get("connected"):
|
||||
return False
|
||||
if _ctp_since_connect_sec(mode) < 120:
|
||||
return True
|
||||
return _account_has_margin_in_use(mode)
|
||||
|
||||
def _account_has_margin_in_use(mode: str) -> bool:
|
||||
if not ctp_status(mode).get("connected"):
|
||||
return False
|
||||
|
||||
Reference in New Issue
Block a user