mirror of
https://github.com/Ichinga-Samuel/aiomql.git
synced 2026-08-18 22:38:06 +00:00
reorganize the library into three folders lib, contrib and core
Write unittests with pytest Make all functions and method signatures as keyword only arguments
This commit is contained in:
@@ -1,14 +0,0 @@
|
||||
import pytest
|
||||
from aiomql import Config
|
||||
import MetaTrader5
|
||||
|
||||
|
||||
@pytest.fixture(scope='session', autouse=True)
|
||||
def config():
|
||||
config = Config(filename='test.json')
|
||||
return config
|
||||
|
||||
|
||||
@pytest.fixture(scope='session', autouse=True)
|
||||
def metatrader5():
|
||||
return MetaTrader5
|
||||
@@ -0,0 +1,93 @@
|
||||
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/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}
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
async def buy_order(mt):
|
||||
sym = 'BTCUSD'
|
||||
sym_info = await mt.symbol_info(sym)
|
||||
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}
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
async def make_orders(mt):
|
||||
sym = 'BTCUSD'
|
||||
sym_info = await mt.symbol_info(sym)
|
||||
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['price'] = sym_info.bid
|
||||
req['sl'] = sym_info.bid + dsl
|
||||
req['tp'] = sym_info.bid - dsl
|
||||
await mt.order_send(req)
|
||||
@@ -0,0 +1,4 @@
|
||||
[pytest]
|
||||
asyncio_default_fixture_loop_scope = session
|
||||
addopts = --rootdir=tests --capture=tee-sys --last-failed
|
||||
asyncio_mode = auto
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"login": 31288540,
|
||||
"password": "nwa0#anaEze",
|
||||
"server": "Deriv-Demo",
|
||||
"demo": 5463204,
|
||||
"fin": 24251812,
|
||||
"deriv-demo": 5463204,
|
||||
"deriv-real": 31288540,
|
||||
"mode": "backtest"
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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/configs/test2.json')
|
||||
assert config.filename == 'test2.json'
|
||||
@@ -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_orders):
|
||||
await self.history.init()
|
||||
|
||||
@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
|
||||
@@ -1,199 +1,207 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
import pytest_asyncio
|
||||
from aiomql import MetaTrader, TimeFrame, OrderType, CopyTicks
|
||||
import MetaTrader5
|
||||
|
||||
from . import metatrader5
|
||||
from aiomql import MetaTrader
|
||||
|
||||
|
||||
class TestMetaTrader:
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls, metatrader5):
|
||||
tz = pytz.timezone('Etc/UTC')
|
||||
def setup_class(cls):
|
||||
cls.mt = MetaTrader()
|
||||
cls.mt5 = metatrader5
|
||||
cls.symbol = "Volatility 100 Index"
|
||||
now = datetime.now(tz=tz)
|
||||
cls.start = now - timedelta(hours=24)
|
||||
cls.end = now + timedelta(hours=2)
|
||||
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
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
cls.mt._shutdown()
|
||||
|
||||
async def test_initialize(self):
|
||||
res = await self.mt.initialize()
|
||||
assert res == True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_login(self):
|
||||
res = await self.mt.login()
|
||||
assert res == True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_last_error(self):
|
||||
res = await self.mt.last_error()
|
||||
assert isinstance(res, tuple)
|
||||
assert res[0] == 1
|
||||
assert res[1] == 'Successful'
|
||||
|
||||
@pytest.mark.asyncio
|
||||
assert res[1] == 'Success'
|
||||
|
||||
async def test_version(self):
|
||||
res = await self.mt.version()
|
||||
res2 = self.mt5.version()
|
||||
res2 = self.mt5.version()
|
||||
assert res is not None
|
||||
assert res == res2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
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
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
async def test_terminal_info(self):
|
||||
res = await self.mt.terminal_info()
|
||||
res2 = await self.mt5.terminal_info()
|
||||
res2 = self.mt5.terminal_info()
|
||||
assert res is not None
|
||||
assert res == res2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_symbols_total(self):
|
||||
res = await self.mt.symbols_total()
|
||||
res2 = await self.mt5.symbols_total()
|
||||
res2 = self.mt5.symbols_total()
|
||||
assert isinstance(res, int)
|
||||
assert res
|
||||
|
||||
@pytest.mark.asyncio
|
||||
assert res == res2
|
||||
|
||||
async def test_symbols_get(self):
|
||||
res = await self.mt.symbols_get()
|
||||
res2 = await self.mt5.symbols_get()
|
||||
res2 = self.mt5.symbols_get()
|
||||
assert res is not None
|
||||
assert res == res2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
assert len(res) == len(res2)
|
||||
|
||||
async def test_symbol_info(self):
|
||||
res = await self.mt.symbol_info(self.symbol)
|
||||
res2 = await self.mt5.symbol_info(self.symbol)
|
||||
res2 = self.mt5.symbol_info(self.symbol)
|
||||
assert res is not None
|
||||
assert res == res2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
async def test_symbol_info_tick(self):
|
||||
res = await self.mt.symbol_info_tick(self.symbol)
|
||||
res2 = await self.mt5.symbol_info_tick(self.symbol)
|
||||
res2 = self.mt5.symbol_info_tick(self.symbol)
|
||||
assert res is not None
|
||||
assert res == res2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
async def test_symbol_select(self):
|
||||
res = await self.mt.symbol_select(self.symbol, True)
|
||||
assert res == True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
async def test_market_book_add(self):
|
||||
res = await self.mt.market_book_add(self.symbol)
|
||||
assert res == True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
async def test_market_book_get(self):
|
||||
res = await self.mt.market_book_get(self.symbol)
|
||||
res2 = await self.mt5.market_book_get(self.symbol)
|
||||
res2 = self.mt5.market_book_get(self.symbol)
|
||||
assert res is not None
|
||||
assert res == res2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
async def test_market_book_release(self):
|
||||
res = await self.mt.market_book_release(self.symbol)
|
||||
assert res == True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
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
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
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
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
async def test_copy_rates_range(self):
|
||||
res = await self.mt.copy_rates_range(self.symbol, TimeFrame.M1, datetime.now(), datetime.now())
|
||||
res = await self.mt.copy_rates_range(self.symbol, self.tf, self.start, self.end)
|
||||
assert res is not None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
assert res.shape[0] == 10
|
||||
|
||||
async def test_copy_ticks_from(self):
|
||||
res = await self.mt.copy_ticks_from(self.symbol, datetime.now(), 10, CopyTicks.ALL)
|
||||
res = await self.mt.copy_ticks_from(self.symbol, self.start, 10, self.mt.COPY_TICKS_ALL)
|
||||
assert res is not None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
assert res.shape[0] == 10
|
||||
|
||||
async def test_copy_ticks_range(self):
|
||||
res = await self.mt.copy_ticks_range(self.symbol, datetime.now(), datetime.now(), CopyTicks.ALL)
|
||||
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
|
||||
|
||||
@pytest.mark.asyncio
|
||||
assert res.shape[0] == res2.shape[0]
|
||||
|
||||
async def test_orders_total(self):
|
||||
res = await self.mt.orders_total()
|
||||
assert isinstance(res, int)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
async def test_orders_get(self):
|
||||
res = await self.mt.orders_get()
|
||||
assert res is not None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_order_calc_margin(self):
|
||||
res = await self.mt.order_calc_margin(OrderType.BUY, self.symbol, 1.0, 1.0)
|
||||
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)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_order_calc_profit(self):
|
||||
res = await self.mt.order_calc_profit(OrderType.BUY, self.symbol, 1.0, 1.0, 1.1)
|
||||
|
||||
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)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_order_check(self):
|
||||
request = {"action": OrderType.BUY, "symbol": self.symbol, "volume": 1.0, "price": 1.0}
|
||||
res = await self.mt.order_check(request)
|
||||
|
||||
async def test_order_check(self, buy_order):
|
||||
res = await self.mt.order_check(buy_order)
|
||||
assert res is not None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_order_send(self):
|
||||
request = {"action": OrderType.BUY, "symbol": self.symbol, "volume": 1.0, "price": 1.0}
|
||||
res = await self.mt.order_send(request)
|
||||
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
|
||||
|
||||
@pytest.mark.asyncio
|
||||
assert res.retcode == 10009
|
||||
|
||||
async def test_positions_total(self):
|
||||
res = await self.mt.positions_total()
|
||||
assert isinstance(res, int)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
assert res >= 0
|
||||
|
||||
async def test_positions_get(self):
|
||||
res = await self.mt.positions_get()
|
||||
assert res is not None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
assert isinstance(res, tuple)
|
||||
assert len(res) >= 0
|
||||
|
||||
async def test_history_orders_total(self):
|
||||
res = await self.mt.history_orders_total(datetime.now(), datetime.now())
|
||||
res = await self.mt.history_orders_total(self.start, self.end)
|
||||
assert isinstance(res, int)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
assert res >= 0
|
||||
|
||||
async def test_history_orders_get(self):
|
||||
res = await self.mt.history_orders_get(datetime.now(), datetime.now())
|
||||
res = await self.mt.history_orders_get(self.start, self.end)
|
||||
assert res is not None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
assert isinstance(res, tuple)
|
||||
assert len(res) >= 0
|
||||
|
||||
async def test_history_deals_total(self):
|
||||
res = await self.mt.history_deals_total(datetime.now(), datetime.now())
|
||||
res = await self.mt.history_deals_total(self.start, self.end)
|
||||
assert isinstance(res, int)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
assert res >= 0
|
||||
|
||||
async def test_history_deals_get(self):
|
||||
res = await self.mt.history_deals_get(datetime.now(), datetime.now())
|
||||
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)
|
||||
@@ -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)
|
||||
@@ -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_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
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -0,0 +1,68 @@
|
||||
from datetime import datetime, time
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
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=pytz.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)
|
||||
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)
|
||||
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=pytz.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()
|
||||
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=pytz.UTC)
|
||||
noon = time(hour=12, tzinfo=pytz.UTC)
|
||||
mid_nite = time(hour=0, tzinfo=pytz.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))
|
||||
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(pytz.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')
|
||||
@@ -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.init()
|
||||
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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user