diff --git a/Untitled.ipynb b/Untitled.ipynb
index 496fa33..1ea62d1 100644
--- a/Untitled.ipynb
+++ b/Untitled.ipynb
@@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": 1,
"id": "f4500c8d-0e58-4d3f-8dd3-06a4896f397f",
"metadata": {},
"outputs": [],
@@ -41,9 +41,52 @@
},
{
"cell_type": "code",
- "execution_count": 19,
+ "execution_count": 3,
"id": "2c598a85-1e90-49b0-abf1-87329597feae",
"metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "sym = Symbol(name='Volatility 25 Index')\n",
+ "await sym.init()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "0a7d319c-5760-4058-994f-27e8d10df108",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0.5"
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "await sym.mt5.order_calc_margin(0, 'Volatility 25 Index', 1, sym.tick.ask)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "id": "4ec498ea-5e4e-4ff6-85da-051acc2eaf28",
+ "metadata": {},
"outputs": [
{
"data": {
@@ -51,136 +94,49 @@
"4"
]
},
- "execution_count": 19,
+ "execution_count": 17,
"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]"
+ "sy = await sym.mt5.symbol_info('Volatility 25 Index')\n",
+ "await sym.mt5.symbol_select('Volatility 25 Index', enable=True)\n",
+ "sy.trade_calc_mode"
]
},
{
"cell_type": "code",
- "execution_count": 30,
- "id": "0a7d319c-5760-4058-994f-27e8d10df108",
+ "execution_count": 12,
+ "id": "9307e3df-a5f3-42cf-9d3d-e65e5254c0e5",
"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 "
+ "0.50097375"
]
},
- "execution_count": 30,
+ "execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
- "df.loc[2:6]"
+ "# margin\n",
+ "# worked for forex.\n",
+ "tcs = 1 * sym.trade_contract_size\n",
+ "lv = Account().leverage /0.125\n",
+ "tcs * sym.tick.ask / lv"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "9307e3df-a5f3-42cf-9d3d-e65e5254c0e5",
+ "id": "e8413f70-244f-47a2-97b3-2be6fcd87589",
"metadata": {},
"outputs": [],
- "source": [
- "now = datetime.now()\n",
- "st = now.replace(hour=0, minute=0, second=0, day=1, month=1, year=2023)\n",
- "et = now.replace(hour=9, minute=0, second=0)\n",
- "diff = et - st\n",
- "secs = int(diff.total_seconds())\n",
- "# st = now.replace(hour=0, day=16)\n",
- "# et = now.replace(hour=9)\n",
- "# start = now.replace(day=now.day-3, tzinfo=tz)\n",
- "# end = now.replace(day=now.day-1, tzinfo=tz)"
- ]
+ "source": []
},
{
"cell_type": "code",
@@ -658,7 +614,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.11.4"
+ "version": "3.11.6"
}
},
"nbformat": 4,
diff --git a/Untitled1.ipynb b/Untitled1.ipynb
index ee4519b..c3a0395 100644
--- a/Untitled1.ipynb
+++ b/Untitled1.ipynb
@@ -14,7 +14,7 @@
},
{
"cell_type": "code",
- "execution_count": 19,
+ "execution_count": 2,
"id": "8d39819f-2cac-437f-b5fc-633ca7443f8a",
"metadata": {},
"outputs": [],
@@ -24,23 +24,93 @@
},
{
"cell_type": "code",
- "execution_count": 32,
+ "execution_count": 6,
"id": "d7976bb8-05cb-4924-a6e2-90ea8af85d9d",
"metadata": {},
"outputs": [
{
"data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " 0 | \n",
+ " 1 | \n",
+ " 2 | \n",
+ " symbols | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " 10 | \n",
+ " 10 | \n",
+ " 20 | \n",
+ " A | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " 20 | \n",
+ " 11 | \n",
+ " 22 | \n",
+ " A | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " 30 | \n",
+ " 12 | \n",
+ " 24 | \n",
+ " A | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " 40 | \n",
+ " 13 | \n",
+ " 26 | \n",
+ " A | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " 50 | \n",
+ " 14 | \n",
+ " 28 | \n",
+ " A | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
"text/plain": [
- "[75, 75, 75, 75, 75, 75, 75, 75, 75, 75]"
+ " 0 1 2 symbols\n",
+ "0 10 10 20 A\n",
+ "1 20 11 22 A\n",
+ "2 30 12 24 A\n",
+ "3 40 13 26 A\n",
+ "4 50 14 28 A"
]
},
- "execution_count": 32,
+ "execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
- "[75]*10"
+ "rs[rs.symbols == 'A']"
]
},
{
@@ -365,7 +435,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.11.4"
+ "version": "3.11.6"
}
},
"nbformat": 4,
diff --git a/src/aiomql/core/backtester/get_data.py b/src/aiomql/core/backtester/get_data.py
index 2042619..39fa3eb 100644
--- a/src/aiomql/core/backtester/get_data.py
+++ b/src/aiomql/core/backtester/get_data.py
@@ -6,24 +6,21 @@ from logging import getLogger
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, TickInfo)
+from ...core.constants import TimeFrame, CopyTicks
+
from ...utils import backoff_decorator
logger = getLogger(__name__)
class Data(TypedDict):
- account: AccountInfo
- symbols: dict[str, SymbolInfo]
+ account: dict
+ symbols: dict[str, dict]
prices: dict[str, DataFrame]
ticks: dict[str, DataFrame]
rates: dict[str, dict[str, DataFrame]]
@@ -93,11 +90,11 @@ class GetData:
return data
- async def get_symbols_info(self) -> dict[str, SymbolInfo]:
+ async def get_symbols_info(self) -> dict[str, dict]:
""""""
tasks = [self.get_symbol_info(symbol) for symbol in self.symbols]
res = await asyncio.gather(*tasks)
- return {symbol: SymbolInfo(**info) for symbol, info in res}
+ return {symbol: info for symbol, info in res}
async def get_symbols_ticks(self) -> dict[str, DataFrame]:
""""""
diff --git a/src/aiomql/core/backtester/test_data.py b/src/aiomql/core/backtester/test_data.py
index 9bfa3a3..81d7a9e 100644
--- a/src/aiomql/core/backtester/test_data.py
+++ b/src/aiomql/core/backtester/test_data.py
@@ -1,11 +1,12 @@
from datetime import datetime, tzinfo
+from typing import Literal
import pytz
import numpy as np
import pandas as pd
from pandas import DataFrame
-from MetaTrader5 import Tick, SymbolInfo, AccountInfo, TradeOrder, TradePosition, TradeDeal
-import MetaTrader5
+from MetaTrader5 import (Tick, SymbolInfo, AccountInfo, TradeOrder, TradePosition, TradeDeal,
+ ORDER_TYPE_BUY, ORDER_TYPE_SELL)
from ..constants import TimeFrame, CopyTicks
from .get_data import Data, GetData
@@ -20,8 +21,8 @@ class TestData:
def __init__(self, data: Data):
self._data = data
- self.account = data['account']
- self.symbols = data['symbols']
+ self.account = AccountInfo(**data['account'])
+ self.symbols = {symbol: SymbolInfo(**info) for symbol, info in data['symbols'].items()}
self.prices = data['prices']
self.ticks = data['ticks']
self.rates = data['rates']
@@ -31,8 +32,7 @@ class TestData:
self.orders: dict[str, dict[int, TradeOrder]] = {}
self.open_orders: dict[int, TradeOrder] = {}
self.positions: dict[str, dict[int, TradePosition]] = {}
- self.open_positions = dict[int, TradePosition] = {}
- self.history_deals = dict[str, dict[int, TradeDeal]] = {}
+ self.open_positions: dict[int, TradePosition] = {}
def __next__(self):
self.cursor = next(self.iter)
@@ -49,7 +49,7 @@ class TestData:
return list(self.symbols.keys())
def get_account_info(self) -> AccountInfo:
- return AccountInfo(**self.account.dict)
+ return AccountInfo(**self.account._asdict())
def get_symbol_info_tick(self, symbol: str) -> Tick:
tick = self.prices[symbol].iloc[self.cursor]
@@ -58,7 +58,7 @@ class TestData:
def get_symbol_info(self, symbol: str) -> SymbolInfo:
info = self.symbols[symbol]
tick = self.get_symbol_info_tick(symbol)
- info = info.dict
+ info = info._asdict()
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)
@@ -103,26 +103,103 @@ class TestData:
end = int(datetime.timestamp(date_to)) if isinstance(date_to, datetime) else int(date_to)
end = ticks[ticks.index >= end].iloc[-1].index
return ticks.loc[start:end].to_numpy()
+
+ def order_calc_margin(self, action: Literal[0, 1], symbol: str, volume: float, price: float):
+ symbol_info = self.get_symbol_info(symbol)
+ margin_rate = symbol_info.margin_rate
+ margin = volume * price / margin_rate
+ return margin
+
+ def order_send(self, request: dict) -> dict:
+ ...
+
+ def order_check(self, request: dict) -> dict:
+ ...
def get_orders_total(self) -> int:
- return len(self.live_orders)
+ return len(self.open_orders)
def get_orders(self, symbol: str = '', group: str = '', ticket: int = None) -> tuple[TradeOrder, ...]:
if ticket:
- return self.live_orders.get(ticket, ())
+ order = self.open_orders.get(ticket)
+ return (order,) if order else ()
elif symbol:
- return tuple(order for order in self.orders.get(symbol, ()) if order.ticket in self.live_orders)
+ return tuple(order for order in self.orders.get(symbol, ()) if order.ticket in self.open_orders)
elif group:
- return tuple(self.live_orders.values())
+ return tuple(order for order in self.open_orders.values())
else:
- return tuple(self.live_orders.values())
+ return tuple(order for order in self.open_orders.values())
+
+ def get_positions_total(self):
+ return len(self.open_positions)
+
+ def get_positions(self, symbol: str = '', group: str = '', ticket: int = None) -> tuple[TradePosition, ...]:
+ if ticket:
+ position = self.open_positions.get(ticket)
+ return (position,) if position else ()
+
+ elif symbol:
+ return tuple(position for position in self.positions.get(symbol, ()) if position.ticket in self.open_positions)
+
+ elif group:
+ return tuple(position for position in self.open_positions.values())
+
+ else:
+ return tuple(position for position in self.open_positions.values())
- def history_orders_total(self, date_from: datetime | float, date_to: datetime | float):
- start =
+ def history_orders_total(self, date_from: datetime | float, date_to: datetime | float) -> int:
+ start = int(date_from.timestamp()) if isinstance(date_from, datetime) else int(date_from)
+ end = int(date_to.timestamp()) if isinstance(date_to, datetime) else int(date_to)
+ start = self.history_orders[self.history_orders.index >= start].iloc[0].name
+ end = self.history_orders[self.history_orders.index <= end].iloc[-1].name
+ return self.history_orders.loc[start:end].shape[0]
+ def history_orders_get(self, date_from: datetime | float, date_to: datetime | float, group: str = '',
+ ticket: int = None, position: int = None) -> tuple[TradeOrder, ...]:
+ start = int(date_from.timestamp()) if isinstance(date_from, datetime) else int(date_from)
+ end = int(date_to.timestamp()) if isinstance(date_to, datetime) else int(date_to)
+ start = self.history_orders[self.history_orders.index >= start].iloc[0].name
+ end = self.history_orders[self.history_orders.index <= end].iloc[-1].name
+ orders = self.history_orders.loc[start:end]
+ if ticket:
+ orders = orders[orders.ticket == ticket]
+ elif position:
+ orders = orders[orders.position == position]
+ elif group:
+ ...
+
+ orders.drop(columns=['symbol'], inplace=True)
+ return tuple(TradeOrder(**order) for order in orders.to_dict(orient='records'))
+
+ def get_history_deals_total(self, date_from: datetime | float, date_to: datetime | float) -> int:
+ start = int(date_from.timestamp()) if isinstance(date_from, datetime) else int(date_from)
+ end = int(date_to.timestamp()) if isinstance(date_to, datetime) else int(date_to)
+ start = self.history_deals[self.history_deals.index >= start].iloc[0].name
+ end = self.history_deals[self.history_deals.index <= end].iloc[-1].name
+ return self.history_deals.loc[start:end].shape[0]
+
+ def get_history_deals(self, date_from: datetime | float, date_to: datetime | float, group: str = '',
+ position: int = None, ticket: int = None) -> tuple[TradeDeal, ...]:
+ start = int(date_from.timestamp()) if isinstance(date_from, datetime) else int(date_from)
+ end = int(date_to.timestamp()) if isinstance(date_to, datetime) else int(date_to)
+ start = self.history_deals[self.history_deals.index >= start].iloc[0].name
+ end = self.history_deals[self.history_deals.index <= end].iloc[-1].name
+ deals = self.history_deals.loc[start:end]
+
+ if ticket:
+ deals = deals[deals.ticket == ticket]
+
+ elif position:
+ deals = deals[deals.position == position]
+
+ elif group:
+ ...
+
+ deals.drop(columns=['symbol'], inplace=True)
+ return tuple(TradeDeal(**deal) for deal in deals.to_dict(orient='records'))
diff --git a/src/aiomql/core/models.py b/src/aiomql/core/models.py
index 2ebd808..13226e7 100644
--- a/src/aiomql/core/models.py
+++ b/src/aiomql/core/models.py
@@ -334,9 +334,9 @@ class SymbolInfo(Base):
path: str
def __init__(self, **kwargs):
- if name := kwargs.pop('name', None) is None:
+ if (name := kwargs.pop('name', None)) is None:
raise AttributeError('Symbol Object Must be initialized with a name')
- self.name = name
+ self.name = name # type: str
super().__init__(**kwargs)
def __repr__(self):
diff --git a/src/aiomql/symbol.py b/src/aiomql/symbol.py
index e1fff22..ab445e4 100644
--- a/src/aiomql/symbol.py
+++ b/src/aiomql/symbol.py
@@ -48,7 +48,7 @@ class Symbol(SymbolInfo):
"""
return self.point * 10
- async def info_tick(self, *, name: str = "", retries=3) -> Tick:
+ async def info_tick(self, *, name: str = "") -> Tick:
"""Get the current price tick of a financial instrument.
Args:
@@ -60,16 +60,11 @@ class Symbol(SymbolInfo):
Raises:
ValueError: If request was unsuccessful and None was returned
"""
- if retries < 1:
- raise ValueError(f'Could not get tick for {name or self.name}. {self.mt5.error}')
tick = await self.mt5.symbol_info_tick(name or self.name)
if tick is not None:
tick = Tick(**tick._asdict())
setattr(self, 'tick', tick) if not name else ...
return tick
- if self.mt5.error.is_connection_error():
- await asyncio.sleep(retries)
- return await self.info_tick(name=name, retries=retries - 1)
raise ValueError(f'Could not get tick for {name or self.name}. {self.mt5.error}')
async def symbol_select(self, *, enable: bool = True) -> bool: