bbstrader 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of bbstrader might be problematic. Click here for more details.
- bbstrader/__ini__.py +17 -0
- bbstrader/btengine/__init__.py +50 -0
- bbstrader/btengine/backtest.py +900 -0
- bbstrader/btengine/data.py +374 -0
- bbstrader/btengine/event.py +201 -0
- bbstrader/btengine/execution.py +83 -0
- bbstrader/btengine/performance.py +309 -0
- bbstrader/btengine/portfolio.py +326 -0
- bbstrader/btengine/strategy.py +31 -0
- bbstrader/metatrader/__init__.py +6 -0
- bbstrader/metatrader/account.py +1038 -0
- bbstrader/metatrader/rates.py +226 -0
- bbstrader/metatrader/risk.py +626 -0
- bbstrader/metatrader/trade.py +1296 -0
- bbstrader/metatrader/utils.py +669 -0
- bbstrader/models/__init__.py +6 -0
- bbstrader/models/risk.py +349 -0
- bbstrader/strategies.py +681 -0
- bbstrader/trading/__init__.py +4 -0
- bbstrader/trading/execution.py +965 -0
- bbstrader/trading/run.py +131 -0
- bbstrader/trading/utils.py +153 -0
- bbstrader/tseries.py +592 -0
- bbstrader-0.0.1.dist-info/LICENSE +21 -0
- bbstrader-0.0.1.dist-info/METADATA +132 -0
- bbstrader-0.0.1.dist-info/RECORD +28 -0
- bbstrader-0.0.1.dist-info/WHEEL +5 -0
- bbstrader-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1038 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import os
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import urllib.request
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
import MetaTrader5 as mt5
|
|
7
|
+
from currency_converter import SINGLE_DAY_ECB_URL, CurrencyConverter
|
|
8
|
+
from bbstrader.metatrader.utils import (
|
|
9
|
+
raise_mt5_error, AccountInfo, TerminalInfo,
|
|
10
|
+
SymbolInfo, TickInfo, TradeRequest, OrderCheckResult,
|
|
11
|
+
OrderSentResult, TradePosition, TradeOrder, TradeDeal,
|
|
12
|
+
)
|
|
13
|
+
from typing import Tuple, Union, List, Dict, Any, Optional, Literal
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__BROKERS__ = {
|
|
17
|
+
'AMG': "Admirals Group AS",
|
|
18
|
+
'JGM': "Just Global Markets Ltd.",
|
|
19
|
+
'FTMO': "FTMO S.R.O."
|
|
20
|
+
}
|
|
21
|
+
_ADMIRAL_MARKETS_URL_ = "https://cabinet.a-partnership.com/visit/?bta=35537&brand=admiralmarkets"
|
|
22
|
+
_ADMIRAL_MARKETS_PRODUCTS_ = ["Stocks", "ETFs",
|
|
23
|
+
"Indices", "Commodities", "Futures", "Forex"]
|
|
24
|
+
_JUST_MARKETS_URL_ = "https://one.justmarkets.link/a/tufvj0xugm/registration/trader"
|
|
25
|
+
_JUST_MARKETS_PRODUCTS_ = ["Stocks", "Crypto", "indices", "Commodities", "Forex"]
|
|
26
|
+
_FTMO_URL_ = "https://trader.ftmo.com/?affiliates=JGmeuQqepAZLMcdOEQRp"
|
|
27
|
+
|
|
28
|
+
INIT_MSG = (
|
|
29
|
+
f"\n* Ensure you have a good and stable internet connexion\n"
|
|
30
|
+
f"* Ensure you have an activete MT5 terminal install on your machine\n"
|
|
31
|
+
f"* Ensure you have an active MT5 Account with {'or '.join(__BROKERS__.values())}\n"
|
|
32
|
+
f"* If you want to trade {', '.join(_ADMIRAL_MARKETS_PRODUCTS_)}, See {_ADMIRAL_MARKETS_URL_}\n"
|
|
33
|
+
f"* If you want to trade {', '.join(_JUST_MARKETS_PRODUCTS_)}, See {_JUST_MARKETS_URL_}\n"
|
|
34
|
+
f"* If you are looking for a prop firm, See {_FTMO_URL_}\n"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
amg_url = _ADMIRAL_MARKETS_URL_
|
|
38
|
+
jgm_url = _JUST_MARKETS_URL_
|
|
39
|
+
ftmo_url = _FTMO_URL_
|
|
40
|
+
|
|
41
|
+
_SYMBOLS_TYPE_ = {
|
|
42
|
+
"STK": r'\b(Stocks?|Equities?|Shares?)\b',
|
|
43
|
+
"ETF": r'\b(ETFs?)\b',
|
|
44
|
+
"IDX": r'\b(?:Indices?|Cash)\b(?!.*\\(?:UKOIL|USOIL))',
|
|
45
|
+
"FX": r'\b(Forex|Exotics?)\b',
|
|
46
|
+
"COMD": r'\b(Metals?|Agricultures?|Energies?|OIL|USOIL|UKOIL)\b',
|
|
47
|
+
"FUT": r'\b(Futures?)\b',
|
|
48
|
+
"CRYPTO": r'\b(Cryptos?)\b'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
class Account(object):
|
|
52
|
+
"""
|
|
53
|
+
The `Account` class is utilized to retrieve information about
|
|
54
|
+
the current trading account or a specific account.
|
|
55
|
+
It enables interaction with the MT5 terminal to manage account details,
|
|
56
|
+
including account informations, terminal status, financial instrument details,
|
|
57
|
+
active orders, open positions, and trading history.
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
>>> # Instantiating the Account class
|
|
61
|
+
>>> account = Account()
|
|
62
|
+
|
|
63
|
+
>>> # Getting account information
|
|
64
|
+
>>> account_info = account.get_account_info()
|
|
65
|
+
|
|
66
|
+
>>> # Printing account information
|
|
67
|
+
>>> account.print_account_info()
|
|
68
|
+
|
|
69
|
+
>>> # Getting terminal information
|
|
70
|
+
>>> terminal_info = account.get_terminal_info()
|
|
71
|
+
|
|
72
|
+
>>> # Retrieving and printing symbol information
|
|
73
|
+
>>> symbol_info = account.show_symbol_info('EURUSD')
|
|
74
|
+
|
|
75
|
+
>>> # Getting active orders
|
|
76
|
+
>>> orders = account.get_orders()
|
|
77
|
+
|
|
78
|
+
>>> # Fetching open positions
|
|
79
|
+
>>> positions = account.get_positions()
|
|
80
|
+
|
|
81
|
+
>>> # Accessing trade history
|
|
82
|
+
>>> from_date = datetime(2020, 1, 1)
|
|
83
|
+
>>> to_date = datetime.now()
|
|
84
|
+
>>> trade_history = account.get_trade_history(from_date, to_date)
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def __init__(self):
|
|
88
|
+
if not mt5.initialize():
|
|
89
|
+
raise_mt5_error(message=INIT_MSG)
|
|
90
|
+
self._check_brokers()
|
|
91
|
+
|
|
92
|
+
def _check_brokers(self):
|
|
93
|
+
supported = __BROKERS__.copy()
|
|
94
|
+
broker = self.get_terminal_info().company
|
|
95
|
+
if broker not in supported.values():
|
|
96
|
+
raise ValueError(
|
|
97
|
+
f"{broker} is not currently supported broker for the Account() class\n"
|
|
98
|
+
f"Currently Supported brokers are: {', '.join(supported.values())}\n"
|
|
99
|
+
f"For {supported['AMG']}, See {amg_url}\n"
|
|
100
|
+
f"For {supported['JGM']}, See {jgm_url}\n"
|
|
101
|
+
f"For {supported['FTMO']}, See {ftmo_url}\n"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def get_account_info(
|
|
105
|
+
self,
|
|
106
|
+
account: Optional[int] = None,
|
|
107
|
+
password: Optional[str] = None,
|
|
108
|
+
server: Optional[str] = None,
|
|
109
|
+
timeout: Optional[int] = 60_000
|
|
110
|
+
) -> Union[AccountInfo, None]:
|
|
111
|
+
"""
|
|
112
|
+
Get info on the current trading account or a specific account .
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
account (int, optinal) : MT5 Trading account number.
|
|
116
|
+
password (str, optinal): MT5 Trading account password.
|
|
117
|
+
|
|
118
|
+
server (str, optinal): MT5 Trading account server
|
|
119
|
+
[Brokers or terminal server ["demo", "real"]]
|
|
120
|
+
If no server is set, the last used server is applied automaticall
|
|
121
|
+
|
|
122
|
+
timeout (int, optinal):
|
|
123
|
+
Connection timeout in milliseconds. Optional named parameter.
|
|
124
|
+
If not specified, the value of 60 000 (60 seconds) is applied.
|
|
125
|
+
If the connection is not established within the specified time,
|
|
126
|
+
the call is forcibly terminated and the exception is generated.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
- AccountInfo in the form of a Namedtuple structure.
|
|
130
|
+
- None in case of an error
|
|
131
|
+
|
|
132
|
+
Raises:
|
|
133
|
+
MT5TerminalError: A specific exception based on the error code.
|
|
134
|
+
"""
|
|
135
|
+
# connect to the trade account specifying a password and a server
|
|
136
|
+
if (
|
|
137
|
+
account is not None and
|
|
138
|
+
password is not None and
|
|
139
|
+
server is not None
|
|
140
|
+
):
|
|
141
|
+
try:
|
|
142
|
+
authorized = mt5.login(
|
|
143
|
+
account, password=password, server=server, timeout=timeout)
|
|
144
|
+
if not authorized:
|
|
145
|
+
raise_mt5_error(
|
|
146
|
+
message=f"Failed to connect to account #{account}")
|
|
147
|
+
else:
|
|
148
|
+
info = mt5.account_info()
|
|
149
|
+
if info is None:
|
|
150
|
+
return None
|
|
151
|
+
else:
|
|
152
|
+
return AccountInfo(**info._asdict())
|
|
153
|
+
except Exception as e:
|
|
154
|
+
raise_mt5_error(e)
|
|
155
|
+
else:
|
|
156
|
+
try:
|
|
157
|
+
info = mt5.account_info()
|
|
158
|
+
if info is None:
|
|
159
|
+
return None
|
|
160
|
+
else:
|
|
161
|
+
return AccountInfo(**info._asdict())
|
|
162
|
+
except Exception as e:
|
|
163
|
+
raise_mt5_error(e)
|
|
164
|
+
|
|
165
|
+
def _show_info(self, info_getter, info_name, symbol=None):
|
|
166
|
+
"""
|
|
167
|
+
Generic function to retrieve and print information.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
info_getter (callable): Function to retrieve the information.
|
|
171
|
+
info_name (str): Name of the information being retrieved.
|
|
172
|
+
symbol (str, optional): Symbol name, required for some info types.
|
|
173
|
+
Defaults to None.
|
|
174
|
+
|
|
175
|
+
Raises:
|
|
176
|
+
MT5TerminalError: A specific exception based on the error code.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
# Call the provided info retrieval function
|
|
180
|
+
if symbol is not None:
|
|
181
|
+
info = info_getter(symbol)
|
|
182
|
+
else:
|
|
183
|
+
info = info_getter()
|
|
184
|
+
|
|
185
|
+
if info is not None:
|
|
186
|
+
info_dict = info._asdict()
|
|
187
|
+
df = pd.DataFrame(list(info_dict.items()),
|
|
188
|
+
columns=['PROPERTY', 'VALUE'])
|
|
189
|
+
|
|
190
|
+
# Construct the print message based on whether a symbol is provided
|
|
191
|
+
if symbol:
|
|
192
|
+
print(
|
|
193
|
+
f"\n{info_name.upper()} INFO FOR {symbol} ({info.description})")
|
|
194
|
+
else:
|
|
195
|
+
print(f"\n{info_name.upper()} INFORMATIONS:")
|
|
196
|
+
|
|
197
|
+
pd.set_option('display.max_rows', None)
|
|
198
|
+
pd.set_option('display.max_columns', None)
|
|
199
|
+
print(df.to_string())
|
|
200
|
+
else:
|
|
201
|
+
if symbol:
|
|
202
|
+
msg = self._symbol_info_msg(symbol)
|
|
203
|
+
raise_mt5_error(message=msg)
|
|
204
|
+
else:
|
|
205
|
+
raise_mt5_error()
|
|
206
|
+
|
|
207
|
+
def show_account_info(self):
|
|
208
|
+
""" Helper function to print account info"""
|
|
209
|
+
self._show_info(self.get_account_info, "account")
|
|
210
|
+
|
|
211
|
+
def get_terminal_info(self, show=False) -> Union[TerminalInfo, None]:
|
|
212
|
+
"""
|
|
213
|
+
Get the connected MetaTrader 5 client terminal status and settings.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
show (bool): If True the Account information will be printed
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
- TerminalInfo in the form of NamedTuple Structure.
|
|
220
|
+
- None in case of an error
|
|
221
|
+
|
|
222
|
+
Raises:
|
|
223
|
+
MT5TerminalError: A specific exception based on the error code.
|
|
224
|
+
"""
|
|
225
|
+
try:
|
|
226
|
+
terminal_info = mt5.terminal_info()
|
|
227
|
+
if terminal_info is None:
|
|
228
|
+
return None
|
|
229
|
+
except Exception as e:
|
|
230
|
+
raise_mt5_error(e)
|
|
231
|
+
|
|
232
|
+
terminal_info_dict = terminal_info._asdict()
|
|
233
|
+
# convert the dictionary into DataFrame and print
|
|
234
|
+
df = pd.DataFrame(list(terminal_info_dict.items()),
|
|
235
|
+
columns=['PROPERTY', 'VALUE'])
|
|
236
|
+
if show:
|
|
237
|
+
pd.set_option('display.max_rows', None)
|
|
238
|
+
pd.set_option('display.max_columns', None)
|
|
239
|
+
print(df.to_string())
|
|
240
|
+
return TerminalInfo(**terminal_info_dict)
|
|
241
|
+
|
|
242
|
+
def convert_currencies(self, qty: float, from_c: str, to_c: str) -> float:
|
|
243
|
+
"""Convert amount from a currency to another one.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
qty (float): The amount of `currency` to convert.
|
|
247
|
+
from_c (str): The currency to convert from.
|
|
248
|
+
to_c (str): The currency to convert to.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
- The value of `qty` in converted in `to_c`.
|
|
252
|
+
|
|
253
|
+
Notes:
|
|
254
|
+
If `from_c` or `to_co` are not supported, the `qty` will be return;
|
|
255
|
+
check "https://www.ecb.europa.eu/stats/eurofxref/eurofxref.zip"
|
|
256
|
+
for supported currencies or you can take a look at the `CurrencyConverter` project
|
|
257
|
+
on Github https://github.com/alexprengere/currencyconverter .
|
|
258
|
+
"""
|
|
259
|
+
filename = f"ecb_{datetime.now():%Y%m%d}.zip"
|
|
260
|
+
if not os.path.isfile(filename):
|
|
261
|
+
urllib.request.urlretrieve(SINGLE_DAY_ECB_URL, filename)
|
|
262
|
+
c = CurrencyConverter(filename)
|
|
263
|
+
os.remove(filename)
|
|
264
|
+
supported = c.currencies
|
|
265
|
+
if (from_c not in supported or
|
|
266
|
+
to_c not in supported
|
|
267
|
+
):
|
|
268
|
+
rate = qty
|
|
269
|
+
else:
|
|
270
|
+
rate = c.convert(amount=qty, currency=from_c, new_currency=to_c)
|
|
271
|
+
return rate
|
|
272
|
+
|
|
273
|
+
def get_currency_rates(self, symbol: str) -> Dict[str, str]:
|
|
274
|
+
"""
|
|
275
|
+
Args:
|
|
276
|
+
symbol (str): The symbol for which to get currencies
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
- `base currency` (bc)
|
|
280
|
+
- `margin currency` (mc)
|
|
281
|
+
- `profit currency` (pc)
|
|
282
|
+
- `account currency` (ac)
|
|
283
|
+
|
|
284
|
+
Exemple:
|
|
285
|
+
>>> account = Account()
|
|
286
|
+
>>> account.get_rates('EURUSD')
|
|
287
|
+
{'bc': 'EUR', 'mc': 'EUR', 'pc': 'USD', 'ac': 'USD'}
|
|
288
|
+
"""
|
|
289
|
+
info = self.get_symbol_info(symbol)
|
|
290
|
+
bc = info.currency_base
|
|
291
|
+
pc = info.currency_profit
|
|
292
|
+
mc = info.currency_margin
|
|
293
|
+
ac = self.get_account_info().currency
|
|
294
|
+
return {'bc': bc, 'mc': mc, 'pc': pc, 'ac': ac}
|
|
295
|
+
|
|
296
|
+
def get_symbols(self,
|
|
297
|
+
symbol_type="ALL",
|
|
298
|
+
check_etf=False,
|
|
299
|
+
save=False,
|
|
300
|
+
file_name="symbols",
|
|
301
|
+
include_desc=False,
|
|
302
|
+
display_total=False
|
|
303
|
+
) -> List[str]:
|
|
304
|
+
"""
|
|
305
|
+
Get all specified financial instruments from the MetaTrader 5 terminal.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
symbol_type (str) The category of instrument to get
|
|
309
|
+
- `ALL`: For all available symbols
|
|
310
|
+
- `STK`: Stocks (e.g., 'GOOGL')
|
|
311
|
+
- `ETF`: ETFs (e.g., 'QQQ')
|
|
312
|
+
- `IDX`: Indices (e.g., 'SP500')
|
|
313
|
+
- `FX`: Forex pairs (e.g., 'EURUSD')
|
|
314
|
+
- `COMD`: Commodities (e.g., 'CRUDOIL', 'GOLD')
|
|
315
|
+
- `FUT`: Futures (e.g., 'USTNote_U4'),
|
|
316
|
+
- `CRYPTO`: Cryptocurrencies (e.g., 'BTC', 'ETH')
|
|
317
|
+
|
|
318
|
+
check_etf (bool): If True and symbol_type is 'etf', check if the
|
|
319
|
+
ETF description contains 'ETF'.
|
|
320
|
+
|
|
321
|
+
save (bool): If True, save the symbols to a file.
|
|
322
|
+
|
|
323
|
+
file_name (str): The name of the file to save the symbols to
|
|
324
|
+
(without the extension).
|
|
325
|
+
|
|
326
|
+
include_desc (bool): If True, include the symbol's description
|
|
327
|
+
in the output and saved file.
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
list: A list of symbols.
|
|
331
|
+
|
|
332
|
+
Raises:
|
|
333
|
+
Exception: If there is an error connecting to MT5 or retrieving symbols.
|
|
334
|
+
"""
|
|
335
|
+
symbols = mt5.symbols_get()
|
|
336
|
+
if not symbols:
|
|
337
|
+
raise_mt5_error()
|
|
338
|
+
|
|
339
|
+
symbol_list = []
|
|
340
|
+
patterns = _SYMBOLS_TYPE_
|
|
341
|
+
|
|
342
|
+
if symbol_type != 'ALL':
|
|
343
|
+
if symbol_type not in patterns:
|
|
344
|
+
raise ValueError(f"Unsupported symbol type: {symbol_type}")
|
|
345
|
+
|
|
346
|
+
if save:
|
|
347
|
+
max_lengh = max([len(s.name) for s in symbols])
|
|
348
|
+
file_path = f"{file_name}.txt"
|
|
349
|
+
with open(file_path, mode='w', encoding='utf-8') as file:
|
|
350
|
+
for s in symbols:
|
|
351
|
+
info = self.get_symbol_info(s.name)
|
|
352
|
+
if symbol_type == 'ALL':
|
|
353
|
+
self._write_symbol(file, info, include_desc, max_lengh)
|
|
354
|
+
symbol_list.append(s.name)
|
|
355
|
+
else:
|
|
356
|
+
pattern = re.compile(patterns[symbol_type])
|
|
357
|
+
match = re.search(pattern, info.path)
|
|
358
|
+
if match:
|
|
359
|
+
if symbol_type == "ETF" and check_etf and "ETF" not in info.description:
|
|
360
|
+
raise ValueError(
|
|
361
|
+
f"{info.name} doesn't have 'ETF' in its description. "
|
|
362
|
+
"If this is intended, set check_etf=False."
|
|
363
|
+
)
|
|
364
|
+
self._write_symbol(
|
|
365
|
+
file, info, include_desc, max_lengh)
|
|
366
|
+
symbol_list.append(s.name)
|
|
367
|
+
|
|
368
|
+
else: # If not saving to a file, just process the symbols
|
|
369
|
+
for s in symbols:
|
|
370
|
+
info = self.get_symbol_info(s.name)
|
|
371
|
+
if symbol_type == 'ALL':
|
|
372
|
+
symbol_list.append(s.name)
|
|
373
|
+
else:
|
|
374
|
+
pattern = re.compile(
|
|
375
|
+
patterns[symbol_type]) # , re.IGNORECASE
|
|
376
|
+
match = re.search(pattern, info.path)
|
|
377
|
+
if match:
|
|
378
|
+
if symbol_type == "ETF" and check_etf and "ETF" not in info.description:
|
|
379
|
+
raise ValueError(
|
|
380
|
+
f"{info.name} doesn't have 'ETF' in its description. "
|
|
381
|
+
"If this is intended, set check_etf=False."
|
|
382
|
+
)
|
|
383
|
+
symbol_list.append(s.name)
|
|
384
|
+
|
|
385
|
+
# Print a summary of the retrieved symbols
|
|
386
|
+
if display_total:
|
|
387
|
+
names = {
|
|
388
|
+
"ALL": 'Symbols',
|
|
389
|
+
"STK": 'Stocks',
|
|
390
|
+
"ETF": 'ETFs',
|
|
391
|
+
"IDX": 'Indices',
|
|
392
|
+
"FX": 'Forex Paires',
|
|
393
|
+
"COMD": 'Commodities',
|
|
394
|
+
"FUT": 'Futures',
|
|
395
|
+
"CRYPTO": 'Cryptos Assets'
|
|
396
|
+
}
|
|
397
|
+
print(f"Total {names[symbol_type]}: {len(symbol_list)}")
|
|
398
|
+
|
|
399
|
+
return symbol_list
|
|
400
|
+
|
|
401
|
+
def _write_symbol(self, file, info, include_desc, max_lengh):
|
|
402
|
+
"""Helper function to write symbol information to a file."""
|
|
403
|
+
if include_desc:
|
|
404
|
+
space = " "*int(max_lengh-len(info.name))
|
|
405
|
+
file.write(info.name + space + '|' +
|
|
406
|
+
info.description + '\n')
|
|
407
|
+
else:
|
|
408
|
+
file.write(info.name + '\n')
|
|
409
|
+
|
|
410
|
+
def get_symbol_type(
|
|
411
|
+
self,
|
|
412
|
+
symbol: str
|
|
413
|
+
) -> Literal[
|
|
414
|
+
"STK", "ETF", "IDX", "FX", "COMD", "FUT", "CRYPTO", "unknown"]:
|
|
415
|
+
"""
|
|
416
|
+
Determines the type of a given financial instrument symbol.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
symbol (str): The symbol of the financial instrument (e.g., `GOOGL`, `EURUSD`).
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
Literal["STK", "ETF", "IDX", "FX", "COMD", "FUT", "CRYPTO", "unknown"]:
|
|
423
|
+
The type of the financial instrument, one of the following:
|
|
424
|
+
|
|
425
|
+
- `STK`: For Stocks (e.g., `GOOGL`)
|
|
426
|
+
- `ETF`: For ETFs (e.g., `QQQ`)
|
|
427
|
+
- `IDX`: For Indices (e.g., `SP500`)
|
|
428
|
+
- `FX` : For Forex pairs (e.g., `EURUSD`)
|
|
429
|
+
- `COMD`: For Commodities (e.g., `CRUDOIL`, `GOLD`)
|
|
430
|
+
- `FUT` : For Futures (e.g., `USTNote_U4`)
|
|
431
|
+
- `CRYPTO`: For Cryptocurrencies (e.g., `BTC`, `ETH`)
|
|
432
|
+
|
|
433
|
+
Returns `unknown` if the type cannot be determined.
|
|
434
|
+
"""
|
|
435
|
+
|
|
436
|
+
patterns = _SYMBOLS_TYPE_
|
|
437
|
+
info = self.get_symbol_info(symbol)
|
|
438
|
+
if info is not None:
|
|
439
|
+
for symbol_type, pattern in patterns.items():
|
|
440
|
+
match = re.search(pattern, info.path) # , re.IGNORECASE
|
|
441
|
+
if match:
|
|
442
|
+
return symbol_type
|
|
443
|
+
return "unknown"
|
|
444
|
+
|
|
445
|
+
def _get_symbols_by_category(self, symbol_type, category, category_map):
|
|
446
|
+
if category not in category_map:
|
|
447
|
+
raise ValueError(
|
|
448
|
+
f"Unsupported category: {category}. Choose from: {', '.join(category_map)}"
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
symbols = self.get_symbols(symbol_type=symbol_type)
|
|
452
|
+
pattern = re.compile(category_map[category], re.IGNORECASE)
|
|
453
|
+
|
|
454
|
+
symbol_list = []
|
|
455
|
+
for s in symbols:
|
|
456
|
+
info = self.get_symbol_info(s)
|
|
457
|
+
match = re.search(pattern, info.path)
|
|
458
|
+
if match:
|
|
459
|
+
symbol_list.append(s)
|
|
460
|
+
return symbol_list
|
|
461
|
+
|
|
462
|
+
def get_fx_symbols(
|
|
463
|
+
self,
|
|
464
|
+
category: Literal["majors", "minors", "exotics"] = 'majors'
|
|
465
|
+
) -> List[str]:
|
|
466
|
+
"""
|
|
467
|
+
Retrieves a list of forex symbols belonging to a specific category.
|
|
468
|
+
|
|
469
|
+
Args:
|
|
470
|
+
category (str, optional): The category of forex symbols to retrieve.
|
|
471
|
+
Possible values are 'majors', 'minors', 'exotics'.
|
|
472
|
+
Defaults to 'majors'.
|
|
473
|
+
|
|
474
|
+
Returns:
|
|
475
|
+
list: A list of forex symbol names matching the specified category.
|
|
476
|
+
|
|
477
|
+
Raises:
|
|
478
|
+
ValueError: If an unsupported category is provided.
|
|
479
|
+
|
|
480
|
+
Notes:
|
|
481
|
+
This mthods works primarly with Admirals Group AS products,
|
|
482
|
+
For other brokers use `get_symbols()`
|
|
483
|
+
"""
|
|
484
|
+
fx_categories = {
|
|
485
|
+
"majors": r"\b(Majors?)\b",
|
|
486
|
+
"minors": r"\b(Minors?)\b",
|
|
487
|
+
"exotics": r"\b(Exotics?)\b",
|
|
488
|
+
}
|
|
489
|
+
return self._get_symbols_by_category('forex', category, fx_categories)
|
|
490
|
+
|
|
491
|
+
def get_stocks_from(self, country_code: str = 'USA') -> List[str]:
|
|
492
|
+
"""
|
|
493
|
+
Retrieves a list of stock symbols from a specific country.
|
|
494
|
+
|
|
495
|
+
Supported countries are:
|
|
496
|
+
* **Australia:** AUS
|
|
497
|
+
* **Belgium:** BEL
|
|
498
|
+
* **Denmark:** DNK
|
|
499
|
+
* **Finland:** FIN
|
|
500
|
+
* **France:** FRA
|
|
501
|
+
* **Germany:** DEU
|
|
502
|
+
* **Netherlands:** NLD
|
|
503
|
+
* **Norway:** NOR
|
|
504
|
+
* **Portugal:** PRT
|
|
505
|
+
* **Spain:** ESP
|
|
506
|
+
* **Sweden:** SWE
|
|
507
|
+
* **United Kingdom:** GBR
|
|
508
|
+
* **United States:** USA
|
|
509
|
+
* **Switzerland:** CHE
|
|
510
|
+
|
|
511
|
+
Args:
|
|
512
|
+
country (str, optional): The country code of stocks to retrieve.
|
|
513
|
+
Defaults to 'USA'.
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
list: A list of stock symbol names from the specified country.
|
|
517
|
+
|
|
518
|
+
Raises:
|
|
519
|
+
ValueError: If an unsupported country is provided.
|
|
520
|
+
|
|
521
|
+
Notes:
|
|
522
|
+
This mthods works primarly with Admirals Group AS products,
|
|
523
|
+
For other brokers use `get_symbols()`
|
|
524
|
+
"""
|
|
525
|
+
country_map = {
|
|
526
|
+
"USA": r"\b(US)\b",
|
|
527
|
+
"AUS": r"\b(Australia)\b",
|
|
528
|
+
"BEL": r"\b(Belgium)\b",
|
|
529
|
+
"DNK": r"\b(Denmark)\b",
|
|
530
|
+
"FIN": r"\b(Finland)\b",
|
|
531
|
+
"FRA": r"\b(France)\b",
|
|
532
|
+
"DEU": r"\b(Germany)\b",
|
|
533
|
+
"NLD": r"\b(Netherlands)\b",
|
|
534
|
+
"NOR": r"\b(Norway)\b",
|
|
535
|
+
"PRT": r"\b(Portugal)\b",
|
|
536
|
+
"ESP": r"\b(Spain)\b",
|
|
537
|
+
"SWE": r"\b(Sweden)\b",
|
|
538
|
+
"GBR": r"\b(UK)\b",
|
|
539
|
+
"CHE": r"\b(Switzerland)\b",
|
|
540
|
+
}
|
|
541
|
+
return self._get_symbols_by_category('stocks', country_code, country_map)
|
|
542
|
+
|
|
543
|
+
def get_symbol_info(self, symbol: str) -> Union[SymbolInfo, None]:
|
|
544
|
+
"""Get symbol properties
|
|
545
|
+
|
|
546
|
+
Args:
|
|
547
|
+
symbol (str): Symbol name
|
|
548
|
+
|
|
549
|
+
Returns:
|
|
550
|
+
- AccountInfo in the form of a NamedTuple().
|
|
551
|
+
- None in case of an error.
|
|
552
|
+
|
|
553
|
+
Raises:
|
|
554
|
+
MT5TerminalError: A specific exception based on the error code.
|
|
555
|
+
"""
|
|
556
|
+
try:
|
|
557
|
+
symbol_info = mt5.symbol_info(symbol)
|
|
558
|
+
if symbol_info is None:
|
|
559
|
+
return None
|
|
560
|
+
else:
|
|
561
|
+
return SymbolInfo(**symbol_info._asdict())
|
|
562
|
+
except Exception as e:
|
|
563
|
+
msg = self._symbol_info_msg(symbol)
|
|
564
|
+
raise_mt5_error(message=f"{e+msg}")
|
|
565
|
+
|
|
566
|
+
def show_symbol_info(self, symbol: str):
|
|
567
|
+
"""
|
|
568
|
+
Print symbol properties
|
|
569
|
+
|
|
570
|
+
Args:
|
|
571
|
+
symbol (str): Symbol name
|
|
572
|
+
"""
|
|
573
|
+
self._show_info(self.get_symbol_info, "symbol", symbol=symbol)
|
|
574
|
+
|
|
575
|
+
def _symbol_info_msg(self, symbol):
|
|
576
|
+
return (
|
|
577
|
+
f"No history found for {symbol} in Market Watch.\n"
|
|
578
|
+
f"* Ensure {symbol} is selected and displayed in the Market Watch window.\n"
|
|
579
|
+
f"* See https://www.metatrader5.com/en/terminal/help/trading/market_watch\n"
|
|
580
|
+
f"* Ensure the symbol name is correct.\n"
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
def get_tick_info(self, symbol: str) -> Union[TickInfo, None]:
|
|
584
|
+
"""Get symbol tick properties
|
|
585
|
+
|
|
586
|
+
Args:
|
|
587
|
+
symbol (str): Symbol name
|
|
588
|
+
|
|
589
|
+
Returns:
|
|
590
|
+
- AccountInfo in the form of a NamedTuple().
|
|
591
|
+
- None in case of an error.
|
|
592
|
+
|
|
593
|
+
Raises:
|
|
594
|
+
MT5TerminalError: A specific exception based on the error code.
|
|
595
|
+
"""
|
|
596
|
+
try:
|
|
597
|
+
tick_info = mt5.symbol_info_tick(symbol)
|
|
598
|
+
if tick_info is None:
|
|
599
|
+
return None
|
|
600
|
+
else:
|
|
601
|
+
return TickInfo(**tick_info._asdict())
|
|
602
|
+
except Exception as e:
|
|
603
|
+
msg = self._symbol_info_msg(symbol)
|
|
604
|
+
raise_mt5_error(message=f"{e+msg}")
|
|
605
|
+
|
|
606
|
+
def show_tick_info(self, symbol: str):
|
|
607
|
+
"""
|
|
608
|
+
Print Tick properties
|
|
609
|
+
|
|
610
|
+
Args:
|
|
611
|
+
symbol (str): Symbol name
|
|
612
|
+
"""
|
|
613
|
+
self._show_info(self.get_tick_info, "tick", symbol=symbol)
|
|
614
|
+
|
|
615
|
+
def check_order(self,
|
|
616
|
+
request: Dict[str, Any]) -> OrderCheckResult:
|
|
617
|
+
"""
|
|
618
|
+
Check funds sufficiency for performing a required trading operation.
|
|
619
|
+
|
|
620
|
+
Args:
|
|
621
|
+
request (Dict[str, Any]): `TradeRequest` type structure describing the required trading action.
|
|
622
|
+
|
|
623
|
+
Returns:
|
|
624
|
+
OrderCheckResult:
|
|
625
|
+
The check result as the `OrderCheckResult` structure.
|
|
626
|
+
|
|
627
|
+
The `request` field in the returned structure contains the trading request passed to `check_order()`.
|
|
628
|
+
|
|
629
|
+
Raises:
|
|
630
|
+
MT5TerminalError: Raised if there is an error in the trading terminal based on the error code.
|
|
631
|
+
|
|
632
|
+
Notes:
|
|
633
|
+
Successful submission of a request does not guarantee that the requested trading
|
|
634
|
+
operation will be executed successfully.
|
|
635
|
+
"""
|
|
636
|
+
|
|
637
|
+
try:
|
|
638
|
+
result = mt5.order_check(request)
|
|
639
|
+
result_dict = result._asdict()
|
|
640
|
+
trade_request = TradeRequest(**result.request._asdict())
|
|
641
|
+
result_dict['request'] = trade_request
|
|
642
|
+
return OrderCheckResult(**result_dict)
|
|
643
|
+
except Exception as e:
|
|
644
|
+
raise_mt5_error(e)
|
|
645
|
+
|
|
646
|
+
def send_order(self,
|
|
647
|
+
request: Dict[str, Any]) -> OrderSentResult:
|
|
648
|
+
"""
|
|
649
|
+
Send a request to perform a trading operation from the terminal to the trade server.
|
|
650
|
+
|
|
651
|
+
Args:
|
|
652
|
+
request (Dict[str, Any]): `TradeRequest` type structure describing the required trading action.
|
|
653
|
+
|
|
654
|
+
Returns:
|
|
655
|
+
OrderSentResult:
|
|
656
|
+
The execution result as the `OrderSentResult` structure.
|
|
657
|
+
|
|
658
|
+
The `request` field in the returned structure contains the trading request passed to `send_order()`.
|
|
659
|
+
|
|
660
|
+
Raises:
|
|
661
|
+
MT5TerminalError: Raised if there is an error in the trading terminal based on the error code.
|
|
662
|
+
"""
|
|
663
|
+
try:
|
|
664
|
+
result = mt5.order_send(request)
|
|
665
|
+
result_dict = result._asdict()
|
|
666
|
+
trade_request = TradeRequest(**result.request._asdict())
|
|
667
|
+
result_dict['request'] = trade_request
|
|
668
|
+
return OrderSentResult(**result_dict)
|
|
669
|
+
except Exception as e:
|
|
670
|
+
raise_mt5_error(e)
|
|
671
|
+
|
|
672
|
+
def get_positions(self,
|
|
673
|
+
symbol: Optional[str] = None,
|
|
674
|
+
group: Optional[str] = None,
|
|
675
|
+
ticket: Optional[int] = None,
|
|
676
|
+
to_df: bool = False
|
|
677
|
+
) -> Union[pd.DataFrame, Tuple[TradePosition], None]:
|
|
678
|
+
"""
|
|
679
|
+
Get open positions with the ability to filter by symbol or ticket.
|
|
680
|
+
There are four call options:
|
|
681
|
+
|
|
682
|
+
- Call without parameters. Returns open positions for all symbols.
|
|
683
|
+
- Call specifying a symbol. Returns open positions for the specified symbol.
|
|
684
|
+
- Call specifying a group of symbols. Returns open positions for the specified group of symbols.
|
|
685
|
+
- Call specifying a position ticket. Returns the position corresponding to the specified ticket.
|
|
686
|
+
|
|
687
|
+
Args:
|
|
688
|
+
symbol (Optional[str]): Symbol name. Optional named parameter.
|
|
689
|
+
If a symbol is specified, the `ticket` parameter is ignored.
|
|
690
|
+
|
|
691
|
+
group (Optional[str]): The filter for arranging a group of necessary symbols.
|
|
692
|
+
Optional named parameter. If the group is specified,
|
|
693
|
+
the function returns only positions meeting specified criteria
|
|
694
|
+
for a symbol name.
|
|
695
|
+
|
|
696
|
+
ticket (Optional[int]): Position ticket. Optional named parameter.
|
|
697
|
+
A unique number assigned to each newly opened position.
|
|
698
|
+
It usually matches the ticket of the order used to open the position,
|
|
699
|
+
except when the ticket is changed as a result of service operations on the server,
|
|
700
|
+
for example, when charging swaps with position re-opening.
|
|
701
|
+
|
|
702
|
+
to_df (bool): If True, a DataFrame is returned.
|
|
703
|
+
|
|
704
|
+
Returns:
|
|
705
|
+
Union[pd.DataFrame, Tuple[TradePosition], None]:
|
|
706
|
+
- `TradePosition` in the form of a named tuple structure (namedtuple) or pd.DataFrame.
|
|
707
|
+
- `None` in case of an error.
|
|
708
|
+
|
|
709
|
+
Notes:
|
|
710
|
+
The method allows receiving all open positions within a specified period.
|
|
711
|
+
|
|
712
|
+
The `group` parameter may contain several comma-separated conditions.
|
|
713
|
+
|
|
714
|
+
A condition can be set as a mask using '*'.
|
|
715
|
+
|
|
716
|
+
The logical negation symbol '!' can be used for exclusion.
|
|
717
|
+
|
|
718
|
+
All conditions are applied sequentially, which means conditions for inclusion
|
|
719
|
+
in a group should be specified first, followed by an exclusion condition.
|
|
720
|
+
|
|
721
|
+
For example, `group="*, !EUR"` means that deals for all symbols should be selected first,
|
|
722
|
+
and those containing "EUR" in symbol names should be excluded afterward.
|
|
723
|
+
"""
|
|
724
|
+
|
|
725
|
+
if (symbol is not None) + (group is not None) + (ticket is not None) > 1:
|
|
726
|
+
raise ValueError(
|
|
727
|
+
"Only one of 'symbol', 'group', or 'ticket' can be specified as filter or None of them.")
|
|
728
|
+
|
|
729
|
+
if symbol is not None:
|
|
730
|
+
positions = mt5.positions_get(symbol=symbol)
|
|
731
|
+
elif group is not None:
|
|
732
|
+
positions = mt5.positions_get(group=group)
|
|
733
|
+
elif ticket is not None:
|
|
734
|
+
positions = mt5.positions_get(ticket=ticket)
|
|
735
|
+
else:
|
|
736
|
+
positions = mt5.positions_get()
|
|
737
|
+
|
|
738
|
+
if len(positions) == 0:
|
|
739
|
+
return None
|
|
740
|
+
if to_df:
|
|
741
|
+
df = pd.DataFrame(list(positions), columns=positions[0]._asdict())
|
|
742
|
+
df['time'] = pd.to_datetime(df['time'], unit='s')
|
|
743
|
+
df.drop(['time_update', 'time_msc', 'time_update_msc', 'external_id'],
|
|
744
|
+
axis=1, inplace=True)
|
|
745
|
+
return df
|
|
746
|
+
else:
|
|
747
|
+
trade_positions = [
|
|
748
|
+
TradePosition(**p._asdict()) for p in positions]
|
|
749
|
+
return tuple(trade_positions)
|
|
750
|
+
|
|
751
|
+
def get_trades_history(
|
|
752
|
+
self,
|
|
753
|
+
date_from: datetime = datetime(2000, 1, 1),
|
|
754
|
+
date_to: Optional[datetime] = None,
|
|
755
|
+
group: Optional[str] = None,
|
|
756
|
+
ticket: Optional[int] = None, # TradeDeal.ticket
|
|
757
|
+
position: Optional[int] = None, # TradePosition.ticket
|
|
758
|
+
to_df: bool = True,
|
|
759
|
+
save: bool = False
|
|
760
|
+
) -> Union[pd.DataFrame, Tuple[TradeDeal], None]:
|
|
761
|
+
"""
|
|
762
|
+
Get deals from trading history within the specified interval
|
|
763
|
+
with the ability to filter by `ticket` or `position`.
|
|
764
|
+
|
|
765
|
+
You can call this method in the following ways:
|
|
766
|
+
|
|
767
|
+
- Call with a `time interval`. Returns all deals falling within the specified interval.
|
|
768
|
+
|
|
769
|
+
- Call specifying the `order ticket`. Returns all deals having the specified `order ticket` in the `DEAL_ORDER` property.
|
|
770
|
+
|
|
771
|
+
- Call specifying the `position ticket`. Returns all deals having the specified `position ticket` in the `DEAL_POSITION_ID` property.
|
|
772
|
+
|
|
773
|
+
Args:
|
|
774
|
+
date_from (datetime): Date the bars are requested from.
|
|
775
|
+
Set by the `datetime` object or as a number of seconds elapsed since 1970-01-01.
|
|
776
|
+
Bars with the open time >= `date_from` are returned. Required unnamed parameter.
|
|
777
|
+
|
|
778
|
+
date_to (Optional[datetime]): Same as `date_from`.
|
|
779
|
+
|
|
780
|
+
group (Optional[str]): The filter for arranging a group of necessary symbols.
|
|
781
|
+
Optional named parameter. If the group is specified,
|
|
782
|
+
the function returns only positions meeting specified criteria
|
|
783
|
+
for a symbol name.
|
|
784
|
+
|
|
785
|
+
ticket (Optional[int]): Ticket of an order (stored in `DEAL_ORDER`) for which all deals should be received.
|
|
786
|
+
Optional parameter. If not specified, the filter is not applied.
|
|
787
|
+
|
|
788
|
+
position (Optional[int]): Ticket of a position (stored in `DEAL_POSITION_ID`) for which all deals should be received.
|
|
789
|
+
Optional parameter. If not specified, the filter is not applied.
|
|
790
|
+
|
|
791
|
+
to_df (bool): If True, a DataFrame is returned.
|
|
792
|
+
|
|
793
|
+
save (bool): If set to True, a CSV file will be created to save the history.
|
|
794
|
+
|
|
795
|
+
Returns:
|
|
796
|
+
Union[pd.DataFrame, Tuple[TradeDeal], None]:
|
|
797
|
+
- `TradeDeal` in the form of a named tuple structure (namedtuple) or pd.DataFrame().
|
|
798
|
+
- `None` in case of an error.
|
|
799
|
+
|
|
800
|
+
Notes:
|
|
801
|
+
The method allows receiving all history orders within a specified period.
|
|
802
|
+
|
|
803
|
+
The `group` parameter may contain several comma-separated conditions.
|
|
804
|
+
|
|
805
|
+
A condition can be set as a mask using '*'.
|
|
806
|
+
|
|
807
|
+
The logical negation symbol '!' can be used for exclusion.
|
|
808
|
+
|
|
809
|
+
All conditions are applied sequentially, which means conditions for inclusion
|
|
810
|
+
in a group should be specified first, followed by an exclusion condition.
|
|
811
|
+
|
|
812
|
+
For example, `group="*, !EUR"` means that deals for all symbols should be selected first
|
|
813
|
+
and those containing "EUR" in symbol names should be excluded afterward.
|
|
814
|
+
|
|
815
|
+
Example:
|
|
816
|
+
>>> # Get the number of deals in history
|
|
817
|
+
>>> from datetime import datetime
|
|
818
|
+
>>> from_date = datetime(2020, 1, 1)
|
|
819
|
+
>>> to_date = datetime.now()
|
|
820
|
+
>>> account = Account()
|
|
821
|
+
>>> history = account.get_trades_history(from_date, to_date)
|
|
822
|
+
"""
|
|
823
|
+
|
|
824
|
+
if date_to is None:
|
|
825
|
+
date_to = datetime.now()
|
|
826
|
+
|
|
827
|
+
if (ticket is not None) + (group is not None) + (position is not None) > 1:
|
|
828
|
+
raise ValueError(
|
|
829
|
+
"Only one of 'position', 'group' or 'ticket' can be specified as filter or None of them .")
|
|
830
|
+
if group is not None:
|
|
831
|
+
position_deals = mt5.history_deals_get(
|
|
832
|
+
date_from, date_to, group=group
|
|
833
|
+
)
|
|
834
|
+
elif ticket is not None:
|
|
835
|
+
position_deals = mt5.history_deals_get(ticket=ticket)
|
|
836
|
+
elif position is not None:
|
|
837
|
+
position_deals = mt5.history_deals_get(position=position)
|
|
838
|
+
else:
|
|
839
|
+
position_deals = mt5.history_deals_get(date_from, date_to)
|
|
840
|
+
|
|
841
|
+
if len(position_deals) == 0:
|
|
842
|
+
return None
|
|
843
|
+
|
|
844
|
+
df = pd.DataFrame(list(position_deals),
|
|
845
|
+
columns=position_deals[0]._asdict())
|
|
846
|
+
df['time'] = pd.to_datetime(df['time'], unit='s')
|
|
847
|
+
if save:
|
|
848
|
+
file = "trade_history.csv"
|
|
849
|
+
df.to_csv(file)
|
|
850
|
+
if to_df:
|
|
851
|
+
return df
|
|
852
|
+
else:
|
|
853
|
+
position_deals = [
|
|
854
|
+
TradeDeal(**td._asdict())for td in position_deals]
|
|
855
|
+
return tuple(position_deals)
|
|
856
|
+
|
|
857
|
+
def get_orders(self,
|
|
858
|
+
symbol: Optional[str] = None,
|
|
859
|
+
group: Optional[str] = None,
|
|
860
|
+
ticket: Optional[int] = None,
|
|
861
|
+
to_df: bool = False
|
|
862
|
+
) -> Union[pd.DataFrame, Tuple[TradeOrder], None]:
|
|
863
|
+
"""
|
|
864
|
+
Get active orders with the ability to filter by symbol or ticket.
|
|
865
|
+
There are four call options:
|
|
866
|
+
|
|
867
|
+
- Call without parameters. Returns open positions for all symbols.
|
|
868
|
+
- Call specifying a symbol, open positions should be received for.
|
|
869
|
+
- Call specifying a group of symbols, open positions should be received for.
|
|
870
|
+
- Call specifying a position ticket.
|
|
871
|
+
|
|
872
|
+
Args:
|
|
873
|
+
symbol (Optional[str]): Symbol name. Optional named parameter.
|
|
874
|
+
If a symbol is specified, the ticket parameter is ignored.
|
|
875
|
+
|
|
876
|
+
group (Optional[str]): The filter for arranging a group of necessary symbols.
|
|
877
|
+
Optional named parameter. If the group is specified,
|
|
878
|
+
the function returns only positions meeting a specified criteria
|
|
879
|
+
for a symbol name.
|
|
880
|
+
|
|
881
|
+
ticket (Optional[int]): Order ticket. Optional named parameter.
|
|
882
|
+
Unique number assigned to each order.
|
|
883
|
+
|
|
884
|
+
to_df (bool): If True, a DataFrame is returned.
|
|
885
|
+
|
|
886
|
+
Returns:
|
|
887
|
+
Union[pd.DataFrame, Tuple[TradeOrder], None]:
|
|
888
|
+
- `TradeOrder` in the form of a named tuple structure (namedtuple) or pd.DataFrame().
|
|
889
|
+
- `None` in case of an error.
|
|
890
|
+
|
|
891
|
+
Notes:
|
|
892
|
+
The method allows receiving all history orders within a specified period.
|
|
893
|
+
The `group` parameter may contain several comma-separated conditions.
|
|
894
|
+
A condition can be set as a mask using '*'.
|
|
895
|
+
|
|
896
|
+
The logical negation symbol '!' can be used for exclusion.
|
|
897
|
+
All conditions are applied sequentially, which means conditions for inclusion
|
|
898
|
+
in a group should be specified first, followed by an exclusion condition.
|
|
899
|
+
|
|
900
|
+
For example, `group="*, !EUR"` means that deals for all symbols should be selected first
|
|
901
|
+
and the ones containing "EUR" in symbol names should be excluded afterward.
|
|
902
|
+
"""
|
|
903
|
+
|
|
904
|
+
if (symbol is not None) + (group is not None) + (ticket is not None) > 1:
|
|
905
|
+
raise ValueError(
|
|
906
|
+
"Only one of 'symbol', 'group', or 'ticket' can be specified as filter or None of them.")
|
|
907
|
+
|
|
908
|
+
if symbol is not None:
|
|
909
|
+
orders = mt5.orders_get(symbol=symbol)
|
|
910
|
+
elif group is not None:
|
|
911
|
+
orders = mt5.orders_get(group=group)
|
|
912
|
+
elif ticket is not None:
|
|
913
|
+
orders = mt5.orders_get(ticket=ticket)
|
|
914
|
+
else:
|
|
915
|
+
orders = mt5.orders_get()
|
|
916
|
+
|
|
917
|
+
if len(orders) == 0:
|
|
918
|
+
return None
|
|
919
|
+
|
|
920
|
+
if to_df:
|
|
921
|
+
df = pd.DataFrame(list(orders), columns=orders[0]._asdict())
|
|
922
|
+
df.drop(['time_expiration', 'type_time', 'state', 'position_by_id', 'reason',
|
|
923
|
+
'volume_current', 'price_stoplimit', 'sl', 'tp'], axis=1, inplace=True)
|
|
924
|
+
df['time_setup'] = pd.to_datetime(df['time_setup'], unit='s')
|
|
925
|
+
df['time_done'] = pd.to_datetime(df['time_done'], unit='s')
|
|
926
|
+
return df
|
|
927
|
+
else:
|
|
928
|
+
trade_orders = [TradeOrder(**o._asdict()) for o in orders]
|
|
929
|
+
return tuple(trade_orders)
|
|
930
|
+
|
|
931
|
+
def get_orders_history(
|
|
932
|
+
self,
|
|
933
|
+
date_from: datetime = datetime(2000, 1, 1),
|
|
934
|
+
date_to: Optional[datetime] = None,
|
|
935
|
+
group: Optional[str] = None,
|
|
936
|
+
ticket: Optional[int] = None, # order ticket
|
|
937
|
+
position: Optional[int] = None, # position ticket
|
|
938
|
+
to_df: bool = True,
|
|
939
|
+
save: bool = False
|
|
940
|
+
) -> Union[pd.DataFrame, Tuple[TradeOrder], None]:
|
|
941
|
+
"""
|
|
942
|
+
Get orders from trading history within the specified interval
|
|
943
|
+
with the ability to filter by `ticket` or `position`.
|
|
944
|
+
|
|
945
|
+
You can call this method in the following ways:
|
|
946
|
+
|
|
947
|
+
- Call with a `time interval`. Returns all deals falling within the specified interval.
|
|
948
|
+
|
|
949
|
+
- Call specifying the `order ticket`. Returns all deals having the specified `order ticket` in the `DEAL_ORDER` property.
|
|
950
|
+
|
|
951
|
+
- Call specifying the `position ticket`. Returns all deals having the specified `position ticket` in the `DEAL_POSITION_ID` property.
|
|
952
|
+
|
|
953
|
+
Args:
|
|
954
|
+
date_from (datetime): Date the bars are requested from.
|
|
955
|
+
Set by the `datetime` object or as a number of seconds elapsed since 1970-01-01.
|
|
956
|
+
Bars with the open time >= `date_from` are returned. Required unnamed parameter.
|
|
957
|
+
|
|
958
|
+
date_to (Optional[datetime]): Same as `date_from`.
|
|
959
|
+
|
|
960
|
+
group (Optional[str]): The filter for arranging a group of necessary symbols.
|
|
961
|
+
Optional named parameter. If the group is specified,
|
|
962
|
+
the function returns only positions meeting specified criteria
|
|
963
|
+
for a symbol name.
|
|
964
|
+
|
|
965
|
+
ticket (Optional[int]): Order ticket to filter results. Optional parameter.
|
|
966
|
+
If not specified, the filter is not applied.
|
|
967
|
+
|
|
968
|
+
position (Optional[int]): Ticket of a position (stored in `DEAL_POSITION_ID`) to filter results.
|
|
969
|
+
Optional parameter. If not specified, the filter is not applied.
|
|
970
|
+
|
|
971
|
+
to_df (bool): If True, a DataFrame is returned.
|
|
972
|
+
|
|
973
|
+
save (bool): If True, a CSV file will be created to save the history.
|
|
974
|
+
|
|
975
|
+
Returns:
|
|
976
|
+
Union[pd.DataFrame, Tuple[TradeOrder], None]
|
|
977
|
+
- `TradeOrder` in the form of a named tuple structure (namedtuple) or pd.DataFrame().
|
|
978
|
+
- `None` in case of an error.
|
|
979
|
+
|
|
980
|
+
Notes:
|
|
981
|
+
The method allows receiving all history orders within a specified period.
|
|
982
|
+
|
|
983
|
+
The `group` parameter may contain several comma-separated conditions.
|
|
984
|
+
|
|
985
|
+
A condition can be set as a mask using '*'.
|
|
986
|
+
|
|
987
|
+
The logical negation symbol '!' can be used for exclusion.
|
|
988
|
+
|
|
989
|
+
All conditions are applied sequentially, which means conditions for inclusion
|
|
990
|
+
in a group should be specified first, followed by an exclusion condition.
|
|
991
|
+
|
|
992
|
+
For example, `group="*, !EUR"` means that deals for all symbols should be selected first
|
|
993
|
+
and those containing "EUR" in symbol names should be excluded afterward.
|
|
994
|
+
|
|
995
|
+
Example:
|
|
996
|
+
>>> # Get the number of deals in history
|
|
997
|
+
>>> from datetime import datetime
|
|
998
|
+
>>> from_date = datetime(2020, 1, 1)
|
|
999
|
+
>>> to_date = datetime.now()
|
|
1000
|
+
>>> account = Account()
|
|
1001
|
+
>>> history = account.get_orders_history(from_date, to_date)
|
|
1002
|
+
"""
|
|
1003
|
+
if date_to is None:
|
|
1004
|
+
date_to = datetime.now()
|
|
1005
|
+
|
|
1006
|
+
if (group is not None) + (ticket is not None) + (position is not None) > 1:
|
|
1007
|
+
raise ValueError(
|
|
1008
|
+
"Only one of 'position', 'group' or 'ticket' can be specified or None of them as filter.")
|
|
1009
|
+
if group is not None:
|
|
1010
|
+
history_orders = mt5.history_orders_get(
|
|
1011
|
+
date_from, date_to, group=group
|
|
1012
|
+
)
|
|
1013
|
+
elif ticket is not None:
|
|
1014
|
+
history_orders = mt5.history_orders_get(ticket=ticket)
|
|
1015
|
+
elif position is not None:
|
|
1016
|
+
history_orders = mt5.history_orders_get(position=position)
|
|
1017
|
+
else:
|
|
1018
|
+
history_orders = mt5.history_orders_get(date_from, date_to)
|
|
1019
|
+
|
|
1020
|
+
if len(history_orders) == 0:
|
|
1021
|
+
return None
|
|
1022
|
+
|
|
1023
|
+
df = pd.DataFrame(list(history_orders),
|
|
1024
|
+
columns=history_orders[0]._asdict())
|
|
1025
|
+
df.drop(['time_expiration', 'type_time', 'state', 'position_by_id', 'reason',
|
|
1026
|
+
'volume_current', 'price_stoplimit', 'sl', 'tp'], axis=1, inplace=True)
|
|
1027
|
+
df['time_setup'] = pd.to_datetime(df['time_setup'], unit='s')
|
|
1028
|
+
df['time_done'] = pd.to_datetime(df['time_done'], unit='s')
|
|
1029
|
+
|
|
1030
|
+
if save:
|
|
1031
|
+
file = "trade_history.csv"
|
|
1032
|
+
df.to_csv(file)
|
|
1033
|
+
if to_df:
|
|
1034
|
+
return df
|
|
1035
|
+
else:
|
|
1036
|
+
history_orders = [
|
|
1037
|
+
TradeOrder(**td._asdict())for td in history_orders]
|
|
1038
|
+
return tuple(history_orders)
|