Fix ghost positions: stop showing local monitor when CTP confirms flat account.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -616,6 +616,11 @@ class CtpBridge:
|
|||||||
trades = self.list_trades()
|
trades = self.list_trades()
|
||||||
preserve_margin = 0.0
|
preserve_margin = 0.0
|
||||||
if self._connected_mode and not positions:
|
if self._connected_mode and not positions:
|
||||||
|
try:
|
||||||
|
since_ok = time.time() - float(self._last_connect_ok_ts or 0)
|
||||||
|
except Exception:
|
||||||
|
since_ok = 9999.0
|
||||||
|
if since_ok < 180:
|
||||||
try:
|
try:
|
||||||
preserve_margin = float(
|
preserve_margin = float(
|
||||||
ctp_account_margin_used(self._connected_mode) or 0,
|
ctp_account_margin_used(self._connected_mode) or 0,
|
||||||
|
|||||||
+34
-48
@@ -2416,8 +2416,16 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
|
|
||||||
ctp_list: list[dict] = []
|
ctp_list: list[dict] = []
|
||||||
if ctp_status(mode).get("connected"):
|
if ctp_status(mode).get("connected"):
|
||||||
|
ctp_raw = list(_ctp_positions(mode, refresh_if_empty=False) or [])
|
||||||
merged: dict[str, dict] = {}
|
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)
|
lots = int(p.get("lots") or 0)
|
||||||
if lots <= 0:
|
if lots <= 0:
|
||||||
continue
|
continue
|
||||||
@@ -2490,21 +2498,12 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
seen.add(rk)
|
seen.add(rk)
|
||||||
deduped.append(row)
|
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_raw = ctp_account_margin_used(mode)
|
||||||
margin_used = float(margin_raw or 0) if margin_raw is not None else 0.0
|
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_margin_hint = margin_raw is not None and margin_used > 0
|
||||||
has_active_mon = any(
|
since_connect = _ctp_since_connect_sec(mode)
|
||||||
int(m.get("lots") or 0) > 0 for m in monitor_by_pk.values()
|
if has_margin_hint or since_connect < 120:
|
||||||
)
|
|
||||||
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:
|
|
||||||
if not monitor_by_pk and has_margin_hint:
|
if not monitor_by_pk and has_margin_hint:
|
||||||
_ensure_monitors_from_sticky_state(conn, mode)
|
_ensure_monitors_from_sticky_state(conn, mode)
|
||||||
monitor_by_pk = _monitors_by_position_key(conn)
|
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:
|
except Exception as exc:
|
||||||
logger.warning("compose monitor fallback row failed: %s", 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
|
return deduped
|
||||||
|
|
||||||
def _build_trading_live_payload(conn, *, fast: bool = False) -> dict:
|
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:
|
if not fast:
|
||||||
_ensure_monitors_from_ctp(conn, mode)
|
_ensure_monitors_from_ctp(conn, mode)
|
||||||
_sync_trade_monitors_with_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:
|
elif count_active_trade_monitors(conn) == 0:
|
||||||
margin_raw = ctp_account_margin_used(mode)
|
margin_raw = ctp_account_margin_used(mode)
|
||||||
if margin_raw is not None and float(margin_raw) > 0:
|
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),
|
(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:
|
def _account_has_margin_in_use(mode: str) -> bool:
|
||||||
if not ctp_status(mode).get("connected"):
|
if not ctp_status(mode).get("connected"):
|
||||||
return False
|
return False
|
||||||
|
|||||||
Reference in New Issue
Block a user