Files
eth_hedge_sim/backend/app/strategy/auto_usdc.py
T

187 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""OKX 开仓前:交易账户 USDC 不足时市价 USDT→USDC(目标=期权所需×2)。
仅 OKX SIM/LIVE。资金账户不参与;期权已可开则跳过。
"""
from __future__ import annotations
import logging
import math
from typing import Any
from ..config import get_settings
from ..models.db import Database, get_db
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
def _is_okx(exchange: str | None = None) -> bool:
from ..exchange.runtime import load_runtime_settings, normalize_exchange_name
ex = exchange
if not ex:
try:
ex = load_runtime_settings().exchange
except Exception:
ex = get_settings().exchange
return normalize_exchange_name(ex) == "okx"
def _round_down(n: float, nd: int = 2) -> float:
if n <= 0:
return 0.0
f = 10**nd
return math.floor(n * f + 1e-12) / f
def ensure_okx_trading_usdc(
db: Database | None = None,
*,
cap: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""
开仓资金门前调用:
- 非 OKX → 跳过
- 期权可开(USDC≥需)→ 跳过
- 否则在**交易账户**市价 USDT→USDC,尽量补到 需×2(并预留永续保证金)
"""
db = db or get_db()
out: dict[str, Any] = {
"ok": True,
"acted": False,
"skipped": True,
"detail": "skip",
}
if not _is_okx():
out["detail"] = "非 OKX,跳过自动兑 USDC"
return out
cap = cap or assess_open_capacity(db)
need = cap.get("option_need_usdc")
have = cap.get("option_have_usdc")
if need is None or have is None:
out["detail"] = "期权所需/持有未知,跳过兑换"
out["capacity"] = cap
return out
need_f = float(need)
have_f = float(have)
if need_f <= 0:
out["detail"] = "期权所需为 0,跳过"
return out
# 可开仓:不兑换(即使低于 2 倍目标)
if have_f + 1e-9 >= need_f:
out["detail"] = (
f"交易账户 USDC 已够开仓(有 {have_f:.2f} ≥ 需 {need_f:.2f}),不兑换"
)
out["capacity"] = cap
return out
target = need_f * _TARGET_MULTIPLE
gap_usdc = target - have_f
if gap_usdc <= 1e-6:
out["detail"] = "无需补足"
return out
from ..live.okx_funds import usdc_usdt_mid_rate
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)
out.update(
{
"need_usdc": round(need_f, 2),
"have_usdc": round(have_f, 2),
"target_usdc": round(target, 2),
"want_usdt": round(want_usdt, 2),
"spend_usdt": spend_usdt,
"rate": rate,
"spendable_usdt": round(spendable, 2),
}
)
if spend_usdt < _MIN_CONVERT_USDT:
out["ok"] = False
out["skipped"] = True
out["detail"] = (
f"交易账户可兑 USDT 不足(可兑 {spendable:.2f}"
f"目标补约 {want_usdt:.2f},门槛 {_MIN_CONVERT_USDT}"
)
out["capacity"] = cap
return out
s = get_settings()
try:
if s.is_sim:
from ..sim.funds_wallets import SimFundsWallets
r = SimFundsWallets(db).convert(
direction="usdt_to_usdc",
amount=spend_usdt,
rate=rate,
account="trading",
)
else:
from ..live.okx_funds import OkxFundsClient
client = OkxFundsClient()
try:
r = client.spot_swap_usdt_usdc(
direction="usdt_to_usdc",
amount=spend_usdt,
)
finally:
client.close()
invalidate_live_balance_cache()
except Exception as e:
logger.exception("auto USDC convert failed")
out["ok"] = False
out["skipped"] = False
out["acted"] = False
out["detail"] = f"自动兑换异常:{e}"
return out
if not r.get("ok"):
out["ok"] = False
out["skipped"] = False
out["acted"] = False
out["detail"] = f"自动兑换失败:{r.get('detail') or r}"
out["raw"] = r
return out
# 兑换后重评
invalidate_live_balance_cache()
cap2 = assess_open_capacity(db)
out.update(
{
"ok": True,
"acted": True,
"skipped": False,
"detail": (
f"交易账户市价兑 USDC:花 {spend_usdt:.2f} USDT"
f"(目标持仓≈{target:.2f}=需{need_f:.2f}×{_TARGET_MULTIPLE:g}"
),
"capacity_before": cap,
"capacity_after": cap2,
"raw": r,
}
)
logger.info("auto_usdc: %s", out["detail"])
return out