bbstrader 2.0.7__tar.gz → 2.0.8__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.
Files changed (97) hide show
  1. {bbstrader-2.0.7 → bbstrader-2.0.8}/PKG-INFO +2 -1
  2. bbstrader-2.0.8/VERSION.txt +1 -0
  3. {bbstrader-2.0.7 → bbstrader-2.0.8}/pyproject.toml +1 -0
  4. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/api/handlers.py +1 -0
  5. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/btengine/performance.py +75 -1
  6. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/metatrader/broker.py +1 -0
  7. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/metatrader/copier.py +1 -0
  8. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/metatrader/rates.py +1 -0
  9. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/metatrader/risk.py +1 -0
  10. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/metatrader/trade.py +1 -0
  11. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/metatrader/utils.py +1 -0
  12. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/models/optimization.py +79 -5
  13. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/trading/execution.py +1 -0
  14. {bbstrader-2.0.7 → bbstrader-2.0.8}/tcopier.iss +1 -1
  15. {bbstrader-2.0.7 → bbstrader-2.0.8}/tests/api/test_metatrader_client.py +1 -0
  16. bbstrader-2.0.8/tests/btengine/test_performance.py +58 -0
  17. bbstrader-2.0.8/tests/models/test_optimization.py +68 -0
  18. bbstrader-2.0.7/VERSION.txt +0 -1
  19. {bbstrader-2.0.7 → bbstrader-2.0.8}/.clang-format +0 -0
  20. {bbstrader-2.0.7 → bbstrader-2.0.8}/.clang-tidy +0 -0
  21. {bbstrader-2.0.7 → bbstrader-2.0.8}/.github/workflows/build.yml +0 -0
  22. {bbstrader-2.0.7 → bbstrader-2.0.8}/.github/workflows/python.yml +0 -0
  23. {bbstrader-2.0.7 → bbstrader-2.0.8}/.gitignore +0 -0
  24. {bbstrader-2.0.7 → bbstrader-2.0.8}/.readthedocs.yaml +0 -0
  25. {bbstrader-2.0.7 → bbstrader-2.0.8}/CMakeLists.txt +0 -0
  26. {bbstrader-2.0.7 → bbstrader-2.0.8}/LICENSE +0 -0
  27. {bbstrader-2.0.7 → bbstrader-2.0.8}/README.md +0 -0
  28. {bbstrader-2.0.7 → bbstrader-2.0.8}/cmake/Helpers.cmake +0 -0
  29. {bbstrader-2.0.7 → bbstrader-2.0.8}/cmake/Versions.cmake +0 -0
  30. {bbstrader-2.0.7 → bbstrader-2.0.8}/docs/Doxyfile.in +0 -0
  31. {bbstrader-2.0.7 → bbstrader-2.0.8}/docs/Makefile +0 -0
  32. {bbstrader-2.0.7 → bbstrader-2.0.8}/docs/bbstrader.api.rst +0 -0
  33. {bbstrader-2.0.7 → bbstrader-2.0.8}/docs/bbstrader.btengine.rst +0 -0
  34. {bbstrader-2.0.7 → bbstrader-2.0.8}/docs/bbstrader.core.rst +0 -0
  35. {bbstrader-2.0.7 → bbstrader-2.0.8}/docs/bbstrader.metatrader.rst +0 -0
  36. {bbstrader-2.0.7 → bbstrader-2.0.8}/docs/bbstrader.models.rst +0 -0
  37. {bbstrader-2.0.7 → bbstrader-2.0.8}/docs/bbstrader.rst +0 -0
  38. {bbstrader-2.0.7 → bbstrader-2.0.8}/docs/bbstrader.trading.rst +0 -0
  39. {bbstrader-2.0.7 → bbstrader-2.0.8}/docs/conf.py +0 -0
  40. {bbstrader-2.0.7 → bbstrader-2.0.8}/docs/index.rst +0 -0
  41. {bbstrader-2.0.7 → bbstrader-2.0.8}/docs/make.bat +0 -0
  42. {bbstrader-2.0.7 → bbstrader-2.0.8}/docs/modules.rst +0 -0
  43. {bbstrader-2.0.7 → bbstrader-2.0.8}/docs/requirements.txt +0 -0
  44. {bbstrader-2.0.7 → bbstrader-2.0.8}/examples/strategies.py +0 -0
  45. {bbstrader-2.0.7 → bbstrader-2.0.8}/include/bbstrader/metatrader.hpp +0 -0
  46. {bbstrader-2.0.7 → bbstrader-2.0.8}/include/bbstrader/objects.hpp +0 -0
  47. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/CMakeLists.txt +0 -0
  48. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/__init__.py +0 -0
  49. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/__main__.py +0 -0
  50. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/api/__init__.py +0 -0
  51. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/api/client.pyi +0 -0
  52. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/assets/bbs_.png +0 -0
  53. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/assets/bbstrader.ico +0 -0
  54. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/assets/bbstrader.png +0 -0
  55. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/assets/qs_metrics_1.png +0 -0
  56. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/btengine/__init__.py +0 -0
  57. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/btengine/backtest.py +0 -0
  58. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/btengine/data.py +0 -0
  59. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/btengine/event.py +0 -0
  60. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/btengine/execution.py +0 -0
  61. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/btengine/portfolio.py +0 -0
  62. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/btengine/strategy.py +0 -0
  63. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/compat.py +0 -0
  64. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/config.py +0 -0
  65. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/core/__init__.py +0 -0
  66. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/core/data.py +0 -0
  67. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/core/strategy.py +0 -0
  68. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/metatrader/__init__.py +0 -0
  69. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/metatrader/_copier.py +0 -0
  70. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/metatrader/account.py +0 -0
  71. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/models/__init__.py +0 -0
  72. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/models/nlp.py +0 -0
  73. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/scripts.py +0 -0
  74. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/trading/__init__.py +0 -0
  75. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/trading/strategy.py +0 -0
  76. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/bbstrader/trading/utils.py +0 -0
  77. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/cmake/BbstraderConfig.cmake.in +0 -0
  78. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/cpp/bbstrader.cpp +0 -0
  79. {bbstrader-2.0.7 → bbstrader-2.0.8}/src/cpp/metatrader.cpp +0 -0
  80. {bbstrader-2.0.7 → bbstrader-2.0.8}/tcopier.spec +0 -0
  81. {bbstrader-2.0.7 → bbstrader-2.0.8}/tests/CMakeLists.txt +0 -0
  82. {bbstrader-2.0.7 → bbstrader-2.0.8}/tests/__init__.py +0 -0
  83. {bbstrader-2.0.7 → bbstrader-2.0.8}/tests/api/__init__.py +0 -0
  84. {bbstrader-2.0.7 → bbstrader-2.0.8}/tests/btengine/__init__.py +0 -0
  85. {bbstrader-2.0.7 → bbstrader-2.0.8}/tests/btengine/test_backtest.py +0 -0
  86. {bbstrader-2.0.7 → bbstrader-2.0.8}/tests/btengine/test_data.py +0 -0
  87. {bbstrader-2.0.7 → bbstrader-2.0.8}/tests/btengine/test_events.py +0 -0
  88. {bbstrader-2.0.7 → bbstrader-2.0.8}/tests/btengine/test_execution.py +0 -0
  89. {bbstrader-2.0.7 → bbstrader-2.0.8}/tests/btengine/test_portfolio.py +0 -0
  90. {bbstrader-2.0.7 → bbstrader-2.0.8}/tests/cpp/test_metatrader_client.cpp +0 -0
  91. {bbstrader-2.0.7 → bbstrader-2.0.8}/tests/metatrader/__init__.py +0 -0
  92. {bbstrader-2.0.7 → bbstrader-2.0.8}/tests/metatrader/test_account.py +0 -0
  93. {bbstrader-2.0.7 → bbstrader-2.0.8}/tests/metatrader/test_rates.py +0 -0
  94. {bbstrader-2.0.7 → bbstrader-2.0.8}/tests/metatrader/test_risk_management.py +0 -0
  95. {bbstrader-2.0.7 → bbstrader-2.0.8}/tests/metatrader/test_trade.py +0 -0
  96. {bbstrader-2.0.7 → bbstrader-2.0.8}/uv.lock +0 -0
  97. {bbstrader-2.0.7 → bbstrader-2.0.8}/vcpkg.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bbstrader
