Files
AlphaMaster/tests/unit/test_trader.py
T

151 lines
7.4 KiB
Python
Raw Normal View History

2026-07-02 00:51:15 +08:00
"""
tests/unit/test_trader.py — MT5Trader 单元测试
涵盖:
- 订单被拒绝(retcode != 10009)→ buy()/sell() 返回 False(Req 6.5)
- 账户信息查询失败 → buy()/sell() 中止并返回 False,且 order_send() 不被调用(Req 6.7)
- 订单成功(retcode == 10009)→ buy()/sell() 返回 True(Req 6.5)
"""
from unittest.mock import MagicMock, patch
import execution.trader as trader_module
from execution.trader import MT5Trader
# ─────────────────────────────────────────────────────────────────
# 辅助:构建一个符合 MT5 接口规范的 mock mt5 对象
# ─────────────────────────────────────────────────────────────────
def _make_mock_mt5(
account_equity: float = 10000.0,
account_margin_free: float = 5000.0,
retcode: int = 10009,
order_ticket: int = 123456,
account_returns_none: bool = False,
) -> MagicMock:
"""构建模拟的 MetaTrader5 模块对象。
Args:
account_equity: account_info().equity 的值
account_margin_free: account_info().margin_free 的值
retcode: order_send().retcode 的值
order_ticket: order_send().order 的值
account_returns_none: 若为 True,则 account_info() 返回 None
"""
mock_mt5 = MagicMock()
# MT5 常量(trader.py 直接引用,必须作为属性存在)
mock_mt5.ORDER_TYPE_BUY = 0
mock_mt5.ORDER_TYPE_SELL = 1
mock_mt5.TRADE_ACTION_DEAL = 1
# account_info()
if account_returns_none:
mock_mt5.account_info.return_value = None
else:
acct = MagicMock()
acct.equity = account_equity
acct.margin_free = account_margin_free
mock_mt5.account_info.return_value = acct
# order_send()
result = MagicMock()
result.retcode = retcode
result.order = order_ticket
result.comment = ""
mock_mt5.order_send.return_value = result
return mock_mt5
# ─────────────────────────────────────────────────────────────────
# 测试 buy() — 订单被拒绝(retcode != 10009)→ 返回 False(Req 6.5)
# ─────────────────────────────────────────────────────────────────
def test_buy_order_rejected_returns_false():
"""retcode=10006(TRADE_RETCODE_REJECT)时 buy() 必须返回 False。"""
mock_mt5 = _make_mock_mt5(retcode=10006)
with patch.object(trader_module, "mt5", mock_mt5):
trader = MT5Trader()
result = trader.buy("XAUUSD", 0.01)
assert result is False, "buy() 在 retcode!=10009 时应返回 False"
# ─────────────────────────────────────────────────────────────────
# 测试 buy() — 账户信息查询失败 → 中止下单,order_send() 不被调用(Req 6.7)
# ─────────────────────────────────────────────────────────────────
def test_buy_account_info_fails_aborts_order():
"""account_info() 返回 None 时,buy() 应中止并返回 False,且不调用 order_send()。"""
mock_mt5 = _make_mock_mt5(account_returns_none=True)
with patch.object(trader_module, "mt5", mock_mt5):
trader = MT5Trader()
result = trader.buy("XAUUSD", 0.01)
assert result is False, "account_info()=None 时 buy() 应返回 False"
mock_mt5.order_send.assert_not_called()
# ─────────────────────────────────────────────────────────────────
# 测试 sell() — 订单被拒绝(retcode != 10009)→ 返回 False(Req 6.5)
# ─────────────────────────────────────────────────────────────────
def test_sell_order_rejected_returns_false():
"""retcode=10006 时 sell() 必须返回 False。"""
mock_mt5 = _make_mock_mt5(retcode=10006)
with patch.object(trader_module, "mt5", mock_mt5):
trader = MT5Trader()
result = trader.sell("XAUUSD", 0.01)
assert result is False, "sell() 在 retcode!=10009 时应返回 False"
# ─────────────────────────────────────────────────────────────────
# 测试 sell() — 账户信息查询失败 → 中止下单,order_send() 不被调用(Req 6.7)
# ─────────────────────────────────────────────────────────────────
def test_sell_account_info_fails_aborts_order():
"""account_info() 返回 None 时,sell() 应中止并返回 False,且不调用 order_send()。"""
mock_mt5 = _make_mock_mt5(account_returns_none=True)
with patch.object(trader_module, "mt5", mock_mt5):
trader = MT5Trader()
result = trader.sell("XAUUSD", 0.01)
assert result is False, "account_info()=None 时 sell() 应返回 False"
mock_mt5.order_send.assert_not_called()
# ─────────────────────────────────────────────────────────────────
# 测试 buy() — 订单成功(retcode == 10009)→ 返回 True(Req 6.5)
# ─────────────────────────────────────────────────────────────────
def test_buy_order_success_returns_true():
"""retcode=10009(TRADE_RETCODE_DONE)时 buy() 必须返回 True。"""
mock_mt5 = _make_mock_mt5(retcode=10009, order_ticket=123456)
with patch.object(trader_module, "mt5", mock_mt5):
trader = MT5Trader()
result = trader.buy("XAUUSD", 0.01)
assert result is True, "buy() 在 retcode==10009 时应返回 True"
# ─────────────────────────────────────────────────────────────────
# 测试 sell() — 订单成功(retcode == 10009)→ 返回 True(Req 6.5)
# ─────────────────────────────────────────────────────────────────
def test_sell_order_success_returns_true():
"""retcode=10009 时 sell() 必须返回 True。"""
mock_mt5 = _make_mock_mt5(retcode=10009, order_ticket=789012)
with patch.object(trader_module, "mt5", mock_mt5):
trader = MT5Trader()
result = trader.sell("XAUUSD", 0.01)
assert result is True, "sell() 在 retcode==10009 时应返回 True"