bbstrader 0.2.9__py3-none-any.whl → 0.2.92__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,16 +1,23 @@
1
+ import multiprocessing as mp
1
2
  import time
2
3
  import traceback
3
- import MetaTrader5 as MT5
4
4
  from datetime import datetime
5
- from logging import Logger
6
5
  from typing import Dict, List, Literal, Optional
7
6
 
7
+ from loguru import logger as log
8
+
8
9
  from bbstrader.btengine.strategy import MT5Strategy, Strategy
9
- from bbstrader.metatrader.account import check_mt5_connection
10
+ from bbstrader.config import BBSTRADER_DIR
11
+ from bbstrader.core.utils import TradeAction
12
+ from bbstrader.metatrader.account import Account, check_mt5_connection
10
13
  from bbstrader.metatrader.trade import Trade
11
- from bbstrader.metatrader.account import Account
12
14
  from bbstrader.trading.scripts import send_message
13
- from bbstrader.core.utils import TradeAction
15
+
16
+ try:
17
+ import MetaTrader5 as MT5
18
+ except ImportError:
19
+ import bbstrader.compat # noqa: F401
20
+
14
21
 
15
22
  __all__ = ["MT5ExecutionEngine", "TWSExecutionEngine"]
16
23
 
@@ -31,6 +38,7 @@ _TF_MAPPING = {
31
38
  }
32
39
 
33
40
  TradingDays = ["monday", "tuesday", "wednesday", "thursday", "friday"]
41
+ WEEK_DAYS = TradingDays + ["saturday", "sunday"]
34
42
 
35
43
  BUYS = ["BMKT", "BLMT", "BSTP", "BSTPLMT"]
36
44
  SELLS = ["SMKT", "SLMT", "SSTP", "SSTPLMT"]
@@ -77,6 +85,13 @@ NON_EXEC_RETCODES = {
77
85
  "SMKT": [MT5.TRADE_RETCODE_LONG_ONLY] + COMMON_RETCODES,
78
86
  }
79
87
 
88
+ log.add(
89
+ f"{BBSTRADER_DIR}/logs/execution.log",
90
+ enqueue=True,
91
+ level="INFO",
92
+ format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {name} | {message}",
93
+ )
94
+
80
95
 
