mirror of
https://github.com/Ichinga-Samuel/aiomql.git
synced 2026-08-25 09:48:03 +00:00
refactor symbol and executor
This commit is contained in:
+1
-1
@@ -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 = [
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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
@@ -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.
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from aiomql import MetaTrader
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from .fixtures import *
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
class BaseTest:
|
||||||
|
""""""
|
||||||
|
mt5 = MetaTrader()
|
||||||
@@ -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)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"account_number": 160286827,
|
||||||
|
"password": "TheN@me0fTheW!nd",
|
||||||
|
"server": "ForexTimeFXTM-Demo01"
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
from aiomql import TradeAction
|
||||||
|
|
||||||
|
|
||||||
|
class TestConstants:
|
||||||
|
|
||||||
|
def test_trade_action(self):
|
||||||
|
assert TradeAction.DEAL == 1
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
|
#
|
||||||
Reference in New Issue
Block a user