Run OKX auto USDC convert before option pick when trading USDC is short.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,12 +1,14 @@
|
|||||||
"""OKX 开仓前:交易账户 USDC 不足时市价 USDT→USDC(目标=期权所需×2)。
|
"""OKX 开仓前:交易账户 USDC 不足时市价 USDT→USDC(目标=期权所需×2)。
|
||||||
|
|
||||||
仅 OKX SIM/LIVE。资金账户不参与;期权已可开则跳过。
|
仅 OKX SIM/LIVE。资金账户不参与;期权已可开则跳过。
|
||||||
|
空仓等待开仓时也会按盘口刷新以损定仓名义后再检测,不依赖已选中合格期权。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
|
import time
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from ..config import get_settings
|
from ..config import get_settings
|
||||||
@@ -19,6 +21,9 @@ logger = logging.getLogger(__name__)
|
|||||||
_TARGET_MULTIPLE = 2.0
|
_TARGET_MULTIPLE = 2.0
|
||||||
# 过小不兑(避免粉尘单)
|
# 过小不兑(避免粉尘单)
|
||||||
_MIN_CONVERT_USDT = 1.0
|
_MIN_CONVERT_USDT = 1.0
|
||||||
|
# 不足时重试间隔,避免每秒砸单
|
||||||
|
_RETRY_COOLDOWN_SEC = 45.0
|
||||||
|
_last_attempt_ts: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
def _is_okx(exchange: str | None = None) -> bool:
|
def _is_okx(exchange: str | None = None) -> bool:
|
||||||
@@ -40,10 +45,49 @@ def _round_down(n: float, nd: int = 2) -> float:
|
|||||||
return math.floor(n * f + 1e-12) / f
|
return math.floor(n * f + 1e-12) / f
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_risk_sizing_from_market(db: Database | None = None) -> dict[str, Any]:
|
||||||
|
"""空仓时用当前指数/卖一刷新以损定仓名义,便于资金门与自动兑对齐展示。"""
|
||||||
|
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
|
||||||
|
|
||||||
|
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)}
|
||||||
|
|
||||||
|
|
||||||
|
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}
|
||||||
|
|
||||||
|
|
||||||
def ensure_okx_trading_usdc(
|
def ensure_okx_trading_usdc(
|
||||||
db: Database | None = None,
|
db: Database | None = None,
|
||||||
*,
|
*,
|
||||||
cap: dict[str, Any] | None = None,
|
cap: dict[str, Any] | None = None,
|
||||||
|
force: bool = False,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
开仓资金门前调用:
|
开仓资金门前调用:
|
||||||
@@ -51,6 +95,7 @@ def ensure_okx_trading_usdc(
|
|||||||
- 期权可开(USDC≥需)→ 跳过
|
- 期权可开(USDC≥需)→ 跳过
|
||||||
- 否则在**交易账户**市价 USDT→USDC,尽量补到 需×2(并预留永续保证金)
|
- 否则在**交易账户**市价 USDT→USDC,尽量补到 需×2(并预留永续保证金)
|
||||||
"""
|
"""
|
||||||
|
global _last_attempt_ts
|
||||||
db = db or get_db()
|
db = db or get_db()
|
||||||
out: dict[str, Any] = {
|
out: dict[str, Any] = {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
@@ -84,6 +129,19 @@ def ensure_okx_trading_usdc(
|
|||||||
out["capacity"] = cap
|
out["capacity"] = cap
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
now = time.time()
|
||||||
|
if (
|
||||||
|
not force
|
||||||
|
and _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
|
||||||
|
out["need_usdc"] = round(need_f, 2)
|
||||||
|
out["have_usdc"] = round(have_f, 2)
|
||||||
|
return out
|
||||||
|
|
||||||
target = need_f * _TARGET_MULTIPLE
|
target = need_f * _TARGET_MULTIPLE
|
||||||
gap_usdc = target - have_f
|
gap_usdc = target - have_f
|
||||||
if gap_usdc <= 1e-6:
|
if gap_usdc <= 1e-6:
|
||||||
@@ -126,6 +184,7 @@ def ensure_okx_trading_usdc(
|
|||||||
out["capacity"] = cap
|
out["capacity"] = cap
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
_last_attempt_ts = now
|
||||||
s = get_settings()
|
s = get_settings()
|
||||||
try:
|
try:
|
||||||
if s.is_sim:
|
if s.is_sim:
|
||||||
@@ -163,9 +222,9 @@ def ensure_okx_trading_usdc(
|
|||||||
out["acted"] = False
|
out["acted"] = False
|
||||||
out["detail"] = f"自动兑换失败:{r.get('detail') or r}"
|
out["detail"] = f"自动兑换失败:{r.get('detail') or r}"
|
||||||
out["raw"] = r
|
out["raw"] = r
|
||||||
|
logger.warning("auto_usdc failed: %s", out["detail"])
|
||||||
return out
|
return out
|
||||||
|
|
||||||
# 兑换后重评
|
|
||||||
invalidate_live_balance_cache()
|
invalidate_live_balance_cache()
|
||||||
cap2 = assess_open_capacity(db)
|
cap2 = assess_open_capacity(db)
|
||||||
out.update(
|
out.update(
|
||||||
|
|||||||
@@ -807,9 +807,44 @@ class StrategyEngine:
|
|||||||
self._set_state(phase="open", last_error="有未平仓,禁止开下一组")
|
self._set_state(phase="open", last_error="有未平仓,禁止开下一组")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# OKX:先按盘口刷新名义并检测 USDC;不够则交易账户市价兑换(不依赖已选中合格期权)
|
||||||
|
try:
|
||||||
|
from .auto_usdc import prepare_okx_trading_usdc
|
||||||
|
|
||||||
|
prep = prepare_okx_trading_usdc(self.db)
|
||||||
|
conv = prep.get("convert") or {}
|
||||||
|
if conv.get("acted"):
|
||||||
|
self._set_state(
|
||||||
|
last_error=None,
|
||||||
|
phase="wait_signal",
|
||||||
|
)
|
||||||
|
logger.info("auto_usdc prepared: %s", conv.get("detail"))
|
||||||
|
elif conv.get("ok") is False and "不足" in str(conv.get("detail") or ""):
|
||||||
|
# 保留资金提示,但仍继续尝试选约(可能只是冷却/短暂失败)
|
||||||
|
logger.warning("auto_usdc: %s", conv.get("detail"))
|
||||||
|
except Exception:
|
||||||
|
logger.exception("prepare OKX USDC failed")
|
||||||
|
|
||||||
self._set_state(phase="wait_signal")
|
self._set_state(phase="wait_signal")
|
||||||
pick = await get_session().pick_for_open_async()
|
pick = await get_session().pick_for_open_async()
|
||||||
if pick is None:
|
if pick is None:
|
||||||
|
# 若期权仍不可开且刚才未兑成功,把资金状态写进错误,便于排查「为何没自动兑」
|
||||||
|
try:
|
||||||
|
from .open_capacity import assess_open_capacity
|
||||||
|
|
||||||
|
cap = assess_open_capacity(self.db)
|
||||||
|
if cap.get("option_can_open") is False:
|
||||||
|
self._set_state(
|
||||||
|
phase="wait_funds",
|
||||||
|
last_error=(
|
||||||
|
"无合格期权;且交易账户 USDC 不够开仓"
|
||||||
|
f"(需≈{cap.get('option_need_usdc')}U / 有{cap.get('option_have_usdc')}U)。"
|
||||||
|
"系统会在冷却后自动市价兑 USDC。"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
self._set_state(
|
self._set_state(
|
||||||
last_error="无合格期权:需剩余时长、杠杆(及已开启的ATM偏差)同时满足"
|
last_error="无合格期权:需剩余时长、杠杆(及已开启的ATM偏差)同时满足"
|
||||||
)
|
)
|
||||||
@@ -842,11 +877,11 @@ class StrategyEngine:
|
|||||||
self._set_state(phase="idle", last_error="以损定仓计算异常,暂不开仓")
|
self._set_state(phase="idle", last_error="以损定仓计算异常,暂不开仓")
|
||||||
return
|
return
|
||||||
|
|
||||||
# OKX:交易账户 USDC 不够开期权时,市价 USDT→USDC(目标=所需×2);够则跳过
|
# 选约后名义可能变化,再检一次 USDC(force 跳过冷却,避免刚选完仍差一截)
|
||||||
try:
|
try:
|
||||||
from .auto_usdc import ensure_okx_trading_usdc
|
from .auto_usdc import ensure_okx_trading_usdc
|
||||||
|
|
||||||
ensure_okx_trading_usdc(self.db)
|
ensure_okx_trading_usdc(self.db, force=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("auto USDC top-up failed")
|
logger.exception("auto USDC top-up failed")
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,15 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-07-30 — 自动兑 USDC:不等待选约,USDC 不够即兑
|
||||||
|
|
||||||
|
### 变更
|
||||||
|
|
||||||
|
1. 策略空仓循环在选约**之前**先刷新以损定仓名义并检测交易账户 USDC;不够则市价兑换(不再因「无合格期权」跳过)。
|
||||||
|
2. 兑换失败/不足时错误提示会带上需/有 USDC;成功兑换有 45s 冷却防砸单。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2026-07-30 — 手动兑换改走交易账户
|
## 2026-07-30 — 手动兑换改走交易账户
|
||||||
|
|
||||||
### 变更
|
### 变更
|
||||||
|
|||||||
Reference in New Issue
Block a user