bbstrader 2.0.3__cp312-cp312-macosx_11_0_arm64.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.
- bbstrader/__init__.py +27 -0
- bbstrader/__main__.py +92 -0
- bbstrader/api/__init__.py +96 -0
- bbstrader/api/handlers.py +245 -0
- bbstrader/api/metatrader_client.cpython-312-darwin.so +0 -0
- bbstrader/api/metatrader_client.pyi +624 -0
- bbstrader/assets/bbs_.png +0 -0
- bbstrader/assets/bbstrader.ico +0 -0
- bbstrader/assets/bbstrader.png +0 -0
- bbstrader/assets/qs_metrics_1.png +0 -0
- bbstrader/btengine/__init__.py +54 -0
- bbstrader/btengine/backtest.py +358 -0
- bbstrader/btengine/data.py +737 -0
- bbstrader/btengine/event.py +229 -0
- bbstrader/btengine/execution.py +287 -0
- bbstrader/btengine/performance.py +408 -0
- bbstrader/btengine/portfolio.py +393 -0
- bbstrader/btengine/strategy.py +588 -0
- bbstrader/compat.py +28 -0
- bbstrader/config.py +100 -0
- bbstrader/core/__init__.py +27 -0
- bbstrader/core/data.py +628 -0
- bbstrader/core/strategy.py +466 -0
- bbstrader/metatrader/__init__.py +48 -0
- bbstrader/metatrader/_copier.py +720 -0
- bbstrader/metatrader/account.py +865 -0
- bbstrader/metatrader/broker.py +418 -0
- bbstrader/metatrader/copier.py +1487 -0
- bbstrader/metatrader/rates.py +495 -0
- bbstrader/metatrader/risk.py +667 -0
- bbstrader/metatrader/trade.py +1692 -0
- bbstrader/metatrader/utils.py +402 -0
- bbstrader/models/__init__.py +39 -0
- bbstrader/models/nlp.py +932 -0
- bbstrader/models/optimization.py +182 -0
- bbstrader/scripts.py +665 -0
- bbstrader/trading/__init__.py +33 -0
- bbstrader/trading/execution.py +1159 -0
- bbstrader/trading/strategy.py +362 -0
- bbstrader/trading/utils.py +69 -0
- bbstrader-2.0.3.dist-info/METADATA +396 -0
- bbstrader-2.0.3.dist-info/RECORD +45 -0
- bbstrader-2.0.3.dist-info/WHEEL +5 -0
- bbstrader-2.0.3.dist-info/entry_points.txt +3 -0
- bbstrader-2.0.3.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Any, Dict, List, Optional, Union
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
from bbstrader.api import (
|
|
7
|
+
AccountInfo,
|
|
8
|
+
SymbolInfo,
|
|
9
|
+
TerminalInfo,
|
|
10
|
+
TickInfo,
|
|
11
|
+
TradeDeal,
|
|
12
|
+
TradeOrder,
|
|
13
|
+
TradePosition,
|
|
14
|
+
)
|
|
15
|
+
from bbstrader.api import Mt5client as client
|
|
16
|
+
from bbstrader.metatrader.broker import Broker, check_mt5_connection
|
|
17
|
+
from bbstrader.metatrader.utils import TIMEFRAMES, RateInfo, SymbolType, raise_mt5_error
|
|
18
|
+
|
|
19
|
+
__all__ = ["Account"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Account(object):
|
|
23
|
+
"""
|
|
24
|
+
The `Account` class is utilized to retrieve information about
|
|
25
|
+
the current trading account or a specific account.
|
|
26
|
+
It enables interaction with the MT5 terminal to manage account details,
|
|
27
|
+
including account informations, terminal status, financial instrument details,
|
|
28
|
+
active orders, open positions, and trading history.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
>>> # Instantiating the Account class
|
|
32
|
+
>>> account = Account()
|
|
33
|
+
|
|
34
|
+
>>> # Getting account information
|
|
35
|
+
>>> account_info = account.get_account_info()
|
|
36
|
+
|
|
37
|
+
>>> # Getting terminal information
|
|
38
|
+
>>> terminal_info = account.get_terminal_info()
|
|
39
|
+
|
|
40
|
+
>>> # Getting active orders
|
|
41
|
+
>>> orders = account.get_orders()
|
|
42
|
+
|
|
43
|
+
>>> # Fetching open positions
|
|
44
|
+
>>> positions = account.get_positions()
|
|
45
|
+
|
|
46
|
+
>>> # Accessing trade history
|
|
47
|
+
>>> from_date = datetime(2020, 1, 1)
|
|
48
|
+
>>> to_date = datetime.now()
|
|
49
|
+
>>> trade_history = account.get_trade_history(from_date, to_date)
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self, broker: Optional[Broker] = None, **kwargs):
|
|
53
|
+
"""
|
|
54
|
+
Initialize the Account class.
|
|
55
|
+
|
|
56
|
+
See `bbstrader.metatrader.broker.check_mt5_connection()`
|
|
57
|
+
for more details on how to connect to MT5 terminal.
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
check_mt5_connection(**kwargs)
|
|
61
|
+
self._info = client.account_info()
|
|
62
|
+
terminal_info = self.get_terminal_info()
|
|
63
|
+
self._broker = (
|
|
64
|
+
broker
|
|
65
|
+
if broker is not None
|
|
66
|
+
else Broker(terminal_info.company if terminal_info else "Unknown")
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def info(self) -> AccountInfo:
|
|
71
|
+
return self._info
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def broker(self) -> Broker:
|
|
75
|
+
return self._broker
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def timezone(self) -> Optional[str]:
|
|
79
|
+
return self.broker.get_terminal_timezone()
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def name(self) -> str:
|
|
83
|
+
return self._info.name
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def number(self) -> int:
|
|
87
|
+
return self._info.login
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def server(self) -> str:
|
|
91
|
+
"""The name of the trade server to which the client terminal is connected.
|
|
92
|
+
(e.g., 'AdmiralsGroup-Demo')
|
|
93
|
+
"""
|
|
94
|
+
return self._info.server
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def balance(self) -> float:
|
|
98
|
+
return self._info.balance
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def leverage(self) -> int:
|
|
102
|
+
return self._info.leverage
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def equity(self) -> float:
|
|
106
|
+
return self._info.equity
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def currency(self) -> str:
|
|
110
|
+
return self._info.currency
|
|
111
|
+
|
|
112
|
+
def shutdown(self):
|
|
113
|
+
"""Close the connection to the MetaTrader 5 terminal."""
|
|
114
|
+
client.shutdown()
|
|
115
|
+
|
|
116
|
+
def get_account_info(
|
|
117
|
+
self,
|
|
118
|
+
account: Optional[int] = None,
|
|
119
|
+
password: Optional[str] = None,
|
|
120
|
+
server: Optional[str] = None,
|
|
121
|
+
timeout: Optional[int] = 60_000,
|
|
122
|
+
path: Optional[str] = None,
|
|
123
|
+
) -> Union[AccountInfo, None]:
|
|
124
|
+
"""
|
|
125
|
+
Get info on the current trading account or a specific account .
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
account (int, optinal) : MT5 Trading account number.
|
|
129
|
+
password (str, optinal): MT5 Trading account password.
|
|
130
|
+
|
|
131
|
+
server (str, optinal): MT5 Trading account server
|
|
132
|
+
[Brokers or terminal server ["demo", "real"]]
|
|
133
|
+
If no server is set, the last used server is applied automaticall
|
|
134
|
+
|
|
135
|
+
timeout (int, optinal):
|
|
136
|
+
Connection timeout in milliseconds. Optional named parameter.
|
|
137
|
+
If not specified, the value of 60 000 (60 seconds) is applied.
|
|
138
|
+
If the connection is not established within the specified time,
|
|
139
|
+
the call is forcibly terminated and the exception is generated.
|
|
140
|
+
path (str, optional): The path to the MetaTrader 5 terminal executable file.
|
|
141
|
+
Defaults to None (e.g., "C:/Program Files/MetaTrader 5/terminal64.exe").
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
- AccountInfo
|
|
145
|
+
- None in case of an error
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
MT5TerminalError: A specific exception based on the error code.
|
|
149
|
+
"""
|
|
150
|
+
# connect to the trade account specifying a password and a server
|
|
151
|
+
if account is not None and password is not None and server is not None:
|
|
152
|
+
try:
|
|
153
|
+
if path is not None:
|
|
154
|
+
self.broker.initialize_connection(
|
|
155
|
+
path=path,
|
|
156
|
+
login=account,
|
|
157
|
+
password=password,
|
|
158
|
+
server=server,
|
|
159
|
+
timeout=timeout,
|
|
160
|
+
)
|
|
161
|
+
authorized = client.login(
|
|
162
|
+
account, password=password, server=server, timeout=timeout
|
|
163
|
+
)
|
|
164
|
+
if not authorized:
|
|
165
|
+
raise_mt5_error(f"Failed to connect to account #{account}")
|
|
166
|
+
info = client.account_info()
|
|
167
|
+
return info
|
|
168
|
+
except Exception as e:
|
|
169
|
+
raise_mt5_error(e)
|
|
170
|
+
else:
|
|
171
|
+
try:
|
|
172
|
+
return client.account_info()
|
|
173
|
+
except Exception as e:
|
|
174
|
+
raise_mt5_error(e)
|
|
175
|
+
|
|
176
|
+
def get_terminal_info(self) -> TerminalInfo | None:
|
|
177
|
+
"""
|
|
178
|
+
Get the connected MetaTrader 5 client terminal status and settings.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
- TerminalInfo
|
|
182
|
+
- None in case of an error
|
|
183
|
+
|
|
184
|
+
Raises:
|
|
185
|
+
MT5TerminalError: A specific exception based on the error code.
|
|
186
|
+
"""
|
|
187
|
+
try:
|
|
188
|
+
terminal_info = client.terminal_info()
|
|
189
|
+
if terminal_info is None:
|
|
190
|
+
return None
|
|
191
|
+
except Exception as e:
|
|
192
|
+
raise_mt5_error(e)
|
|
193
|
+
return terminal_info
|
|
194
|
+
|
|
195
|
+
def get_symbol_info(self, symbol: str) -> SymbolInfo | None:
|
|
196
|
+
"""Get symbol properties
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
symbol (str): Symbol name
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
- SymbolInfo.
|
|
203
|
+
- None in case of an error.
|
|
204
|
+
|
|
205
|
+
Raises:
|
|
206
|
+
MT5TerminalError: A specific exception based on the error code.
|
|
207
|
+
|
|
208
|
+
"""
|
|
209
|
+
try:
|
|
210
|
+
symbol_info = client.symbol_info(symbol)
|
|
211
|
+
if symbol_info is None:
|
|
212
|
+
return None
|
|
213
|
+
else:
|
|
214
|
+
return symbol_info
|
|
215
|
+
except Exception as e:
|
|
216
|
+
msg = self._symbol_info_msg(symbol)
|
|
217
|
+
raise_mt5_error(message=f"{str(e)} {msg}")
|
|
218
|
+
|
|
219
|
+
def _symbol_info_msg(self, symbol):
|
|
220
|
+
return (
|
|
221
|
+
f"No history found for {symbol} in Market Watch.\n"
|
|
222
|
+
f"* Ensure {symbol} is selected and displayed in the Market Watch window.\n"
|
|
223
|
+
f"* See https://www.metatrader5.com/en/terminal/help/trading/market_watch\n"
|
|
224
|
+
f"* Ensure the symbol name is correct.\n"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def get_tick_info(self, symbol: str) -> TickInfo | None:
|
|
228
|
+
"""Get symbol tick properties
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
symbol (str): Symbol name
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
- TickInfo.
|
|
235
|
+
- None in case of an error.
|
|
236
|
+
|
|
237
|
+
Raises:
|
|
238
|
+
MT5TerminalError: A specific exception based on the error code.
|
|
239
|
+
|
|
240
|
+
"""
|
|
241
|
+
try:
|
|
242
|
+
tick_info = client.symbol_info_tick(symbol)
|
|
243
|
+
if tick_info is None:
|
|
244
|
+
return None
|
|
245
|
+
else:
|
|
246
|
+
return tick_info
|
|
247
|
+
except Exception as e:
|
|
248
|
+
msg = self._symbol_info_msg(symbol)
|
|
249
|
+
raise_mt5_error(message=f"{str(e)} {msg}")
|
|
250
|
+
|
|
251
|
+
def get_currency_rates(self, symbol: str) -> Dict[str, str]:
|
|
252
|
+
"""
|
|
253
|
+
Args:
|
|
254
|
+
symbol (str): The symbol for which to get currencies
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
- `base currency` (bc)
|
|
258
|
+
- `margin currency` (mc)
|
|
259
|
+
- `profit currency` (pc)
|
|
260
|
+
- `account currency` (ac)
|
|
261
|
+
|
|
262
|
+
Exemple:
|
|
263
|
+
>>> account = Account()
|
|
264
|
+
>>> account.get_currency_rates('EURUSD')
|
|
265
|
+
{'bc': 'EUR', 'mc': 'EUR', 'pc': 'USD', 'ac': 'USD'}
|
|
266
|
+
"""
|
|
267
|
+
info = self.get_symbol_info(symbol)
|
|
268
|
+
bc = info.currency_base
|
|
269
|
+
pc = info.currency_profit
|
|
270
|
+
mc = info.currency_margin
|
|
271
|
+
ac = self._info.currency
|
|
272
|
+
return {"bc": bc, "mc": mc, "pc": pc, "ac": ac}
|
|
273
|
+
|
|
274
|
+
def get_symbols(
|
|
275
|
+
self,
|
|
276
|
+
symbol_type: SymbolType | str = "ALL",
|
|
277
|
+
check_etf=False,
|
|
278
|
+
save=False,
|
|
279
|
+
file_name="symbols",
|
|
280
|
+
include_desc=False,
|
|
281
|
+
display_total=False,
|
|
282
|
+
) -> List[str]:
|
|
283
|
+
"""
|
|
284
|
+
Get all specified financial instruments from the MetaTrader 5 terminal.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
symbol_type (SymbolType | str): The type of financial instruments to retrieve.
|
|
288
|
+
- `ALL`: For all available symbols
|
|
289
|
+
- See `bbstrader.metatrader.utils.SymbolType` for more details.
|
|
290
|
+
|
|
291
|
+
check_etf (bool): If True and symbol_type is 'etf', check if the
|
|
292
|
+
ETF description contains 'ETF'.
|
|
293
|
+
|
|
294
|
+
save (bool): If True, save the symbols to a file.
|
|
295
|
+
|
|
296
|
+
file_name (str): The name of the file to save the symbols to
|
|
297
|
+
(without the extension).
|
|
298
|
+
|
|
299
|
+
include_desc (bool): If True, include the symbol's description
|
|
300
|
+
in the output and saved file.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
list: A list of symbols.
|
|
304
|
+
|
|
305
|
+
Raises:
|
|
306
|
+
Exception: If there is an error connecting to MT5 or retrieving symbols.
|
|
307
|
+
"""
|
|
308
|
+
return self.broker.get_symbols(
|
|
309
|
+
symbol_type=symbol_type,
|
|
310
|
+
check_etf=check_etf,
|
|
311
|
+
save=save,
|
|
312
|
+
file_name=file_name,
|
|
313
|
+
include_desc=include_desc,
|
|
314
|
+
display_total=display_total,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
def get_symbol_type(self, symbol: str) -> SymbolType:
|
|
318
|
+
"""
|
|
319
|
+
Determines the type of a given financial instrument symbol.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
symbol (str): The symbol of the financial instrument (e.g., `GOOGL`, `EURUSD`).
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
SymbolType: The type of the financial instrument, one of the following:
|
|
326
|
+
- `SymbolType.ETFs`
|
|
327
|
+
- `SymbolType.BONDS`
|
|
328
|
+
- `SymbolType.FOREX`
|
|
329
|
+
- `SymbolType.FUTURES`
|
|
330
|
+
- `SymbolType.STOCKS`
|
|
331
|
+
- `SymbolType.INDICES`
|
|
332
|
+
- `SymbolType.COMMODITIES`
|
|
333
|
+
- `SymbolType.CRYPTO`
|
|
334
|
+
- `SymbolType.unknown` if the type cannot be determined.
|
|
335
|
+
|
|
336
|
+
"""
|
|
337
|
+
return self.broker.get_symbol_type(symbol)
|
|
338
|
+
|
|
339
|
+
def _get_symbols_by_category(
|
|
340
|
+
self, symbol_type: SymbolType | str, category: str, category_map: Dict[str, str]
|
|
341
|
+
) -> List[str]:
|
|
342
|
+
return self.broker.get_symbols_by_category(symbol_type, category, category_map)
|
|
343
|
+
|
|
344
|
+
def get_stocks_from_country(
|
|
345
|
+
self, country_code: str = "USA", etf=False
|
|
346
|
+
) -> List[str]:
|
|
347
|
+
"""
|
|
348
|
+
Retrieves a list of stock symbols from a specific country.
|
|
349
|
+
|
|
350
|
+
Supported countries are:
|
|
351
|
+
* **Australia:** AUS
|
|
352
|
+
* **Belgium:** BEL
|
|
353
|
+
* **Denmark:** DNK
|
|
354
|
+
* **Finland:** FIN
|
|
355
|
+
* **France:** FRA
|
|
356
|
+
* **Germany:** DEU
|
|
357
|
+
* **Netherlands:** NLD
|
|
358
|
+
* **Norway:** NOR
|
|
359
|
+
* **Portugal:** PRT
|
|
360
|
+
* **Spain:** ESP
|
|
361
|
+
* **Sweden:** SWE
|
|
362
|
+
* **United Kingdom:** GBR
|
|
363
|
+
* **United States:** USA
|
|
364
|
+
* **Switzerland:** CHE
|
|
365
|
+
* **Hong Kong:** HKG
|
|
366
|
+
* **Ireland:** IRL
|
|
367
|
+
* **Austria:** AUT
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
country (str, optional): The country code of stocks to retrieve.
|
|
371
|
+
Defaults to 'USA'.
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
list: A list of stock symbol names from the specified country.
|
|
375
|
+
|
|
376
|
+
Raises:
|
|
377
|
+
ValueError: If an unsupported country is provided.
|
|
378
|
+
|
|
379
|
+
Notes:
|
|
380
|
+
This mthods works primarly with brokers who specify the stock symbols type and exchanges,
|
|
381
|
+
For other brokers use `get_symbols()` or this method will use it by default.
|
|
382
|
+
"""
|
|
383
|
+
stocks = self._get_symbols_by_category(
|
|
384
|
+
SymbolType.STOCKS, country_code, self.broker.countries_stocks
|
|
385
|
+
)
|
|
386
|
+
etfs = (
|
|
387
|
+
self._get_symbols_by_category(
|
|
388
|
+
SymbolType.ETFs, country_code, self.broker.countries_stocks
|
|
389
|
+
)
|
|
390
|
+
if etf
|
|
391
|
+
else []
|
|
392
|
+
)
|
|
393
|
+
if not stocks and not etfs:
|
|
394
|
+
stocks = self.get_symbols(symbol_type=SymbolType.STOCKS)
|
|
395
|
+
etfs = self.get_symbols(symbol_type=SymbolType.ETFs) if etf else []
|
|
396
|
+
return stocks + etfs
|
|
397
|
+
|
|
398
|
+
def get_stocks_from_exchange(
|
|
399
|
+
self, exchange_code: str = "XNYS", etf=True
|
|
400
|
+
) -> List[str]:
|
|
401
|
+
"""
|
|
402
|
+
Get stock symbols from a specific exchange using the ISO Code for the exchange.
|
|
403
|
+
|
|
404
|
+
Supported exchanges are from Admirals Group AS products:
|
|
405
|
+
* **XASX:** **Australian Securities Exchange**
|
|
406
|
+
* **XBRU:** **Euronext Brussels Exchange**
|
|
407
|
+
* **XCSE:** **Copenhagen Stock Exchange**
|
|
408
|
+
* **XHEL:** **NASDAQ OMX Helsinki**
|
|
409
|
+
* **XPAR:** **Euronext Paris**
|
|
410
|
+
* **XETR:** **Xetra Frankfurt**
|
|
411
|
+
* **XOSL:** **Oslo Stock Exchange**
|
|
412
|
+
* **XLIS:** **Euronext Lisbon**
|
|
413
|
+
* **XMAD:** **Bolsa de Madrid**
|
|
414
|
+
* **XSTO:** **NASDAQ OMX Stockholm**
|
|
415
|
+
* **XLON:** **London Stock Exchange**
|
|
416
|
+
* **NYSE:** **New York Stock Exchange**
|
|
417
|
+
* **ARCA:** **NYSE ARCA**
|
|
418
|
+
* **AMEX:** **NYSE AMEX**
|
|
419
|
+
* **XNYS:** **New York Stock Exchange (AMEX, ARCA, NYSE)**
|
|
420
|
+
* **NASDAQ:** **NASDAQ**
|
|
421
|
+
* **BATS:** **BATS Exchange**
|
|
422
|
+
* **XSWX:** **SWX Swiss Exchange**
|
|
423
|
+
* **XAMS:** **Euronext Amsterdam**
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
exchange_code (str, optional): The ISO code of the exchange.
|
|
427
|
+
etf (bool, optional): If True, include ETFs from the exchange. Defaults to True.
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
list: A list of stock symbol names from the specified exchange.
|
|
431
|
+
|
|
432
|
+
Raises:
|
|
433
|
+
ValueError: If an unsupported exchange is provided.
|
|
434
|
+
|
|
435
|
+
Notes:
|
|
436
|
+
This mthods works primarly with brokers who specify the stock symbols type and exchanges,
|
|
437
|
+
For other brokers use `get_symbols()` or this method will use it by default.
|
|
438
|
+
"""
|
|
439
|
+
stocks = self._get_symbols_by_category(
|
|
440
|
+
SymbolType.STOCKS, exchange_code, self.broker.exchanges
|
|
441
|
+
)
|
|
442
|
+
etfs = (
|
|
443
|
+
self._get_symbols_by_category(
|
|
444
|
+
SymbolType.ETFs, exchange_code, self.broker.exchanges
|
|
445
|
+
)
|
|
446
|
+
if etf
|
|
447
|
+
else []
|
|
448
|
+
)
|
|
449
|
+
if not stocks and not etfs:
|
|
450
|
+
stocks = self.get_symbols(symbol_type=SymbolType.STOCKS)
|
|
451
|
+
etfs = self.get_symbols(symbol_type=SymbolType.ETFs) if etf else []
|
|
452
|
+
return stocks + etfs
|
|
453
|
+
|
|
454
|
+
def get_rate_info(self, symbol: str, timeframe: str = "1m") -> RateInfo | None:
|
|
455
|
+
"""Get the most recent bar for a specified symbol and timeframe.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
symbol (str): The symbol for which to get the rate information.
|
|
459
|
+
timeframe (str): The timeframe for the rate information. Default is '1m'.
|
|
460
|
+
See ``bbstrader.metatrader.utils.TIMEFRAMES`` for supported timeframes.
|
|
461
|
+
Returns:
|
|
462
|
+
RateInfo: The most recent bar as a RateInfo named tuple.
|
|
463
|
+
None: If no rates are found or an error occurs.
|
|
464
|
+
Raises:
|
|
465
|
+
MT5TerminalError: A specific exception based on the error code.
|
|
466
|
+
"""
|
|
467
|
+
rates = client.copy_rates_from_pos(symbol, TIMEFRAMES[timeframe], 0, 1)
|
|
468
|
+
if rates is None or len(rates) == 0:
|
|
469
|
+
return None
|
|
470
|
+
rate = rates[0]
|
|
471
|
+
return RateInfo(*rate)
|
|
472
|
+
|
|
473
|
+
def get_positions(
|
|
474
|
+
self,
|
|
475
|
+
symbol: Optional[str] = None,
|
|
476
|
+
group: Optional[str] = None,
|
|
477
|
+
ticket: Optional[int] = None,
|
|
478
|
+
) -> Union[List[TradePosition] | None]:
|
|
479
|
+
"""
|
|
480
|
+
Get open positions with the ability to filter by symbol or ticket.
|
|
481
|
+
There are four call options:
|
|
482
|
+
|
|
483
|
+
- Call without parameters. Returns open positions for all symbols.
|
|
484
|
+
- Call specifying a symbol. Returns open positions for the specified symbol.
|
|
485
|
+
- Call specifying a group of symbols. Returns open positions for the specified group of symbols.
|
|
486
|
+
- Call specifying a position ticket. Returns the position corresponding to the specified ticket.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
symbol (Optional[str]): Symbol name. Optional named parameter.
|
|
490
|
+
If a symbol is specified, the `ticket` parameter is ignored.
|
|
491
|
+
|
|
492
|
+
group (Optional[str]): The filter for arranging a group of necessary symbols.
|
|
493
|
+
Optional named parameter. If the group is specified,
|
|
494
|
+
the function returns only positions meeting specified criteria
|
|
495
|
+
for a symbol name.
|
|
496
|
+
|
|
497
|
+
ticket (Optional[int]): Position ticket. Optional named parameter.
|
|
498
|
+
A unique number assigned to each newly opened position.
|
|
499
|
+
It usually matches the ticket of the order used to open the position,
|
|
500
|
+
except when the ticket is changed as a result of service operations on the server,
|
|
501
|
+
for example, when charging swaps with position re-opening.
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
[List[TradePosition] | None]:
|
|
506
|
+
- List of `TradePosition`.
|
|
507
|
+
|
|
508
|
+
Notes:
|
|
509
|
+
The method allows receiving all open positions within a specified period.
|
|
510
|
+
|
|
511
|
+
The `group` parameter may contain several comma-separated conditions.
|
|
512
|
+
|
|
513
|
+
A condition can be set as a mask using '*'.
|
|
514
|
+
|
|
515
|
+
The logical negation symbol '!' can be used for exclusion.
|
|
516
|
+
|
|
517
|
+
All conditions are applied sequentially, which means conditions for inclusion
|
|
518
|
+
in a group should be specified first, followed by an exclusion condition.
|
|
519
|
+
|
|
520
|
+
For example, `group="*, !EUR"` means that deals for all symbols should be selected first,
|
|
521
|
+
and those containing "EUR" in symbol names should be excluded afterward.
|
|
522
|
+
"""
|
|
523
|
+
|
|
524
|
+
if (symbol is not None) + (group is not None) + (ticket is not None) > 1:
|
|
525
|
+
raise ValueError(
|
|
526
|
+
"Only one of 'symbol', 'group', or 'ticket' can be specified as filter or None of them."
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
if symbol is not None:
|
|
530
|
+
positions = client.positions_get(symbol)
|
|
531
|
+
elif group is not None:
|
|
532
|
+
positions = client.positions_get_by_group(group)
|
|
533
|
+
elif ticket is not None:
|
|
534
|
+
positions = client.position_get_by_ticket(ticket)
|
|
535
|
+
else:
|
|
536
|
+
positions = client.positions_get()
|
|
537
|
+
|
|
538
|
+
if positions is None:
|
|
539
|
+
return None
|
|
540
|
+
if isinstance(positions, TradePosition):
|
|
541
|
+
return [positions]
|
|
542
|
+
if len(positions) == 0:
|
|
543
|
+
return None
|
|
544
|
+
|
|
545
|
+
return positions
|
|
546
|
+
|
|
547
|
+
def get_orders(
|
|
548
|
+
self,
|
|
549
|
+
symbol: Optional[str] = None,
|
|
550
|
+
group: Optional[str] = None,
|
|
551
|
+
ticket: Optional[int] = None,
|
|
552
|
+
) -> Union[List[TradeOrder] | None]:
|
|
553
|
+
"""
|
|
554
|
+
Get active orders with the ability to filter by symbol or ticket.
|
|
555
|
+
There are four call options:
|
|
556
|
+
|
|
557
|
+
- Call without parameters. Returns open positions for all symbols.
|
|
558
|
+
- Call specifying a symbol, open positions should be received for.
|
|
559
|
+
- Call specifying a group of symbols, open positions should be received for.
|
|
560
|
+
- Call specifying a position ticket.
|
|
561
|
+
|
|
562
|
+
Args:
|
|
563
|
+
symbol (Optional[str]): Symbol name. Optional named parameter.
|
|
564
|
+
If a symbol is specified, the ticket parameter is ignored.
|
|
565
|
+
|
|
566
|
+
group (Optional[str]): The filter for arranging a group of necessary symbols.
|
|
567
|
+
Optional named parameter. If the group is specified,
|
|
568
|
+
the function returns only positions meeting a specified criteria
|
|
569
|
+
for a symbol name.
|
|
570
|
+
|
|
571
|
+
ticket (Optional[int]): Order ticket. Optional named parameter.
|
|
572
|
+
Unique number assigned to each order.
|
|
573
|
+
|
|
574
|
+
to_df (bool): If True, a DataFrame is returned.
|
|
575
|
+
|
|
576
|
+
Returns:
|
|
577
|
+
[List[TradeOrder] | None]:
|
|
578
|
+
- List of `TradeOrder` .
|
|
579
|
+
|
|
580
|
+
Notes:
|
|
581
|
+
The method allows receiving all history orders within a specified period.
|
|
582
|
+
The `group` parameter may contain several comma-separated conditions.
|
|
583
|
+
A condition can be set as a mask using '*'.
|
|
584
|
+
|
|
585
|
+
The logical negation symbol '!' can be used for exclusion.
|
|
586
|
+
All conditions are applied sequentially, which means conditions for inclusion
|
|
587
|
+
in a group should be specified first, followed by an exclusion condition.
|
|
588
|
+
|
|
589
|
+
For example, `group="*, !EUR"` means that deals for all symbols should be selected first
|
|
590
|
+
and the ones containing "EUR" in symbol names should be excluded afterward.
|
|
591
|
+
"""
|
|
592
|
+
|
|
593
|
+
if (symbol is not None) + (group is not None) + (ticket is not None) > 1:
|
|
594
|
+
raise ValueError(
|
|
595
|
+
"Only one of 'symbol', 'group', or 'ticket' can be specified as filter or None of them."
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
orders = None
|
|
599
|
+
if symbol is not None:
|
|
600
|
+
orders = client.orders_get(symbol)
|
|
601
|
+
elif group is not None:
|
|
602
|
+
orders = client.orders_get_by_group(group)
|
|
603
|
+
elif ticket is not None:
|
|
604
|
+
orders = client.order_get_by_ticket(ticket)
|
|
605
|
+
else:
|
|
606
|
+
orders = client.orders_get()
|
|
607
|
+
|
|
608
|
+
if orders is None or len(orders) == 0:
|
|
609
|
+
return None
|
|
610
|
+
return orders
|
|
611
|
+
|
|
612
|
+
def _fetch_history(
|
|
613
|
+
self,
|
|
614
|
+
fetch_type: str, # "deals" or "orders"
|
|
615
|
+
date_from: datetime,
|
|
616
|
+
date_to: Optional[datetime],
|
|
617
|
+
group: Optional[str],
|
|
618
|
+
ticket: Optional[int],
|
|
619
|
+
position: Optional[int],
|
|
620
|
+
to_df: bool,
|
|
621
|
+
drop_cols: List[str],
|
|
622
|
+
time_cols: List[str],
|
|
623
|
+
) -> Any:
|
|
624
|
+
date_to = date_to or datetime.now()
|
|
625
|
+
|
|
626
|
+
filters = [group, ticket, position]
|
|
627
|
+
if sum(f is not None for f in filters) > 1:
|
|
628
|
+
raise ValueError(
|
|
629
|
+
"Only one of 'position', 'group', or 'ticket' can be specified."
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
if fetch_type == "deals":
|
|
633
|
+
client_func = client.history_deals_get
|
|
634
|
+
pos_func = client.history_deals_get_by_pos
|
|
635
|
+
else:
|
|
636
|
+
client_func = client.history_orders_get
|
|
637
|
+
pos_func = client.history_orders_get_by_pos
|
|
638
|
+
|
|
639
|
+
data = None
|
|
640
|
+
if ticket:
|
|
641
|
+
data = client_func(ticket)
|
|
642
|
+
elif position:
|
|
643
|
+
data = pos_func(position)
|
|
644
|
+
elif group:
|
|
645
|
+
data = client_func(date_from, date_to, group)
|
|
646
|
+
else:
|
|
647
|
+
data = client_func(date_from, date_to)
|
|
648
|
+
|
|
649
|
+
if not data:
|
|
650
|
+
return None
|
|
651
|
+
|
|
652
|
+
if to_df:
|
|
653
|
+
from bbstrader.api import trade_object_to_df
|
|
654
|
+
|
|
655
|
+
df = trade_object_to_df(data)
|
|
656
|
+
for col in time_cols:
|
|
657
|
+
if col in df.columns:
|
|
658
|
+
df[col] = pd.to_datetime(df[col], unit="s")
|
|
659
|
+
|
|
660
|
+
df.drop(columns=[c for c in drop_cols if c in df.columns], inplace=True)
|
|
661
|
+
if fetch_type == "deals" and "time" in df.columns:
|
|
662
|
+
df.set_index("time", inplace=True)
|
|
663
|
+
return df
|
|
664
|
+
|
|
665
|
+
return data
|
|
666
|
+
|
|
667
|
+
def get_trades_history(
|
|
668
|
+
self,
|
|
669
|
+
date_from: datetime = datetime(2000, 1, 1),
|
|
670
|
+
date_to: Optional[datetime] = None,
|
|
671
|
+
group: Optional[str] = None,
|
|
672
|
+
ticket: Optional[int] = None, # TradeDeal.ticket
|
|
673
|
+
position: Optional[int] = None, # TradePosition.ticket
|
|
674
|
+
to_df: bool = True,
|
|
675
|
+
) -> Union[pd.DataFrame, List[TradeDeal] | None]:
|
|
676
|
+
"""
|
|
677
|
+
Get deals from trading history within the specified interval
|
|
678
|
+
with the ability to filter by `ticket` or `position`.
|
|
679
|
+
|
|
680
|
+
This method is useful if you need panda dataframe.
|
|
681
|
+
|
|
682
|
+
You can call this method in the following ways:
|
|
683
|
+
|
|
684
|
+
- Call with a `time interval`. Returns all deals falling within the specified interval.
|
|
685
|
+
|
|
686
|
+
- Call specifying the `order ticket`. Returns all deals having the specified `order ticket` in the `DEAL_ORDER` property.
|
|
687
|
+
|
|
688
|
+
- Call specifying the `position ticket`. Returns all deals having the specified `position ticket` in the `DEAL_POSITION_ID` property.
|
|
689
|
+
|
|
690
|
+
Args:
|
|
691
|
+
date_from (datetime): Date the bars are requested from.
|
|
692
|
+
Set by the `datetime` object or as a number of seconds elapsed since 1970-01-01.
|
|
693
|
+
Bars with the open time >= `date_from` are returned. Required unnamed parameter.
|
|
694
|
+
|
|
695
|
+
date_to (Optional[datetime]): Same as `date_from`.
|
|
696
|
+
|
|
697
|
+
group (Optional[str]): The filter for arranging a group of necessary symbols.
|
|
698
|
+
Optional named parameter. If the group is specified,
|
|
699
|
+
the function returns only positions meeting specified criteria
|
|
700
|
+
for a symbol name.
|
|
701
|
+
|
|
702
|
+
ticket (Optional[int]): Ticket of an order (stored in `DEAL_ORDER`) for which all deals should be received.
|
|
703
|
+
Optional parameter. If not specified, the filter is not applied.
|
|
704
|
+
|
|
705
|
+
position (Optional[int]): Ticket of a position (stored in `DEAL_POSITION_ID`) for which all deals should be received.
|
|
706
|
+
Optional parameter. If not specified, the filter is not applied.
|
|
707
|
+
|
|
708
|
+
to_df (bool): If True, a DataFrame is returned.
|
|
709
|
+
|
|
710
|
+
Returns:
|
|
711
|
+
Union[pd.DataFrame, Tuple[TradeDeal], None]:
|
|
712
|
+
- `TradeDeal` in the form of a named tuple structure (namedtuple) or pd.DataFrame().
|
|
713
|
+
|
|
714
|
+
Notes:
|
|
715
|
+
The method allows receiving all history orders within a specified period.
|
|
716
|
+
|
|
717
|
+
The `group` parameter may contain several comma-separated conditions.
|
|
718
|
+
|
|
719
|
+
A condition can be set as a mask using '*'.
|
|
720
|
+
|
|
721
|
+
The logical negation symbol '!' can be used for exclusion.
|
|
722
|
+
|
|
723
|
+
All conditions are applied sequentially, which means conditions for inclusion
|
|
724
|
+
in a group should be specified first, followed by an exclusion condition.
|
|
725
|
+
|
|
726
|
+
For example, `group="*, !EUR"` means that deals for all symbols should be selected first
|
|
727
|
+
and those containing "EUR" in symbol names should be excluded afterward.
|
|
728
|
+
|
|
729
|
+
Example:
|
|
730
|
+
>>> # Get the number of deals in history
|
|
731
|
+
>>> from datetime import datetime
|
|
732
|
+
>>> from_date = datetime(2020, 1, 1)
|
|
733
|
+
>>> to_date = datetime.now()
|
|
734
|
+
>>> account = Account()
|
|
735
|
+
>>> history = account.get_trades_history(from_date, to_date)
|
|
736
|
+
"""
|
|
737
|
+
|
|
738
|
+
return self._fetch_history(
|
|
739
|
+
fetch_type="deals",
|
|
740
|
+
drop_cols=["time_msc", "external_id"],
|
|
741
|
+
time_cols=["time"],
|
|
742
|
+
**dict(
|
|
743
|
+
date_from=date_from,
|
|
744
|
+
date_to=date_to,
|
|
745
|
+
group=group,
|
|
746
|
+
ticket=ticket,
|
|
747
|
+
position=position,
|
|
748
|
+
to_df=to_df,
|
|
749
|
+
),
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
def get_orders_history(
|
|
753
|
+
self,
|
|
754
|
+
date_from: datetime = datetime(2000, 1, 1),
|
|
755
|
+
date_to: Optional[datetime] = None,
|
|
756
|
+
group: Optional[str] = None,
|
|
757
|
+
ticket: Optional[int] = None, # order ticket
|
|
758
|
+
position: Optional[int] = None, # position ticket
|
|
759
|
+
to_df: bool = True,
|
|
760
|
+
) -> Union[pd.DataFrame, List[TradeOrder] | None]:
|
|
761
|
+
"""
|
|
762
|
+
Get orders 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]): Order ticket to filter results. Optional parameter.
|
|
786
|
+
If not specified, the filter is not applied.
|
|
787
|
+
|
|
788
|
+
position (Optional[int]): Ticket of a position (stored in `DEAL_POSITION_ID`) to filter results.
|
|
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 True, a CSV file will be created to save the history.
|
|
794
|
+
|
|
795
|
+
Returns:
|
|
796
|
+
Union[pd.DataFrame, List[TradeOrder], None]
|
|
797
|
+
- List of `TradeOrder` .
|
|
798
|
+
|
|
799
|
+
Notes:
|
|
800
|
+
The method allows receiving all history orders within a specified period.
|
|
801
|
+
|
|
802
|
+
The `group` parameter may contain several comma-separated conditions.
|
|
803
|
+
|
|
804
|
+
A condition can be set as a mask using '*'.
|
|
805
|
+
|
|
806
|
+
The logical negation symbol '!' can be used for exclusion.
|
|
807
|
+
|
|
808
|
+
All conditions are applied sequentially, which means conditions for inclusion
|
|
809
|
+
in a group should be specified first, followed by an exclusion condition.
|
|
810
|
+
|
|
811
|
+
For example, `group="*, !EUR"` means that deals for all symbols should be selected first
|
|
812
|
+
and those containing "EUR" in symbol names should be excluded afterward.
|
|
813
|
+
|
|
814
|
+
Example:
|
|
815
|
+
>>> # Get the number of deals in history
|
|
816
|
+
>>> from datetime import datetime
|
|
817
|
+
>>> from_date = datetime(2020, 1, 1)
|
|
818
|
+
>>> to_date = datetime.now()
|
|
819
|
+
>>> account = Account()
|
|
820
|
+
>>> history = account.get_orders_history(from_date, to_date)
|
|
821
|
+
"""
|
|
822
|
+
return self._fetch_history(
|
|
823
|
+
fetch_type="orders",
|
|
824
|
+
drop_cols=[
|
|
825
|
+
"time_expiration",
|
|
826
|
+
"type_time",
|
|
827
|
+
"state",
|
|
828
|
+
"position_by_id",
|
|
829
|
+
"reason",
|
|
830
|
+
"volume_current",
|
|
831
|
+
"price_stoplimit",
|
|
832
|
+
"sl",
|
|
833
|
+
"tp",
|
|
834
|
+
],
|
|
835
|
+
time_cols=["time_setup", "time_done"],
|
|
836
|
+
**dict(
|
|
837
|
+
date_from=date_from,
|
|
838
|
+
date_to=date_to,
|
|
839
|
+
group=group,
|
|
840
|
+
ticket=ticket,
|
|
841
|
+
position=position,
|
|
842
|
+
to_df=to_df,
|
|
843
|
+
),
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
def get_today_deals(self, id, group=None) -> List[TradeDeal]:
|
|
847
|
+
"""
|
|
848
|
+
Get all today deals for a specific symbol or group of symbols
|
|
849
|
+
|
|
850
|
+
Args:
|
|
851
|
+
id (int): strategy or expert id
|
|
852
|
+
group (str): Symbol or group or symbol
|
|
853
|
+
Returns:
|
|
854
|
+
List[TradeDeal]: List of today deals
|
|
855
|
+
"""
|
|
856
|
+
history = self.get_trades_history(group=group, to_df=False) or []
|
|
857
|
+
positions_ids = set([deal.position_id for deal in history if deal.magic == id])
|
|
858
|
+
today_deals = []
|
|
859
|
+
for position in positions_ids:
|
|
860
|
+
deal = self.get_trades_history(position=position, to_df=False) or []
|
|
861
|
+
if deal is not None and len(deal) == 2:
|
|
862
|
+
deal_time = datetime.fromtimestamp(deal[1].time)
|
|
863
|
+
if deal_time.date() == datetime.now().date():
|
|
864
|
+
today_deals.append(deal[1])
|
|
865
|
+
return today_deals
|