bbstrader 0.3.5__py3-none-any.whl → 0.3.6__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/__init__.py CHANGED
@@ -7,7 +7,14 @@ __author__ = "Bertin Balouki SIMYELI"
7
7
  __copyright__ = "2023-2025 Bertin Balouki SIMYELI"
8
8
  __email__ = "bertin@bbstrader.com"
9
9
  __license__ = "MIT"
10
- __version__ = "0.3.5"
10
+
11
+ from importlib.metadata import version, PackageNotFoundError
12
+
13
+ try:
14
+ __version__ = version("bbstrader")
15
+ except PackageNotFoundError:
16
+ __version__ = "unknown"
17
+
11
18
 
12
19
  from bbstrader import compat # noqa: F401
13
20
  from bbstrader import core # noqa: F401
@@ -17,3 +24,5 @@ from bbstrader import models # noqa: F401
17
24
  from bbstrader import trading # noqa: F401
18
25
  from bbstrader import tseries # noqa: F401
19
26
  from bbstrader.config import config_logger # noqa: F401
27
+
28
+ version = __version__
bbstrader/__main__.py CHANGED
@@ -10,6 +10,7 @@ from bbstrader.btengine.scripts import backtest
10
10
  from bbstrader.core.scripts import send_news_feed
11
11
  from bbstrader.metatrader.scripts import copy_trades
12
12
  from bbstrader.trading.scripts import execute_strategy
13
+ from . import __author__, __version__
13
14
 
14
15
 
15
16
  class _Module(Enum):
@@ -50,6 +51,10 @@ def main():
50
51
  if ("-h" in sys.argv or "--help" in sys.argv) and args.run is None:
51
52
  print(Fore.WHITE + USAGE_TEXT)
52
53
  sys.exit(0)
54
+ if ("-v" in sys.argv or "--version" in sys.argv):
55
+ print(Fore.GREEN + f"bbstrader version {__version__}")
56
+ print(Fore.WHITE + f"bbstrader maintained and supported by {__author__}")
57
+ sys.exit(0)
53
58
  try:
54
59
  match args.run:
55
60
  case _Module.COPIER.value:
bbstrader/apps/_copier.py CHANGED
@@ -431,7 +431,7 @@ class TradeCopierApp(object):
431
431
  or not dest["path"].get()
432
432
  ):
433
433
  messagebox.showerror(
434
- "Error", f"Destination account {i+1} details are incomplete."
434
+ "Error", f"Destination account {i + 1} details are incomplete."
435
435
  )
436
436
  return False
437
437
  if (
@@ -440,7 +440,7 @@ class TradeCopierApp(object):
440
440
  ):
441
441
  messagebox.showerror(
442
442
  "Error",
443
- f"Value is required for mode '{dest['mode'].get()}' in Destination {i+1}.",
443
+ f"Value is required for mode '{dest['mode'].get()}' in Destination {i + 1}.",
444
444
  )
445
445
  return False
446
446
  try:
@@ -451,7 +451,7 @@ class TradeCopierApp(object):
451
451
  except ValueError:
452
452
  messagebox.showerror(
453
453
  "Error",
454
- f"Invalid Value or Slippage for Destination {i+1}. "
454
+ f"Invalid Value or Slippage for Destination {i + 1}. "
455
455
  "Must be a number (Slippage must be an integer).",
456
456
  )
457
457
  return False
@@ -2,7 +2,7 @@ import string
2
2
  from abc import ABCMeta, abstractmethod
3
3
  from datetime import datetime
4
4
  from queue import Queue
5
- from typing import Dict, List, Literal, Union
5
+ from typing import Callable, Dict, List, Literal, Union
6
6
 
7
7
  import numpy as np
8
8
  import pandas as pd
@@ -11,19 +11,19 @@ from loguru import logger
11
11
 
12
12
  from bbstrader.btengine.data import DataHandler
13
13
  from bbstrader.btengine.event import Events, FillEvent, SignalEvent
14
- from bbstrader.metatrader.trade import generate_signal, TradeAction
15
14
  from bbstrader.config import BBSTRADER_DIR
16
15
  from bbstrader.metatrader import (
17
16
  Account,
18
17
  AdmiralMarktsGroup,
19
18
  MetaQuotes,
20
19
  PepperstoneGroupLimited,
21
- TradeOrder,
22
20
  Rates,
23
- TradeSignal,
21
+ SymbolType,
22
+ TradeOrder,
23
+ TradeSignal,
24
24
  TradingMode,
25
- SymbolType
26
25
  )
26
+ from bbstrader.metatrader.trade import TradeAction, generate_signal
27
27
  from bbstrader.models.optimization import optimized_weights
28
28
 
29
29
  __all__ = ["Strategy", "MT5Strategy"]
