Mirror option expiry settlements from exchange bills; debit OO LIVE open premiums.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-08 16:02:16 +08:00
parent 8d67f3fc6c
commit 3491681c28
10 changed files with 739 additions and 54 deletions
+131 -23
View File
@@ -829,6 +829,22 @@ class BinanceLiveExecutor(Matcher):
put_qty = eth_from_contracts(put_contracts, put_ct)
call_prem = of_px * call_qty
put_prem = pf_px * put_qty
call_fee = float(getattr(call_fill, "fee", 0) or 0)
put_fee = float(getattr(put_fill, "fee", 0) or 0)
self.ledger.apply_cash(
-(call_prem + call_fee),
kind="open_option",
group_id=group_id,
note=f"LIVE-BN oo open call {group_id}",
allow_negative=True,
)
self.ledger.apply_cash(
-(put_prem + put_fee),
kind="open_option",
group_id=group_id,
note=f"LIVE-BN oo open put {group_id}",
allow_negative=True,
)
now = int(time.time() * 1000)
with self.db._lock:
self.db._conn.execute(
@@ -850,8 +866,7 @@ class BinanceLiveExecutor(Matcher):
entry_index_px,
call_prem,
now,
float(getattr(call_fill, "fee", 0) or 0)
+ float(getattr(put_fill, "fee", 0) or 0),
call_fee + put_fee,
0.0,
"LIVE",
"option_option",
@@ -1023,6 +1038,20 @@ class BinanceLiveExecutor(Matcher):
for i, (leg, inst, qty, contracts) in enumerate(legs):
if not inst:
continue
if reason == "expiry":
px, fee, notional, cash = self._live_option_settlement_fill(
option_inst_id=inst, qty_eth=qty, group_id=group_id
)
else:
px, fee, notional, cash = 0.0, 0.0, 0.0, 0.0
if abs(cash) > 1e-12:
self.ledger.apply_cash(
cash,
kind="close_option",
group_id=group_id,
note=f"LIVE-BN oo settle {leg} {reason}",
allow_negative=True,
)
with self.db._lock:
self.db._conn.execute(
"""INSERT INTO fills(group_id, leg, action, side, inst_id, qty_eth, qty_contracts,
@@ -1036,11 +1065,11 @@ class BinanceLiveExecutor(Matcher):
inst,
qty,
contracts,
px,
px,
fee,
0.0,
0.0,
0.0,
0.0,
0.0,
notional,
now + i,
"LIVE",
),
@@ -1062,7 +1091,7 @@ class BinanceLiveExecutor(Matcher):
int(time.time() * 1000),
reason,
net,
f"oo full close {reason} exchange_flat_mirror",
f"oo full close {reason} exchange_flat_or_settle",
group_id,
),
)
@@ -1296,13 +1325,17 @@ class BinanceLiveExecutor(Matcher):
ex_opt,
group_id,
)
of_px = 0.0
of_fee = 0.0
of_notional = 0.0
of_px, of_fee, of_notional, settle_cash = self._live_option_settlement_fill(
option_inst_id=option_inst_id,
qty_eth=opt_qty,
group_id=group_id,
)
of_slip = 0.0
option_apply_cash = False
option_apply_cash = abs(settle_cash) > 1e-12
logger.info(
"bn expiry: skip option order, close perp only group=%s", group_id
"bn expiry: skip option order, close perp only group=%s settle_cash=%.4f",
group_id,
settle_cash,
)
self._ensure_option_closed_perp_pending(
group_id=group_id,
@@ -1314,7 +1347,7 @@ class BinanceLiveExecutor(Matcher):
of_notional=of_notional,
of_slip=of_slip,
reason=reason,
apply_cash=False,
apply_cash=option_apply_cash,
)
pending_perp_only = True
else:
@@ -1474,6 +1507,44 @@ class BinanceLiveExecutor(Matcher):
settle_index_px=spot,
)
def _live_option_settlement_fill(
self,
*,
option_inst_id: str,
qty_eth: float,
group_id: str | None = None,
begin_ms: int | None = None,
) -> tuple[float, float, float, float]:
from .option_settle import fetch_option_settlement, settlement_to_fill
open_ms = begin_ms
if open_ms is None and group_id:
g = self.db.fetchone(
"SELECT open_at_ms FROM groups WHERE group_id=?", (group_id,)
)
if g and g["open_at_ms"]:
open_ms = int(g["open_at_ms"])
st = fetch_option_settlement(
self._client(),
exchange="binance",
option_inst_id=option_inst_id,
qty_eth=float(qty_eth),
begin_ms=open_ms,
)
px, fee, notional = settlement_to_fill(st, qty_eth=float(qty_eth))
cash = float(st.cash) if st.found else 0.0
if st.found:
logger.info(
"bn option settlement %s source=%s px=%.6f fee=%.6f cash=%.6f (%s)",
option_inst_id,
st.source,
px,
fee,
cash,
st.detail,
)
return px, fee, notional, cash
def _ensure_option_closed_perp_pending(
self,
*,
@@ -1772,16 +1843,36 @@ class BinanceLiveExecutor(Matcher):
)
if ex_sz <= 1e-8:
now_ms = int(time.time() * 1000)
qty_eth = float(row.get("option_qty_eth") or 0)
begin = None
try:
begin = int(row.get("created_at_ms") or 0) or None
except Exception:
begin = None
px, fee, notional, _cash = self._live_option_settlement_fill(
option_inst_id=option_inst_id,
qty_eth=qty_eth,
group_id=group_id,
begin_ms=begin,
)
booked = self._book_residual_market_close(
row,
fill_px=0.0,
fee=0.0,
notional=0.0,
fill_px=px,
fee=fee,
notional=notional,
slip=0.0,
now_ms=now_ms,
note="LIVE-BN residual already flat on exchange",
note=(
f"LIVE-BN residual already flat; settlement px={px}"
if notional > 0 or fee > 0
else "LIVE-BN residual already flat on exchange"
),
exec_mode="LIVE",
filled_contracts=0.0,
filled_contracts=(
float(row.get("option_qty_contracts") or 0)
if (notional > 0 or fee > 0)
else 0.0
),
remaining_contracts=0.0,
close_reason="residual_premium_close",
)
@@ -1927,14 +2018,31 @@ class BinanceLiveExecutor(Matcher):
)
return None
if ex_sz <= 1e-8:
qty_eth = float(row.get("option_qty_eth") or 0)
begin = None
try:
begin = int(row.get("created_at_ms") or 0) or None
except Exception:
begin = None
gid = str(row.get("group_id") or "")
px, fee, notional, _cash = self._live_option_settlement_fill(
option_inst_id=option_inst_id,
qty_eth=qty_eth,
group_id=gid or None,
begin_ms=begin,
)
return {
"fill_px": 0.0,
"fee": 0.0,
"notional": 0.0,
"fill_px": px,
"fee": fee,
"notional": notional,
"slip": 0.0,
"filled_contracts": 0.0,
"filled_contracts": float(row.get("option_qty_contracts") or 0),
"remaining_contracts": 0.0,
"note": "LIVE-BN residual flat on exchange before settle",
"note": (
f"LIVE-BN residual flat; settlement px={px}"
if notional > 0 or fee > 0
else "LIVE-BN residual flat on exchange before settle"
),
"exec_mode": "LIVE",
"close_reason": "emergency" if force else "expiry",
}