Harden residual mid-close: IOC partial fills, exchange reconcile, atomic book.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1149,12 +1149,72 @@ class BinanceLiveExecutor(Matcher):
|
||||
},
|
||||
)
|
||||
|
||||
def _sync_residual_contracts_with_exchange(self, row: dict) -> dict | None:
|
||||
option_inst_id = str(row.get("option_inst_id") or "")
|
||||
group_id = str(row.get("group_id") or "")
|
||||
client = self._client()
|
||||
ex_sz = exchange_option_abs_size(client, option_inst_id)
|
||||
if ex_sz is None:
|
||||
return row
|
||||
ct = self._ct_mult(option_inst_id)
|
||||
local_c = float(row.get("option_qty_contracts") or 0)
|
||||
if local_c <= 0:
|
||||
local_c = float(
|
||||
contracts_for_eth(float(row.get("option_qty_eth") or 0), ct) or 0
|
||||
)
|
||||
if ex_sz <= 1e-8:
|
||||
now_ms = int(time.time() * 1000)
|
||||
booked = self._book_residual_market_close(
|
||||
row,
|
||||
fill_px=0.0,
|
||||
fee=0.0,
|
||||
notional=0.0,
|
||||
slip=0.0,
|
||||
now_ms=now_ms,
|
||||
note="LIVE-BN residual already flat on exchange",
|
||||
exec_mode="LIVE",
|
||||
filled_contracts=0.0,
|
||||
remaining_contracts=0.0,
|
||||
close_reason="residual_premium_close",
|
||||
)
|
||||
logger.warning(
|
||||
"residual %s already flat on exchange; local settled=%s",
|
||||
group_id,
|
||||
booked is not None,
|
||||
)
|
||||
return None
|
||||
if local_c > ex_sz + 1e-8:
|
||||
rem_eth = eth_from_contracts(float(ex_sz), ct)
|
||||
init = float(row.get("initial_premium") or 0)
|
||||
local_eth = float(row.get("option_qty_eth") or 0)
|
||||
if local_eth > 1e-12:
|
||||
init = init * (rem_eth / local_eth)
|
||||
with self.db._lock:
|
||||
self.db._conn.execute(
|
||||
"""UPDATE residual_options SET
|
||||
option_qty_eth=?, option_qty_contracts=?, initial_premium=?
|
||||
WHERE group_id=? AND status='pending'""",
|
||||
(rem_eth, float(ex_sz), init, group_id),
|
||||
)
|
||||
self.db._conn.commit()
|
||||
row = {
|
||||
**row,
|
||||
"option_qty_eth": rem_eth,
|
||||
"option_qty_contracts": float(ex_sz),
|
||||
"initial_premium": init,
|
||||
}
|
||||
return row
|
||||
|
||||
def try_close_one_residual(self, row: dict) -> dict | None:
|
||||
"""LIVE-BN:权利金达标后按最新买一 IOC 限价卖出归档期权(不扫市价)。"""
|
||||
err = self._guard_live()
|
||||
if err:
|
||||
logger.warning("residual premium close blocked: %s", err)
|
||||
return None
|
||||
synced = self._sync_residual_contracts_with_exchange(row)
|
||||
if synced is None:
|
||||
return None
|
||||
row = synced
|
||||
skip, close_bid, _oq = self._evaluate_residual_premium_close(row)
|
||||
if skip or close_bid is None:
|
||||
if skip:
|
||||
@@ -1176,10 +1236,15 @@ class BinanceLiveExecutor(Matcher):
|
||||
)
|
||||
return None
|
||||
oq2 = self._quote_held_option(option_inst_id)
|
||||
bid_px = float(oq2.bid) if oq2 is not None and oq2.bid is not None else float(close_bid)
|
||||
if bid_px <= 0:
|
||||
if oq2 is None or oq2.bid is None:
|
||||
return None
|
||||
bid_px = float(oq2.bid)
|
||||
skip2 = self._residual_bid_gate(row, bid=bid_px, oq=oq2)
|
||||
if skip2:
|
||||
logger.debug(
|
||||
"residual premium close skip %s: bid vanished", row.get("group_id")
|
||||
"residual premium close recheck skip %s: %s",
|
||||
row.get("group_id"),
|
||||
skip2,
|
||||
)
|
||||
return None
|
||||
client = self._client()
|
||||
@@ -1200,26 +1265,125 @@ class BinanceLiveExecutor(Matcher):
|
||||
return None
|
||||
of_px = float(opt_live.avg_px)
|
||||
of_fee = float(opt_live.fee)
|
||||
filled_c = (
|
||||
float(opt_live.sz)
|
||||
if opt_live.sz and float(opt_live.sz) > 0
|
||||
else opt_contracts
|
||||
filled_c = float(opt_live.sz) if opt_live.sz and float(opt_live.sz) > 0 else 0.0
|
||||
if filled_c <= 1e-12:
|
||||
return None
|
||||
ex_left = exchange_option_abs_size(client, option_inst_id)
|
||||
remaining = (
|
||||
max(0.0, float(ex_left))
|
||||
if ex_left is not None
|
||||
else max(0.0, opt_contracts - filled_c)
|
||||
)
|
||||
opt_qty = eth_from_contracts(filled_c, self._ct_mult(option_inst_id))
|
||||
row = {**row, "option_qty_eth": opt_qty, "option_qty_contracts": filled_c}
|
||||
of_notional = of_px * opt_qty
|
||||
fill_eth = eth_from_contracts(filled_c, self._ct_mult(option_inst_id))
|
||||
now_ms = int(time.time() * 1000)
|
||||
return self._book_residual_market_close(
|
||||
row,
|
||||
fill_px=of_px,
|
||||
fee=of_fee,
|
||||
notional=of_notional,
|
||||
notional=of_px * fill_eth,
|
||||
slip=0.0,
|
||||
now_ms=now_ms,
|
||||
note=f"LIVE-BN residual mid-close at bid IOC px={bid_px}",
|
||||
exec_mode="LIVE",
|
||||
filled_contracts=filled_c,
|
||||
remaining_contracts=remaining,
|
||||
close_reason="residual_premium_close",
|
||||
)
|
||||
|
||||
def _try_exchange_flatten_residual(
|
||||
self, row: dict, *, force: bool = False
|
||||
) -> dict | None:
|
||||
err = self._guard_live()
|
||||
if err:
|
||||
return None
|
||||
option_inst_id = str(row.get("option_inst_id") or "")
|
||||
client = self._client()
|
||||
ex_sz = exchange_option_abs_size(client, option_inst_id)
|
||||
if ex_sz is not None and ex_sz <= 1e-8:
|
||||
return {
|
||||
"fill_px": 0.0,
|
||||
"fee": 0.0,
|
||||
"notional": 0.0,
|
||||
"slip": 0.0,
|
||||
"filled_contracts": 0.0,
|
||||
"remaining_contracts": 0.0,
|
||||
"note": "LIVE-BN residual flat on exchange before settle",
|
||||
"exec_mode": "LIVE",
|
||||
"close_reason": "emergency" if force else "expiry",
|
||||
}
|
||||
opt_contracts = float(row.get("option_qty_contracts") or 0)
|
||||
if ex_sz is not None and ex_sz > 0:
|
||||
opt_contracts = float(ex_sz)
|
||||
if opt_contracts <= 0:
|
||||
opt_contracts = float(
|
||||
contracts_for_eth(
|
||||
float(row.get("option_qty_eth") or 0),
|
||||
self._ct_mult(option_inst_id),
|
||||
)
|
||||
or 0
|
||||
)
|
||||
if opt_contracts <= 0:
|
||||
return None
|
||||
oq = self._quote_held_option(option_inst_id)
|
||||
bid_px = float(oq.bid) if oq is not None and oq.bid is not None else 0.0
|
||||
try:
|
||||
if bid_px > 0 and not force:
|
||||
opt_live = client.place_option_ioc(
|
||||
symbol=option_inst_id,
|
||||
side="SELL",
|
||||
quantity=max(1.0, opt_contracts),
|
||||
price=bid_px,
|
||||
reduce_only=True,
|
||||
)
|
||||
else:
|
||||
opt_live = client.place_option_market(
|
||||
symbol=option_inst_id,
|
||||
side="SELL",
|
||||
quantity=max(1.0, opt_contracts),
|
||||
reduce_only=True,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"residual exchange flatten failed %s force=%s: %s",
|
||||
row.get("group_id"),
|
||||
force,
|
||||
e,
|
||||
)
|
||||
return None
|
||||
filled_c = float(opt_live.sz) if opt_live.sz and float(opt_live.sz) > 0 else 0.0
|
||||
if filled_c <= 1e-12 and force:
|
||||
try:
|
||||
opt_live = client.place_option_market(
|
||||
symbol=option_inst_id,
|
||||
side="SELL",
|
||||
quantity=max(1.0, opt_contracts),
|
||||
reduce_only=True,
|
||||
)
|
||||
filled_c = (
|
||||
float(opt_live.sz) if opt_live.sz and float(opt_live.sz) > 0 else 0.0
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning("residual emergency market sell failed: %s", e)
|
||||
return None
|
||||
if filled_c <= 1e-12:
|
||||
return None
|
||||
of_px = float(opt_live.avg_px)
|
||||
of_fee = float(opt_live.fee)
|
||||
fill_eth = eth_from_contracts(filled_c, self._ct_mult(option_inst_id))
|
||||
ex_left = exchange_option_abs_size(client, option_inst_id)
|
||||
remaining = max(0.0, float(ex_left)) if ex_left is not None else 0.0
|
||||
return {
|
||||
"fill_px": of_px,
|
||||
"fee": of_fee,
|
||||
"notional": of_px * fill_eth,
|
||||
"slip": 0.0,
|
||||
"filled_contracts": filled_c,
|
||||
"remaining_contracts": remaining,
|
||||
"note": f"LIVE-BN residual exchange flatten force={force}",
|
||||
"exec_mode": "LIVE",
|
||||
"close_reason": "emergency" if force else "expiry",
|
||||
}
|
||||
|
||||
def close_perp_abandon_option(
|
||||
self, *, reason: str = "target_perp_only", require_deep_otm: bool = True
|
||||
) -> CloseResult:
|
||||
|
||||
Reference in New Issue
Block a user