From a62b24ac1cb3ead25d05c19a9f0c0a1d9638bb80 Mon Sep 17 00:00:00 2001 From: Ichinga Samuel Date: Thu, 29 Aug 2024 06:36:09 +0100 Subject: [PATCH] testdata --- Untitled.ipynb | 166 +++++++++--------------- Untitled1.ipynb | 82 +++++++++++- src/aiomql/core/backtester/get_data.py | 15 +-- src/aiomql/core/backtester/test_data.py | 107 ++++++++++++--- src/aiomql/core/models.py | 4 +- src/aiomql/symbol.py | 7 +- 6 files changed, 238 insertions(+), 143 deletions(-) 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", - " \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 " + "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", + " \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", + "
012symbols
0101020A
1201122A
2301224A
3401326A
4501428A
\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: