Add OKX trading-account USDC auto-swap and lock exit target while in position.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-30 00:16:47 +08:00
parent f0b0b91f1b
commit 0837982714
16 changed files with 626 additions and 36 deletions
+99
View File
@@ -0,0 +1,99 @@
"""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(预留永续保证金)。"""
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 − 永续所需。"""
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)
assert r["acted"] is True
assert wallets.convert.call_args.kwargs["amount"] == 50.0
+42
View File
@@ -0,0 +1,42 @@
"""持仓锁定出场目标。"""
from __future__ import annotations
from app.strategy.exits import check_exits, resolve_exit_target
def test_locked_target_ignores_setting_drift() -> None:
d = check_exits(
net_pnl=20.0,
exit_mode="fixed_usdt",
net_profit_target=50.0, # 设置已被改大
premium_exit_multiple=1.0,
initial_premium=10.0,
locked_exit_target=15.0, # 开仓锁定
)
assert d.should_close is True
assert d.target == 15.0
def test_locked_target_not_yet() -> None:
d = check_exits(
net_pnl=10.0,
exit_mode="fixed_usdt",
net_profit_target=5.0,
premium_exit_multiple=1.0,
initial_premium=10.0,
locked_exit_target=15.0,
)
assert d.should_close is False
assert d.target == 15.0
def test_resolve_premium_still_works_without_lock() -> None:
t, mode = resolve_exit_target(
exit_mode="premium_multiple",
net_profit_target=15.0,
premium_exit_multiple=2.0,
initial_premium=10.0,
)
assert t == 20.0
assert mode == "premium_multiple"