Harden strategy SoT: fail-closed funds gate, shared open pipeline, LIVE switch guards.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-30 01:06:32 +08:00
parent 4c537e7520
commit 200702d066
14 changed files with 471 additions and 139 deletions
+33 -44
View File
@@ -1,7 +1,7 @@
"""OKX 开仓前:交易账户 USDC 不足时市价 USDT→USDC(目标=期权所需×2)。
"""OKX:交易账户 USDC 不足时市价 USDT→USDC(目标=期权所需×2)。
仅 OKX SIM/LIVE。资金账户不参与;期权已可开则跳过。
空仓等待开仓时也会按盘口刷新以损定仓名义后再检测,不依赖已选中合格期权
等待选约阶段只用预览名义检测(不落库);落库定仓在 open_pipeline.size_and_gate
"""
from __future__ import annotations
@@ -17,11 +17,8 @@ from .open_capacity import assess_open_capacity, invalidate_live_balance_cache
logger = logging.getLogger(__name__)
# 目标持仓 = 期权开仓所需 USDC × 倍数
_TARGET_MULTIPLE = 2.0
# 过小不兑(避免粉尘单)
_MIN_CONVERT_USDT = 1.0
# 不足时重试间隔,避免每秒砸单
_RETRY_COOLDOWN_SEC = 45.0
_last_attempt_ts: float = 0.0
@@ -45,42 +42,40 @@ def _round_down(n: float, nd: int = 2) -> float:
return math.floor(n * f + 1e-12) / f
def refresh_risk_sizing_from_market(db: Database | None = None) -> dict[str, Any]:
"""空仓时用当前指数/卖一刷新以损定仓名义,便于资金门与自动兑对齐展示。"""
def preview_capacity_for_convert(db: Database | None = None) -> dict[str, Any]:
"""
用盘口预览以损定仓名义评估资金门(不写 settings)。
手动仓位则直接 assess 当前账本名义。
"""
database = db or get_db()
out: dict[str, Any] = {"ok": True, "detail": "skip"}
try:
from ..sim.ledger import Ledger
from .open_capacity import _index_and_option_ask
from .risk_sizing import apply_risk_sizing_to_ledger, is_risk_based
from .risk_sizing import preview_risk_sizing
if not is_risk_based(Ledger(database)):
out["detail"] = "manual_sizing"
return out
idx, ask = _index_and_option_ask()
if idx is None or ask is None or float(idx) <= 0 or float(ask) <= 0:
out["ok"] = False
out["detail"] = "暂无指数或期权卖一"
return out
r = apply_risk_sizing_to_ledger(
index_px=float(idx), option_ask=float(ask), db=database
)
out["ok"] = bool(r.ok)
out["detail"] = r.detail
out["k"] = r.k
out["option_qty_eth"] = r.option_qty_eth
return out
except Exception as e:
logger.exception("refresh risk sizing for auto_usdc failed")
return {"ok": False, "detail": str(e)}
prev = preview_risk_sizing(database)
if prev.get("risk_based") and prev.get("ok"):
return assess_open_capacity(
database,
option_ask=float(prev["option_ask"])
if prev.get("option_ask") is not None
else None,
option_qty_eth=float(prev["option_qty_eth"])
if prev.get("option_qty_eth") is not None
else None,
perp_qty_eth=float(prev["perp_qty_eth"])
if prev.get("perp_qty_eth") is not None
else None,
)
except Exception:
logger.exception("preview capacity for convert failed")
return assess_open_capacity(database)
def prepare_okx_trading_usdc(db: Database | None = None) -> dict[str, Any]:
"""选约前也可调用:先刷新名义,再按资金门自动兑 USDC"""
"""等待阶段:预览名义评估 + 兑换,不落库"""
database = db or get_db()
sized = refresh_risk_sizing_from_market(database)
top = ensure_okx_trading_usdc(database)
return {"sizing": sized, "convert": top}
cap = preview_capacity_for_convert(database)
conv = ensure_okx_trading_usdc(database, cap=cap, force=False)
return {"capacity": cap, "convert": conv}
def ensure_okx_trading_usdc(
@@ -90,12 +85,13 @@ def ensure_okx_trading_usdc(
force: bool = False,
) -> dict[str, Any]:
"""
开仓资金门前调用:
- 非 OKX → 跳过
- 期权可开(USDC≥需)→ 跳过
- 否则在**交易账户**市价 USDT→USDC,尽量补到 需×2(预留永续保证金)
- 否则交易账户市价 USDT→USDC,尽量补到 需×2(预留永续保证金)
- force 不再绕过冷却(防砸单);保留参数仅为兼容调用方
"""
global _last_attempt_ts
_ = force # 明确忽略:冷却始终生效
db = db or get_db()
out: dict[str, Any] = {
"ok": True,
@@ -121,7 +117,6 @@ def ensure_okx_trading_usdc(
out["detail"] = "期权所需为 0,跳过"
return out
# 可开仓:不兑换(即使低于 2 倍目标)
if have_f + 1e-9 >= need_f:
out["detail"] = (
f"交易账户 USDC 已够开仓(有 {have_f:.2f} ≥ 需 {need_f:.2f}),不兑换"
@@ -130,11 +125,7 @@ def ensure_okx_trading_usdc(
return out
now = time.time()
if (
not force
and _last_attempt_ts > 0
and now - _last_attempt_ts < _RETRY_COOLDOWN_SEC
):
if _last_attempt_ts > 0 and now - _last_attempt_ts < _RETRY_COOLDOWN_SEC:
left = _RETRY_COOLDOWN_SEC - (now - _last_attempt_ts)
out["detail"] = f"USDC 不足,自动兑换冷却中({left:.0f}s"
out["capacity"] = cap
@@ -153,12 +144,10 @@ def ensure_okx_trading_usdc(
rate = float(usdc_usdt_mid_rate() or 1.0)
if rate <= 0:
rate = 1.0
# usdt_to_usdcamount = 花费的 USDT(与 OkxFundsClient / SIM 一致)
want_usdt = gap_usdc * rate
perp_need = float(cap.get("perp_need_usdt") or 0)
trading_usdt = float(cap.get("perp_have_usdt") or 0)
# 预留永续保证金,避免兑光导致永续不可开
spendable = max(0.0, trading_usdt - max(0.0, perp_need))
spend_usdt = _round_down(min(want_usdt, spendable), 2)