trading-platform/apps/llm-agent/tests/test_mt4_integration.py

305 lines
10 KiB
Python

"""
Tests for MT4 Integration with LLM Agent Auto-Trading
"""
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from datetime import datetime
from src.clients.mt4_client import (
MT4Client,
MT4ClientError,
OrderAction,
TradeResult
)
from src.services.auto_trade_service import AutoTradeService
from src.models.auto_trade import AutoTradeConfig, TradeDecision
class TestMT4Client:
"""Tests for MT4Client"""
@pytest.fixture
def client(self):
return MT4Client(data_service_url="http://localhost:8001")
@pytest.mark.asyncio
async def test_check_connection_disconnected(self, client):
"""Test check_connection when not connected"""
with patch.object(client, '_request', new_callable=AsyncMock) as mock_request:
mock_request.return_value = {"connected": False, "account_id": ""}
result = await client.check_connection()
assert result is False
assert client.is_connected is False
@pytest.mark.asyncio
async def test_check_connection_connected(self, client):
"""Test check_connection when connected"""
with patch.object(client, '_request', new_callable=AsyncMock) as mock_request:
mock_request.return_value = {
"connected": True,
"account_id": "test123",
"login": "12345",
"server": "Demo-Server",
"balance": 10000.0,
"currency": "USD"
}
result = await client.check_connection()
assert result is True
assert client.is_connected is True
assert client.account_info is not None
assert client.account_info.login == "12345"
@pytest.mark.asyncio
async def test_open_trade_buy(self, client):
"""Test opening a BUY trade"""
with patch.object(client, '_request', new_callable=AsyncMock) as mock_request:
mock_request.return_value = {
"success": True,
"order_id": "ORD123",
"position_id": "POS456"
}
result = await client.open_trade(
symbol="EURUSD",
action=OrderAction.BUY,
volume=0.1,
stop_loss=1.0900,
take_profit=1.1100,
comment="Test trade"
)
assert result.success is True
assert result.order_id == "ORD123"
assert result.position_id == "POS456"
@pytest.mark.asyncio
async def test_open_trade_failure(self, client):
"""Test handling trade failure"""
with patch.object(client, '_request', new_callable=AsyncMock) as mock_request:
mock_request.side_effect = MT4ClientError("Insufficient margin")
result = await client.open_trade(
symbol="XAUUSD",
action=OrderAction.BUY,
volume=1.0
)
assert result.success is False
assert "Insufficient margin" in result.error
@pytest.mark.asyncio
async def test_close_position(self, client):
"""Test closing a position"""
with patch.object(client, '_request', new_callable=AsyncMock) as mock_request:
mock_request.return_value = {
"success": True,
"position_id": "POS456"
}
result = await client.close_position("POS456")
assert result.success is True
assert result.position_id == "POS456"
class TestAutoTradeServiceMT4:
"""Tests for AutoTradeService MT4 integration"""
@pytest.fixture
def service(self):
return AutoTradeService()
@pytest.fixture
def sample_decision(self):
return TradeDecision(
symbol="EURUSD",
action="BUY",
confidence=0.85,
reasoning="Strong bullish signal in accumulation phase",
entry_price=1.1000,
take_profit=1.1100,
stop_loss=1.0950,
position_size=0.1,
ml_signal={"direction": "bullish", "confidence": 0.87},
amd_phase="accumulation"
)
@pytest.mark.asyncio
async def test_execute_paper_trade(self, service, sample_decision):
"""Test paper trade execution"""
result = await service._execute_paper_trade(sample_decision)
assert result["success"] is True
assert result["mode"] == "paper"
assert "PAPER-" in result["order_id"]
assert result["symbol"] == "EURUSD"
assert result["action"] == "BUY"
@pytest.mark.asyncio
async def test_execute_mt4_trade_not_connected(self, service, sample_decision):
"""Test MT4 trade when not connected"""
with patch.object(
service.mt4_client, 'check_connection', new_callable=AsyncMock
) as mock_check:
mock_check.return_value = False
result = await service._execute_mt4_trade(sample_decision)
assert result["success"] is False
assert "not connected" in result["error"].lower()
@pytest.mark.asyncio
async def test_execute_mt4_trade_success(self, service, sample_decision):
"""Test successful MT4 trade execution"""
with patch.object(
service.mt4_client, 'check_connection', new_callable=AsyncMock
) as mock_check:
mock_check.return_value = True
with patch.object(
service.mt4_client, 'open_trade', new_callable=AsyncMock
) as mock_trade:
mock_trade.return_value = TradeResult(
success=True,
order_id="MT4-ORD-123",
position_id="MT4-POS-456"
)
result = await service._execute_mt4_trade(sample_decision)
assert result["success"] is True
assert result["mode"] == "live"
assert result["order_id"] == "MT4-ORD-123"
def test_calculate_mt4_volume(self, service):
"""Test volume calculation"""
# Normal case
volume = service._calculate_mt4_volume("EURUSD", 0.5, 1.1000)
assert volume == 0.5
# Minimum lot size
volume = service._calculate_mt4_volume("EURUSD", 0.001, 1.1000)
assert volume == 0.01
# Maximum safety cap
volume = service._calculate_mt4_volume("EURUSD", 50.0, 1.1000)
assert volume == 10.0
# None position size
volume = service._calculate_mt4_volume("EURUSD", None, 1.1000)
assert volume == 0.01
@pytest.mark.asyncio
async def test_connect_mt4(self, service):
"""Test MT4 connection through service"""
with patch.object(
service.mt4_client, 'connect', new_callable=AsyncMock
) as mock_connect:
mock_connect.return_value = True
# Mock account_info
service.mt4_client._account_info = MagicMock()
service.mt4_client._account_info.login = "12345"
service.mt4_client._account_info.server = "Demo"
service.mt4_client._account_info.balance = 10000.0
service.mt4_client._account_info.currency = "USD"
service.mt4_client._account_info.leverage = 100
result = await service.connect_mt4("test-account-id")
assert result["success"] is True
assert result["connected"] is True
assert result["account"]["login"] == "12345"
@pytest.mark.asyncio
async def test_get_mt4_positions(self, service):
"""Test getting MT4 positions"""
from src.clients.mt4_client import MT4Position
with patch.object(
service.mt4_client, 'check_connection', new_callable=AsyncMock
) as mock_check:
mock_check.return_value = True
with patch.object(
service.mt4_client, 'get_positions', new_callable=AsyncMock
) as mock_positions:
mock_positions.return_value = [
MT4Position(
id="POS1",
symbol="EURUSD",
type="BUY",
volume=0.1,
open_price=1.1000,
current_price=1.1050,
stop_loss=1.0950,
take_profit=1.1100,
profit=50.0,
swap=-0.5,
open_time=datetime.utcnow(),
comment="Test"
)
]
result = await service.get_mt4_positions()
assert result["success"] is True
assert len(result["positions"]) == 1
assert result["positions"][0]["symbol"] == "EURUSD"
assert result["positions"][0]["profit"] == 50.0
class TestAutoTradeConfigModes:
"""Tests for paper vs live trading modes"""
@pytest.fixture
def service(self):
return AutoTradeService()
@pytest.fixture
def paper_config(self):
return AutoTradeConfig(
user_id="test_user",
enabled=True,
symbols=["EURUSD"],
max_risk_percent=1.0,
min_confidence=0.7,
paper_trading=True, # Paper mode
require_confirmation=False,
max_open_positions=3,
check_interval_minutes=5
)
@pytest.fixture
def live_config(self):
return AutoTradeConfig(
user_id="test_user",
enabled=True,
symbols=["EURUSD"],
max_risk_percent=1.0,
min_confidence=0.7,
paper_trading=False, # Live mode
require_confirmation=True, # Require confirmation for safety
max_open_positions=3,
check_interval_minutes=5
)
def test_paper_config_defaults(self, paper_config):
"""Test paper trading config"""
assert paper_config.paper_trading is True
assert paper_config.require_confirmation is False
def test_live_config_safety(self, live_config):
"""Test live trading requires confirmation by default"""
assert live_config.paper_trading is False
assert live_config.require_confirmation is True
if __name__ == "__main__":
pytest.main([__file__, "-v"])