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

Files changed (45) hide show
  1. bbstrader/__init__.py +11 -2
  2. bbstrader/__main__.py +6 -1
  3. bbstrader/apps/_copier.py +43 -40
  4. bbstrader/btengine/backtest.py +33 -28
  5. bbstrader/btengine/data.py +105 -81
  6. bbstrader/btengine/event.py +21 -22
  7. bbstrader/btengine/execution.py +51 -24
  8. bbstrader/btengine/performance.py +23 -12
  9. bbstrader/btengine/portfolio.py +40 -30
  10. bbstrader/btengine/scripts.py +13 -12
  11. bbstrader/btengine/strategy.py +396 -134
  12. bbstrader/compat.py +4 -3
  13. bbstrader/config.py +20 -36
  14. bbstrader/core/data.py +76 -48
  15. bbstrader/core/scripts.py +22 -21
  16. bbstrader/core/utils.py +13 -12
  17. bbstrader/metatrader/account.py +51 -26
  18. bbstrader/metatrader/analysis.py +30 -16
  19. bbstrader/metatrader/copier.py +75 -40
  20. bbstrader/metatrader/trade.py +29 -39
  21. bbstrader/metatrader/utils.py +5 -4
  22. bbstrader/models/nlp.py +83 -66
  23. bbstrader/trading/execution.py +45 -22
  24. bbstrader/tseries.py +158 -166
  25. {bbstrader-0.3.5.dist-info → bbstrader-0.3.7.dist-info}/METADATA +7 -21
  26. bbstrader-0.3.7.dist-info/RECORD +62 -0
  27. bbstrader-0.3.7.dist-info/top_level.txt +3 -0
  28. docs/conf.py +56 -0
  29. tests/__init__.py +0 -0
  30. tests/engine/__init__.py +1 -0
  31. tests/engine/test_backtest.py +58 -0
  32. tests/engine/test_data.py +536 -0
  33. tests/engine/test_events.py +300 -0
  34. tests/engine/test_execution.py +219 -0
  35. tests/engine/test_portfolio.py +308 -0
  36. tests/metatrader/__init__.py +0 -0
  37. tests/metatrader/test_account.py +1769 -0
  38. tests/metatrader/test_rates.py +292 -0
  39. tests/metatrader/test_risk_management.py +700 -0
  40. tests/metatrader/test_trade.py +439 -0
  41. bbstrader-0.3.5.dist-info/RECORD +0 -49
  42. bbstrader-0.3.5.dist-info/top_level.txt +0 -1
  43. {bbstrader-0.3.5.dist-info → bbstrader-0.3.7.dist-info}/WHEEL +0 -0
  44. {bbstrader-0.3.5.dist-info → bbstrader-0.3.7.dist-info}/entry_points.txt +0 -0
  45. {bbstrader-0.3.5.dist-info → bbstrader-0.3.7.dist-info}/licenses/LICENSE +0 -0
