mirror of
https://github.com/Ichinga-Samuel/aiomql.git
synced 2026-07-30 13:47:44 +00:00
379 lines
14 KiB
Python
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 == ()
|