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

294 lines
8.6 KiB
Python

"""
Tests for Auto-Trading functionality
"""
import pytest
from datetime import datetime
from src.models.auto_trade import (
TradeDecision,
AutoTradeConfig,
AutoTradeStatus,
DecisionLog
)
from src.services.auto_trade_service import AutoTradeService
class TestTradeDecisionModel:
"""Test TradeDecision model validation"""
def test_valid_trade_decision(self):
"""Test creating a valid trade decision"""
decision = TradeDecision(
symbol="BTC/USD",
action="BUY",
confidence=0.85,
reasoning="Strong bullish signal",
entry_price=45000.0,
take_profit=47500.0,
stop_loss=44000.0,
position_size=0.5,
ml_signal={"direction": "bullish", "confidence": 0.87},
amd_phase="accumulation"
)
assert decision.symbol == "BTC/USD"
assert decision.action == "BUY"
assert decision.confidence == 0.85
assert decision.amd_phase == "accumulation"
def test_invalid_confidence(self):
"""Test that confidence must be between 0 and 1"""
with pytest.raises(ValueError):
TradeDecision(
symbol="BTC/USD",
action="BUY",
confidence=1.5, # Invalid: > 1.0
reasoning="Test",
ml_signal={},
amd_phase="accumulation"
)
def test_invalid_action(self):
"""Test that action must be BUY, SELL, or HOLD"""
with pytest.raises(ValueError):
TradeDecision(
symbol="BTC/USD",
action="INVALID", # Invalid action
confidence=0.8,
reasoning="Test",
ml_signal={},
amd_phase="accumulation"
)
class TestAutoTradeConfig:
"""Test AutoTradeConfig model validation"""
def test_valid_config(self):
"""Test creating a valid config"""
config = AutoTradeConfig(
user_id="user_123",
enabled=True,
symbols=["BTC/USD", "ETH/USD"],
max_risk_percent=1.5,
min_confidence=0.75,
paper_trading=True,
require_confirmation=True,
max_open_positions=3,
check_interval_minutes=5
)
assert config.user_id == "user_123"
assert config.enabled is True
assert len(config.symbols) == 2
assert config.max_risk_percent == 1.5
def test_risk_percent_bounds(self):
"""Test risk percent must be within bounds"""
with pytest.raises(ValueError):
AutoTradeConfig(
user_id="user_123",
max_risk_percent=10.0 # Invalid: > 5.0
)
def test_default_values(self):
"""Test default values are applied"""
config = AutoTradeConfig(user_id="user_123")
assert config.enabled is False
assert config.symbols == []
assert config.max_risk_percent == 1.0
assert config.min_confidence == 0.7
assert config.paper_trading is True
assert config.require_confirmation is True
@pytest.mark.asyncio
class TestAutoTradeService:
"""Test AutoTradeService functionality"""
@pytest.fixture
def service(self):
"""Create a fresh service instance for each test"""
return AutoTradeService()
@pytest.fixture
def sample_config(self):
"""Sample configuration for testing"""
return AutoTradeConfig(
user_id="test_user",
enabled=False, # Start disabled for safety
symbols=["BTC/USD"],
max_risk_percent=1.0,
min_confidence=0.7,
paper_trading=True,
require_confirmation=True,
max_open_positions=3,
check_interval_minutes=5
)
async def test_set_config(self, service, sample_config):
"""Test setting configuration"""
status = await service.set_config(sample_config)
assert status is not None
assert status.user_id == "test_user"
assert status.enabled is False
assert status.monitored_symbols == ["BTC/USD"]
async def test_enable_monitoring(self, service, sample_config):
"""Test enabling monitoring starts background task"""
sample_config.enabled = True
status = await service.set_config(sample_config)
assert status.enabled is True
assert status.active_since is not None
assert "test_user" in service.monitoring_tasks
# Cleanup
await service._stop_monitoring("test_user")
async def test_disable_monitoring(self, service, sample_config):
"""Test disabling monitoring stops background task"""
# First enable
sample_config.enabled = True
await service.set_config(sample_config)
# Then disable
sample_config.enabled = False
status = await service.set_config(sample_config)
assert status.enabled is False
assert status.active_since is None
assert "test_user" not in service.monitoring_tasks
async def test_get_status(self, service, sample_config):
"""Test getting status"""
await service.set_config(sample_config)
status = await service.get_status("test_user")
assert status is not None
assert status.user_id == "test_user"
async def test_get_decision_logs(self, service, sample_config):
"""Test getting decision logs"""
await service.set_config(sample_config)
# Add a test decision log
decision = TradeDecision(
symbol="BTC/USD",
action="BUY",
confidence=0.85,
reasoning="Test decision",
ml_signal={"direction": "bullish"},
amd_phase="accumulation"
)
log = DecisionLog(
id="test_log_1",
user_id="test_user",
decision=decision,
executed=False
)
service.decision_logs.append(log)
# Get logs
logs = await service.get_decision_logs("test_user")
assert len(logs) == 1
assert logs[0].id == "test_log_1"
assert logs[0].decision.symbol == "BTC/USD"
async def test_cancel_decision(self, service, sample_config):
"""Test cancelling a pending decision"""
await service.set_config(sample_config)
# Add a test decision
decision = TradeDecision(
symbol="BTC/USD",
action="BUY",
confidence=0.85,
reasoning="Test",
ml_signal={},
amd_phase="accumulation"
)
log = DecisionLog(
id="test_log_cancel",
user_id="test_user",
decision=decision,
executed=False
)
service.decision_logs.append(log)
# Update status
status = await service.get_status("test_user")
status.pending_confirmations = 1
# Cancel decision
success = await service.cancel_decision("test_user", "test_log_cancel")
assert success is True
assert len(service.decision_logs) == 0
async def test_cannot_cancel_executed_decision(self, service, sample_config):
"""Test that executed decisions cannot be cancelled"""
await service.set_config(sample_config)
# Add an executed decision
decision = TradeDecision(
symbol="BTC/USD",
action="BUY",
confidence=0.85,
reasoning="Test",
ml_signal={},
amd_phase="accumulation"
)
log = DecisionLog(
id="test_log_executed",
user_id="test_user",
decision=decision,
executed=True # Already executed
)
service.decision_logs.append(log)
# Try to cancel
success = await service.cancel_decision("test_user", "test_log_executed")
assert success is False
assert len(service.decision_logs) == 1 # Still there
def test_decision_log_serialization():
"""Test that decision logs can be serialized"""
decision = TradeDecision(
symbol="BTC/USD",
action="BUY",
confidence=0.85,
reasoning="Test",
entry_price=45000.0,
ml_signal={"direction": "bullish"},
amd_phase="accumulation"
)
log = DecisionLog(
id="test_log",
user_id="test_user",
decision=decision,
executed=False
)
# Should be able to convert to dict
log_dict = log.model_dump()
assert log_dict["id"] == "test_log"
assert log_dict["user_id"] == "test_user"
assert log_dict["decision"]["symbol"] == "BTC/USD"
assert log_dict["executed"] is False