diff --git a/src/aiomql/core/models.py b/src/aiomql/core/models.py index e75b7ec..4b4282c 100644 --- a/src/aiomql/core/models.py +++ b/src/aiomql/core/models.py @@ -4,7 +4,7 @@ from .constants import BookType, TradeAction, OrderType, OrderTime, OrderFilling DealReason, SymbolChartMode, SymbolTradeMode, SymbolCalcMode, SymbolOptionMode, SymbolOrderGTCMode, \ SymbolOptionRight, \ SymbolTradeExecution, SymbolSwapMode, DayOfWeek, AccountTradeMode, AccountStopOutMode, AccountMarginMode, \ - OrderReason + OrderReason, TickFlag from .base import Base @@ -334,9 +334,9 @@ class SymbolInfo(Base): path: str def __init__(self, **kwargs): - if 'name' not in kwargs: + if name := kwargs.pop('name', None): raise AttributeError('Symbol Object Must be initialized with a name') - self.name = kwargs.pop('name') + self.name = name super().__init__(**kwargs) def __repr__(self): @@ -351,6 +351,28 @@ class SymbolInfo(Base): def __hash__(self): return hash(self.name) +class TickInfo(Base): + """Price Tick of a Financial Instrument. + + Attributes: + time (int): Time of the last prices update for the symbol + bid (float): Current Bid price + ask (float): Current Ask price + last (float): Price of the last deal (Last) + volume (float): Volume for the current Last price + time_msc (int): Time of the last prices update for the symbol in milliseconds + flags (TickFlag): Tick flags + volume_real (float): Volume for the current Last price + Index (int): Custom attribute representing the position of the tick in a sequence. + """ + time: float + bid: float + ask: float + last: float + volume: float + time_msc: float + flags: TickFlag + volume_real: float class BookInfo(Base): """Book Information Class. diff --git a/src/aiomql/lib/backtester/get_data.py b/src/aiomql/lib/backtester/get_data.py index 0e1a7f4..9d11038 100644 --- a/src/aiomql/lib/backtester/get_data.py +++ b/src/aiomql/lib/backtester/get_data.py @@ -1,5 +1,6 @@ +from typing import TypeDict import pickle -import random +import lzma from datetime import datetime from logging import getLogger import asyncio @@ -7,21 +8,31 @@ import asyncio import pytz from MetaTrader5 import Tick, SymbolInfo import pandas as pd +from pandas import DataFrame from ...core.meta_trader import MetaTrader from ...core.config import Config from ...core.errors import Error from ...core.constants import TimeFrame, CopyTicks, OrderType from ...core.models import (AccountInfo, SymbolInfo, BookInfo, TradeOrder, OrderCheckResult, OrderSendResult, - TradePosition, TradeDeal) + TradePosition, TradeDeal, TickInfo) from ...utils import backoff_decorator logger = getLogger(__name__) +class Data(TypeDict): + account: AccountInfo + symbols: dict[str, SymbolInfo] + prices: DataFrame + ticks: DataFrame + rates: DataFrame + span: range + class GetData(MetaTrader): + config: Config = Config() - def __init__(self, start: datetime, end: datetime, timeframes: set[TimeFrame], symbols: set[str], + def __init__(self, *, start: datetime, end: datetime, timeframes: set[TimeFrame], symbols: set[str], interval: int = 60, name: str = '', tz: str = 'Etc/UTC'): """""" super().__init__() @@ -37,7 +48,7 @@ class GetData(MetaTrader): self.span = range(start := int(self.start.timestamp()), diff + start) self.mt5 = MetaTrader() - async def get_test_data(self) -> dict: + async def get_data(self) -> Data: """""" data = {} rates, ticks, prices = await asyncio.gather(self.get_symbols_rates(), self.get_symbols_ticks(), @@ -48,43 +59,55 @@ class GetData(MetaTrader): data['prices'] = prices data['symbols'] = await self.get_symbols_info() data['account'] = self.get_account_info() + data['range'] = self.span return data - async def get_and_save_data(self) -> None: + async def pickle_data(self) -> None: """""" - data = await self.get_test_data() + data = await self.get_data() fh = open(f'{self.config.root}/data/{self.name}', 'wb') pickle.dump(data, fh) fh.close() - - def load_data(self, name: str = '') -> dict: + + async def compress_data(self): """""" - name = name or self.name - file = open(f'{self.config.root}/data/{name}', 'rb') - data = pickle.load(file) - file.close() + data = await self.get_data() + bdata = pickle.dumps(data) + name = self.name + 'xz' + with lzma.open(name, 'w') as fh: + fh.write(bdata) + + @classmethod + def load_data(cls, name: str, compressed=False) -> dict: + """""" + fo = open(f'{cls.config.root}/data/{name}', 'rb') + + if compressed: + data = lzma.decompress(fo) + data = pickle.load(data) + fo.close() return data - async def get_symbols_info(self): + async def get_symbols_info(self) -> dict[str, SymbolInfo]: """""" tasks = [self.get_symbol_info(symbol) for symbol in self.symbols] res = await asyncio.gather(*tasks) - return {symbol: info for symbol, info in res} + return {symbol: SymbolInfo(**info) for symbol, info in res} - async def get_symbols_ticks(self): + async def get_symbols_ticks(self) -> dict[str, DataFrame]: """""" tasks = [self.get_symbol_ticks(symbol) for symbol in self.symbols] res = await asyncio.gather(*tasks) - return {symbol: ticks for symbol, ticks in res} + return {symbol: tick for symbol, tick in res} - async def get_symbols_prices(self): + async def get_symbols_prices(self) -> dict[str, DataFrame]: """""" tasks = [self.get_symbol_prices(symbol) for symbol in self.symbols] res = await asyncio.gather(*tasks) return {symbol: prices for symbol, prices in res} - async def get_symbols_rates(self): + async def get_symbols_rates(self) -> dict[str: dict[TimeFrame: DataFrame]]: """""" tasks = [self.get_symbol_rates(symbol, timeframe) for symbol in self.symbols for timeframe in self.timeframes] res = await asyncio.gather(*tasks) @@ -94,19 +117,19 @@ class GetData(MetaTrader): return data @backoff_decorator(max_retries=5) - async def get_account_info(self) -> AccountInfo | None: + async def get_account_info(self) -> dict: """""" res = await self.mt5.account_info() return res._asdict() @backoff_decorator(max_retries=5) - async def get_symbol_info(self, symbol: str): + async def get_symbol_info(self, symbol: str) -> tuple[str, dict]: """""" res = await self.mt5.symbol_info(symbol) return symbol, res._asdict() @backoff_decorator(max_retries=5) - async def get_symbol_ticks(self, symbol: str): + async def get_symbol_ticks(self, symbol: str) -> tuple[str, DataFrame]: """""" res = await self.mt5.copy_ticks_range(symbol, self.start, self.end, CopyTicks.ALL) res = pd.DataFrame(res) @@ -115,7 +138,7 @@ class GetData(MetaTrader): return symbol, res @backoff_decorator(max_retries=5) - async def get_symbol_prices(self, symbol: str): + async def get_symbol_prices(self, symbol: str) -> tuple[str, DataFrame]: """""" res = await self.mt5.copy_ticks_range(symbol, self.start, self.end, CopyTicks.ALL) res = pd.DataFrame(res) @@ -125,7 +148,7 @@ class GetData(MetaTrader): return symbol, res @backoff_decorator(max_retries=5) - async def get_symbol_rates(self, symbol: str, timeframe: TimeFrame): + async def get_symbol_rates(self, symbol: str, timeframe: TimeFrame) -> tuple[str, TimeFrame, DataFrame]: """""" res = await self.mt5.copy_rates_range(symbol, timeframe, self.start, self.end) res = pd.DataFrame(res) diff --git a/src/aiomql/lib/backtester/test_data.py b/src/aiomql/lib/backtester/test_data.py index 2c4b15c..876d621 100644 --- a/src/aiomql/lib/backtester/test_data.py +++ b/src/aiomql/lib/backtester/test_data.py @@ -1,14 +1,15 @@ -from typing import Literal -from ...core.models import AccountInfo, SymbolInfo +from typing import Literal, TypedDict +from ...core.models import AccountInfo, SymbolInfo, Tick from ...core.constants import TimeFrame from ...core.meta_trader import MetaTrader -# from ...account import Account +from .get_data import Data, GetData class TestData: - def __init__(self, data): - self._data = data + def __init__(self, data: Data): + self.data = data + self.cursor = 0 def __getitem__(self, item: tuple[Literal['ticks', 'rates'], SymbolInfo, TimeFrame]): type_, symbol, time_frame = item diff --git a/src/aiomql/ticks.py b/src/aiomql/ticks.py index fe62229..4a3a947 100644 --- a/src/aiomql/ticks.py +++ b/src/aiomql/ticks.py @@ -32,7 +32,7 @@ class Tick: last: float volume: float time_msc: float - flags: float + flags: TickFlag volume_real: float Index: int diff --git a/src/aiomql/utils.py b/src/aiomql/utils.py index 29ebeb1..f14ad9d 100644 --- a/src/aiomql/utils.py +++ b/src/aiomql/utils.py @@ -60,4 +60,5 @@ def backoff_decorator(func=None, *, max_retries: int = 3, retries: int = 0, dela delay += 1 retries += 1 return await wrapper(*args, **kwargs) + return wrapper