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:
+50
-21
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user