mirror of
https://github.com/Ichinga-Samuel/aiomql.git
synced 2026-07-30 21:57:44 +00:00
324 lines
11 KiB
Python
324 lines
11 KiB
Python
"""Comprehensive tests for the Symbol class.
|
|
|
|
This module contains live tests for the Symbol class, which provides
|
|
the interface for interacting with trading instruments in MetaTrader 5.
|
|
"""
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
import pytest
|
|
|
|
from aiomql.lib.symbol import Symbol
|
|
from aiomql.lib.candle import Candles
|
|
from aiomql.lib.ticks import Tick, Ticks
|
|
from aiomql.core.models import SymbolInfo, BookInfo
|
|
|
|
|
|
class TestSymbolInit:
|
|
"""Tests for Symbol initialisation."""
|
|
|
|
async def test_requires_name(self):
|
|
"""Test that Symbol raises AssertionError without a name."""
|
|
with pytest.raises(AssertionError):
|
|
Symbol()
|
|
|
|
async def test_initialized_starts_false(self):
|
|
"""Test that a newly created Symbol is not initialized."""
|
|
sym = Symbol(name="BTCUSD")
|
|
assert sym.initialized is False
|
|
|
|
async def test_has_account(self):
|
|
"""Test that a newly created Symbol has an account attribute."""
|
|
sym = Symbol(name="BTCUSD")
|
|
assert sym.account is not None
|
|
|
|
async def test_name_attribute(self):
|
|
"""Test that the name attribute is set correctly."""
|
|
sym = Symbol(name="BTCUSD")
|
|
assert sym.name == "BTCUSD"
|
|
|
|
|
|
class TestSymbolInitialize:
|
|
"""Tests for Symbol.initialize() and Symbol.initialize_sync()."""
|
|
|
|
@pytest.fixture(scope="class")
|
|
async def btc(self):
|
|
symbol = Symbol(name="BTCUSD")
|
|
await symbol.initialize()
|
|
return symbol
|
|
|
|
async def test_initialize_returns_true(self, btc):
|
|
"""Test that initialize() returns True for a valid symbol."""
|
|
assert btc.initialized is True
|
|
|
|
async def test_initialize_sets_select(self, btc):
|
|
"""Test that initialize() sets the select attribute."""
|
|
assert btc.select is True
|
|
|
|
async def test_initialize_sets_tick(self, btc):
|
|
"""Test that initialize() sets the tick attribute."""
|
|
assert btc.tick is not None
|
|
assert isinstance(btc.tick, Tick)
|
|
|
|
async def test_initialize_sets_symbol_properties(self, btc):
|
|
"""Test that initialize() populates SymbolInfo properties."""
|
|
assert btc.volume_min > 0
|
|
assert btc.volume_max > 0
|
|
assert btc.volume_step > 0
|
|
assert btc.point > 0
|
|
|
|
async def test_initialize_sync(self):
|
|
"""Test that initialize_sync() works correctly."""
|
|
sym = Symbol(name="BTCUSD")
|
|
result = sym.initialize_sync()
|
|
assert result is True
|
|
assert sym.initialized is True
|
|
assert sym.tick is not None
|
|
|
|
|
|
class TestSymbolInfo:
|
|
"""Tests for Symbol.info() and Symbol.info_tick()."""
|
|
|
|
@pytest.fixture(scope="class")
|
|
async def btc(self):
|
|
symbol = Symbol(name="BTCUSD")
|
|
await symbol.initialize()
|
|
return symbol
|
|
|
|
async def test_info_returns_symbol_info(self, btc):
|
|
"""Test that info() returns a SymbolInfo object."""
|
|
result = await btc.info()
|
|
assert result is not None
|
|
assert isinstance(result, SymbolInfo)
|
|
|
|
async def test_info_tick_returns_tick(self, btc):
|
|
"""Test that info_tick() returns a Tick object."""
|
|
tick = await btc.info_tick()
|
|
assert tick is not None
|
|
assert isinstance(tick, Tick)
|
|
|
|
async def test_info_tick_updates_tick_attribute(self, btc):
|
|
"""Test that info_tick() updates the symbol's tick attribute."""
|
|
tick = await btc.info_tick()
|
|
assert btc.tick is tick
|
|
|
|
async def test_info_tick_with_name(self, btc):
|
|
"""Test info_tick() with an explicit symbol name."""
|
|
tick = await btc.info_tick(name="ETHUSD")
|
|
assert tick is not None
|
|
assert isinstance(tick, Tick)
|
|
|
|
|
|
class TestSymbolSelect:
|
|
"""Tests for Symbol.symbol_select()."""
|
|
|
|
@pytest.fixture(scope="class")
|
|
async def btc(self):
|
|
symbol = Symbol(name="BTCUSD")
|
|
await symbol.initialize()
|
|
return symbol
|
|
|
|
async def test_symbol_select_enable(self, btc):
|
|
"""Test that symbol_select(enable=True) selects the symbol."""
|
|
result = await btc.symbol_select(enable=True)
|
|
assert result is True
|
|
assert btc.select is True
|
|
|
|
async def test_symbol_select_disable_and_renable(self, btc):
|
|
"""Test toggling symbol selection off and on."""
|
|
await btc.symbol_select(enable=False)
|
|
assert btc.select is False
|
|
await btc.symbol_select(enable=True)
|
|
assert btc.select is True
|
|
|
|
|
|
class TestSymbolBook:
|
|
"""Tests for Symbol market depth (book) methods."""
|
|
|
|
@pytest.fixture(scope="class")
|
|
async def btc(self):
|
|
symbol = Symbol(name="BTCUSD")
|
|
await symbol.initialize()
|
|
return symbol
|
|
|
|
async def test_book_add(self, btc):
|
|
"""Test that book_add() returns a boolean."""
|
|
result = await btc.book_add()
|
|
assert isinstance(result, bool)
|
|
|
|
async def test_book_get(self, btc):
|
|
"""Test that book_get() returns a tuple of BookInfo."""
|
|
await btc.book_add()
|
|
try:
|
|
books = await btc.book_get()
|
|
assert isinstance(books, tuple)
|
|
if len(books) > 0:
|
|
assert isinstance(books[0], BookInfo)
|
|
except ValueError:
|
|
# Market depth may not be available for all symbols
|
|
pass
|
|
|
|
async def test_book_release(self, btc):
|
|
"""Test that book_release() returns a boolean."""
|
|
result = await btc.book_release()
|
|
assert isinstance(result, bool)
|
|
|
|
|
|
class TestSymbolVolume:
|
|
"""Tests for Symbol volume-related methods."""
|
|
|
|
@pytest.fixture(scope="class")
|
|
async def btc(self):
|
|
symbol = Symbol(name="BTCUSD")
|
|
await symbol.initialize()
|
|
return symbol
|
|
|
|
async def test_check_volume_below_min(self, btc):
|
|
"""Test check_volume with volume below minimum."""
|
|
volume = btc.volume_min - btc.volume_step
|
|
success, result_vol = btc.check_volume(volume=volume)
|
|
assert success is False
|
|
assert result_vol == btc.volume_min
|
|
|
|
async def test_check_volume_valid(self, btc):
|
|
"""Test check_volume with a valid volume."""
|
|
volume = btc.volume_min + btc.volume_step * 2
|
|
success, result_vol = btc.check_volume(volume=volume)
|
|
assert success is True
|
|
assert result_vol == volume
|
|
|
|
async def test_check_volume_above_max(self, btc):
|
|
"""Test check_volume with volume above maximum."""
|
|
volume = btc.volume_max + btc.volume_step
|
|
success, result_vol = btc.check_volume(volume=volume)
|
|
assert success is False
|
|
assert result_vol == btc.volume_max
|
|
|
|
async def test_round_off_volume(self, btc):
|
|
"""Test round_off_volume rounds to nearest step."""
|
|
volume = btc.volume_min + btc.volume_step * 2.5
|
|
rounded = btc.round_off_volume(volume=volume, round_down=True)
|
|
assert rounded == btc.volume_min + btc.volume_step * 2
|
|
|
|
async def test_round_off_volume_up(self, btc):
|
|
"""Test round_off_volume rounding up."""
|
|
volume = btc.volume_min + btc.volume_step * 2.5
|
|
rounded = btc.round_off_volume(volume=volume, round_down=False)
|
|
assert rounded == btc.volume_min + btc.volume_step * 3
|
|
|
|
async def test_compute_volume_returns_min(self, btc):
|
|
"""Test that the base compute_volume() returns volume_min."""
|
|
result = await btc.compute_volume()
|
|
assert result == btc.volume_min
|
|
|
|
|
|
class TestSymbolCurrency:
|
|
"""Tests for Symbol currency conversion methods."""
|
|
|
|
@pytest.fixture(scope="class")
|
|
async def btc(self):
|
|
symbol = Symbol(name="BTCUSD")
|
|
await symbol.initialize()
|
|
return symbol
|
|
|
|
async def test_amount_in_quote_currency(self, btc):
|
|
"""Test amount_in_quote_currency returns a float."""
|
|
result = await btc.amount_in_quote_currency(amount=100.0)
|
|
assert isinstance(result, (int, float))
|
|
|
|
async def test_convert_currency(self, btc):
|
|
"""Test convert_currency between two currencies."""
|
|
result = await btc.convert_currency(
|
|
amount=100.0, from_currency="USD", to_currency="EUR"
|
|
)
|
|
# Result may be None if the conversion pair is not available
|
|
if result is not None:
|
|
assert isinstance(result, float)
|
|
assert result > 0
|
|
|
|
|
|
class TestSymbolRates:
|
|
"""Tests for Symbol copy rates and ticks methods."""
|
|
|
|
@pytest.fixture(scope="class")
|
|
async def btc(self):
|
|
symbol = Symbol(name="BTCUSD")
|
|
await symbol.initialize()
|
|
return symbol
|
|
|
|
async def test_copy_rates_from(self, btc):
|
|
"""Test copy_rates_from returns Candles."""
|
|
start = datetime(year=2023, month=10, day=5)
|
|
rates = await btc.copy_rates_from(
|
|
timeframe=btc.mt5.TIMEFRAME_H1, date_from=start, count=10
|
|
)
|
|
assert isinstance(rates, Candles)
|
|
assert len(rates) == 10
|
|
|
|
async def test_copy_rates_from_pos(self, btc):
|
|
"""Test copy_rates_from_pos returns Candles."""
|
|
rates = await btc.copy_rates_from_pos(
|
|
timeframe=btc.mt5.TIMEFRAME_H1, count=10, start_position=0
|
|
)
|
|
assert isinstance(rates, Candles)
|
|
assert len(rates) == 10
|
|
|
|
async def test_copy_rates_range(self, btc):
|
|
"""Test copy_rates_range returns Candles."""
|
|
start = datetime(year=2023, month=10, day=5)
|
|
end = start + timedelta(hours=9)
|
|
rates = await btc.copy_rates_range(
|
|
timeframe=btc.mt5.TIMEFRAME_H1, date_from=start, date_to=end
|
|
)
|
|
assert isinstance(rates, Candles)
|
|
assert len(rates) == 10
|
|
|
|
async def test_copy_ticks_from(self, btc):
|
|
"""Test copy_ticks_from returns Ticks."""
|
|
start = datetime(year=2023, month=10, day=5)
|
|
ticks = await btc.copy_ticks_from(date_from=start, count=10)
|
|
assert isinstance(ticks, Ticks)
|
|
assert len(ticks) == 10
|
|
|
|
async def test_copy_ticks_range(self, btc):
|
|
"""Test copy_ticks_range returns Ticks."""
|
|
start = datetime(year=2023, month=10, day=5)
|
|
end = start + timedelta(seconds=20)
|
|
ticks = await btc.copy_ticks_range(date_from=start, date_to=end)
|
|
assert isinstance(ticks, Ticks)
|
|
assert len(ticks) >= 10
|
|
|
|
|
|
class TestSymbolProperties:
|
|
"""Tests for Symbol properties."""
|
|
|
|
@pytest.fixture(scope="class")
|
|
async def btc(self):
|
|
symbol = Symbol(name="BTCUSD")
|
|
await symbol.initialize()
|
|
return symbol
|
|
|
|
async def test_pip_property(self, btc):
|
|
"""Test that pip equals point * 10."""
|
|
assert btc.pip == btc.point * 10
|
|
|
|
|
|
class TestSymbolAbstract:
|
|
"""Tests for Symbol abstract/overridable methods."""
|
|
|
|
@pytest.fixture(scope="class")
|
|
async def btc(self):
|
|
symbol = Symbol(name="BTCUSD")
|
|
await symbol.initialize()
|
|
return symbol
|
|
|
|
async def test_compute_volume_sl_raises(self, btc):
|
|
"""Test that compute_volume_sl raises NotImplementedError."""
|
|
with pytest.raises(NotImplementedError):
|
|
await btc.compute_volume_sl(amount=100.0, price=50000.0, sl=49500.0)
|
|
|
|
async def test_compute_volume_points_raises(self, btc):
|
|
"""Test that compute_volume_points raises NotImplementedError."""
|
|
with pytest.raises(NotImplementedError):
|
|
await btc.compute_volume_points(amount=100.0, points=500.0)
|