3
- Version: 2.0.7
3
+ Version: 2.0.8
4
4
  Summary: Simplified Investment & Trading Toolkit with Python & C++
5
5
  Keywords: Finance,Toolkit,Financial,Analysis,Fundamental,Quantitative,Database,Equities,Currencies,Economics,ETFs,Funds,Indices,Moneymarkets,Commodities,Futures,CFDs,Derivatives,Trading,Investing,Portfolio,Optimization,Performance
6
6
  Author-Email: Bertin Balouki SIMYELI <bertin@bbs-trading.com>
@@ -28,6 +28,7 @@ Requires-Dist: ipython>=9.5.0
28
28
  Requires-Dist: nltk>=3.9.1
29
29
  Requires-Dist: notify_py>=0.3.43
30
30
  Requires-Dist: numpy>=2.2.6
31
+ Requires-Dist: plotly>=5.24.1
31
32
  Requires-Dist: praw>=7.8.1
32
33
  Requires-Dist: pybind11>=3.0.1
33
34
  Requires-Dist: pyfiglet>=1.0.4
@@ -0,0 +1 @@
1
+ 2.0.8
@@ -17,6 +17,7 @@ dependencies = [
17
17
  "nltk>=3.9.1",
18
18
  "notify_py>=0.3.43",
19
19
  "numpy>=2.2.6",
20
+ "plotly>=5.24.1",
20
21
  "praw>=7.8.1",
21
22
  "pybind11>=3.0.1",
22
23
  "pyfiglet>=1.0.4",
@@ -21,6 +21,7 @@ try:
21
21
  import MetaTrader5 as mt5
22
22
  except ImportError:
23
23
  import bbstrader.compat # noqa: F401
24
+ import MetaTrader5 as mt5
24
25
 
25
26
 
26
27
  def _convert_obj(obj, obj_type):
@@ -17,6 +17,10 @@ __all__ = [
17
17
  "plot_performance",
18
18
  "create_sharpe_ratio",
19
19
  "create_sortino_ratio",
20
+ "create_omega_ratio",
21
+ "create_calmar_ratio",
22
+ "create_tail_ratio",
23
+ "calculate_risk_metrics",
20
24
  "plot_returns_and_dd",
21
25
  "plot_monthly_yearly_returns",
22
26
  "show_qs_stats",
@@ -111,6 +115,72 @@ def create_sortino_ratio(returns: pd.Series, periods: int = 252) -> float:
111
115
  return qs.stats.sortino(returns, periods=periods)
112
116
 
113
117
 
118
+ def create_omega_ratio(
119
+ returns: pd.Series, periods: int = 252, rf: float = 0.0
120
+ ) -> float:
121
+ """
122
+ Create the Omega ratio for the strategy.
123
+
124
+ Args:
125
+ returns : A pandas Series representing period percentage returns.
126
+ periods (int): Daily (252), Hourly (252*6.5), Minutely(252*6.5*60) etc.
127
+ rf (float): Risk-free rate.
128
+
129
+ Returns:
130
+ float: Omega ratio
131
+ """
132
+ return qs.stats.omega(returns, rf=rf)
133
+
134
+
135
+ def create_calmar_ratio(returns: pd.Series, periods: int = 252) -> float:
136
+ """
137
+ Create the Calmar ratio for the strategy.
138
+
139
+ Args:
140
+ returns : A pandas Series representing period percentage returns.
141
+ periods (int): Daily (252), Hourly (252*6.5), Minutely(252*6.5*60) etc.
142
+
143
+ Returns:
144
+ float: Calmar ratio
145
+ """
146
+ return qs.stats.calmar(returns)
147
+
148
+
149
+ def create_tail_ratio(returns: pd.Series) -> float:
150
+ """
151
+ Create the Tail ratio for the strategy.
152
+
153
+ Args:
154
+ returns : A pandas Series representing period percentage returns.
155
+
156
+ Returns:
157
+ float: Tail ratio
158
+ """
159
+ return qs.stats.tail_ratio(returns)
160
+
161
+
162
+ def calculate_risk_metrics(
163
+ returns: pd.Series, benchmark_returns: pd.Series, periods: int = 252
164
+ ) -> Dict[str, float]:
165
+ """
166
+ Calculate Alpha, Beta and Volatility for the strategy.
167
+
168
+ Args:
169
+ returns : A pandas Series representing period percentage returns.
170
+ benchmark_returns : A pandas Series representing benchmark period percentage returns.
171
+ periods (int): Daily (252), Hourly (252*6.5), Minutely(252*6.5*60) etc.
172
+
173
+ Returns:
174
+ Dict[str, float]: Alpha, Beta, Volatility
175
+ """
176
+ g_beta = qs.stats.greeks(returns, benchmark_returns)
177
+ alpha = g_beta["alpha"]
178
+ beta = g_beta["beta"]
179
+ volatility = qs.stats.volatility(returns, periods=periods)
180
+
181
+ return {"alpha": alpha, "beta": beta, "volatility": volatility}
182
+
183
+
114
184
  def create_drawdowns(pnl: pd.Series) -> Tuple[pd.Series, float, float]:
115
185
  """
116
186
  Calculate the largest peak-to-trough drawdown of the PnL curve
@@ -125,6 +195,8 @@ def create_drawdowns(pnl: pd.Series) -> Tuple[pd.Series, float, float]:
125
195
  """
126
196
  # Calculate the cumulative returns curve
127
197
  # and set up the High Water Mark
198
+ if pnl.empty:
199
+ return pd.Series(dtype=float), 0.0, 0.0
128
200
  hwm = pd.Series(index=pnl.index)
129
201
  hwm.iloc[0] = 0
130
202
 
@@ -139,7 +211,9 @@ def create_drawdowns(pnl: pd.Series) -> Tuple[pd.Series, float, float]:
139
211
  drawdown.iloc[t] = hwm.iloc[t] - pnl.iloc[t]
140
212
  duration.iloc[t] = 0 if drawdown.iloc[t] == 0 else duration.iloc[t - 1] + 1
141
213
 
142
- return drawdown, drawdown.max(), duration.max()
214
+ max_drawdown = drawdown.max() if not drawdown.empty else 0.0
215
+ max_duration = duration.max() if not duration.empty else 0.0
216
+ return drawdown, max_drawdown, max_duration
143
217
 
144
218
 
145
219
  def plot_performance(df: pd.DataFrame, title: str) -> None:
@@ -11,6 +11,7 @@ try:
11
11
  import MetaTrader5 as mt5
12
12
  except ImportError:
13
13
  import bbstrader.compat # noqa: F401
14
+ import MetaTrader5 as mt5
14
15
 
15
16
  COUNTRIES_STOCKS = {
16
17
  "USA": r"\b(US|USA)\b",
@@ -22,6 +22,7 @@ try:
22
22
  import MetaTrader5 as Mt5
23
23
  except ImportError:
24
24
  import bbstrader.compat # noqa: F401
25
+ import MetaTrader5 as Mt5
25
26
 
26
27
 
27
28
  __all__ = [
@@ -12,6 +12,7 @@ try:
12
12
  import MetaTrader5 as Mt5
13
13
  except ImportError:
14
14
  import bbstrader.compat # noqa: F401
15
+ import MetaTrader5 as Mt5
15
16
 
16
17
 
17
18
  __all__ = [
@@ -14,6 +14,7 @@ try:
14
14
  import MetaTrader5 as mt5
15
15
  except ImportError:
16
16
  import bbstrader.compat # noqa: F401
17
+ import MetaTrader5 as mt5
17
18
 
18
19
  logger.add(
19
20
  f"{BBSTRADER_DIR}/logs/trade.log",
@@ -22,6 +22,7 @@ try:
22
22
  import MetaTrader5 as Mt5
23
23
  except ImportError:
24
24
  import bbstrader.compat # noqa: F401
25
+ import MetaTrader5 as Mt5
25
26
 
26
27
  __all__ = [
27
28
  "Trade",
@@ -7,6 +7,7 @@ try:
7
7
  import MetaTrader5 as MT5
8
8
  except ImportError:
9
9
  import bbstrader.compat # noqa: F401
10
+ import MetaTrader5 as MT5
10
11
 
11
12
 
12
13
  __all__ = [
@@ -3,30 +3,36 @@ import warnings
3
3
  from pypfopt import expected_returns, risk_models
4
4
  from pypfopt.efficient_frontier import EfficientFrontier
5
5
  from pypfopt.hierarchical_portfolio import HRPOpt
6
+ from pypfopt.black_litterman import BlackLittermanModel
6
7
 
7
8
  __all__ = [
8
9
  "markowitz_weights",
9
10
  "hierarchical_risk_parity",
11
+ "black_litterman_weights",
10
12
  "equal_weighted",
11
13
  "optimized_weights",
12
14
  ]
13
15
 
14
16
 
15
- def markowitz_weights(prices=None, rfr=0.0, freq=252):
17
+ def markowitz_weights(prices=None, rfr=0.0, freq=252, min_vol=False):
16
18
  """
17
- Calculates optimal portfolio weights using Markowitz's mean-variance optimization (Max Sharpe Ratio) with multiple solvers.
19
+ Calculates optimal portfolio weights using Markowitz's mean-variance optimization (Max Sharpe Ratio or Min Volatility) with multiple solvers.
18
20
 
19
21
  Parameters
20
22
  ----------
21
23
  prices : pd.DataFrame, optional
22
24
  Price data for assets, where rows represent time periods and columns represent assets.
25
+ rfr : float, optional
26
+ Risk-free rate (default is 0.0).
23
27
  freq : int, optional
24
28
  Frequency of the data, such as 252 for daily returns in a year (default is 252).
29
+ min_vol : bool, optional
30
+ If True, optimizes for minimum volatility instead of maximum Sharpe ratio (default is False).
25
31
 
26
32
  Returns
27
33
  -------
28
34
  dict
29
- Dictionary containing the optimal asset weights for maximizing the Sharpe ratio, normalized to sum to 1.
35
+ Dictionary containing the optimal asset weights for maximizing the Sharpe ratio or minimizing volatility, normalized to sum to 1.
30
36
 
31
37
  Notes
32
38
  -----
@@ -53,10 +59,15 @@ def markowitz_weights(prices=None, rfr=0.0, freq=252):
53
59
  solver=solver,
54
60
  )
55
61
  try:
56
- ef.max_sharpe(risk_free_rate=rfr)
62
+ if min_vol:
63
+ ef.min_volatility()
64
+ else:
65
+ ef.max_sharpe(risk_free_rate=rfr)
57
66
  return ef.clean_weights()
58
67
  except Exception as e:
59
68
  print(f"Solver {solver} failed with error: {e}")
69
+ # Default to equal weighted if all solvers fail
70
+ return equal_weighted(prices=prices)
60
71
 
61
72
 
62
73
  def hierarchical_risk_parity(prices=None, returns=None, freq=252):
@@ -140,7 +151,66 @@ def equal_weighted(prices=None, returns=None, round_digits=5):
140
151
  return {col: round(1 / n, round_digits) for col in columns}
141
152
 
142
153
 
143
- def optimized_weights(prices=None, returns=None, rfr=0.0, freq=252, method="equal"):
154
+ def black_litterman_weights(
155
+ prices=None,
156
+ rfr=0.0,
157
+ freq=252,
158
+ views=None,
159
+ view_confidences=None,
160
+ pi=None,
161
+ market_caps=None,
162
+ ):
163
+ """
164
+ Computes portfolio weights using the Black-Litterman model.
165
+
166
+ Parameters
167
+ ----------
168
+ prices : pd.DataFrame
169
+ Price data for assets.
170
+ rfr : float, optional
171
+ Risk-free rate (default is 0.0).
172
+ freq : int, optional
173
+ Frequency of the data (default is 252).
174
+ views : dict, optional
175
+ Investor's views on asset returns.
176
+ view_confidences : list or np.array, optional
177
+ Confidence levels for each view.
178
+ pi : pd.Series, optional
179
+ Market-implied prior returns.
180
+ market_caps : pd.Series, optional
181
+ Market capitalization of assets.
182
+
183
+ Returns
184
+ -------
185
+ dict
186
+ Optimal asset weights based on the Black-Litterman model.
187
+ """
188
+ cov_matrix = risk_models.sample_cov(prices, frequency=freq)
189
+ if pi is None:
190
+ if market_caps is not None:
191
+ # If market caps are provided, we can use them to compute the prior
192
+ # This requires a benchmark, which we don't have here easily.
193
+ # For simplicity, we use the mean historical return if pi is not provided.
194
+ pi = expected_returns.mean_historical_return(prices, frequency=freq)
195
+ else:
196
+ pi = expected_returns.mean_historical_return(prices, frequency=freq)
197
+
198
+ bl = BlackLittermanModel(
199
+ cov_matrix,
200
+ pi=pi,
201
+ absolute_views=views,
202
+ omega=None,
203
+ view_confidences=view_confidences,
204
+ )
205
+ ret_bl = bl.bl_returns()
206
+ ef = EfficientFrontier(ret_bl, cov_matrix)
207
+ ef.max_sharpe(risk_free_rate=rfr)
208
+ return ef.clean_weights()
209
+
210
+
211
+ def optimized_weights(
212
+ prices=None, returns=None, rfr=0.0, freq=252, method="equal", **kwargs
213
+ ):
144
214
  """
145
215
  Selects an optimization method to calculate portfolio weights based on user preference.
146
216
 
@@ -174,8 +244,12 @@ def optimized_weights(prices=None, returns=None, rfr=0.0, freq=252, method="equa
174
244
  """
175
245
  if method == "markowitz":
176
246
  return markowitz_weights(prices=prices, rfr=rfr, freq=freq)
247
+ elif method == "min_vol":
248
+ return markowitz_weights(prices=prices, rfr=rfr, freq=freq, min_vol=True)
177
249
  elif method == "hrp":
178
250
  return hierarchical_risk_parity(prices=prices, returns=returns, freq=freq)
251
+ elif method == "black_litterman":
252
+ return black_litterman_weights(prices=prices, rfr=rfr, freq=freq, **kwargs)
179
253
  elif method == "equal":
180
254
  return equal_weighted(prices=prices, returns=returns)
181
255
  else:
@@ -21,6 +21,7 @@ try:
21
21
  import MetaTrader5 as MT5
22
22
  except ImportError:
23
23
  import bbstrader.compat # noqa: F401
24
+ import MetaTrader5 as MT5
24
25
 
25
26
 
26
27
  __all__ = ["Mt5ExecutionEngine", "RunMt5Engine", "RunMt5Engines"]
@@ -1,6 +1,6 @@
1
1
  [Setup]
2
2
  AppName=TradeCopier
3
- AppVersion=2.0.4
3
+ AppVersion=2.0.8
4
4
  AppPublisher=bbstrading
5
5
  DefaultDirName={pf}\TradeCopier
6
6
  DefaultGroupName=TradeCopier
@@ -7,6 +7,7 @@ try:
7
7
  import MetaTrader5 as mt5
8
8
  except ImportError:
9
9
  import bbstrader.compat # noqa: F401
10
+ import MetaTrader5 as mt5
10
11
 
11
12
  from bbstrader.api.handlers import Mt5Handlers
12
13
  from bbstrader.api.client import MetaTraderClient # type: ignore
@@ -0,0 +1,58 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ import pytest
4
+
5
+ from bbstrader.btengine.performance import (
6
+ calculate_risk_metrics,
7
+ create_calmar_ratio,
8
+ create_drawdowns,
9
+ create_omega_ratio,
10
+ create_sharpe_ratio,
11
+ create_sortino_ratio,
12
+ create_tail_ratio,
13
+ )
14
+
15
+
16
+ @pytest.fixture
17
+ def sample_returns():
18
+ dates = pd.date_range("2020-01-01", periods=100)
19
+ returns = pd.Series(np.random.normal(0.001, 0.02, 100), index=dates)
20
+ return returns
21
+
22
+
23
+ @pytest.fixture
24
+ def sample_benchmark():
25
+ dates = pd.date_range("2020-01-01", periods=100)
26
+ returns = pd.Series(np.random.normal(0.0005, 0.015, 100), index=dates)
27
+ return returns
28
+
29
+
30
+ def test_create_drawdowns(sample_returns):
31
+ drawdown, max_dd, max_duration = create_drawdowns(sample_returns)
32
+ assert isinstance(drawdown, pd.Series)
33
+ assert isinstance(max_dd, float)
34
+ assert isinstance(max_duration, (float, int))
35
+
36
+
37
+ def test_create_drawdowns_empty():
38
+ empty_returns = pd.Series([], dtype=float)
39
+ drawdown, max_dd, max_duration = create_drawdowns(empty_returns)
40
+ assert drawdown.empty
41
+ assert max_dd == 0.0
42
+ assert max_duration == 0.0
43
+
44
+
45
+ def test_ratios(sample_returns):
46
+ assert isinstance(create_sharpe_ratio(sample_returns), (float, np.float64))
47
+ assert isinstance(create_sortino_ratio(sample_returns), (float, np.float64))
48
+ assert isinstance(create_omega_ratio(sample_returns), (float, np.float64))
49
+ assert isinstance(create_calmar_ratio(sample_returns), (float, np.float64))
50
+ assert isinstance(create_tail_ratio(sample_returns), (float, np.float64))
51
+
52
+
53
+ def test_risk_metrics(sample_returns, sample_benchmark):
54
+ metrics = calculate_risk_metrics(sample_returns, sample_benchmark)
55
+ assert isinstance(metrics, dict)
56
+ assert "alpha" in metrics
57
+ assert "beta" in metrics
58
+ assert "volatility" in metrics
@@ -0,0 +1,68 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ import pytest
4
+ from bbstrader.models.optimization import (
5
+ markowitz_weights,
6
+ hierarchical_risk_parity,
7
+ equal_weighted,
8
+ optimized_weights,
9
+ black_litterman_weights,
10
+ )
11
+
12
+
13
+ @pytest.fixture
14
+ def sample_prices():
15
+ dates = pd.date_range("2020-01-01", periods=100)
16
+ data = {
17
+ "AAPL": np.linspace(100, 150, 100) + np.random.normal(0, 2, 100),
18
+ "MSFT": np.linspace(200, 250, 100) + np.random.normal(0, 2, 100),
19
+ "GOOG": np.linspace(1000, 1100, 100) + np.random.normal(0, 5, 100),
20
+ }
21
+ return pd.DataFrame(data, index=dates)
22
+
23
+
24
+ def test_markowitz_weights(sample_prices):
25
+ weights = markowitz_weights(sample_prices)
26
+ assert isinstance(weights, dict)
27
+ assert len(weights) == 3
28
+ assert np.isclose(sum(weights.values()), 1.0)
29
+
30
+
31
+ def test_markowitz_min_vol(sample_prices):
32
+ weights = markowitz_weights(sample_prices, min_vol=True)
33
+ assert isinstance(weights, dict)
34
+ assert len(weights) == 3
35
+ assert np.isclose(sum(weights.values()), 1.0)
36
+
37
+
38
+ def test_hrp_weights(sample_prices):
39
+ weights = hierarchical_risk_parity(prices=sample_prices)
40
+ assert isinstance(weights, dict)
41
+ assert len(weights) == 3
42
+ assert np.isclose(sum(weights.values()), 1.0)
43
+
44
+
45
+ def test_equal_weighted(sample_prices):
46
+ weights = equal_weighted(prices=sample_prices)
47
+ assert isinstance(weights, dict)
48
+ assert len(weights) == 3
49
+ assert all(np.isclose(w, 1 / 3) for w in weights.values())
50
+
51
+
52
+ def test_black_litterman_weights(sample_prices):
53
+ views = {"AAPL": 0.05, "MSFT": 0.02}
54
+ weights = black_litterman_weights(sample_prices, views=views)
55
+ assert isinstance(weights, dict)
56
+ assert len(weights) == 3
57
+ assert np.isclose(sum(weights.values()), 1.0)
58
+
59
+
60
+ def test_optimized_weights_methods(sample_prices):
61
+ methods = ["markowitz", "min_vol", "hrp", "equal", "black_litterman"]
62
+ for method in methods:
63
+ kwargs = {}
64
+ if method == "black_litterman":
65
+ kwargs = {"views": {"AAPL": 0.05}}
66
+ weights = optimized_weights(prices=sample_prices, method=method, **kwargs)
67
+ assert isinstance(weights, dict)
68
+ assert np.isclose(sum(weights.values()), 1.0)
@@ -1 +0,0 @@
1
- 2.0.7
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes