Block option auto-close on stub bids far below mark.

Reject target and depth closes when bid is a residual tick, and show invalid-bid UI instead of recycling at junk prices.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-15 21:05:54 +08:00
parent e15eca76f3
commit c025c06fac
7 changed files with 387 additions and 21 deletions
+99 -2
View File
@@ -6,7 +6,14 @@ import time
from typing import Any, Callable
from lib.options.options_db import init_options_tables
from lib.options.options_pricing_lib import estimate_close_by_bids, total_premium
from lib.options.options_pricing_lib import (
close_ref_prices,
estimate_close_by_bids,
fetch_option_mark_px,
filter_bids_for_close,
is_stub_bid_px,
total_premium,
)
def _safe_float(v: Any) -> float | None:
@@ -18,6 +25,24 @@ def _safe_float(v: Any) -> float | None:
return None
def _pos_close_refs(ex: Any, pos: dict[str, Any], quote: dict[str, Any] | None = None) -> tuple[float | None, float | None]:
from lib.exchange.okx_options_lib import option_fields_from_inst_id
inst_id = str(pos.get("instId") or pos.get("inst_id") or "")
mark = _safe_float(pos.get("markPx")) or _safe_float((quote or {}).get("mark_px") or (quote or {}).get("mark"))
if mark is None:
mark = fetch_option_mark_px(ex, inst_id)
opt_type = pos.get("optType") or (quote or {}).get("opt_type")
strike = _safe_float(pos.get("stk")) or _safe_float((quote or {}).get("strike"))
if not opt_type or strike is None:
pt, ps = option_fields_from_inst_id(inst_id)
opt_type = opt_type or pt
if strike is None:
strike = ps
idx = _safe_float(pos.get("idxPx")) or _safe_float((quote or {}).get("index_px"))
return close_ref_prices(mark_px=mark, opt_type=str(opt_type or ""), strike=strike, index_px=idx)
def ensure_target_tables(conn: sqlite3.Connection) -> None:
init_options_tables(conn)
conn.execute(
@@ -290,6 +315,46 @@ def close_option_by_bid_depth(
return {"ok": False, "msg": "可平张数不足", "already_flat": True}
td_mode = str(pos.get("mgnMode") or cfg.get("td_mode") or "isolated")
pos_side = _pos_side_from_position(pos) or "net"
mark_px, intrinsic_px = _pos_close_refs(ex, pos, q)
def _cancel_sell_pending() -> None:
try:
pending = ex.private_get_trade_orders_pending({"instType": "OPTION", "instId": inst_id}) or {}
for o in pending.get("data") or []:
if str(o.get("side") or "").lower() != "sell":
continue
oid = o.get("ordId")
if not oid:
continue
try:
ex.private_post_trade_cancel_order({"instId": inst_id, "ordId": oid})
except Exception:
pass
except Exception:
pass
# 残档买盘:禁止自动按买盘平仓(并撤掉可能已挂的异常低价卖单)
try:
book0 = cfg["fetch_option_book_depth"](ex, inst_id, 5)
usable0, stub_only0, stub_reason0 = filter_bids_for_close(
book0.get("bids") or [], mark_px=mark_px, intrinsic_px=intrinsic_px
)
raw_bid0 = None
if book0.get("bids"):
raw_bid0 = _safe_float((book0.get("bids") or [{}])[0].get("px"))
if not usable0:
bid_chk = raw_bid0 or _safe_float(q.get("bid"))
stub, stub_reason = is_stub_bid_px(bid_chk, mark_px=mark_px, intrinsic_px=intrinsic_px)
if stub or stub_only0:
_cancel_sell_pending()
return {
"ok": False,
"msg": stub_reason0 or stub_reason or "暂无有效买盘,禁止自动平仓",
"stopped_reason": "stub_bid",
"auto_close_blocked": True,
}
except Exception:
pass
# 已有未成交卖平单时先等成交,避免每轮撤单重挂反复推送/吃档
try:
@@ -378,12 +443,36 @@ def close_option_by_bid_depth(
break
remaining = min(remaining, current_avail)
book = cfg["fetch_option_book_depth"](ex, inst_id, 5)
preview = estimate_close_by_bids(book.get("bids") or [], remaining, ct_mult=ct_mult)
preview = estimate_close_by_bids(
book.get("bids") or [],
remaining,
ct_mult=ct_mult,
mark_px=mark_px,
intrinsic_px=intrinsic_px,
)
if preview.get("auto_close_blocked") or preview.get("bid_invalid"):
stopped_reason = "stub_bid"
_cancel_sell_pending()
return {
"ok": False,
"msg": preview.get("bid_invalid_reason") or "暂无有效买盘,禁止自动平仓",
"stopped_reason": "stub_bid",
"auto_close_blocked": True,
}
levels = preview.get("levels") or []
if not levels:
# 无买盘深度时仅允许真实买一价,不用标记价挂单
q2 = cfg["quote_option_contract"](ex, inst_id)
bid_px = _safe_float(q2.get("bid")) or _safe_float(q.get("bid"))
stub, stub_reason = is_stub_bid_px(bid_px, mark_px=mark_px, intrinsic_px=intrinsic_px)
if stub:
stopped_reason = "stub_bid"
return {
"ok": False,
"msg": stub_reason or "暂无有效买盘,禁止自动平仓",
"stopped_reason": "stub_bid",
"auto_close_blocked": True,
}
if bid_px is None or bid_px <= 0:
stopped_reason = "no_bid"
break
@@ -394,6 +483,14 @@ def close_option_by_bid_depth(
if level_sheets <= 0 or level_px <= 0:
stopped_reason = "invalid_bid_depth"
break
stub_lv, stub_lv_reason = is_stub_bid_px(level_px, mark_px=mark_px, intrinsic_px=intrinsic_px)
if stub_lv:
return {
"ok": False,
"msg": stub_lv_reason or "暂无有效买盘,禁止自动平仓",
"stopped_reason": "stub_bid",
"auto_close_blocked": True,
}
before_avail = current_avail
order = cfg["place_option_limit_order"](
ex,