@@ -157,22 +157,38 @@ def check_mt5_connection(
157
157
  """
158
158
  Initialize the connection to the MetaTrader 5 terminal.
159
159
 
160
- Args:
161
- path (str, optional): The path to the MetaTrader 5 terminal executable file.
162
- Defaults to None (e.g., "C:/Program Files/MetaTrader 5/terminal64.exe").
163
- login (int, optional): The login ID of the trading account. Defaults to None.
164
- password (str, optional): The password of the trading account. Defaults to None.
165
- server (str, optional): The name of the trade server to which the client terminal is connected.
166
- Defaults to None.
167
- timeout (int, optional): Connection timeout in milliseconds. Defaults to 60_000.
168
- portable (bool, optional): If True, the portable mode of the terminal is used.
169
- Defaults to False (See https://www.metatrader5.com/en/terminal/help/start_advanced/start#portable).
170
-
171
- Notes:
172
- If you want to lunch multiple terminal instances:
173
- - Follow these instructions to lunch each terminal in portable mode first:
174
- https://www.metatrader5.com/en/terminal/help/start_advanced/start#configuration_file
160
+ Parameters
161
+ ----------
162
+ path : str, optional
163
+ Path to the MetaTrader 5 terminal executable file.
164
+ Defaults to ``None`` (e.g., ``"C:/Program Files/MetaTrader 5/terminal64.exe"``).
165
+ login : int, optional
166
+ The login ID of the trading account. Defaults to ``None``.
167
+ password : str, optional
168
+ The password of the trading account. Defaults to ``None``.
169
+ server : str, optional
170
+ The name of the trade server to which the client terminal is connected.
171
+ Defaults to ``None``.
172
+ timeout : int, optional
173
+ Connection timeout in milliseconds. Defaults to ``60_000``.
174
+ portable : bool, optional
175
+ If ``True``, the portable mode of the terminal is used.
176
+ Defaults to ``False``.
177
+ See: https://www.metatrader5.com/en/terminal/help/start_advanced/start#portable
178
+
179
+ Returns
180
+ -------
181
+ bool
182
+ ``True`` if the connection is successfully established, otherwise ``False``.
183
+
184
+ Notes
185
+ -----
186
+ If you want to launch multiple terminal instances:
187
+
188
+ * First, launch each terminal in **portable mode**.
189
+ * See instructions: https://www.metatrader5.com/en/terminal/help/start_advanced/start#configuration_file
175
190
  """
191
+
176
192
  if login is not None and server is not None:
177
193
  account_info = mt5.account_info()
178
194
  if account_info is not None:
@@ -201,9 +217,9 @@ def check_mt5_connection(
201
217
  else:
202
218
  init = mt5.initialize()
203
219
  if not init:
204
- raise_mt5_error(INIT_MSG)
220
+ raise_mt5_error(str(mt5.last_error()) + INIT_MSG)
205
221
  except Exception:
206
- raise_mt5_error(INIT_MSG)
222
+ raise_mt5_error(str(mt5.last_error()) + INIT_MSG)
207
223
  return init
208
224
 
209
225
 
@@ -1209,17 +1225,26 @@ class Account(object):
1209
1225
  def get_market_book(self, symbol: str) -> Tuple[BookInfo]:
1210
1226
  """
1211
1227
  Get the Market Depth content for a specific symbol.
1212
- Args:
1213
- symbol (str): Financial instrument name. Required unnamed parameter.
1214
- The symbol name should be specified in the same format as in the Market Watch window.
1215
-
1216
- Returns:
1217
- The Market Depth content as a tuple from BookInfo entries featuring order type, price and volume in lots.
1218
- Return None in case of an error.
1219
1228
 
1220
- Raises:
1221
- MT5TerminalError: A specific exception based on the error code.
1229
+ Parameters
1230
+ ----------
1231
+ symbol : str
1232
+ Financial instrument name. The symbol should be specified in the
1233
+ same format as in the Market Watch window.
1234
+
1235
+ Returns
1236
+ -------
1237
+ tuple of BookInfo or None
1238
+ The Market Depth content as a tuple of ``BookInfo`` entries
1239
+ (order type, price, and volume in lots).
1240
+ Returns ``None`` in case of an error.
1241
+
1242
+ Raises
1243
+ ------
1244
+ MT5TerminalError
1245
+ A specific exception based on the error code.
1222
1246
  """
1247
+
1223
1248
  try:
1224
1249
  book = mt5.market_book_get(symbol)
1225
1250
  return (
@@ -57,23 +57,37 @@ def display_volume_profile(
57
57
 
58
58
  This function retrieves historical price and volume data for a given symbol and
59
59
  plots a vertical volume profile chart showing the volume distribution across
60
- price levels. It highlights key levels such as:
61
- - Point of Control (POC): Price level with the highest traded volume.
62
- - Value Area High (VAH): Upper bound of the value area.
63
- - Value Area Low (VAL): Lower bound of the value area.
64
- - Current Price: Latest bid price from MetaTrader 5.
65
-
66
- Args:
67
- symbol (str): Market symbol (e.g., "AAPL", "EURUSD").
68
- path (str): Path to the historical data see ``bbstrader.metatrader.account.check_mt5_connection()``.
69
- timeframe (str, optional): Timeframe for each candle (default is "1m").
70
- bars (int, optional): Number of historical bars to fetch (default is 1440).
71
- bins (int, optional): Number of price bins for volume profile calculation (default is 100).
72
- va_percentage (float, optional): Percentage of total volume to define the value area (default is 0.7).
73
-
74
- Returns:
75
- None: Displays a matplotlib chart of the volume profile.
60
+ price levels.
61
+
62
+ Highlights
63
+ ----------
64
+ * **Point of Control (POC)**: Price level with the highest traded volume.
65
+ * **Value Area High (VAH)**: Upper bound of the value area.
66
+ * **Value Area Low (VAL)**: Lower bound of the value area.
67
+ * **Current Price**: Latest bid price from MetaTrader 5.
68
+
69
+ Parameters
70
+ ----------
71
+ symbol : str
72
+ Market symbol (e.g., ``"AAPL"``, ``"EURUSD"``).
73
+ path : str
74
+ Path to the historical data. See
75
+ ``bbstrader.metatrader.account.check_mt5_connection()``.
76
+ timeframe : str, optional
77
+ Timeframe for each candle. Default is ``"1m"``.
78
+ bars : int, optional
79
+ Number of historical bars to fetch. Default is ``1440``.
80
+ bins : int, optional
81
+ Number of price bins for volume profile calculation. Default is ``100``.
82
+ va_percentage : float, optional
83
+ Percentage of total volume to define the value area. Default is ``0.7``.
84
+
85
+ Returns
86
+ -------
87
+ None
88
+ Displays a matplotlib chart of the volume profile.
76
89
  """
90
+
77
91
  check_mt5_connection(path=path)
78
92
  df = _get_data(symbol, TIMEFRAMES[timeframe], bars)
79
93
  if df.empty:
@@ -1177,29 +1177,45 @@ def RunCopier(
1177
1177
  shutdown_event=None,
1178
1178
  log_queue=None,
1179
1179
  ):
1180
- """Initializes and runs a TradeCopier instance in a single process.
1180
+ """
1181
+ Initialize and run a TradeCopier instance in a single process.
1181
1182
 
1182
1183
  This function serves as a straightforward wrapper to start a copying session
1183
1184
  that handles one source account and one or more destination accounts
1184
- *sequentially* within the same thread. It does not create any new processes itself.
1185
+ sequentially within the same thread. It does not create any new processes itself.
1185
1186
 
1186
- This is useful for:
1187
- - Simpler, command-line based use cases.
1188
- - Scenarios where parallelism is not required.
1189
- - As the target for `RunMultipleCopier`, where each process handles a
1187
+ Use Cases
1188
+ ---------
1189
+ * Simpler, command-line based use cases.
1190
+ * Scenarios where parallelism is not required.
1191
+ * As the target for ``RunMultipleCopier``, where each process handles a
1190
1192
  full source-to-destinations session.
1191
1193
 
1192
- Args:
1193
- source (dict): Configuration dictionary for the source account.
1194
- destinations (list): A list of configuration dictionaries, one for each
1195
- destination account to be processed sequentially.
1196
- sleeptime (float): The time in seconds to wait after completing a full
1197
- cycle through all destinations.
1198
- start_time (str): The time of day to start copying (e.g., "08:00").
1199
- end_time (str): The time of day to stop copying (e.g., "22:00").
1200
- custom_logger: An optional custom logger instance.
1201
- shutdown_event (multiprocessing.Event): An event to signal shutdown.
1202
- log_queue (multiprocessing.Queue): A queue for log messages.
1194
+ Parameters
1195
+ ----------
1196
+ source : dict
1197
+ Configuration dictionary for the source account.
1198
+ destinations : list
1199
+ A list of configuration dictionaries, one for each
1200
+ destination account to be processed sequentially.
1201
+ sleeptime : float
1202
+ The time in seconds to wait after completing a full
1203
+ cycle through all destinations.
1204
+ start_time : str
1205
+ The time of day to start copying (e.g., ``"08:00"``).
1206
+ end_time : str
1207
+ The time of day to stop copying (e.g., ``"22:00"``).
1208
+ custom_logger : logging.Logger, optional
1209
+ An optional custom logger instance.
1210
+ shutdown_event : multiprocessing.Event, optional
1211
+ An event to signal shutdown.
1212
+ log_queue : multiprocessing.Queue, optional
1213
+ A queue for log messages.
1214
+
1215
+ Returns
1216
+ -------
1217
+ None
1218
+ Runs until stopped via ``shutdown_event`` or external interruption.
1203
1219
  """
1204
1220
  copier = TradeCopier(
1205
1221
  source,
@@ -1224,7 +1240,8 @@ def RunMultipleCopier(
1224
1240
  custom_logger=None,
1225
1241
  log_queue=None,
1226
1242
  ):
1227
- """Manages multiple, independent trade copying sessions in parallel.
1243
+ """
1244
+ Manage multiple, independent trade copying sessions in parallel.
1228
1245
 
1229
1246
  This function acts as a high-level manager that takes a list of account
1230
1247
  setups and creates a separate, dedicated process for each one. Each process
@@ -1232,28 +1249,46 @@ def RunMultipleCopier(
1232
1249
  destination accounts.
1233
1250
 
1234
1251
  The parallelism occurs at the **source account level**. Within each spawned
1235
- process, the destinations for that source are handled sequentially by `RunCopier`.
1236
-
1237
- Example `accounts` structure:
1238
- [
1239
- { "source": {...}, "destinations": [{...}, {...}] }, # -> Process 1
1240
- { "source": {...}, "destinations": [{...}] } # -> Process 2
1241
- ]
1242
-
1243
- Args:
1244
- accounts (List[dict]): A list of account configurations. Each item in the
1245
- list must be a dictionary with a 'source' key and a 'destinations' key.
1246
- sleeptime (float): The sleep time passed down to each `RunCopier` process.
1247
- start_delay (float): A delay in seconds between starting each new process.
1248
- This helps prevent resource contention by staggering the initialization
1249
- of multiple MetaTrader 5 terminals.
1250
- start_time (str): The start time passed down to each `RunCopier` process.
1251
- end_time (str): The end time passed down to each `RunCopier` process.
1252
- shutdown_event (multiprocessing.Event): An event to signal shutdown to all
1253
- child processes.
1254
- custom_logger: An optional custom logger instance.
1255
- log_queue (multiprocessing.Queue): A queue for aggregating log messages
1256
- from all child processes.
1252
+ process, the destinations for that source are handled sequentially by
1253
+ ``RunCopier``.
1254
+
1255
+ Example
1256
+ -------
1257
+ An example ``accounts`` structure:
1258
+
1259
+ .. code-block:: python
1260
+
1261
+ accounts = [
1262
+ {"source": {...}, "destinations": [{...}, {...}]}, # -> Process 1
1263
+ {"source": {...}, "destinations": [{...}]} # -> Process 2
1264
+ ]
1265
+
1266
+ Parameters
1267
+ ----------
1268
+ accounts : list of dict
1269
+ A list of account configurations. Each item must be a dictionary with
1270
+ a ``source`` key and a ``destinations`` key.
1271
+ sleeptime : float, optional
1272
+ The sleep time passed down to each ``RunCopier`` process.
1273
+ start_delay : float, optional
1274
+ A delay in seconds between starting each new process.
1275
+ Helps prevent resource contention by staggering the initialization of
1276
+ multiple MetaTrader 5 terminals.
1277
+ start_time : str, optional
1278
+ The start time passed down to each ``RunCopier`` process.
1279
+ end_time : str, optional
1280
+ The end time passed down to each ``RunCopier`` process.
1281
+ shutdown_event : multiprocessing.Event, optional
1282
+ An event to signal shutdown to all child processes.
1283
+ custom_logger : logging.Logger, optional
1284
+ An optional custom logger instance.
1285
+ log_queue : multiprocessing.Queue, optional
1286
+ A queue for aggregating log messages from all child processes.
1287
+
1288
+ Returns
1289
+ -------
1290
+ None
1291
+ Runs until stopped via ``shutdown_event`` or external interruption.
1257
1292
  """
1258
1293
  processes = []
1259
1294
 
@@ -95,27 +95,16 @@ class TradeSignal:
95
95
  """
96
96
  Represents a trading signal generated by a trading system or strategy.
97
97
 
98
+ Notes
99
+ -----
98
100
  Attributes:
99
- id (int):
100
- A unique identifier for the trade signal or the strategy.
101
101
 
102
- symbol (str):
103
- The trading symbol (e.g., stock ticker, forex pair, crypto asset)
104
- related to the signal.
105
-
106
- action (TradeAction):
107
- The trading action to perform.
108
- Must be an instance of the `TradeAction` enum (e.g., BUY, SELL).
109
-
110
- price (float, optional):
111
- The price at which the trade should be executed.
112
-
113
- stoplimit (float, optional):
114
- A stop-limit price for the trade.
115
- Must not be set without specifying a price.
116
-
117
- comment (str, optional):
118
- An optional comment or description related to the trade signal.
102
+ - id (int): A unique identifier for the trade signal or the strategy.
103
+ - symbol (str): The trading symbol (e.g., stock ticker, forex pair, crypto asset).
104
+ - action (TradeAction): The trading action to perform. Must be an instance of the ``TradeAction`` enum (e.g., BUY, SELL).
105
+ - price (float, optional): The price at which the trade should be executed.
106
+ - stoplimit (float, optional): A stop-limit price for the trade. Must not be set without specifying a price.
107
+ - comment (str, optional): An optional comment or description related to the trade signal.
119
108
  """
120
109
 
121
110
  id: int
@@ -781,14 +770,14 @@ class Trade(RiskManagement):
781
770
  self.check_order(request)
782
771
  result = self.send_order(request)
783
772
  except Exception as e:
784
- msg = trade_retcode_message(result.retcode) if result else "N/A"
773
+ msg = trade_retcode_message(result.retcode) if result else "N/A"
785
774
  LOGGER.error(f"Trade Order Request, {msg}{addtionnal}, {e}")
786
775
  if result and result.retcode != Mt5.TRADE_RETCODE_DONE:
787
776
  if result.retcode == Mt5.TRADE_RETCODE_INVALID_FILL: # 10030
788
777
  for fill in FILLING_TYPE:
789
778
  request["type_filling"] = fill
790
779
  result = self.send_order(request)
791
- if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
780
+ if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
792
781
  break
793
782
  elif result.retcode == Mt5.TRADE_RETCODE_INVALID_VOLUME: # 10014
794
783
  new_volume = int(request["volume"])
@@ -812,14 +801,14 @@ class Trade(RiskManagement):
812
801
  self.check_order(request)
813
802
  result = self.send_order(request)
814
803
  except Exception as e:
815
- msg = trade_retcode_message(result.retcode) if result else "N/A"
804
+ msg = trade_retcode_message(result.retcode) if result else "N/A"
816
805
  LOGGER.error(f"Trade Order Request, {msg}{addtionnal}, {e}")
817
- if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
806
+ if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
818
807
  break
819
808
  tries += 1
820
809
  # Print the result
821
- if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
822
- msg = trade_retcode_message(result.retcode)
810
+ if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
811
+ msg = trade_retcode_message(result.retcode)
823
812
  LOGGER.info(f"Trade Order {msg}{addtionnal}")
824
813
  if type != "BMKT" or type != "SMKT":
825
814
  self.opened_orders.append(result.order)
@@ -855,7 +844,7 @@ class Trade(RiskManagement):
855
844
  LOGGER.info(pos_info)
856
845
  return True
857
846
  else:
858
- msg = trade_retcode_message(result.retcode) if result else "N/A"
847
+ msg = trade_retcode_message(result.retcode) if result else "N/A"
859
848
  LOGGER.error(
860
849
  f"Unable to Open Position, RETCODE={result.retcode}: {msg}{addtionnal}"
861
850
  )
@@ -1332,10 +1321,10 @@ class Trade(RiskManagement):
1332
1321
  self.check_order(request)
1333
1322
  result = self.send_order(request)
1334
1323
  except Exception as e:
1335
- msg = trade_retcode_message(result.retcode) if result else "N/A"
1324
+ msg = trade_retcode_message(result.retcode) if result else "N/A"
1336
1325
  LOGGER.error(f"Break-Even Order Request, {msg}{addtionnal}, Error: {e}")
1337
- if result and result.retcode != Mt5.TRADE_RETCODE_DONE:
1338
- msg = trade_retcode_message(result.retcode)
1326
+ if result and result.retcode != Mt5.TRADE_RETCODE_DONE:
1327
+ msg = trade_retcode_message(result.retcode)
1339
1328
  if result.retcode != Mt5.TRADE_RETCODE_NO_CHANGES:
1340
1329
  LOGGER.error(
1341
1330
  f"Break-Even Order Request, Position: #{tiket}, RETCODE={result.retcode}: {msg}{addtionnal}"
@@ -1350,15 +1339,15 @@ class Trade(RiskManagement):
1350
1339
  self.check_order(request)
1351
1340
  result = self.send_order(request)
1352
1341
  except Exception as e:
1353
- msg = trade_retcode_message(result.retcode) if result else "N/A"
1342
+ msg = trade_retcode_message(result.retcode) if result else "N/A"
1354
1343
  LOGGER.error(
1355
1344
  f"Break-Even Order Request, {msg}{addtionnal}, Error: {e}"
1356
1345
  )
1357
- if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
1346
+ if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
1358
1347
  break
1359
1348
  tries += 1
1360
- if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
1361
- msg = trade_retcode_message(result.retcode)
1349
+ if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
1350
+ msg = trade_retcode_message(result.retcode)
1362
1351
  LOGGER.info(f"Break-Even Order {msg}{addtionnal}")
1363
1352
  info = f"Stop loss set to Break-even, Position: #{tiket}, Symbol: {self.symbol}, Price: @{round(price, 5)}"
1364
1353
  LOGGER.info(info)
@@ -1434,7 +1423,7 @@ class Trade(RiskManagement):
1434
1423
  """
1435
1424
  ticket = request[type]
1436
1425
  addtionnal = f", SYMBOL={self.symbol}"
1437
- result = None
1426
+ result = None
1438
1427
  try:
1439
1428
  self.check_order(request)
1440
1429
  result = self.send_order(request)
@@ -1839,15 +1828,16 @@ class Trade(RiskManagement):
1839
1828
 
1840
1829
  def sleep_time(self, weekend=False):
1841
1830
  if weekend:
1842
- # claculate number of minute from the friday and to monday start
1843
- friday_time = datetime.strptime(self.current_time(), "%H:%M")
1831
+ # calculate number of minute from now and monday start
1832
+ multiplyer = {"friday": 3, "saturday": 2, "sunday": 1}
1833
+ current_time = datetime.strptime(self.current_time(), "%H:%M")
1844
1834
  monday_time = datetime.strptime(self.start, "%H:%M")
1845
- intra_day_diff = (monday_time - friday_time).total_seconds() // 60
1846
- inter_day_diff = 3 * 24 * 60
1835
+ intra_day_diff = (monday_time - current_time).total_seconds() // 60
1836
+ inter_day_diff = multiplyer[datetime.now().strftime("%A").lower()] * 24 * 60
1847
1837
  total_minutes = inter_day_diff + intra_day_diff
1848
1838
  return total_minutes
1849
1839
  else:
1850
- # claculate number of minute from the end to the start
1840
+ # calculate number of minute from the end to the start
1851
1841
  start = datetime.strptime(self.start, "%H:%M")
1852
1842
  end = datetime.strptime(self.current_time(), "%H:%M")
1853
1843
  minutes = (end - start).total_seconds() // 60
@@ -607,7 +607,8 @@ class AutoTradingDisabled(MT5TerminalError):
607
607
  class InternalFailError(MT5TerminalError):
608
608
  """Base exception class for internal IPC errors."""
609
609
 
610
- pass
610
+ def __init__(self, code, message):
611
+ super().__init__(code, message)
611
612
 
612
613
 
613
614
  class InternalFailSend(InternalFailError):
@@ -700,9 +701,9 @@ def raise_mt5_error(message: Optional[str] = None):
700
701
  """
701
702
  if message and isinstance(message, Exception):
702
703
  message = str(message)
703
- error = _ERROR_CODE_TO_EXCEPTION_.get(MT5.last_error()[0])
704
- if error is not None:
705
- raise Exception(f"{error(None)} {message or MT5.last_error()[1]}")
704
+ exception = _ERROR_CODE_TO_EXCEPTION_.get(MT5.last_error()[0])
705
+ if exception is not None:
706
+ raise exception(f"{message or MT5.last_error()[1]}")
706
707
  else:
707
708
  raise Exception(f"{message or MT5.last_error()[1]}")
708
709