diff --git a/Untitled.ipynb b/Untitled.ipynb index 5f6f68b..c73189d 100644 --- a/Untitled.ipynb +++ b/Untitled.ipynb @@ -2,20 +2,166 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "f4500c8d-0e58-4d3f-8dd3-06a4896f397f", "metadata": {}, "outputs": [], "source": [ - "import shelve\n", - "import pickle\n", - "import zlib\n", - "import lzma\n", - "import pytz\n", + "# import shelve\n", + "# import pickle\n", + "# import zlib\n", + "# import lzma\n", + "# import pytz\n", "from datetime import datetime, timedelta\n", - "from aiomql import MetaTrader, MetaTester, TimeFrame, TestData, AccountInfo, TimeFrame, CopyTicks, Account, GetData\n", - "# from MetaTrader5 import SymbolInfo\n", - "import pandas as pd" + "from aiomql import MetaTrader, TimeFrame, AccountInfo, TimeFrame, CopyTicks, Account, Symbol\n", + "from MetaTrader5 import SymbolInfo\n", + "import pandas as pd\n", + "from pandas import DataFrame\n", + "import pytz" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "f2ef126c-6edc-4651-8a81-06bb97eed6f9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "res = await Account().sign_in()\n", + "print(res)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "2c598a85-1e90-49b0-abf1-87329597feae", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tz = pytz.timezone('Etc/UTC')\n", + "sym = Symbol(name='EURUSD')\n", + "start = datetime(day=22, month=8, year=2024, tzinfo=tz)\n", + "end = datetime(day=26, month=8, year=2024, hour=12, tzinfo=tz)\n", + "# rates = await sym.mt5.copy_rates_from(symbol='EURUSD', date_from=end, count=5, timeframe=TimeFrame.H12)\n", + "rates = await sym.mt5.copy_rates_from_pos(symbol='EURUSD', start_pos=0, count=5, timeframe=TimeFrame.H12)\n", + "df = DataFrame(rates)\n", + "df['time'] = pd.to_datetime(df['time'], unit='s')\n", + "df.index[-1]" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "0a7d319c-5760-4058-994f-27e8d10df108", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
timeopenhighlowclosetick_volumespreadreal_volume
22024-08-25 12:00:001.118691.119471.118491.11894457010
32024-08-26 00:00:001.118941.120161.116281.116524358300
42024-08-26 12:00:001.116521.117901.115011.116184503400
\n", + "
" + ], + "text/plain": [ + " time open high low close tick_volume \\\n", + "2 2024-08-25 12:00:00 1.11869 1.11947 1.11849 1.11894 4570 \n", + "3 2024-08-26 00:00:00 1.11894 1.12016 1.11628 1.11652 43583 \n", + "4 2024-08-26 12:00:00 1.11652 1.11790 1.11501 1.11618 45034 \n", + "\n", + " spread real_volume \n", + "2 1 0 \n", + "3 0 0 \n", + "4 0 0 " + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.loc[2:6]" ] }, { diff --git a/src/aiomql/lib/backtester/__init__.py b/src/aiomql/core/backtester/__init__.py similarity index 100% rename from src/aiomql/lib/backtester/__init__.py rename to src/aiomql/core/backtester/__init__.py diff --git a/src/aiomql/lib/backtester/check.py b/src/aiomql/core/backtester/check.py similarity index 100% rename from src/aiomql/lib/backtester/check.py rename to src/aiomql/core/backtester/check.py diff --git a/src/aiomql/lib/backtester/get_data.py b/src/aiomql/core/backtester/get_data.py similarity index 98% rename from src/aiomql/lib/backtester/get_data.py rename to src/aiomql/core/backtester/get_data.py index 5c67ffe..2042619 100644 --- a/src/aiomql/lib/backtester/get_data.py +++ b/src/aiomql/core/backtester/get_data.py @@ -20,12 +20,13 @@ from ...utils import backoff_decorator logger = getLogger(__name__) + class Data(TypedDict): account: AccountInfo symbols: dict[str, SymbolInfo] - prices: DataFrame - ticks: DataFrame - rates: DataFrame + prices: dict[str, DataFrame] + ticks: dict[str, DataFrame] + rates: dict[str, dict[str, DataFrame]] interval: range diff --git a/src/aiomql/lib/backtester/meta_data.py b/src/aiomql/core/backtester/meta_data.py similarity index 100% rename from src/aiomql/lib/backtester/meta_data.py rename to src/aiomql/core/backtester/meta_data.py diff --git a/src/aiomql/lib/backtester/meta_tester.py b/src/aiomql/core/backtester/meta_tester.py similarity index 100% rename from src/aiomql/lib/backtester/meta_tester.py rename to src/aiomql/core/backtester/meta_tester.py diff --git a/src/aiomql/core/backtester/test_data.py b/src/aiomql/core/backtester/test_data.py new file mode 100644 index 0000000..27f3a37 --- /dev/null +++ b/src/aiomql/core/backtester/test_data.py @@ -0,0 +1,82 @@ +from datetime import datetime, tzinfo + +import pytz +import numpy as np +import pandas as pd +from pandas import DataFrame +from MetaTrader5 import Tick, SymbolInfo, AccountInfo + +from ..constants import TimeFrame, CopyTicks +from .get_data import Data, GetData +from ...utils import round_down, round_up + +tz = pytz.timezone('Etc/UTC') + + +class TestData: + def __init__(self, data: Data): + self._data = data + self.account = data['account'] + self.symbols = data['symbols'] + self.prices = data['prices'] + self.ticks = data['ticks'] + self.rates = data['rates'] + self.interval = data['interval'] + self.cursor = 0 + self.iter = iter(self.interval) + + def __next__(self): + self.cursor = next(self.iter) + return self.cursor + + def reset(self): + self.iter = iter(self.interval) + return self.iter + + def get_symbols_total(self) -> int: + return len(self.symbols) + + def get_symbols(self) -> list: + return list(self.symbols.keys()) + + def get_account_info(self) -> AccountInfo: + return AccountInfo(**self.account.dict) + + def get_symbol_info_tick(self, symbol: str) -> Tick: + tick = self.prices[symbol].iloc[self.cursor] + return Tick(**tick) + + def get_symbol_info(self, symbol: str) -> SymbolInfo: + info = self.symbols[symbol] + tick = self.get_symbol_info_tick(symbol) + info = info.dict + info |= {'bid': tick.bid, 'bidhigh': tick.bid, 'bidlow': tick.bid, 'ask': tick.ask, + 'askhigh': tick.ask, 'asklow': tick.bid, 'last': tick.last, 'volume_real': tick.volume_real} + return SymbolInfo(**info) + + def get_rates_from(self, symbol: str, timeframe: TimeFrame, date_from: datetime | float, count: int) -> np.ndarray: + rates = self.rates[symbol][timeframe.name] + start = int(datetime.timestamp(date_from)) if isinstance(date_from, datetime) else int(date_from) + start = round_down(start, timeframe.time) + start = rates.index.get_loc(start) + end = start + count + return rates.iloc[start:end].to_numpy() + + def get_rates_from_pos(self, symbol: str, timeframe: TimeFrame, start_pos: int, count: int) -> np.ndarray: + rates = self.rates[symbol][timeframe.name] + end = -start_pos + count + return rates.iloc[-start_pos:end].to_numpy() + + def get_rates_range(self, symbol: str, timeframe: TimeFrame, date_from: datetime, date_to: datetime) -> np.ndarray: + rates = self.rates[symbol][timeframe.name] + start = int(datetime.timestamp(date_from)) + start = round_down(start, timeframe.time) + end = round_up(int(datetime.timestamp(date_to)), timeframe.time) + return rates.loc[start:end].to_numpy() + + def get_ticks_from(self, symbol: str, date_from: datetime | float, count: int, flags: CopyTicks) -> DataFrame: + ticks = self.ticks[symbol] + start = int(datetime.timestamp(date_from)) if isinstance(date_from, datetime) else int(date_from) + start = round_down(start, 1) + end = start + count + return ticks.loc[start:end] diff --git a/src/aiomql/core/models.py b/src/aiomql/core/models.py index 4b4282c..2ebd808 100644 --- a/src/aiomql/core/models.py +++ b/src/aiomql/core/models.py @@ -334,7 +334,7 @@ class SymbolInfo(Base): path: str def __init__(self, **kwargs): - if name := kwargs.pop('name', None): + if name := kwargs.pop('name', None) is None: raise AttributeError('Symbol Object Must be initialized with a name') self.name = name super().__init__(**kwargs) diff --git a/src/aiomql/lib/__init__.py b/src/aiomql/lib/__init__.py index 5fd08d6..130b456 100644 --- a/src/aiomql/lib/__init__.py +++ b/src/aiomql/lib/__init__.py @@ -1,4 +1,3 @@ from .strategies import * from .traders import * from .symbols import * -from .backtester import * diff --git a/src/aiomql/lib/backtester/test_data.py b/src/aiomql/lib/backtester/test_data.py deleted file mode 100644 index 7efb383..0000000 --- a/src/aiomql/lib/backtester/test_data.py +++ /dev/null @@ -1,44 +0,0 @@ -from ...core.models import AccountInfo, SymbolInfo, TickInfo -from .get_data import Data, GetData -from MetaTrader5 import Tick, SymbolInfo - -class TestData: - def __init__(self, data: Data): - self._data = data - self.account = data['account'] - self.symbols = data['symbols'] - self.prices = data['prices'] - self.ticks = data['ticks'] - self.rates = data['rates'] - self.interval = data['interval'] - self.cursor = 0 - self.iter = iter(self.interval) - - def __next__(self): - self.cursor = next(self.iter) - return self.cursor - - def reset(self): - self.iter = iter(self.interval) - return self.iter - - def get_symbol_info_tick(self, symbol: str) -> Tick: - tick = self.prices[symbol].iloc[self.cursor] - return Tick(**tick) - - def get_symbol_info(self, symbol: str) -> SymbolInfo: - symbol = self.symbols[symbol] - symbol |= {'bid': tick.bid, 'bidhigh': tick.bid, 'bidlow': tick.bid, 'bid': tick.bid, 'bidhigh': tick.bid, 'bidlow': tick.bid} - symbol = SymbolInfo(**symbol) - tick = self.get_symbol_info_tick(symbol) - symbol.bid = tick.bid - symbol.bidhigh = 120.506 - symbol.bidlow = tick. - ask=120.041 - askhigh=120.526 - asklow=118.828 - symbol.update() - - - - diff --git a/src/aiomql/utils.py b/src/aiomql/utils.py index f14ad9d..f1d2201 100644 --- a/src/aiomql/utils.py +++ b/src/aiomql/utils.py @@ -62,3 +62,10 @@ def backoff_decorator(func=None, *, max_retries: int = 3, retries: int = 0, dela return await wrapper(*args, **kwargs) return wrapper + + +def round_down(value: int, base: int) -> int: + return value if value % base == 0 else value - (value % base) + +def round_up(value: int, base: int) -> int: + return value if value % base == 0 else value + base - (value % base)