Add Binance live trading, anti-stuck open/close recovery, and configurable rate limits.

OKX/Binance LIVE share half_open and option_closed_perp_pending repair paths; private REST throttles default to 1s and are tunable in settings.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-26 21:35:10 +08:00
parent e666230d0b
commit dbc86a1ce6
23 changed files with 2381 additions and 385 deletions
+6 -2
View File
@@ -27,15 +27,19 @@ class Ledger:
kind: str,
group_id: str | None = None,
note: str = "",
allow_negative: bool = False,
) -> float:
"""amount>0 入账;amount<0 出账。返回余额。"""
"""amount>0 入账;amount<0 出账。返回余额。
LIVE 实盘成交后本地账本仅作镜像,须 allow_negative=True,避免「交易所已成交、本地拒记」导致卡仓。
"""
now = int(time.time() * 1000)
with self.db._lock:
row = self.db._conn.execute("SELECT * FROM ledger_meta WHERE id=1").fetchone()
assert row is not None
equity = float(row["equity"]) + float(amount)
available = float(row["available"]) + float(amount)
if available < -1e-9:
if not allow_negative and available < -1e-9:
raise RuntimeError("可用资金不足")
self.db._conn.execute(
"UPDATE ledger_meta SET equity=?, available=?, updated_at_ms=? WHERE id=1",
+17 -3
View File
@@ -21,6 +21,11 @@ from .pricing import (
resolve_option_close_bid,
)
# 禁止新开仓的本地仓位状态(实盘防卡)
BLOCKING_STATUSES = frozenset(
{"open", "half_open", "option_closed_perp_pending"}
)
@dataclass(slots=True)
class OpenResult:
@@ -67,8 +72,15 @@ class Matcher:
return dict(row)
def has_open_position(self) -> bool:
"""是否禁止新开:含 open / half_open / option_closed_perp_pending。"""
pos = self.current_position()
return pos.get("status") == "open" and bool(pos.get("group_id"))
st = str(pos.get("status") or "")
if st not in BLOCKING_STATUSES:
return False
return bool(pos.get("group_id") or pos.get("option_inst_id"))
def position_status(self) -> str:
return str(self.current_position().get("status") or "flat")
def _liquidity_wait(self, group_id: str, detail: str) -> CloseResult:
note = f"liquidity_wait:{int(time.time())}:{detail[:80]}"
@@ -601,7 +613,9 @@ class Matcher:
option_side=option_side, strike=float(strike), spot=float(spot)
)
def close_perp_abandon_option(self, *, reason: str = "target_perp_only") -> CloseResult:
def close_perp_abandon_option(
self, *, reason: str = "target_perp_only", require_deep_otm: bool = True
) -> CloseResult:
"""
目标平仓 B:只平永续,期权归档为到期残留(不再盯盘、不挡新开)。
"""
@@ -622,7 +636,7 @@ class Matcher:
spot = self._close_spot_px(snap)
if strike is None or spot is None:
return CloseResult(ok=False, detail="无法判断远虚:缺行权价或标的价")
if not is_deep_otm(
if require_deep_otm and not is_deep_otm(
option_side=option_side, strike=float(strike), spot=float(spot)
):
return CloseResult(ok=False, detail="期权非远虚,应走双腿全平")