Recover stuck opening and harden LIVE open/close reconcile.
Stamp open intent, recover opening from exchange option/perp state, skip resell/reopen when already flat, and persist Binance margin mode. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -242,23 +242,32 @@ class StrategyEngine:
|
||||
if r.ok:
|
||||
self.enter_rest_after_close()
|
||||
elif st == "opening":
|
||||
# 开仓未确认:不自动清槽(可能期权已成交);紧急全平仅告警,防重复开
|
||||
ok = False
|
||||
detail = (
|
||||
"stuck opening:请核对交易所期权/永续后人工处理"
|
||||
"(已占槽防重复开,紧急全平不会自动释放)"
|
||||
)
|
||||
close_data = {"status": "opening"}
|
||||
try:
|
||||
from ..notify import wecom
|
||||
|
||||
wecom.notify_fault(
|
||||
title="紧急全平遇到 stuck opening",
|
||||
detail=detail,
|
||||
dedupe_key="emergency:opening",
|
||||
recover = getattr(self.matcher, "recover_opening", None)
|
||||
if callable(recover):
|
||||
r = recover()
|
||||
ok = r.ok
|
||||
detail = r.detail
|
||||
close_data = r.data
|
||||
st2 = str(self.matcher.current_position().get("status") or "")
|
||||
if r.ok and st2 in ("flat",):
|
||||
self.enter_rest_after_close()
|
||||
else:
|
||||
ok = False
|
||||
detail = (
|
||||
"stuck opening:请核对交易所期权/永续后人工处理"
|
||||
"(已占槽防重复开)"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
close_data = {"status": "opening"}
|
||||
try:
|
||||
from ..notify import wecom
|
||||
|
||||
wecom.notify_fault(
|
||||
title="紧急全平遇到 stuck opening",
|
||||
detail=detail,
|
||||
dedupe_key="emergency:opening",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
elif st in ("open", "option_closed_perp_pending"):
|
||||
# A:双腿(或续平永续)
|
||||
r = self.matcher.close_group(reason="emergency", bypass_liquidity=True)
|
||||
@@ -534,6 +543,46 @@ class StrategyEngine:
|
||||
pos = self.matcher.current_position()
|
||||
st_pos = str(pos.get("status") or "flat")
|
||||
|
||||
if st_pos == "opening":
|
||||
allowed, left = self._retry_allowed("opening")
|
||||
if not allowed:
|
||||
self._set_state(
|
||||
phase="opening_stuck",
|
||||
last_error=f"opening 恢复退避中,{left:.0f}s 后再试",
|
||||
)
|
||||
return
|
||||
recover = getattr(self.matcher, "recover_opening", None)
|
||||
if callable(recover):
|
||||
r = await asyncio.to_thread(recover)
|
||||
self._note_retry_result("opening", ok=r.ok, detail=r.detail)
|
||||
if r.ok:
|
||||
self._set_state(phase="idle", last_error=None)
|
||||
if "half_open" in (r.detail or "") or "提升为 open" in (r.detail or ""):
|
||||
pass # 继续本 tick 后续逻辑由下一轮处理
|
||||
try:
|
||||
from ..notify import wecom
|
||||
|
||||
wecom.notify_fault(
|
||||
title="opening 已自动恢复",
|
||||
detail=r.detail,
|
||||
dedupe_key=f"opening_ok:{r.detail[:60]}",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
self._set_state(phase="opening_stuck", last_error=r.detail)
|
||||
try:
|
||||
from ..notify import wecom
|
||||
|
||||
wecom.notify_fault(
|
||||
title="opening 恢复失败",
|
||||
detail=r.detail,
|
||||
dedupe_key=f"opening_fail:{r.detail[:60]}",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
|
||||
if st_pos == "half_open":
|
||||
allowed, left = self._retry_allowed("half_open")
|
||||
if not allowed:
|
||||
@@ -733,7 +782,12 @@ class StrategyEngine:
|
||||
except Exception:
|
||||
logger.exception("wecom notify_open failed")
|
||||
else:
|
||||
self._set_state(phase="idle", last_error=r.detail)
|
||||
# 保留 opening 时勿伪装 idle
|
||||
pos_after = self.matcher.current_position()
|
||||
if str(pos_after.get("status") or "") == "opening":
|
||||
self._set_state(phase="opening_stuck", last_error=r.detail)
|
||||
else:
|
||||
self._set_state(phase="idle", last_error=r.detail)
|
||||
try:
|
||||
from ..notify import wecom
|
||||
|
||||
|
||||
Reference in New Issue
Block a user