bbstrader 0.2.91__tar.gz → 0.2.93__tar.gz

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 (52) hide show
  1. {bbstrader-0.2.91/bbstrader.egg-info → bbstrader-0.2.93}/PKG-INFO +6 -2
  2. {bbstrader-0.2.91 → bbstrader-0.2.93}/README.md +3 -1
  3. bbstrader-0.2.93/bbstrader/__main__.py +50 -0
  4. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/btengine/data.py +12 -9
  5. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/btengine/execution.py +13 -2
  6. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/btengine/performance.py +50 -1
  7. bbstrader-0.2.93/bbstrader/btengine/scripts.py +157 -0
  8. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/btengine/strategy.py +12 -2
  9. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/compat.py +2 -2
  10. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/config.py +2 -4
  11. bbstrader-0.2.93/bbstrader/core/utils.py +146 -0
  12. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/metatrader/__init__.py +2 -1
  13. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/metatrader/account.py +29 -39
  14. bbstrader-0.2.93/bbstrader/metatrader/copier.py +745 -0
  15. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/metatrader/rates.py +6 -3
  16. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/metatrader/risk.py +19 -8
  17. bbstrader-0.2.93/bbstrader/metatrader/scripts.py +81 -0
  18. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/metatrader/trade.py +178 -66
  19. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/metatrader/utils.py +5 -2
  20. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/models/ml.py +20 -12
  21. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/trading/execution.py +150 -33
  22. bbstrader-0.2.93/bbstrader/trading/script.py +155 -0
  23. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/trading/scripts.py +2 -0
  24. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/tseries.py +33 -7
  25. {bbstrader-0.2.91 → bbstrader-0.2.93/bbstrader.egg-info}/PKG-INFO +6 -2
  26. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader.egg-info/SOURCES.txt +6 -0
  27. bbstrader-0.2.93/bbstrader.egg-info/entry_points.txt +2 -0
  28. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader.egg-info/requires.txt +2 -0
  29. {bbstrader-0.2.91 → bbstrader-0.2.93}/requirements.txt +3 -0
  30. {bbstrader-0.2.91 → bbstrader-0.2.93}/setup.py +6 -1
  31. bbstrader-0.2.91/bbstrader/core/utils.py +0 -57
  32. {bbstrader-0.2.91 → bbstrader-0.2.93}/LICENSE +0 -0
  33. {bbstrader-0.2.91 → bbstrader-0.2.93}/MANIFEST.in +0 -0
  34. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/__ini__.py +0 -0
  35. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/btengine/__init__.py +0 -0
  36. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/btengine/backtest.py +0 -0
  37. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/btengine/event.py +0 -0
  38. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/btengine/portfolio.py +0 -0
  39. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/core/__init__.py +0 -0
  40. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/core/data.py +0 -0
  41. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/ibkr/__init__.py +0 -0
  42. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/ibkr/utils.py +0 -0
  43. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/models/__init__.py +0 -0
  44. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/models/factors.py +0 -0
  45. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/models/optimization.py +0 -0
  46. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/models/portfolio.py +0 -0
  47. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/models/risk.py +0 -0
  48. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/trading/__init__.py +0 -0
  49. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader/trading/strategies.py +0 -0
  50. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader.egg-info/dependency_links.txt +0 -0
  51. {bbstrader-0.2.91 → bbstrader-0.2.93}/bbstrader.egg-info/top_level.txt +0 -0
  52. {bbstrader-0.2.91 → bbstrader-0.2.93}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: bbstrader
3
- Version: 0.2.91
3
+ Version: 0.2.93
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,6 +55,8 @@ Requires-Dist: financetoolkit
55
55
  Requires-Dist: tables
56
56
  Requires-Dist: lightgbm
57
57
  Requires-Dist: alphalens-reloaded
58
+ Requires-Dist: pyfiglet
59
+ Requires-Dist: colorama
58
60
  Provides-Extra: mt5
59
61
  Requires-Dist: MetaTrader5; extra == "mt5"
60
62
  Dynamic: author
@@ -104,6 +106,7 @@ It leverages statistical models and algorithms to perform tasks such as cointegr
104
106
  - **Comprehensive Backtesting**: Assess the performance of trading strategies with historical market data to optimize parameters and strategies for live trading environments.
