refactor symbol and executor

This commit is contained in:
Ichinga Samuel
2023-10-24 13:59:28 +01:00
parent 21043dd326
commit aa39fdf033
14 changed files with 214 additions and 13 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "aiomql"
version = "3.0.4"
version = "3.0.5"
readme = "README.md"
requires-python = ">=3.10"
classifiers = [
+4 -8
View File
@@ -19,7 +19,7 @@ class Bot:
Attributes:
account (Account): Account Object.
executor: The default thread executor.
symbols (set[Symbols]): A set of symbols for the trading session
symbols (list[Symbols]): A set of symbols for the trading session
"""
account: Account = Account()
@@ -82,7 +82,6 @@ class Bot:
Notes:
Make sure the symbol has been added to the market
"""
self.symbols.add(strategy.symbol)
self.executor.add_worker(strategy)
def add_strategies(self, strategies: Iterable[Strategy]):
@@ -104,9 +103,8 @@ class Bot:
[self.add_strategy(strategy(symbol=symbol, params=params)) for symbol in self.symbols]
async def init_symbols(self):
"""Initialize the symbols for the current trading session. This method is called internally by the bot.
"""
syms = [self.init_symbol(symbol) for symbol in self.symbols]
"""Initialize the symbols for the current trading session. This method is called internally by the bot."""
syms = [self.init_symbol(strategy.symbol) for strategy in self.executor.workers]
await asyncio.gather(*syms, return_exceptions=True)
async def init_symbol(self, symbol: Symbol) -> Symbol:
@@ -123,9 +121,7 @@ class Bot:
if self.account.has_symbol(symbol):
init = await symbol.init()
if init:
self.symbols.add(symbol)
return symbol
self.symbols.discard(symbol)
logger.warning(f'Unable to initialize symbol {symbol}')
self.symbols.remove(symbol)
logger.warning(f'{symbol} not a available for this market')
-1
View File
@@ -13,7 +13,6 @@ class Executor:
workers (list): List of strategies.
coroutines (dict[Coroutine, dict]): A dictionary of coroutines to run in the executor
functions (dict[Callable, dict]): A dictionary of functions to run in the executor
"""
def __init__(self, bot=None):
+2 -1
View File
@@ -47,7 +47,8 @@ class Strategy(ABC):
self.parameters = params.copy() if isinstance(params, dict) else {}
self.parameters['symbol'] = symbol.name
self.parameters['name'] = self.name or self.__class__.__name__
self.sessions = sessions or Sessions(Session(start=0, end=23))
self.sessions = sessions or Sessions(Session(start=0, end=dtime(hour=23, minute=59, second=59,
microsecond=999999)))
def __repr__(self):
return f"{self.name}({self.symbol!r})"
+15 -1
View File
@@ -1,6 +1,7 @@
"""Symbol class for handling a financial instrument."""
from datetime import datetime
from logging import getLogger
from math import log10, ceil
from .core.constants import TimeFrame, CopyTicks
from .core.models import SymbolInfo, BookInfo
@@ -135,7 +136,20 @@ class Symbol(SymbolInfo):
"""
return await self.mt5.market_book_release(self.name)
async def compute_volume(self, *, amount: float, pips: float, use_limits: bool = True) -> float:
def check_volume(self, volume) -> tuple[bool, float]:
check = self.volume_min <= volume <= self.volume_max
if check:
return check, volume
if not check and volume < self.volume_min:
return check, self.volume_min
else:
return check, self.volume_max
def round_off_volume(self, volume):
step = ceil(abs(log10(self.volume_step)))
return round(volume, step)
async def compute_volume(self, *, amount: float, pips: float, use_limits: bool = False) -> float:
"""Computes the volume of a trade based on the amount and the number of pips to target.
This is a dummy method that returns the minimum volume of the symbol. It is meant to be overridden by a subclass
that implements the computation of volume.
+2 -1
View File
@@ -54,7 +54,8 @@ class Trader:
"""
# check if pips is passed in as a keyword argument, if not use the pips attribute of the ram instance
pips = kwargs.get('pips', 0) or self.ram.pips
self.order.volume = kwargs.get('volume', self.ram.volume) or await self.ram.get_volume(symbol=self.symbol, pips=pips)
self.order.volume = kwargs.get('volume', self.ram.volume) or await self.ram.get_volume(symbol=self.symbol,
pips=pips)
self.order.type = order_type
await self.set_order_limits(pips=pips)
+10
View File
@@ -0,0 +1,10 @@
from aiomql import MetaTrader
import pytest
from .fixtures import *
@pytest.mark.asyncio
class BaseTest:
""""""
mt5 = MetaTrader()
+31
View File
@@ -0,0 +1,31 @@
import json
import os
from aiomql import MetaTrader as mt5, Config
import pytest
@pytest.fixture(scope="session")
def get_default_config():
data = {"win_percentage": 0.90, "record_dir": "Trade Records"}
obj = open('mt5.json', 'w')
json.dump(data, obj)
obj.close()
yield
os.remove('mt5.json')
@pytest.fixture(scope="session")
def get_config():
data = {"win_percentage": 0.8, "record_dir": "Trade_Records"}
obj = open('config.json', 'w')
json.dump(data, obj)
obj.close()
yield
os.remove('config.json')
@pytest.fixture(autouse=True, scope="session")
def init():
config = Config(filename="test_config.json")
mt5._initialize()
mt5._login(login=config.account_number, password=config.password, server=config.server)
+5
View File
@@ -0,0 +1,5 @@
{
"account_number": 160286827,
"password": "TheN@me0fTheW!nd",
"server": "ForexTimeFXTM-Demo01"
}
+13
View File
@@ -0,0 +1,13 @@
from aiomql import config
from . import get_config, get_default_config
def test_default_config_file(get_default_config):
conf = config.Config()
assert conf.win_percentage == 0.90
def test_config_file_name(get_config):
conf = config.Config(filename='config.json')
assert conf.win_percentage == 0.8
+7
View File
@@ -0,0 +1,7 @@
from aiomql import TradeAction
class TestConstants:
def test_trade_action(self):
assert TradeAction.DEAL == 1
+11
View File
@@ -0,0 +1,11 @@
from aiomql.symbol import Symbol
from . import *
class TestSymbol(BaseTest):
sym = Symbol(name="EURJPY")
async def test_init(self):
await self.sym.init()
assert self.sym.select is True
+22
View File
@@ -0,0 +1,22 @@
from . import *
from aiomql import Terminal
class TestTerminal(BaseTest):
terminal = Terminal()
async def test_version(self):
res = await self.terminal.version
assert len(res) == 3
async def test_info(self):
res = await self.terminal.info()
assert res.connected is True
async def test_error(self):
res = await self.terminal.last_error()
assert res.code == 1
async def test_symbols_get(self):
res = await self.terminal.symbols_get()
sym = next(res)
assert isinstance(sym.name, str)
+91
View File
@@ -0,0 +1,91 @@
# from datetime import datetime
# from collections import defaultdict
# from pickle import HIGHEST_PROTOCOL
# import _pickle as pickle
# import lzma
# import asyncio
# from itertools import product
# from typing import Iterable, TypeAlias
#
# from .meta_trader import MetaTrader
# from .constants import TimeFrame
# from .. import account, Account, Ticks, Symbol, Candles
#
# Rates: TypeAlias = dict[Symbol, dict[TimeFrame, Candles]]
# PriceTicks: TypeAlias = dict[Symbol, Ticks]
#
#
# class MetaTester(MetaTrader):
# def __init__(self, *, file=None, data: 'TestData' = None):
# self.file = file
#
# @property
# def data(self):
# return TestData.load(self.file)
#
#
# class TestData:
# rates: Rates
# ticks: PriceTicks
# account: Account
#
# def __init__(self, symbols: Iterable[Symbol], timeframes: Iterable[TimeFrame], start: datetime, end: datetime, file: str):
# self.symbols = symbols
# self.timeframes = timeframes
# self.start = start
# self.end = end
# self.file = file
#
# @property
# async def _account(self) -> Account:
# await account.refresh()
# return account
#
# @property
# async def _ticks(self) -> PriceTicks:
# tasks = []
# symbols = []
# for symbol in self.symbols:
# coro = symbol.copy_ticks_range(date_from=self.start, date_to=self.end)
# symbols.append(symbol)
# tasks.append(asyncio.create_task(coro))
# ticks = await asyncio.gather(*tasks)
# return {symbol: ticks for symbol, ticks in zip(symbols, ticks)}
#
# @property
# async def _rates(self) -> Rates:
# _data = {'tasks': [], 'symbols': [], 'timeframes': []}
# args: Iterable[tuple[Symbol, TimeFrame]] = product(self.symbols, self.timeframes)
# for symbol, timeframe in args:
# coro = symbol.copy_rates_range(date_from=self.start, date_to=self.end, timeframe=timeframe)
# _data['tasks'].append(asyncio.create_task(coro))
# _data['symbols'].append(symbol)
# _data['timeframes'].append(timeframe)
# _data['rates'] = await asyncio.gather(*_data['tasks'])
#
# data = defaultdict(dict)
# for rates, symbol, timeframe in zip(_data['rates'], _data['symbols'], _data['timeframes']):
# data[symbol] |= {timeframe: rates}
# return data
#
# async def copy_data(self):
# self.rates, self.ticks, self.account = await asyncio.gather(self._rates, self._ticks, self._account)
#
# async def dumps(self):
# return pickle.dumps(self, protocol=HIGHEST_PROTOCOL)
#
# async def dump(self):
# await self.copy_data()
# with lzma.open(self.file, 'wb') as fh:
# pickle.dump(self, fh, protocol=HIGHEST_PROTOCOL)
#
# @classmethod
# def load(cls, file) -> 'TestData':
# with lzma.open(file, 'rb') as fh:
# return pickle.load(fh)
#
# @classmethod
# def loads(cls, obj):
# return pickle.loads(obj)
#