"""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"]