Files

127 lines
4.2 KiB
Python

"""OKX 交易账户自动兑 USDC 测试。"""
from __future__ import annotations
from unittest.mock import MagicMock, patch
from app.strategy.auto_usdc import ensure_okx_trading_usdc
def test_skip_when_usdc_enough() -> None:
cap = {
"option_need_usdc": 100.0,
"option_have_usdc": 100.0,
"perp_need_usdt": 50.0,
"perp_have_usdt": 500.0,
"option_can_open": True,
}
with (
patch("app.strategy.auto_usdc._is_okx", return_value=True),
patch("app.strategy.auto_usdc.assess_open_capacity", return_value=cap),
):
r = ensure_okx_trading_usdc(db=MagicMock(), cap=cap)
assert r["skipped"] is True
assert r["acted"] is False
assert "已够开仓" in r["detail"]
def test_skip_non_okx() -> None:
with patch("app.strategy.auto_usdc._is_okx", return_value=False):
r = ensure_okx_trading_usdc(db=MagicMock())
assert r["skipped"] is True
assert "非 OKX" in r["detail"]
def test_sim_convert_to_2x_need() -> None:
"""USDC 不足 → 交易账户兑到 需×2(预留永续保证金)。"""
import app.strategy.auto_usdc as m
m._last_attempt_ts = 0.0
cap = {
"option_need_usdc": 100.0,
"option_have_usdc": 20.0,
"perp_need_usdt": 50.0,
"perp_have_usdt": 500.0,
"option_can_open": False,
}
cap_after = {
**cap,
"option_have_usdc": 200.0,
"option_can_open": True,
"perp_have_usdt": 320.0,
}
wallets = MagicMock()
wallets.convert.return_value = {"ok": True, "detail": "converted"}
with (
patch("app.strategy.auto_usdc._is_okx", return_value=True),
patch("app.strategy.auto_usdc.assess_open_capacity", side_effect=[cap, cap_after]),
patch("app.strategy.auto_usdc.get_settings") as gs,
patch("app.live.okx_funds.usdc_usdt_mid_rate", return_value=1.0),
patch("app.sim.funds_wallets.SimFundsWallets", return_value=wallets),
patch("app.strategy.auto_usdc.invalidate_live_balance_cache"),
):
gs.return_value.is_sim = True
r = ensure_okx_trading_usdc(db=MagicMock(), cap=cap)
assert r["acted"] is True
assert r["ok"] is True
# target=200, have=20 → gap=180 USDT
wallets.convert.assert_called_once()
kwargs = wallets.convert.call_args.kwargs
assert kwargs["direction"] == "usdt_to_usdc"
assert kwargs["account"] == "trading"
assert kwargs["amount"] == 180.0
def test_reserve_perp_margin() -> None:
"""可兑 USDT = 交易 USDT − 永续所需。"""
import app.strategy.auto_usdc as m
m._last_attempt_ts = 0.0
cap = {
"option_need_usdc": 100.0,
"option_have_usdc": 0.0,
"perp_need_usdt": 400.0,
"perp_have_usdt": 450.0, # 可兑仅 50
"option_can_open": False,
}
wallets = MagicMock()
wallets.convert.return_value = {"ok": True, "detail": "converted"}
cap_after = {**cap, "option_have_usdc": 50.0}
with (
patch("app.strategy.auto_usdc._is_okx", return_value=True),
patch("app.strategy.auto_usdc.assess_open_capacity", side_effect=[cap, cap_after]),
patch("app.strategy.auto_usdc.get_settings") as gs,
patch("app.live.okx_funds.usdc_usdt_mid_rate", return_value=1.0),
patch("app.sim.funds_wallets.SimFundsWallets", return_value=wallets),
patch("app.strategy.auto_usdc.invalidate_live_balance_cache"),
):
gs.return_value.is_sim = True
r = ensure_okx_trading_usdc(db=MagicMock(), cap=cap, force=True)
assert r["acted"] is True
assert wallets.convert.call_args.kwargs["amount"] == 50.0
def test_force_does_not_bypass_cooldown() -> None:
import app.strategy.auto_usdc as m
import time
m._last_attempt_ts = time.time()
cap = {
"option_need_usdc": 100.0,
"option_have_usdc": 0.0,
"perp_need_usdt": 10.0,
"perp_have_usdt": 500.0,
"option_can_open": False,
}
with (
patch("app.strategy.auto_usdc._is_okx", return_value=True),
patch("app.strategy.auto_usdc.assess_open_capacity", return_value=cap),
):
r = ensure_okx_trading_usdc(db=MagicMock(), cap=cap, force=True)
assert r["acted"] is False
assert "冷却" in r["detail"]