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.
- bbstrader/__ini__.py +20 -20
- bbstrader/__main__.py +50 -50
- bbstrader/btengine/__init__.py +54 -54
- bbstrader/btengine/data.py +11 -9
- 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 -735
- bbstrader/metatrader/rates.py +584 -584
- bbstrader/metatrader/risk.py +749 -748
- bbstrader/metatrader/scripts.py +81 -81
- bbstrader/metatrader/trade.py +1836 -1826
- bbstrader/metatrader/utils.py +645 -645
- bbstrader/models/__init__.py +10 -10
- bbstrader/models/factors.py +312 -312
- bbstrader/models/ml.py +1272 -1265
- 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 -842
- 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.92.dist-info → bbstrader-0.2.94.dist-info}/LICENSE +21 -21
- {bbstrader-0.2.92.dist-info → bbstrader-0.2.94.dist-info}/METADATA +188 -187
- bbstrader-0.2.94.dist-info/RECORD +44 -0
- {bbstrader-0.2.92.dist-info → bbstrader-0.2.94.dist-info}/WHEEL +1 -1
- bbstrader-0.2.92.dist-info/RECORD +0 -44
- {bbstrader-0.2.92.dist-info → bbstrader-0.2.94.dist-info}/entry_points.txt +0 -0
- {bbstrader-0.2.92.dist-info → bbstrader-0.2.94.dist-info}/top_level.txt +0 -0
bbstrader/models/optimization.py
CHANGED
|
@@ -1,182 +1,182 @@
|
|
|
1
|
-
import warnings
|
|
2
|
-
|
|
3
|
-
from pypfopt import expected_returns, risk_models
|
|
4
|
-
from pypfopt.efficient_frontier import EfficientFrontier
|
|
5
|
-
from pypfopt.hierarchical_portfolio import HRPOpt
|
|
6
|
-
|
|
7
|
-
__all__ = [
|
|
8
|
-
"markowitz_weights",
|
|
9
|
-
"hierarchical_risk_parity",
|
|
10
|
-
"equal_weighted",
|
|
11
|
-
"optimized_weights",
|
|
12
|
-
]
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def markowitz_weights(prices=None, rfr=0.0, freq=252):
|
|
16
|
-
"""
|
|
17
|
-
Calculates optimal portfolio weights using Markowitz's mean-variance optimization (Max Sharpe Ratio) with multiple solvers.
|
|
18
|
-
|
|
19
|
-
Parameters
|
|
20
|
-
----------
|
|
21
|
-
prices : pd.DataFrame, optional
|
|
22
|
-
Price data for assets, where rows represent time periods and columns represent assets.
|
|
23
|
-
freq : int, optional
|
|
24
|
-
Frequency of the data, such as 252 for daily returns in a year (default is 252).
|
|
25
|
-
|
|
26
|
-
Returns
|
|
27
|
-
-------
|
|
28
|
-
dict
|
|
29
|
-
Dictionary containing the optimal asset weights for maximizing the Sharpe ratio, normalized to sum to 1.
|
|
30
|
-
|
|
31
|
-
Notes
|
|
32
|
-
-----
|
|
33
|
-
This function attempts to maximize the Sharpe ratio by iterating through various solvers ('SCS', 'ECOS', 'OSQP')
|
|
34
|
-
from the PyPortfolioOpt library. If a solver fails, it proceeds to the next one. If none succeed, an error message
|
|
35
|
-
is printed for each solver that fails.
|
|
36
|
-
|
|
37
|
-
This function is useful for portfolio with a small number of assets, as it may not scale well for large portfolios.
|
|
38
|
-
|
|
39
|
-
Raises
|
|
40
|
-
------
|
|
41
|
-
Exception
|
|
42
|
-
If all solvers fail, each will print an exception error message during runtime.
|
|
43
|
-
"""
|
|
44
|
-
returns = expected_returns.mean_historical_return(prices, frequency=freq)
|
|
45
|
-
cov = risk_models.sample_cov(prices, frequency=freq)
|
|
46
|
-
|
|
47
|
-
# Try different solvers to maximize Sharpe ratio
|
|
48
|
-
for solver in ["SCS", "ECOS", "OSQP"]:
|
|
49
|
-
ef = EfficientFrontier(
|
|
50
|
-
expected_returns=returns,
|
|
51
|
-
cov_matrix=cov,
|
|
52
|
-
weight_bounds=(0, 1),
|
|
53
|
-
solver=solver,
|
|
54
|
-
)
|
|
55
|
-
try:
|
|
56
|
-
ef.max_sharpe(risk_free_rate=rfr)
|
|
57
|
-
return ef.clean_weights()
|
|
58
|
-
except Exception as e:
|
|
59
|
-
print(f"Solver {solver} failed with error: {e}")
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def hierarchical_risk_parity(prices=None, returns=None, freq=252):
|
|
63
|
-
"""
|
|
64
|
-
Computes asset weights using Hierarchical Risk Parity (HRP) for risk-averse portfolio allocation.
|
|
65
|
-
|
|
66
|
-
Parameters
|
|
67
|
-
----------
|
|
68
|
-
prices : pd.DataFrame, optional
|
|
69
|
-
Price data for assets; if provided, daily returns will be calculated.
|
|
70
|
-
returns : pd.DataFrame, optional
|
|
71
|
-
Daily returns for assets. One of `prices` or `returns` must be provided.
|
|
72
|
-
freq : int, optional
|
|
73
|
-
Number of days to consider in calculating portfolio weights (default is 252).
|
|
74
|
-
|
|
75
|
-
Returns
|
|
76
|
-
-------
|
|
77
|
-
dict
|
|
78
|
-
Optimized asset weights using the HRP method, with asset weights summing to 1.
|
|
79
|
-
|
|
80
|
-
Raises
|
|
81
|
-
------
|
|
82
|
-
ValueError
|
|
83
|
-
If neither `prices` nor `returns` are provided.
|
|
84
|
-
|
|
85
|
-
Notes
|
|
86
|
-
-----
|
|
87
|
-
Hierarchical Risk Parity is particularly useful for portfolios with a large number of assets,
|
|
88
|
-
as it mitigates issues of multicollinearity and estimation errors in covariance matrices by
|
|
89
|
-
using hierarchical clustering.
|
|
90
|
-
"""
|
|
91
|
-
warnings.filterwarnings("ignore")
|
|
92
|
-
if returns is None and prices is None:
|
|
93
|
-
raise ValueError("Either prices or returns must be provided")
|
|
94
|
-
if returns is None:
|
|
95
|
-
returns = prices.pct_change().dropna(how="all")
|
|
96
|
-
# Remove duplicate columns and index
|
|
97
|
-
returns = returns.loc[:, ~returns.columns.duplicated()]
|
|
98
|
-
returns = returns.loc[~returns.index.duplicated(keep="first")]
|
|
99
|
-
hrp = HRPOpt(returns=returns.iloc[-freq:])
|
|
100
|
-
return hrp.optimize()
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def equal_weighted(prices=None, returns=None, round_digits=5):
|
|
104
|
-
"""
|
|
105
|
-
Generates an equal-weighted portfolio by assigning an equal proportion to each asset.
|
|
106
|
-
|
|
107
|
-
Parameters
|
|
108
|
-
----------
|
|
109
|
-
prices : pd.DataFrame, optional
|
|
110
|
-
Price data for assets, where each column represents an asset.
|
|
111
|
-
returns : pd.DataFrame, optional
|
|
112
|
-
Return data for assets. One of `prices` or `returns` must be provided.
|
|
113
|
-
round_digits : int, optional
|
|
114
|
-
Number of decimal places to round each weight to (default is 5).
|
|
115
|
-
|
|
116
|
-
Returns
|
|
117
|
-
-------
|
|
118
|
-
dict
|
|
119
|
-
Dictionary with equal weights assigned to each asset, summing to 1.
|
|
120
|
-
|
|
121
|
-
Raises
|
|
122
|
-
------
|
|
123
|
-
ValueError
|
|
124
|
-
If neither `prices` nor `returns` are provided.
|
|
125
|
-
|
|
126
|
-
Notes
|
|
127
|
-
-----
|
|
128
|
-
Equal weighting is a simple allocation method that assumes equal importance across all assets,
|
|
129
|
-
useful as a baseline model and when no strong views exist on asset return expectations or risk.
|
|
130
|
-
"""
|
|
131
|
-
|
|
132
|
-
if returns is None and prices is None:
|
|
133
|
-
raise ValueError("Either prices or returns must be provided")
|
|
134
|
-
if returns is None:
|
|
135
|
-
n = len(prices.columns)
|
|
136
|
-
columns = prices.columns
|
|
137
|
-
else:
|
|
138
|
-
n = len(returns.columns)
|
|
139
|
-
columns = returns.columns
|
|
140
|
-
return {col: round(1 / n, round_digits) for col in columns}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def optimized_weights(prices=None, returns=None, rfr=0.0, freq=252, method="equal"):
|
|
144
|
-
"""
|
|
145
|
-
Selects an optimization method to calculate portfolio weights based on user preference.
|
|
146
|
-
|
|
147
|
-
Parameters
|
|
148
|
-
----------
|
|
149
|
-
prices : pd.DataFrame, optional
|
|
150
|
-
Price data for assets, required for certain methods.
|
|
151
|
-
returns : pd.DataFrame, optional
|
|
152
|
-
Returns data for assets, an alternative input for certain methods.
|
|
153
|
-
freq : int, optional
|
|
154
|
-
Number of days for calculating portfolio weights, such as 252 for a year's worth of daily returns (default is 252).
|
|
155
|
-
method : str, optional
|
|
156
|
-
Optimization method to use ('markowitz', 'hrp', or 'equal') (default is 'markowitz').
|
|
157
|
-
|
|
158
|
-
Returns
|
|
159
|
-
-------
|
|
160
|
-
dict
|
|
161
|
-
Dictionary containing optimized asset weights based on the chosen method.
|
|
162
|
-
|
|
163
|
-
Raises
|
|
164
|
-
------
|
|
165
|
-
ValueError
|
|
166
|
-
If an unknown optimization method is specified.
|
|
167
|
-
|
|
168
|
-
Notes
|
|
169
|
-
-----
|
|
170
|
-
This function integrates different optimization methods:
|
|
171
|
-
- 'markowitz': mean-variance optimization with max Sharpe ratio
|
|
172
|
-
- 'hrp': Hierarchical Risk Parity, for risk-based clustering of assets
|
|
173
|
-
- 'equal': Equal weighting across all assets
|
|
174
|
-
"""
|
|
175
|
-
if method == "markowitz":
|
|
176
|
-
return markowitz_weights(prices=prices, rfr=rfr, freq=freq)
|
|
177
|
-
elif method == "hrp":
|
|
178
|
-
return hierarchical_risk_parity(prices=prices, returns=returns, freq=freq)
|
|
179
|
-
elif method == "equal":
|
|
180
|
-
return equal_weighted(prices=prices, returns=returns)
|
|
181
|
-
else:
|
|
182
|
-
raise ValueError(f"Unknown method: {method}")
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
3
|
+
from pypfopt import expected_returns, risk_models
|
|
4
|
+
from pypfopt.efficient_frontier import EfficientFrontier
|
|
5
|
+
from pypfopt.hierarchical_portfolio import HRPOpt
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"markowitz_weights",
|
|
9
|
+
"hierarchical_risk_parity",
|
|
10
|
+
"equal_weighted",
|
|
11
|
+
"optimized_weights",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def markowitz_weights(prices=None, rfr=0.0, freq=252):
|
|
16
|
+
"""
|
|
17
|
+
Calculates optimal portfolio weights using Markowitz's mean-variance optimization (Max Sharpe Ratio) with multiple solvers.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
prices : pd.DataFrame, optional
|
|
22
|
+
Price data for assets, where rows represent time periods and columns represent assets.
|
|
23
|
+
freq : int, optional
|
|
24
|
+
Frequency of the data, such as 252 for daily returns in a year (default is 252).
|
|
25
|
+
|
|
26
|
+
Returns
|
|
27
|
+
-------
|
|
28
|
+
dict
|
|
29
|
+
Dictionary containing the optimal asset weights for maximizing the Sharpe ratio, normalized to sum to 1.
|
|
30
|
+
|
|
31
|
+
Notes
|
|
32
|
+
-----
|
|
33
|
+
This function attempts to maximize the Sharpe ratio by iterating through various solvers ('SCS', 'ECOS', 'OSQP')
|
|
34
|
+
from the PyPortfolioOpt library. If a solver fails, it proceeds to the next one. If none succeed, an error message
|
|
35
|
+
is printed for each solver that fails.
|
|
36
|
+
|
|
37
|
+
This function is useful for portfolio with a small number of assets, as it may not scale well for large portfolios.
|
|
38
|
+
|
|
39
|
+
Raises
|
|
40
|
+
------
|
|
41
|
+
Exception
|
|
42
|
+
If all solvers fail, each will print an exception error message during runtime.
|
|
43
|
+
"""
|
|
44
|
+
returns = expected_returns.mean_historical_return(prices, frequency=freq)
|
|
45
|
+
cov = risk_models.sample_cov(prices, frequency=freq)
|
|
46
|
+
|
|
47
|
+
# Try different solvers to maximize Sharpe ratio
|
|
48
|
+
for solver in ["SCS", "ECOS", "OSQP"]:
|
|
49
|
+
ef = EfficientFrontier(
|
|
50
|
+
expected_returns=returns,
|
|
51
|
+
cov_matrix=cov,
|
|
52
|
+
weight_bounds=(0, 1),
|
|
53
|
+
solver=solver,
|
|
54
|
+
)
|
|
55
|
+
try:
|
|
56
|
+
ef.max_sharpe(risk_free_rate=rfr)
|
|
57
|
+
return ef.clean_weights()
|
|
58
|
+
except Exception as e:
|
|
59
|
+
print(f"Solver {solver} failed with error: {e}")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def hierarchical_risk_parity(prices=None, returns=None, freq=252):
|
|
63
|
+
"""
|
|
64
|
+
Computes asset weights using Hierarchical Risk Parity (HRP) for risk-averse portfolio allocation.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
prices : pd.DataFrame, optional
|
|
69
|
+
Price data for assets; if provided, daily returns will be calculated.
|
|
70
|
+
returns : pd.DataFrame, optional
|
|
71
|
+
Daily returns for assets. One of `prices` or `returns` must be provided.
|
|
72
|
+
freq : int, optional
|
|
73
|
+
Number of days to consider in calculating portfolio weights (default is 252).
|
|
74
|
+
|
|
75
|
+
Returns
|
|
76
|
+
-------
|
|
77
|
+
dict
|
|
78
|
+
Optimized asset weights using the HRP method, with asset weights summing to 1.
|
|
79
|
+
|
|
80
|
+
Raises
|
|
81
|
+
------
|
|
82
|
+
ValueError
|
|
83
|
+
If neither `prices` nor `returns` are provided.
|
|
84
|
+
|
|
85
|
+
Notes
|
|
86
|
+
-----
|
|
87
|
+
Hierarchical Risk Parity is particularly useful for portfolios with a large number of assets,
|
|
88
|
+
as it mitigates issues of multicollinearity and estimation errors in covariance matrices by
|
|
89
|
+
using hierarchical clustering.
|
|
90
|
+
"""
|
|
91
|
+
warnings.filterwarnings("ignore")
|
|
92
|
+
if returns is None and prices is None:
|
|
93
|
+
raise ValueError("Either prices or returns must be provided")
|
|
94
|
+
if returns is None:
|
|
95
|
+
returns = prices.pct_change().dropna(how="all")
|
|
96
|
+
# Remove duplicate columns and index
|
|
97
|
+
returns = returns.loc[:, ~returns.columns.duplicated()]
|
|
98
|
+
returns = returns.loc[~returns.index.duplicated(keep="first")]
|
|
99
|
+
hrp = HRPOpt(returns=returns.iloc[-freq:])
|
|
100
|
+
return hrp.optimize()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def equal_weighted(prices=None, returns=None, round_digits=5):
|
|
104
|
+
"""
|
|
105
|
+
Generates an equal-weighted portfolio by assigning an equal proportion to each asset.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
prices : pd.DataFrame, optional
|
|
110
|
+
Price data for assets, where each column represents an asset.
|
|
111
|
+
returns : pd.DataFrame, optional
|
|
112
|
+
Return data for assets. One of `prices` or `returns` must be provided.
|
|
113
|
+
round_digits : int, optional
|
|
114
|
+
Number of decimal places to round each weight to (default is 5).
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
dict
|
|
119
|
+
Dictionary with equal weights assigned to each asset, summing to 1.
|
|
120
|
+
|
|
121
|
+
Raises
|
|
122
|
+
------
|
|
123
|
+
ValueError
|
|
124
|
+
If neither `prices` nor `returns` are provided.
|
|
125
|
+
|
|
126
|
+
Notes
|
|
127
|
+
-----
|
|
128
|
+
Equal weighting is a simple allocation method that assumes equal importance across all assets,
|
|
129
|
+
useful as a baseline model and when no strong views exist on asset return expectations or risk.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
if returns is None and prices is None:
|
|
133
|
+
raise ValueError("Either prices or returns must be provided")
|
|
134
|
+
if returns is None:
|
|
135
|
+
n = len(prices.columns)
|
|
136
|
+
columns = prices.columns
|
|
137
|
+
else:
|
|
138
|
+
n = len(returns.columns)
|
|
139
|
+
columns = returns.columns
|
|
140
|
+
return {col: round(1 / n, round_digits) for col in columns}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def optimized_weights(prices=None, returns=None, rfr=0.0, freq=252, method="equal"):
|
|
144
|
+
"""
|
|
145
|
+
Selects an optimization method to calculate portfolio weights based on user preference.
|
|
146
|
+
|
|
147
|
+
Parameters
|
|
148
|
+
----------
|
|
149
|
+
prices : pd.DataFrame, optional
|
|
150
|
+
Price data for assets, required for certain methods.
|
|
151
|
+
returns : pd.DataFrame, optional
|
|
152
|
+
Returns data for assets, an alternative input for certain methods.
|
|
153
|
+
freq : int, optional
|
|
154
|
+
Number of days for calculating portfolio weights, such as 252 for a year's worth of daily returns (default is 252).
|
|
155
|
+
method : str, optional
|
|
156
|
+
Optimization method to use ('markowitz', 'hrp', or 'equal') (default is 'markowitz').
|
|
157
|
+
|
|
158
|
+
Returns
|
|
159
|
+
-------
|
|
160
|
+
dict
|
|
161
|
+
Dictionary containing optimized asset weights based on the chosen method.
|
|
162
|
+
|
|
163
|
+
Raises
|
|
164
|
+
------
|
|
165
|
+
ValueError
|
|
166
|
+
If an unknown optimization method is specified.
|
|
167
|
+
|
|
168
|
+
Notes
|
|
169
|
+
-----
|
|
170
|
+
This function integrates different optimization methods:
|
|
171
|
+
- 'markowitz': mean-variance optimization with max Sharpe ratio
|
|
172
|
+
- 'hrp': Hierarchical Risk Parity, for risk-based clustering of assets
|
|
173
|
+
- 'equal': Equal weighting across all assets
|
|
174
|
+
"""
|
|
175
|
+
if method == "markowitz":
|
|
176
|
+
return markowitz_weights(prices=prices, rfr=rfr, freq=freq)
|
|
177
|
+
elif method == "hrp":
|
|
178
|
+
return hierarchical_risk_parity(prices=prices, returns=returns, freq=freq)
|
|
179
|
+
elif method == "equal":
|
|
180
|
+
return equal_weighted(prices=prices, returns=returns)
|
|
181
|
+
else:
|
|
182
|
+
raise ValueError(f"Unknown method: {method}")
|