v4.0.17 no-backtest

This commit is contained in:
Ichinga Samuel
2026-02-21 07:43:33 +01:00
parent 844e39194b
commit 97aea6952e
124 changed files with 8198 additions and 14349 deletions
-23
View File
@@ -1,23 +0,0 @@
import logging
from aiomql.lib.bot import Bot
from aiomql.contrib.strategies import Chaos
from aiomql.contrib.symbols import ForexSymbol
logger = logging.getLogger(__name__)
async def test_bot():
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
syms = ["BTCUSD", "SOLUSD", "ETHUSD"]
symbols = [ForexSymbol(name=sym) for sym in syms]
strategies = [Chaos(symbol=symbol, name="test_chaos", params={"interval": 3}) for symbol in symbols]
bot = Bot()
bot.executor.timeout = 10
bot.add_strategies(strategies=strategies)
await bot.initialize()
bot.executor.execute()
assert len(bot.executor.coroutines) == 1
assert len(bot.executor.coroutine_threads) == 1
assert len(bot.executor.strategy_runners) == 3
assert bot.config.shutdown is True
@@ -1,20 +0,0 @@
import logging
from aiomql.lib.bot import Bot
from aiomql.contrib.strategies import Chaos
from aiomql.contrib.symbols import ForexSymbol
def test_bot_sync():
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
syms = ["BTCUSD", "SOLUSD", "ETHUSD"]
symbols = [ForexSymbol(name=sym) for sym in syms]
strategies = [Chaos(symbol=symbol, name="test_chaos", params={"interval": 3}) for symbol in symbols]
bot = Bot()
bot.executor.timeout = 10
bot.add_strategies(strategies=strategies)
bot.initialize_sync()
bot.executor.execute()
assert len(bot.executor.strategy_runners) == 3
assert len(bot.executor.coroutines) == 1
assert len(bot.executor.coroutine_threads) == 1
@@ -1,110 +0,0 @@
import asyncio
import json
from csv import DictReader
import pytest
from aiomql.lib.result import Result
from aiomql.core.models import OrderSendResult
from aiomql.lib.trade_records import TradeRecords
from aiomql.lib.positions import Positions
class TestRecordsAndResults:
@classmethod
def setup_class(cls):
cls.trade_records = TradeRecords()
@pytest.fixture(scope="class")
async def buy(self, mt):
sym = "BTCUSD"
sym_info = await mt.symbol_info(sym)
dsl = (sym_info.trade_stops_level + sym_info.spread) * 2 * sym_info.point
sl = sym_info.ask - dsl
tp = sym_info.ask + dsl
return {
"action": mt.TRADE_ACTION_DEAL,
"symbol": sym,
"volume": sym_info.volume_min,
"type": mt.ORDER_TYPE_BUY,
"price": sym_info.ask,
"sl": sl,
"tp": tp,
}
@pytest.fixture(scope="class")
async def sell(self, mt):
sym = "BTCUSD"
sym_info = await mt.symbol_info(sym)
return {
"action": mt.TRADE_ACTION_DEAL,
"symbol": sym,
"volume": sym_info.volume_min,
"type": mt.ORDER_TYPE_SELL,
"price": sym_info.bid,
}
@pytest.fixture(scope="class", autouse=True)
async def setup(self, sell, buy, mt):
buy_res = await mt.order_send(buy)
buy_res_2 = await mt.order_send(buy)
sell_res = await mt.order_send(sell)
sell_res_2 = await mt.order_send(sell)
buy_res = Result(result=OrderSendResult(**buy_res._asdict()), name="test_result")
sell_res = Result(result=OrderSendResult(**sell_res._asdict()), name="test_result")
sell_res_2 = Result(result=OrderSendResult(**sell_res_2._asdict()), name="test_result")
buy_res_2 = Result(result=OrderSendResult(**buy_res_2._asdict()), name="test_result")
await asyncio.gather(
buy_res.save(),
sell_res.save(),
buy_res_2.save(trade_record_mode="json"),
sell_res_2.save(trade_record_mode="json"),
)
await Positions().close_all()
def test_records_dir(self):
records_dir = self.trade_records.records_dir
assert records_dir.is_dir()
recs = list(records_dir.iterdir())
assert len(recs) >= 2
csvs, jsons = [], []
matched_recs = list(records_dir.glob("test_result.*"))
assert len(matched_recs) == 2
for rec in matched_recs:
if rec.match("test_result.json"):
jsons.append(rec)
elif rec.match("test_result.csv"):
csvs.append(rec)
else:
continue
assert len(csvs) == 1
assert len(jsons) == 1
async def test_json_records(self):
json_records = self.trade_records.get_json_records()
matched_recs = [record for record in json_records if record.match("test_result.json")]
assert len(matched_recs) == 1
record = matched_recs[0]
record_data = json.load(record.open())
assert isinstance(record_data, list)
assert len(record_data) == 2
is_open = [data["closed"] is False for data in record_data]
assert all(is_open)
await self.trade_records.update_json_records()
is_close = [data["closed"] is True for data in record_data]
assert len(is_close) == 2
async def test_csv_records(self):
csv_records = self.trade_records.get_csv_records()
matched_recs = [record for record in csv_records if record.match("test_result.csv")]
assert len(matched_recs) == 1
record = matched_recs[0]
record_data = DictReader(record.open())
record_data = [row for row in record_data]
assert isinstance(record_data, list)
assert len(record_data) == 2
is_open = [data["closed"].title() == "False" for data in record_data]
assert all(is_open)
await self.trade_records.update_json_records()
is_close = [data["closed"].title() == "True" for data in record_data]
assert len(is_close) == 2
@@ -0,0 +1,421 @@
"""Full integration tests for the Bot class with live MetaTrader 5 connection.
Tests cover the complete Bot lifecycle including:
- Bot initialization and default state
- Strategy management (add_strategy, add_strategies, add_strategy_all)
- Terminal connection (async and sync)
- Strategy initialization (async and sync, including failures)
- Full execution lifecycle with Executor timeout
- add_function and add_coroutine integration
"""
import asyncio
import time
import threading
import pytest
from aiomql.lib.bot import Bot
from aiomql.lib.strategy import Strategy
from aiomql.lib.executor import Executor
from aiomql.lib.symbol import Symbol
from aiomql.core.config import Config
from aiomql.core.meta_trader import MetaTrader
from aiomql.core.exceptions import StopTrading
# ---------------------------------------------------------------------------
# Test Strategies
# ---------------------------------------------------------------------------
class TickLoggerStrategy(Strategy):
"""Strategy that logs the current tick and self-terminates."""
async def trade(self):
tick = await self.mt5.symbol_info_tick(self.symbol.name)
if tick is not None:
self.parameters["last_bid"] = tick.bid
self.parameters["last_ask"] = tick.ask
self.parameters["executed"] = True
self.running = False
class CandleFetchStrategy(Strategy):
"""Strategy that fetches the last 5 candles and self-terminates."""
async def trade(self):
rates = await self.mt5.copy_rates_from_pos(self.symbol.name, self.mt5.TIMEFRAME_M1, 0, 5)
if rates is not None:
self.parameters["candle_count"] = len(rates)
self.parameters["executed"] = True
self.running = False
class FailingStrategy(Strategy):
"""Strategy that raises StopTrading to test graceful shutdown."""
async def trade(self):
self.parameters["executed"] = True
raise StopTrading("Intentional stop for testing")
# ---------------------------------------------------------------------------
# Bot Initialization
# ---------------------------------------------------------------------------
class TestBotInitialization:
"""Test Bot class creation and default state."""
def test_bot_creates_executor(self):
"""Test Bot creates an Executor on init."""
bot = Bot()
assert isinstance(bot.executor, Executor)
def test_bot_default_flags(self):
"""Test Bot has correct default flags."""
bot = Bot()
assert bot.initialized is False
assert bot.login is False
assert bot.strategies == []
def test_bot_config_reference(self):
"""Test Bot holds a Config reference pointing back to itself."""
bot = Bot()
assert isinstance(bot.config, Config)
assert bot.config.bot is bot
def test_bot_has_mt5(self):
"""Test Bot creates a MetaTrader instance."""
bot = Bot()
assert isinstance(bot.mt5, MetaTrader)
# ---------------------------------------------------------------------------
# Strategy Management
# ---------------------------------------------------------------------------
class TestBotStrategyManagement:
"""Test adding strategies to the Bot."""
def test_add_strategy(self):
"""Test adding a single strategy."""
bot = Bot()
sym = Symbol(name="BTCUSD")
strategy = TickLoggerStrategy(symbol=sym)
bot.add_strategy(strategy=strategy)
assert len(bot.strategies) == 1
assert bot.strategies[0] is strategy
def test_add_strategies(self):
"""Test adding multiple strategies at once."""
bot = Bot()
strategies = [
TickLoggerStrategy(symbol=Symbol(name="BTCUSD")),
CandleFetchStrategy(symbol=Symbol(name="ETHUSD")),
]
bot.add_strategies(strategies=strategies)
assert len(bot.strategies) == 2
def test_add_strategy_all(self):
"""Test adding one strategy type across multiple symbols."""
bot = Bot()
symbols = [Symbol(name="BTCUSD"), Symbol(name="ETHUSD")]
bot.add_strategy_all(strategy=TickLoggerStrategy, symbols=symbols)
assert len(bot.strategies) == 2
assert all(isinstance(s, TickLoggerStrategy) for s in bot.strategies)
names = {s.symbol.name for s in bot.strategies}
assert names == {"BTCUSD", "ETHUSD"}
def test_add_strategy_all_with_params(self):
"""Test add_strategy_all passes params to each instance."""
bot = Bot()
symbols = [Symbol(name="BTCUSD")]
bot.add_strategy_all(
strategy=TickLoggerStrategy,
symbols=symbols,
params={"risk": 0.02},
)
assert bot.strategies[0].parameters["risk"] == 0.02
def test_add_strategy_preserves_order(self):
"""Test strategies are added in order."""
bot = Bot()
s1 = TickLoggerStrategy(symbol=Symbol(name="BTCUSD"), name="first")
s2 = CandleFetchStrategy(symbol=Symbol(name="ETHUSD"), name="second")
bot.add_strategy(strategy=s1)
bot.add_strategy(strategy=s2)
assert bot.strategies[0].name == "first"
assert bot.strategies[1].name == "second"
# ---------------------------------------------------------------------------
# Terminal Connection
# ---------------------------------------------------------------------------
class TestBotTerminalConnection:
"""Test terminal initialization and login."""
async def test_start_terminal_async(self):
"""Test async terminal start sets initialized and login flags."""
bot = Bot()
result = await bot.start_terminal()
assert result is True
assert bot.initialized is True
assert bot.login is True
def test_start_terminal_sync(self):
"""Test sync terminal start sets initialized and login flags."""
bot = Bot()
result = bot.start_terminal_sync()
assert result is True
assert bot.initialized is True
assert bot.login is True
# ---------------------------------------------------------------------------
# Strategy Initialization
# ---------------------------------------------------------------------------
class TestBotStrategyInitialization:
"""Test strategy initialization through the Bot."""
async def test_init_strategy_async(self):
"""Test async init_strategy initializes and registers a strategy."""
bot = Bot()
await bot.start_terminal()
strategy = TickLoggerStrategy(symbol=Symbol(name="BTCUSD"))
result = await bot.init_strategy(strategy=strategy)
assert result is True
assert strategy in bot.executor.strategy_runners
async def test_init_strategies_async(self):
"""Test async init_strategies initializes all strategies."""
bot = Bot()
await bot.start_terminal()
s1 = TickLoggerStrategy(symbol=Symbol(name="BTCUSD"))
s2 = CandleFetchStrategy(symbol=Symbol(name="ETHUSD"))
bot.add_strategy(strategy=s1)
bot.add_strategy(strategy=s2)
await bot.init_strategies()
assert len(bot.executor.strategy_runners) == 2
def test_init_strategy_sync(self):
"""Test sync init_strategy_sync initializes and registers a strategy."""
bot = Bot()
bot.start_terminal_sync()
strategy = TickLoggerStrategy(symbol=Symbol(name="BTCUSD"))
result = bot.init_strategy_sync(strategy=strategy)
assert result is True
assert strategy in bot.executor.strategy_runners
async def test_init_strategy_invalid_symbol(self):
"""Test init_strategy with an invalid symbol returns False."""
bot = Bot()
await bot.start_terminal()
strategy = TickLoggerStrategy(symbol=Symbol(name="INVALID_SYMBOL_XYZ"))
result = await bot.init_strategy(strategy=strategy)
assert result is False
assert strategy not in bot.executor.strategy_runners
async def test_init_strategies_partial_failure(self):
"""Test init_strategies handles mix of valid and invalid symbols."""
bot = Bot()
await bot.start_terminal()
s_good = TickLoggerStrategy(symbol=Symbol(name="BTCUSD"))
s_bad = CandleFetchStrategy(symbol=Symbol(name="INVALID_SYMBOL_XYZ"))
bot.add_strategy(strategy=s_good)
bot.add_strategy(strategy=s_bad)
await bot.init_strategies()
# Only the valid strategy should be in the executor
assert len(bot.executor.strategy_runners) == 1
assert s_good in bot.executor.strategy_runners
# ---------------------------------------------------------------------------
# Full Lifecycle Synchronous (execute)
# ---------------------------------------------------------------------------
class TestBotFullLifecycle:
"""Test full bot execution lifecycle using executor.timeout."""
def test_execute_with_tick_logger(self):
"""Test execute() runs a TickLoggerStrategy to completion."""
bot = Bot()
strategy = TickLoggerStrategy(
symbol=Symbol(name="BTCUSD"),
params={"executed": False},
)
bot.add_strategy(strategy=strategy)
bot.executor.timeout = 3
bot.execute()
assert strategy.parameters["executed"] is True
assert "last_bid" in strategy.parameters
assert "last_ask" in strategy.parameters
def test_execute_with_multiple_strategies(self):
"""Test execute() runs multiple strategies."""
bot = Bot()
s1 = TickLoggerStrategy(
symbol=Symbol(name="BTCUSD"),
params={"executed": False},
)
s2 = CandleFetchStrategy(
symbol=Symbol(name="ETHUSD"),
params={"executed": False},
)
bot.add_strategy(strategy=s1)
bot.add_strategy(strategy=s2)
bot.executor.timeout = 5
bot.execute()
assert s1.parameters["executed"] is True
assert s2.parameters["executed"] is True
assert s2.parameters["candle_count"] == 5
def test_execute_with_failing_strategy(self):
"""Test execute() handles a strategy that raises StopTrading."""
bot = Bot()
strategy = FailingStrategy(
symbol=Symbol(name="BTCUSD"),
params={"executed": False},
)
bot.add_strategy(strategy=strategy)
bot.executor.timeout = 3
bot.execute()
# Strategy should have run and set executed before raising
assert strategy.parameters["executed"] is True
# Strategy should have stopped running
assert strategy.running is False
def test_execute_no_strategies_sets_shutdown(self):
"""Test execute() with no strategies triggers shutdown flag."""
bot = Bot()
bot.executor.timeout = 2
bot.execute()
assert bot.config.shutdown is True
# ---------------------------------------------------------------------------
# Full Lifecycle Asynchronous (start)
# ---------------------------------------------------------------------------
class TestBotAsyncLifecycle:
"""Test full async bot lifecycle using executor.timeout."""
async def test_start_with_tick_logger(self):
"""Test start() runs a TickLoggerStrategy to completion."""
bot = Bot()
strategy = TickLoggerStrategy(
symbol=Symbol(name="BTCUSD"),
params={"executed": False},
)
bot.add_strategy(strategy=strategy)
bot.executor.timeout = 3
await bot.start()
assert strategy.parameters["executed"] is True
async def test_start_with_candle_fetcher(self):
"""Test start() runs a CandleFetchStrategy to completion."""
bot = Bot()
strategy = CandleFetchStrategy(
symbol=Symbol(name="BTCUSD"),
params={"executed": False},
)
bot.add_strategy(strategy=strategy)
bot.executor.timeout = 3
await bot.start()
assert strategy.parameters["executed"] is True
assert strategy.parameters["candle_count"] == 5
# ---------------------------------------------------------------------------
# add_function / add_coroutine
# ---------------------------------------------------------------------------
class TestBotAddFunctionCoroutine:
"""Test add_function and add_coroutine execute during bot lifecycle."""
def test_add_function_runs(self):
"""Test a function added via add_function is executed."""
result_holder = {"called": False}
def mark_called():
result_holder["called"] = True
bot = Bot()
strategy = TickLoggerStrategy(
symbol=Symbol(name="BTCUSD"),
params={"executed": False},
)
bot.add_strategy(strategy=strategy)
bot.add_function(function=mark_called)
bot.executor.timeout = 3
bot.execute()
assert result_holder["called"] is True
def test_add_coroutine_runs(self):
"""Test a coroutine added via add_coroutine is executed."""
result_holder = {"called": False}
async def async_mark():
result_holder["called"] = True
bot = Bot()
strategy = TickLoggerStrategy(
symbol=Symbol(name="BTCUSD"),
params={"executed": False},
)
bot.add_strategy(strategy=strategy)
bot.add_coroutine(coroutine=async_mark, on_separate_thread=True)
bot.executor.timeout = 3
bot.execute()
assert result_holder["called"] is True
# ---------------------------------------------------------------------------
# Mixed Strategy Types
# ---------------------------------------------------------------------------
class TestBotMixedStrategies:
"""Test Bot with a mix of strategies on different symbols."""
def test_execute_three_strategies_different_symbols(self):
"""Test execute with tick, candle, and failing strategies on different symbols."""
bot = Bot()
s1 = TickLoggerStrategy(
symbol=Symbol(name="BTCUSD"),
params={"executed": False},
)
s2 = CandleFetchStrategy(
symbol=Symbol(name="ETHUSD"),
params={"executed": False},
)
s3 = FailingStrategy(
symbol=Symbol(name="BTCUSD"),
params={"executed": False},
)
bot.add_strategies(strategies=[s1, s2, s3])
bot.executor.timeout = 5
bot.execute()
# All strategies should have executed
assert s1.parameters["executed"] is True
assert s2.parameters["executed"] is True
assert s3.parameters["executed"] is True
# Verify specific strategy results
assert "last_bid" in s1.parameters
assert s2.parameters["candle_count"] == 5
# Failing strategy should have stopped
assert s3.running is False
def test_same_strategy_on_multiple_symbols(self):
"""Test add_strategy_all runs the same strategy type on multiple symbols."""
bot = Bot()
symbols = [Symbol(name="BTCUSD"), Symbol(name="ETHUSD")]
bot.add_strategy_all(
strategy=TickLoggerStrategy,
symbols=symbols,
params={"executed": False},
)
bot.executor.timeout = 5
bot.execute()
for s in bot.strategies:
assert s.parameters["executed"] is True
assert "last_bid" in s.parameters
@@ -1,557 +0,0 @@
"""Full integration tests for aiomql library in live mode.
This module tests the integration of all major components:
- Bot initialization and terminal connection
- Multiple strategies running concurrently on different symbols
- Position trackers and tracking functions
- Order creation and management
- State management and configuration
Note: These tests require a live MetaTrader 5 connection and should be run
with caution on a demo account.
"""
import pytest
import asyncio
import logging
from unittest.mock import MagicMock, AsyncMock, patch
from aiomql.lib.bot import Bot
from aiomql.lib.strategy import Strategy
from aiomql.lib.symbol import Symbol
from aiomql.lib.trader import Trader
from aiomql.lib.executor import Executor
from aiomql.lib.order import Order
from aiomql.lib.positions import Positions
from aiomql.lib.account import Account
from aiomql.lib.ram import RAM
from aiomql.core.config import Config
from aiomql.core.state import State
from aiomql.core.constants import OrderType, TimeFrame, TradeAction
from aiomql.contrib.symbols import ForexSymbol
from aiomql.contrib.strategies import Chaos
from aiomql.contrib.trackers import (
PositionTracker,
OpenPositionsTracker,
OpenPosition,
exit_at_profit,
extend_take_profit
)
from aiomql.contrib.utils.strategy_tracker import StrategyTracker
logger = logging.getLogger(__name__)
class SimpleTestStrategy(Strategy):
"""A simple test strategy for integration testing."""
parameters = {"interval": 1, "test_param": "value"}
def __init__(self, *, symbol: Symbol, params: dict = None, sessions=None, name="TestStrategy"):
super().__init__(symbol=symbol, params=params, sessions=sessions, name=name)
self.trade_count = 0
self.tracker = StrategyTracker()
async def trade(self):
"""Execute a simple trade iteration."""
self.trade_count += 1
self.tracker.update(trend="bullish" if self.trade_count % 2 == 0 else "bearish")
await self.sleep(secs=self.interval)
class TrendFollowerStrategy(Strategy):
"""A trend following strategy for testing multiple strategy types."""
parameters = {"timeframe": TimeFrame.M1, "period": 20}
def __init__(self, *, symbol: Symbol, params: dict = None, sessions=None, name="TrendFollower"):
super().__init__(symbol=symbol, params=params, sessions=sessions, name=name)
self.signals = []
async def trade(self):
"""Execute trend following logic."""
# Simulate checking trend
self.signals.append({"time": asyncio.get_event_loop().time(), "symbol": self.symbol.name})
await self.sleep(secs=1)
class TestBotIntegration:
"""Integration tests for Bot class with multiple strategies."""
@pytest.fixture
def mock_mt5(self):
"""Mock MetaTrader connection for testing."""
with patch("aiomql.core.meta_trader.MetaTrader") as mock:
mock_instance = MagicMock()
mock_instance.initialize = AsyncMock(return_value=True)
mock_instance.login = AsyncMock(return_value=True)
mock_instance.shutdown = AsyncMock()
mock.return_value = mock_instance
yield mock_instance
def test_bot_initialization(self):
"""Test Bot initializes with correct components."""
bot = Bot()
assert bot.config is not None
assert bot.executor is not None
assert bot.mt5 is not None
assert bot.initialized is False
assert bot.login is False
def test_bot_add_single_strategy(self):
"""Test adding a single strategy to bot."""
bot = Bot()
mock_symbol = MagicMock(spec=Symbol)
mock_symbol.name = "EURUSD"
strategy = SimpleTestStrategy(symbol=mock_symbol, name="test_simple")
bot.add_strategy(strategy=strategy)
assert len(bot.strategies) == 1
assert bot.strategies[0].name == "test_simple"
def test_bot_add_multiple_strategies(self):
"""Test adding multiple strategies to bot."""
bot = Bot()
symbols = []
for name in ["EURUSD", "GBPUSD", "USDJPY"]:
mock_symbol = MagicMock(spec=Symbol)
mock_symbol.name = name
symbols.append(mock_symbol)
strategies = [
SimpleTestStrategy(symbol=symbols[0], name="strategy_eur"),
TrendFollowerStrategy(symbol=symbols[1], name="strategy_gbp"),
SimpleTestStrategy(symbol=symbols[2], name="strategy_jpy")
]
bot.add_strategies(strategies=strategies)
assert len(bot.strategies) == 3
def test_bot_add_coroutine(self):
"""Test adding coroutine to bot."""
bot = Bot()
async def test_coro(param1="default"):
await asyncio.sleep(0.1)
bot.add_coroutine(coroutine=test_coro, param1="value")
assert len(bot.executor.coroutines) == 1
def test_bot_add_function(self):
"""Test adding synchronous function to bot."""
bot = Bot()
def test_func(param1="default"):
pass
bot.add_function(function=test_func, param1="value")
assert len(bot.executor.functions) == 1
class TestExecutorIntegration:
"""Integration tests for Executor with multiple strategies."""
def test_executor_add_strategies(self):
"""Test executor can add multiple strategies."""
executor = Executor()
mock_symbol = MagicMock(spec=Symbol)
mock_symbol.name = "EURUSD"
strategies = [
SimpleTestStrategy(symbol=mock_symbol, name=f"strategy_{i}")
for i in range(5)
]
executor.add_strategies(strategies=tuple(strategies))
assert len(executor.strategy_runners) == 5
def test_executor_mixed_tasks(self):
"""Test executor with strategies, coroutines, and functions."""
executor = Executor()
mock_symbol = MagicMock(spec=Symbol)
mock_symbol.name = "EURUSD"
# Add strategy
strategy = SimpleTestStrategy(symbol=mock_symbol, name="test")
executor.add_strategy(strategy=strategy)
# Add coroutine
async def coro():
pass
executor.add_coroutine(coroutine=coro, kwargs={})
# Add function
def func():
pass
executor.add_function(function=func, kwargs={})
assert len(executor.strategy_runners) == 1
assert len(executor.coroutines) == 1
assert len(executor.functions) == 1
class TestStrategyIntegration:
"""Integration tests for Strategy class."""
def test_strategy_initialization(self):
"""Test strategy initializes with parameters."""
mock_symbol = MagicMock(spec=Symbol)
mock_symbol.name = "EURUSD"
strategy = SimpleTestStrategy(
symbol=mock_symbol,
name="test_strategy",
params={"custom_param": 100}
)
assert strategy.name == "test_strategy"
assert strategy.symbol is mock_symbol
assert strategy.custom_param == 100
def test_strategy_tracker_integration(self):
"""Test strategy with StrategyTracker."""
mock_symbol = MagicMock(spec=Symbol)
mock_symbol.name = "EURUSD"
strategy = SimpleTestStrategy(symbol=mock_symbol)
# Simulate trade iterations
strategy.tracker.update(trend="bullish")
assert strategy.tracker.bullish is True
assert strategy.tracker.bearish is False
strategy.tracker.update(trend="bearish")
assert strategy.tracker.bearish is True
assert strategy.tracker.bullish is False
def test_multiple_strategy_types(self):
"""Test different strategy types can coexist."""
mock_symbol = MagicMock(spec=Symbol)
mock_symbol.name = "EURUSD"
simple = SimpleTestStrategy(symbol=mock_symbol, name="simple")
trend = TrendFollowerStrategy(symbol=mock_symbol, name="trend")
assert simple.name == "simple"
assert trend.name == "trend"
assert simple.parameters != trend.parameters
class TestTrackerIntegration:
"""Integration tests for position tracking components."""
def test_position_tracker_initialization(self):
"""Test PositionTracker initialization with OpenPosition."""
mock_open_position = MagicMock()
mock_open_position.add_tracker = MagicMock()
async def tracking_func(pos, **kwargs):
pass
tracking_func.__name__ = "tracking_func"
tracker = PositionTracker(
mock_open_position,
tracking_func,
name="test_tracker",
rank=1,
function_params={"param": "value"}
)
assert tracker.name == "test_tracker"
assert tracker.rank == 1
assert tracker.params == {"param": "value"}
mock_open_position.add_tracker.assert_called_once()
async def test_position_tracker_execution(self):
"""Test PositionTracker executes tracking function."""
mock_open_position = MagicMock()
mock_open_position.add_tracker = MagicMock()
call_log = []
async def tracking_func(pos, **kwargs):
call_log.append({"pos": pos, "kwargs": kwargs})
tracking_func.__name__ = "tracking_func"
tracker = PositionTracker(
mock_open_position,
tracking_func,
function_params={"sl": -10, "tp": 20}
)
await tracker()
assert len(call_log) == 1
assert call_log[0]["kwargs"]["sl"] == -10
assert call_log[0]["kwargs"]["tp"] == 20
def test_strategy_tracker_state_management(self):
"""Test StrategyTracker manages trend state correctly."""
tracker = StrategyTracker()
# Initial state
assert tracker.ranging is True
assert tracker.bullish is False
assert tracker.bearish is False
# Transition to bullish
tracker.update(trend="bullish")
assert tracker.ranging is False
assert tracker.bullish is True
assert tracker.bearish is False
# Direct to bearish
tracker.update(trend="bearish")
assert tracker.ranging is False
assert tracker.bullish is False
assert tracker.bearish is True
# Back to ranging
tracker.update(trend="ranging")
assert tracker.ranging is True
assert tracker.bullish is False
assert tracker.bearish is False
class TestTrackingFunctionsIntegration:
"""Integration tests for position tracking functions."""
async def test_exit_at_profit_integration(self):
"""Test exit_at_profit with mock position."""
mock_position = MagicMock()
mock_position.profit = 100.0
mock_pos = MagicMock()
mock_pos.symbol = MagicMock()
mock_pos.symbol.name = "EURUSD"
mock_pos.ticket = 12345
mock_pos.position = mock_position
mock_pos.update_position = AsyncMock(return_value=True)
mock_pos.close_position = AsyncMock(return_value=(True, MagicMock()))
# Should close when profit >= tp
await exit_at_profit(mock_pos, tp=50.0)
mock_pos.close_position.assert_called_once()
async def test_exit_at_profit_no_action(self):
"""Test exit_at_profit does not close when conditions not met."""
mock_position = MagicMock()
mock_position.profit = 30.0 # Below tp, above sl
mock_pos = MagicMock()
mock_pos.position = mock_position
mock_pos.update_position = AsyncMock(return_value=True)
mock_pos.close_position = AsyncMock()
await exit_at_profit(mock_pos, tp=50.0, sl=-20.0)
mock_pos.close_position.assert_not_called()
class TestStateAndConfigIntegration:
"""Integration tests for State and Config components."""
def test_config_singleton(self):
"""Test Config is singleton."""
config1 = Config()
config2 = Config()
assert config1 is config2
def test_config_shutdown_flag(self):
"""Test config shutdown flag affects all references."""
config1 = Config()
config2 = Config()
original_shutdown = config1.shutdown
config1.shutdown = True
assert config2.shutdown is True
# Restore
config1.shutdown = original_shutdown
def test_state_initialization(self):
"""Test State can be initialized with key."""
state = State()
# State should support dict-like access
assert hasattr(state, "__setitem__")
assert hasattr(state, "__getitem__")
class TestMultiSymbolIntegration:
"""Integration tests for multiple symbols."""
def test_forex_symbol_creation(self):
"""Test creating multiple forex symbols."""
symbols = ["EURUSD", "GBPUSD", "USDJPY", "AUDUSD"]
forex_symbols = [ForexSymbol(name=sym) for sym in symbols]
assert len(forex_symbols) == 4
for i, sym in enumerate(forex_symbols):
assert sym.name == symbols[i]
def test_strategies_on_different_symbols(self):
"""Test strategies assigned to different symbols."""
symbols = [
ForexSymbol(name="EURUSD"),
ForexSymbol(name="GBPUSD"),
ForexSymbol(name="USDJPY")
]
strategies = []
for i, symbol in enumerate(symbols):
strategy = SimpleTestStrategy(
symbol=symbol,
name=f"strategy_{symbol.name}",
params={"interval": i + 1}
)
strategies.append(strategy)
assert len(strategies) == 3
assert strategies[0].symbol.name == "EURUSD"
assert strategies[1].symbol.name == "GBPUSD"
assert strategies[2].symbol.name == "USDJPY"
assert strategies[0].interval == 1
assert strategies[1].interval == 2
assert strategies[2].interval == 3
class TestFullBotWorkflow:
"""Integration tests for complete bot workflow."""
async def test_bot_with_chaos_strategy(self):
"""Test bot with Chaos strategy from contrib."""
symbols = [ForexSymbol(name=sym) for sym in ["BTCUSD", "ETHUSD"]]
strategies = [
Chaos(symbol=symbol, name=f"chaos_{symbol.name}", params={"interval": 1})
for symbol in symbols
]
bot = Bot()
bot.executor.timeout = 2 # Short timeout for testing
bot.add_strategies(strategies=strategies)
assert len(bot.strategies) == 2
assert bot.executor is not None
async def test_bot_with_mixed_strategies(self):
"""Test bot with different strategy types."""
symbol1 = ForexSymbol(name="EURUSD")
symbol2 = ForexSymbol(name="GBPUSD")
symbol3 = ForexSymbol(name="USDJPY")
strategies = [
SimpleTestStrategy(symbol=symbol1, name="simple_eur"),
TrendFollowerStrategy(symbol=symbol2, name="trend_gbp"),
Chaos(symbol=symbol3, name="chaos_jpy", params={"interval": 1})
]
bot = Bot()
bot.executor.timeout = 2
bot.add_strategies(strategies=strategies)
assert len(bot.strategies) == 3
# Verify different strategy types
strategy_names = [s.name for s in bot.strategies]
assert "simple_eur" in strategy_names
assert "trend_gbp" in strategy_names
assert "chaos_jpy" in strategy_names
async def test_bot_with_coroutines_and_functions(self):
"""Test bot with strategies, coroutines and functions."""
symbol = ForexSymbol(name="EURUSD")
strategy = SimpleTestStrategy(symbol=symbol, name="test")
async def monitor_task(interval=1):
"""Async monitoring task."""
await asyncio.sleep(interval)
def sync_logger(message=""):
"""Sync logging function."""
logger.info(message)
bot = Bot()
bot.executor.timeout = 2
bot.add_strategy(strategy=strategy)
bot.add_coroutine(coroutine=monitor_task, interval=1)
bot.add_function(function=sync_logger, message="test")
assert len(bot.strategies) == 1
assert len(bot.executor.coroutines) == 1
assert len(bot.executor.functions) == 1
class TestRAMIntegration:
"""Integration tests for Risk Assessment and Management."""
def test_ram_initialization(self):
"""Test RAM can be initialized with parameters."""
ram = RAM(
risk_to_reward=2.0,
risk=2.0,
min_amount=10.0,
max_amount=100.0
)
assert ram.risk_to_reward == 2.0
assert ram.risk == 2.0
def test_ram_default_values(self):
"""Test RAM has sensible defaults."""
ram = RAM()
# RAM should have default attributes
assert hasattr(ram, "risk_to_reward")
assert hasattr(ram, "risk")
assert ram.risk_to_reward == 2
assert ram.risk == 1
class TestOrderIntegration:
"""Integration tests for Order class."""
def test_order_creation(self):
"""Test Order can be created with required fields."""
order = Order(
symbol="EURUSD",
volume=0.1,
type=OrderType.BUY,
action=TradeAction.DEAL
)
assert order.symbol == "EURUSD"
assert order.volume == 0.1
assert order.type == OrderType.BUY
assert order.action == TradeAction.DEAL
def test_order_with_stops(self):
"""Test Order can include stop levels."""
order = Order(
symbol="EURUSD",
volume=0.1,
type=OrderType.BUY,
action=TradeAction.DEAL,
sl=1.0900,
tp=1.1100,
price=1.1000
)
assert order.sl == 1.0900
assert order.tp == 1.1100
assert order.price == 1.1000