@@ -79,10 +79,11 @@ class MT5Strategy(Strategy):
79
79
  It is recommanded that every strategy specfic method to be a private method
80
80
  in order to avoid naming collusion.
81
81
  """
82
+
82
83
  tf: str
83
84
  id: int
84
85
  ID: int
85
-
86
+
86
87
  max_trades: Dict[str, int]
87
88
  risk_budget: Dict[str, float] | str | None
88
89
 
@@ -116,8 +117,10 @@ class MT5Strategy(Strategy):
116
117
  self.symbols = symbol_list
117
118
  self.mode = mode
118
119
  if self.mode not in [TradingMode.BACKTEST, TradingMode.LIVE]:
119
- raise ValueError(f"Mode must be an instance of {type(TradingMode)} not {type(self.mode)}")
120
-
120
+ raise ValueError(
121
+ f"Mode must be an instance of {type(TradingMode)} not {type(self.mode)}"
122
+ )
123
+
121
124
  self.risk_budget = self._check_risk_budget(**kwargs)
122
125
 
123
126
  self.max_trades = kwargs.get("max_trades", {s: 1 for s in self.symbols})
@@ -189,7 +192,7 @@ class MT5Strategy(Strategy):
189
192
  def _initialize_portfolio(self):
190
193
  self._orders = {}
191
194
  self._positions = {}
192
- self._trades = {}
195
+ self._trades = {}
193
196
  for symbol in self.symbols:
194
197
  self._positions[symbol] = {}
195
198
  self._orders[symbol] = {}
@@ -254,30 +257,40 @@ class MT5Strategy(Strategy):
254
257
  def signal(self, signal: int, symbol: str) -> TradeSignal:
255
258
  """
256
259
  Generate a ``TradeSignal`` object based on the signal value.
257
- Args:
258
- signal : An integer value representing the signal type:
259
- 0: BUY
260
- 1: SELL
261
- 2: EXIT_LONG
262
- 3: EXIT_SHORT
263
- 4: EXIT_ALL_POSITIONS
264
- 5: EXIT_ALL_ORDERS
265
- 6: EXIT_STOP
266
- 7: EXIT_LIMIT
267
-
268
- symbol : The symbol for the trade.
269
-
270
- Returns:
271
- TradeSignal : A ``TradeSignal`` object representing the trade signal.
272
-
273
- Note:
274
- This generate only common signals. For more complex signals, use `generate_signal` directly.
275
260
 
276
- Raises:
277
- ValueError : If the signal value is not between 0 and 7.
261
+ Parameters
262
+ ----------
263
+ signal : int
264
+ An integer value representing the signal type:
265
+ * 0: BUY
266
+ * 1: SELL
267
+ * 2: EXIT_LONG
268
+ * 3: EXIT_SHORT
269
+ * 4: EXIT_ALL_POSITIONS
270
+ * 5: EXIT_ALL_ORDERS
271
+ * 6: EXIT_STOP
272
+ * 7: EXIT_LIMIT
273
+ symbol : str
274
+ The symbol for the trade.
275
+
276
+ Returns
277
+ -------
278
+ TradeSignal
279
+ A ``TradeSignal`` object representing the trade signal.
280
+
281
+ Raises
282
+ ------
283
+ ValueError
284
+ If the signal value is not between 0 and 7.
285
+
286
+ Notes
287
+ -----
288
+ This generates only common signals. For more complex signals, use
289
+ ``generate_signal`` directly.
278
290
  """
291
+
279
292
  signal_id = getattr(self, "id", None) or getattr(self, "ID")
280
-
293
+
281
294
  match signal:
282
295
  case 0:
283
296
  return generate_signal(signal_id, symbol, TradeAction.BUY)
@@ -288,7 +301,9 @@ class MT5Strategy(Strategy):
288
301
  case 3:
289
302
  return generate_signal(signal_id, symbol, TradeAction.EXIT_SHORT)
290
303
  case 4:
291
- return generate_signal(signal_id, symbol, TradeAction.EXIT_ALL_POSITIONS)
304
+ return generate_signal(
305
+ signal_id, symbol, TradeAction.EXIT_ALL_POSITIONS
306
+ )
292
307
  case 5:
293
308
  return generate_signal(signal_id, symbol, TradeAction.EXIT_ALL_ORDERS)
294
309
  case 6:
@@ -296,8 +311,64 @@ class MT5Strategy(Strategy):
296
311
  case 7:
297
312
  return generate_signal(signal_id, symbol, TradeAction.EXIT_LIMIT)
298
313
  case _:
299
- raise ValueError(f"Invalid signal value: {signal}. Must be an integer between 0 and 7.")
314
+ raise ValueError(
315
+ f"Invalid signal value: {signal}. Must be an integer between 0 and 7."
316
+ )
300
317
 
318
+ def send_trade_repport(self, perf_analyzer: Callable, **kwargs):
319
+ """
320
+ Generates and sends a trade report message containing performance metrics for the current strategy.
321
+ This method retrieves the trade history for the current account, filters it by the strategy's ID,
322
+ computes performance metrics using the provided `perf_analyzer` callable, and formats the results
323
+ into a message. The message includes account information, strategy details, a timestamp, and
324
+ performance metrics. The message is then sent via Telegram using the specified bot token and chat ID.
325
+
326
+ Args:
327
+ perf_analyzer (Callable): A function or callable object that takes the filtered trade history
328
+ (as a DataFrame) and additional keyword arguments, and returns a DataFrame of performance metrics.
329
+ **kwargs: Additional keyword arguments, which may include
330
+ - Any other param requires by ``perf_analyzer``
331
+ """
332
+
333
+ from bbstrader.trading.utils import send_message
334
+
335
+ history = self.account.get_trades_history()
336
+ if history is None:
337
+ return
338
+
339
+ ID = getattr(self, "id", None) or getattr(self, "ID")
340
+ history = history[history["magic"] == ID]
341
+ performance = perf_analyzer(history, **kwargs)
342
+
343
+ account = self.kwargs.get("account", "MT5 Account")
344
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
345
+
346
+ header = (
347
+ f"==== TRADE REPORT =====\n\n"
348
+ f"ACCOUNT: {account}\n"
349
+ f"STRATEGY: {self.NAME}\n"
350
+ f"ID: {ID}\n"
351
+ f"DESCRIPTION: {self.DESCRIPTION}\n"
352
+ f"TIMESTAMP: {timestamp}\n\n"
353
+ f"📊 PERFORMANCE:\n"
354
+ )
355
+ metrics = performance.iloc[0].to_dict()
356
+
357
+ lines = []
358
+ for key, value in metrics.items():
359
+ if isinstance(value, float):
360
+ value = round(value, 4)
361
+ lines.append(f"{key:<15}: {value}")
362
+
363
+ performance_str = "\n".join(lines)
364
+ message = f"{header}{performance_str}"
365
+
366
+ send_message(
367
+ message=message,
368
+ telegram=True,
369
+ token=self.kwargs.get("bot_token"),
370
+ chat_id=self.kwargs.get("chat_id"),
371
+ )
301
372
 
302
373
  def perform_period_end_checks(self, *args, **kwargs):
303
374
  """
