Fix OKX abandon residual double-book and fill/residual ledger holes.
After dual-close leaves option pending, continue close_group instead of false residual; reject partial fills on wait timeout; LIVE residual settle allow_negative. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -115,7 +115,8 @@ class BinanceLiveExecutor(Matcher):
|
||||
except Exception as e:
|
||||
logger.exception("binance live open option failed")
|
||||
msg = str(e)
|
||||
if "orderId=" in msg or "orderId" in msg.lower():
|
||||
# 仅当错误带明确 orderId= 时保留 opening(避免校验文案误卡槽)
|
||||
if "orderId=" in msg:
|
||||
return OpenResult(
|
||||
ok=False,
|
||||
detail=f"币安开期权未确认成交(保留 opening 防重复开,请核对交易所): {e}",
|
||||
|
||||
@@ -924,6 +924,11 @@ class OkxLiveExecutor(Matcher):
|
||||
if dual.ok:
|
||||
return dual
|
||||
|
||||
# close_group 可能已卖掉期权并落 option_closed_perp_pending;勿再记 residual,只续平永续
|
||||
pos_after = self.current_position()
|
||||
if str(pos_after.get("status") or "") == "option_closed_perp_pending":
|
||||
return self.close_group(reason=reason, bypass_liquidity=True)
|
||||
|
||||
if require_deep_otm and not self.option_is_deep_otm():
|
||||
return CloseResult(
|
||||
ok=False,
|
||||
|
||||
@@ -191,15 +191,13 @@ class OkxTradeClient:
|
||||
if state in ("canceled", "failed"):
|
||||
raise RuntimeError(f"OKX 订单失败 state={state} {last}")
|
||||
time.sleep(0.3)
|
||||
# 超时兜底:已 filled 或已有成交量+均价则回填,避免「有仓无账」
|
||||
# 超时兜底:仅接受完全成交;部分成交不得当全成记账(会错张数/对冲)
|
||||
state = str(last.get("state") or "")
|
||||
avg = safe_float(last.get("avgPx"))
|
||||
acc = safe_float(last.get("accFillSz")) or 0.0
|
||||
if state == "filled" or (acc > 0 and avg and avg > 0):
|
||||
if state == "filled" and avg and avg > 0:
|
||||
logger.warning(
|
||||
"OKX fill wait timeout but using last fill data ordId=%s state=%s",
|
||||
"OKX fill wait timeout but order filled ordId=%s",
|
||||
ord_id,
|
||||
state,
|
||||
)
|
||||
return self._fill_from_order_row(inst_id, ord_id, last)
|
||||
raise RuntimeError(f"OKX 订单未完全成交 ordId={ord_id} last={last}")
|
||||
|
||||
@@ -854,11 +854,15 @@ class Matcher:
|
||||
opt_qty = float(row["option_qty_eth"])
|
||||
opt_pnl = (of.fill_px - opt_entry) * opt_qty
|
||||
opt_cash = of.notional - of.fee
|
||||
from ..config import get_settings
|
||||
|
||||
self.ledger.apply_cash(
|
||||
opt_cash,
|
||||
kind="close_option",
|
||||
group_id=group_id,
|
||||
note=f"residual option expiry settle{' force' if force else ''}",
|
||||
# LIVE 本地账本仅镜像;拒记会导致 residual 永久 pending
|
||||
allow_negative=not get_settings().is_sim,
|
||||
)
|
||||
|
||||
with self.db._lock:
|
||||
|
||||
@@ -241,6 +241,24 @@ class StrategyEngine:
|
||||
close_data = r.data
|
||||
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",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
elif st in ("open", "option_closed_perp_pending"):
|
||||
# A:双腿(或续平永续)
|
||||
r = self.matcher.close_group(reason="emergency", bypass_liquidity=True)
|
||||
|
||||
Reference in New Issue
Block a user