bbstrader 0.2.92__py3-none-any.whl → 0.2.94__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of bbstrader might be problematic. Click here for more details.

Files changed (36) hide show
  1. bbstrader/__ini__.py +20 -20
  2. bbstrader/__main__.py +50 -50
  3. bbstrader/btengine/__init__.py +54 -54
  4. bbstrader/btengine/data.py +11 -9
  5. bbstrader/btengine/scripts.py +157 -157
  6. bbstrader/compat.py +19 -19
  7. bbstrader/config.py +137 -137
  8. bbstrader/core/data.py +22 -22
  9. bbstrader/core/utils.py +146 -146
  10. bbstrader/metatrader/__init__.py +6 -6
  11. bbstrader/metatrader/account.py +1516 -1516
  12. bbstrader/metatrader/copier.py +750 -735
  13. bbstrader/metatrader/rates.py +584 -584
  14. bbstrader/metatrader/risk.py +749 -748
  15. bbstrader/metatrader/scripts.py +81 -81
  16. bbstrader/metatrader/trade.py +1836 -1826
  17. bbstrader/metatrader/utils.py +645 -645
  18. bbstrader/models/__init__.py +10 -10
  19. bbstrader/models/factors.py +312 -312
  20. bbstrader/models/ml.py +1272 -1265
  21. bbstrader/models/optimization.py +182 -182
  22. bbstrader/models/portfolio.py +223 -223
  23. bbstrader/models/risk.py +398 -398
  24. bbstrader/trading/__init__.py +11 -11
  25. bbstrader/trading/execution.py +846 -842
  26. bbstrader/trading/script.py +155 -155
  27. bbstrader/trading/scripts.py +69 -69
  28. bbstrader/trading/strategies.py +860 -860
  29. bbstrader/tseries.py +1842 -1842
  30. {bbstrader-0.2.92.dist-info → bbstrader-0.2.94.dist-info}/LICENSE +21 -21
  31. {bbstrader-0.2.92.dist-info → bbstrader-0.2.94.dist-info}/METADATA +188 -187
  32. bbstrader-0.2.94.dist-info/RECORD +44 -0
  33. {bbstrader-0.2.92.dist-info → bbstrader-0.2.94.dist-info}/WHEEL +1 -1
  34. bbstrader-0.2.92.dist-info/RECORD +0 -44
  35. {bbstrader-0.2.92.dist-info → bbstrader-0.2.94.dist-info}/entry_points.txt +0 -0
  36. {bbstrader-0.2.92.dist-info → bbstrader-0.2.94.dist-info}/top_level.txt +0 -0
