bbstrader 0.2.93__py3-none-any.whl → 0.2.95__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of bbstrader might be problematic. Click here for more details.
- bbstrader/__ini__.py +20 -20
- bbstrader/__main__.py +50 -50
- bbstrader/btengine/__init__.py +54 -54
- bbstrader/btengine/scripts.py +157 -157
- bbstrader/compat.py +19 -19
- bbstrader/config.py +137 -137
- bbstrader/core/data.py +22 -22
- bbstrader/core/utils.py +146 -146
- bbstrader/metatrader/__init__.py +6 -6
- bbstrader/metatrader/account.py +1516 -1516
- bbstrader/metatrader/copier.py +750 -745
- bbstrader/metatrader/rates.py +584 -584
- bbstrader/metatrader/risk.py +749 -748
- bbstrader/metatrader/scripts.py +81 -81
- bbstrader/metatrader/trade.py +1836 -1836
- bbstrader/metatrader/utils.py +645 -645
- bbstrader/models/__init__.py +10 -10
- bbstrader/models/factors.py +312 -312
- bbstrader/models/ml.py +1272 -1272
- bbstrader/models/optimization.py +182 -182
- bbstrader/models/portfolio.py +223 -223
- bbstrader/models/risk.py +398 -398
- bbstrader/trading/__init__.py +11 -11
- bbstrader/trading/execution.py +846 -846
- bbstrader/trading/script.py +155 -155
- bbstrader/trading/scripts.py +69 -69
- bbstrader/trading/strategies.py +860 -860
- bbstrader/tseries.py +1842 -1842
- {bbstrader-0.2.93.dist-info → bbstrader-0.2.95.dist-info}/LICENSE +21 -21
- {bbstrader-0.2.93.dist-info → bbstrader-0.2.95.dist-info}/METADATA +188 -187
- bbstrader-0.2.95.dist-info/RECORD +44 -0
- bbstrader-0.2.93.dist-info/RECORD +0 -44
- {bbstrader-0.2.93.dist-info → bbstrader-0.2.95.dist-info}/WHEEL +0 -0
- {bbstrader-0.2.93.dist-info → bbstrader-0.2.95.dist-info}/entry_points.txt +0 -0
- {bbstrader-0.2.93.dist-info → bbstrader-0.2.95.dist-info}/top_level.txt +0 -0
bbstrader/models/portfolio.py
CHANGED
|
@@ -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
|