105
107
  - **Integrated Risk Management**: Leverage advanced risk management techniques to adapt to changing market conditions and maintain control over risk exposure.
106
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.
107
110
  - **Flexible Framework**: Customize existing strategies or develop new ones with the flexible, modular architecture designed to accommodate traders' evolving needs.
108
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.
109
112
  You can read the full documentation [here](https://bbstrader.readthedocs.io/en/latest/index.html)
@@ -118,7 +121,8 @@ This Module currenlty support three brokers, [Admirals Group AS](https://cabinet
118
121
 
119
122
  Then, you can install `bbstrader` using pip:
120
123
  ```bash
121
- pip install bbstrader
124
+ pip install bbstrader # Mac and Linux
125
+ pip install bbstrader[MT5] # Windows
122
126
  ```
123
127
 
124
128
  ## Examples
@@ -30,6 +30,7 @@ It leverages statistical models and algorithms to perform tasks such as cointegr
30
30
  - **Comprehensive Backtesting**: Assess the performance of trading strategies with historical market data to optimize parameters and strategies for live trading environments.
31
31
  - **Integrated Risk Management**: Leverage advanced risk management techniques to adapt to changing market conditions and maintain control over risk exposure.
32
32
  - **Automated Trading**: Execute trades automatically on the MT5 platform, with support for managing orders, positions, and risk in real-time.
33
+ - **Trade Copier**: Copy trades from one account to another or multiple accounts.
33
34
  - **Flexible Framework**: Customize existing strategies or develop new ones with the flexible, modular architecture designed to accommodate traders' evolving needs.
34
35
  - **Advanced Time Series Analysis**: Conduct in-depth analysis of financial time series data to identify patterns, trends, and relationships that can inform trading strategies.
35
36
  You can read the full documentation [here](https://bbstrader.readthedocs.io/en/latest/index.html)
@@ -44,7 +45,8 @@ This Module currenlty support three brokers, [Admirals Group AS](https://cabinet
44
45
 
45
46
  Then, you can install `bbstrader` using pip:
46
47
  ```bash
47
- pip install bbstrader
48
+ pip install bbstrader # Mac and Linux
49
+ pip install bbstrader[MT5] # Windows
48
50
  ```
49
51
 
50
52
  ## Examples
@@ -0,0 +1,50 @@
1
+ import argparse
2
+ import sys
3
+
4
+ import pyfiglet
5
+ from colorama import Fore
6
+
7
+ from bbstrader.btengine.scripts import backtest
8
+ from bbstrader.metatrader.scripts import copy_trades
9
+ from bbstrader.trading.script import execute_strategy
10
+
11
+ DESCRIPTION = "BBSTRADER"
12
+ USAGE_TEXT = """
13
+ Usage:
14
+ python -m bbstrader --run <module> [options]
15
+
16
+ Modules:
17
+ copier: Copy trades from one MetaTrader account to another or multiple accounts
18
+ backtest: Backtest a strategy, see bbstrader.btengine.backtest.run_backtest
19
+ execution: Execute a strategy, see bbstrader.trading.execution.MT5ExecutionEngine
20
+
21
+ python -m bbstrader --run <module> --help for more information on the module
22
+ """
23
+
24
+ FONT = pyfiglet.figlet_format("BBSTRADER", font="big")
25
+
26
+
27
+ def main():
28
+ print(Fore.BLUE + FONT)
29
+ parser = argparse.ArgumentParser(
30
+ prog="BBSTRADER",
31
+ usage=USAGE_TEXT,
32
+ description=DESCRIPTION,
33
+ formatter_class=argparse.RawTextHelpFormatter,
34
+ add_help=False,
35
+ )
36
+ parser.add_argument("--run", type=str, nargs="?", default=None, help="Run a module")
37
+ args, unknown = parser.parse_known_args()
38
+ if ("-h" in sys.argv or "--help" in sys.argv) and args.run is None:
39
+ print(USAGE_TEXT)
40
+ sys.exit(0)
41
+ if args.run == "copier":
42
+ copy_trades(unknown)
43
+ elif args.run == "backtest":
44
+ backtest(unknown)
45
+ elif args.run == "execution":
46
+ execute_strategy(unknown)
47
+
48
+
49
+ if __name__ == "__main__":
50
+ main()
@@ -175,9 +175,9 @@ class BaseCSVDataHandler(DataHandler):
175
175
  new_names = self.columns or default_names
176
176
  new_names = [name.strip().lower().replace(" ", "_") for name in new_names]
177
177
  self.columns = new_names
178
- assert (
179
- "adj_close" in new_names or "close" in new_names
180
- ), "Column names must contain 'Adj Close' and 'Close' or adj_close and close"
178
+ assert "adj_close" in new_names or "close" in new_names, (
179
+ "Column names must contain 'Adj Close' and 'Close' or adj_close and close"
180
+ )
181
181
  comb_index = None
182
182
  for s in self.symbol_list:
183
183
  # Load the CSV file with no header information,
@@ -207,7 +207,9 @@ class BaseCSVDataHandler(DataHandler):
207
207
  self.columns.append("adj_close")
208
208
  self.symbol_data[s]["adj_close"] = self.symbol_data[s]["close"]
209
209
  self.symbol_data[s]["returns"] = (
210
- self.symbol_data[s]["adj_close" if "adj_close" in new_names else "close"]
210
+ self.symbol_data[s][
211
+ "adj_close" if "adj_close" in new_names else "close"
212
+ ]
211
213
  .pct_change()
212
214
  .dropna()
213
215
  )
@@ -362,7 +364,7 @@ class CSVDataHandler(BaseCSVDataHandler):
362
364
 
363
365
  """
364
366
  csv_dir = kwargs.get("csv_dir")
365
- csv_dir = csv_dir or BBSTRADER_DIR / "csv_data"
367
+ csv_dir = csv_dir or BBSTRADER_DIR / "data" / "csv_data"
366
368
  super().__init__(
367
369
  events,
368
370
  symbol_list,
@@ -422,7 +424,7 @@ class MT5DataHandler(BaseCSVDataHandler):
422
424
  )
423
425
 
424
426
  def _download_and_cache_data(self, cache_dir: str):
425
- data_dir = cache_dir or BBSTRADER_DIR / "mt5" / self.tf
427
+ data_dir = cache_dir or BBSTRADER_DIR / "data" / "mt5" / self.tf
426
428
  data_dir.mkdir(parents=True, exist_ok=True)
427
429
  for symbol in self.symbol_list:
428
430
  try:
@@ -486,7 +488,7 @@ class YFDataHandler(BaseCSVDataHandler):
486
488
 
487
489
  def _download_and_cache_data(self, cache_dir: str):
488
490
  """Downloads and caches historical data as CSV files."""
489
- cache_dir = cache_dir or BBSTRADER_DIR / "yfinance" / "daily"
491
+ cache_dir = cache_dir or BBSTRADER_DIR / "data" / "yfinance" / "daily"
490
492
  os.makedirs(cache_dir, exist_ok=True)
491
493
  for symbol in self.symbol_list:
492
494
  filepath = os.path.join(cache_dir, f"{symbol}.csv")
@@ -496,6 +498,7 @@ class YFDataHandler(BaseCSVDataHandler):
496
498
  start=self.start_date,
497
499
  end=self.end_date,
498
500
  multi_level_index=False,
501
+ auto_adjust=True,
499
502
  progress=False,
500
503
  )
501
504
  if "Adj Close" not in data.columns:
@@ -597,7 +600,7 @@ class EODHDataHandler(BaseCSVDataHandler):
597
600
 
598
601
  def _download_and_cache_data(self, cache_dir: str):
599
602
  """Downloads and caches historical data as CSV files."""
600
- cache_dir = cache_dir or BBSTRADER_DIR / "eodhd" / self.period
603
+ cache_dir = cache_dir or BBSTRADER_DIR / "data" / "eodhd" / self.period
601
604
  os.makedirs(cache_dir, exist_ok=True)
602
605
  for symbol in self.symbol_list:
603
606
  filepath = os.path.join(cache_dir, f"{symbol}.csv")
@@ -698,7 +701,7 @@ class FMPDataHandler(BaseCSVDataHandler):
698
701
 
699
702
  def _download_and_cache_data(self, cache_dir: str):
700
703
  """Downloads and caches historical data as CSV files."""
701
- cache_dir = cache_dir or BBSTRADER_DIR / "fmp" / self.period
704
+ cache_dir = cache_dir or BBSTRADER_DIR / "data" / "fmp" / self.period
702
705
  os.makedirs(cache_dir, exist_ok=True)
703
706
  for symbol in self.symbol_list:
704
707
  filepath = os.path.join(cache_dir, f"{symbol}.csv")
@@ -1,13 +1,24 @@
1
1
  from abc import ABCMeta, abstractmethod
2
2
  from queue import Queue
3
3
 
4
+ from loguru import logger
5
+
4
6
  from bbstrader.btengine.data import DataHandler
5
7
  from bbstrader.btengine.event import FillEvent, OrderEvent
8
+ from bbstrader.config import BBSTRADER_DIR
6
9
  from bbstrader.metatrader.account import Account
7
10
 
8
11
  __all__ = ["ExecutionHandler", "SimExecutionHandler", "MT5ExecutionHandler"]
9
12
 
10
13
 
14
+ logger.add(
15
+ f"{BBSTRADER_DIR}/logs/execution.log",
16
+ enqueue=True,
17
+ level="INFO",
18
+ format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {name} | {message}",
19
+ )
20
+
21
+
11
22
  class ExecutionHandler(metaclass=ABCMeta):
12
23
  """
13
24
  The ExecutionHandler abstract class handles the interaction
@@ -59,7 +70,7 @@ class SimExecutionHandler(ExecutionHandler):
59
70
  """
60
71
  self.events = events
61
72
  self.bardata = data
62
- self.logger = kwargs.get("logger")
73
+ self.logger = kwargs.get("logger") or logger
63
74
 
64
75
  def execute_order(self, event: OrderEvent):
65
76
  """
@@ -123,7 +134,7 @@ class MT5ExecutionHandler(ExecutionHandler):
123
134
  """
124
135
  self.events = events
125
136
  self.bardata = data
126
- self.logger = kwargs.get("logger")
137
+ self.logger = kwargs.get("logger") or logger
127
138
  self.__account = Account(**kwargs)
128
139
 
129
140
  def _calculate_lot(self, symbol, quantity, price):
@@ -1,3 +1,4 @@
1
+ from typing import Dict, List
1
2
  import warnings
2
3
 
3
4
  import matplotlib.pyplot as plt
@@ -19,8 +20,56 @@ __all__ = [
19
20
  "plot_returns_and_dd",
20
21
  "plot_monthly_yearly_returns",
21
22
  "show_qs_stats",
23
+ "get_asset_performances",
24
+ "get_perfbased_weights",
22
25
  ]
23
26
 
27
+ def get_asset_performances(
28
+ portfolio: pd.DataFrame, assets: List[str], plot=True, strategy=""
29
+ ) -> pd.Series:
30
+ """
31
+ Calculate the performance of the assets in the portfolio.
32
+
33
+ Args:
34
+ portfolio (pd.DataFrame): The portfolio DataFrame.
35
+ assets (List[str]): The list of assets to calculate the performance for.
36
+ plot (bool): Whether to plot the performance of the assets.
37
+ strategy (str): The name of the strategy.
38
+
39
+ Returns:
40
+ pd.Series: The performance of the assets.
41
+ """
42
+ asset_prices = portfolio[assets]
43
+ asset_prices = asset_prices.abs()
44
+ asset_prices.replace(0, np.nan, inplace=True)
45
+ asset_prices.ffill(inplace=True)
46
+ asset_returns = asset_prices.pct_change()
47
+ asset_returns.replace([np.inf, -np.inf], np.nan, inplace=True)
48
+ asset_returns.fillna(0, inplace=True)
49
+ asset_cum_returns = (1.0 + asset_returns).cumprod()
50
+ if plot:
51
+ asset_cum_returns.plot(figsize=(12, 6), title=f"{strategy} Strategy Assets Performance")
52
+ plt.show()
53
+ return asset_cum_returns.iloc[-1] - 1
54
+
55
+
56
+ def get_perfbased_weights(performances) -> Dict[str, float]:
57
+ """
58
+ Calculate the weights of the assets based on their performances.
59
+
60
+ Args:
61
+ performances (pd.Series): The performances of the assets.
62
+
63
+ Returns:
64
+ Dict[str, float]: The weights of the assets.
65
+ """
66
+ weights = (
67
+ performances.to_frame()
68
+ .assign(weight=performances.values / performances.sum())
69
+ .weight.to_dict()
70
+ )
71
+ return weights
72
+
24
73
 
25
74
  def create_sharpe_ratio(returns, periods=252) -> float:
26
75
  """
@@ -173,7 +222,7 @@ def plot_returns_and_dd(df: pd.DataFrame, benchmark: str, title):
173
222
  # To avoid errors, we use the try-except block
174
223
  # in case the benchmark is not available
175
224
  try:
176
- bm = yf.download(benchmark, start=first_date, end=last_date)
225
+ bm = yf.download(benchmark, start=first_date, end=last_date, auto_adjust=True)
177
226
  bm["log_return"] = np.log(bm["Close"] / bm["Close"].shift(1))
178
227
  # Use exponential to get cumulative returns
179
228
  bm_returns = np.exp(np.cumsum(bm["log_return"].fillna(0)))
@@ -0,0 +1,157 @@
1
+ import argparse
2
+ import json
3
+ import os
4
+ import sys
5
+ from datetime import datetime
6
+
7
+ from bbstrader.btengine.backtest import run_backtest
8
+ from bbstrader.btengine.data import (
9
+ CSVDataHandler,
10
+ DataHandler,
11
+ EODHDataHandler,
12
+ FMPDataHandler,
13
+ MT5DataHandler,
14
+ YFDataHandler,
15
+ )
16
+ from bbstrader.btengine.execution import (
17
+ ExecutionHandler,
18
+ MT5ExecutionHandler,
19
+ SimExecutionHandler,
20
+ )
21
+ from bbstrader.core.utils import load_class, load_module
22
+
23
+ BACKTEST_PATH = os.path.expanduser("~/.bbstrader/backtest/backtest.py")
24
+ CONFIG_PATH = os.path.expanduser("~/.bbstrader/backtest/backtest.json")
25
+
26
+ DATA_HANDLER_MAP = {
27
+ "csv": CSVDataHandler,
28
+ "mt5": MT5DataHandler,
29
+ "yf": YFDataHandler,
30
+ "eodh": EODHDataHandler,
31
+ "fmp": FMPDataHandler,
32
+ }
33
+
34
+ EXECUTION_HANDLER_MAP = {
35
+ "sim": SimExecutionHandler,
36
+ "mt5": MT5ExecutionHandler,
37
+ }
38
+
39
+
40
+ def load_exc_handler(module, handler_name):
41
+ return load_class(module, handler_name, ExecutionHandler)
42
+
43
+
44
+ def load_data_handler(module, handler_name):
45
+ return load_class(module, handler_name, DataHandler)
46
+
47
+
48
+ def load_strategy(module, strategy_name):
49
+ from bbstrader.btengine.strategy import MT5Strategy, Strategy
50
+
51
+ return load_class(module, strategy_name, (Strategy, MT5Strategy))
52
+
53
+
54
+ def load_config(config_path, strategy_name):
55
+ if not os.path.exists(config_path):
56
+ raise FileNotFoundError(
57
+ f"Configuration file {config_path} not found. Please create it."
58
+ )
59
+
60
+ with open(config_path, "r") as f:
61
+ config = json.load(f)
62
+ try:
63
+ config = config[strategy_name]
64
+ except KeyError:
65
+ raise ValueError(
66
+ f"Strategy {strategy_name} not found in the configuration file."
67
+ )
68
+
69
+ required_fields = ["symbol_list", "start_date", "data_handler", "execution_handler"]
70
+ for field in required_fields:
71
+ if not config.get(field):
72
+ raise ValueError(f"{field} is required in the configuration file.")
73
+
74
+ config["start_date"] = datetime.strptime(config["start_date"], "%Y-%m-%d")
75
+
76
+ if config.get("execution_handler") not in EXECUTION_HANDLER_MAP:
77
+ try:
78
+ backtest_module = load_module(BACKTEST_PATH)
79
+ exc_handler_class = load_exc_handler(
80
+ backtest_module, config["execution_handler"]
81
+ )
82
+ except Exception as e:
83
+ raise ValueError(f"Invalid execution handler: {e}")
84
+ else:
85
+ exc_handler_class = EXECUTION_HANDLER_MAP[config["execution_handler"]]
86
+
87
+ if config.get("data_handler") not in DATA_HANDLER_MAP:
88
+ try:
89
+ backtest_module = load_module(BACKTEST_PATH)
90
+ data_handler_class = load_data_handler(
91
+ backtest_module, config["data_handler"]
92
+ )
93
+ except Exception as e:
94
+ raise ValueError(f"Invalid data handler: {e}")
95
+ else:
96
+ data_handler_class = DATA_HANDLER_MAP[config["data_handler"]]
97
+
98
+ config["execution_handler"] = exc_handler_class
99
+ config["data_handler"] = data_handler_class
100
+
101
+ return config
102
+
103
+
104
+ def backtest(unknown):
105
+ HELP_MSG = """
106
+ Usage:
107
+ python -m bbstrader --run backtest [options]
108
+
109
+ Options:
110
+ -s, --strategy: Strategy class name to run
111
+ -c, --config: Configuration file path (default: ~/.bbstrader/backtest/backtest.json)
112
+ -p, --path: Path to the backtest file (default: ~/.bbstrader/backtest/backtest.py)
113
+
114
+ Note:
115
+ The configuration file must contain all the required parameters
116
+ for the data handler and execution handler and strategy.
117
+ See bbstrader.btengine.BacktestEngine for more details on the parameters.
118
+ """
119
+ if "-h" in unknown or "--help" in unknown:
120
+ print(HELP_MSG)
121
+ sys.exit(0)
122
+
123
+ parser = argparse.ArgumentParser(description="Backtesting Engine CLI")
124
+ parser.add_argument(
125
+ "-s", "--strategy", type=str, required=True, help="Strategy class name to run"
126
+ )
127
+ parser.add_argument(
128
+ "-c", "--config", type=str, default=CONFIG_PATH, help="Configuration file path"
129
+ )
130
+ parser.add_argument(
131
+ "-p",
132
+ "--path",
133
+ type=str,
134
+ default=BACKTEST_PATH,
135
+ help="Path to the backtest file",
136
+ )
137
+ args = parser.parse_args(unknown)
138
+ config = load_config(args.config, args.strategy)
139
+ strategy_module = load_module(args.path)
140
+ strategy_class = load_strategy(strategy_module, args.strategy)
141
+
142
+ symbol_list = config.pop("symbol_list")
143
+ start_date = config.pop("start_date")
144
+ data_handler = config.pop("data_handler")
145
+ execution_handler = config.pop("execution_handler")
146
+
147
+ try:
148
+ run_backtest(
149
+ symbol_list,
150
+ start_date,
151
+ data_handler,
152
+ strategy_class,
153
+ exc_handler=execution_handler,
154
+ **config,
155
+ )
156
+ except Exception as e:
157
+ print(f"Error: {e}")
@@ -7,9 +7,12 @@ from typing import Dict, List, Literal, Union
7
7
  import numpy as np
8
8
  import pandas as pd
9
9
  import pytz
10
+ from loguru import logger
10
11
 
11
12
  from bbstrader.btengine.data import DataHandler
12
13
  from bbstrader.btengine.event import FillEvent, SignalEvent
14
+ from bbstrader.config import BBSTRADER_DIR
15
+ from bbstrader.core.utils import TradeSignal
13
16
  from bbstrader.metatrader.account import (
14
17
  Account,
15
18
  AdmiralMarktsGroup,
@@ -17,10 +20,16 @@ from bbstrader.metatrader.account import (
17
20
  )
18
21
  from bbstrader.metatrader.rates import Rates
19
22
  from bbstrader.models.optimization import optimized_weights
20
- from bbstrader.core.utils import TradeSignal
21
23
 
22
24
  __all__ = ["Strategy", "MT5Strategy"]
23
25
 
26
+ logger.add(
27
+ f"{BBSTRADER_DIR}/logs/strategy.log",
28
+ enqueue=True,
29
+ level="INFO",
30
+ format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {name} | {message}",
31
+ )
32
+
24
33
 
25
34
  class Strategy(metaclass=ABCMeta):
26
35
  """
@@ -91,10 +100,11 @@ class MT5Strategy(Strategy):
91
100
  self.risk_budget = self._check_risk_budget(**kwargs)
92
101
  self.max_trades = kwargs.get("max_trades", {s: 1 for s in self.symbols})
93
102
  self.tf = kwargs.get("time_frame", "D1")
94
- self.logger = kwargs.get("logger")
103
+ self.logger = kwargs.get("logger") or logger
95
104
  if self.mode == "backtest":
96
105
  self._initialize_portfolio()
97
106
  self.kwargs = kwargs
107
+ self.periodes = 0
98
108
 
99
109
  @property
100
110
  def cash(self) -> float:
@@ -3,8 +3,8 @@ import sys
3
3
 
4
4
 
5
5
  def setup_mock_metatrader():
6
- """Mock MetaTrader5 on Linux to prevent import errors."""
7
- if platform.system() == "Linux":
6
+ """Mock MetaTrader5 on Linux and MacOS to prevent import errors."""
7
+ if platform.system() != "Windows":
8
8
  from unittest.mock import MagicMock
9
9
 
10
10
  class Mock(MagicMock):
@@ -7,17 +7,15 @@ TERMINAL = "\\terminal64.exe"
7
7
  BASE_FOLDER = "C:\\Program Files\\"
8
8
 
9
9
  AMG_PATH = BASE_FOLDER + "Admirals Group MT5 Terminal" + TERMINAL
10
- XCB_PATH = BASE_FOLDER + "4xCube MT5 Terminal" + TERMINAL
11
- TML_PATH = BASE_FOLDER + "Trinota Markets MetaTrader 5 Terminal" + TERMINAL
12
10
  PGL_PATH = BASE_FOLDER + "Pepperstone MetaTrader 5" + TERMINAL
13
11
  FTMO_PATH = BASE_FOLDER + "FTMO MetaTrader 5" + TERMINAL
12
+ JGM_PATH = BASE_FOLDER + "JustMarkets MetaTrader 5" + TERMINAL
14
13
 
15
14
  BROKERS_PATHS = {
16
15
  "AMG": AMG_PATH,
17
16
  "FTMO": FTMO_PATH,
18
- "XCB": XCB_PATH,
19
- "TML": TML_PATH,
20
17
  "PGL": PGL_PATH,
18
+ "JGM": JGM_PATH,
21
19
  }
22
20
 
23
21
 
@@ -0,0 +1,146 @@
1
+ import configparser
2
+ import importlib
3
+ import importlib.util
4
+ import os
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+ from typing import Any, Dict, List
8
+
9
+
10
+ def load_module(file_path):
11
+ """Load a module from a file path.
12
+
13
+ Args:
14
+ file_path: Path to the file to load.
15
+
16
+ Returns:
17
+ The loaded module.
18
+ """
19
+ if not os.path.exists(file_path):
20
+ raise FileNotFoundError(
21
+ f"Strategy file {file_path} not found. Please create it."
22
+ )
23
+ spec = importlib.util.spec_from_file_location("bbstrader.cli", file_path)
24
+ module = importlib.util.module_from_spec(spec)
25
+ spec.loader.exec_module(module)
26
+ return module
27
+
28
+
29
+ def load_class(module, class_name, base_class):
30
+ """Load a class from a module.
31
+
32
+ Args:
33
+ module: The module to load the class from.
34
+ class_name: The name of the class to load.
35
+ base_class: The base class that the class must inherit from.
36
+ """
37
+ if not hasattr(module, class_name):
38
+ raise AttributeError(f"{class_name} not found in {module}")
39
+ class_ = getattr(module, class_name)
40
+ if not issubclass(class_, base_class):
41
+ raise TypeError(f"{class_name} must inherit from {base_class}.")
42
+ return class_
43
+
44
+
45
+ def auto_convert(value):
46
+ """Convert string values to appropriate data types"""
47
+ if value.lower() in {"true", "false"}: # Boolean
48
+ return value.lower() == "true"
49
+ elif value.lower() in {"none", "null"}: # None
50
+ return None
51
+ elif value.isdigit():
52
+ return int(value)
53
+ try:
54
+ return float(value)
55
+ except ValueError:
56
+ return value
57
+
58
+
59
+ def dict_from_ini(file_path, sections: str | List[str] = None) -> Dict[str, Any]:
60
+ """Reads an INI file and converts it to a dictionary with proper data types.
61
+
62
+ Args:
63
+ file_path: Path to the INI file to read.
64
+ sections: Optional list of sections to read from the INI file.
65
+
66
+ Returns:
67
+ A dictionary containing the INI file contents with proper data types.
68
+ """
69
+ config = configparser.ConfigParser(interpolation=None)
70
+ config.read(file_path)
71
+
72
+ ini_dict = {}
73
+ for section in config.sections():
74
+ ini_dict[section] = {
75
+ key: auto_convert(value) for key, value in config.items(section)
76
+ }
77
+
78
+ if isinstance(sections, str):
79
+ try:
80
+ return ini_dict[sections]
81
+ except KeyError:
82
+ raise KeyError(f"{sections} not found in the {file_path} file")
83
+ if isinstance(sections, list):
84
+ sect_dict = {}
85
+ for section in sections:
86
+ try:
87
+ sect_dict[section] = ini_dict[section]
88
+ except KeyError:
89
+ raise KeyError(f"{section} not found in the {file_path} file")
90
+ return ini_dict
91
+
92
+
93
+ class TradeAction(Enum):
94
+ """
95
+ An enumeration class for trade actions.
96
+ """
97
+
98
+ BUY = "LONG"
99
+ LONG = "LONG"
100
+ SELL = "SHORT"
101
+ EXIT = "EXIT"
102
+ BMKT = "BMKT"
103
+ SMKT = "SMKT"
104
+ BLMT = "BLMT"
105
+ SLMT = "SLMT"
106
+ BSTP = "BSTP"
107
+ SSTP = "SSTP"
108
+ SHORT = "SHORT"
109
+ BSTPLMT = "BSTPLMT"
110
+ SSTPLMT = "SSTPLMT"
111
+ EXIT_LONG = "EXIT_LONG"
112
+ EXIT_SHORT = "EXIT_SHORT"
113
+ EXIT_STOP = "EXIT_STOP"
114
+ EXIT_LIMIT = "EXIT_LIMIT"
115
+ EXIT_LONG_STOP = "EXIT_LONG_STOP"
116
+ EXIT_LONG_LIMIT = "EXIT_LONG_LIMIT"
117
+ EXIT_SHORT_STOP = "EXIT_SHORT_STOP"
118
+ EXIT_SHORT_LIMIT = "EXIT_SHORT_LIMIT"
119
+ EXIT_LONG_STOP_LIMIT = "EXIT_LONG_STOP_LIMIT"
120
+ EXIT_SHORT_STOP_LIMIT = "EXIT_SHORT_STOP_LIMIT"
121
+ EXIT_PROFITABLES = "EXIT_PROFITABLES"
122
+ EXIT_LOSINGS = "EXIT_LOSINGS"
123
+ EXIT_ALL_POSITIONS = "EXIT_ALL_POSITIONS"
124
+ EXIT_ALL_ORDERS = "EXIT_ALL_ORDERS"
125
+
126
+ def __str__(self):
127
+ return self.value
128
+
129
+
130
+ @dataclass()
131
+ class TradeSignal:
132
+ """
133
+ A dataclass for storing trading signal.
134
+ """
135
+
136
+ id: int
137
+ symbol: str
138
+ action: TradeAction
139
+ price: float = None
140
+ stoplimit: float = None
141
+
142
+ def __repr__(self):
143
+ return (
144
+ f"TradeSignal(id={self.id}, symbol='{self.symbol}', "
145
+ f"action='{self.action.value}', price={self.price}, stoplimit={self.stoplimit})"
146
+ )
@@ -3,4 +3,5 @@ from bbstrader.metatrader.account import * # noqa: F403
3
3
  from bbstrader.metatrader.rates import * # noqa: F403
4
4
  from bbstrader.metatrader.risk import * # noqa: F403
5
5
  from bbstrader.metatrader.trade import * # noqa: F403
6
- from bbstrader.metatrader.utils import * # noqa: F403
6
+ from bbstrader.metatrader.utils import * # noqa: F403*
7
+ from bbstrader.metatrader.copier import * # noqa: F403