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] [project]
name = "aiomql" name = "aiomql"
version = "3.0.4" version = "3.0.5"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"
classifiers = [ classifiers = [
+4 -8
View File
@@ -19,7 +19,7 @@ class Bot:
Attributes: Attributes:
account (Account): Account Object. account (Account): Account Object.
executor: The default thread executor. 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() account: Account = Account()
@@ -82,7 +82,6 @@ class Bot:
Notes: Notes:
Make sure the symbol has been added to the market Make sure the symbol has been added to the market
""" """
self.symbols.add(strategy.symbol)
self.executor.add_worker(strategy) self.executor.add_worker(strategy)
def add_strategies(self, strategies: Iterable[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] [self.add_strategy(strategy(symbol=symbol, params=params)) for symbol in self.symbols]
async def init_symbols(self): async def init_symbols(self):
"""Initialize the symbols for the current trading session. This method is called internally by the bot. """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]
syms = [self.init_symbol(symbol) for symbol in self.symbols]
await asyncio.gather(*syms, return_exceptions=True) await asyncio.gather(*syms, return_exceptions=True)
async def init_symbol(self, symbol: Symbol) -> Symbol: async def init_symbol(self, symbol: Symbol) -> Symbol:
@@ -123,9 +121,7 @@ class Bot:
if self.account.has_symbol(symbol): if self.account.has_symbol(symbol):
init = await symbol.init() init = await symbol.init()
if init: if init:
self.symbols.add(symbol)
return symbol return symbol
self.symbols.discard(symbol)
logger.warning(f'Unable to initialize symbol {symbol}') logger.warning(f'Unable to initialize symbol {symbol}')
self.symbols.remove(symbol)
logger.warning(f'{symbol} not a available for this market') logger.warning(f'{symbol} not a available for this market')
-1
View File
@@ -13,7 +13,6 @@ class Executor:
workers (list): List of strategies. workers (list): List of strategies.
coroutines (dict[Coroutine, dict]): A dictionary of coroutines to run in the executor 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 functions (dict[Callable, dict]): A dictionary of functions to run in the executor
""" """
def __init__(self, bot=None): 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 = params.copy() if isinstance(params, dict) else {}
self.parameters['symbol'] = symbol.name self.parameters['symbol'] = symbol.name
self.parameters['name'] = self.name or self.__class__.__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): def __repr__(self):
return f"{self.name}({self.symbol!r})" return f"{self.name}({self.symbol!r})"
+15 -1
View File
@@ -1,6 +1,7 @@
"""Symbol class for handling a financial instrument.""" """Symbol class for handling a financial instrument."""
from datetime import datetime from datetime import datetime
from logging import getLogger from logging import getLogger
from math import log10, ceil
from .core.constants import TimeFrame, CopyTicks from .core.constants import TimeFrame, CopyTicks
from .core.models import SymbolInfo, BookInfo from .core.models import SymbolInfo, BookInfo
@@ -135,7 +136,20 @@ class Symbol(SymbolInfo):
""" """
return await self.mt5.market_book_release(self.name) 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. """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 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. 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 # 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 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 self.order.type = order_type
await self.set_order_limits(pips=pips) 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)
#