mirror of
https://github.com/Ichinga-Samuel/aiomql.git
synced 2026-07-30 21:57:44 +00:00
816 lines
30 KiB
Python
816 lines
30 KiB
Python
"""Comprehensive tests for the synchronous Trader module.
|
|
|
|
Tests cover:
|
|
- Trader initialization with default and custom values
|
|
- set_trade_stop_levels_pips method
|
|
- set_trade_stop_levels_points method
|
|
- create_order_with_stops method
|
|
- create_order_with_sl method
|
|
- create_order_with_points method
|
|
- create_order_no_stops method
|
|
- check_order method
|
|
- send_order method
|
|
- record_trade method
|
|
- Integration tests with various order types
|
|
- Edge cases and boundary conditions
|
|
"""
|
|
|
|
from math import floor
|
|
import pytest
|
|
|
|
from aiomql.lib.ram import RAM
|
|
from aiomql.lib.sync.trader import Trader
|
|
from aiomql.contrib.traders.sync import SimpleTrader
|
|
from aiomql.contrib.symbols.sync import ForexSymbol
|
|
from aiomql.lib.sync.symbol import Symbol
|
|
from aiomql.core.constants import OrderType
|
|
from aiomql.lib.sync.account import Account
|
|
from aiomql.lib.sync.order import Order
|
|
from aiomql.core.config import Config
|
|
from aiomql.core.models import OrderSendResult, OrderCheckResult
|
|
|
|
|
|
class TestTraderInitialization:
|
|
"""Test Trader class initialization."""
|
|
|
|
@classmethod
|
|
def setup_class(cls):
|
|
"""Set up test fixtures."""
|
|
cls.symbol = ForexSymbol(name="BTCUSD")
|
|
cls.ram = RAM(fixed_amount=10)
|
|
|
|
@pytest.fixture(scope="class", autouse=True)
|
|
def initialize(self):
|
|
"""Initialize symbol before tests."""
|
|
self.symbol.initialize()
|
|
|
|
def test_init_with_symbol_only(self):
|
|
"""Test Trader can be initialized with just a symbol."""
|
|
trader = SimpleTrader(symbol=self.symbol)
|
|
assert trader.symbol == self.symbol
|
|
assert isinstance(trader.ram, RAM)
|
|
assert isinstance(trader.order, Order)
|
|
|
|
def test_init_with_symbol_and_ram(self):
|
|
"""Test Trader initialized with symbol and custom RAM."""
|
|
trader = SimpleTrader(symbol=self.symbol, ram=self.ram)
|
|
assert trader.symbol == self.symbol
|
|
assert trader.ram == self.ram
|
|
|
|
def test_init_creates_order_with_symbol_name(self):
|
|
"""Test Trader creates order with correct symbol name."""
|
|
trader = SimpleTrader(symbol=self.symbol)
|
|
assert trader.order.symbol == self.symbol.name
|
|
|
|
def test_init_has_config_attribute(self):
|
|
"""Test Trader has config attribute."""
|
|
trader = SimpleTrader(symbol=self.symbol)
|
|
assert hasattr(trader, 'config')
|
|
assert isinstance(trader.config, Config)
|
|
|
|
def test_init_has_parameters_attribute(self):
|
|
"""Test Trader has empty parameters dict."""
|
|
trader = SimpleTrader(symbol=self.symbol)
|
|
assert hasattr(trader, 'parameters')
|
|
assert isinstance(trader.parameters, dict)
|
|
assert trader.parameters == {}
|
|
|
|
def test_init_with_default_ram_values(self):
|
|
"""Test Trader uses default RAM if not provided."""
|
|
trader = SimpleTrader(symbol=self.symbol)
|
|
assert trader.ram.risk_to_reward == 2
|
|
assert trader.ram.risk == 1
|
|
|
|
|
|
class TestSetTradeStopLevelsPips:
|
|
"""Test set_trade_stop_levels_pips method."""
|
|
|
|
@classmethod
|
|
def setup_class(cls):
|
|
"""Set up test fixtures."""
|
|
cls.symbol = ForexSymbol(name="EURUSD")
|
|
cls.ram = RAM(fixed_amount=10, risk_to_reward=2)
|
|
cls.trader = SimpleTrader(symbol=cls.symbol, ram=cls.ram)
|
|
|
|
@pytest.fixture(scope="class", autouse=True)
|
|
def initialize(self):
|
|
"""Initialize symbol."""
|
|
self.symbol.initialize()
|
|
|
|
def test_set_stop_levels_pips_buy_order(self):
|
|
"""Test setting stop levels for buy order using pips."""
|
|
tick = self.symbol.info_tick()
|
|
self.trader.order.price = tick.ask
|
|
self.trader.order.type = OrderType.BUY
|
|
pips = 50
|
|
|
|
self.trader.set_trade_stop_levels_pips(pips=pips)
|
|
|
|
expected_sl = round(tick.ask - (pips * self.symbol.pip), self.symbol.digits)
|
|
expected_tp = round(tick.ask + (pips * self.ram.risk_to_reward * self.symbol.pip), self.symbol.digits)
|
|
assert self.trader.order.sl == expected_sl
|
|
assert self.trader.order.tp == expected_tp
|
|
|
|
def test_set_stop_levels_pips_sell_order(self):
|
|
"""Test setting stop levels for sell order using pips."""
|
|
tick = self.symbol.info_tick()
|
|
self.trader.order.price = tick.bid
|
|
self.trader.order.type = OrderType.SELL
|
|
pips = 50
|
|
|
|
self.trader.set_trade_stop_levels_pips(pips=pips)
|
|
|
|
expected_sl = round(tick.bid + (pips * self.symbol.pip), self.symbol.digits)
|
|
expected_tp = round(tick.bid - (pips * self.ram.risk_to_reward * self.symbol.pip), self.symbol.digits)
|
|
assert self.trader.order.sl == expected_sl
|
|
assert self.trader.order.tp == expected_tp
|
|
|
|
def test_set_stop_levels_pips_custom_risk_to_reward(self):
|
|
"""Test setting stop levels with custom risk to reward ratio."""
|
|
tick = self.symbol.info_tick()
|
|
self.trader.order.price = tick.ask
|
|
self.trader.order.type = OrderType.BUY
|
|
pips = 30
|
|
custom_rr = 3
|
|
|
|
self.trader.set_trade_stop_levels_pips(pips=pips, risk_to_reward=custom_rr)
|
|
|
|
expected_sl = round(tick.ask - (pips * self.symbol.pip), self.symbol.digits)
|
|
expected_tp = round(tick.ask + (pips * custom_rr * self.symbol.pip), self.symbol.digits)
|
|
assert self.trader.order.sl == expected_sl
|
|
assert self.trader.order.tp == expected_tp
|
|
|
|
|
|
class TestSetTradeStopLevelsPoints:
|
|
"""Test set_trade_stop_levels_points method."""
|
|
|
|
@classmethod
|
|
def setup_class(cls):
|
|
"""Set up test fixtures."""
|
|
cls.symbol = ForexSymbol(name="EURUSD")
|
|
cls.ram = RAM(fixed_amount=10, risk_to_reward=2)
|
|
cls.trader = SimpleTrader(symbol=cls.symbol, ram=cls.ram)
|
|
|
|
@pytest.fixture(scope="class", autouse=True)
|
|
def initialize(self):
|
|
"""Initialize symbol."""
|
|
self.symbol.initialize()
|
|
|
|
def test_set_stop_levels_points_buy_order(self):
|
|
"""Test setting stop levels for buy order using points."""
|
|
tick = self.symbol.info_tick()
|
|
self.trader.order.price = tick.ask
|
|
self.trader.order.type = OrderType.BUY
|
|
points = 500
|
|
|
|
self.trader.set_trade_stop_levels_points(points=points)
|
|
|
|
expected_sl = round(tick.ask - (points * self.symbol.point), self.symbol.digits)
|
|
expected_tp = round(tick.ask + (points * self.ram.risk_to_reward * self.symbol.point), self.symbol.digits)
|
|
assert self.trader.order.sl == expected_sl
|
|
assert self.trader.order.tp == expected_tp
|
|
|
|
def test_set_stop_levels_points_custom_risk_to_reward(self):
|
|
"""Test setting stop levels with custom risk to reward."""
|
|
tick = self.symbol.info_tick()
|
|
self.trader.order.price = tick.ask
|
|
self.trader.order.type = OrderType.BUY
|
|
points = 500
|
|
custom_rr = 4
|
|
|
|
self.trader.set_trade_stop_levels_points(points=points, risk_to_reward=custom_rr)
|
|
|
|
expected_sl = round(tick.ask - (points * self.symbol.point), self.symbol.digits)
|
|
expected_tp = round(tick.ask + (points * custom_rr * self.symbol.point), self.symbol.digits)
|
|
assert self.trader.order.sl == expected_sl
|
|
assert self.trader.order.tp == expected_tp
|
|
|
|
|
|
class TestCreateOrderNoStops:
|
|
"""Test create_order_no_stops method."""
|
|
|
|
@classmethod
|
|
def setup_class(cls):
|
|
"""Set up test fixtures."""
|
|
cls.symbol = ForexSymbol(name="BTCUSD")
|
|
cls.ram = RAM(fixed_amount=10)
|
|
cls.trader = SimpleTrader(symbol=cls.symbol, ram=cls.ram)
|
|
|
|
@pytest.fixture(scope="class", autouse=True)
|
|
def initialize(self):
|
|
"""Initialize symbol."""
|
|
self.symbol.initialize()
|
|
|
|
def test_create_order_no_stops_buy(self):
|
|
"""Test creating buy order without stops."""
|
|
self.trader.create_order_no_stops(order_type=OrderType.BUY)
|
|
|
|
assert self.trader.order.type == OrderType.BUY
|
|
assert self.trader.order.volume == self.symbol.volume_min
|
|
assert self.trader.order.price is not None
|
|
|
|
def test_create_order_no_stops_sell(self):
|
|
"""Test creating sell order without stops."""
|
|
self.trader.create_order_no_stops(order_type=OrderType.SELL)
|
|
|
|
assert self.trader.order.type == OrderType.SELL
|
|
assert self.trader.order.volume == self.symbol.volume_min
|
|
assert self.trader.order.price is not None
|
|
|
|
def test_create_order_no_stops_with_custom_volume(self):
|
|
"""Test creating order with custom volume."""
|
|
custom_volume = self.symbol.volume_min * 2
|
|
self.trader.create_order_no_stops(order_type=OrderType.BUY, volume=custom_volume)
|
|
|
|
assert self.trader.order.volume == custom_volume
|
|
|
|
def test_create_order_no_stops_uses_correct_price(self):
|
|
"""Test order uses ask for buy and bid for sell."""
|
|
tick = self.symbol.info_tick()
|
|
|
|
self.trader.create_order_no_stops(order_type=OrderType.BUY)
|
|
# Price should be close to ask (may differ slightly due to timing)
|
|
assert abs(self.trader.order.price - tick.ask) < tick.ask * 0.01
|
|
|
|
def test_create_order_no_stops_send_success(self):
|
|
"""Test sending order without stops succeeds."""
|
|
self.trader.create_order_no_stops(order_type=OrderType.BUY)
|
|
result = self.trader.order.send()
|
|
|
|
assert result is not None
|
|
assert result.retcode == 10009
|
|
|
|
|
|
class TestCreateOrderWithSl:
|
|
"""Test create_order_with_sl method."""
|
|
|
|
@classmethod
|
|
def setup_class(cls):
|
|
"""Set up test fixtures."""
|
|
cls.symbol = ForexSymbol(name="BTCUSD")
|
|
cls.ram = RAM(fixed_amount=10, risk_to_reward=2)
|
|
cls.trader = SimpleTrader(symbol=cls.symbol, ram=cls.ram)
|
|
cls.account = Account()
|
|
|
|
@pytest.fixture(scope="class", autouse=True)
|
|
def initialize(self):
|
|
"""Initialize symbol and account."""
|
|
self.symbol.initialize()
|
|
self.account.refresh()
|
|
|
|
def test_create_order_with_sl_sell(self):
|
|
"""Test creating sell order with stop loss."""
|
|
dsl = (self.symbol.trade_stops_level * 2 + self.symbol.spread) * self.symbol.point
|
|
tick = self.symbol.info_tick()
|
|
sl = tick.bid + dsl
|
|
|
|
self.trader.create_order_with_sl(order_type=OrderType.SELL, sl=sl)
|
|
|
|
assert self.trader.order.type == OrderType.SELL
|
|
assert self.trader.order.sl == sl
|
|
assert self.trader.order.tp is not None
|
|
assert self.trader.order.volume > 0
|
|
|
|
def test_create_order_with_sl_buy(self):
|
|
"""Test creating buy order with stop loss."""
|
|
dsl = (self.symbol.trade_stops_level * 2 + self.symbol.spread) * self.symbol.point
|
|
tick = self.symbol.info_tick()
|
|
sl = tick.ask - dsl
|
|
|
|
self.trader.create_order_with_sl(order_type=OrderType.BUY, sl=sl)
|
|
|
|
assert self.trader.order.type == OrderType.BUY
|
|
assert self.trader.order.sl == sl
|
|
assert self.trader.order.tp is not None
|
|
|
|
def test_create_order_with_sl_respects_risk_to_reward(self):
|
|
"""Test TP is set according to risk to reward ratio."""
|
|
dsl = (self.symbol.trade_stops_level * 2 + self.symbol.spread) * self.symbol.point
|
|
tick = self.symbol.info_tick()
|
|
sl = tick.bid + dsl
|
|
|
|
self.trader.create_order_with_sl(order_type=OrderType.SELL, sl=sl)
|
|
|
|
# TP should be approximately at dsl * risk_to_reward distance from price
|
|
expected_dtp = dsl * self.ram.risk_to_reward
|
|
actual_dtp = abs(self.trader.order.price - self.trader.order.tp)
|
|
assert abs(actual_dtp - expected_dtp) < self.symbol.point * 10
|
|
|
|
def test_create_order_with_sl_custom_amount_to_risk(self):
|
|
"""Test creating order with custom amount to risk."""
|
|
dsl = (self.symbol.trade_stops_level * 2 + self.symbol.spread) * self.symbol.point
|
|
tick = self.symbol.info_tick()
|
|
sl = tick.bid + dsl
|
|
custom_amount = 20
|
|
|
|
self.trader.create_order_with_sl(order_type=OrderType.SELL, sl=sl, amount_to_risk=custom_amount)
|
|
|
|
assert self.trader.order.volume > 0
|
|
|
|
def test_create_order_with_sl_send_success(self):
|
|
"""Test order with SL can be sent successfully."""
|
|
dsl = (self.symbol.trade_stops_level * 2 + self.symbol.spread) * self.symbol.point
|
|
tick = self.symbol.info_tick()
|
|
sl = tick.bid + dsl
|
|
|
|
self.trader.create_order_with_sl(order_type=OrderType.SELL, sl=sl)
|
|
result = self.trader.order.send()
|
|
|
|
assert result is not None
|
|
assert result.retcode == 10009
|
|
|
|
|
|
class TestCreateOrderWithStops:
|
|
"""Test create_order_with_stops method."""
|
|
|
|
@classmethod
|
|
def setup_class(cls):
|
|
"""Set up test fixtures."""
|
|
cls.symbol = ForexSymbol(name="BTCUSD")
|
|
cls.ram = RAM(fixed_amount=10, risk_to_reward=2)
|
|
cls.trader = SimpleTrader(symbol=cls.symbol, ram=cls.ram)
|
|
cls.account = Account()
|
|
|
|
@pytest.fixture(scope="class", autouse=True)
|
|
def initialize(self):
|
|
"""Initialize symbol and account."""
|
|
self.symbol.initialize()
|
|
self.account.refresh()
|
|
|
|
def test_create_order_with_stops_buy(self):
|
|
"""Test creating buy order with SL and TP."""
|
|
dsl = (self.symbol.trade_stops_level * 2 + self.symbol.spread) * self.symbol.point
|
|
dtp = dsl * self.ram.risk_to_reward
|
|
tick = self.symbol.info_tick()
|
|
sl = tick.ask - dsl
|
|
tp = tick.ask + dtp
|
|
|
|
self.trader.create_order_with_stops(order_type=OrderType.BUY, sl=sl, tp=tp)
|
|
|
|
assert self.trader.order.type == OrderType.BUY
|
|
assert self.trader.order.sl == sl
|
|
assert self.trader.order.tp == tp
|
|
assert self.trader.order.volume > 0
|
|
|
|
def test_create_order_with_stops_sell(self):
|
|
"""Test creating sell order with SL and TP."""
|
|
dsl = (self.symbol.trade_stops_level * 2 + self.symbol.spread) * self.symbol.point
|
|
dtp = dsl * self.ram.risk_to_reward
|
|
tick = self.symbol.info_tick()
|
|
sl = tick.bid + dsl
|
|
tp = tick.bid - dtp
|
|
|
|
self.trader.create_order_with_stops(order_type=OrderType.SELL, sl=sl, tp=tp)
|
|
|
|
assert self.trader.order.type == OrderType.SELL
|
|
assert self.trader.order.sl == sl
|
|
assert self.trader.order.tp == tp
|
|
|
|
def test_create_order_with_stops_send_success(self):
|
|
"""Test order with stops can be sent successfully."""
|
|
dsl = (self.symbol.trade_stops_level * 2 + self.symbol.spread) * self.symbol.point
|
|
dtp = dsl * self.ram.risk_to_reward
|
|
tick = self.symbol.info_tick()
|
|
sl = tick.ask - dsl
|
|
tp = tick.ask + dtp
|
|
|
|
self.trader.create_order_with_stops(order_type=OrderType.BUY, sl=sl, tp=tp)
|
|
result = self.trader.order.send()
|
|
|
|
assert result is not None
|
|
assert result.retcode == 10009
|
|
|
|
def test_create_order_with_stops_custom_amount(self):
|
|
"""Test creating order with custom amount to risk."""
|
|
dsl = (self.symbol.trade_stops_level * 2 + self.symbol.spread) * self.symbol.point
|
|
dtp = dsl * 2
|
|
tick = self.symbol.info_tick()
|
|
sl = tick.ask - dsl
|
|
tp = tick.ask + dtp
|
|
custom_amount = 25
|
|
|
|
self.trader.create_order_with_stops(
|
|
order_type=OrderType.BUY, sl=sl, tp=tp, amount_to_risk=custom_amount
|
|
)
|
|
|
|
assert self.trader.order.volume > 0
|
|
|
|
|
|
class TestCreateOrderWithPoints:
|
|
"""Test create_order_with_points method."""
|
|
|
|
@classmethod
|
|
def setup_class(cls):
|
|
"""Set up test fixtures."""
|
|
cls.symbol = ForexSymbol(name="BTCUSD")
|
|
cls.ram = RAM(fixed_amount=10, risk_to_reward=2)
|
|
cls.trader = SimpleTrader(symbol=cls.symbol, ram=cls.ram)
|
|
cls.account = Account()
|
|
|
|
@pytest.fixture(scope="class", autouse=True)
|
|
def initialize(self):
|
|
"""Initialize symbol."""
|
|
self.symbol.initialize()
|
|
self.account.refresh()
|
|
|
|
def test_create_order_with_points_buy(self):
|
|
"""Test creating buy order with points."""
|
|
points = self.symbol.trade_stops_level * 2 + self.symbol.spread
|
|
|
|
self.trader.create_order_with_points(order_type=OrderType.BUY, points=points)
|
|
|
|
assert self.trader.order.type == OrderType.BUY
|
|
assert self.trader.order.volume > 0
|
|
assert self.trader.order.sl is not None
|
|
assert self.trader.order.tp is not None
|
|
|
|
def test_create_order_with_points_sell(self):
|
|
"""Test creating sell order with points."""
|
|
points = self.symbol.trade_stops_level * 2 + self.symbol.spread
|
|
|
|
self.trader.create_order_with_points(order_type=OrderType.SELL, points=points)
|
|
|
|
assert self.trader.order.type == OrderType.SELL
|
|
assert self.trader.order.volume > 0
|
|
|
|
def test_create_order_with_points_send_success(self):
|
|
"""Test order with points can be sent successfully."""
|
|
points = self.symbol.trade_stops_level * 2 + self.symbol.spread
|
|
|
|
self.trader.create_order_with_points(order_type=OrderType.BUY, points=points)
|
|
result = self.trader.order.send()
|
|
|
|
assert result is not None
|
|
assert result.retcode == 10009
|
|
|
|
def test_create_order_with_points_custom_risk_to_reward(self):
|
|
"""Test order with custom risk to reward."""
|
|
points = self.symbol.trade_stops_level * 2 + self.symbol.spread
|
|
custom_rr = 3
|
|
|
|
self.trader.create_order_with_points(
|
|
order_type=OrderType.BUY, points=points, risk_to_reward=custom_rr
|
|
)
|
|
|
|
# TP should be at points * custom_rr distance from price
|
|
expected_tp_distance = points * custom_rr * self.symbol.point
|
|
actual_tp_distance = abs(self.trader.order.tp - self.trader.order.price)
|
|
assert abs(actual_tp_distance - expected_tp_distance) < self.symbol.point * 10
|
|
|
|
|
|
class TestCheckOrder:
|
|
"""Test check_order method."""
|
|
|
|
@classmethod
|
|
def setup_class(cls):
|
|
"""Set up test fixtures."""
|
|
cls.symbol = ForexSymbol(name="BTCUSD")
|
|
cls.ram = RAM(fixed_amount=10)
|
|
cls.trader = SimpleTrader(symbol=cls.symbol, ram=cls.ram)
|
|
|
|
@pytest.fixture(scope="class", autouse=True)
|
|
def initialize(self):
|
|
"""Initialize symbol."""
|
|
self.symbol.initialize()
|
|
|
|
def test_check_order_returns_order_check_result(self):
|
|
"""Test check_order returns OrderCheckResult."""
|
|
self.trader.create_order_no_stops(order_type=OrderType.BUY)
|
|
result = self.trader.check_order()
|
|
|
|
assert result is None or isinstance(result, OrderCheckResult)
|
|
|
|
def test_check_order_success(self):
|
|
"""Test check_order succeeds for valid order."""
|
|
self.trader.create_order_no_stops(order_type=OrderType.BUY)
|
|
result = self.trader.check_order()
|
|
|
|
assert result is not None
|
|
assert result.retcode == 0
|
|
|
|
def test_check_order_has_margin_info(self):
|
|
"""Test check result contains margin information."""
|
|
self.trader.create_order_no_stops(order_type=OrderType.BUY)
|
|
result = self.trader.check_order()
|
|
|
|
assert result is not None
|
|
assert hasattr(result, 'margin')
|
|
|
|
def test_check_order_sell(self):
|
|
"""Test check_order works for sell orders."""
|
|
self.trader.create_order_no_stops(order_type=OrderType.SELL)
|
|
result = self.trader.check_order()
|
|
|
|
assert result is not None
|
|
assert result.retcode == 0
|
|
|
|
|
|
class TestSendOrder:
|
|
"""Test send_order method."""
|
|
|
|
@classmethod
|
|
def setup_class(cls):
|
|
"""Set up test fixtures."""
|
|
cls.symbol = ForexSymbol(name="BTCUSD")
|
|
cls.ram = RAM(fixed_amount=10)
|
|
cls.trader = SimpleTrader(symbol=cls.symbol, ram=cls.ram)
|
|
|
|
@pytest.fixture(scope="class", autouse=True)
|
|
def initialize(self):
|
|
"""Initialize symbol."""
|
|
self.symbol.initialize()
|
|
|
|
def test_send_order_returns_order_send_result(self):
|
|
"""Test send_order returns OrderSendResult."""
|
|
self.trader.create_order_no_stops(order_type=OrderType.BUY)
|
|
result = self.trader.send_order()
|
|
|
|
assert result is None or isinstance(result, OrderSendResult)
|
|
|
|
def test_send_order_success(self):
|
|
"""Test send_order succeeds for valid order."""
|
|
self.trader.create_order_no_stops(order_type=OrderType.BUY)
|
|
result = self.trader.send_order()
|
|
|
|
assert result is not None
|
|
assert result.retcode == 10009
|
|
|
|
def test_send_order_has_deal_ticket(self):
|
|
"""Test send result contains deal ticket."""
|
|
self.trader.create_order_no_stops(order_type=OrderType.BUY)
|
|
result = self.trader.send_order()
|
|
|
|
assert result is not None
|
|
assert hasattr(result, 'deal')
|
|
assert result.deal > 0
|
|
|
|
def test_send_order_has_order_ticket(self):
|
|
"""Test send result contains order ticket."""
|
|
self.trader.create_order_no_stops(order_type=OrderType.BUY)
|
|
result = self.trader.send_order()
|
|
|
|
assert result is not None
|
|
assert hasattr(result, 'order')
|
|
assert result.order > 0
|
|
|
|
|
|
class TestRecordTrade:
|
|
"""Test record_trade method."""
|
|
|
|
@classmethod
|
|
def setup_class(cls):
|
|
"""Set up test fixtures."""
|
|
cls.symbol = ForexSymbol(name="BTCUSD")
|
|
cls.ram = RAM(fixed_amount=10)
|
|
cls.trader = SimpleTrader(symbol=cls.symbol, ram=cls.ram)
|
|
|
|
@pytest.fixture(scope="class", autouse=True)
|
|
def initialize(self):
|
|
"""Initialize symbol."""
|
|
self.symbol.initialize()
|
|
|
|
def test_record_trade_with_successful_order(self):
|
|
"""Test recording a successful trade."""
|
|
self.trader.create_order_no_stops(order_type=OrderType.BUY)
|
|
result = self.trader.send_order()
|
|
|
|
# Should not raise an error
|
|
self.trader.record_trade(result=result, parameters={"test": "value"}, name="TestStrategy", use_task_queue=False)
|
|
|
|
def test_record_trade_with_parameters(self):
|
|
"""Test recording trade with custom parameters."""
|
|
self.trader.create_order_no_stops(order_type=OrderType.BUY)
|
|
result = self.trader.send_order()
|
|
|
|
params = {"strategy": "test", "risk": 1, "timeframe": "H1"}
|
|
self.trader.record_trade(result=result, parameters=params, name="MyStrategy", use_task_queue=False)
|
|
|
|
def test_record_trade_without_parameters(self):
|
|
"""Test recording trade without parameters."""
|
|
self.trader.create_order_no_stops(order_type=OrderType.BUY)
|
|
result = self.trader.send_order()
|
|
|
|
# Should not raise an error
|
|
self.trader.record_trade(result=result, name="SimpleStrategy", use_task_queue=False)
|
|
|
|
|
|
class TestTraderWithDifferentSymbols:
|
|
"""Test Trader with different symbols."""
|
|
|
|
@classmethod
|
|
def setup_class(cls):
|
|
"""Set up test fixtures."""
|
|
cls.btc_usd = ForexSymbol(name="BTCUSD")
|
|
cls.eur_jpy = ForexSymbol(name="EURJPY")
|
|
cls.ram = RAM(fixed_amount=10)
|
|
|
|
@pytest.fixture(scope="class", autouse=True)
|
|
def initialize(self):
|
|
"""Initialize symbols."""
|
|
self.btc_usd.initialize()
|
|
self.eur_jpy.initialize()
|
|
|
|
def test_trader_btc_usd(self):
|
|
"""Test trader with BTCUSD symbol."""
|
|
trader = SimpleTrader(symbol=self.btc_usd, ram=self.ram)
|
|
trader.create_order_no_stops(order_type=OrderType.BUY)
|
|
result = trader.send_order()
|
|
|
|
assert result is not None
|
|
assert result.retcode == 10009
|
|
|
|
def test_trader_eur_jpy(self):
|
|
"""Test trader with EURJPY symbol."""
|
|
trader = SimpleTrader(symbol=self.eur_jpy, ram=self.ram)
|
|
trader.create_order_no_stops(order_type=OrderType.SELL)
|
|
result = trader.send_order()
|
|
|
|
assert result is not None
|
|
assert result.retcode == 10009
|
|
|
|
|
|
class TestTraderIntegration:
|
|
"""Integration tests for Trader."""
|
|
|
|
@classmethod
|
|
def setup_class(cls):
|
|
"""Set up test fixtures."""
|
|
cls.symbol = ForexSymbol(name="BTCUSD")
|
|
cls.ram = RAM(fixed_amount=10, risk_to_reward=2)
|
|
cls.trader = SimpleTrader(symbol=cls.symbol, ram=cls.ram)
|
|
cls.account = Account()
|
|
|
|
@pytest.fixture(scope="class", autouse=True)
|
|
def initialize(self):
|
|
"""Initialize symbol and account."""
|
|
self.symbol.initialize()
|
|
self.account.refresh()
|
|
|
|
def test_full_trade_flow_buy(self):
|
|
"""Test complete trade flow for buy order."""
|
|
# Create order
|
|
points = self.symbol.trade_stops_level * 2 + self.symbol.spread
|
|
self.trader.create_order_with_points(order_type=OrderType.BUY, points=points)
|
|
|
|
# Check order
|
|
check_result = self.trader.check_order()
|
|
assert check_result is not None
|
|
assert check_result.retcode == 0
|
|
|
|
# Send order
|
|
send_result = self.trader.send_order()
|
|
assert send_result is not None
|
|
assert send_result.retcode == 10009
|
|
|
|
# Record trade
|
|
self.trader.record_trade(result=send_result, parameters={"test": True}, name="IntegrationTest", use_task_queue=False)
|
|
|
|
def test_full_trade_flow_sell(self):
|
|
"""Test complete trade flow for sell order."""
|
|
# Create order
|
|
dsl = (self.symbol.trade_stops_level * 2 + self.symbol.spread) * self.symbol.point
|
|
tick = self.symbol.info_tick()
|
|
sl = tick.bid + dsl
|
|
|
|
self.trader.create_order_with_sl(order_type=OrderType.SELL, sl=sl)
|
|
|
|
# Check order
|
|
check_result = self.trader.check_order()
|
|
assert check_result is not None
|
|
assert check_result.retcode == 0
|
|
|
|
# Send order
|
|
send_result = self.trader.send_order()
|
|
assert send_result is not None
|
|
assert send_result.retcode == 10009
|
|
|
|
def test_multiple_orders_same_trader(self):
|
|
"""Test creating multiple orders with same trader."""
|
|
# First order
|
|
self.trader.create_order_no_stops(order_type=OrderType.BUY)
|
|
result1 = self.trader.send_order()
|
|
assert result1 is not None
|
|
assert result1.retcode == 10009
|
|
|
|
# Second order (different type)
|
|
self.trader.create_order_no_stops(order_type=OrderType.SELL)
|
|
result2 = self.trader.send_order()
|
|
assert result2 is not None
|
|
assert result2.retcode == 10009
|
|
|
|
|
|
class TestTraderEdgeCases:
|
|
"""Test edge cases and boundary conditions."""
|
|
|
|
@classmethod
|
|
def setup_class(cls):
|
|
"""Set up test fixtures."""
|
|
cls.symbol = ForexSymbol(name="BTCUSD")
|
|
|
|
@pytest.fixture(scope="class", autouse=True)
|
|
def initialize(self):
|
|
"""Initialize symbol."""
|
|
self.symbol.initialize()
|
|
|
|
def test_trader_with_zero_fixed_amount(self):
|
|
"""Test trader with zero fixed amount RAM."""
|
|
ram = RAM(fixed_amount=0)
|
|
trader = SimpleTrader(symbol=self.symbol, ram=ram)
|
|
assert trader.ram.fixed_amount == 0
|
|
|
|
def test_trader_with_high_risk_to_reward(self):
|
|
"""Test trader with high risk to reward ratio."""
|
|
ram = RAM(fixed_amount=10, risk_to_reward=10)
|
|
trader = SimpleTrader(symbol=self.symbol, ram=ram)
|
|
assert trader.ram.risk_to_reward == 10
|
|
|
|
def test_trader_with_low_risk_to_reward(self):
|
|
"""Test trader with low risk to reward ratio."""
|
|
ram = RAM(fixed_amount=10, risk_to_reward=0.5)
|
|
trader = SimpleTrader(symbol=self.symbol, ram=ram)
|
|
assert trader.ram.risk_to_reward == 0.5
|
|
|
|
def test_trader_order_modification_after_creation(self):
|
|
"""Test modifying order attributes after creation."""
|
|
ram = RAM(fixed_amount=10)
|
|
trader = SimpleTrader(symbol=self.symbol, ram=ram)
|
|
trader.create_order_no_stops(order_type=OrderType.BUY)
|
|
|
|
original_volume = trader.order.volume
|
|
trader.order.volume = original_volume * 2
|
|
assert trader.order.volume == original_volume * 2
|
|
|
|
def test_trader_parameters_modification(self):
|
|
"""Test modifying trader parameters."""
|
|
ram = RAM(fixed_amount=10)
|
|
trader = SimpleTrader(symbol=self.symbol, ram=ram)
|
|
|
|
trader.parameters["custom_param"] = "value"
|
|
trader.parameters["risk"] = 5
|
|
|
|
assert trader.parameters["custom_param"] == "value"
|
|
assert trader.parameters["risk"] == 5
|
|
|
|
def test_trader_with_minimum_volume(self):
|
|
"""Test creating order with minimum volume."""
|
|
ram = RAM(fixed_amount=1) # Very small amount
|
|
trader = SimpleTrader(symbol=self.symbol, ram=ram)
|
|
trader.create_order_no_stops(order_type=OrderType.BUY)
|
|
|
|
assert trader.order.volume >= self.symbol.volume_min
|
|
|
|
|
|
class TestTraderRAMIntegration:
|
|
"""Test Trader integration with RAM."""
|
|
|
|
@classmethod
|
|
def setup_class(cls):
|
|
"""Set up test fixtures."""
|
|
cls.symbol = ForexSymbol(name="BTCUSD")
|
|
|
|
@pytest.fixture(scope="class", autouse=True)
|
|
def initialize(self):
|
|
"""Initialize symbol."""
|
|
self.symbol.initialize()
|
|
|
|
def test_trader_uses_ram_get_amount(self):
|
|
"""Test trader uses RAM get_amount for volume calculation."""
|
|
ram = RAM(fixed_amount=20)
|
|
trader = SimpleTrader(symbol=self.symbol, ram=ram)
|
|
|
|
dsl = (self.symbol.trade_stops_level * 2 + self.symbol.spread) * self.symbol.point
|
|
tick = trader.symbol.info_tick()
|
|
sl = tick.ask - dsl
|
|
|
|
trader.create_order_with_sl(order_type=OrderType.BUY, sl=sl)
|
|
|
|
# Volume should be calculated based on RAM fixed_amount (20)
|
|
assert trader.order.volume > 0
|
|
|
|
def test_trader_uses_ram_risk_to_reward(self):
|
|
"""Test trader uses RAM risk_to_reward for TP calculation."""
|
|
ram = RAM(fixed_amount=10, risk_to_reward=3)
|
|
trader = SimpleTrader(symbol=self.symbol, ram=ram)
|
|
|
|
dsl = (self.symbol.trade_stops_level * 2 + self.symbol.spread) * self.symbol.point
|
|
tick = trader.symbol.info_tick()
|
|
sl = tick.ask - dsl
|
|
|
|
trader.create_order_with_sl(order_type=OrderType.BUY, sl=sl)
|
|
|
|
# TP distance should be 3x the SL distance
|
|
sl_distance = abs(trader.order.price - trader.order.sl)
|
|
tp_distance = abs(trader.order.tp - trader.order.price)
|
|
|
|
assert abs(tp_distance - (sl_distance * 3)) < self.symbol.point * 10
|
|
|
|
def test_trader_modifying_ram_after_init(self):
|
|
"""Test modifying RAM after trader initialization."""
|
|
ram = RAM(fixed_amount=10)
|
|
trader = SimpleTrader(symbol=self.symbol, ram=ram)
|
|
|
|
trader.ram.modify_ram(fixed_amount=25, risk_to_reward=4)
|
|
|
|
assert trader.ram.fixed_amount == 25
|
|
assert trader.ram.risk_to_reward == 4
|