mirror of
https://github.com/Ichinga-Samuel/aiomql.git
synced 2026-08-19 23:08:08 +00:00
v4
This commit is contained in:
@@ -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
|
||||
@@ -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())
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user