This commit is contained in:
Ichinga Samuel
2024-10-28 06:36:10 +01:00
parent 06061ec917
commit 780d664741
90 changed files with 2230 additions and 636 deletions
View File
+20
View File
@@ -0,0 +1,20 @@
import pytest
from aiomql.lib.account import Account
class TestAccount:
@classmethod
def setup_class(cls):
cls.account = Account()
@pytest.fixture(scope='class', autouse=True)
async def refresh(self):
await self.account.refresh()
async def test_connected(self):
assert self.account.connected is True
async def test_account_info(self):
acc_info = await self.account.mt5.account_info()
assert acc_info.login == self.account.login
assert acc_info.server == self.account.server
+269
View File
@@ -0,0 +1,269 @@
from datetime import datetime, UTC
from aiomql import TimeFrame
from aiomql.contrib.backtesting import BackTestEngine
from aiomql.contrib.backtesting.get_data import GetData
from aiomql._utils import round_down
from aiomql.core.constants import OrderType, TradeAction
import pytest
class TestBackTestEngine:
@classmethod
def setup_class(cls):
cls.start = datetime(2024, 2, 1)
cls.end = datetime(2024, 2, 7)
cls.g_data = GetData(start=cls.start, end=cls.end, symbols=['BTCUSD', 'SOLUSD'],
timeframes=[TimeFrame.H1, TimeFrame.H2], name='test_engine')
cls.bte = BackTestEngine(start=cls.start, end=cls.end)
@pytest.fixture(scope='class')
async def bte2(self):
await self.g_data.get_data()
bte2 = BackTestEngine(start=self.start, end=self.end, data=self.g_data.data, use_terminal=False)
await bte2.setup_account(balance=100)
return bte2
@pytest.fixture(scope='class')
async def sell_order(self):
sym = await self.bte.get_symbol_info(symbol='BTCUSD')
request = {'type': OrderType.SELL, 'symbol': 'BTCUSD', 'volume': sym.volume_min,
'price': sym.bid, 'action': TradeAction.DEAL}
return request
@pytest.fixture(scope='class')
async def buy_order(self):
sym = await self.bte.get_symbol_info(symbol='BTCUSD')
dsl = (sym.trade_stops_level + sym.spread) * 2 * sym.point
sl = sym.ask - dsl
tp = sym.ask + dsl
request = {'type': OrderType.BUY, 'symbol': 'BTCUSD', 'volume': sym.volume_min,
'price': sym.ask, 'action': TradeAction.DEAL, 'sl': sl, 'tp': tp}
return request
def modify_stops(self, order):
...
def test_span_and_range(self):
assert self.bte.range == range(0, int((self.end - self.start).total_seconds()), self.bte.speed)
assert self.bte.span == range(int(self.start.timestamp()), int(self.end.timestamp()), self.bte.speed)
assert len(self.bte.span) == len(self.bte.range)
def test_cursor(self):
self.bte.next()
r, t = self.bte.cursor
self.bte.fast_forward(steps=100)
assert self.bte.cursor.time == t + 100
assert self.bte.cursor.index == r + 100
go_to = datetime(2024, 2, 3, tzinfo=UTC)
self.bte.go_to(time=go_to)
assert self.bte.cursor.time == int(datetime.timestamp(go_to))
self.bte.reset()
assert self.bte.cursor.time == int(self.start.timestamp())
def test_speed(self):
self.bte.setup_test_range(start=self.start, end=self.end, speed=3600)
assert self.bte.speed == 3600
self.bte.next()
now = datetime.fromtimestamp(self.bte.cursor.time, tz=UTC)
index = self.bte.cursor.index
self.bte.next()
assert self.bte.cursor.index == index + 3600
assert self.bte.cursor.time == int(now.timestamp()) + 3600
self.bte.setup_test_range(start=self.start, end=self.end)
assert self.bte.speed == 1
async def test_account(self):
await self.bte.setup_account(balance=100)
acc = self.bte.get_account_info()
self.bte.use_terminal_for_backtesting = False
self.bte.use_terminal_for_backtesting = True
assert acc.balance == 100
assert acc.equity == 100
assert acc.margin == 0
assert acc.margin_free == 100
assert acc.margin_level == 0
self.bte.deposit(amount=50)
acc = self.bte.get_account_info()
assert acc.balance == 150
assert acc.equity == 150
assert acc.margin == 0
assert acc.margin_free == 150
assert acc.margin_level == 0
self.bte.withdraw(amount=80)
acc = self.bte.get_account_info()
assert acc.balance == 70
assert acc.equity == 70
assert acc.margin == 0
assert acc.margin_free == 70
assert acc.margin_level == 0
self.bte.update_account(profit=-5)
acc = self.bte.get_account_info()
assert acc.equity == 65
assert acc.balance == 70
assert acc.profit == -5
assert acc.margin == 0
assert acc.margin_free == 65
assert acc.margin_level == 0
self.bte.update_account(margin=2.5)
acc = self.bte.get_account_info()
assert acc.balance == 70
assert acc.equity == 65
assert acc.margin == 2.5
assert acc.margin_free == 62.5
assert acc.margin_level == 2600
async def test_bte2_init(self, bte2):
assert bte2._data.fully_loaded is True
assert bte2.span == self.bte.span
assert bte2.range == self.bte.range
assert bte2.use_terminal is False
async def test_get_rates_from(self):
start = datetime(2024, 2, 3, 12, 43, tzinfo=UTC)
rates = await self.bte.get_rates_from(symbol='BTCUSD', timeframe=TimeFrame.H1, date_from=start, count=24)
assert len(rates) == 24
async def test_get_rates_from_2(self, bte2):
start = datetime(2024, 2, 3, 12, 12, tzinfo=UTC)
rates = await bte2.get_rates_from(symbol='BTCUSD', timeframe=TimeFrame.H1, date_from=start, count=24)
assert len(rates) == 24
async def test_get_rates_from_pos(self):
now = datetime(2024, 2, 3, 11, 55, tzinfo=UTC)
self.bte.go_to(time=now)
tf = TimeFrame.H2
start_pos = 2
rates = await self.bte.get_rates_from_pos(symbol='BTCUSD', timeframe=tf, start_pos=start_pos, count=24)
assert int(rates[-1][0]) == round_down(int(now.replace(hour=7).timestamp()), tf.seconds)
assert len(rates) == 24
async def test_get_rates_from_pos2(self, bte2):
now = datetime(2024, 2, 4, 12, 15, tzinfo=UTC)
bte2.go_to(time=now)
tf = TimeFrame.H1
start_pos = 2
rates = await bte2.get_rates_from_pos(symbol='BTCUSD', timeframe=tf, start_pos=start_pos, count=24)
assert int(rates[-1][0]) == round_down(int(now.replace(hour=10).timestamp()), tf.seconds)
# assert int(rates[-1][0]) == round_up(int(now.timestamp()), tf.seconds) - start_pos * tf.seconds
assert len(rates) == 24
async def test_get_rates_range(self):
start = datetime(2024, 2, 3, 12, tzinfo=UTC)
end = datetime(2024, 2, 4, 18, tzinfo=UTC)
rates = await self.bte.get_rates_range(symbol="BTCUSD", timeframe=TimeFrame.H1, date_from=start, date_to=end)
assert len(rates) == 31
assert int(rates[-1][0]) == int(end.timestamp())
async def test_get_rates_range2(self, bte2):
start = datetime(2024, 2, 3, 12, tzinfo=UTC)
end = datetime(2024, 2, 4, 18, tzinfo=UTC)
rates = await bte2.get_rates_range(symbol="BTCUSD", timeframe=TimeFrame.H1, date_from=start, date_to=end)
assert len(rates) == 31
assert int(rates[-1][0]) == int(end.timestamp())
async def test_get_ticks_from(self):
start = datetime(2024, 2, 3, 12, tzinfo=UTC)
ticks = await self.bte.get_ticks_from(symbol='BTCUSD', date_from=start, count=24)
assert len(ticks) == 24
async def test_get_ticks_from2(self, bte2):
start = datetime(2024, 2, 3, 12, tzinfo=UTC)
ticks = await bte2.get_ticks_from(symbol='BTCUSD', date_from=start, count=24)
assert len(ticks) == 24
async def test_get_ticks_range(self):
start = datetime(2024, 2, 3, 12, tzinfo=UTC)
end = datetime(2024, 2, 3, 15, tzinfo=UTC)
ticks = await self.bte.get_ticks_range(symbol="BTCUSD", date_from=start, date_to=end)
approx_total = (end - start).total_seconds() // 2 # assuming 2 ticks per second at least
assert len(ticks) >= approx_total
async def test_get_ticks_range2(self, bte2):
start = datetime(2024, 2, 3, 12, tzinfo=UTC)
end = datetime(2024, 2, 3, 15, tzinfo=UTC)
ticks = await bte2.get_ticks_range(symbol="BTCUSD", date_from=start, date_to=end)
approx_total = (end - start).total_seconds() // 2 # assuming 2 ticks per second at least
assert len(ticks) >= approx_total
async def test_price_tick(self, bte2):
moment = datetime(2024, 2, 3, 12, 12, tzinfo=UTC)
self.bte.reset()
self.bte.go_to(time=moment)
tick = await self.bte.get_price_tick(symbol='BTCUSD', time=self.bte.cursor.time)
assert tick is not None
assert isinstance(tick.ask, float)
assert tick.ask > 0
bte2.reset()
bte2.go_to(time=moment)
tick2 = await bte2.get_price_tick(symbol='BTCUSD', time=bte2.cursor.time)
assert tick.ask == tick2.ask
async def test_get_symbol_info(self, bte2):
moment = datetime(2024, 2, 3, 12, 12, tzinfo=UTC)
self.bte.reset()
self.bte.go_to(time=moment)
bte2.reset()
bte2.go_to(time=moment)
sym = 'BTCUSD'
sym_info = await self.bte.get_symbol_info(symbol=sym)
assert sym_info is not None
assert sym_info.name == sym
sym_info2 = await bte2.get_symbol_info(symbol=sym)
assert sym_info.ask == sym_info2.ask
async def test_order_profit(self, bte2):
moment = datetime(2024, 2, 3, 12, 12, tzinfo=UTC)
self.bte.reset()
self.bte.go_to(time=moment)
bte2.reset()
bte2.go_to(time=moment)
sym = 'BTCUSD'
sym_info = await self.bte.get_symbol_info(symbol=sym)
dsl = (sym_info.trade_stops_level + sym_info.spread) * 2 * sym_info.point
tp = sym_info.ask + dsl
profit = await self.bte.order_calc_profit(action=OrderType.BUY, symbol=sym,
volume=sym_info.volume_min, price_open=sym_info.ask,
price_close=tp)
assert profit > 0
sym_info2 = await bte2.get_symbol_info(symbol=sym)
dsl2 = (sym_info2.trade_stops_level + sym_info2.spread) * 2 * sym_info2.point
tp2 = sym_info2.ask + dsl2
profit2 = await bte2.order_calc_profit(action=OrderType.BUY, symbol=sym,
volume=sym_info2.volume_min, price_open=sym_info2.ask,
price_close=tp2)
assert profit == profit2
async def test_order_margin(self, bte2):
moment = datetime(2024, 2, 3, 12, 12, tzinfo=UTC)
self.bte.reset()
self.bte.go_to(time=moment)
bte2.reset()
bte2.go_to(time=moment)
sym = 'BTCUSD'
sym_info = await self.bte.get_symbol_info(symbol=sym)
margin = await self.bte.order_calc_margin(action=OrderType.SELL, symbol=sym,
volume=sym_info.volume_min, price=sym_info.bid)
assert margin > 0
sym_info2 = await self.bte.get_symbol_info(symbol=sym)
margin2 = await bte2.order_calc_margin(action=OrderType.SELL, symbol=sym,
volume=sym_info2.volume_min, price=sym_info2.bid)
assert margin2 > 0
async def test_order_check(self, buy_order, sell_order):
ocr = await self.bte.order_check(request=buy_order)
assert ocr is not None
assert ocr.retcode == 0
ocr2 = await self.bte.order_check(request=sell_order)
assert ocr2 is not None
assert ocr2.retcode == 0
async def test_order_send(self, buy_order, sell_order):
ocr = await self.bte.order_send(request=buy_order)
assert ocr is not None
assert ocr.retcode == 10009
ocr2 = await self.bte.order_send(request=sell_order)
assert ocr2 is not None
assert ocr2.retcode == 10009
+60
View File
@@ -0,0 +1,60 @@
import pytest
from aiomql.core.base import Base
class ChildClass(Base):
attr: int
attr2: str
cls_attr: int = 10
class TestBaseClass:
@pytest.fixture
def child(self):
return ChildClass(attr=1, attr2="test")
def test_repr(self, child):
repr_str = repr(child)
assert repr_str.startswith("ChildClass(")
assert "attr=1" in repr_str
assert "attr2=test" in repr_str
def test_set_attributes(self, child):
child.set_attributes(attr3=3.14, attr2='str')
assert child.attr2 == 'str'
assert getattr(child, 'attr3', None) is None
def test_annotations(self, child):
annotations = child.annotations
assert isinstance(annotations, dict)
def test_get_dict(self, child):
child.set_attributes(attr2='test')
result = child.get_dict()
assert result["attr"] == 1
assert result["attr2"] == "test"
def test_get_dict_with_exclude(self, child):
child.set_attributes(attr2='test')
result = child.get_dict(exclude={"attr"})
assert "attr" not in result
assert result["attr2"] == "test"
def test_get_dict_with_include(self, child):
child.set_attributes(attr3=3.14)
result = child.get_dict(include={"attr"})
assert result["attr"] == 1
assert "attr2" not in result
def test_class_vars(self, child):
class_vars = child.class_vars
assert isinstance(class_vars, dict)
assert 'cls_attr' in class_vars
assert 'attr' not in class_vars
def test_dict_property(self, child):
child.set_attributes(attr2="test")
dict_prop = child.dict
assert dict_prop["attr"] == 1
assert dict_prop["attr2"] == "test"
assert dict_prop["cls_attr"] == 10
+51
View File
@@ -0,0 +1,51 @@
import asyncio
import pytest
from aiomql.lib.bot import Bot
class TestBotFactoryAndExecutor:
@classmethod
def setup_class(cls):
cls.bot = Bot()
@pytest.fixture(scope='class', autouse=True)
async def initialize(self):
self.bot.add_coroutine(coroutine=self.coro_one)
self.bot.add_coroutine(coroutine=self.coro_two)
self.bot.add_function(function=self.fun_one)
self.bot.add_coroutine(coroutine=self.coro_thread, on_separate_thread=True)
await self.bot.initialize()
@staticmethod
def fun_one():
print('function one')
@staticmethod
async def coro_thread():
while True:
print('coroutine thread')
await asyncio.sleep(1)
@staticmethod
async def coro_one():
while True:
print('coroutine one')
await asyncio.sleep(1)
@staticmethod
async def coro_two():
while True:
print('coroutine two')
await asyncio.sleep(1)
def test_add_workers(self):
assert len(self.bot.executor.coroutines) == 2
# exit function already added
assert len(self.bot.executor.functions) == 2
# task_queue already added coroutine_thread
assert len(self.bot.executor.coroutine_threads) == 2
# def
+106
View File
@@ -0,0 +1,106 @@
from datetime import datetime
import pytest
import pytz
import pandas as pd
from aiomql.lib.candle import Candle, Candles
from aiomql.core.meta_trader import MetaTrader
from aiomql.core.constants import TimeFrame
class TestCandle:
@classmethod
def setup_class(cls):
cls.bullish_candle = Candle(open=1.3421, high=1.3462, low=1.3405, close=1.3452, time=0, Index=0)
cls.bearish_candle = Candle(open=1.3452, high=1.3405, low=1.3462, close=1.3421, time=1, Index=1)
def test_repr(self):
repr_str = repr(self.bearish_candle)
assert repr_str.startswith("Candle(")
assert "open=" in repr_str
assert "high=" in repr_str
assert "low=" in repr_str
assert "close=" in repr_str
def test_set_attributes(self):
self.bearish_candle.set_attributes(ema=10)
assert self.bearish_candle.ema == 10
def test_compare(self):
assert self.bearish_candle > self.bullish_candle
assert self.bullish_candle != self.bearish_candle
assert self.bullish_candle < self.bearish_candle
def test_dict(self):
self.bearish_candle.set_attributes(ema=10)
result = self.bearish_candle.dict(exclude={'time'})
result2 = self.bearish_candle.dict(include={'close', 'high'})
assert result['open'] == 1.3452
assert result['ema'] == 10
assert 'time' not in result
assert set(result2.keys()) == {'close', 'high'}
def test_dictionary_properties(self):
self.bearish_candle['ema'] = 4
assert self.bearish_candle['ema'] == 4
def test_candle_type(self):
assert self.bearish_candle.is_bearish()
assert self.bullish_candle.is_bullish()
class TestCandles:
@pytest.fixture(scope='class')
async def candles(self):
mt = MetaTrader()
start = datetime(day=5, month=10, year=2023)
rates = await mt.copy_rates_from("BTCUSD", mt.TIMEFRAME_H1, start, 200)
return Candles(data=rates)
def test_get_series(self, candles):
series = candles['open']
assert isinstance(series, pd.Series)
assert len(series) == 200
def test_get_candle(self, candles):
candle = candles[10]
assert isinstance(candle, Candle)
assert candle in candles
assert candle.Index == 10
def test_slice(self, candles):
sliced = candles[10:15]
assert len(sliced) == 5
assert isinstance(sliced, Candles)
def test_setitem(self, candles):
new_series = candles.open
new_series = new_series * 2
candles['double_open'] = new_series
assert 'double_open' in candles.data.columns
def test_getattr(self, candles):
open_series = candles.open
assert isinstance(open_series, pd.Series)
assert open_series.equals(candles.data['open'])
def test_iter(self, candles):
l_5 = candles[-5:]
assert all(isinstance(candle, Candle) for candle in l_5)
def test_timeframe(self, candles):
tf = candles.timeframe
assert tf == TimeFrame.H1
def test_ta_and_rename(self, candles):
ema = candles.ta.ema(close='open', length=10, append=True)
assert 'EMA_10' in candles.data.columns
candles.rename(inplace=True, EMA_10='ema')
assert 'ema' in candles.data.columns
def test_ta_lib(self, candles):
fas = candles.ta_lib.above(candles.open, candles.close)
assert isinstance(fas, pd.Series)
candles['fas'] = fas
assert 'fas' in candles.data.columns
+29
View File
@@ -0,0 +1,29 @@
from aiomql.core.config import Config
from aiomql.contrib.backtesting import BackTestEngine
class TestConfig:
def test_singleton(self, config):
config2 = Config(filename='test.json')
assert config is config2
def test_set_attributes(self, config):
config.set_attributes(timeout=5000, record_trades=False)
assert config.timeout == 5000
assert config.record_trades is False
def test_backtest_engine(self, config):
engine = BackTestEngine()
config.backtest_engine = engine
assert config.backtest_engine is engine
def test_account_info(self, config):
account_info = config.account_info()
assert isinstance(account_info, dict)
assert 'login' in account_info
assert 'password' in account_info
assert 'server' in account_info
def test_load_config(self, config):
config.load_config(file='tests/live/configs/test2.json')
assert config.filename == 'test2.json'
+48
View File
@@ -0,0 +1,48 @@
from pathlib import Path
from datetime import datetime, UTC
import pytest
from aiomql.contrib.backtesting.get_data import GetData
from aiomql.core.constants import TimeFrame
class TestGetData:
@classmethod
def setup_class(cls):
cls.start = datetime(2024, 2, 1, tzinfo=UTC)
cls.end = datetime(2024, 2, 2, tzinfo=UTC)
cls.symbols = ['BTCUSD', "ETHUSD"]
cls.timeframes = [TimeFrame.H1, TimeFrame.H2]
cls.g_data = GetData(start=cls.start, end=cls.end, symbols=cls.symbols, timeframes=cls.timeframes,
name='test_data')
@pytest.fixture(scope='class', autouse=True)
async def get_data(self):
await self.g_data.get_data()
self.g_data.save_data()
def test_init(self):
assert self.g_data.start == self.start
assert self.g_data.end == self.end
assert self.g_data.symbols == set(self.symbols)
assert self.g_data.timeframes == set(self.timeframes)
assert self.g_data.name == 'test_data'
assert self.g_data.range == range(int((self.end - self.start).total_seconds()))
assert self.g_data.span == range(int(self.start.timestamp()), int(self.end.timestamp()))
async def test_get_data(self):
assert self.g_data.data.fully_loaded is True
assert len(self.g_data.data.ticks.keys()) == 2
assert len(self.g_data.data.symbols.keys()) == 2
async def test_save_data(self):
file = Path(self.g_data.config.backtest_dir / 'test_data.pkl')
assert file.exists()
async def test_load_data(self):
data = GetData.load_data(name='tests/live/backtesting/test_data.pkl')
assert data.name == 'test_data'
assert data.fully_loaded is True
assert len(data.ticks.keys()) == 2
assert len(data.symbols.keys()) == 2
+49
View File
@@ -0,0 +1,49 @@
from datetime import datetime
import pytest
from aiomql.lib.history import History
class TestHistory:
@pytest.fixture(scope='class', autouse=True)
async def init(self, make_buy_sell_orders):
await self.history.initialize()
@classmethod
def setup_class(cls):
now = datetime.now()
cls.start = now.replace(hour=0)
cls.end = now.replace(hour=23)
history = History(date_from=cls.start, date_to=cls.end)
cls.history = history
async def test_init(self):
assert self.history.total_deals > 0
assert self.history.total_orders > 0
async def test_get_deals(self):
deals = await self.history.get_deals()
assert len(deals) > 0
async def test_get_deals_by_ticket(self):
ticket = self.history.deals[0].order
deals = self.history.get_deals_by_ticket(ticket=ticket)
assert len(deals) > 0
async def test_get_deals_by_position(self):
position = self.history.deals[0].position_id
deals = self.history.get_deals_by_position(position=position)
assert len(deals) > 0
async def test_get_orders(self):
orders = await self.history.get_orders()
assert len(orders) > 0
async def test_get_orders_by_ticket(self):
ticket = self.history.orders[0].ticket
orders = self.history.get_orders_by_ticket(ticket=ticket)
assert len(orders) > 0
async def test_get_orders_by_position(self):
position = self.history.orders[0].position_id
orders = self.history.get_orders_by_position(position=position)
assert len(orders) > 0
+207
View File
@@ -0,0 +1,207 @@
from datetime import datetime, timedelta
import pytz
import MetaTrader5
from aiomql import MetaTrader
class TestMetaTrader:
@classmethod
def setup_class(cls):
cls.mt = MetaTrader()
cls.mt5 = MetaTrader5
cls.symbol = "BTCUSD"
now = datetime.now(tz=pytz.UTC)
cls.start = now - timedelta(hours=10)
cls.end = now + timedelta(hours=1)
cls.tf = cls.mt.TIMEFRAME_H1
@classmethod
def teardown_class(cls):
cls.mt._shutdown()
async def test_initialize(self):
res = await self.mt.initialize()
assert res == True
async def test_login(self):
res = await self.mt.login()
assert res == True
async def test_last_error(self):
res = await self.mt.last_error()
assert isinstance(res, tuple)
assert res[0] == 1
assert res[1] == 'Success'
async def test_version(self):
res = await self.mt.version()
res2 = self.mt5.version()
assert res is not None
assert res == res2
async def test_account_info(self):
res = await self.mt.account_info()
res2 = self.mt5.account_info()
assert res is not None
assert res == res2
async def test_terminal_info(self):
res = await self.mt.terminal_info()
res2 = self.mt5.terminal_info()
assert res is not None
assert res == res2
async def test_symbols_total(self):
res = await self.mt.symbols_total()
res2 = self.mt5.symbols_total()
assert isinstance(res, int)
assert res == res2
async def test_symbols_get(self):
res = await self.mt.symbols_get()
res2 = self.mt5.symbols_get()
assert res is not None
assert len(res) == len(res2)
async def test_symbol_info(self):
res = await self.mt.symbol_info(self.symbol)
res2 = self.mt5.symbol_info(self.symbol)
assert res is not None
assert res == res2
async def test_symbol_info_tick(self):
res = await self.mt.symbol_info_tick(self.symbol)
res2 = self.mt5.symbol_info_tick(self.symbol)
assert res is not None
assert res == res2
async def test_symbol_select(self):
res = await self.mt.symbol_select(self.symbol, True)
assert res == True
async def test_market_book_add(self):
res = await self.mt.market_book_add(self.symbol)
assert res == True
async def test_market_book_get(self):
res = await self.mt.market_book_get(self.symbol)
res2 = self.mt5.market_book_get(self.symbol)
assert res is not None
assert res == res2
async def test_market_book_release(self):
res = await self.mt.market_book_release(self.symbol)
assert res == True
async def test_copy_rates_from(self):
res = await self.mt.copy_rates_from(self.symbol, self.tf, self.start, 10)
assert res is not None
assert res.shape[0] == 10
async def test_copy_rates_from_pos(self):
res = await self.mt.copy_rates_from_pos(self.symbol, self.tf, 0, 10)
assert res is not None
assert res.shape[0] == 10
async def test_copy_rates_range(self):
res = await self.mt.copy_rates_range(self.symbol, self.tf, self.start, self.end)
assert res is not None
assert res.shape[0] == 10
async def test_copy_ticks_from(self):
res = await self.mt.copy_ticks_from(self.symbol, self.start, 10, self.mt.COPY_TICKS_ALL)
assert res is not None
assert res.shape[0] == 10
async def test_copy_ticks_range(self):
res = await self.mt.copy_ticks_range(self.symbol, self.start, self.end, self.mt.COPY_TICKS_ALL)
res2 = self.mt5.copy_ticks_range(self.symbol, self.start, self.end, self.mt5.COPY_TICKS_ALL)
assert res is not None
assert res.shape[0] == res2.shape[0]
async def test_orders_total(self):
res = await self.mt.orders_total()
assert isinstance(res, int)
async def test_orders_get(self):
res = await self.mt.orders_get()
assert res is not None
assert isinstance(res, tuple)
assert len(res) == 0
async def test_order_calc_margin(self, sell_order):
price = sell_order['price']
volume = sell_order['volume']
type_ = sell_order['type']
res = await self.mt.order_calc_margin(type_, self.symbol, volume, price)
assert isinstance(res, float)
async def test_order_calc_profit(self, buy_order):
volume = buy_order['volume']
price_open = buy_order['price']
price_close = buy_order['tp']
type_ = buy_order['type']
res = await self.mt.order_calc_profit(type_, self.symbol, volume, price_open, price_close)
assert isinstance(res, float)
async def test_order_check(self, buy_order):
res = await self.mt.order_check(buy_order)
assert res is not None
assert res.retcode == 0
async def test_order_send(self, sell_order):
res = await self.mt.order_send(sell_order)
assert res is not None
assert res.retcode == 10009
async def test_positions_total(self):
res = await self.mt.positions_total()
assert isinstance(res, int)
assert res >= 0
async def test_positions_get(self):
res = await self.mt.positions_get()
assert res is not None
assert isinstance(res, tuple)
assert len(res) >= 0
async def test_history_orders_total(self):
res = await self.mt.history_orders_total(self.start, self.end)
assert isinstance(res, int)
assert res >= 0
async def test_history_orders_get(self):
res = await self.mt.history_orders_get(self.start, self.end)
assert res is not None
assert isinstance(res, tuple)
assert len(res) >= 0
async def test_history_deals_total(self):
res = await self.mt.history_deals_total(self.start, self.end)
assert isinstance(res, int)
assert res >= 0
async def test_history_deals_get(self):
res = await self.mt.history_deals_get(self.start, self.end)
assert res is not None
assert isinstance(res, tuple)
assert len(res) >= 0
# sym = order_request['symbol']
# sym_info = await self.mt.symbol_info(sym)
# dsl = (sym_info.trade_stops_level + sym_info.spread) * 2 * sym_info.point
# order_request['volume'] = sym_info.volume_min
# order_request['price'] = sym_info.ask
# order_request['tp'] = round(sym_info.ask + dsl, sym_info.digits)
# order_request['sl'] = round(sym_info.ask - dsl, sym_info.digits)
# sym = order_request['symbol']
# sym_info = await self.mt.symbol_info(sym)
# dsl = (sym_info.trade_stops_level + sym_info.spread) * 2 * sym_info.point
# order_request['volume'] = sym_info.volume_min
# order_request['price'] = sym_info.ask
# order_request['tp'] = round(sym_info.ask + dsl, sym_info.digits)
# order_request['sl'] = round(sym_info.ask - dsl, sym_info.digits)
+27
View File
@@ -0,0 +1,27 @@
from aiomql.lib.order import Order
class TestOrder:
async def test_check(self, sell_order):
order = Order(**sell_order)
check = await order.check()
assert check.retcode == 0
async def test_send(self, buy_order):
order = Order(**buy_order)
send = await order.send()
assert send.retcode == 10009
async def test_margin(self, buy_order):
order = Order(**buy_order)
margin = await order.calc_margin()
assert margin is not None
assert margin > 0
assert isinstance(margin, float)
async def test_profit(self, buy_order):
order = Order(**buy_order)
profit = await order.calc_profit()
assert profit is not None
assert profit > 0
assert isinstance(profit, float)
+30
View File
@@ -0,0 +1,30 @@
import pytest
from aiomql.lib.positions import Positions
class TestPositions:
@pytest.fixture(scope='class', autouse=True)
async def init(self, make_buy_sell_orders):
await self.positions.get_positions()
@classmethod
def setup_class(cls):
cls.positions = Positions()
@pytest.mark.order(1)
async def test_get_positions(self):
await self.positions.get_positions()
assert len(self.positions.positions) >= 0
async def test_get_position_by_ticket(self):
ticket = self.positions.positions[0].ticket
position = await self.positions.get_position_by_ticket(ticket=ticket)
assert position is not None
assert position.ticket == ticket
async def test_get_position_by_symbol(self):
symbol = self.positions.positions[0].symbol
positions = await self.positions.get_position_by_symbol(symbol=symbol)
assert len(positions) >= 0
assert positions[0].symbol == symbol
+22
View File
@@ -0,0 +1,22 @@
from aiomql.lib.ram import RAM
class TestRAM:
@classmethod
def setup_class(cls):
cls.ram = RAM(min_amount=5, max_amount=10, loss_limit=3, open_limit=5)
async def test_get_amount(self):
res = await self.ram.get_amount()
assert self.ram.min_amount <= res <= self.ram.max_amount
async def test_checks(self, buy_order, sell_order, mt):
for i in range(self.ram.open_limit+1):
if i % 2 == 0:
await mt.order_send(buy_order)
else:
await mt.order_send(sell_order)
res1 = await self.ram.check_losing_positions()
res2 = await self.ram.check_open_positions()
assert res2 is False
assert isinstance(res1, bool)
+44
View File
@@ -0,0 +1,44 @@
import asyncio
import pytest
from aiomql.lib.result import Result
from aiomql.core.models import OrderSendResult
class TestResult:
@pytest.fixture(scope='class')
def parameters(self):
return {'name': 'test_trades', 'ema': 20, 'rsi': 14}
@pytest.fixture(scope='function')
async def order_results(self, mt, sell_order, buy_order, parameters):
res1 = await mt.order_send(sell_order)
res2 = await mt.order_send(buy_order)
res1 = Result(result=OrderSendResult(**res1._asdict()), parameters=parameters)
res2 = Result(result=OrderSendResult(**res2._asdict()), parameters=parameters)
return res1, res2
async def test_get_data(self, order_results):
res1, res2 = order_results
data1 = res1.get_data()
data2 = res2.get_data()
assert data1['actual_profit'] == data2['actual_profit'] == 0
assert data1['closed'] == data2['closed'] == False
assert data1['win'] == data2['win'] == False
async def test_csv(self, order_results):
res1, res2 = order_results
await asyncio.gather(res1.save(), res2.save())
assert res1.config.records_dir.exists()
record = res1.config.records_dir / f"{res1.name}.csv"
assert record.exists()
async def test_json(self, order_results):
res1, res2 = order_results
await asyncio.gather(res1.save(trade_record_mode='json'), res2.save(trade_record_mode='json'))
assert res1.config.records_dir.exists()
record = res1.config.records_dir / f"{res1.name}.json"
assert record.exists()
+67
View File
@@ -0,0 +1,67 @@
from datetime import datetime, time, UTC
import pytest
from aiomql.lib.sessions import Session, Sessions, delta
class TestSessions:
@pytest.fixture(scope='class')
def make_sessions(self, make_session):
london, all_day, over_night = make_session
return Sessions(sessions=[london, all_day, over_night])
@pytest.fixture(scope='class')
def make_session(self):
end = time(hour=16, minute=59, second=59, microsecond=999_999, tzinfo=UTC)
london = Session(start=8, end=end, name='London', on_end='close_all')
start, end = time(hour=0, tzinfo=UTC), time(hour=23, minute=59, second=59, tzinfo=UTC)
all_day = Session(start=start, end=end, name='AllDay', on_end='close_all')
end = time(hour=6, minute=59, second=59, microsecond=999_999, tzinfo=UTC)
over_night = Session(start=18, end=end, name='OverNight', on_end='close_all')
return london, all_day, over_night
def test_session_attributes(self, make_session):
london, all_day, over_night = make_session
period = over_night.duration()
assert london.name == 'London'
assert london.start == time(hour=8, tzinfo=UTC)
assert london.end.hour == 16
assert period.hours == 12
assert period.minutes == period.seconds == 59
def test_session_intervals(self, make_session):
london, all_day, over_night = make_session
two_am = time(hour=2, tzinfo=UTC)
noon = time(hour=12, tzinfo=UTC)
now = datetime.now(UTC).time()
hours_till_london_starts = (delta(london.start) - delta(now)).seconds // 3600
assert hours_till_london_starts == london.until() // 3600
assert two_am in over_night
assert noon in london
assert two_am not in london
assert noon not in over_night
# all_day session is always open
assert all_day.in_session()
async def test_sessions(self, make_session):
london, all_day, over_night = make_session
sessions = Sessions(sessions=[london, over_night])
now = time(hour=21, tzinfo=UTC)
noon = time(hour=12, tzinfo=UTC)
mid_nite = time(hour=0, tzinfo=UTC)
next_sess = sessions.find_next(moment=now)
noon_sess = sessions.find(moment=noon)
no_sess = sessions.find(moment=time(hour=17, tzinfo=UTC))
mid_nite_sess = sessions.find(moment=mid_nite)
current_sess = sessions.find(moment=now)
assert current_sess.name == 'OverNight'
assert noon_sess.name == 'London'
assert no_sess is None
assert next_sess.name == 'London'
assert mid_nite_sess.name == 'OverNight'
current = datetime.now(UTC).time()
if current.hour not in (7, 17):
await sessions.check()
assert sessions.current_session is not None
assert sessions.current_session.name in ('London', 'OverNight')
+53
View File
@@ -0,0 +1,53 @@
from datetime import datetime, timedelta
import pytest
from aiomql.lib.symbol import Symbol
from aiomql.lib.candle import Candles
from aiomql.lib.ticks import Ticks
class TestSymbol:
@pytest.fixture(scope='class', autouse=True)
async def btc(self):
symbol = Symbol(name='BTCUSD')
select = getattr(symbol, 'select', False)
if select is False:
await symbol.initialize()
return symbol
async def test_symbol_attributes(self, btc):
assert btc.name == 'BTCUSD'
assert btc.select is True
assert btc.tick is not None
async def test_volume(self, btc):
volume = btc.volume_min - btc.volume_step
success, volume = btc.check_volume(volume=volume)
assert success is False
volume = btc.volume_min + btc.volume_step * 2
success, volume = btc.check_volume(volume=volume)
assert success is True
volume = btc.volume_min + btc.volume_step * 2.5
volume = btc.round_off_volume(volume=volume, round_down=True)
assert volume == btc.volume_min + btc.volume_step * 2
async def test_rates(self, btc):
start = datetime(year=2023, month=10, day=5)
end = start + timedelta(hours=9)
rates_from = await btc.copy_rates_from(timeframe=btc.mt5.TIMEFRAME_H1, date_from=start, count=10)
assert isinstance(rates_from, Candles)
assert len(rates_from) == 10
rates_from_pos = await btc.copy_rates_from_pos(timeframe=btc.mt5.TIMEFRAME_H1, count=10, start_position=0)
assert isinstance(rates_from_pos, Candles)
assert len(rates_from_pos) == 10
rates_range = await btc.copy_rates_range(timeframe=btc.mt5.TIMEFRAME_H1, date_from=start, date_to=end)
assert isinstance(rates_range, Candles)
assert len(rates_range) == 10
ticks_from = await btc.copy_ticks_from(date_from=start, count=10)
assert isinstance(ticks_from, Ticks)
assert len(ticks_from) == 10
end = start + timedelta(seconds=20)
ticks_from_pos = await btc.copy_ticks_range(date_from=start, date_to=end)
assert isinstance(ticks_from_pos, Ticks)
assert len(ticks_from_pos) >= 10
+40
View File
@@ -0,0 +1,40 @@
import asyncio
from aiomql.core.task_queue import TaskQueue, QueueItem
class TestTaskQueue:
@classmethod
def setup_class(cls):
cls.task_queue = TaskQueue(timeout=5, worker_timeout=1)
cls.data = {}
async def task_one(self):
for i in range(10):
await asyncio.sleep(0.5)
self.data.setdefault('task_one', {})[i] = f"task_one_{i}"
async def task_two(self):
for i in range(10):
await asyncio.sleep(0.5)
self.data.setdefault('task_two', {})[i] = f"task_two_{i}"
async def task_three(self):
for i in range(10):
self.data.setdefault('task_three', {})[i] = f"task_three_{i}"
await asyncio.sleep(10)
async def test_queue(self):
item_one = QueueItem(self.task_one)
self.task_queue.add(item=item_one, must_complete=False)
assert len(self.task_queue.priority_tasks) == 0
assert self.task_queue.queue.qsize() == 1
self.task_queue.add(item=QueueItem(self.task_two), must_complete=True)
assert len(self.task_queue.priority_tasks) == 1
assert self.task_queue.queue.qsize() == 2
self.task_queue.add(item=QueueItem(self.task_three), must_complete=False)
await self.task_queue.run()
assert len(self.data['task_one']) >= 2
assert len(self.data['task_two']) == 10
assert len(self.data['task_three']) == 1
assert len(self.task_queue.priority_tasks) == 0
+17
View File
@@ -0,0 +1,17 @@
import pytest
from aiomql.lib.terminal import Terminal
class TestTerminal:
@pytest.fixture(scope='class', autouse=True)
async def init_terminal(self):
terminal = Terminal()
init = await terminal.initialize()
return init, terminal
async def test_terminal(self, init_terminal):
init, terminal = init_terminal
assert init is True
assert terminal.connected is True
assert terminal.version is not None
+26
View File
@@ -0,0 +1,26 @@
from datetime import datetime
from aiomql.lib.ticks import Ticks, Tick
from pandas import Series
class TestTicks:
async def test_tick(self, mt):
btc_tick = await mt.symbol_info_tick("BTCUSD")
btc_tick = Tick(**btc_tick._asdict())
tick_dict = btc_tick.dict(include={'ask', 'bid', 'time', 'volume'})
assert isinstance(btc_tick, Tick)
assert isinstance(tick_dict, dict)
assert 'ask' in tick_dict
assert 'bid' in tick_dict
assert 'volume_real' not in tick_dict
async def test_ticks(self, mt):
start = datetime(year=2023, month=10, day=5)
ticks = await mt.copy_ticks_from("BTCUSD", start, 10, mt.COPY_TICKS_ALL)
ticks = Ticks(data=ticks)
assert isinstance(ticks, Ticks)
assert len(ticks) == 10
assert isinstance(ticks[0], Tick)
bids = ticks['bid']
assert len(bids) == 10
assert isinstance(bids, Series)
+69
View File
@@ -0,0 +1,69 @@
from math import floor
import pytest
from aiomql.lib.ram import RAM
from aiomql.contrib.traders import SimpleTrader
from aiomql.contrib.symbols import ForexSymbol
from aiomql.core.constants import OrderType
class TestTrader:
@classmethod
def setup_class(cls):
ram = RAM(fixed_amount=10)
cls.trader = SimpleTrader(symbol=ForexSymbol(name='BTCUSD'), ram=ram)
cls.simple_trader2 = SimpleTrader(symbol=ForexSymbol(name='EURJPY'), ram=ram)
@pytest.fixture(scope='class', autouse=True)
async def initialize(self):
await self.trader.symbol.initialize()
await self.simple_trader2.symbol.initialize()
async def test_create_order_no_stops(self):
await self.trader.create_order_no_stops(order_type=OrderType.BUY)
assert self.trader.order.volume == self.trader.symbol.volume_min
res = await self.trader.order.send()
assert res is not None
assert res.retcode == 10009
async def test_create_order_with_sl(self):
sl = (self.trader.symbol.trade_stops_level * 2 + self.trader.symbol.spread) * self.trader.symbol.point
tick = await self.trader.symbol.info_tick()
sl = tick.bid + sl
await self.trader.create_order_with_sl(order_type=OrderType.SELL, sl=sl)
res = await self.trader.order.send()
profit = floor(await self.trader.order.calc_profit())
loss = -floor(abs(await self.trader.order.calc_loss()))
assert profit == -loss*self.trader.ram.risk_to_reward
assert profit == self.trader.ram.fixed_amount * self.trader.ram.risk_to_reward
assert loss == -self.trader.ram.fixed_amount
assert res is not None
assert res.retcode == 10009
async def test_create_order_with_points(self):
points = (self.trader.symbol.trade_stops_level * 2 + self.trader.symbol.spread)
await self.trader.create_order_with_points(order_type=OrderType.BUY, points=points)
res = await self.trader.order.send()
profit = floor(await self.trader.order.calc_profit())
loss = -floor(abs(await self.trader.order.calc_loss()))
assert profit == -loss * self.trader.ram.risk_to_reward
assert profit == self.trader.ram.fixed_amount * self.trader.ram.risk_to_reward
assert loss == -self.trader.ram.fixed_amount
assert res is not None
assert res.retcode == 10009
async def test_create_order_with_stops(self):
sl = (self.trader.symbol.trade_stops_level * 2 + self.trader.symbol.spread) * self.trader.symbol.point
tp = sl * self.trader.ram.risk_to_reward
tick = await self.trader.symbol.info_tick()
sl = tick.ask - sl
tp = tick.ask + tp
await self.trader.create_order_with_stops(order_type=OrderType.BUY, sl=sl, tp=tp)
res = await self.trader.order.send()
profit = floor(await self.trader.order.calc_profit())
loss = -floor(abs(await self.trader.order.calc_loss()))
assert profit == -loss * self.trader.ram.risk_to_reward
assert profit == self.trader.ram.fixed_amount * self.trader.ram.risk_to_reward
assert loss == -self.trader.ram.fixed_amount
assert res is not None
assert res.retcode == 10009