@@ -1,223 +1,223 @@
1
- import matplotlib.pyplot as plt
2
- import pandas as pd
3
- import seaborn as sns
4
- from sklearn.decomposition import PCA
5
- from sklearn.preprocessing import scale
6
-
7
- from bbstrader.models.optimization import (
8
- equal_weighted,
9
- hierarchical_risk_parity,
10
- markowitz_weights,
11
- )
12
-
13
- __all__ = ["EigenPortfolios"]
14
-
15
-
16
- class EigenPortfolios(object):
17
- """
18
- The `EigenPortfolios` class applies Principal Component Analysis (PCA) to a covariance matrix of normalized asset returns
19
- to derive portfolios (eigenportfolios) that capture distinct risk factors in the asset returns. Each eigenportfolio
20
- represents a principal component of the return covariance matrix, ordered by the magnitude of its eigenvalue. These
21
- portfolios capture most of the variance in asset returns and are mutually uncorrelated.
22
-
23
- Notes
24
- -----
25
- The implementation is inspired by the book "Machine Learning for Algorithmic Trading" by Stefan Jansen.
26
-
27
- References
28
- ----------
29
- Stefan Jansen (2020). Machine Learning for Algorithmic Trading - Second Edition.
30
- chapter 13, Data-Driven Risk Factors and Asset Allocation with Unsupervised Learning.
31
-
32
- """
33
-
34
- def __init__(self):
35
- self.returns = None
36
- self.n_portfolios = None
37
- self._portfolios = None
38
- self._fit_called = False
39
-
40
- def get_portfolios(self) -> pd.DataFrame:
41
- """
42
- Returns the computed eigenportfolios (weights of assets in each portfolio).
43
-
44
- Returns
45
- -------
46
- pd.DataFrame
47
- DataFrame containing eigenportfolio weights for each asset.
48
-
49
- Raises
50
- ------
51
- ValueError
52
- If `fit()` has not been called before retrieving portfolios.
53
- """
54
- if not self._fit_called:
55
- raise ValueError("fit() must be called first")
56
- return self._portfolios
57
-
58
- def fit(self, returns: pd.DataFrame, n_portfolios: int = 4) -> pd.DataFrame:
59
- """
60
- Computes the eigenportfolios based on PCA of the asset returns' covariance matrix.
61
-
62
- Parameters
63
- ----------
64
- returns : pd.DataFrame
65
- Historical returns of assets to be used for PCA.
66
- n_portfolios : int, optional
67
- Number of eigenportfolios to compute (default is 4).
68
-
69
- Returns
70
- -------
71
- pd.DataFrame
72
- DataFrame containing normalized weights for each eigenportfolio.
73
-
74
- Notes
75
- -----
76
- This method performs winsorization and normalization on returns to reduce the impact of outliers
77
- and achieve zero mean and unit variance. It uses the first `n_portfolios` principal components
78
- as portfolio weights.
79
- """
80
- # Winsorize and normalize the returns
81
- normed_returns = scale(
82
- returns.clip(
83
- lower=returns.quantile(q=0.025), upper=returns.quantile(q=0.975), axis=1
84
- ).apply(lambda x: x.sub(x.mean()).div(x.std()))
85
- )
86
- returns = returns.dropna(thresh=int(normed_returns.shape[0] * 0.95), axis=1)
87
- returns = returns.dropna(thresh=int(normed_returns.shape[1] * 0.95))
88
-
89
- cov = returns.cov()
90
- cov.columns = cov.columns.astype(str)
91
- pca = PCA()
92
- pca.fit(cov)
93
-
94
- top_portfolios = pd.DataFrame(
95
- pca.components_[:n_portfolios], columns=cov.columns
96
- )
97
- eigen_portfolios = top_portfolios.div(top_portfolios.sum(axis=1), axis=0)
98
- eigen_portfolios.index = [f"Portfolio {i}" for i in range(1, n_portfolios + 1)]
99
- self._portfolios = eigen_portfolios
100
- self.returns = returns
101
- self.n_portfolios = n_portfolios
102
- self._fit_called = True
103
-
104
- def plot_weights(self):
105
- """
106
- Plots the weights of each asset in each eigenportfolio as bar charts.
107
-
108
- Notes
109
- -----
110
- Each subplot represents one eigenportfolio, showing the contribution of each asset.
111
- """
112
- eigen_portfolios = self.get_portfolios()
113
- n_cols = 2
114
- n_rows = (self.n_portfolios + 1) // n_cols
115
- figsize = (n_cols * 10, n_rows * 5)
116
- axes = eigen_portfolios.T.plot.bar(
117
- subplots=True, layout=(n_rows, n_cols), figsize=figsize, legend=False
118
- )
119
- for ax in axes.flatten():
120
- ax.set_ylabel("Portfolio Weight")
121
- ax.set_xlabel("")
122
-
123
- sns.despine()
124
- plt.tight_layout()
125
- plt.show()
126
-
127
- def plot_performance(self):
128
- """
129
- Plots the cumulative returns of each eigenportfolio over time.
130
-
131
- Notes
132
- -----
133
- This method calculates the historical cumulative performance of each eigenportfolio
134
- by weighting asset returns according to eigenportfolio weights.
135
- """
136
- eigen_portfolios = self.get_portfolios()
137
- returns = self.returns.copy()
138
-
139
- n_cols = 2
140
- n_rows = (self.n_portfolios + 1 + n_cols - 1) // n_cols
141
- figsize = (n_cols * 10, n_rows * 5)
142
- fig, axes = plt.subplots(
143
- nrows=n_rows, ncols=n_cols, figsize=figsize, sharex=True
144
- )
145
- axes = axes.flatten()
146
- returns.mean(1).add(1).cumprod().sub(1).plot(title="The Market", ax=axes[0])
147
-
148
- for i in range(self.n_portfolios):
149
- rc = returns.mul(eigen_portfolios.iloc[i]).sum(1).add(1).cumprod().sub(1)
150
- rc.plot(title=f"Portfolio {i+1}", ax=axes[i + 1], lw=1, rot=0)
151
-
152
- for j in range(self.n_portfolios + 1, len(axes)):
153
- fig.delaxes(axes[j])
154
-
155
- for i in range(self.n_portfolios + 1):
156
- axes[i].set_xlabel("")
157
-
158
- sns.despine()
159
- fig.tight_layout()
160
- plt.show()
161
-
162
- def optimize(
163
- self,
164
- portfolio: int = 1,
165
- optimizer: str = "hrp",
166
- prices=None,
167
- freq=252,
168
- plot=True,
169
- ):
170
- """
171
- Optimizes the chosen eigenportfolio based on a specified optimization method.
172
-
173
- Parameters
174
- ----------
175
- portfolio : int, optional
176
- Index of the eigenportfolio to optimize (default is 1).
177
- optimizer : str, optional
178
- Optimization method: 'markowitz', 'hrp' (Hierarchical Risk Parity), or 'equal' (default is 'hrp').
179
- prices : pd.DataFrame, optional
180
- Asset prices used for Markowitz optimization (required if optimizer is 'markowitz').
181
- freq : int, optional
182
- Frequency of returns (e.g., 252 for daily returns).
183
- plot : bool, optional
184
- Whether to plot the performance of the optimized portfolio (default is True).
185
-
186
- Returns
187
- -------
188
- dict
189
- Dictionary of optimized asset weights.
190
-
191
- Raises
192
- ------
193
- ValueError
194
- If an unknown optimizer is specified, or if prices are not provided when using Markowitz optimization.
195
-
196
- Notes
197
- -----
198
- The optimization method varies based on risk-return assumptions, with options for traditional Markowitz optimization,
199
- Hierarchical Risk Parity, or equal weighting.
200
- """
201
- portfolio = self.get_portfolios().iloc[portfolio - 1]
202
- returns = self.returns.loc[:, portfolio.index]
203
- returns = returns.loc[:, ~returns.columns.duplicated()]
204
- returns = returns.loc[~returns.index.duplicated(keep="first")]
205
- if optimizer == "markowitz":
206
- if prices is None:
207
- raise ValueError("prices must be provided for markowitz optimization")
208
- prices = prices.loc[:, returns.columns]
209
- weights = markowitz_weights(prices=prices, freq=freq)
210
- elif optimizer == "hrp":
211
- weights = hierarchical_risk_parity(returns=returns, freq=freq)
212
- elif optimizer == "equal":
213
- weights = equal_weighted(returns=returns)
214
- else:
215
- raise ValueError(f"Unknown optimizer: {optimizer}")
216
- if plot:
217
- # plot the optimized potfolio performance
218
- returns = returns.filter(weights.keys())
219
- rc = returns.mul(weights).sum(1).add(1).cumprod().sub(1)
220
- rc.plot(title=f"Optimized {portfolio.name}", lw=1, rot=0)
221
- sns.despine()
222
- plt.show()
223
- return weights
1
+ import matplotlib.pyplot as plt
2
+ import pandas as pd
3
+ import seaborn as sns
4
+ from sklearn.decomposition import PCA
5
+ from sklearn.preprocessing import scale
6
+
7
+ from bbstrader.models.optimization import (
8
+ equal_weighted,
9
+ hierarchical_risk_parity,
10
+ markowitz_weights,
11
+ )
12
+
13
+ __all__ = ["EigenPortfolios"]
14
+
15
+
16
+ class EigenPortfolios(object):
17
+ """
18
+ The `EigenPortfolios` class applies Principal Component Analysis (PCA) to a covariance matrix of normalized asset returns
19
+ to derive portfolios (eigenportfolios) that capture distinct risk factors in the asset returns. Each eigenportfolio
20
+ represents a principal component of the return covariance matrix, ordered by the magnitude of its eigenvalue. These
21
+ portfolios capture most of the variance in asset returns and are mutually uncorrelated.
22
+
23
+ Notes
24
+ -----
25
+ The implementation is inspired by the book "Machine Learning for Algorithmic Trading" by Stefan Jansen.
26
+
27
+ References
28
+ ----------
29
+ Stefan Jansen (2020). Machine Learning for Algorithmic Trading - Second Edition.
30
+ chapter 13, Data-Driven Risk Factors and Asset Allocation with Unsupervised Learning.
31
+
32
+ """
33
+
34
+ def __init__(self):
35
+ self.returns = None
36
+ self.n_portfolios = None
37
+ self._portfolios = None
38
+ self._fit_called = False
39
+
40
+ def get_portfolios(self) -> pd.DataFrame:
41
+ """
42
+ Returns the computed eigenportfolios (weights of assets in each portfolio).
43
+
44
+ Returns
45
+ -------
46
+ pd.DataFrame
47
+ DataFrame containing eigenportfolio weights for each asset.
48
+
49
+ Raises
50
+ ------
51
+ ValueError
52
+ If `fit()` has not been called before retrieving portfolios.
53
+ """
54
+ if not self._fit_called:
55
+ raise ValueError("fit() must be called first")
56
+ return self._portfolios
57
+
58
+ def fit(self, returns: pd.DataFrame, n_portfolios: int = 4) -> pd.DataFrame:
59
+ """
60
+ Computes the eigenportfolios based on PCA of the asset returns' covariance matrix.
61
+
62
+ Parameters
63
+ ----------
64
+ returns : pd.DataFrame
65
+ Historical returns of assets to be used for PCA.
66
+ n_portfolios : int, optional
67
+ Number of eigenportfolios to compute (default is 4).
68
+
69
+ Returns
70
+ -------
71
+ pd.DataFrame
72
+ DataFrame containing normalized weights for each eigenportfolio.
73
+
74
+ Notes
75
+ -----
76
+ This method performs winsorization and normalization on returns to reduce the impact of outliers
77
+ and achieve zero mean and unit variance. It uses the first `n_portfolios` principal components
78
+ as portfolio weights.
79
+ """
80
+ # Winsorize and normalize the returns
81
+ normed_returns = scale(
82
+ returns.clip(
83
+ lower=returns.quantile(q=0.025), upper=returns.quantile(q=0.975), axis=1
84
+ ).apply(lambda x: x.sub(x.mean()).div(x.std()))
85
+ )
86
+ returns = returns.dropna(thresh=int(normed_returns.shape[0] * 0.95), axis=1)
87
+ returns = returns.dropna(thresh=int(normed_returns.shape[1] * 0.95))
88
+
89
+ cov = returns.cov()
90
+ cov.columns = cov.columns.astype(str)
91
+ pca = PCA()
92
+ pca.fit(cov)
93
+
94
+ top_portfolios = pd.DataFrame(
95
+ pca.components_[:n_portfolios], columns=cov.columns
96
+ )
97
+ eigen_portfolios = top_portfolios.div(top_portfolios.sum(axis=1), axis=0)
98
+ eigen_portfolios.index = [f"Portfolio {i}" for i in range(1, n_portfolios + 1)]
99
+ self._portfolios = eigen_portfolios
100
+ self.returns = returns
101
+ self.n_portfolios = n_portfolios
102
+ self._fit_called = True
103
+
104
+ def plot_weights(self):
105
+ """
106
+ Plots the weights of each asset in each eigenportfolio as bar charts.
107
+
108
+ Notes
109
+ -----
110
+ Each subplot represents one eigenportfolio, showing the contribution of each asset.
111
+ """
112
+ eigen_portfolios = self.get_portfolios()
113
+ n_cols = 2
114
+ n_rows = (self.n_portfolios + 1) // n_cols
115
+ figsize = (n_cols * 10, n_rows * 5)
116
+ axes = eigen_portfolios.T.plot.bar(
117
+ subplots=True, layout=(n_rows, n_cols), figsize=figsize, legend=False
118
+ )
119
+ for ax in axes.flatten():
120
+ ax.set_ylabel("Portfolio Weight")
121
+ ax.set_xlabel("")
122
+
123
+ sns.despine()
124
+ plt.tight_layout()
125
+ plt.show()
126
+
127
+ def plot_performance(self):
128
+ """
129
+ Plots the cumulative returns of each eigenportfolio over time.
130
+
131
+ Notes
132
+ -----
133
+ This method calculates the historical cumulative performance of each eigenportfolio
134
+ by weighting asset returns according to eigenportfolio weights.
135
+ """
136
+ eigen_portfolios = self.get_portfolios()
137
+ returns = self.returns.copy()
138
+
139
+ n_cols = 2
140
+ n_rows = (self.n_portfolios + 1 + n_cols - 1) // n_cols
141
+ figsize = (n_cols * 10, n_rows * 5)
142
+ fig, axes = plt.subplots(
143
+ nrows=n_rows, ncols=n_cols, figsize=figsize, sharex=True
144
+ )
145
+ axes = axes.flatten()
146
+ returns.mean(1).add(1).cumprod().sub(1).plot(title="The Market", ax=axes[0])
147
+
148
+ for i in range(self.n_portfolios):
149
+ rc = returns.mul(eigen_portfolios.iloc[i]).sum(1).add(1).cumprod().sub(1)
150
+ rc.plot(title=f"Portfolio {i+1}", ax=axes[i + 1], lw=1, rot=0)
151
+
152
+ for j in range(self.n_portfolios + 1, len(axes)):
153
+ fig.delaxes(axes[j])
154
+
155
+ for i in range(self.n_portfolios + 1):
156
+ axes[i].set_xlabel("")
157
+
158
+ sns.despine()
159
+ fig.tight_layout()
160
+ plt.show()
161
+
162
+ def optimize(
163
+ self,
164
+ portfolio: int = 1,
165
+ optimizer: str = "hrp",
166
+ prices=None,
167
+ freq=252,
168
+ plot=True,
169
+ ):
170
+ """
171
+ Optimizes the chosen eigenportfolio based on a specified optimization method.
172
+
173
+ Parameters
174
+ ----------
175
+ portfolio : int, optional
176
+ Index of the eigenportfolio to optimize (default is 1).
177
+ optimizer : str, optional
178
+ Optimization method: 'markowitz', 'hrp' (Hierarchical Risk Parity), or 'equal' (default is 'hrp').
179
+ prices : pd.DataFrame, optional
180
+ Asset prices used for Markowitz optimization (required if optimizer is 'markowitz').
181
+ freq : int, optional
182
+ Frequency of returns (e.g., 252 for daily returns).
183
+ plot : bool, optional
184
+ Whether to plot the performance of the optimized portfolio (default is True).
185
+
186
+ Returns
187
+ -------
188
+ dict
189
+ Dictionary of optimized asset weights.
190
+
191
+ Raises
192
+ ------
193
+ ValueError
194
+ If an unknown optimizer is specified, or if prices are not provided when using Markowitz optimization.
195
+
196
+ Notes
197
+ -----
198
+ The optimization method varies based on risk-return assumptions, with options for traditional Markowitz optimization,
199
+ Hierarchical Risk Parity, or equal weighting.
200
+ """
201
+ portfolio = self.get_portfolios().iloc[portfolio - 1]
202
+ returns = self.returns.loc[:, portfolio.index]
203
+ returns = returns.loc[:, ~returns.columns.duplicated()]
204
+ returns = returns.loc[~returns.index.duplicated(keep="first")]
205
+ if optimizer == "markowitz":
206
+ if prices is None:
207
+ raise ValueError("prices must be provided for markowitz optimization")
208
+ prices = prices.loc[:, returns.columns]
209
+ weights = markowitz_weights(prices=prices, freq=freq)
210
+ elif optimizer == "hrp":
211
+ weights = hierarchical_risk_parity(returns=returns, freq=freq)
212
+ elif optimizer == "equal":
213
+ weights = equal_weighted(returns=returns)
214
+ else:
215
+ raise ValueError(f"Unknown optimizer: {optimizer}")
216
+ if plot:
217
+ # plot the optimized potfolio performance
218
+ returns = returns.filter(weights.keys())
219
+ rc = returns.mul(weights).sum(1).add(1).cumprod().sub(1)
220
+ rc.plot(title=f"Optimized {portfolio.name}", lw=1, rot=0)
221
+ sns.despine()
222
+ plt.show()
223
+ return weights