Close residual options when premium recovers above configurable threshold.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -829,6 +829,232 @@ class Matcher:
|
||||
)
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
def _residual_min_premium_pct(self) -> float:
|
||||
s = get_settings()
|
||||
return float(
|
||||
self.ledger.get_setting_float(
|
||||
"residual_min_premium_pct", s.residual_min_premium_pct
|
||||
)
|
||||
)
|
||||
|
||||
def _evaluate_residual_premium_close(
|
||||
self, row: dict[str, Any]
|
||||
) -> tuple[str | None, float | None, Any]:
|
||||
"""
|
||||
残留中途平前置:权利金比例 + 买一流动性。
|
||||
返回 (skip_reason, close_bid, option_quote);skip_reason 非空则本轮不卖。
|
||||
"""
|
||||
s = get_settings()
|
||||
initial_premium = float(row.get("initial_premium") or 0)
|
||||
opt_qty = float(row.get("option_qty_eth") or 0)
|
||||
if initial_premium <= 0 or opt_qty <= 0:
|
||||
return ("invalid_initial_premium_or_qty", None, None)
|
||||
|
||||
option_inst_id = str(row.get("option_inst_id") or "")
|
||||
if not option_inst_id:
|
||||
return ("missing_option_inst", None, None)
|
||||
|
||||
oq = self._quote_held_option(option_inst_id)
|
||||
if oq is None or oq.bid is None:
|
||||
return ("option_bid_unavailable", None, None)
|
||||
|
||||
close_bid = float(oq.bid)
|
||||
current_premium = close_bid * opt_qty
|
||||
min_pct = self._residual_min_premium_pct()
|
||||
threshold = initial_premium * (min_pct / 100.0)
|
||||
if current_premium + 1e-12 < threshold:
|
||||
return (
|
||||
f"premium_below_threshold curr={current_premium:.4f} "
|
||||
f"need>={threshold:.4f} ({min_pct:g}%)",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
|
||||
ct_mult = self._ct_mult(option_inst_id)
|
||||
if not bid_covers_eth(
|
||||
bid_sz_contracts=oq.bid_sz,
|
||||
ct_mult=ct_mult,
|
||||
need_eth=opt_qty,
|
||||
):
|
||||
return ("option_bid_liquidity_insufficient", None, None)
|
||||
|
||||
max_dev = self.ledger.get_setting_float(
|
||||
"close_bid_mark_max_pct", s.close_bid_mark_max_pct
|
||||
)
|
||||
ok_dev, why = bid_mark_ok(bid=close_bid, mark=oq.mark_px, max_dev_pct=max_dev)
|
||||
if not ok_dev:
|
||||
return (why or "bid_mark_deviation", None, None)
|
||||
|
||||
strike = row.get("strike")
|
||||
spot = self._close_spot_px(get_session().snapshot())
|
||||
intrinsic: float | None = None
|
||||
if strike is not None and spot is not None:
|
||||
intrinsic = option_intrinsic(
|
||||
option_side=str(row["option_side"]),
|
||||
strike=float(strike),
|
||||
spot=float(spot),
|
||||
)
|
||||
resolved = resolve_option_close_bid(
|
||||
bid=close_bid,
|
||||
mark=oq.mark_px,
|
||||
intrinsic=intrinsic,
|
||||
bypass_liquidity=False,
|
||||
)
|
||||
if resolved is None:
|
||||
return ("option_close_px_unavailable", None, None)
|
||||
return (None, float(resolved), oq)
|
||||
|
||||
def _book_residual_market_close(
|
||||
self,
|
||||
row: dict[str, Any],
|
||||
*,
|
||||
fill_px: float,
|
||||
fee: float,
|
||||
notional: float,
|
||||
slip: float,
|
||||
now_ms: int,
|
||||
note: str,
|
||||
exec_mode: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""买一卖出残留后的入账与结清(SIM/LIVE 共用)。"""
|
||||
group_id = str(row["group_id"])
|
||||
opt_qty = float(row["option_qty_eth"])
|
||||
opt_entry = float(row["option_entry_px"])
|
||||
opt_pnl = (float(fill_px) - opt_entry) * opt_qty
|
||||
opt_cash = float(notional) - float(fee)
|
||||
self.ledger.apply_cash(
|
||||
opt_cash,
|
||||
kind="close_option",
|
||||
group_id=group_id,
|
||||
note=note,
|
||||
allow_negative=not get_settings().is_sim,
|
||||
)
|
||||
|
||||
fill_cols = (
|
||||
"group_id, leg, action, side, inst_id, qty_eth, qty_contracts, "
|
||||
"base_px, fill_px, fee, slip, notional, ts_ms"
|
||||
)
|
||||
fill_vals: list[Any] = [
|
||||
group_id,
|
||||
"option",
|
||||
"close",
|
||||
"flat",
|
||||
str(row["option_inst_id"]),
|
||||
opt_qty,
|
||||
float(row["option_qty_contracts"] or 0),
|
||||
float(fill_px),
|
||||
float(fill_px),
|
||||
float(fee),
|
||||
float(slip),
|
||||
float(notional),
|
||||
now_ms,
|
||||
]
|
||||
if exec_mode:
|
||||
fill_cols += ", exec_mode"
|
||||
fill_vals.append(exec_mode)
|
||||
|
||||
with self.db._lock:
|
||||
self.db._conn.execute(
|
||||
f"""INSERT INTO fills({fill_cols})
|
||||
VALUES ({",".join("?" for _ in fill_vals)})""",
|
||||
tuple(fill_vals),
|
||||
)
|
||||
fills = self.db._conn.execute(
|
||||
"SELECT * FROM fills WHERE group_id=? ORDER BY id ASC", (group_id,)
|
||||
).fetchall()
|
||||
from ..sim.pnl import summarize_fills_pnl
|
||||
|
||||
summary = summarize_fills_pnl(list(fills))
|
||||
net = summary.get("net_pnl")
|
||||
if net is None:
|
||||
net = opt_pnl - float(fee)
|
||||
g = self.db._conn.execute(
|
||||
"SELECT fees, slip_cost FROM groups WHERE group_id=?", (group_id,)
|
||||
).fetchone()
|
||||
fees = float(g["fees"] or 0) + float(fee) if g else float(fee)
|
||||
slip_total = float(g["slip_cost"] or 0) + float(slip) if g else float(slip)
|
||||
self.db._conn.execute(
|
||||
"""UPDATE residual_options SET status=?, settled_at_ms=?, settle_px=?, settle_pnl=?, note=?
|
||||
WHERE group_id=?""",
|
||||
(
|
||||
"settled",
|
||||
now_ms,
|
||||
float(fill_px),
|
||||
opt_pnl,
|
||||
note,
|
||||
group_id,
|
||||
),
|
||||
)
|
||||
self.db._conn.execute(
|
||||
"""UPDATE groups SET status=?, close_at_ms=COALESCE(close_at_ms, ?),
|
||||
close_reason=COALESCE(close_reason, ?), realized_pnl=?, fees=?, slip_cost=?
|
||||
WHERE group_id=?""",
|
||||
(
|
||||
"closed",
|
||||
now_ms,
|
||||
"residual_premium_close",
|
||||
float(net),
|
||||
fees,
|
||||
slip_total,
|
||||
group_id,
|
||||
),
|
||||
)
|
||||
self.db._conn.commit()
|
||||
|
||||
return {
|
||||
"group_id": group_id,
|
||||
"option_pnl": opt_pnl,
|
||||
"settle_px": float(fill_px),
|
||||
"net_pnl": float(net),
|
||||
"reason": "residual_premium_close",
|
||||
"current_premium": float(fill_px) * opt_qty,
|
||||
"initial_premium": float(row.get("initial_premium") or 0),
|
||||
}
|
||||
|
||||
def try_close_one_residual(self, row: dict[str, Any]) -> dict[str, Any] | None:
|
||||
"""SIM:权利金达标且流动性通过则本地吃买一平残留。"""
|
||||
skip, close_bid, oq = self._evaluate_residual_premium_close(row)
|
||||
if skip or close_bid is None or oq is None:
|
||||
if skip:
|
||||
logger.debug(
|
||||
"residual premium close skip %s: %s",
|
||||
row.get("group_id"),
|
||||
skip,
|
||||
)
|
||||
return None
|
||||
of = option_fill(
|
||||
action="close",
|
||||
bid=float(close_bid),
|
||||
ask=float(oq.ask or close_bid),
|
||||
qty_eth=float(row["option_qty_eth"]),
|
||||
fee_rate=self._fee_rate(),
|
||||
)
|
||||
now_ms = int(time.time() * 1000)
|
||||
return self._book_residual_market_close(
|
||||
row,
|
||||
fill_px=of.fill_px,
|
||||
fee=of.fee,
|
||||
notional=of.notional,
|
||||
slip=of.slip,
|
||||
now_ms=now_ms,
|
||||
note="residual mid-close by premium recovery",
|
||||
)
|
||||
|
||||
def try_close_pending_residuals(self) -> list[dict[str, Any]]:
|
||||
"""巡检全部 pending 残留,尝试权利金回收平仓。"""
|
||||
out: list[dict[str, Any]] = []
|
||||
for row in self.list_residual_options(pending_only=True):
|
||||
try:
|
||||
r = self.try_close_one_residual(row)
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"try_close_one_residual failed group=%s", row.get("group_id")
|
||||
)
|
||||
continue
|
||||
if r:
|
||||
out.append(r)
|
||||
return out
|
||||
|
||||
def settle_due_residuals(self, *, now_ms: int | None = None) -> list[dict[str, Any]]:
|
||||
"""到期结算所有 pending 残留期权(不扫描进活跃组平仓)。"""
|
||||
now = int(now_ms if now_ms is not None else time.time() * 1000)
|
||||
|
||||
Reference in New Issue
Block a user