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",
+ " time | \n",
+ " open | \n",
+ " high | \n",
+ " low | \n",
+ " close | \n",
+ " tick_volume | \n",
+ " spread | \n",
+ " real_volume | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 2 | \n",
+ " 2024-08-25 12:00:00 | \n",
+ " 1.11869 | \n",
+ " 1.11947 | \n",
+ " 1.11849 | \n",
+ " 1.11894 | \n",
+ " 4570 | \n",
+ " 1 | \n",
+ " 0 | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " 2024-08-26 00:00:00 | \n",
+ " 1.11894 | \n",
+ " 1.12016 | \n",
+ " 1.11628 | \n",
+ " 1.11652 | \n",
+ " 43583 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " 2024-08-26 12:00:00 | \n",
+ " 1.11652 | \n",
+ " 1.11790 | \n",
+ " 1.11501 | \n",
+ " 1.11618 | \n",
+ " 45034 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ "
\n",
+ " \n",
+ "
\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)