81
96
  def _mt5_execution(
82
97
  symbol_list,
@@ -99,18 +114,22 @@ def _mt5_execution(
99
114
  comment,
100
115
  **kwargs,
101
116
  ):
117
+ logger = kwargs.get("logger", log)
118
+
102
119
  def _print_exc(dm, msg):
103
120
  traceback.print_exc() if dm else logger.error(msg)
104
121
 
105
122
  try:
106
123
  symbols = symbol_list.copy()
107
124
  time_frame = kwargs.get("time_frame", "15m")
125
+ daily_risk = kwargs.get("daily_risk")
108
126
  STRATEGY = kwargs.get("strategy_name")
109
127
  mtrades = kwargs.get("max_trades")
110
128
  notify = kwargs.get("notify", False)
111
129
  signal_tickers = kwargs.get("signal_tickers", symbols)
112
130
  debug_mode = kwargs.get("debug_mode", False)
113
131
  delay = kwargs.get("delay", 0)
132
+ ACCOUNT = kwargs.get("account", "MT5 Account")
114
133
  if notify:
115
134
  telegram = kwargs.get("telegram", False)
116
135
  bot_token = kwargs.get("bot_token")
@@ -124,9 +143,6 @@ def _mt5_execution(
124
143
  elif isinstance(expert_ids, int):
125
144
  expert_ids = [expert_ids]
126
145
 
127
- logger: Logger = kwargs.get("logger")
128
- if logger is None:
129
- logger: Logger = trades_instances[symbols[0]].logger
130
146
  max_trades = {
131
147
  symbol: mtrades[symbol]
132
148
  if mtrades is not None and symbol in mtrades
@@ -136,8 +152,14 @@ def _mt5_execution(
136
152
  if comment is None:
137
153
  trade = trades_instances[symbols[0]]
138
154
  comment = f"{trade.expert_name}@{trade.version}"
155
+
156
+ if period.lower() == "24/7":
157
+ trading_days = WEEK_DAYS
139
158
  except Exception:
140
- _print_exc(debug_mode, f"Initializing Execution Engine, STRATEGY={STRATEGY}")
159
+ _print_exc(
160
+ debug_mode,
161
+ f"Initializing Execution Engine, STRATEGY={STRATEGY}, ACCOUNT={ACCOUNT}",
162
+ )
141
163
  return
142
164
 
143
165
  def update_risk(weights):
@@ -146,7 +168,8 @@ def _mt5_execution(
146
168
  if symbol not in weights:
147
169
  continue
148
170
  trade = trades_instances[symbol]
149
- trade.dailydd = round(weights[symbol], 5)
171
+ dailydd = round(weights[symbol] * daily_risk, 5)
172
+ trade.dailydd = dailydd
150
173
 
151
174
  def check_retcode(trade: Trade, position):
152
175
  if len(trade.retcodes) > 0:
@@ -181,14 +204,18 @@ def _mt5_execution(
181
204
  check_mt5_connection(**kwargs)
182
205
  strategy: MT5Strategy = strategy_cls(symbol_list=symbols, mode="live", **kwargs)
183
206
  except Exception:
184
- _print_exc(debug_mode, f"Initializing strategy, STRATEGY={STRATEGY}")
207
+ _print_exc(
208
+ debug_mode, f"Initializing strategy, STRATEGY={STRATEGY}, ACCOUNT={ACCOUNT}"
209
+ )
185
210
  return
186
- logger.info(f"Running {STRATEGY} Strategy in {time_frame} Interval ...")
211
+ logger.info(
212
+ f"Running {STRATEGY} Strategy in {time_frame} Interval ..., ACCOUNT={ACCOUNT}"
213
+ )
187
214
 
188
215
  def run_trade_algorithm(signal, symbol, id, trade: Trade, price, stoplimit):
189
216
  signal = "BMKT" if signal == "LONG" or signal == "BUY" else signal
190
217
  signal = "SMKT" if signal == "SHORT" or signal == "SELL" else signal
191
- info = f"SIGNAL = {signal}, SYMBOL={symbol}, STRATEGY={STRATEGY}, TIMEFRAME={time_frame}"
218
+ info = f"SIGNAL = {signal}, SYMBOL={symbol}, STRATEGY={STRATEGY}, TIMEFRAME={time_frame} , ACCOUNT={ACCOUNT}"
192
219
  account = Account(**kwargs)
193
220
  symbol_type = account.get_symbol_type(symbol)
194
221
  desc = account.get_symbol_info(symbol).description
@@ -197,9 +224,9 @@ def _mt5_execution(
197
224
  f"\nPRICE={price}, \nSTOPLIMIT={stoplimit}, \nSTRATEGY={STRATEGY}, \nTIMEFRAME={time_frame}"
198
225
  f"\nBROKER={account.broker.name}, \nTIMESTAMP={datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
199
226
  )
200
- msg = f"Sending {signal} Order ... SYMBOL={symbol}, STRATEGY={STRATEGY}"
201
- tfmsg = f"Time Frame Not completed !!! SYMBOL={symbol}, STRATEGY={STRATEGY}"
202
- riskmsg = f"Risk not allowed !!! SYMBOL={symbol}, STRATEGY={STRATEGY}"
227
+ msg = f"Sending {signal} Order ... SYMBOL={symbol}, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
228
+ tfmsg = f"Time Frame Not completed !!! SYMBOL={symbol}, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
229
+ riskmsg = f"Risk not allowed !!! SYMBOL={symbol}, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
203
230
  if signal not in EXIT_SIGNAL_ACTIONS:
204
231
  if signal in NON_EXEC_RETCODES and not check_retcode(trade, signal):
205
232
  logger.info(info)
@@ -324,7 +351,7 @@ def _mt5_execution(
324
351
  if show_positions_orders:
325
352
  logger.info(
326
353
  f"Current {type.upper()} SYMBOL={symbol}: \
327
- {positions_orders[type][symbol]}, STRATEGY={STRATEGY}"
354
+ {positions_orders[type][symbol]}, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
328
355
  )
329
356
  long_market = {
330
357
  symbol: buys[symbol] is not None
@@ -338,7 +365,8 @@ def _mt5_execution(
338
365
  }
339
366
  except Exception:
340
367
  _print_exc(
341
- debug_mode, f"Checking positions and orders, STRATEGY={STRATEGY}"
368
+ debug_mode,
369
+ f"Checking positions and orders, STRATEGY={STRATEGY}, ACCOUNT={ACCOUNT}",
342
370
  )
343
371
  continue
344
372
  time.sleep(0.5)
@@ -352,7 +380,10 @@ def _mt5_execution(
352
380
  )
353
381
  update_risk(weights)
354
382
  except Exception:
355
- _print_exc(debug_mode, f"Calculating Signals, STRATEGY={STRATEGY}")
383
+ _print_exc(
384
+ debug_mode,
385
+ f"Calculating Signals, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}",
386
+ )
356
387
  continue
357
388
  if len(signals) == 0:
358
389
  for symbol in symbols:
@@ -382,16 +413,16 @@ def _mt5_execution(
382
413
  if len(symbols) >= 10:
383
414
  if symbol == symbols[-1]:
384
415
  logger.info(
385
- f"Not trading Time !!!, STRATEGY={STRATEGY}"
416
+ f"Not trading Time !!!, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
386
417
  )
387
418
  else:
388
419
  logger.info(
389
- f"Not trading Time !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}"
420
+ f"Not trading Time !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
390
421
  )
391
422
  check(buys[symbol], sells[symbol], symbol)
392
423
 
393
424
  except Exception:
394
- msg = f"Handling Signals, SYMBOL={symbol}, STRATEGY={STRATEGY}"
425
+ msg = f"Handling Signals, SYMBOL={symbol}, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
395
426
  _print_exc(debug_mode, msg)
396
427
  continue
397
428
 
@@ -420,20 +451,40 @@ def _mt5_execution(
420
451
 
421
452
  def logmsg(period, symbol):
422
453
  logger.info(
423
- f"End of the {period} !!! SYMBOL={symbol}, STRATEGY={STRATEGY}"
454
+ f"End of the {period} !!! SYMBOL={symbol}, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
424
455
  )
425
456
 
426
457
  def logmsgif(period, symbol):
427
458
  if len(symbols) <= 10:
428
459
  logmsg(period, symbol)
429
460
  elif len(symbols) > 10 and symbol == symbols[-1]:
430
- logger.info(f"End of the {period} !!! STRATEGY={STRATEGY}")
461
+ logger.info(
462
+ f"End of the {period} !!! STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
463
+ )
431
464
 
432
465
  def sleepmsg(sleep_time):
433
- logger.info(f"Sleeping for {sleep_time} minutes ...\n")
466
+ logger.info(f"{ACCOUNT} Sleeping for {sleep_time} minutes ...\n")
434
467
 
435
- sessionmsg = "STARTING NEW TRADING SESSION ...\n"
436
- if period.lower() == "day":
468
+ sessionmsg = f"{ACCOUNT} STARTING NEW TRADING SESSION ...\n"
469
+ if period.lower() == "24/7": # For Cryptos
470
+ for symbol in symbols:
471
+ trade = trades_instances[symbol]
472
+ if trade.days_end() and closing:
473
+ for id in expert_ids:
474
+ trade.close_positions(
475
+ position_type="all", id=id, comment=comment
476
+ )
477
+ logmsgif("Day", symbol)
478
+ trade.statistics(save=True)
479
+ if day_end:
480
+ strategy.perform_period_end_checks()
481
+ sleep_time = trades_instances[symbols[-1]].sleep_time()
482
+ sleepmsg(sleep_time + delay)
483
+ time.sleep(60 * sleep_time + delay)
484
+ logger.info(sessionmsg)
485
+ time_intervals = 0
486
+
487
+ elif period.lower() == "day":
437
488
  for symbol in symbols:
438
489
  trade = trades_instances[symbol]
439
490
  if trade.days_end() and closing:
@@ -455,6 +506,7 @@ def _mt5_execution(
455
506
  sleepmsg(sleep_time + delay)
456
507
  time.sleep(60 * sleep_time + delay)
457
508
  logger.info(sessionmsg)
509
+ time_intervals = 0
458
510
  elif period_end_action == "sleep" and today == FRIDAY:
459
511
  sleep_time = trades_instances[symbols[-1]].sleep_time(
460
512
  weekend=True
@@ -462,6 +514,7 @@ def _mt5_execution(
462
514
  sleepmsg(sleep_time + delay)
463
515
  time.sleep(60 * sleep_time + delay)
464
516
  logger.info(sessionmsg)
517
+ time_intervals = 0
465
518
 
466
519
  elif period.lower() == "week":
467
520
  for symbol in symbols:
@@ -482,6 +535,7 @@ def _mt5_execution(
482
535
  sleepmsg(sleep_time + delay)
483
536
  time.sleep(60 * sleep_time + delay)
484
537
  logger.info(sessionmsg)
538
+ time_intervals = 0
485
539
  elif day_end and today == FRIDAY:
486
540
  strategy.perform_period_end_checks()
487
541
  if period_end_action == "break" and closing:
@@ -493,6 +547,7 @@ def _mt5_execution(
493
547
  sleepmsg(sleep_time + delay)
494
548
  time.sleep(60 * sleep_time + delay)
495
549
  logger.info(sessionmsg)
550
+ time_intervals = 0
496
551
 
497
552
  elif period.lower() == "month":
498
553
  for symbol in symbols:
@@ -502,9 +557,7 @@ def _mt5_execution(
502
557
  elif trade.days_end() and today == FRIDAY:
503
558
  logmsgif("Week", symbol)
504
559
  elif (
505
- trade.days_end()
506
- and today == FRIDAY
507
- and num_days >= 20
560
+ trade.days_end() and today == FRIDAY and num_days >= 20
508
561
  ) and closing:
509
562
  for id in expert_ids:
510
563
  trade.close_positions(
@@ -517,18 +570,22 @@ def _mt5_execution(
517
570
  sleepmsg(sleep_time + delay)
518
571
  time.sleep(60 * sleep_time + delay)
519
572
  logger.info(sessionmsg)
573
+ time_intervals = 0
520
574
  num_days += 1
521
575
  elif day_end and today == FRIDAY:
522
576
  sleep_time = trades_instances[symbols[-1]].sleep_time(weekend=True)
523
577
  sleepmsg(sleep_time + delay)
524
578
  time.sleep(60 * sleep_time + delay)
525
579
  logger.info(sessionmsg)
580
+ time_intervals = 0
526
581
  num_days += 1
527
582
  elif day_end and today == FRIDAY and num_days >= 20:
528
583
  strategy.perform_period_end_checks()
529
584
  break
530
585
  except Exception:
531
- msg = f"Handling period end actions, STRATEGY={STRATEGY}"
586
+ msg = (
587
+ f"Handling period end actions, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
588
+ )
532
589
  _print_exc(debug_mode, msg)
533
590
  continue
534
591
 
@@ -638,7 +695,7 @@ class MT5ExecutionEngine:
638
695
  symbol_list : List of symbols to trade
639
696
  trades_instances : Dictionary of Trade instances
640
697
  strategy_cls : Strategy class to use for trading
641
- mm : Enable Money Management. Defaults to False.
698
+ mm : Enable Money Management. Defaults to True.
642
699
  optimizer : Risk management optimizer. Defaults to 'equal'.
643
700
  See `bbstrader.models.optimization` module for more information.
644
701
  show_positions_orders : Print open positions and orders. Defaults to False.
@@ -726,4 +783,60 @@ class MT5ExecutionEngine:
726
783
  )
727
784
 
728
785
 
786
+ def mt5_engine(account_id: str, **kwargs):
787
+ """Starts an MT5 execution engine for a given account.
788
+ Args:
789
+ account_id: Account ID to run the execution engine on.
790
+ **kwargs: Additional keyword arguments
791
+ _ symbol_list : List of symbols to trade.
792
+ - trades_instances : Dictionary of Trade instances.
793
+ - strategy_cls : Strategy class to use for trading.
794
+ """
795
+ log.info(f"Starting execution engine for {account_id}")
796
+
797
+ symbol_list = kwargs.pop("symbol_list")
798
+ trades_instances = kwargs.pop("trades_instances")
799
+ strategy_cls = kwargs.pop("strategy_cls")
800
+
801
+ if symbol_list is None or trades_instances is None or strategy_cls is None:
802
+ log.error(f"Missing required arguments for account {account_id}")
803
+ raise ValueError(f"Missing required arguments for account {account_id}")
804
+
805
+ try:
806
+ engine = MT5ExecutionEngine(
807
+ symbol_list, trades_instances, strategy_cls, **kwargs
808
+ )
809
+ engine.run()
810
+ except Exception as e:
811
+ log.exception(f"Error running execution engine for {account_id}: {e}")
812
+ finally:
813
+ log.info(f"Execution engine for {account_id} completed")
814
+
815
+
816
+ def RunMt5Engines(accounts: Dict[str, Dict], start_delay: float = 1.0):
817
+ """Runs multiple MT5 execution engines in parallel using multiprocessing.
818
+
819
+ Args:
820
+ accounts: Dictionary of accounts to run the execution engines on.
821
+ Keys are the account names or IDs and values are the parameters for the execution engine.
822
+ The parameters are the same as the ones passed to the `MT5ExecutionEngine` class.
823
+ start_delay: Delay in seconds between starting the processes. Defaults to 1.0.
824
+ """
825
+
826
+ processes = {}
827
+
828
+ for account_id, params in accounts.items():
829
+ log.info(f"Starting process for {account_id}")
830
+ process = mp.Process(target=mt5_engine, args=(account_id,), kwargs=params)
831
+ process.start()
832
+ processes[process] = account_id
833
+
834
+ if start_delay:
835
+ time.sleep(start_delay)
836
+
837
+ for process, account_id in processes.items():
838
+ process.join()
839
+ log.info(f"Process for {account_id} joined")
840
+
841
+
729
842
  class TWSExecutionEngine: ...
@@ -0,0 +1,155 @@
1
+ import argparse
2
+ import json
3
+ import multiprocessing as mp
4
+ import os
5
+ import sys
6
+
7
+ from bbstrader.btengine import MT5Strategy, Strategy
8
+ from bbstrader.core.utils import load_class, load_module
9
+ from bbstrader.metatrader.trade import create_trade_instance
10
+ from bbstrader.trading.execution import mt5_engine
11
+
12
+ EXECUTION_PATH = os.path.expanduser("~/.bbstrader/execution/execution.py")
13
+ CONFIG_PATH = os.path.expanduser("~/.bbstrader/execution/execution.json")
14
+
15
+
16
+ def load_config(config_path, strategy_name, account=None):
17
+ if not os.path.exists(config_path):
18
+ raise FileNotFoundError(f"Configuration file not found at {config_path}")
19
+ with open(config_path, "r") as f:
20
+ config = json.load(f)
21
+ try:
22
+ config = config[strategy_name]
23
+ except KeyError:
24
+ raise ValueError(
25
+ f"Strategy {strategy_name} not found in the configuration file."
26
+ )
27
+ if account is not None:
28
+ try:
29
+ config = config[account]
30
+ except KeyError:
31
+ raise ValueError(f"Account {account} not found in the configuration file.")
32
+ if config.get("symbol_list") is None:
33
+ raise ValueError("symbol_list is required in the configuration file.")
34
+ if config.get("trades_kwargs") is None:
35
+ raise ValueError("trades_kwargs is required in the configuration file.")
36
+ return config
37
+
38
+
39
+ def worker_function(account, args):
40
+ strategy_module = load_module(args.path)
41
+ strategy_class = load_class(strategy_module, args.strategy, (MT5Strategy, Strategy))
42
+
43
+ config = load_config(args.config, args.strategy, account)
44
+ symbol_list = config.pop("symbol_list")
45
+ trades_kwargs = config.pop("trades_kwargs")
46
+ trades = create_trade_instance(symbol_list, trades_kwargs)
47
+
48
+ kwargs = {
49
+ "symbol_list": symbol_list,
50
+ "trades_instances": trades,
51
+ "strategy_cls": strategy_class,
52
+ "account": account,
53
+ **config,
54
+ }
55
+ mt5_engine(account, **kwargs)
56
+
57
+
58
+ def mt5_terminal(args):
59
+ if args.parallel:
60
+ if len(args.account) == 0:
61
+ raise ValueError(
62
+ "account or accounts are required when running in parallel"
63
+ )
64
+
65
+ processes = []
66
+ try:
67
+ for account in args.account:
68
+ p = mp.Process(target=worker_function, args=(account, args))
69
+ p.start()
70
+ processes.append(p)
71
+
72
+ for p in processes:
73
+ p.join()
74
+ except Exception as e:
75
+ print(f"Error in parallel execution: {e}")
76
+ raise e
77
+ except KeyboardInterrupt:
78
+ print("\nTerminating Execution...")
79
+ for p in processes:
80
+ p.terminate()
81
+ for p in processes:
82
+ p.join()
83
+ print("Execution terminated")
84
+ else:
85
+ worker_function(None, args)
86
+
87
+
88
+ def tws_terminal(args):
89
+ raise NotImplementedError("TWS terminal is not implemented yet")
90
+
91
+
92
+ def execute_strategy(unknown):
93
+ HELP_MSG = """
94
+ Execute a strategy on one or multiple MT5 accounts.
95
+
96
+ Usage:
97
+ python -m bbstrader --run execution [options]
98
+
99
+ Options:
100
+ -s, --strategy: Strategy class name to run
101
+ -a, --account: Account(s) name(s) or ID(s) to run the strategy on (must be the same as in the configuration file)
102
+ -p, --path: Path to the execution file (default: ~/.bbstrader/execution/execution.py)
103
+ -c, --config: Path to the configuration file (default: ~/.bbstrader/execution/execution.json)
104
+ -l, --parallel: Run the strategy in parallel (default: False)
105
+ -t, --terminal: Terminal to use (default: MT5)
106
+ -h, --help: Show this help message and exit
107
+
108
+ Note:
109
+ The configuration file must contain all the required parameters
110
+ to create trade instances for each account and strategy.
111
+ The configuration file must be a dictionary with the following structure:
112
+ If parallel is True:
113
+ {
114
+ "strategy_name": {
115
+ "account_name": {
116
+ "symbol_list": ["symbol1", "symbol2"],
117
+ "trades_kwargs": {"param1": "value1", "param2": "value2"}
118
+ **other_parameters (for the strategy and the execution engine)
119
+ }
120
+ }
121
+ }
122
+ If parallel is False:
123
+ {
124
+ "strategy_name": {
125
+ "symbol_list": ["symbol1", "symbol2"],
126
+ "trades_kwargs": {"param1": "value1", "param2": "value2"}
127
+ **other_parameters (for the strategy and the execution engine)
128
+ }
129
+ }
130
+ See bbstrader.metatrader.trade.create_trade_instance for more details on the trades_kwargs.
131
+ See bbstrader.trading.execution.MT5ExecutionEngine for more details on the other parameters.
132
+
133
+ All other paramaters must be python built-in types.
134
+ If you have custom type you must set them in your strategy class
135
+ or run the MT5ExecutionEngine directly, don't run on CLI.
136
+ """
137
+ if "-h" in unknown or "--help" in unknown:
138
+ print(HELP_MSG)
139
+ sys.exit(0)
140
+
141
+ parser = argparse.ArgumentParser()
142
+ parser.add_argument("-s", "--strategy", type=str, required=True)
143
+ parser.add_argument("-a", "--account", type=str, nargs="*", default=[])
144
+ parser.add_argument("-p", "--path", type=str, default=EXECUTION_PATH)
145
+ parser.add_argument("-c", "--config", type=str, default=CONFIG_PATH)
146
+ parser.add_argument("-l", "--parallel", type=bool, default=False)
147
+ parser.add_argument(
148
+ "-t", "--terminal", type=str, default="MT5", choices=["MT5", "TWS"]
149
+ )
150
+ args = parser.parse_args(unknown)
151
+
152
+ if args.terminal == "MT5":
153
+ mt5_terminal(args)
154
+ elif args.terminal == "TWS":
155
+ tws_terminal(args)
@@ -4,6 +4,7 @@ from notifypy import Notify
4
4
  from telegram import Bot
5
5
  from telegram.error import TelegramError
6
6
 
7
+
7
8
  __all__ = ["send_telegram_message", "send_notification", "send_message"]
8
9
 
9
10
 
@@ -65,3 +66,4 @@ def send_message(
65
66
  if token is None or chat_id is None:
66
67
  raise ValueError("Token and chat_id must be provided")
67
68
  asyncio.run(send_telegram_message(token, chat_id, text=message))
69
+
bbstrader/tseries.py CHANGED
@@ -598,7 +598,9 @@ def plot_residuals(df: pd.DataFrame):
598
598
 
599
599
 
600
600
  def run_cadf_test(
601
- pair: Union[List[str], Tuple[str, ...]], start: str, end: str
601
+ pair: Union[List[str], Tuple[str, ...]],
602
+ start: str,
603
+ end: str,
602
604
  ) -> None:
603
605
  """
604
606
  Performs the Cointegration Augmented Dickey-Fuller (CADF) test on a pair of stock tickers
@@ -666,8 +668,22 @@ def run_cadf_test(
666
668
  """
667
669
  # Download historical data for required stocks
668
670
  p0, p1 = pair[0], pair[1]
669
- _p0 = yf.download(p0, start=start, end=end, progress=False, multi_level_index=False)
670
- _p1 = yf.download(p1, start=start, end=end, progress=False, multi_level_index=False)
671
+ _p0 = yf.download(
672
+ p0,
673
+ start=start,
674
+ end=end,
675
+ progress=False,
676
+ multi_level_index=False,
677
+ auto_adjust=True,
678
+ )
679
+ _p1 = yf.download(
680
+ p1,
681
+ start=start,
682
+ end=end,
683
+ progress=False,
684
+ multi_level_index=False,
685
+ auto_adjust=True,
686
+ )
671
687
  df = pd.DataFrame(index=_p0.index)
672
688
  df[p0] = _p0["Adj Close"]
673
689
  df[p1] = _p1["Adj Close"]
@@ -751,7 +767,12 @@ def run_hurst_test(symbol: str, start: str, end: str):
751
767
  >>> run_hurst_test('AAPL', '2023-01-01', '2023-12-31')
752
768
  """
753
769
  data = yf.download(
754
- symbol, start=start, end=end, progress=False, multi_level_index=False
770
+ symbol,
771
+ start=start,
772
+ end=end,
773
+ progress=False,
774
+ multi_level_index=False,
775
+ auto_adjust=True,
755
776
  )
756
777
 
757
778
  # Create a Geometric Brownian Motion, Mean-Reverting, and Trending Series
@@ -774,6 +795,7 @@ def test_cointegration(ticker1, ticker2, start, end):
774
795
  end=end,
775
796
  progress=False,
776
797
  multi_level_index=False,
798
+ auto_adjust=True,
777
799
  )["Adj Close"].dropna()
778
800
 
779
801
  # Perform Johansen cointegration test
@@ -917,8 +939,12 @@ def run_kalman_filter(
917
939
 
918
940
  >>> run_kalman_filter(['SPY', 'QQQ'], '2023-01-01', '2023-12-31')
919
941
  """
920
- etf_df1 = yf.download(etfs[0], start, end, progress=False, multi_level_index=False)
921
- etf_df2 = yf.download(etfs[1], start, end, progress=False, multi_level_index=False)
942
+ etf_df1 = yf.download(
943
+ etfs[0], start, end, progress=False, multi_level_index=False, auto_adjust=True
944
+ )
945
+ etf_df2 = yf.download(
946
+ etfs[1], start, end, progress=False, multi_level_index=False, auto_adjust=True
947
+ )
922
948
 
923
949
  prices = pd.DataFrame(index=etf_df1.index)
924
950
  prices[etfs[0]] = etf_df1["Adj Close"]
@@ -1674,7 +1700,7 @@ def analyze_cointegrated_pairs(
1674
1700
  y = spreads.coint
1675
1701
  X = spreads[["drift", "vol", "corr", "corr_ret"]]
1676
1702
  decision_tree.fit(X, y)
1677
- res = f'{decision_tree.best_score_:.2%}, Depth: {decision_tree.best_params_["max_depth"]}'
1703
+ res = f"{decision_tree.best_score_:.2%}, Depth: {decision_tree.best_params_['max_depth']}"
1678
1704
  print(res)
1679
1705
 
1680
1706
  if crosstab:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: bbstrader
3
- Version: 0.2.9
3
+ Version: 0.2.92
4
4
  Summary: Simplified Investment & Trading Toolkit
5
5
  Home-page: https://github.com/bbalouki/bbstrader
6
6
  Download-URL: https://pypi.org/project/bbstrader/
@@ -55,7 +55,10 @@ Requires-Dist: financetoolkit
55
55
  Requires-Dist: tables
56
56
  Requires-Dist: lightgbm
57
57
  Requires-Dist: alphalens-reloaded
58
- Requires-Dist: MetaTrader5
58
+ Requires-Dist: pyfiglet
59
+ Requires-Dist: colorama
60
+ Provides-Extra: mt5
61
+ Requires-Dist: MetaTrader5; extra == "mt5"
59
62
  Dynamic: author
60
63
  Dynamic: author-email
61
64
  Dynamic: classifier
@@ -67,6 +70,7 @@ Dynamic: keywords
67
70
  Dynamic: license
68
71
  Dynamic: maintainer
69
72
  Dynamic: project-url
73
+ Dynamic: provides-extra
70
74
  Dynamic: requires-dist
71
75
  Dynamic: summary
72
76
 
@@ -102,6 +106,7 @@ It leverages statistical models and algorithms to perform tasks such as cointegr
102
106
  - **Comprehensive Backtesting**: Assess the performance of trading strategies with historical market data to optimize parameters and strategies for live trading environments.
103
107
  - **Integrated Risk Management**: Leverage advanced risk management techniques to adapt to changing market conditions and maintain control over risk exposure.
104
108
  - **Automated Trading**: Execute trades automatically on the MT5 platform, with support for managing orders, positions, and risk in real-time.
109
+ - **Trade Copier**: Copy trades from one account to another or multiple accounts.
105
110
  - **Flexible Framework**: Customize existing strategies or develop new ones with the flexible, modular architecture designed to accommodate traders' evolving needs.
106
111
  - **Advanced Time Series Analysis**: Conduct in-depth analysis of financial time series data to identify patterns, trends, and relationships that can inform trading strategies.
107
112
  You can read the full documentation [here](https://bbstrader.readthedocs.io/en/latest/index.html)
@@ -116,7 +121,8 @@ This Module currenlty support three brokers, [Admirals Group AS](https://cabinet
116
121
 
117
122
  Then, you can install `bbstrader` using pip:
118
123
  ```bash
119
- pip install bbstrader
124
+ pip install bbstrader # Mac and Linux
125
+ pip install bbstrader[MT5] # Windows
120
126
  ```
121
127
 
122
128
  ## Examples