Add option close liquidity gate with 30% bid/mark cap.

Block new opens while flat legs wait; emergency close bypasses the check.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-25 09:05:04 +08:00
parent 8c14759e78
commit 9fd2a842af
11 changed files with 136 additions and 28 deletions
+50 -21
View File
@@ -11,7 +11,7 @@ from ..exchange import get_exchange
from ..models.db import Database, get_db
from ..strategy.session import get_session
from .ledger import Ledger
from .liquidity import bid_covers_eth, contracts_for_eth
from .liquidity import bid_covers_eth, bid_mark_ok, contracts_for_eth
from .pricing import option_fill, perp_fill
@@ -54,6 +54,18 @@ class Matcher:
assert row is not None
return dict(row)
def has_open_position(self) -> bool:
pos = self.current_position()
return pos.get("status") == "open" and bool(pos.get("group_id"))
def _liquidity_wait(self, group_id: str, detail: str) -> CloseResult:
note = f"liquidity_wait:{int(time.time())}:{detail[:80]}"
self.db.execute(
"UPDATE groups SET note=? WHERE group_id=? AND status='open'",
(note, group_id),
)
return CloseResult(ok=False, detail=detail, liquidity_wait=True)
def open_group(
self,
*,
@@ -219,7 +231,11 @@ class Matcher:
},
)
def close_group(self, *, reason: str) -> CloseResult:
def close_group(self, *, reason: str, bypass_liquidity: bool = False) -> CloseResult:
"""
全平一组。默认校验期权买一深度 + 买一/标记偏差(默认≤30%)。
bypass_liquidity=True:紧急全平可绕过(仍需有可用买一价才能成交;无买一时用标记近似)。
"""
s = get_settings()
pos = self.current_position()
if pos.get("status") != "open" or not pos.get("group_id"):
@@ -236,27 +252,40 @@ class Matcher:
oq = get_exchange().quote(option_inst_id) or (
snap.call if option_side == "call" else snap.put
)
if not oq or oq.bid is None:
return CloseResult(ok=False, detail="期权买一不可用", liquidity_wait=True)
if not oq:
return CloseResult(
ok=False,
detail="期权盘口不可用",
liquidity_wait=not bypass_liquidity,
)
ct_mult = self._ct_mult(option_inst_id)
need_eth = float(pos["option_qty_eth"] or s.option_qty_eth)
if not bid_covers_eth(
bid_sz_contracts=oq.bid_sz,
ct_mult=ct_mult,
need_eth=need_eth,
):
# 记流动性不足到组 note,不改变仓位
note = f"liquidity_wait:{int(time.time())}"
self.db.execute(
"UPDATE groups SET note=? WHERE group_id=? AND status='open'",
(note, group_id),
)
return CloseResult(
ok=False,
detail="期权买一流动性不足",
liquidity_wait=True,
max_dev = self.ledger.get_setting_float(
"close_bid_mark_max_pct", s.close_bid_mark_max_pct
)
close_bid = oq.bid
if not bypass_liquidity:
if close_bid is None:
return self._liquidity_wait(group_id, "期权买一不可用")
if not bid_covers_eth(
bid_sz_contracts=oq.bid_sz,
ct_mult=ct_mult,
need_eth=need_eth,
):
return self._liquidity_wait(group_id, "期权买一流动性不足")
ok_dev, why = bid_mark_ok(
bid=close_bid, mark=oq.mark_px, max_dev_pct=max_dev
)
if not ok_dev:
return self._liquidity_wait(group_id, why)
else:
# 紧急:优先买一,否则用标记价近似成交(SIM)
if close_bid is None:
close_bid = oq.mark_px
if close_bid is None:
return CloseResult(ok=False, detail="紧急全平失败:无买一/标记价")
fee_rate = self._fee_rate()
perp_side = str(pos["perp_side"])
@@ -275,8 +304,8 @@ class Matcher:
)
of = option_fill(
action="close",
bid=float(oq.bid),
ask=float(oq.ask or oq.bid),
bid=float(close_bid),
ask=float(oq.ask or close_bid),
qty_eth=opt_qty,
fee_rate=fee_rate,
)