Fix LIVE SoT P0/P1: closing state machine, OO exchange fills, BN balances.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-08 15:54:45 +08:00
parent 99e58910d3
commit 8d67f3fc6c
10 changed files with 864 additions and 167 deletions
+67 -21
View File
@@ -26,7 +26,13 @@ logger = logging.getLogger(__name__)
# 禁止新开仓的本地仓位状态(实盘防卡)
BLOCKING_STATUSES = frozenset(
{"open", "half_open", "option_closed_perp_pending", "opening"}
{
"open",
"half_open",
"option_closed_perp_pending",
"opening",
"closing", # 期期盈利腿已卖、账本收尾中
}
)
@@ -624,7 +630,14 @@ class Matcher:
)
def close_winning_oo_leave_residual(
self, *, reason: str = "target_oo_win", skip_market: bool = False
self,
*,
reason: str = "target_oo_win",
skip_market: bool = False,
live_fill_px: float | None = None,
live_fill_fee: float | None = None,
live_fill_contracts: float | None = None,
live_win_leg: str | None = None,
) -> CloseResult:
"""期期达标:平盈利腿,亏损腿进 residual。skip_market=True 时假定已在交易所卖掉盈利腿。"""
pos = self.current_position()
@@ -649,6 +662,13 @@ class Matcher:
upl = self.unrealized()
call_upl = float(upl.get("option_upl") or 0)
put_upl = float(upl.get("option2_upl") or 0)
# LIVE 收尾可指定 win_leg(崩溃恢复时 UPL 可能已不可用)
force_leg = (live_win_leg or "").strip()
if force_leg in ("option", "option2"):
if force_leg == "option":
call_upl, put_upl = 1.0, 0.0
else:
call_upl, put_upl = 0.0, 1.0
# 盈利腿:UPL 更高且 > 0
if call_upl >= put_upl and call_upl > 0:
win_leg, lose_leg = "option", "option2"
@@ -681,16 +701,38 @@ class Matcher:
return CloseResult(ok=False, detail="无明确盈利腿,暂不平")
fee_rate = self._fee_rate()
if skip_market:
oq = self._quote_held_option(win_id)
fill_px = float(oq.bid) if oq and oq.bid else float(win_entry)
of = option_fill(
action="close",
bid=fill_px,
ask=fill_px,
qty_eth=win_qty,
fee_rate=fee_rate,
exec_mode = "SIM" if get_settings().is_sim else "LIVE"
from .pricing import PriceResult
if skip_market and live_fill_px is not None:
fill_px = float(live_fill_px)
fill_fee = float(live_fill_fee or 0)
if live_fill_contracts is not None and float(live_fill_contracts) > 0:
win_contracts = float(live_fill_contracts)
win_qty = eth_from_contracts(win_contracts, self._ct_mult(win_id))
of = PriceResult(
base_px=fill_px,
fill_px=fill_px,
fee=fill_fee,
slip=0.0,
notional=fill_px * win_qty,
)
elif skip_market:
# LIVE 未传真实成交:禁止用盘口发明价,仅允许 0 价零现金镜像
if not get_settings().is_sim:
of = PriceResult(
base_px=0.0, fill_px=0.0, fee=0.0, slip=0.0, notional=0.0
)
else:
oq = self._quote_held_option(win_id)
fill_px = float(oq.bid) if oq and oq.bid else float(win_entry)
of = option_fill(
action="close",
bid=fill_px,
ask=fill_px,
qty_eth=win_qty,
fee_rate=fee_rate,
)
else:
oq = self._quote_held_option(win_id)
if oq is None or oq.bid is None or float(oq.bid) <= 0:
@@ -715,21 +757,24 @@ class Matcher:
fee_rate=fee_rate,
)
cash = of.notional - of.fee
if get_settings().is_sim or not skip_market:
self.ledger.apply_cash(
cash, kind="close_option", group_id=group_id, note=f"oo win {win_leg}"
)
elif skip_market:
# LIVE:交易所已成交,仍记本地账本现金(与其它 LIVE 平仓一致)
apply_live_cash = bool(
get_settings().is_sim
or not skip_market
or (live_fill_px is not None and abs(float(live_fill_px)) + abs(float(live_fill_fee or 0)) > 1e-12)
)
if apply_live_cash:
try:
self.ledger.apply_cash(
cash,
kind="close_option",
group_id=group_id,
note=f"oo win live {win_leg}",
note=f"oo win {win_leg}",
allow_negative=not get_settings().is_sim,
)
except Exception:
logger.exception("oo win live ledger cash failed")
if get_settings().is_sim:
raise
logger.exception("oo win ledger cash failed")
now = int(time.time() * 1000)
expiry_ymd = None
@@ -764,7 +809,7 @@ class Matcher:
of.slip,
of.notional,
now,
"SIM",
exec_mode,
),
)
self.db._conn.execute(
@@ -836,7 +881,8 @@ class Matcher:
) -> CloseResult:
"""期期全平两腿(到期/紧急);无永续。"""
pos = self.current_position()
if str(pos.get("status") or "") != "open" or not pos.get("group_id"):
st = str(pos.get("status") or "")
if st not in ("open", "closing") or not pos.get("group_id"):
return CloseResult(ok=False, detail="无期期持仓可平")
if not (
str(pos.get("hedge_mode") or "") == "option_option"