Files
2026-02-21 07:43:33 +01:00

887 lines
30 KiB
Python

"""Comprehensive tests for the synchronous Trader module.
Tests cover:
- Trader initialization (__init__)
- set_trade_stop_levels_pips (long/short orders)
- set_trade_stop_levels_points (long/short orders)
- create_order_with_stops
- create_order_with_sl
- create_order_with_points
- create_order_no_stops
- check_order
- send_order
- record_trade
- place_trade (abstract method enforcement)
"""
from unittest.mock import MagicMock, patch
import pytest
from aiomql.lib.sync.trader import Trader
from aiomql.lib.sync.order import Order
from aiomql.lib.ram import RAM
from aiomql.lib.sync.symbol import Symbol
from aiomql.core.models import OrderType, OrderSendResult, OrderCheckResult
from aiomql.core.config import Config
from aiomql.core.task_queue import QueueItem
# --- Concrete subclass for testing abstract Trader ---
class ConcreteTrader(Trader):
"""Non-abstract subclass of Trader for testing purposes."""
def place_trade(self, *args, **kwargs):
pass
# --- Fixtures ---
@pytest.fixture
def mock_symbol():
"""Create a mock Symbol with standard forex attributes."""
symbol = MagicMock(spec=Symbol)
symbol.name = "EURUSD"
symbol.pip = 0.0001
symbol.point = 0.00001
symbol.digits = 5
symbol.volume_min = 0.01
symbol.compute_volume_sl = MagicMock(return_value=0.1)
symbol.compute_volume_points = MagicMock(return_value=0.1)
symbol.amount_in_quote_currency = MagicMock(return_value=100.0)
symbol.info_tick = MagicMock()
return symbol
@pytest.fixture
def mock_ram():
"""Create a mock RAM instance."""
ram = MagicMock(spec=RAM)
ram.risk_to_reward = 2.0
ram.get_amount = MagicMock(return_value=100.0)
return ram
@pytest.fixture
def mock_tick():
"""Create a mock price tick."""
tick = MagicMock()
tick.ask = 1.10000
tick.bid = 1.09990
return tick
@pytest.fixture
def trader(mock_symbol, mock_ram, mock_tick):
"""Create a ConcreteTrader for testing."""
mock_symbol.info_tick.return_value = mock_tick
t = ConcreteTrader(symbol=mock_symbol, ram=mock_ram)
return t
# --- Tests ---
class TestTraderInit:
"""Test Trader initialization."""
def test_init_sets_symbol(self, mock_symbol, mock_ram):
"""Test __init__ sets symbol."""
t = ConcreteTrader(symbol=mock_symbol, ram=mock_ram)
assert t.symbol is mock_symbol
def test_init_sets_ram(self, mock_symbol, mock_ram):
"""Test __init__ sets provided RAM."""
t = ConcreteTrader(symbol=mock_symbol, ram=mock_ram)
assert t.ram is mock_ram
def test_init_creates_default_ram(self, mock_symbol):
"""Test __init__ creates default RAM when none provided."""
t = ConcreteTrader(symbol=mock_symbol)
assert isinstance(t.ram, RAM)
def test_init_creates_order(self, mock_symbol, mock_ram):
"""Test __init__ creates Order with symbol name."""
t = ConcreteTrader(symbol=mock_symbol, ram=mock_ram)
assert isinstance(t.order, Order)
assert t.order.symbol == "EURUSD"
def test_init_creates_config(self, mock_symbol, mock_ram):
"""Test __init__ creates Config instance."""
t = ConcreteTrader(symbol=mock_symbol, ram=mock_ram)
assert isinstance(t.config, Config)
def test_init_creates_empty_parameters(self, mock_symbol, mock_ram):
"""Test __init__ creates empty parameters dict."""
t = ConcreteTrader(symbol=mock_symbol, ram=mock_ram)
assert t.parameters == {}
class TestSetTradeStopLevelsPips:
"""Test set_trade_stop_levels_pips method."""
def test_long_order_sl_below_price(self, trader):
"""Test long order sets SL below price."""
trader.order.price = 1.10000
trader.order.type = MagicMock()
trader.order.type.is_long = True
trader.order.type.is_short = False
trader.set_trade_stop_levels_pips(pips=50)
assert trader.order.sl < trader.order.price
def test_long_order_tp_above_price(self, trader):
"""Test long order sets TP above price."""
trader.order.price = 1.10000
trader.order.type = MagicMock()
trader.order.type.is_long = True
trader.order.type.is_short = False
trader.set_trade_stop_levels_pips(pips=50)
assert trader.order.tp > trader.order.price
def test_short_order_sl_above_price(self, trader):
"""Test short order sets SL above price."""
trader.order.price = 1.10000
trader.order.type = MagicMock()
trader.order.type.is_long = False
trader.order.type.is_short = True
trader.set_trade_stop_levels_pips(pips=50)
assert trader.order.sl > trader.order.price
def test_short_order_tp_below_price(self, trader):
"""Test short order sets TP below price."""
trader.order.price = 1.10000
trader.order.type = MagicMock()
trader.order.type.is_long = False
trader.order.type.is_short = True
trader.set_trade_stop_levels_pips(pips=50)
assert trader.order.tp < trader.order.price
def test_custom_risk_to_reward(self, trader):
"""Test custom risk_to_reward overrides RAM default."""
trader.order.price = 1.10000
trader.order.type = MagicMock()
trader.order.type.is_long = True
trader.order.type.is_short = False
trader.set_trade_stop_levels_pips(pips=50, risk_to_reward=3.0)
sl_distance = abs(trader.order.price - trader.order.sl)
tp_distance = abs(trader.order.tp - trader.order.price)
assert round(tp_distance / sl_distance, 1) == 3.0
def test_uses_ram_risk_to_reward_by_default(self, trader):
"""Test uses RAM risk_to_reward when not specified."""
trader.order.price = 1.10000
trader.order.type = MagicMock()
trader.order.type.is_long = True
trader.order.type.is_short = False
trader.ram.risk_to_reward = 2.0
trader.set_trade_stop_levels_pips(pips=50)
sl_distance = abs(trader.order.price - trader.order.sl)
tp_distance = abs(trader.order.tp - trader.order.price)
assert round(tp_distance / sl_distance, 1) == 2.0
def test_rounds_to_symbol_digits(self, trader):
"""Test SL and TP are rounded to symbol.digits."""
trader.order.price = 1.10000
trader.order.type = MagicMock()
trader.order.type.is_long = True
trader.order.type.is_short = False
trader.set_trade_stop_levels_pips(pips=50)
sl_str = f"{trader.order.sl:.10f}".rstrip("0")
tp_str = f"{trader.order.tp:.10f}".rstrip("0")
sl_decimals = len(sl_str.split(".")[1]) if "." in sl_str else 0
tp_decimals = len(tp_str.split(".")[1]) if "." in tp_str else 0
assert sl_decimals <= 5
assert tp_decimals <= 5
class TestSetTradeStopLevelsPoints:
"""Test set_trade_stop_levels_points method."""
def test_long_order_sl_below_price(self, trader):
"""Test long order sets SL below price."""
trader.order.price = 1.10000
trader.order.type = MagicMock()
trader.order.type.is_long = True
trader.order.type.is_short = False
trader.set_trade_stop_levels_points(points=500)
assert trader.order.sl < trader.order.price
def test_long_order_tp_above_price(self, trader):
"""Test long order sets TP above price."""
trader.order.price = 1.10000
trader.order.type = MagicMock()
trader.order.type.is_long = True
trader.order.type.is_short = False
trader.set_trade_stop_levels_points(points=500)
assert trader.order.tp > trader.order.price
def test_custom_risk_to_reward(self, trader):
"""Test custom risk_to_reward for points."""
trader.order.price = 1.10000
trader.order.type = MagicMock()
trader.order.type.is_long = True
trader.order.type.is_short = False
trader.set_trade_stop_levels_points(points=500, risk_to_reward=4.0)
sl_distance = abs(trader.order.price - trader.order.sl)
tp_distance = abs(trader.order.tp - trader.order.price)
assert round(tp_distance / sl_distance, 1) == 4.0
class TestCreateOrderWithStops:
"""Test create_order_with_stops method."""
def test_sets_order_attributes(self, trader, mock_tick):
"""Test sets sl, tp, volume, price, and type on order."""
order_type = MagicMock()
order_type.is_long = True
trader.order.set_attributes = MagicMock()
trader.create_order_with_stops(
order_type=order_type, sl=1.09500, tp=1.11000
)
trader.order.set_attributes.assert_called_once()
call_kwargs = trader.order.set_attributes.call_args[1]
assert call_kwargs["sl"] == 1.09500
assert call_kwargs["tp"] == 1.11000
assert call_kwargs["type"] is order_type
def test_uses_ask_for_long(self, trader, mock_tick):
"""Test uses ask price for long orders."""
order_type = MagicMock()
order_type.is_long = True
trader.order.set_attributes = MagicMock()
trader.create_order_with_stops(
order_type=order_type, sl=1.09500, tp=1.11000
)
call_kwargs = trader.order.set_attributes.call_args[1]
assert call_kwargs["price"] == mock_tick.ask
def test_uses_bid_for_short(self, trader, mock_tick):
"""Test uses bid price for short orders."""
order_type = MagicMock()
order_type.is_long = False
trader.order.set_attributes = MagicMock()
trader.create_order_with_stops(
order_type=order_type, sl=1.10500, tp=1.09000
)
call_kwargs = trader.order.set_attributes.call_args[1]
assert call_kwargs["price"] == mock_tick.bid
def test_calculates_volume(self, trader):
"""Test computes volume using symbol.compute_volume_sl."""
order_type = MagicMock()
order_type.is_long = True
trader.order.set_attributes = MagicMock()
trader.create_order_with_stops(
order_type=order_type, sl=1.09500, tp=1.11000
)
trader.symbol.compute_volume_sl.assert_called_once()
def test_custom_amount_to_risk(self, trader):
"""Test custom amount_to_risk overrides RAM.get_amount."""
order_type = MagicMock()
order_type.is_long = True
trader.order.set_attributes = MagicMock()
trader.create_order_with_stops(
order_type=order_type, sl=1.09500, tp=1.11000, amount_to_risk=500.0
)
trader.ram.get_amount.assert_not_called()
def test_default_amount_from_ram(self, trader):
"""Test uses RAM.get_amount when amount_to_risk not specified."""
order_type = MagicMock()
order_type.is_long = True
trader.order.set_attributes = MagicMock()
trader.create_order_with_stops(
order_type=order_type, sl=1.09500, tp=1.11000
)
trader.ram.get_amount.assert_called_once()
def test_converts_amount_to_quote_currency(self, trader):
"""Test converts amount using symbol.amount_in_quote_currency."""
order_type = MagicMock()
order_type.is_long = True
trader.order.set_attributes = MagicMock()
trader.create_order_with_stops(
order_type=order_type, sl=1.09500, tp=1.11000
)
trader.symbol.amount_in_quote_currency.assert_called_once()
class TestCreateOrderWithSl:
"""Test create_order_with_sl method."""
def test_calculates_tp_from_sl(self, trader, mock_tick):
"""Test calculates TP based on SL distance and risk_to_reward."""
order_type = OrderType.BUY
trader.order.set_attributes = MagicMock()
trader.create_order_with_sl(
order_type=order_type, sl=1.09500
)
call_kwargs = trader.order.set_attributes.call_args[1]
assert call_kwargs["tp"] > call_kwargs["price"]
def test_buy_uses_ask_price(self, trader, mock_tick):
"""Test BUY order uses ask price."""
trader.order.set_attributes = MagicMock()
trader.create_order_with_sl(
order_type=OrderType.BUY, sl=1.09500
)
call_kwargs = trader.order.set_attributes.call_args[1]
assert call_kwargs["price"] == mock_tick.ask
def test_sell_uses_bid_price(self, trader, mock_tick):
"""Test SELL order uses bid price."""
trader.order.set_attributes = MagicMock()
trader.create_order_with_sl(
order_type=OrderType.SELL, sl=1.10500
)
call_kwargs = trader.order.set_attributes.call_args[1]
assert call_kwargs["price"] == mock_tick.bid
def test_custom_risk_to_reward(self, trader, mock_tick):
"""Test custom risk_to_reward affects TP calculation."""
trader.order.set_attributes = MagicMock()
trader.create_order_with_sl(
order_type=OrderType.BUY, sl=1.09500, risk_to_reward=3.0
)
call_kwargs = trader.order.set_attributes.call_args[1]
price = call_kwargs["price"]
sl_dist = abs(price - 1.09500)
tp_dist = abs(call_kwargs["tp"] - price)
assert round(tp_dist / sl_dist, 1) == 3.0
def test_sell_tp_below_price(self, trader, mock_tick):
"""Test SELL order sets TP below price."""
trader.order.set_attributes = MagicMock()
trader.create_order_with_sl(
order_type=OrderType.SELL, sl=1.10500
)
call_kwargs = trader.order.set_attributes.call_args[1]
assert call_kwargs["tp"] < call_kwargs["price"]
def test_custom_amount_to_risk(self, trader):
"""Test custom amount_to_risk skips RAM.get_amount."""
trader.order.set_attributes = MagicMock()
trader.create_order_with_sl(
order_type=OrderType.BUY, sl=1.09500, amount_to_risk=200.0
)
trader.ram.get_amount.assert_not_called()
class TestCreateOrderWithPoints:
"""Test create_order_with_points method."""
def test_sets_order_type(self, trader):
"""Test sets order type on order."""
trader.create_order_with_points(
order_type=OrderType.BUY, points=500
)
assert trader.order.type == OrderType.BUY
def test_sets_price_from_tick(self, trader, mock_tick):
"""Test sets price from tick."""
trader.create_order_with_points(
order_type=OrderType.BUY, points=500
)
assert trader.order.price == mock_tick.ask
def test_sell_uses_bid(self, trader, mock_tick):
"""Test SELL uses bid price."""
trader.create_order_with_points(
order_type=OrderType.SELL, points=500
)
assert trader.order.price == mock_tick.bid
def test_computes_volume_with_points(self, trader):
"""Test uses symbol.compute_volume_points."""
trader.create_order_with_points(
order_type=OrderType.BUY, points=500
)
trader.symbol.compute_volume_points.assert_called_once()
def test_sets_stop_levels(self, trader):
"""Test calls set_trade_stop_levels_points."""
with patch.object(trader, 'set_trade_stop_levels_points') as mock_set_stops:
trader.create_order_with_points(
order_type=OrderType.BUY, points=500
)
mock_set_stops.assert_called_once_with(points=500, risk_to_reward=None)
def test_custom_risk_to_reward(self, trader):
"""Test passes custom risk_to_reward to set_trade_stop_levels_points."""
with patch.object(trader, 'set_trade_stop_levels_points') as mock_set_stops:
trader.create_order_with_points(
order_type=OrderType.BUY, points=500, risk_to_reward=3.0
)
mock_set_stops.assert_called_once_with(points=500, risk_to_reward=3.0)
class TestCreateOrderNoStops:
"""Test create_order_no_stops method."""
def test_sets_order_type(self, trader):
"""Test sets order type."""
trader.create_order_no_stops(order_type=OrderType.BUY)
assert trader.order.type == OrderType.BUY
def test_uses_min_volume_default(self, trader):
"""Test uses symbol.volume_min when volume not specified."""
trader.symbol.volume_min = 0.01
trader.create_order_no_stops(order_type=OrderType.BUY)
assert trader.order.volume == 0.01
def test_custom_volume(self, trader):
"""Test uses custom volume when provided."""
trader.create_order_no_stops(order_type=OrderType.BUY, volume=1.5)
assert trader.order.volume == 1.5
def test_buy_uses_ask_price(self, trader, mock_tick):
"""Test BUY uses ask price."""
trader.create_order_no_stops(order_type=OrderType.BUY)
assert trader.order.price == mock_tick.ask
def test_sell_uses_bid_price(self, trader, mock_tick):
"""Test SELL uses bid price."""
trader.create_order_no_stops(order_type=OrderType.SELL)
assert trader.order.price == mock_tick.bid
class TestCheckOrder:
"""Test check_order method."""
def test_check_order_success(self, trader):
"""Test check_order returns OrderCheckResult on success."""
mock_result = MagicMock(spec=OrderCheckResult)
mock_result.retcode = 0
trader.order.check = MagicMock(return_value=mock_result)
result = trader.check_order()
assert result is mock_result
def test_check_order_none_result(self, trader):
"""Test check_order handles None result."""
trader.order.check = MagicMock(return_value=None)
trader.order.mt5 = MagicMock()
trader.order.mt5.error = "Connection failed"
result = trader.check_order()
assert result is None
def test_check_order_nonzero_retcode(self, trader):
"""Test check_order logs warning for non-zero retcode."""
mock_result = MagicMock(spec=OrderCheckResult)
mock_result.retcode = 10015
mock_result.comment = "Invalid price"
trader.order.check = MagicMock(return_value=mock_result)
result = trader.check_order()
assert result is mock_result
assert result.retcode != 0
def test_check_order_calls_order_check(self, trader):
"""Test check_order delegates to order.check."""
mock_result = MagicMock(spec=OrderCheckResult)
mock_result.retcode = 0
trader.order.check = MagicMock(return_value=mock_result)
trader.check_order()
trader.order.check.assert_called_once()
class TestSendOrder:
"""Test send_order method."""
def test_send_order_success(self, trader):
"""Test send_order returns result on success (retcode 10009)."""
mock_result = MagicMock(spec=OrderSendResult)
mock_result.retcode = 10009
trader.order.send = MagicMock(return_value=mock_result)
result = trader.send_order()
assert result is mock_result
assert result.retcode == 10009
def test_send_order_none_result(self, trader):
"""Test send_order handles None result."""
trader.order.send = MagicMock(return_value=None)
trader.order.mt5 = MagicMock()
trader.order.mt5.error = "No connection"
result = trader.send_order()
assert result is None
def test_send_order_failure_retcode(self, trader):
"""Test send_order returns result for non-10009 retcode."""
mock_result = MagicMock(spec=OrderSendResult)
mock_result.retcode = 10006
mock_result.comment = "Requote"
trader.order.send = MagicMock(return_value=mock_result)
result = trader.send_order()
assert result is mock_result
assert result.retcode == 10006
def test_send_order_calls_order_send(self, trader):
"""Test send_order delegates to order.send."""
mock_result = MagicMock(spec=OrderSendResult)
mock_result.retcode = 10009
trader.order.send = MagicMock(return_value=mock_result)
trader.send_order()
trader.order.send.assert_called_once()
class TestRecordTrade:
"""Test record_trade method."""
@pytest.fixture
def successful_result(self):
"""Create a successful OrderSendResult."""
result = MagicMock(spec=OrderSendResult)
result.retcode = 10009
result.order = 12345
result.request = MagicMock()
return result
def test_record_trade_skips_when_disabled(self, trader, successful_result):
"""Test record_trade does nothing when record_trades is False."""
trader.config.record_trades = False
trader.record_trade(result=successful_result)
def test_record_trade_skips_failed_orders(self, trader):
"""Test record_trade skips when retcode is not 10009."""
trader.config.record_trades = True
result = MagicMock(spec=OrderSendResult)
result.retcode = 10006
trader.record_trade(result=result)
def test_record_trade_uses_task_queue(self, trader, successful_result):
"""Test record_trade adds to task queue by default."""
trader.config.record_trades = True
trader.config.task_queue = MagicMock()
trader.config.task_queue.add = MagicMock()
mock_order = MagicMock()
mock_order.sl = 1.09500
mock_order.tp = 1.11000
mock_order.time_setup_msc = 1705312800000
trader.order.get_history_order_by_ticket = MagicMock(return_value=mock_order)
trader.order.calc_profit = MagicMock(return_value=50.0)
with patch('aiomql.lib.sync.trader.Result') as MockResult:
mock_res = MagicMock()
mock_res.save_sync = MagicMock()
MockResult.return_value = mock_res
trader.record_trade(result=successful_result, parameters={"key": "value"})
trader.config.task_queue.add.assert_called_once()
def test_record_trade_direct_save(self, trader, successful_result):
"""Test record_trade saves directly when use_task_queue=False."""
trader.config.record_trades = True
mock_order = MagicMock()
mock_order.sl = 1.09500
mock_order.tp = 1.11000
mock_order.time_setup_msc = 1705312800000
trader.order.get_history_order_by_ticket = MagicMock(return_value=mock_order)
trader.order.calc_profit = MagicMock(return_value=50.0)
with patch('aiomql.lib.sync.trader.Result') as MockResult:
mock_res = MagicMock()
mock_res.save_sync = MagicMock()
MockResult.return_value = mock_res
trader.record_trade(
result=successful_result,
use_task_queue=False
)
mock_res.save_sync.assert_called_once()
def test_record_trade_with_parameters(self, trader, successful_result):
"""Test record_trade passes parameters to Result."""
trader.config.record_trades = True
trader.config.task_queue = MagicMock()
trader.config.task_queue.add = MagicMock()
mock_order = MagicMock()
mock_order.sl = 1.09500
mock_order.tp = 1.11000
mock_order.time_setup_msc = 1705312800000
trader.order.get_history_order_by_ticket = MagicMock(return_value=mock_order)
trader.order.calc_profit = MagicMock(return_value=50.0)
params = {"strategy": "scalping", "timeframe": "M5"}
with patch('aiomql.lib.sync.trader.Result') as MockResult:
mock_res = MagicMock()
mock_res.save_sync = MagicMock()
MockResult.return_value = mock_res
trader.record_trade(
result=successful_result, parameters=params, name="TestStrategy"
)
call_kwargs = MockResult.call_args[1]
assert call_kwargs["parameters"] == params
assert call_kwargs["name"] == "TestStrategy"
def test_record_trade_with_expected_profit(self, trader, successful_result):
"""Test record_trade uses provided expected_profit."""
trader.config.record_trades = True
trader.config.task_queue = MagicMock()
trader.config.task_queue.add = MagicMock()
mock_order = MagicMock()
mock_order.sl = 1.09500
mock_order.tp = 1.11000
mock_order.time_setup_msc = 1705312800000
trader.order.get_history_order_by_ticket = MagicMock(return_value=mock_order)
with patch('aiomql.lib.sync.trader.Result') as MockResult:
mock_res = MagicMock()
mock_res.save_sync = MagicMock()
MockResult.return_value = mock_res
trader.record_trade(
result=successful_result, expected_profit=75.0
)
call_kwargs = MockResult.call_args[1]
assert call_kwargs["expected_profit"] == 75.0
def test_record_trade_non_dict_parameters(self, trader, successful_result):
"""Test record_trade handles non-dict parameters."""
trader.config.record_trades = True
trader.config.task_queue = MagicMock()
trader.config.task_queue.add = MagicMock()
mock_order = MagicMock()
mock_order.sl = 1.09500
mock_order.tp = 1.11000
mock_order.time_setup_msc = 1705312800000
trader.order.get_history_order_by_ticket = MagicMock(return_value=mock_order)
trader.order.calc_profit = MagicMock(return_value=10.0)
with patch('aiomql.lib.sync.trader.Result') as MockResult:
mock_res = MagicMock()
mock_res.save_sync = MagicMock()
MockResult.return_value = mock_res
trader.record_trade(
result=successful_result, parameters="not_a_dict"
)
call_kwargs = MockResult.call_args[1]
assert call_kwargs["parameters"] == {}
def test_record_trade_updates_sl_tp_from_history(self, trader, successful_result):
"""Test record_trade sets sl and tp on result.request from history order."""
trader.config.record_trades = True
trader.config.task_queue = MagicMock()
trader.config.task_queue.add = MagicMock()
mock_order = MagicMock()
mock_order.sl = 1.08000
mock_order.tp = 1.12000
mock_order.time_setup_msc = 1705312800000
trader.order.get_history_order_by_ticket = MagicMock(return_value=mock_order)
trader.order.calc_profit = MagicMock(return_value=10.0)
with patch('aiomql.lib.sync.trader.Result') as MockResult:
mock_res = MagicMock()
mock_res.save_sync = MagicMock()
MockResult.return_value = mock_res
trader.record_trade(result=successful_result)
assert successful_result.request.sl == 1.08000
assert successful_result.request.tp == 1.12000
class TestPlaceTrade:
"""Test abstract place_trade method."""
def test_cannot_instantiate_trader_directly(self, mock_symbol, mock_ram):
"""Test Trader cannot be instantiated due to abstract method."""
with pytest.raises(TypeError):
Trader(symbol=mock_symbol, ram=mock_ram)
def test_subclass_must_implement_place_trade(self):
"""Test subclass without place_trade raises TypeError."""
with pytest.raises(TypeError):
class IncompleteTrader(Trader):
pass
IncompleteTrader(symbol=MagicMock())
def test_concrete_place_trade_callable(self, trader):
"""Test ConcreteTrader.place_trade is callable."""
trader.place_trade()
class TestIntegration:
"""Integration tests for sync Trader."""
def test_create_and_check_order(self, trader, mock_tick):
"""Test creating order then checking it."""
order_type = MagicMock()
order_type.is_long = True
trader.order.set_attributes = MagicMock()
trader.create_order_with_stops(
order_type=order_type, sl=1.09500, tp=1.11000
)
mock_check_result = MagicMock(spec=OrderCheckResult)
mock_check_result.retcode = 0
trader.order.check = MagicMock(return_value=mock_check_result)
check = trader.check_order()
assert check.retcode == 0
def test_create_and_send_order(self, trader, mock_tick):
"""Test creating order then sending it."""
order_type = MagicMock()
order_type.is_long = True
trader.order.set_attributes = MagicMock()
trader.create_order_with_stops(
order_type=order_type, sl=1.09500, tp=1.11000
)
mock_send_result = MagicMock(spec=OrderSendResult)
mock_send_result.retcode = 10009
trader.order.send = MagicMock(return_value=mock_send_result)
result = trader.send_order()
assert result.retcode == 10009
def test_full_trade_lifecycle(self, trader, mock_tick):
"""Test full lifecycle: create, check, send, record."""
order_type = MagicMock()
order_type.is_long = True
trader.order.set_attributes = MagicMock()
# Create
trader.create_order_with_stops(
order_type=order_type, sl=1.09500, tp=1.11000
)
# Check
mock_check = MagicMock(spec=OrderCheckResult)
mock_check.retcode = 0
trader.order.check = MagicMock(return_value=mock_check)
check = trader.check_order()
assert check.retcode == 0
# Send
mock_result = MagicMock(spec=OrderSendResult)
mock_result.retcode = 10009
mock_result.order = 99999
mock_result.request = MagicMock()
trader.order.send = MagicMock(return_value=mock_result)
result = trader.send_order()
assert result.retcode == 10009
# Record
trader.config.record_trades = True
trader.config.task_queue = MagicMock()
trader.config.task_queue.add = MagicMock()
mock_history_order = MagicMock()
mock_history_order.sl = 1.09500
mock_history_order.tp = 1.11000
mock_history_order.time_setup_msc = 1705312800000
trader.order.get_history_order_by_ticket = MagicMock(return_value=mock_history_order)
trader.order.calc_profit = MagicMock(return_value=50.0)
with patch('aiomql.lib.sync.trader.Result') as MockResult:
mock_res = MagicMock()
mock_res.save_sync = MagicMock()
MockResult.return_value = mock_res
trader.record_trade(result=result, name="TestStrategy")
trader.config.task_queue.add.assert_called_once()