bbstrader 0.1.9__py3-none-any.whl → 0.1.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.
- bbstrader/__ini__.py +4 -2
- bbstrader/btengine/__init__.py +5 -5
- bbstrader/btengine/backtest.py +60 -12
- bbstrader/btengine/data.py +157 -57
- bbstrader/btengine/event.py +14 -5
- bbstrader/btengine/execution.py +128 -26
- bbstrader/btengine/performance.py +4 -7
- bbstrader/btengine/portfolio.py +83 -36
- bbstrader/btengine/strategy.py +525 -6
- bbstrader/config.py +111 -0
- bbstrader/metatrader/__init__.py +4 -4
- bbstrader/metatrader/account.py +348 -53
- bbstrader/metatrader/rates.py +280 -27
- bbstrader/metatrader/risk.py +34 -23
- bbstrader/metatrader/trade.py +328 -169
- bbstrader/metatrader/utils.py +2 -53
- bbstrader/models/factors.py +0 -0
- bbstrader/models/ml.py +0 -0
- bbstrader/models/optimization.py +170 -0
- bbstrader/models/portfolios.py +202 -0
- bbstrader/trading/__init__.py +1 -1
- bbstrader/trading/execution.py +275 -169
- bbstrader/trading/scripts.py +57 -0
- bbstrader/trading/strategies.py +41 -65
- bbstrader/tseries.py +274 -39
- {bbstrader-0.1.9.dist-info → bbstrader-0.1.92.dist-info}/METADATA +12 -3
- bbstrader-0.1.92.dist-info/RECORD +32 -0
- {bbstrader-0.1.9.dist-info → bbstrader-0.1.92.dist-info}/WHEEL +1 -1
- bbstrader-0.1.9.dist-info/RECORD +0 -26
- {bbstrader-0.1.9.dist-info → bbstrader-0.1.92.dist-info}/LICENSE +0 -0
- {bbstrader-0.1.9.dist-info → bbstrader-0.1.92.dist-info}/top_level.txt +0 -0
bbstrader/metatrader/utils.py
CHANGED
|
@@ -33,61 +33,8 @@ __all__ = [
|
|
|
33
33
|
"InternalFailTimeout",
|
|
34
34
|
"trade_retcode_message",
|
|
35
35
|
"raise_mt5_error",
|
|
36
|
-
"config_logger",
|
|
37
36
|
]
|
|
38
37
|
|
|
39
|
-
def config_logger(log_file: str, console_log=True):
|
|
40
|
-
# Configure the logger
|
|
41
|
-
logger = logging.getLogger(__name__)
|
|
42
|
-
logger.setLevel(logging.DEBUG)
|
|
43
|
-
|
|
44
|
-
# File handler
|
|
45
|
-
file_handler = logging.FileHandler(log_file)
|
|
46
|
-
file_handler.setLevel(logging.INFO)
|
|
47
|
-
|
|
48
|
-
# Formatter
|
|
49
|
-
formatter = logging.Formatter(
|
|
50
|
-
'%(asctime)s - %(levelname)s: %(message)s', datefmt="%Y-%m-%d %H:%M:%S")
|
|
51
|
-
file_handler.setFormatter(formatter)
|
|
52
|
-
|
|
53
|
-
# Add the handler to the logger
|
|
54
|
-
logger.addHandler(file_handler)
|
|
55
|
-
|
|
56
|
-
if console_log:
|
|
57
|
-
# handler for the console with a different level
|
|
58
|
-
console_handler = logging.StreamHandler()
|
|
59
|
-
console_handler.setLevel(logging.DEBUG)
|
|
60
|
-
console_handler.setFormatter(formatter)
|
|
61
|
-
logger.addHandler(console_handler)
|
|
62
|
-
|
|
63
|
-
return logger
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
class LogLevelFilter(logging.Filter):
|
|
67
|
-
def __init__(self, levels: List[int]):
|
|
68
|
-
"""
|
|
69
|
-
Initializes the filter with specific logging levels.
|
|
70
|
-
|
|
71
|
-
Args:
|
|
72
|
-
levels: A list of logging level values (integers) to include.
|
|
73
|
-
"""
|
|
74
|
-
super().__init__()
|
|
75
|
-
self.levels = levels
|
|
76
|
-
|
|
77
|
-
def filter(self, record: logging.LogRecord) -> bool:
|
|
78
|
-
"""
|
|
79
|
-
Filters log records based on their level.
|
|
80
|
-
|
|
81
|
-
Args:
|
|
82
|
-
record: The log record to check.
|
|
83
|
-
|
|
84
|
-
Returns:
|
|
85
|
-
True if the record's level is in the allowed levels, False otherwise.
|
|
86
|
-
"""
|
|
87
|
-
return record.levelno in self.levels
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
38
|
# TIMEFRAME is an enumeration with possible chart period values
|
|
92
39
|
# See https://www.mql5.com/en/docs/python_metatrader5/mt5copyratesfrom_py#timeframe
|
|
93
40
|
TIMEFRAMES = {
|
|
@@ -141,6 +88,7 @@ class TimeFrame(Enum):
|
|
|
141
88
|
W1 = "W1"
|
|
142
89
|
MN1 = "MN1"
|
|
143
90
|
|
|
91
|
+
|
|
144
92
|
class TerminalInfo(NamedTuple):
|
|
145
93
|
"""
|
|
146
94
|
Represents general information about the trading terminal.
|
|
@@ -470,6 +418,7 @@ class TradeDeal(NamedTuple):
|
|
|
470
418
|
comment: str
|
|
471
419
|
external_id: str
|
|
472
420
|
|
|
421
|
+
|
|
473
422
|
class InvalidBroker(Exception):
|
|
474
423
|
"""Exception raised for invalid broker errors."""
|
|
475
424
|
def __init__(self, message="Invalid broker."):
|
|
File without changes
|
bbstrader/models/ml.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from pypfopt import risk_models
|
|
4
|
+
from pypfopt import expected_returns
|
|
5
|
+
from pypfopt.efficient_frontier import EfficientFrontier
|
|
6
|
+
from pypfopt.hierarchical_portfolio import HRPOpt
|
|
7
|
+
import warnings
|
|
8
|
+
|
|
9
|
+
def markowitz_weights(prices=None, freq=252):
|
|
10
|
+
"""
|
|
11
|
+
Calculates optimal portfolio weights using Markowitz's mean-variance optimization (Max Sharpe Ratio) with multiple solvers.
|
|
12
|
+
|
|
13
|
+
Parameters:
|
|
14
|
+
----------
|
|
15
|
+
prices : pd.DataFrame, optional
|
|
16
|
+
Price data for assets, where rows represent time periods and columns represent assets.
|
|
17
|
+
freq : int, optional
|
|
18
|
+
Frequency of the data, such as 252 for daily returns in a year (default is 252).
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
-------
|
|
22
|
+
dict
|
|
23
|
+
Dictionary containing the optimal asset weights for maximizing the Sharpe ratio, normalized to sum to 1.
|
|
24
|
+
|
|
25
|
+
Notes:
|
|
26
|
+
-----
|
|
27
|
+
This function attempts to maximize the Sharpe ratio by iterating through various solvers ('SCS', 'ECOS', 'OSQP')
|
|
28
|
+
from the PyPortfolioOpt library. If a solver fails, it proceeds to the next one. If none succeed, an error message
|
|
29
|
+
is printed for each solver that fails.
|
|
30
|
+
|
|
31
|
+
This function is useful for portfolio with a small number of assets, as it may not scale well for large portfolios.
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
------
|
|
35
|
+
Exception
|
|
36
|
+
If all solvers fail, each will print an exception error message during runtime.
|
|
37
|
+
"""
|
|
38
|
+
returns = expected_returns.mean_historical_return(prices, frequency=freq)
|
|
39
|
+
cov = risk_models.sample_cov(prices, frequency=freq)
|
|
40
|
+
|
|
41
|
+
# Try different solvers to maximize Sharpe ratio
|
|
42
|
+
for solver in ['SCS', 'ECOS', 'OSQP']:
|
|
43
|
+
ef = EfficientFrontier(expected_returns=returns,
|
|
44
|
+
cov_matrix=cov,
|
|
45
|
+
weight_bounds=(0, 1),
|
|
46
|
+
solver=solver)
|
|
47
|
+
try:
|
|
48
|
+
weights = ef.max_sharpe()
|
|
49
|
+
return ef.clean_weights()
|
|
50
|
+
except Exception as e:
|
|
51
|
+
print(f"Solver {solver} failed with error: {e}")
|
|
52
|
+
|
|
53
|
+
def hierarchical_risk_parity(prices=None, returns=None, freq=252):
|
|
54
|
+
"""
|
|
55
|
+
Computes asset weights using Hierarchical Risk Parity (HRP) for risk-averse portfolio allocation.
|
|
56
|
+
|
|
57
|
+
Parameters:
|
|
58
|
+
----------
|
|
59
|
+
prices : pd.DataFrame, optional
|
|
60
|
+
Price data for assets; if provided, daily returns will be calculated.
|
|
61
|
+
returns : pd.DataFrame, optional
|
|
62
|
+
Daily returns for assets. One of `prices` or `returns` must be provided.
|
|
63
|
+
freq : int, optional
|
|
64
|
+
Number of days to consider in calculating portfolio weights (default is 252).
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
-------
|
|
68
|
+
dict
|
|
69
|
+
Optimized asset weights using the HRP method, with asset weights summing to 1.
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
------
|
|
73
|
+
ValueError
|
|
74
|
+
If neither `prices` nor `returns` are provided.
|
|
75
|
+
|
|
76
|
+
Notes:
|
|
77
|
+
-----
|
|
78
|
+
Hierarchical Risk Parity is particularly useful for portfolios with a large number of assets,
|
|
79
|
+
as it mitigates issues of multicollinearity and estimation errors in covariance matrices by
|
|
80
|
+
using hierarchical clustering.
|
|
81
|
+
"""
|
|
82
|
+
warnings.filterwarnings("ignore")
|
|
83
|
+
if returns is None and prices is None:
|
|
84
|
+
raise ValueError("Either prices or returns must be provided")
|
|
85
|
+
if returns is None:
|
|
86
|
+
returns = prices.pct_change().dropna()
|
|
87
|
+
# Remove duplicate columns and index
|
|
88
|
+
returns = returns.loc[:, ~returns.columns.duplicated()]
|
|
89
|
+
returns = returns.loc[~returns.index.duplicated(keep='first')]
|
|
90
|
+
hrp = HRPOpt(returns=returns.iloc[-freq:])
|
|
91
|
+
return hrp.optimize()
|
|
92
|
+
|
|
93
|
+
def equal_weighted(prices=None, returns=None, round_digits=5):
|
|
94
|
+
"""
|
|
95
|
+
Generates an equal-weighted portfolio by assigning an equal proportion to each asset.
|
|
96
|
+
|
|
97
|
+
Parameters:
|
|
98
|
+
----------
|
|
99
|
+
prices : pd.DataFrame, optional
|
|
100
|
+
Price data for assets, where each column represents an asset.
|
|
101
|
+
returns : pd.DataFrame, optional
|
|
102
|
+
Return data for assets. One of `prices` or `returns` must be provided.
|
|
103
|
+
round_digits : int, optional
|
|
104
|
+
Number of decimal places to round each weight to (default is 5).
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
-------
|
|
108
|
+
dict
|
|
109
|
+
Dictionary with equal weights assigned to each asset, summing to 1.
|
|
110
|
+
|
|
111
|
+
Raises:
|
|
112
|
+
------
|
|
113
|
+
ValueError
|
|
114
|
+
If neither `prices` nor `returns` are provided.
|
|
115
|
+
|
|
116
|
+
Notes:
|
|
117
|
+
-----
|
|
118
|
+
Equal weighting is a simple allocation method that assumes equal importance across all assets,
|
|
119
|
+
useful as a baseline model and when no strong views exist on asset return expectations or risk.
|
|
120
|
+
"""
|
|
121
|
+
if returns is None and prices is None:
|
|
122
|
+
raise ValueError("Either prices or returns must be provided")
|
|
123
|
+
if returns is None:
|
|
124
|
+
n = len(prices.columns)
|
|
125
|
+
columns = prices.columns
|
|
126
|
+
else:
|
|
127
|
+
n = len(returns.columns)
|
|
128
|
+
columns = returns.columns
|
|
129
|
+
return {col: round(1/n, round_digits) for col in columns}
|
|
130
|
+
|
|
131
|
+
def optimized_weights(prices=None, returns=None, freq=252, method='markowitz'):
|
|
132
|
+
"""
|
|
133
|
+
Selects an optimization method to calculate portfolio weights based on user preference.
|
|
134
|
+
|
|
135
|
+
Parameters:
|
|
136
|
+
----------
|
|
137
|
+
prices : pd.DataFrame, optional
|
|
138
|
+
Price data for assets, required for certain methods.
|
|
139
|
+
returns : pd.DataFrame, optional
|
|
140
|
+
Returns data for assets, an alternative input for certain methods.
|
|
141
|
+
freq : int, optional
|
|
142
|
+
Number of days for calculating portfolio weights, such as 252 for a year's worth of daily returns (default is 252).
|
|
143
|
+
method : str, optional
|
|
144
|
+
Optimization method to use ('markowitz', 'hrp', or 'equal') (default is 'markowitz').
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
-------
|
|
148
|
+
dict
|
|
149
|
+
Dictionary containing optimized asset weights based on the chosen method.
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
------
|
|
153
|
+
ValueError
|
|
154
|
+
If an unknown optimization method is specified.
|
|
155
|
+
|
|
156
|
+
Notes:
|
|
157
|
+
-----
|
|
158
|
+
This function integrates different optimization methods:
|
|
159
|
+
- 'markowitz': mean-variance optimization with max Sharpe ratio
|
|
160
|
+
- 'hrp': Hierarchical Risk Parity, for risk-based clustering of assets
|
|
161
|
+
- 'equal': Equal weighting across all assets
|
|
162
|
+
"""
|
|
163
|
+
if method == 'markowitz':
|
|
164
|
+
return markowitz_weights(prices=prices, freq=freq)
|
|
165
|
+
elif method == 'hrp':
|
|
166
|
+
return hierarchical_risk_parity(prices=prices, returns=returns, freq=freq)
|
|
167
|
+
elif method == 'equal':
|
|
168
|
+
return equal_weighted(prices=prices, returns=returns)
|
|
169
|
+
else:
|
|
170
|
+
raise ValueError(f"Unknown method: {method}")
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import seaborn as sns
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
from sklearn.decomposition import PCA
|
|
6
|
+
from sklearn.preprocessing import scale
|
|
7
|
+
from bbstrader.models.optimization import (
|
|
8
|
+
markowitz_weights,
|
|
9
|
+
hierarchical_risk_parity,
|
|
10
|
+
equal_weighted
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EigenPortfolios(object):
|
|
15
|
+
"""
|
|
16
|
+
The `EigenPortfolios` class applies Principal Component Analysis (PCA) to a covariance matrix of normalized asset returns
|
|
17
|
+
to derive portfolios (eigenportfolios) that capture distinct risk factors in the asset returns. Each eigenportfolio
|
|
18
|
+
represents a principal component of the return covariance matrix, ordered by the magnitude of its eigenvalue. These
|
|
19
|
+
portfolios capture most of the variance in asset returns and are mutually uncorrelated.
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self.returns = None
|
|
24
|
+
self.n_portfolios = None
|
|
25
|
+
self._portfolios = None
|
|
26
|
+
self._fit_called = False
|
|
27
|
+
|
|
28
|
+
def get_portfolios(self) -> pd.DataFrame:
|
|
29
|
+
"""
|
|
30
|
+
Returns the computed eigenportfolios (weights of assets in each portfolio).
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
-------
|
|
34
|
+
pd.DataFrame
|
|
35
|
+
DataFrame containing eigenportfolio weights for each asset.
|
|
36
|
+
|
|
37
|
+
Raises:
|
|
38
|
+
------
|
|
39
|
+
ValueError
|
|
40
|
+
If `fit()` has not been called before retrieving portfolios.
|
|
41
|
+
"""
|
|
42
|
+
if not self._fit_called:
|
|
43
|
+
raise ValueError("fit() must be called first")
|
|
44
|
+
return self._portfolios
|
|
45
|
+
|
|
46
|
+
def fit(self, returns: pd.DataFrame, n_portfolios: int=4) -> pd.DataFrame:
|
|
47
|
+
"""
|
|
48
|
+
Computes the eigenportfolios based on PCA of the asset returns' covariance matrix.
|
|
49
|
+
|
|
50
|
+
Parameters:
|
|
51
|
+
----------
|
|
52
|
+
returns : pd.DataFrame
|
|
53
|
+
Historical returns of assets to be used for PCA.
|
|
54
|
+
n_portfolios : int, optional
|
|
55
|
+
Number of eigenportfolios to compute (default is 4).
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
-------
|
|
59
|
+
pd.DataFrame
|
|
60
|
+
DataFrame containing normalized weights for each eigenportfolio.
|
|
61
|
+
|
|
62
|
+
Notes:
|
|
63
|
+
-----
|
|
64
|
+
This method performs winsorization and normalization on returns to reduce the impact of outliers
|
|
65
|
+
and achieve zero mean and unit variance. It uses the first `n_portfolios` principal components
|
|
66
|
+
as portfolio weights.
|
|
67
|
+
"""
|
|
68
|
+
# Winsorize and normalize the returns
|
|
69
|
+
normed_returns = scale(returns
|
|
70
|
+
.clip(lower=returns.quantile(q=.025),
|
|
71
|
+
upper=returns.quantile(q=.975),
|
|
72
|
+
axis=1)
|
|
73
|
+
.apply(lambda x: x.sub(x.mean()).div(x.std())))
|
|
74
|
+
returns = returns.dropna(thresh=int(normed_returns.shape[0] * .95), axis=1)
|
|
75
|
+
returns = returns.dropna(thresh=int(normed_returns.shape[1] * .95))
|
|
76
|
+
|
|
77
|
+
cov = returns.cov()
|
|
78
|
+
cov.columns = cov.columns.astype(str)
|
|
79
|
+
pca = PCA()
|
|
80
|
+
pca.fit(cov)
|
|
81
|
+
|
|
82
|
+
top_portfolios = pd.DataFrame(pca.components_[:n_portfolios], columns=cov.columns)
|
|
83
|
+
eigen_portfolios = top_portfolios.div(top_portfolios.sum(axis=1), axis=0)
|
|
84
|
+
eigen_portfolios.index = [f"Portfolio {i}" for i in range(1, n_portfolios + 1)]
|
|
85
|
+
self._portfolios = eigen_portfolios
|
|
86
|
+
self.returns = returns
|
|
87
|
+
self.n_portfolios = n_portfolios
|
|
88
|
+
self._fit_called = True
|
|
89
|
+
|
|
90
|
+
def plot_weights(self):
|
|
91
|
+
"""
|
|
92
|
+
Plots the weights of each asset in each eigenportfolio as bar charts.
|
|
93
|
+
|
|
94
|
+
Notes:
|
|
95
|
+
-----
|
|
96
|
+
Each subplot represents one eigenportfolio, showing the contribution of each asset.
|
|
97
|
+
"""
|
|
98
|
+
eigen_portfolios = self.get_portfolios()
|
|
99
|
+
n_cols = 2
|
|
100
|
+
n_rows = (self.n_portfolios + 1) // n_cols
|
|
101
|
+
figsize = (n_cols * 10, n_rows * 5)
|
|
102
|
+
axes = eigen_portfolios.T.plot.bar(subplots=True,
|
|
103
|
+
layout=(n_rows, n_cols),
|
|
104
|
+
figsize=figsize,
|
|
105
|
+
legend=False)
|
|
106
|
+
for ax in axes.flatten():
|
|
107
|
+
ax.set_ylabel('Portfolio Weight')
|
|
108
|
+
ax.set_xlabel('')
|
|
109
|
+
|
|
110
|
+
sns.despine()
|
|
111
|
+
plt.tight_layout()
|
|
112
|
+
plt.show()
|
|
113
|
+
|
|
114
|
+
def plot_performance(self):
|
|
115
|
+
"""
|
|
116
|
+
Plots the cumulative returns of each eigenportfolio over time.
|
|
117
|
+
|
|
118
|
+
Notes:
|
|
119
|
+
-----
|
|
120
|
+
This method calculates the historical cumulative performance of each eigenportfolio
|
|
121
|
+
by weighting asset returns according to eigenportfolio weights.
|
|
122
|
+
"""
|
|
123
|
+
eigen_portfolios = self.get_portfolios()
|
|
124
|
+
returns = self.returns.copy()
|
|
125
|
+
|
|
126
|
+
n_cols = 2
|
|
127
|
+
n_rows = (self.n_portfolios + 1 + n_cols - 1) // n_cols
|
|
128
|
+
figsize = (n_cols * 10, n_rows * 5)
|
|
129
|
+
fig, axes = plt.subplots(nrows=n_rows, ncols=n_cols,
|
|
130
|
+
figsize=figsize, sharex=True)
|
|
131
|
+
axes = axes.flatten()
|
|
132
|
+
returns.mean(1).add(1).cumprod().sub(1).plot(title='The Market', ax=axes[0])
|
|
133
|
+
|
|
134
|
+
for i in range(self.n_portfolios):
|
|
135
|
+
rc = returns.mul(eigen_portfolios.iloc[i]).sum(1).add(1).cumprod().sub(1)
|
|
136
|
+
rc.plot(title=f'Portfolio {i+1}', ax=axes[i + 1], lw=1, rot=0)
|
|
137
|
+
|
|
138
|
+
for j in range(self.n_portfolios + 1, len(axes)):
|
|
139
|
+
fig.delaxes(axes[j])
|
|
140
|
+
|
|
141
|
+
for i in range(self.n_portfolios + 1):
|
|
142
|
+
axes[i].set_xlabel('')
|
|
143
|
+
|
|
144
|
+
sns.despine()
|
|
145
|
+
fig.tight_layout()
|
|
146
|
+
plt.show()
|
|
147
|
+
|
|
148
|
+
def optimize(self, portfolio: int = 1, optimizer: str = 'hrp', prices=None, freq=252, plot=True):
|
|
149
|
+
"""
|
|
150
|
+
Optimizes the chosen eigenportfolio based on a specified optimization method.
|
|
151
|
+
|
|
152
|
+
Parameters:
|
|
153
|
+
----------
|
|
154
|
+
portfolio : int, optional
|
|
155
|
+
Index of the eigenportfolio to optimize (default is 1).
|
|
156
|
+
optimizer : str, optional
|
|
157
|
+
Optimization method: 'markowitz', 'hrp' (Hierarchical Risk Parity), or 'equal' (default is 'hrp').
|
|
158
|
+
prices : pd.DataFrame, optional
|
|
159
|
+
Asset prices used for Markowitz optimization (required if optimizer is 'markowitz').
|
|
160
|
+
freq : int, optional
|
|
161
|
+
Frequency of returns (e.g., 252 for daily returns).
|
|
162
|
+
plot : bool, optional
|
|
163
|
+
Whether to plot the performance of the optimized portfolio (default is True).
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
-------
|
|
167
|
+
dict
|
|
168
|
+
Dictionary of optimized asset weights.
|
|
169
|
+
|
|
170
|
+
Raises:
|
|
171
|
+
------
|
|
172
|
+
ValueError
|
|
173
|
+
If an unknown optimizer is specified, or if prices are not provided when using Markowitz optimization.
|
|
174
|
+
|
|
175
|
+
Notes:
|
|
176
|
+
-----
|
|
177
|
+
The optimization method varies based on risk-return assumptions, with options for traditional Markowitz optimization,
|
|
178
|
+
Hierarchical Risk Parity, or equal weighting.
|
|
179
|
+
"""
|
|
180
|
+
portfolio = self.get_portfolios().iloc[portfolio - 1]
|
|
181
|
+
returns = self.returns.loc[:, portfolio.index]
|
|
182
|
+
returns = returns.loc[:, ~returns.columns.duplicated()]
|
|
183
|
+
returns = returns.loc[~returns.index.duplicated(keep='first')]
|
|
184
|
+
if optimizer == 'markowitz':
|
|
185
|
+
if prices is None:
|
|
186
|
+
raise ValueError("prices must be provided for markowitz optimization")
|
|
187
|
+
prices = prices.loc[:, returns.columns]
|
|
188
|
+
weights = markowitz_weights(prices=prices, freq=freq)
|
|
189
|
+
elif optimizer == 'hrp':
|
|
190
|
+
weights = hierarchical_risk_parity(returns=returns, freq=freq)
|
|
191
|
+
elif optimizer == 'equal':
|
|
192
|
+
weights = equal_weighted(returns=returns)
|
|
193
|
+
else:
|
|
194
|
+
raise ValueError(f"Unknown optimizer: {optimizer}")
|
|
195
|
+
if plot:
|
|
196
|
+
# plot the optimized potfolio performance
|
|
197
|
+
returns = returns.filter(weights.keys())
|
|
198
|
+
rc = returns.mul(weights).sum(1).add(1).cumprod().sub(1)
|
|
199
|
+
rc.plot(title=f'Optimized {portfolio.name}', lw=1, rot=0)
|
|
200
|
+
sns.despine()
|
|
201
|
+
plt.show()
|
|
202
|
+
return weights
|
bbstrader/trading/__init__.py
CHANGED
|
@@ -7,5 +7,5 @@ The module is designed to be flexible and extensible, allowing users to define t
|
|
|
7
7
|
strategies and customize the trading process.
|
|
8
8
|
|
|
9
9
|
"""
|
|
10
|
-
from bbstrader.trading.execution import
|
|
10
|
+
from bbstrader.trading.execution import *
|
|
11
11
|
from bbstrader.trading.strategies import *
|