Audit fixes: LIVE symbols/fills/expiry/pending, security harden, add 更新说明.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,11 +7,12 @@ import time
|
||||
|
||||
from ..config import get_settings
|
||||
from ..env_store import live_ready
|
||||
from ..sim.liquidity import contracts_for_eth
|
||||
from ..sim.liquidity import contracts_for_eth, eth_from_contracts
|
||||
from ..sim.matcher import CloseResult, Matcher, OpenResult
|
||||
from ..sim.pricing import option_expiry_settle, option_intrinsic
|
||||
from ..strategy.session import get_session
|
||||
from .binance_trade import BinanceTradeClient
|
||||
from .symbols import live_settings, resolve_perp_inst_id
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -38,9 +39,11 @@ class BinanceLiveExecutor(Matcher):
|
||||
return base
|
||||
from .live_pnl import enrich_live_unrealized
|
||||
|
||||
s = get_settings()
|
||||
gid = base.get("group_id")
|
||||
open_at = None
|
||||
perp_inst = resolve_perp_inst_id(
|
||||
self.db, group_id=str(gid) if gid else None
|
||||
)
|
||||
if gid:
|
||||
g = self.db.fetchone(
|
||||
"SELECT open_at_ms, perp_inst_id FROM groups WHERE group_id=?",
|
||||
@@ -48,11 +51,8 @@ class BinanceLiveExecutor(Matcher):
|
||||
)
|
||||
if g:
|
||||
open_at = int(g["open_at_ms"] or 0) or None
|
||||
perp_inst = str(g["perp_inst_id"] or s.perp_inst_id)
|
||||
else:
|
||||
perp_inst = s.perp_inst_id
|
||||
else:
|
||||
perp_inst = s.perp_inst_id
|
||||
if g["perp_inst_id"]:
|
||||
perp_inst = str(g["perp_inst_id"])
|
||||
try:
|
||||
client = self._client()
|
||||
except Exception:
|
||||
@@ -83,7 +83,7 @@ class BinanceLiveExecutor(Matcher):
|
||||
if err:
|
||||
return OpenResult(ok=False, detail=err)
|
||||
|
||||
s = get_settings()
|
||||
s = live_settings()
|
||||
if self.has_open_position():
|
||||
st = self.position_status()
|
||||
return OpenResult(
|
||||
@@ -92,6 +92,7 @@ class BinanceLiveExecutor(Matcher):
|
||||
)
|
||||
|
||||
client = self._client()
|
||||
perp_inst = resolve_perp_inst_id(self.db)
|
||||
perp_qty = self.ledger.get_setting_float("perp_qty_eth", s.perp_qty_eth)
|
||||
opt_qty = self.ledger.get_setting_float("option_qty_eth", s.option_qty_eth)
|
||||
ct_mult = self._ct_mult(option_inst_id)
|
||||
@@ -107,6 +108,12 @@ class BinanceLiveExecutor(Matcher):
|
||||
logger.exception("binance live open option failed")
|
||||
return OpenResult(ok=False, detail=f"币安开期权失败: {e}")
|
||||
|
||||
filled_opt_contracts = float(opt_fill.sz) if opt_fill.sz and opt_fill.sz > 0 else float(
|
||||
int(round(opt_contracts))
|
||||
)
|
||||
opt_contracts = filled_opt_contracts
|
||||
opt_qty = eth_from_contracts(opt_contracts, ct_mult)
|
||||
|
||||
# 永续市价失败(多为保证金不足)→ 必须回滚期权
|
||||
try:
|
||||
if perp_side == "long":
|
||||
@@ -114,7 +121,7 @@ class BinanceLiveExecutor(Matcher):
|
||||
else:
|
||||
side, pos_side = "SELL", "SHORT"
|
||||
perp_fill_live = client.place_perp_market(
|
||||
symbol=s.perp_inst_id,
|
||||
symbol=perp_inst,
|
||||
side=side,
|
||||
qty_eth=perp_qty,
|
||||
position_side=pos_side,
|
||||
@@ -159,6 +166,12 @@ class BinanceLiveExecutor(Matcher):
|
||||
pf_px = float(perp_fill_live.avg_px)
|
||||
of_fee = float(opt_fill.fee)
|
||||
pf_fee = float(perp_fill_live.fee)
|
||||
filled_perp_qty = (
|
||||
float(perp_fill_live.sz)
|
||||
if perp_fill_live.sz and perp_fill_live.sz > 0
|
||||
else perp_qty
|
||||
)
|
||||
perp_qty = filled_perp_qty
|
||||
initial_premium = of_px * opt_qty
|
||||
of_notional = of_px * opt_qty
|
||||
pf_notional = pf_px * perp_qty
|
||||
@@ -194,7 +207,7 @@ class BinanceLiveExecutor(Matcher):
|
||||
option_side,
|
||||
perp_side,
|
||||
option_inst_id,
|
||||
s.perp_inst_id,
|
||||
perp_inst,
|
||||
strike,
|
||||
expiry_ymd,
|
||||
entry_index_px,
|
||||
@@ -235,7 +248,7 @@ class BinanceLiveExecutor(Matcher):
|
||||
"perp",
|
||||
"open",
|
||||
perp_side,
|
||||
s.perp_inst_id,
|
||||
perp_inst,
|
||||
perp_qty,
|
||||
None,
|
||||
pf_px,
|
||||
@@ -303,7 +316,7 @@ class BinanceLiveExecutor(Matcher):
|
||||
detail: str,
|
||||
) -> None:
|
||||
"""期权已成交、永续未开且回滚失败 → 落 half_open,禁止新开,待 repair。"""
|
||||
s = get_settings()
|
||||
perp_inst = resolve_perp_inst_id(self.db, group_id=group_id)
|
||||
initial_premium = of_px * opt_qty
|
||||
self.ledger.apply_cash(
|
||||
-(of_px * opt_qty + of_fee),
|
||||
@@ -331,7 +344,7 @@ class BinanceLiveExecutor(Matcher):
|
||||
option_side,
|
||||
perp_side,
|
||||
option_inst_id,
|
||||
s.perp_inst_id,
|
||||
perp_inst,
|
||||
strike,
|
||||
expiry_ymd,
|
||||
entry_index_px,
|
||||
@@ -475,7 +488,7 @@ class BinanceLiveExecutor(Matcher):
|
||||
if err:
|
||||
return CloseResult(ok=False, detail=err)
|
||||
|
||||
s = get_settings()
|
||||
s = live_settings()
|
||||
pos = self.current_position()
|
||||
st = str(pos.get("status") or "")
|
||||
if st == "half_open":
|
||||
@@ -490,6 +503,7 @@ class BinanceLiveExecutor(Matcher):
|
||||
opt_qty = float(pos["option_qty_eth"])
|
||||
perp_qty = float(pos["perp_qty_eth"])
|
||||
opt_contracts = float(pos["option_qty_contracts"] or 0)
|
||||
perp_inst = resolve_perp_inst_id(self.db, group_id=group_id)
|
||||
client = self._client()
|
||||
is_expiry = reason == "expiry"
|
||||
fee_rate = self._fee_rate()
|
||||
@@ -527,34 +541,45 @@ class BinanceLiveExecutor(Matcher):
|
||||
of_fee = float(prev["fee"] or 0)
|
||||
of_notional = float(prev["notional"] or (of_px * opt_qty))
|
||||
of_slip = float(prev["slip"] or 0)
|
||||
elif is_expiry:
|
||||
if intrinsic is None:
|
||||
return CloseResult(ok=False, detail="到期结算失败:缺行权价或标的价")
|
||||
of = option_expiry_settle(
|
||||
intrinsic=float(intrinsic), qty_eth=opt_qty, fee_rate=fee_rate
|
||||
)
|
||||
of_px, of_fee, of_slip, of_notional = of.fill_px, of.fee, of.slip, of.notional
|
||||
else:
|
||||
# 含到期:优先交易所真实平期权;失败且无内在价值时可本地结算
|
||||
try:
|
||||
opt_live = client.place_option_market(
|
||||
symbol=option_inst_id,
|
||||
side="SELL",
|
||||
quantity=opt_contracts,
|
||||
quantity=max(1.0, opt_contracts),
|
||||
reduce_only=True,
|
||||
)
|
||||
of_px = float(opt_live.avg_px)
|
||||
of_fee = float(opt_live.fee)
|
||||
filled_c = float(opt_live.sz) if opt_live.sz and opt_live.sz > 0 else opt_contracts
|
||||
opt_contracts = filled_c
|
||||
opt_qty = eth_from_contracts(opt_contracts, self._ct_mult(option_inst_id))
|
||||
of_notional = of_px * opt_qty
|
||||
except Exception as e:
|
||||
if not bypass_liquidity:
|
||||
if is_expiry and intrinsic is not None:
|
||||
of = option_expiry_settle(
|
||||
intrinsic=float(intrinsic), qty_eth=opt_qty, fee_rate=fee_rate
|
||||
)
|
||||
of_px, of_fee, of_slip, of_notional = (
|
||||
of.fill_px,
|
||||
of.fee,
|
||||
of.slip,
|
||||
of.notional,
|
||||
)
|
||||
logger.warning(
|
||||
"expiry option exchange close failed, local settle: %s", e
|
||||
)
|
||||
elif not bypass_liquidity:
|
||||
return CloseResult(
|
||||
ok=False,
|
||||
detail=f"币安平期权失败: {e}",
|
||||
liquidity_wait=True,
|
||||
)
|
||||
return CloseResult(ok=False, detail=f"币安平期权失败: {e}")
|
||||
else:
|
||||
return CloseResult(ok=False, detail=f"币安平期权失败: {e}")
|
||||
|
||||
# 期权已平:立刻落 pending,避免永续失败后重试再卖期权
|
||||
# 期权已平(或到期本地结算):立刻落 pending,避免永续失败后重试再卖期权
|
||||
self._mark_option_closed_perp_pending(
|
||||
group_id=group_id,
|
||||
option_inst_id=option_inst_id,
|
||||
@@ -574,7 +599,7 @@ class BinanceLiveExecutor(Matcher):
|
||||
else:
|
||||
side, pos_side = "BUY", "SHORT"
|
||||
perp_live = client.place_perp_market(
|
||||
symbol=s.perp_inst_id,
|
||||
symbol=perp_inst,
|
||||
side=side,
|
||||
qty_eth=perp_qty,
|
||||
position_side=pos_side,
|
||||
@@ -681,7 +706,8 @@ class BinanceLiveExecutor(Matcher):
|
||||
option_fill_already_written: bool,
|
||||
skip_option_cash: bool,
|
||||
) -> CloseResult:
|
||||
s = get_settings()
|
||||
s = live_settings()
|
||||
perp_inst = resolve_perp_inst_id(self.db, group_id=group_id)
|
||||
perp_side = str(pos["perp_side"])
|
||||
perp_qty = float(pos["perp_qty_eth"])
|
||||
opt_entry = float(pos["option_entry_px"])
|
||||
@@ -749,7 +775,7 @@ class BinanceLiveExecutor(Matcher):
|
||||
"perp",
|
||||
"close",
|
||||
"flat",
|
||||
s.perp_inst_id,
|
||||
perp_inst,
|
||||
perp_qty,
|
||||
None,
|
||||
pf_px,
|
||||
@@ -793,7 +819,7 @@ class BinanceLiveExecutor(Matcher):
|
||||
client=self._client(),
|
||||
exchange="binance",
|
||||
group_id=group_id,
|
||||
perp_inst_id=str((g2["perp_inst_id"] if g2 else None) or s.perp_inst_id),
|
||||
perp_inst_id=str((g2["perp_inst_id"] if g2 else None) or resolve_perp_inst_id(self.db, group_id=group_id)),
|
||||
open_at_ms=int(g2["open_at_ms"]) if g2 and g2["open_at_ms"] else None,
|
||||
local_net=float(net) if net is not None else None,
|
||||
)
|
||||
@@ -816,10 +842,7 @@ class BinanceLiveExecutor(Matcher):
|
||||
err = self._guard_live()
|
||||
if err:
|
||||
return CloseResult(ok=False, detail=err)
|
||||
if require_deep_otm and not self.option_is_deep_otm():
|
||||
return CloseResult(ok=False, detail="期权非远虚,应走双腿全平")
|
||||
|
||||
s = get_settings()
|
||||
pos = self.current_position()
|
||||
st = str(pos.get("status") or "")
|
||||
if st not in ("open", "option_closed_perp_pending") or not pos.get("group_id"):
|
||||
@@ -829,17 +852,57 @@ class BinanceLiveExecutor(Matcher):
|
||||
return self.close_group(reason=reason, bypass_liquidity=True)
|
||||
|
||||
group_id = str(pos["group_id"])
|
||||
option_inst_id = str(pos["option_inst_id"])
|
||||
option_side = str(pos["option_side"])
|
||||
opt_contracts = float(pos["option_qty_contracts"] or 0)
|
||||
opt_qty = float(pos["option_qty_eth"])
|
||||
perp_side = str(pos["perp_side"])
|
||||
perp_qty = float(pos["perp_qty_eth"])
|
||||
perp_entry = float(pos["perp_entry_px"])
|
||||
perp_inst = resolve_perp_inst_id(self.db, group_id=group_id)
|
||||
client = self._client()
|
||||
|
||||
# 优先尝试交易所平期权;成功则走双腿全平
|
||||
try:
|
||||
opt_live = client.place_option_market(
|
||||
symbol=option_inst_id,
|
||||
side="SELL",
|
||||
quantity=max(1.0, opt_contracts),
|
||||
reduce_only=True,
|
||||
)
|
||||
of_px = float(opt_live.avg_px)
|
||||
of_fee = float(opt_live.fee)
|
||||
filled_c = float(opt_live.sz) if opt_live.sz and opt_live.sz > 0 else opt_contracts
|
||||
opt_contracts = filled_c
|
||||
opt_qty = eth_from_contracts(opt_contracts, self._ct_mult(option_inst_id))
|
||||
of_notional = of_px * opt_qty
|
||||
self._mark_option_closed_perp_pending(
|
||||
group_id=group_id,
|
||||
option_inst_id=option_inst_id,
|
||||
opt_qty=opt_qty,
|
||||
opt_contracts=opt_contracts,
|
||||
of_px=of_px,
|
||||
of_fee=of_fee,
|
||||
of_notional=of_notional,
|
||||
of_slip=0.0,
|
||||
reason=reason,
|
||||
)
|
||||
return self.close_group(reason=reason, bypass_liquidity=True)
|
||||
except Exception as e:
|
||||
logger.warning("abandon: option exchange sell failed: %s", e)
|
||||
if require_deep_otm and not self.option_is_deep_otm():
|
||||
return CloseResult(
|
||||
ok=False,
|
||||
detail=f"期权平单失败且非远虚,应走双腿全平: {e}",
|
||||
)
|
||||
|
||||
try:
|
||||
if perp_side == "long":
|
||||
side, pos_side = "SELL", "LONG"
|
||||
else:
|
||||
side, pos_side = "BUY", "SHORT"
|
||||
perp_live = client.place_perp_market(
|
||||
symbol=s.perp_inst_id,
|
||||
symbol=perp_inst,
|
||||
side=side,
|
||||
qty_eth=perp_qty,
|
||||
position_side=pos_side,
|
||||
@@ -862,8 +925,6 @@ class BinanceLiveExecutor(Matcher):
|
||||
note=f"LIVE-BN close perp abandon option {reason}",
|
||||
)
|
||||
|
||||
option_inst_id = str(pos["option_inst_id"])
|
||||
option_side = str(pos["option_side"])
|
||||
strike = self._group_strike(group_id, option_inst_id)
|
||||
g = self.db.fetchone("SELECT * FROM groups WHERE group_id=?", (group_id,))
|
||||
expiry_ymd = str(g["expiry_ymd"]) if g and g["expiry_ymd"] else None
|
||||
@@ -893,7 +954,7 @@ class BinanceLiveExecutor(Matcher):
|
||||
"perp",
|
||||
"close",
|
||||
"flat",
|
||||
s.perp_inst_id,
|
||||
perp_inst,
|
||||
perp_qty,
|
||||
None,
|
||||
pf_px,
|
||||
|
||||
Reference in New Issue
Block a user