This commit is contained in:
Ichinga Samuel
2024-08-29 06:36:09 +01:00
parent 24eec96fae
commit a62b24ac1c
6 changed files with 238 additions and 143 deletions
+61 -105
View File
@@ -2,7 +2,7 @@
"cells": [ "cells": [
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": 1,
"id": "f4500c8d-0e58-4d3f-8dd3-06a4896f397f", "id": "f4500c8d-0e58-4d3f-8dd3-06a4896f397f",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
@@ -41,9 +41,52 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 19, "execution_count": 3,
"id": "2c598a85-1e90-49b0-abf1-87329597feae", "id": "2c598a85-1e90-49b0-abf1-87329597feae",
"metadata": {}, "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": [ "outputs": [
{ {
"data": { "data": {
@@ -51,136 +94,49 @@
"4" "4"
] ]
}, },
"execution_count": 19, "execution_count": 17,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
], ],
"source": [ "source": [
"tz = pytz.timezone('Etc/UTC')\n", "sy = await sym.mt5.symbol_info('Volatility 25 Index')\n",
"sym = Symbol(name='EURUSD')\n", "await sym.mt5.symbol_select('Volatility 25 Index', enable=True)\n",
"start = datetime(day=22, month=8, year=2024, tzinfo=tz)\n", "sy.trade_calc_mode"
"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", "cell_type": "code",
"execution_count": 30, "execution_count": 12,
"id": "0a7d319c-5760-4058-994f-27e8d10df108", "id": "9307e3df-a5f3-42cf-9d3d-e65e5254c0e5",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"data": { "data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>time</th>\n",
" <th>open</th>\n",
" <th>high</th>\n",
" <th>low</th>\n",
" <th>close</th>\n",
" <th>tick_volume</th>\n",
" <th>spread</th>\n",
" <th>real_volume</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>2024-08-25 12:00:00</td>\n",
" <td>1.11869</td>\n",
" <td>1.11947</td>\n",
" <td>1.11849</td>\n",
" <td>1.11894</td>\n",
" <td>4570</td>\n",
" <td>1</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>2024-08-26 00:00:00</td>\n",
" <td>1.11894</td>\n",
" <td>1.12016</td>\n",
" <td>1.11628</td>\n",
" <td>1.11652</td>\n",
" <td>43583</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>2024-08-26 12:00:00</td>\n",
" <td>1.11652</td>\n",
" <td>1.11790</td>\n",
" <td>1.11501</td>\n",
" <td>1.11618</td>\n",
" <td>45034</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [ "text/plain": [
" time open high low close tick_volume \\\n", "0.50097375"
"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, "execution_count": 12,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
], ],
"source": [ "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", "cell_type": "code",
"execution_count": null, "execution_count": null,
"id": "9307e3df-a5f3-42cf-9d3d-e65e5254c0e5", "id": "e8413f70-244f-47a2-97b3-2be6fcd87589",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "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)"
]
}, },
{ {
"cell_type": "code", "cell_type": "code",
@@ -658,7 +614,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.11.4" "version": "3.11.6"
} }
}, },
"nbformat": 4, "nbformat": 4,
+76 -6
View File
@@ -14,7 +14,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 19, "execution_count": 2,
"id": "8d39819f-2cac-437f-b5fc-633ca7443f8a", "id": "8d39819f-2cac-437f-b5fc-633ca7443f8a",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
@@ -24,23 +24,93 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 32, "execution_count": 6,
"id": "d7976bb8-05cb-4924-a6e2-90ea8af85d9d", "id": "d7976bb8-05cb-4924-a6e2-90ea8af85d9d",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"data": { "data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>0</th>\n",
" <th>1</th>\n",
" <th>2</th>\n",
" <th>symbols</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>10</td>\n",
" <td>10</td>\n",
" <td>20</td>\n",
" <td>A</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>20</td>\n",
" <td>11</td>\n",
" <td>22</td>\n",
" <td>A</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>30</td>\n",
" <td>12</td>\n",
" <td>24</td>\n",
" <td>A</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>40</td>\n",
" <td>13</td>\n",
" <td>26</td>\n",
" <td>A</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>50</td>\n",
" <td>14</td>\n",
" <td>28</td>\n",
" <td>A</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [ "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": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
], ],
"source": [ "source": [
"[75]*10" "rs[rs.symbols == 'A']"
] ]
}, },
{ {
@@ -365,7 +435,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.11.4" "version": "3.11.6"
} }
}, },
"nbformat": 4, "nbformat": 4,
+6 -9
View File
@@ -6,24 +6,21 @@ from logging import getLogger
import asyncio import asyncio
import pytz import pytz
from MetaTrader5 import Tick, SymbolInfo
import pandas as pd import pandas as pd
from pandas import DataFrame from pandas import DataFrame
from ...core.meta_trader import MetaTrader from ...core.meta_trader import MetaTrader
from ...core.config import Config from ...core.config import Config
from ...core.errors import Error from ...core.constants import TimeFrame, CopyTicks
from ...core.constants import TimeFrame, CopyTicks, OrderType
from ...core.models import (AccountInfo, SymbolInfo, BookInfo, TradeOrder, OrderCheckResult, OrderSendResult,
TradePosition, TradeDeal, TickInfo)
from ...utils import backoff_decorator from ...utils import backoff_decorator
logger = getLogger(__name__) logger = getLogger(__name__)
class Data(TypedDict): class Data(TypedDict):
account: AccountInfo account: dict
symbols: dict[str, SymbolInfo] symbols: dict[str, dict]
prices: dict[str, DataFrame] prices: dict[str, DataFrame]
ticks: dict[str, DataFrame] ticks: dict[str, DataFrame]
rates: dict[str, dict[str, DataFrame]] rates: dict[str, dict[str, DataFrame]]
@@ -93,11 +90,11 @@ class GetData:
return data 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] tasks = [self.get_symbol_info(symbol) for symbol in self.symbols]
res = await asyncio.gather(*tasks) 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]: async def get_symbols_ticks(self) -> dict[str, DataFrame]:
"""""" """"""
+92 -15
View File
@@ -1,11 +1,12 @@
from datetime import datetime, tzinfo from datetime import datetime, tzinfo
from typing import Literal
import pytz import pytz
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from pandas import DataFrame from pandas import DataFrame
from MetaTrader5 import Tick, SymbolInfo, AccountInfo, TradeOrder, TradePosition, TradeDeal from MetaTrader5 import (Tick, SymbolInfo, AccountInfo, TradeOrder, TradePosition, TradeDeal,
import MetaTrader5 ORDER_TYPE_BUY, ORDER_TYPE_SELL)
from ..constants import TimeFrame, CopyTicks from ..constants import TimeFrame, CopyTicks
from .get_data import Data, GetData from .get_data import Data, GetData
@@ -20,8 +21,8 @@ class TestData:
def __init__(self, data: Data): def __init__(self, data: Data):
self._data = data self._data = data
self.account = data['account'] self.account = AccountInfo(**data['account'])
self.symbols = data['symbols'] self.symbols = {symbol: SymbolInfo(**info) for symbol, info in data['symbols'].items()}
self.prices = data['prices'] self.prices = data['prices']
self.ticks = data['ticks'] self.ticks = data['ticks']
self.rates = data['rates'] self.rates = data['rates']
@@ -31,8 +32,7 @@ class TestData:
self.orders: dict[str, dict[int, TradeOrder]] = {} self.orders: dict[str, dict[int, TradeOrder]] = {}
self.open_orders: dict[int, TradeOrder] = {} self.open_orders: dict[int, TradeOrder] = {}
self.positions: dict[str, dict[int, TradePosition]] = {} self.positions: dict[str, dict[int, TradePosition]] = {}
self.open_positions = dict[int, TradePosition] = {} self.open_positions: dict[int, TradePosition] = {}
self.history_deals = dict[str, dict[int, TradeDeal]] = {}
def __next__(self): def __next__(self):
self.cursor = next(self.iter) self.cursor = next(self.iter)
@@ -49,7 +49,7 @@ class TestData:
return list(self.symbols.keys()) return list(self.symbols.keys())
def get_account_info(self) -> AccountInfo: 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: def get_symbol_info_tick(self, symbol: str) -> Tick:
tick = self.prices[symbol].iloc[self.cursor] tick = self.prices[symbol].iloc[self.cursor]
@@ -58,7 +58,7 @@ class TestData:
def get_symbol_info(self, symbol: str) -> SymbolInfo: def get_symbol_info(self, symbol: str) -> SymbolInfo:
info = self.symbols[symbol] info = self.symbols[symbol]
tick = self.get_symbol_info_tick(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, 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} 'askhigh': tick.ask, 'asklow': tick.bid, 'last': tick.last, 'volume_real': tick.volume_real}
return SymbolInfo(**info) 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 = int(datetime.timestamp(date_to)) if isinstance(date_to, datetime) else int(date_to)
end = ticks[ticks.index >= end].iloc[-1].index end = ticks[ticks.index >= end].iloc[-1].index
return ticks.loc[start:end].to_numpy() 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: 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, ...]: def get_orders(self, symbol: str = '', group: str = '', ticket: int = None) -> tuple[TradeOrder, ...]:
if ticket: if ticket:
return self.live_orders.get(ticket, ()) order = self.open_orders.get(ticket)
return (order,) if order else ()
elif symbol: 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: elif group:
return tuple(self.live_orders.values()) return tuple(order for order in self.open_orders.values())
else: 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): def history_orders_total(self, date_from: datetime | float, date_to: datetime | float) -> int:
start = 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'))
+2 -2
View File
@@ -334,9 +334,9 @@ class SymbolInfo(Base):
path: str path: str
def __init__(self, **kwargs): 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') raise AttributeError('Symbol Object Must be initialized with a name')
self.name = name self.name = name # type: str
super().__init__(**kwargs) super().__init__(**kwargs)
def __repr__(self): def __repr__(self):
+1 -6
View File
@@ -48,7 +48,7 @@ class Symbol(SymbolInfo):
""" """
return self.point * 10 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. """Get the current price tick of a financial instrument.
Args: Args:
@@ -60,16 +60,11 @@ class Symbol(SymbolInfo):
Raises: Raises:
ValueError: If request was unsuccessful and None was returned 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) tick = await self.mt5.symbol_info_tick(name or self.name)
if tick is not None: if tick is not None:
tick = Tick(**tick._asdict()) tick = Tick(**tick._asdict())
setattr(self, 'tick', tick) if not name else ... setattr(self, 'tick', tick) if not name else ...
return tick 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}') raise ValueError(f'Could not get tick for {name or self.name}. {self.mt5.error}')
async def symbol_select(self, *, enable: bool = True) -> bool: async def symbol_select(self, *, enable: bool = True) -> bool: