mirror of
https://github.com/Ichinga-Samuel/aiomql.git
synced 2026-08-07 17:27:45 +00:00
refactor symbol and executor
This commit is contained in:
+1
-1
@@ -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 = [
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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