@@ -831,8 +902,10 @@ class MT5Strategy(Strategy):
831
902
  )
832
903
  return prices
833
904
  return np.array([])
834
-
835
- def get_active_orders(self, symbol: str, strategy_id: int, order_type: int = None) -> List[TradeOrder]:
905
+
906
+ def get_active_orders(
907
+ self, symbol: str, strategy_id: int, order_type: int = None
908
+ ) -> List[TradeOrder]:
836
909
  """
837
910
  Get the active orders for a given symbol and strategy.
838
911
 
@@ -850,7 +923,9 @@ class MT5Strategy(Strategy):
850
923
  Returns:
851
924
  List[TradeOrder] : A list of active orders for the given symbol and strategy.
852
925
  """
853
- orders = [o for o in self.orders if o.symbol == symbol and o.magic == strategy_id]
926
+ orders = [
927
+ o for o in self.orders if o.symbol == symbol and o.magic == strategy_id
928
+ ]
854
929
  if order_type is not None and len(orders) > 0:
855
930
  orders = [o for o in orders if o.type == order_type]
856
931
  return orders
@@ -866,7 +941,7 @@ class MT5Strategy(Strategy):
866
941
  elif len(prices) in range(2, self.max_trades[asset] + 1):
867
942
  price = np.mean(prices)
868
943
  if price is not None:
869
- if position == 0:
944
+ if position == 0:
870
945
  return self.calculate_pct_change(ask, price) >= th
871
946
  elif position == 1:
872
947
  return self.calculate_pct_change(bid, price) <= -th
@@ -920,7 +995,7 @@ class MT5Strategy(Strategy):
920
995
  Returns:
921
996
  mt5_equivalent : The MetaTrader 5 equivalent symbols for the symbols in the list.
922
997
  """
923
-
998
+
924
999
  account = Account(**kwargs)
925
1000
  mt5_symbols = account.get_symbols(symbol_type=symbol_type)
926
1001
  mt5_equivalent = []
@@ -933,7 +1008,7 @@ class MT5Strategy(Strategy):
933
1008
  mt5_equivalent.append(s)
934
1009
 
935
1010
  def _get_pepperstone_symbols():
936
- for s in mt5_symbols:
1011
+ for s in mt5_symbols:
937
1012
  for symbol in symbols:
938
1013
  if s.split(".")[0] == symbol:
939
1014
  mt5_equivalent.append(s)
@@ -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