Fix ghost finalize on unknown perp size and open rollback hazards.

Distinguish exchange query None from flat zero; keep opening when size unknown; idempotent half_open cash; scan options while opening; continue manage after recover.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-29 20:47:29 +08:00
parent e2a19a1614
commit 101cb3c045
6 changed files with 153 additions and 76 deletions
+24 -13
View File
@@ -171,7 +171,8 @@ def assert_safe_to_open_live(executor) -> tuple[bool, str]:
False,
f"交易所有期权仓({opt_inst})但本地未确认持仓,禁止新开,请人工核对",
)
elif st in ("flat", ""):
elif st in ("flat", "", "opening"):
# opening 且尚未 stamp option_inst_id 时仍须扫任意期权残留
any_opt = exchange_any_option_abs(client)
if any_opt is None:
return False, "无法核对交易所期权持仓"
@@ -209,18 +210,20 @@ def perp_close_contracts_okx(
perp_qty_eth: float,
ct_val: float,
allow_db_fallback: bool = True,
) -> int:
) -> int | None:
"""平永续张数:优先交易所持仓。
allow_db_fallback=False 且交易所已空仓时返回 0(勿用 DB 数量再下单,防反向开仓)。
返回 >0 应下单;0=已确认空仓(仅 allow_db_fallback=False);
None=查仓失败(调用方不得当空仓 finalize)。
"""
ps = "long" if perp_side == "long" else "short"
ex_sz = client.get_perp_pos_sz(perp_inst, pos_side=ps)
if ex_sz is not None and ex_sz > _PERP_EPS:
return max(1, int(round(ex_sz)))
if ex_sz is not None and ex_sz <= _PERP_EPS:
if ex_sz is None:
if not allow_db_fallback:
return 0
return None
return max(1, int(round(perp_qty_eth / ct_val)))
if ex_sz > _PERP_EPS:
return max(1, int(round(ex_sz)))
if not allow_db_fallback:
return 0
return max(1, int(round(perp_qty_eth / ct_val)))
@@ -233,20 +236,28 @@ def perp_close_qty_eth_binance(
perp_side: str,
perp_qty_eth: float,
allow_db_fallback: bool = True,
) -> float:
"""平永续 ETH 数量:优先交易所持仓。交易所空且不允许 fallback → 0"""
) -> float | None:
"""平永续 ETH>0 下单;0=已确认空;None=查仓失败"""
ps = "LONG" if perp_side == "long" else "SHORT"
ex_sz = client.get_perp_pos_sz(perp_inst, position_side=ps)
if ex_sz is not None and ex_sz > _PERP_EPS:
return float(ex_sz)
if ex_sz is not None and ex_sz <= _PERP_EPS:
if ex_sz is None:
if not allow_db_fallback:
return 0.0
return None
return float(perp_qty_eth)
if ex_sz > _PERP_EPS:
return float(ex_sz)
if not allow_db_fallback:
return 0.0
return float(perp_qty_eth)
def perp_open_contracts_okx(*, perp_qty_eth: float, ct_val: float) -> int:
"""开仓张数:仅按设置名义/面值,不跟交易所残留。"""
if ct_val <= 0:
raise RuntimeError("ct_val invalid")
return max(1, int(round(float(perp_qty_eth) / float(ct_val))))
def recover_stuck_opening(executor) -> CloseResult | None:
"""恢复本地 status=opening