Files

379 lines
14 KiB
Python

"""Comprehensive tests for the synchronous Positions module.
Tests cover:
- Positions class initialization (BaseMeta metaclass behavior)
- Getting positions with various filters
- Getting positions by ticket and symbol
- Closing positions (individual and all)
- Class methods for position operations
- Edge cases and error handling
"""
import pytest
from aiomql.lib.sync.positions import Positions
from aiomql.core.models import TradePosition, OrderSendResult
from aiomql.core.exceptions import InvalidRequest
class TestPositionsInitialization:
"""Test Positions class initialization."""
def test_has_mt5_attribute(self):
"""Test Positions has mt5 class attribute."""
assert hasattr(Positions, 'mt5')
def test_has_config_attribute(self):
"""Test Positions has config class attribute."""
assert hasattr(Positions, 'config')
def test_class_attributes_are_shared(self):
"""Test that class attributes are shared across access points."""
assert Positions.mt5 is Positions.mt5
assert Positions.config is Positions.config
class TestGetPositionsLive:
"""Live tests for getting positions."""
@pytest.fixture(scope="class", autouse=True)
def init(self, make_buy_sell_orders_sync):
"""Initialize with live trades."""
cls = type(self)
cls.positions = Positions.get_positions()
def test_get_positions_returns_tuple(self):
"""Test get_positions returns a tuple."""
positions = Positions.get_positions()
assert isinstance(positions, tuple)
def test_get_positions_contains_trade_positions(self):
"""Test get_positions contains TradePosition objects."""
positions = Positions.get_positions()
for position in positions:
assert isinstance(position, TradePosition)
def test_get_positions_by_symbol(self):
"""Test get_positions can filter by symbol."""
if self.positions:
symbol = self.positions[0].symbol
positions = Positions.get_positions(symbol=symbol)
for position in positions:
assert position.symbol == symbol
def test_get_positions_by_ticket(self):
"""Test get_positions can filter by ticket."""
if self.positions:
ticket = self.positions[0].ticket
positions = Positions.get_positions(ticket=ticket)
assert len(positions) <= 1
if positions:
assert positions[0].ticket == ticket
def test_get_positions_by_group(self):
"""Test get_positions can filter by group."""
positions = Positions.get_positions(group="*USD*")
for position in positions:
assert "USD" in position.symbol
def test_get_positions_symbol_overrides_ticket(self):
"""Test that symbol filter takes precedence over ticket."""
if self.positions:
symbol = self.positions[0].symbol
# Pass both symbol and ticket, symbol should take precedence
positions = Positions.get_positions(symbol=symbol, ticket=99999999)
# Should still return positions for symbol, not error on ticket
for position in positions:
assert position.symbol == symbol
class TestGetPositionByTicketLive:
"""Live tests for get_position_by_ticket class method."""
@pytest.fixture(scope="class", autouse=True)
def init(self, make_buy_sell_orders_sync):
"""Initialize with live trades."""
cls = type(self)
cls.positions = Positions.get_positions()
def test_get_position_by_ticket_returns_trade_position(self):
"""Test get_position_by_ticket returns TradePosition."""
if self.positions:
ticket = self.positions[0].ticket
position = Positions.get_position_by_ticket(ticket=ticket)
assert isinstance(position, TradePosition)
def test_get_position_by_ticket_correct_ticket(self):
"""Test get_position_by_ticket returns position with matching ticket."""
if self.positions:
ticket = self.positions[0].ticket
position = Positions.get_position_by_ticket(ticket=ticket)
assert position.ticket == ticket
def test_get_position_by_ticket_nonexistent_returns_none(self):
"""Test get_position_by_ticket returns None for nonexistent ticket."""
position = Positions.get_position_by_ticket(ticket=999999999999)
assert position is None
def test_get_position_by_ticket_has_required_attributes(self):
"""Test returned position has required attributes."""
if self.positions:
ticket = self.positions[0].ticket
position = Positions.get_position_by_ticket(ticket=ticket)
assert hasattr(position, 'ticket')
assert hasattr(position, 'symbol')
assert hasattr(position, 'volume')
assert hasattr(position, 'type')
assert hasattr(position, 'price_open')
assert hasattr(position, 'price_current')
assert hasattr(position, 'profit')
class TestGetPositionsBySymbolLive:
"""Live tests for get_positions_by_symbol class method."""
@pytest.fixture(scope="class", autouse=True)
def init(self, make_buy_sell_orders_sync):
"""Ensure trades exist."""
pass
def test_get_positions_by_symbol_returns_tuple(self):
"""Test get_positions_by_symbol returns a tuple."""
positions = Positions.get_positions_by_symbol(symbol="BTCUSD")
assert isinstance(positions, tuple)
def test_get_positions_by_symbol_contains_trade_positions(self):
"""Test get_positions_by_symbol contains TradePosition objects."""
positions = Positions.get_positions_by_symbol(symbol="BTCUSD")
for position in positions:
assert isinstance(position, TradePosition)
def test_get_positions_by_symbol_correct_symbol(self):
"""Test all returned positions have the requested symbol."""
positions = Positions.get_positions_by_symbol(symbol="BTCUSD")
for position in positions:
assert position.symbol == "BTCUSD"
def test_get_positions_by_symbol_nonexistent_returns_empty(self):
"""Test get_positions_by_symbol returns empty tuple for nonexistent symbol."""
positions = Positions.get_positions_by_symbol(symbol="NONEXISTENT123")
assert positions == ()
class TestGetTotalPositionsLive:
"""Live tests for get_total_positions class method."""
@pytest.fixture(scope="class", autouse=True)
def init(self, make_buy_sell_orders_sync):
"""Ensure trades exist."""
pass
def test_get_total_positions_returns_int(self):
"""Test get_total_positions returns an integer."""
total = Positions.get_total_positions()
assert isinstance(total, int)
def test_get_total_positions_non_negative(self):
"""Test get_total_positions returns non-negative value."""
total = Positions.get_total_positions()
assert total >= 0
def test_get_total_positions_matches_get_positions(self):
"""Test get_total_positions matches length of get_positions."""
total = Positions.get_total_positions()
positions = Positions.get_positions()
assert total == len(positions)
class TestClosePositionLive:
"""Live tests for closing positions."""
@pytest.fixture(scope="class", autouse=True)
def init(self, make_buy_sell_orders_sync):
"""Initialize with live trades."""
cls = type(self)
cls.positions = Positions.get_positions()
def test_close_position_returns_tuple(self):
"""Test close_position returns a tuple of (bool, OrderSendResult)."""
if self.positions:
position = self.positions[0]
result = Positions.close_position(position=position)
assert isinstance(result, tuple)
assert len(result) == 2
def test_close_position_success(self):
"""Test close_position successfully closes a position."""
# Refresh positions
positions = Positions.get_positions()
if positions:
position = positions[0]
success, result = Positions.close_position(position=position)
if success:
assert isinstance(result, OrderSendResult)
assert result.retcode == 10009
def test_close_position_by_ticket_returns_tuple(self):
"""Test close_position_by_ticket returns a tuple."""
# Refresh positions
positions = Positions.get_positions()
if positions:
ticket = positions[0].ticket
result = Positions.close_position_by_ticket(ticket=ticket)
assert isinstance(result, tuple)
assert len(result) == 2
def test_close_position_by_ticket_nonexistent_raises(self):
"""Test close_position_by_ticket raises InvalidRequest for nonexistent ticket."""
with pytest.raises(InvalidRequest):
Positions.close_position_by_ticket(ticket=999999999999)
class TestCloseStaticMethodLive:
"""Live tests for the static close method."""
@pytest.fixture(scope="class", autouse=True)
def init(self, make_buy_sell_orders_sync):
"""Initialize with live trades."""
cls = type(self)
cls.positions = Positions.get_positions()
def test_close_returns_tuple(self):
"""Test close static method returns tuple of (bool, OrderSendResult)."""
# Refresh positions
positions = Positions.get_positions()
if positions:
position = positions[0]
result = Positions.close(
ticket=position.ticket,
symbol=position.symbol,
price=position.price_current,
volume=position.volume,
order_type=position.type,
)
assert isinstance(result, tuple)
assert len(result) == 2
class TestClosePositionsLive:
"""Live tests for close_positions class method."""
@pytest.fixture(scope="class", autouse=True)
def init(self, make_buy_sell_orders_sync):
"""Initialize with live trades."""
cls = type(self)
cls.positions = Positions.get_positions()
def test_close_positions_returns_tuple(self):
"""Test close_positions returns a tuple."""
positions = Positions.get_positions()
result = Positions.close_positions(positions=positions)
assert isinstance(result, tuple)
def test_close_positions_empty_positions(self):
"""Test close_positions with empty positions returns empty tuple."""
result = Positions.close_positions(positions=())
assert result == ()
class TestCloseAllPositionsLive:
"""Live tests for closing all positions."""
@pytest.fixture(scope="class", autouse=True)
def init(self, make_buy_sell_orders_sync):
"""Initialize with live trades."""
pass
def test_close_all_positions_returns_tuple(self):
"""Test close_all_positions class method returns a tuple."""
result = Positions.close_all_positions()
assert isinstance(result, tuple)
def test_close_all_positions_contains_order_send_results(self):
"""Test close_all_positions returns OrderSendResult objects."""
result = Positions.close_all_positions()
for res in result:
assert isinstance(res, OrderSendResult)
class TestPositionAttributes:
"""Test TradePosition attributes from positions."""
@pytest.fixture(scope="class", autouse=True)
def init(self, make_buy_sell_orders_sync):
"""Initialize with live trades."""
cls = type(self)
cls.positions = Positions.get_positions()
def test_position_has_ticket(self):
"""Test position has ticket attribute."""
if self.positions:
position = self.positions[0]
assert hasattr(position, 'ticket')
assert isinstance(position.ticket, int)
def test_position_has_symbol(self):
"""Test position has symbol attribute."""
if self.positions:
position = self.positions[0]
assert hasattr(position, 'symbol')
assert isinstance(position.symbol, str)
def test_position_has_volume(self):
"""Test position has volume attribute."""
if self.positions:
position = self.positions[0]
assert hasattr(position, 'volume')
assert isinstance(position.volume, float)
def test_position_has_type(self):
"""Test position has type attribute."""
if self.positions:
position = self.positions[0]
assert hasattr(position, 'type')
def test_position_has_price_open(self):
"""Test position has price_open attribute."""
if self.positions:
position = self.positions[0]
assert hasattr(position, 'price_open')
assert isinstance(position.price_open, float)
def test_position_has_price_current(self):
"""Test position has price_current attribute."""
if self.positions:
position = self.positions[0]
assert hasattr(position, 'price_current')
assert isinstance(position.price_current, float)
def test_position_has_profit(self):
"""Test position has profit attribute."""
if self.positions:
position = self.positions[0]
assert hasattr(position, 'profit')
assert isinstance(position.profit, float)
def test_position_has_sl_tp(self):
"""Test position has sl and tp attributes."""
if self.positions:
position = self.positions[0]
assert hasattr(position, 'sl')
assert hasattr(position, 'tp')
class TestPositionsEdgeCases:
"""Test edge cases and error handling."""
def test_get_positions_empty_when_no_positions(self):
"""Test get_positions returns empty tuple when no positions exist."""
# Close all positions first
Positions.close_all_positions()
result = Positions.get_positions()
# Result should be a tuple (possibly empty)
assert isinstance(result, tuple)
def test_get_positions_with_invalid_group(self):
"""Test get_positions with nonexistent group returns empty."""
result = Positions.get_positions(group="NONEXISTENT_GROUP_12345")
assert result == ()