176 lines
6.4 KiB
Python
176 lines
6.4 KiB
Python
"""Tests for intelligent autopilot functionality."""
|
|
|
|
import pytest
|
|
from decimal import Decimal
|
|
from unittest.mock import Mock, patch, AsyncMock
|
|
from src.core.database import OrderSide, OrderType
|
|
|
|
|
|
class TestPreFlightValidation:
|
|
"""Tests for pre-flight order validation."""
|
|
|
|
@pytest.fixture
|
|
def mock_autopilot(self):
|
|
"""Create mock autopilot with necessary attributes."""
|
|
from src.autopilot.intelligent_autopilot import IntelligentAutopilot
|
|
|
|
with patch.object(IntelligentAutopilot, '__init__', lambda x, *args, **kwargs: None):
|
|
autopilot = IntelligentAutopilot.__new__(IntelligentAutopilot)
|
|
autopilot.symbol = 'BTC/USD'
|
|
autopilot.paper_trading = True
|
|
autopilot.logger = Mock()
|
|
|
|
# Mock trading engine
|
|
autopilot.trading_engine = Mock()
|
|
autopilot.trading_engine.paper_trading = Mock()
|
|
autopilot.trading_engine.paper_trading.get_balance.return_value = Decimal('1000.0')
|
|
autopilot.trading_engine.paper_trading.get_positions.return_value = []
|
|
|
|
return autopilot
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_can_execute_order_insufficient_funds(self, mock_autopilot):
|
|
"""Test that insufficient funds returns False."""
|
|
mock_autopilot.trading_engine.paper_trading.get_balance.return_value = Decimal('10.0')
|
|
|
|
can_execute, reason = await mock_autopilot._can_execute_order(
|
|
side=OrderSide.BUY,
|
|
quantity=Decimal('1.0'),
|
|
price=Decimal('100.0')
|
|
)
|
|
|
|
assert can_execute is False
|
|
assert 'Insufficient funds' in reason
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_can_execute_order_sufficient_funds(self, mock_autopilot):
|
|
"""Test that sufficient funds returns True."""
|
|
mock_autopilot.trading_engine.paper_trading.get_balance.return_value = Decimal('1000.0')
|
|
|
|
can_execute, reason = await mock_autopilot._can_execute_order(
|
|
side=OrderSide.BUY,
|
|
quantity=Decimal('1.0'),
|
|
price=Decimal('100.0')
|
|
)
|
|
|
|
assert can_execute is True
|
|
assert reason == 'OK'
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_can_execute_order_no_position_for_sell(self, mock_autopilot):
|
|
"""Test that SELL without position returns False."""
|
|
mock_autopilot.trading_engine.paper_trading.get_positions.return_value = []
|
|
|
|
can_execute, reason = await mock_autopilot._can_execute_order(
|
|
side=OrderSide.SELL,
|
|
quantity=Decimal('1.0'),
|
|
price=Decimal('100.0')
|
|
)
|
|
|
|
assert can_execute is False
|
|
assert 'No position to sell' in reason
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_can_execute_order_minimum_value(self, mock_autopilot):
|
|
"""Test that order below minimum value returns False."""
|
|
can_execute, reason = await mock_autopilot._can_execute_order(
|
|
side=OrderSide.BUY,
|
|
quantity=Decimal('0.001'),
|
|
price=Decimal('0.10') # Order value = $0.0001
|
|
)
|
|
|
|
assert can_execute is False
|
|
assert 'below minimum' in reason
|
|
|
|
|
|
class TestSmartOrderTypeSelection:
|
|
"""Tests for smart order type selection."""
|
|
|
|
@pytest.fixture
|
|
def mock_autopilot(self):
|
|
"""Create mock autopilot for order type tests."""
|
|
from src.autopilot.intelligent_autopilot import IntelligentAutopilot
|
|
|
|
with patch.object(IntelligentAutopilot, '__init__', lambda x, *args, **kwargs: None):
|
|
autopilot = IntelligentAutopilot.__new__(IntelligentAutopilot)
|
|
autopilot.logger = Mock()
|
|
return autopilot
|
|
|
|
def test_strong_signal_uses_market(self, mock_autopilot):
|
|
"""Test that strong signals (>80%) use MARKET orders."""
|
|
order_type, price = mock_autopilot._determine_order_type_and_price(
|
|
side=OrderSide.BUY,
|
|
signal_strength=0.85,
|
|
current_price=Decimal('100.0'),
|
|
is_stop_loss=False
|
|
)
|
|
|
|
assert order_type == OrderType.MARKET
|
|
assert price is None
|
|
|
|
def test_normal_signal_uses_limit(self, mock_autopilot):
|
|
"""Test that normal signals use LIMIT orders."""
|
|
order_type, price = mock_autopilot._determine_order_type_and_price(
|
|
side=OrderSide.BUY,
|
|
signal_strength=0.65,
|
|
current_price=Decimal('100.0'),
|
|
is_stop_loss=False
|
|
)
|
|
|
|
assert order_type == OrderType.LIMIT
|
|
assert price is not None
|
|
# BUY limit should be below market
|
|
assert price < Decimal('100.0')
|
|
|
|
def test_stop_loss_uses_market(self, mock_autopilot):
|
|
"""Test that stop-loss exits use MARKET orders."""
|
|
order_type, price = mock_autopilot._determine_order_type_and_price(
|
|
side=OrderSide.SELL,
|
|
signal_strength=0.5,
|
|
current_price=Decimal('100.0'),
|
|
is_stop_loss=True
|
|
)
|
|
|
|
assert order_type == OrderType.MARKET
|
|
assert price is None
|
|
|
|
def test_take_profit_uses_limit(self, mock_autopilot):
|
|
"""Test that take-profit exits can use LIMIT orders."""
|
|
order_type, price = mock_autopilot._determine_order_type_and_price(
|
|
side=OrderSide.SELL,
|
|
signal_strength=0.6,
|
|
current_price=Decimal('100.0'),
|
|
is_stop_loss=False
|
|
)
|
|
|
|
assert order_type == OrderType.LIMIT
|
|
assert price is not None
|
|
# SELL limit should be above market
|
|
assert price > Decimal('100.0')
|
|
|
|
def test_buy_limit_price_discount(self, mock_autopilot):
|
|
"""Test BUY LIMIT price is 0.1% below market."""
|
|
order_type, price = mock_autopilot._determine_order_type_and_price(
|
|
side=OrderSide.BUY,
|
|
signal_strength=0.6,
|
|
current_price=Decimal('1000.00'),
|
|
is_stop_loss=False
|
|
)
|
|
|
|
# 0.1% discount = 999.00
|
|
expected = Decimal('999.00')
|
|
assert price == expected
|
|
|
|
def test_sell_limit_price_premium(self, mock_autopilot):
|
|
"""Test SELL LIMIT price is 0.1% above market."""
|
|
order_type, price = mock_autopilot._determine_order_type_and_price(
|
|
side=OrderSide.SELL,
|
|
signal_strength=0.6,
|
|
current_price=Decimal('1000.00'),
|
|
is_stop_loss=False
|
|
)
|
|
|
|
# 0.1% premium = 1001.00
|
|
expected = Decimal('1001.00')
|
|
assert price == expected
|