""" 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