Mirror option expiry settlements from exchange bills; debit OO LIVE open premiums.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+134
-24
@@ -871,6 +871,23 @@ class OkxLiveExecutor(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)
|
||||
# 与永期 LIVE 开仓一致:镜像扣权利金+手续费
|
||||
self.ledger.apply_cash(
|
||||
-(call_prem + call_fee),
|
||||
kind="open_option",
|
||||
group_id=group_id,
|
||||
note=f"LIVE 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 oo open put {group_id}",
|
||||
allow_negative=True,
|
||||
)
|
||||
now = int(time.time() * 1000)
|
||||
with self.db._lock:
|
||||
self.db._conn.execute(
|
||||
@@ -892,8 +909,7 @@ class OkxLiveExecutor(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",
|
||||
@@ -1069,6 +1085,20 @@ class OkxLiveExecutor(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 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,
|
||||
@@ -1082,11 +1112,11 @@ class OkxLiveExecutor(Matcher):
|
||||
inst,
|
||||
qty,
|
||||
contracts,
|
||||
px,
|
||||
px,
|
||||
fee,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
notional,
|
||||
now + i,
|
||||
"LIVE",
|
||||
),
|
||||
@@ -1108,7 +1138,7 @@ class OkxLiveExecutor(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,
|
||||
),
|
||||
)
|
||||
@@ -1336,7 +1366,7 @@ class OkxLiveExecutor(Matcher):
|
||||
of_slip = 0.0
|
||||
option_apply_cash = False
|
||||
elif is_expiry:
|
||||
# 到期:交易所自动结算期权,本地只平永续;不卖期权、不用 intrinsic 发明 fill
|
||||
# 到期:交易所自动结算期权,本地只平永续;交割现金从账单镜像(禁止 intrinsic)
|
||||
ex_opt = exchange_option_abs_size(client, option_inst_id)
|
||||
if ex_opt is not None and ex_opt > 1e-8:
|
||||
logger.warning(
|
||||
@@ -1345,13 +1375,17 @@ class OkxLiveExecutor(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(
|
||||
"expiry: skip option order, close perp only group=%s", group_id
|
||||
"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,
|
||||
@@ -1363,7 +1397,7 @@ class OkxLiveExecutor(Matcher):
|
||||
of_notional=of_notional,
|
||||
of_slip=of_slip,
|
||||
reason=reason,
|
||||
apply_cash=False,
|
||||
apply_cash=option_apply_cash,
|
||||
)
|
||||
pending_perp_only = True
|
||||
else:
|
||||
@@ -1534,6 +1568,49 @@ class OkxLiveExecutor(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]:
|
||||
"""返回 (fill_px, fee, notional, cash_to_apply)。查不到交割则全 0。"""
|
||||
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"])
|
||||
try:
|
||||
ex_name = load_runtime_settings().exchange
|
||||
except Exception:
|
||||
ex_name = get_settings().exchange
|
||||
st = fetch_option_settlement(
|
||||
self._client(),
|
||||
exchange=str(ex_name or "okx"),
|
||||
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(
|
||||
"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,
|
||||
*,
|
||||
@@ -1838,16 +1915,32 @@ class OkxLiveExecutor(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 residual already flat on exchange",
|
||||
note=(
|
||||
f"LIVE residual already flat; settlement px={px}"
|
||||
if notional > 0 or fee > 0
|
||||
else "LIVE 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",
|
||||
)
|
||||
@@ -2002,14 +2095,31 @@ class OkxLiveExecutor(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 residual flat on exchange before settle",
|
||||
"note": (
|
||||
f"LIVE residual flat on exchange; settlement px={px}"
|
||||
if notional > 0 or fee > 0
|
||||
else "LIVE residual flat on exchange before settle"
|
||||
),
|
||||
"exec_mode": "LIVE",
|
||||
"close_reason": "emergency" if force else "expiry",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user