200702d066
Co-authored-by: Cursor <cursoragent@cursor.com>
127 lines
4.2 KiB
Python
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"]
|