bbstrader 0.3.1__py3-none-any.whl → 0.3.3__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.

@@ -1,12 +1,10 @@
1
1
  import os
2
2
  import re
3
3
  import urllib.request
4
- from datetime import datetime
4
+ from datetime import datetime, timedelta
5
5
  from typing import Any, Dict, List, Literal, Optional, Tuple, Union
6
6
 
7
7
  import pandas as pd
8
- from currency_converter import SINGLE_DAY_ECB_URL, CurrencyConverter
9
-
10
8
  from bbstrader.metatrader.utils import (
11
9
  AccountInfo,
12
10
  BookInfo,
@@ -23,6 +21,7 @@ from bbstrader.metatrader.utils import (
23
21
  TradeRequest,
24
22
  raise_mt5_error,
25
23
  )
24
+ from currency_converter import SINGLE_DAY_ECB_URL, CurrencyConverter
26
25
 
27
26
  try:
28
27
  import MetaTrader5 as mt5
@@ -33,6 +32,7 @@ except ImportError:
33
32
  __all__ = [
34
33
  "Account",
35
34
  "Broker",
35
+ "MetaQuotes",
36
36
  "AdmiralMarktsGroup",
37
37
  "JustGlobalMarkets",
38
38
  "PepperstoneGroupLimited",
@@ -41,6 +41,7 @@ __all__ = [
41
41
  ]
42
42
 
43
43
  __BROKERS__ = {
44
+ "MQL": "MetaQuotes Ltd.",
44
45
  "AMG": "Admirals Group AS",
45
46
  "JGM": "Just Global Markets Ltd.",
46
47
  "FTMO": "FTMO S.R.O.",
@@ -54,9 +55,7 @@ BROKERS_TIMEZONES = {
54
55
  "PGL": "Europe/Helsinki",
55
56
  }
56
57
 
57
- _ADMIRAL_MARKETS_URL_ = (
58
- "https://cabinet.a-partnership.com/visit/?bta=35537&brand=admiralmarkets"
59
- )
58
+ _ADMIRAL_MARKETS_URL_ = "https://one.justmarkets.link/a/tufvj0xugm/registration/trader"
60
59
  _JUST_MARKETS_URL_ = "https://one.justmarkets.link/a/tufvj0xugm/registration/trader"
61
60
  _FTMO_URL_ = "https://trader.ftmo.com/?affiliates=JGmeuQqepAZLMcdOEQRp"
62
61
  _ADMIRAL_MARKETS_PRODUCTS_ = [
@@ -69,14 +68,13 @@ _ADMIRAL_MARKETS_PRODUCTS_ = [
69
68
  ]
70
69
  _JUST_MARKETS_PRODUCTS_ = ["Stocks", "Crypto", "indices", "Commodities", "Forex"]
71
70
 
72
- SUPPORTED_BROKERS = [__BROKERS__[b] for b in {"AMG", "JGM", "FTMO"}]
71
+ SUPPORTED_BROKERS = [__BROKERS__[b] for b in {"MQL", "AMG", "JGM", "FTMO"}]
73
72
  INIT_MSG = (
74
- f"\n* Ensure you have a good and stable internet connexion\n"
75
- f"* Ensure you have an activete MT5 terminal install on your machine\n"
76
- f"* Ensure you have an active MT5 Account with {' or '.join(SUPPORTED_BROKERS)}\n"
77
- f"* If you want to trade {', '.join(_ADMIRAL_MARKETS_PRODUCTS_)}, See [{_ADMIRAL_MARKETS_URL_}]\n"
78
- f"* If you want to trade {', '.join(_JUST_MARKETS_PRODUCTS_)}, See [{_JUST_MARKETS_URL_}]\n"
79
- f"* If you are looking for a prop firm, See [{_FTMO_URL_}]\n"
73
+ f"\n* Check your internet connection\n"
74
+ f"* Make sure MT5 is installed and active\n"
75
+ f"* Looking for a boker? See [{_ADMIRAL_MARKETS_URL_}] "
76
+ f"or [{_JUST_MARKETS_URL_}]\n"
77
+ f"* Looking for a prop firm? See [{_FTMO_URL_}]\n"
80
78
  )
81
79
 
82
80
  amg_url = _ADMIRAL_MARKETS_URL_
@@ -138,13 +136,22 @@ AMG_EXCHANGES = {
138
136
  }
139
137
 
140
138
 
141
- def check_mt5_connection(**kwargs):
139
+ def check_mt5_connection(
140
+ *,
141
+ path=None,
142
+ login=None,
143
+ password=None,
144
+ server=None,
145
+ timeout=60_000,
146
+ portable=False,
147
+ **kwargs,
148
+ ) -> bool:
142
149
  """
143
150
  Initialize the connection to the MetaTrader 5 terminal.
144
151
 
145
152
  Args:
146
153
  path (str, optional): The path to the MetaTrader 5 terminal executable file.
147
- Defaults to None (e.g., "C:\\Program Files\\MetaTrader 5\\terminal64.exe").
154
+ Defaults to None (e.g., "C:/Program Files/MetaTrader 5/terminal64.exe").
148
155
  login (int, optional): The login ID of the trading account. Defaults to None.
149
156
  password (str, optional): The password of the trading account. Defaults to None.
150
157
  server (str, optional): The name of the trade server to which the client terminal is connected.
@@ -158,13 +165,13 @@ def check_mt5_connection(**kwargs):
158
165
  - Follow these instructions to lunch each terminal in portable mode first:
159
166
  https://www.metatrader5.com/en/terminal/help/start_advanced/start#configuration_file
160
167
  """
161
- path = kwargs.get("path", None)
162
- login = kwargs.get("login", None)
163
- password = kwargs.get("password", None)
164
- server = kwargs.get("server", None)
165
- timeout = kwargs.get("timeout", 60_000)
166
- portable = kwargs.get("portable", False)
167
-
168
+ if login is not None and server is not None:
169
+ account_info = mt5.account_info()
170
+ if account_info is not None:
171
+ if account_info.login == login and account_info.server == server:
172
+ return True
173
+
174
+ init = False
168
175
  if path is None and (login or password or server):
169
176
  raise ValueError(
170
177
  "You must provide a path to the terminal executable file"
@@ -189,6 +196,7 @@ def check_mt5_connection(**kwargs):
189
196
  raise_mt5_error(INIT_MSG)
190
197
  except Exception:
191
198
  raise_mt5_error(INIT_MSG)
199
+ return init
192
200
 
193
201
 
194
202
  def shutdown_mt5():
@@ -221,6 +229,11 @@ class Broker(object):
221
229
  return f"{self.__class__.__name__}({self.name})"
222
230
 
223
231
 
232
+ class MetaQuotes(Broker):
233
+ def __init__(self, **kwargs):
234
+ super().__init__(__BROKERS__["MQL"], **kwargs)
235
+
236
+
224
237
  class AdmiralMarktsGroup(Broker):
225
238
  def __init__(self, **kwargs):
226
239
  super().__init__(__BROKERS__["AMG"], **kwargs)
@@ -262,6 +275,7 @@ class AMP(Broker): ...
262
275
 
263
276
  BROKERS: Dict[str, Broker] = {
264
277
  "FTMO": FTMO(),
278
+ "MQL": MetaQuotes(),
265
279
  "AMG": AdmiralMarktsGroup(),
266
280
  "JGM": JustGlobalMarkets(),
267
281
  "PGL": PepperstoneGroupLimited(),
@@ -391,6 +405,7 @@ class Account(object):
391
405
  password: Optional[str] = None,
392
406
  server: Optional[str] = None,
393
407
  timeout: Optional[int] = 60_000,
408
+ path: Optional[str] = None,
394
409
  ) -> Union[AccountInfo, None]:
395
410
  """
396
411
  Get info on the current trading account or a specific account .
@@ -408,6 +423,8 @@ class Account(object):
408
423
  If not specified, the value of 60 000 (60 seconds) is applied.
409
424
  If the connection is not established within the specified time,
410
425
  the call is forcibly terminated and the exception is generated.
426
+ path (str, optional): The path to the MetaTrader 5 terminal executable file.
427
+ Defaults to None (e.g., "C:/Program Files/MetaTrader 5/terminal64.exe").
411
428
 
412
429
  Returns:
413
430
  - AccountInfo in the form of a Namedtuple structure.
@@ -419,6 +436,15 @@ class Account(object):
419
436
  # connect to the trade account specifying a password and a server
420
437
  if account is not None and password is not None and server is not None:
421
438
  try:
439
+ # If a path is provided, initialize the MT5 terminal with it
440
+ if path is not None:
441
+ check_mt5_connection(
442
+ path=path,
443
+ login=account,
444
+ password=password,
445
+ server=server,
446
+ timeout=timeout,
447
+ )
422
448
  authorized = mt5.login(
423
449
  account, password=password, server=server, timeout=timeout
424
450
  )
@@ -1079,6 +1105,39 @@ class Account(object):
1079
1105
  except Exception as e:
1080
1106
  raise_mt5_error(e)
1081
1107
 
1108
+ def calculate_profit(
1109
+ self,
1110
+ action: Literal["buy", "sell"],
1111
+ symbol: str,
1112
+ lot: float,
1113
+ price_open: float,
1114
+ price_close: float,
1115
+ ) -> float:
1116
+ """
1117
+ Calculate profit in the account currency for a specified trading operation.
1118
+
1119
+ Args:
1120
+ action (str): The trading action, either 'buy' or 'sell'.
1121
+ symbol (str): The symbol of the financial instrument.
1122
+ lot (float): The lot size of the order.
1123
+ price_open (float): The price at which to position was opened.
1124
+ price_close (float): The price at which to position was closed.
1125
+
1126
+ Returns:
1127
+ float: The profit value
1128
+
1129
+ Raises:
1130
+ MT5TerminalError: A specific exception based on the error code.
1131
+
1132
+ """
1133
+ actions = {"buy": mt5.ORDER_TYPE_BUY, "sell": mt5.ORDER_TYPE_SELL}
1134
+ try:
1135
+ return mt5.order_calc_profit(
1136
+ actions[action], symbol, lot, price_open, price_close
1137
+ )
1138
+ except Exception as e:
1139
+ raise_mt5_error(e)
1140
+
1082
1141
  def check_order(self, request: Dict[str, Any]) -> OrderCheckResult:
1083
1142
  """
1084
1143
  Check funds sufficiency for performing a required trading operation.
@@ -1327,7 +1386,7 @@ class Account(object):
1327
1386
  group: Optional[str] = None,
1328
1387
  ticket: Optional[int] = None,
1329
1388
  to_df: bool = False,
1330
- ) -> Union[pd.DataFrame, Tuple[TradeOrder], None]:
1389
+ ) -> Union[pd.DataFrame, Tuple[TradeOrder]]:
1331
1390
  """
1332
1391
  Get active orders with the ability to filter by symbol or ticket.
1333
1392
  There are four call options:
@@ -1527,3 +1586,29 @@ class Account(object):
1527
1586
  else:
1528
1587
  history_orders = [TradeOrder(**td._asdict()) for td in history_orders]
1529
1588
  return tuple(history_orders)
1589
+
1590
+ def get_today_deals(self, id, group=None) -> List[TradeDeal]:
1591
+ """
1592
+ Get all today deals for a specific symbol or group of symbols
1593
+
1594
+ Args:
1595
+ id (int): strategy or expert id
1596
+ group (str): Symbol or group or symbol
1597
+ Returns:
1598
+ List[TradeDeal]: List of today deals
1599
+ """
1600
+ date_from = datetime.now() - timedelta(days=2)
1601
+ history = (
1602
+ self.get_trades_history(date_from=date_from, group=group, to_df=False) or []
1603
+ )
1604
+ positions_ids = set([deal.position_id for deal in history if deal.magic == id])
1605
+ today_deals = []
1606
+ for position in positions_ids:
1607
+ deal = self.get_trades_history(
1608
+ date_from=date_from, position=position, to_df=False
1609
+ )
1610
+ if deal is not None and len(deal) == 2:
1611
+ deal_time = datetime.fromtimestamp(deal[1].time)
1612
+ if deal_time.date() == datetime.now().date():
1613
+ today_deals.append(deal[1])
1614
+ return today_deals