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
View File
+96
View File
@@ -0,0 +1,96 @@
from datetime import datetime, UTC
import asyncio
import json
import shutil
from logging import getLogger
from pathlib import Path
import pytest
from aiomql.core import Config
from aiomql.core.meta_backtester import MetaBackTester
from aiomql.contrib import BackTestEngine
from aiomql.lib import Positions, History, Order
logger = getLogger(__name__)
async def cleanup():
try:
shutil.rmtree(Path('tests/backtest/configs'), ignore_errors=True)
Path.unlink(Path('tests/backtest/test.json'), missing_ok=True)
shutil.rmtree(Path('tests/backtest/trade_records'), ignore_errors=True)
shutil.rmtree(Path('tests/backtest/backtesting'), ignore_errors=True)
await close_all_positions()
await MetaBackTester().shutdown()
except Exception as err:
logger.error(f"Failed to complete cleanup: {err}")
async def close_all_positions():
try:
mt = MetaBackTester()
positions = await mt.positions_get()
tasks = []
for position in positions:
order_type = mt.ORDER_TYPE_BUY if position.type == mt.ORDER_TYPE_SELL else mt.ORDER_TYPE_SELL
req = {'action': mt.TRADE_ACTION_DEAL, 'symbol': position.symbol, 'volume': position.volume,
'type': order_type, 'position': position.ticket, 'price': position.price_current}
tasks.append(mt.order_send(req))
await asyncio.gather(*tasks)
except Exception as err:
logger.error(f"Failed to close all positions: {err}")
@pytest.fixture(scope='package', autouse=True)
async def config(request):
Path('tests/backtest/configs').mkdir(exist_ok=True)
with open('aiomql.json', 'r') as fh, open('tests/backtest/configs/test2.json', 'w') as fh1, open('tests/backtest/test.json', 'w') as fh2:
data = json.load(fh)
data['mode'] = 'backtest'
json.dump(data, fh1, indent=2)
json.dump(data, fh2, indent=2)
config = Config(filename='test.json', root='tests/backtest')
yield config
await cleanup()
@pytest.fixture(scope='package', autouse=True)
async def mt():
mt = MetaBackTester()
await mt.initialize()
await mt.login()
yield mt
await mt.shutdown()
@pytest.fixture(scope='package')
async def period():
return {'start': datetime(2024, 2, 1, hour=8, tzinfo=UTC),
'end': datetime(2024, 2, 7, hour=16, tzinfo=UTC)}
@pytest.fixture(scope='package')
async def backtest_engine(period):
start = period['start']
end = period['end']
return BackTestEngine(start=start, end=end, name='backtest_data')
@pytest.fixture(scope='function')
def order_sell(sell_order):
return Order(**sell_order)
@pytest.fixture(scope='function')
def order_buy(buy_order):
return Order(**buy_order)
@pytest.fixture(scope='package')
def positions():
return Positions()
@pytest.fixture(scope='package')
def history(period):
start = period['start']
end = period['end']
return History(date_from=start, date_to=end)
@@ -0,0 +1,140 @@
from aiomql.contrib import BackTestEngine, ForexSymbol, GetData
from aiomql.core import MetaBackTester
from aiomql.lib import Order
async def make_buy_sell_orders():
sym = ForexSymbol(name='BTCUSD')
sym_info = await sym.mt5.symbol_info(sym.name)
dsl = (sym_info.trade_stops_level + sym_info.spread) * 2 * sym_info.point
sl = sym_info.ask - dsl
tp = sym_info.ask + dsl
buy_req = {'action': sym.mt5.TRADE_ACTION_DEAL, 'symbol': sym.name, 'volume': sym_info.volume_min,
'type': sym.mt5.ORDER_TYPE_BUY, 'price': sym_info.ask, 'sl': sl, 'tp': tp}
sell_req = buy_req.copy()
sell_req['type'] = sym.mt5.ORDER_TYPE_SELL
sell_req['price'] = sym_info.bid
del sell_req['tp']
del sell_req['sl']
return {'buy': Order(**buy_req), 'sell': Order(**sell_req)}
def test_trade_mode(config, backtest_engine, history, positions, order_sell, order_buy, btc_usd):
assert config.mode == 'backtest'
assert isinstance(backtest_engine, BackTestEngine)
assert isinstance(history.mt5, MetaBackTester)
assert isinstance(positions.mt5, MetaBackTester)
assert isinstance(order_sell.mt5, MetaBackTester)
assert isinstance(order_buy.mt5, MetaBackTester)
assert isinstance(btc_usd.mt5, MetaBackTester)
async def test_order_send(backtest_engine, order_sell, order_buy):
await backtest_engine.setup_account(balance=100)
so = await backtest_engine.order_send(request=order_sell.request)
bo = await backtest_engine.order_send(request=order_buy.request)
assert so.retcode == 10009
assert bo.retcode == 10009
backtest_engine.reset(clear_data=True)
async def test_positions(backtest_engine, positions, order_sell, order_buy):
await backtest_engine.setup_account(balance=100)
so = await backtest_engine.order_send(request=order_sell.request)
bo = await backtest_engine.order_send(request=order_buy.request)
all_positions = await positions.get_positions()
assert len(all_positions) == 2
await positions.close_position_by_ticket(ticket=so.order)
all_positions = await positions.get_positions()
assert len(all_positions) == 1
await positions.close_position_by_ticket(ticket=bo.order)
all_positions = await positions.get_positions()
assert len(all_positions) == 0
backtest_engine.reset(clear_data=True)
async def test_history(backtest_engine, history, order_sell, order_buy, positions):
await backtest_engine.setup_account(balance=100)
so = await backtest_engine.order_send(request=order_sell.request)
await backtest_engine.order_send(request=order_buy.request)
await history.initialize()
assert len(history.orders) == 2
assert len(history.deals) == 2
await positions.close_position_by_ticket(ticket=so.order)
deals = await history.get_deals()
assert len(deals) == 3
backtest_engine.reset(clear_data=True)
async def test_margin(backtest_engine, order_sell, order_buy):
await backtest_engine.setup_account(balance=100)
so_margin = await backtest_engine.order_calc_margin(action=order_sell.action, volume=order_sell.volume,
symbol=order_sell.symbol, price=order_sell.price)
bo_margin = await backtest_engine.order_calc_margin(action=order_buy.action, volume=order_buy.volume,
symbol=order_buy.symbol, price=order_buy.price)
total_margin = so_margin + bo_margin
await backtest_engine.order_send(request=order_sell.request)
await backtest_engine.order_send(request=order_buy.request)
# noinspection PyTestUnpassedFixture
assert backtest_engine.positions.margin == total_margin == backtest_engine._account.margin
backtest_engine.reset(clear_data=True)
async def test_account(backtest_engine, positions):
await backtest_engine.setup_account(balance=100)
backtest_engine.fast_forward(steps=100)
balance = backtest_engine._account.balance
equity = backtest_engine._account.equity
orders = await make_buy_sell_orders()
buy_order = orders['buy']
sell_order = orders['sell']
so = await backtest_engine.order_send(request=sell_order.request)
bo = await backtest_engine.order_send(request=buy_order.request)
backtest_engine.fast_forward(steps=22000)
all_pos = await positions.get_positions()
for _ in range(1000):
backtest_engine.fast_forward(steps=1)
await backtest_engine.tracker()
all_pos = await positions.get_positions()
if len(all_pos) == 1:
break
deal = backtest_engine.deals.history_deals_get(position=bo.order)
bo_profit = deal[-1].profit
assert len(all_pos) == 1
assert backtest_engine.positions.margin == backtest_engine._account.margin == backtest_engine.positions.margins[so.order]
profit = sum([pos.profit for pos in all_pos])
n_balance = backtest_engine._account.balance
n_equity = backtest_engine._account.equity
assert backtest_engine._account.profit == profit
assert n_balance == balance + bo_profit
assert n_equity == equity + bo_profit + profit
so_pos = await positions.get_position_by_ticket(ticket=so.order)
gain = so_pos.profit
await positions.close_position(position=so_pos)
assert backtest_engine._account.balance == n_balance + gain
backtest_engine.reset(clear_data=True)
async def test_wrapup(positions, buy_order, sell_order, backtest_engine, config):
await backtest_engine.setup_account(balance=100)
backtest_engine.fast_forward(steps=500)
bo = await backtest_engine.order_send(request=buy_order)
await backtest_engine.order_send(request=sell_order)
backtest_engine.fast_forward(steps=5000)
await backtest_engine.tracker()
await positions.close_position_by_ticket(ticket=bo.order)
last_balance = backtest_engine._account.balance
last_equity = backtest_engine._account.equity
last_profit = backtest_engine._account.profit
backtest_engine.wrap_up()
tdata = GetData.load_data(name=config.backtest_dir / f'{backtest_engine.name}.pkl')
new_bte = BackTestEngine(data=tdata, restart=False)
assert new_bte._account.balance == last_balance
assert new_bte._account.equity == last_equity
assert new_bte._account.profit == last_profit
assert new_bte.span == backtest_engine.span
assert new_bte.range == backtest_engine.range
assert new_bte.name == backtest_engine.name
assert new_bte.cursor.time == backtest_engine.cursor.time
+24
View File
@@ -0,0 +1,24 @@
# noinspection PyTestUnpassedFixture
async def test_deals_manager(backtest_engine, sell_order, buy_order, period, positions):
backtest_engine.reset(clear_data=True)
await backtest_engine.setup_account(balance=100)
backtest_engine.fast_forward(steps=100)
await backtest_engine.order_send(request=sell_order)
bo = await backtest_engine.order_send(request=buy_order)
start = period['start']
end = period['end']
all_deals = backtest_engine.deals.get_deals_range(date_from=start, date_to=end)
assert len(all_deals) == 2
backtest_engine.fast_forward(steps=10_000)
start2 = backtest_engine.cursor.time
bo2 = await backtest_engine.order_send(request=buy_order)
backtest_engine.fast_forward(steps=50)
end2 = backtest_engine.cursor.time
deals = backtest_engine.deals.history_deals_get(date_from=start2, date_to=end2)
assert len(deals) == 1
assert deals[0].order == bo2.order
await positions.close_position_by_ticket(ticket=bo.order)
deals = backtest_engine.deals.history_deals_get(position=bo.order)
assert len(deals) <= 2
orders = backtest_engine.deals.get_deals_range(date_from=start, date_to=end)
assert len(orders) == backtest_engine.deals.history_deals_total(date_from=start, date_to=end) == len(backtest_engine.deals._data.keys())
+24
View File
@@ -0,0 +1,24 @@
# noinspection PyTestUnpassedFixture
async def test_orders_manager(backtest_engine, sell_order, buy_order, period, positions):
backtest_engine.reset(clear_data=True)
await backtest_engine.setup_account(balance=100)
backtest_engine.fast_forward(steps=100)
await backtest_engine.order_send(request=sell_order)
bo = await backtest_engine.order_send(request=buy_order)
start = period['start']
end = period['end']
all_orders = backtest_engine.orders.get_orders_range(date_from=start, date_to=end)
assert len(all_orders) == 2
backtest_engine.fast_forward(steps=10_000)
start2 = backtest_engine.cursor.time
bo2 = await backtest_engine.order_send(request=buy_order)
backtest_engine.fast_forward(steps=50)
end2 = backtest_engine.cursor.time
orders = backtest_engine.orders.history_orders_get(date_from=start2, date_to=end2)
assert len(orders) == 1
assert orders[0].ticket == bo2.order
await positions.close_position_by_ticket(ticket=bo.order)
orders = backtest_engine.orders.history_orders_get(position=bo.order)
assert len(orders) <= 2
orders = backtest_engine.orders.get_orders_range(date_from=start, date_to=end)
assert len(orders) == backtest_engine.orders.history_orders_total(date_from=start, date_to=end) == len(backtest_engine.orders._data.keys())
@@ -0,0 +1,17 @@
# noinspection PyTestUnpassedFixture
async def test_positions_manager(backtest_engine, sell_order, buy_order):
backtest_engine.reset(clear_data=True)
await backtest_engine.setup_account(balance=100)
backtest_engine.fast_forward(steps=100)
so = await backtest_engine.order_send(request=sell_order)
bo = await backtest_engine.order_send(request=buy_order)
all_pos = backtest_engine.positions.positions_get()
assert len(all_pos) == 2
so_positions = backtest_engine.positions.positions_get(ticket=so.order)
so_position = so_positions[0]
assert so_position.ticket == so.order
btc_positions = backtest_engine.positions.positions_get(symbol='BTCUSD')
assert len(btc_positions) == 2
assert backtest_engine.positions.positions_total() == 2
backtest_engine.positions.close(ticket=bo.order)
assert backtest_engine.positions.positions_total() == 1
+25 -75
View File
@@ -1,93 +1,43 @@
import asyncio
import json
import shutil
from logging import getLogger
from pathlib import Path
from aiomql.lib.symbol import Symbol
import pytest
from aiomql.core import Config
from aiomql.core.meta_trader import MetaTrader
logger = getLogger(__name__)
async def cleanup():
try:
shutil.rmtree(Path('tests/configs'), ignore_errors=True)
Path.unlink(Path('tests/test.json'), missing_ok=True)
shutil.rmtree(Path('tests/trade_records'), ignore_errors=True)
await close_all_positions()
await MetaTrader().shutdown()
except Exception as err:
logger.error(f"Failed to complete cleanup: {err}")
async def close_all_positions():
try:
mt = MetaTrader()
positions = await mt.positions_get()
tasks = []
for position in positions:
order_type = mt.ORDER_TYPE_BUY if position.type == mt.ORDER_TYPE_SELL else mt.ORDER_TYPE_SELL
req = {'action': mt.TRADE_ACTION_DEAL, 'symbol': position.symbol, 'volume': position.volume,
'type': order_type, 'position': position.ticket, 'price': position.price_current}
tasks.append(mt.order_send(req))
await asyncio.gather(*tasks)
except Exception as err:
logger.error(f"Failed to close all positions: {err}")
@pytest.fixture(scope='session', autouse=True)
async def config(request):
Path('tests/configs').mkdir(exist_ok=True)
with open('aiomql.json', 'r') as fh, open('tests/configs/test2.json', 'w') as fh1, open('tests/test.json', 'w') as fh2:
data = json.load(fh)
json.dump(data, fh1, indent=2)
json.dump(data, fh2, indent=2)
config = Config(filename='test.json', root='tests')
yield config
await cleanup()
@pytest.fixture(scope='session')
async def mt():
mt = MetaTrader()
await mt.initialize()
await mt.login()
yield mt
await mt.shutdown()
@pytest.fixture(scope='function')
async def sell_order(mt):
sym = 'BTCUSD'
sym_info = await mt.symbol_info(sym)
return {'action': mt.TRADE_ACTION_DEAL, 'symbol': sym, 'volume': sym_info.volume_min,
'type': mt.ORDER_TYPE_SELL, 'price': sym_info.bid}
def btc_usd():
return Symbol(name='BTCUSD')
@pytest.fixture(scope='function')
async def buy_order(mt):
sym = 'BTCUSD'
sym_info = await mt.symbol_info(sym)
async def buy_order(btc_usd):
sym = btc_usd
sym_info = await sym.mt5.symbol_info(sym.name)
dsl = (sym_info.trade_stops_level + sym_info.spread) * 2 * sym_info.point
sl = sym_info.ask - dsl
tp = sym_info.ask + dsl
return {'action': mt.TRADE_ACTION_DEAL, 'symbol': sym, 'volume': sym_info.volume_min,
'type': mt.ORDER_TYPE_BUY, 'price': sym_info.ask, 'sl': sl, 'tp': tp}
return {'action': sym.mt5.TRADE_ACTION_DEAL, 'symbol': sym.name, 'volume': sym_info.volume_min,
'type': sym.mt5.ORDER_TYPE_BUY, 'price': sym_info.ask, 'sl': sl, 'tp': tp}
@pytest.fixture(scope='function')
async def sell_order(btc_usd):
sym = btc_usd
sym_info = await sym.mt5.symbol_info(sym.name)
return {'action': sym.mt5.TRADE_ACTION_DEAL, 'symbol': sym.name, 'volume': sym_info.volume_min,
'type': sym.mt5.ORDER_TYPE_SELL, 'price': sym_info.bid}
@pytest.fixture(scope='class')
async def make_buy_sell_orders(mt):
sym = 'BTCUSD'
sym_info = await mt.symbol_info(sym)
async def make_buy_sell_orders():
sym = Symbol(name='BTCUSD')
sym_info = await sym.mt5.symbol_info(sym.name)
dsl = (sym_info.trade_stops_level + sym_info.spread) * 2 * sym_info.point
sl = sym_info.ask - dsl
tp = sym_info.ask + dsl
req = {'action': mt.TRADE_ACTION_DEAL, 'symbol': sym, 'volume': sym_info.volume_min,
'type': mt.ORDER_TYPE_BUY, 'price': sym_info.ask, 'sl': sl, 'tp': tp}
await mt.order_send(req)
req['type'] = mt.ORDER_TYPE_SELL
req = {'action': sym.mt5.TRADE_ACTION_DEAL, 'symbol': sym.name, 'volume': sym_info.volume_min,
'type': sym.mt5.ORDER_TYPE_BUY, 'price': sym_info.ask, 'sl': sl, 'tp': tp}
await sym.mt5.order_send(req)
req['type'] = sym.mt5.ORDER_TYPE_SELL
req['price'] = sym_info.bid
req['sl'] = sym_info.bid + dsl
req['tp'] = sym_info.bid - dsl
await mt.order_send(req)
await sym.mt5.order_send(req)
View File
+59
View File
@@ -0,0 +1,59 @@
import asyncio
import json
import shutil
from logging import getLogger
from pathlib import Path
import pytest
from aiomql.core import Config
from aiomql.core.meta_trader import MetaTrader
logger = getLogger(__name__)
async def cleanup():
try:
shutil.rmtree(Path('tests/live/configs'), ignore_errors=True)
Path.unlink(Path('tests/live/test.json'), missing_ok=True)
shutil.rmtree(Path('tests/live/trade_records'), ignore_errors=True)
shutil.rmtree(Path('tests/live/backtesting'), ignore_errors=True)
await close_all_positions()
await MetaTrader().shutdown()
except Exception as err:
logger.error(f"Failed to complete cleanup: {err}")
async def close_all_positions():
try:
mt = MetaTrader()
positions = await mt.positions_get()
tasks = []
for position in positions:
order_type = mt.ORDER_TYPE_BUY if position.type == mt.ORDER_TYPE_SELL else mt.ORDER_TYPE_SELL
req = {'action': mt.TRADE_ACTION_DEAL, 'symbol': position.symbol, 'volume': position.volume,
'type': order_type, 'position': position.ticket, 'price': position.price_current}
tasks.append(mt.order_send(req))
await asyncio.gather(*tasks)
except Exception as err:
logger.error(f"Failed to close all positions: {err}")
@pytest.fixture(scope='package', autouse=True)
async def config(request):
Path('tests/live/configs').mkdir(exist_ok=True)
with open('aiomql.json', 'r') as fh, open('tests/live/configs/test2.json', 'w') as fh1, open('tests/live/test.json', 'w') as fh2:
data = json.load(fh)
json.dump(data, fh1, indent=2)
json.dump(data, fh2, indent=2)
config = Config(filename='test.json', root='tests/live')
yield config
await cleanup()
@pytest.fixture(scope='package', autouse=True)
async def mt():
mt = MetaTrader()
await mt.initialize()
await mt.login()
yield mt
await mt.shutdown()
View File
+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
+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
@@ -25,5 +25,5 @@ class TestConfig:
assert 'server' in account_info
def test_load_config(self, config):
config.load_config(file='tests/configs/test2.json')
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
@@ -6,7 +6,7 @@ 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.init()
await self.history.initialize()
@classmethod
def setup_class(cls):
@@ -1,7 +1,6 @@
from datetime import datetime, time
from datetime import datetime, time, UTC
import pytest
import pytz
from aiomql.lib.sessions import Session, Sessions, delta
@@ -14,11 +13,11 @@ class TestSessions:
@pytest.fixture(scope='class')
def make_session(self):
end = time(hour=16, minute=59, second=59, microsecond=999_999, tzinfo=pytz.UTC)
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=pytz.UTC), time(hour=23, minute=59, second=59, tzinfo=pytz.UTC)
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=pytz.UTC)
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
@@ -26,16 +25,16 @@ class TestSessions:
london, all_day, over_night = make_session
period = over_night.duration()
assert london.name == 'London'
assert london.start == time(hour=8, tzinfo=pytz.UTC)
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=pytz.UTC)
noon = time(hour=12, tzinfo=pytz.UTC)
now = datetime.now(pytz.UTC).time()
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
@@ -48,12 +47,12 @@ class TestSessions:
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=pytz.UTC)
noon = time(hour=12, tzinfo=pytz.UTC)
mid_nite = time(hour=0, tzinfo=pytz.UTC)
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=pytz.UTC))
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'
@@ -61,7 +60,7 @@ class TestSessions:
assert no_sess is None
assert next_sess.name == 'London'
assert mid_nite_sess.name == 'OverNight'
current = datetime.now(pytz.UTC).time()
current = datetime.now(UTC).time()
if current.hour not in (7, 17):
await sessions.check()
assert sessions.current_session is not None
@@ -13,7 +13,7 @@ class TestSymbol:
symbol = Symbol(name='BTCUSD')
select = getattr(symbol, 'select', False)
if select is False:
await symbol.init()
await symbol.initialize()
return symbol
async def test_symbol_attributes(self, btc):
+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
+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