bbstrader 0.1.94__py3-none-any.whl → 0.2.1__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 +9 -9
- bbstrader/btengine/__init__.py +7 -7
- bbstrader/btengine/backtest.py +30 -26
- bbstrader/btengine/data.py +100 -79
- bbstrader/btengine/event.py +2 -1
- bbstrader/btengine/execution.py +18 -16
- bbstrader/btengine/performance.py +11 -7
- bbstrader/btengine/portfolio.py +35 -36
- bbstrader/btengine/strategy.py +119 -94
- bbstrader/config.py +14 -8
- bbstrader/core/__init__.py +0 -0
- bbstrader/core/data.py +22 -0
- bbstrader/core/utils.py +57 -0
- bbstrader/ibkr/__init__.py +0 -0
- bbstrader/ibkr/utils.py +0 -0
- bbstrader/metatrader/__init__.py +5 -5
- bbstrader/metatrader/account.py +117 -121
- bbstrader/metatrader/rates.py +83 -80
- bbstrader/metatrader/risk.py +23 -37
- bbstrader/metatrader/trade.py +169 -140
- bbstrader/metatrader/utils.py +3 -3
- bbstrader/models/__init__.py +5 -5
- bbstrader/models/factors.py +280 -0
- bbstrader/models/ml.py +1092 -0
- bbstrader/models/optimization.py +31 -28
- bbstrader/models/{portfolios.py → portfolio.py} +64 -46
- bbstrader/models/risk.py +15 -9
- bbstrader/trading/__init__.py +2 -2
- bbstrader/trading/execution.py +252 -164
- bbstrader/trading/scripts.py +8 -4
- bbstrader/trading/strategies.py +79 -66
- bbstrader/tseries.py +482 -107
- {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/LICENSE +1 -1
- {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/METADATA +6 -1
- bbstrader-0.2.1.dist-info/RECORD +37 -0
- bbstrader-0.1.94.dist-info/RECORD +0 -32
- {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/WHEEL +0 -0
- {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/top_level.txt +0 -0
bbstrader/models/optimization.py
CHANGED
|
@@ -1,43 +1,42 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
from pypfopt import risk_models
|
|
4
|
-
from pypfopt import expected_returns
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
3
|
+
from pypfopt import expected_returns, risk_models
|
|
5
4
|
from pypfopt.efficient_frontier import EfficientFrontier
|
|
6
5
|
from pypfopt.hierarchical_portfolio import HRPOpt
|
|
7
|
-
import warnings
|
|
8
6
|
|
|
9
7
|
__all__ = [
|
|
10
|
-
'markowitz_weights',
|
|
11
|
-
'hierarchical_risk_parity',
|
|
12
|
-
'equal_weighted',
|
|
8
|
+
'markowitz_weights',
|
|
9
|
+
'hierarchical_risk_parity',
|
|
10
|
+
'equal_weighted',
|
|
13
11
|
'optimized_weights'
|
|
14
12
|
]
|
|
15
13
|
|
|
14
|
+
|
|
16
15
|
def markowitz_weights(prices=None, rfr=0.0, freq=252):
|
|
17
16
|
"""
|
|
18
17
|
Calculates optimal portfolio weights using Markowitz's mean-variance optimization (Max Sharpe Ratio) with multiple solvers.
|
|
19
18
|
|
|
20
|
-
Parameters
|
|
19
|
+
Parameters
|
|
21
20
|
----------
|
|
22
21
|
prices : pd.DataFrame, optional
|
|
23
22
|
Price data for assets, where rows represent time periods and columns represent assets.
|
|
24
23
|
freq : int, optional
|
|
25
24
|
Frequency of the data, such as 252 for daily returns in a year (default is 252).
|
|
26
25
|
|
|
27
|
-
Returns
|
|
26
|
+
Returns
|
|
28
27
|
-------
|
|
29
28
|
dict
|
|
30
29
|
Dictionary containing the optimal asset weights for maximizing the Sharpe ratio, normalized to sum to 1.
|
|
31
30
|
|
|
32
|
-
Notes
|
|
31
|
+
Notes
|
|
33
32
|
-----
|
|
34
33
|
This function attempts to maximize the Sharpe ratio by iterating through various solvers ('SCS', 'ECOS', 'OSQP')
|
|
35
34
|
from the PyPortfolioOpt library. If a solver fails, it proceeds to the next one. If none succeed, an error message
|
|
36
35
|
is printed for each solver that fails.
|
|
37
36
|
|
|
38
37
|
This function is useful for portfolio with a small number of assets, as it may not scale well for large portfolios.
|
|
39
|
-
|
|
40
|
-
Raises
|
|
38
|
+
|
|
39
|
+
Raises
|
|
41
40
|
------
|
|
42
41
|
Exception
|
|
43
42
|
If all solvers fail, each will print an exception error message during runtime.
|
|
@@ -47,21 +46,22 @@ def markowitz_weights(prices=None, rfr=0.0, freq=252):
|
|
|
47
46
|
|
|
48
47
|
# Try different solvers to maximize Sharpe ratio
|
|
49
48
|
for solver in ['SCS', 'ECOS', 'OSQP']:
|
|
50
|
-
ef = EfficientFrontier(expected_returns=returns,
|
|
51
|
-
cov_matrix=cov,
|
|
49
|
+
ef = EfficientFrontier(expected_returns=returns,
|
|
50
|
+
cov_matrix=cov,
|
|
52
51
|
weight_bounds=(0, 1),
|
|
53
52
|
solver=solver)
|
|
54
53
|
try:
|
|
55
|
-
|
|
54
|
+
ef.max_sharpe(risk_free_rate=rfr)
|
|
56
55
|
return ef.clean_weights()
|
|
57
56
|
except Exception as e:
|
|
58
57
|
print(f"Solver {solver} failed with error: {e}")
|
|
59
58
|
|
|
59
|
+
|
|
60
60
|
def hierarchical_risk_parity(prices=None, returns=None, freq=252):
|
|
61
61
|
"""
|
|
62
62
|
Computes asset weights using Hierarchical Risk Parity (HRP) for risk-averse portfolio allocation.
|
|
63
63
|
|
|
64
|
-
Parameters
|
|
64
|
+
Parameters
|
|
65
65
|
----------
|
|
66
66
|
prices : pd.DataFrame, optional
|
|
67
67
|
Price data for assets; if provided, daily returns will be calculated.
|
|
@@ -70,17 +70,17 @@ def hierarchical_risk_parity(prices=None, returns=None, freq=252):
|
|
|
70
70
|
freq : int, optional
|
|
71
71
|
Number of days to consider in calculating portfolio weights (default is 252).
|
|
72
72
|
|
|
73
|
-
Returns
|
|
73
|
+
Returns
|
|
74
74
|
-------
|
|
75
75
|
dict
|
|
76
76
|
Optimized asset weights using the HRP method, with asset weights summing to 1.
|
|
77
77
|
|
|
78
|
-
Raises
|
|
78
|
+
Raises
|
|
79
79
|
------
|
|
80
80
|
ValueError
|
|
81
81
|
If neither `prices` nor `returns` are provided.
|
|
82
82
|
|
|
83
|
-
Notes
|
|
83
|
+
Notes
|
|
84
84
|
-----
|
|
85
85
|
Hierarchical Risk Parity is particularly useful for portfolios with a large number of assets,
|
|
86
86
|
as it mitigates issues of multicollinearity and estimation errors in covariance matrices by
|
|
@@ -97,11 +97,12 @@ def hierarchical_risk_parity(prices=None, returns=None, freq=252):
|
|
|
97
97
|
hrp = HRPOpt(returns=returns.iloc[-freq:])
|
|
98
98
|
return hrp.optimize()
|
|
99
99
|
|
|
100
|
+
|
|
100
101
|
def equal_weighted(prices=None, returns=None, round_digits=5):
|
|
101
102
|
"""
|
|
102
103
|
Generates an equal-weighted portfolio by assigning an equal proportion to each asset.
|
|
103
104
|
|
|
104
|
-
Parameters
|
|
105
|
+
Parameters
|
|
105
106
|
----------
|
|
106
107
|
prices : pd.DataFrame, optional
|
|
107
108
|
Price data for assets, where each column represents an asset.
|
|
@@ -110,21 +111,22 @@ def equal_weighted(prices=None, returns=None, round_digits=5):
|
|
|
110
111
|
round_digits : int, optional
|
|
111
112
|
Number of decimal places to round each weight to (default is 5).
|
|
112
113
|
|
|
113
|
-
Returns
|
|
114
|
+
Returns
|
|
114
115
|
-------
|
|
115
116
|
dict
|
|
116
117
|
Dictionary with equal weights assigned to each asset, summing to 1.
|
|
117
118
|
|
|
118
|
-
Raises
|
|
119
|
+
Raises
|
|
119
120
|
------
|
|
120
121
|
ValueError
|
|
121
122
|
If neither `prices` nor `returns` are provided.
|
|
122
123
|
|
|
123
|
-
Notes
|
|
124
|
+
Notes
|
|
124
125
|
-----
|
|
125
126
|
Equal weighting is a simple allocation method that assumes equal importance across all assets,
|
|
126
127
|
useful as a baseline model and when no strong views exist on asset return expectations or risk.
|
|
127
128
|
"""
|
|
129
|
+
|
|
128
130
|
if returns is None and prices is None:
|
|
129
131
|
raise ValueError("Either prices or returns must be provided")
|
|
130
132
|
if returns is None:
|
|
@@ -135,11 +137,12 @@ def equal_weighted(prices=None, returns=None, round_digits=5):
|
|
|
135
137
|
columns = returns.columns
|
|
136
138
|
return {col: round(1/n, round_digits) for col in columns}
|
|
137
139
|
|
|
140
|
+
|
|
138
141
|
def optimized_weights(prices=None, returns=None, rfr=0.0, freq=252, method='equal'):
|
|
139
142
|
"""
|
|
140
143
|
Selects an optimization method to calculate portfolio weights based on user preference.
|
|
141
144
|
|
|
142
|
-
Parameters
|
|
145
|
+
Parameters
|
|
143
146
|
----------
|
|
144
147
|
prices : pd.DataFrame, optional
|
|
145
148
|
Price data for assets, required for certain methods.
|
|
@@ -150,17 +153,17 @@ def optimized_weights(prices=None, returns=None, rfr=0.0, freq=252, method='equa
|
|
|
150
153
|
method : str, optional
|
|
151
154
|
Optimization method to use ('markowitz', 'hrp', or 'equal') (default is 'markowitz').
|
|
152
155
|
|
|
153
|
-
Returns
|
|
156
|
+
Returns
|
|
154
157
|
-------
|
|
155
158
|
dict
|
|
156
159
|
Dictionary containing optimized asset weights based on the chosen method.
|
|
157
160
|
|
|
158
|
-
Raises
|
|
161
|
+
Raises
|
|
159
162
|
------
|
|
160
163
|
ValueError
|
|
161
164
|
If an unknown optimization method is specified.
|
|
162
165
|
|
|
163
|
-
Notes
|
|
166
|
+
Notes
|
|
164
167
|
-----
|
|
165
168
|
This function integrates different optimization methods:
|
|
166
169
|
- 'markowitz': mean-variance optimization with max Sharpe ratio
|
|
@@ -1,27 +1,38 @@
|
|
|
1
|
-
import
|
|
1
|
+
import matplotlib.pyplot as plt
|
|
2
2
|
import pandas as pd
|
|
3
3
|
import seaborn as sns
|
|
4
|
-
import matplotlib.pyplot as plt
|
|
5
4
|
from sklearn.decomposition import PCA
|
|
6
5
|
from sklearn.preprocessing import scale
|
|
6
|
+
|
|
7
7
|
from bbstrader.models.optimization import (
|
|
8
|
-
|
|
9
|
-
hierarchical_risk_parity,
|
|
10
|
-
|
|
8
|
+
equal_weighted,
|
|
9
|
+
hierarchical_risk_parity,
|
|
10
|
+
markowitz_weights,
|
|
11
11
|
)
|
|
12
12
|
|
|
13
13
|
__all__ = [
|
|
14
14
|
'EigenPortfolios'
|
|
15
15
|
]
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
class EigenPortfolios(object):
|
|
18
19
|
"""
|
|
19
20
|
The `EigenPortfolios` class applies Principal Component Analysis (PCA) to a covariance matrix of normalized asset returns
|
|
20
21
|
to derive portfolios (eigenportfolios) that capture distinct risk factors in the asset returns. Each eigenportfolio
|
|
21
22
|
represents a principal component of the return covariance matrix, ordered by the magnitude of its eigenvalue. These
|
|
22
23
|
portfolios capture most of the variance in asset returns and are mutually uncorrelated.
|
|
23
|
-
|
|
24
|
+
|
|
25
|
+
Notes
|
|
26
|
+
-----
|
|
27
|
+
The implementation is inspired by the book "Machine Learning for Algorithmic Trading" by Stefan Jansen.
|
|
28
|
+
|
|
29
|
+
References
|
|
30
|
+
----------
|
|
31
|
+
Stefan Jansen (2020). Machine Learning for Algorithmic Trading - Second Edition.
|
|
32
|
+
chapter 13, Data-Driven Risk Factors and Asset Allocation with Unsupervised Learning.
|
|
33
|
+
|
|
24
34
|
"""
|
|
35
|
+
|
|
25
36
|
def __init__(self):
|
|
26
37
|
self.returns = None
|
|
27
38
|
self.n_portfolios = None
|
|
@@ -31,13 +42,13 @@ class EigenPortfolios(object):
|
|
|
31
42
|
def get_portfolios(self) -> pd.DataFrame:
|
|
32
43
|
"""
|
|
33
44
|
Returns the computed eigenportfolios (weights of assets in each portfolio).
|
|
34
|
-
|
|
35
|
-
Returns
|
|
45
|
+
|
|
46
|
+
Returns
|
|
36
47
|
-------
|
|
37
48
|
pd.DataFrame
|
|
38
49
|
DataFrame containing eigenportfolio weights for each asset.
|
|
39
|
-
|
|
40
|
-
Raises
|
|
50
|
+
|
|
51
|
+
Raises
|
|
41
52
|
------
|
|
42
53
|
ValueError
|
|
43
54
|
If `fit()` has not been called before retrieving portfolios.
|
|
@@ -46,23 +57,23 @@ class EigenPortfolios(object):
|
|
|
46
57
|
raise ValueError("fit() must be called first")
|
|
47
58
|
return self._portfolios
|
|
48
59
|
|
|
49
|
-
def fit(self, returns: pd.DataFrame, n_portfolios: int=4) -> pd.DataFrame:
|
|
60
|
+
def fit(self, returns: pd.DataFrame, n_portfolios: int = 4) -> pd.DataFrame:
|
|
50
61
|
"""
|
|
51
62
|
Computes the eigenportfolios based on PCA of the asset returns' covariance matrix.
|
|
52
|
-
|
|
53
|
-
Parameters
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
54
65
|
----------
|
|
55
66
|
returns : pd.DataFrame
|
|
56
67
|
Historical returns of assets to be used for PCA.
|
|
57
68
|
n_portfolios : int, optional
|
|
58
69
|
Number of eigenportfolios to compute (default is 4).
|
|
59
|
-
|
|
60
|
-
Returns
|
|
70
|
+
|
|
71
|
+
Returns
|
|
61
72
|
-------
|
|
62
73
|
pd.DataFrame
|
|
63
74
|
DataFrame containing normalized weights for each eigenportfolio.
|
|
64
75
|
|
|
65
|
-
Notes
|
|
76
|
+
Notes
|
|
66
77
|
-----
|
|
67
78
|
This method performs winsorization and normalization on returns to reduce the impact of outliers
|
|
68
79
|
and achieve zero mean and unit variance. It uses the first `n_portfolios` principal components
|
|
@@ -70,11 +81,12 @@ class EigenPortfolios(object):
|
|
|
70
81
|
"""
|
|
71
82
|
# Winsorize and normalize the returns
|
|
72
83
|
normed_returns = scale(returns
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
returns = returns.dropna(thresh=int(
|
|
84
|
+
.clip(lower=returns.quantile(q=.025),
|
|
85
|
+
upper=returns.quantile(q=.975),
|
|
86
|
+
axis=1)
|
|
87
|
+
.apply(lambda x: x.sub(x.mean()).div(x.std())))
|
|
88
|
+
returns = returns.dropna(thresh=int(
|
|
89
|
+
normed_returns.shape[0] * .95), axis=1)
|
|
78
90
|
returns = returns.dropna(thresh=int(normed_returns.shape[1] * .95))
|
|
79
91
|
|
|
80
92
|
cov = returns.cov()
|
|
@@ -82,9 +94,12 @@ class EigenPortfolios(object):
|
|
|
82
94
|
pca = PCA()
|
|
83
95
|
pca.fit(cov)
|
|
84
96
|
|
|
85
|
-
top_portfolios = pd.DataFrame(
|
|
86
|
-
|
|
87
|
-
eigen_portfolios
|
|
97
|
+
top_portfolios = pd.DataFrame(
|
|
98
|
+
pca.components_[:n_portfolios], columns=cov.columns)
|
|
99
|
+
eigen_portfolios = top_portfolios.div(
|
|
100
|
+
top_portfolios.sum(axis=1), axis=0)
|
|
101
|
+
eigen_portfolios.index = [
|
|
102
|
+
f"Portfolio {i}" for i in range(1, n_portfolios + 1)]
|
|
88
103
|
self._portfolios = eigen_portfolios
|
|
89
104
|
self.returns = returns
|
|
90
105
|
self.n_portfolios = n_portfolios
|
|
@@ -93,8 +108,8 @@ class EigenPortfolios(object):
|
|
|
93
108
|
def plot_weights(self):
|
|
94
109
|
"""
|
|
95
110
|
Plots the weights of each asset in each eigenportfolio as bar charts.
|
|
96
|
-
|
|
97
|
-
Notes
|
|
111
|
+
|
|
112
|
+
Notes
|
|
98
113
|
-----
|
|
99
114
|
Each subplot represents one eigenportfolio, showing the contribution of each asset.
|
|
100
115
|
"""
|
|
@@ -109,7 +124,7 @@ class EigenPortfolios(object):
|
|
|
109
124
|
for ax in axes.flatten():
|
|
110
125
|
ax.set_ylabel('Portfolio Weight')
|
|
111
126
|
ax.set_xlabel('')
|
|
112
|
-
|
|
127
|
+
|
|
113
128
|
sns.despine()
|
|
114
129
|
plt.tight_layout()
|
|
115
130
|
plt.show()
|
|
@@ -117,25 +132,27 @@ class EigenPortfolios(object):
|
|
|
117
132
|
def plot_performance(self):
|
|
118
133
|
"""
|
|
119
134
|
Plots the cumulative returns of each eigenportfolio over time.
|
|
120
|
-
|
|
121
|
-
Notes
|
|
135
|
+
|
|
136
|
+
Notes
|
|
122
137
|
-----
|
|
123
138
|
This method calculates the historical cumulative performance of each eigenportfolio
|
|
124
139
|
by weighting asset returns according to eigenportfolio weights.
|
|
125
140
|
"""
|
|
126
141
|
eigen_portfolios = self.get_portfolios()
|
|
127
142
|
returns = self.returns.copy()
|
|
128
|
-
|
|
143
|
+
|
|
129
144
|
n_cols = 2
|
|
130
145
|
n_rows = (self.n_portfolios + 1 + n_cols - 1) // n_cols
|
|
131
146
|
figsize = (n_cols * 10, n_rows * 5)
|
|
132
147
|
fig, axes = plt.subplots(nrows=n_rows, ncols=n_cols,
|
|
133
148
|
figsize=figsize, sharex=True)
|
|
134
149
|
axes = axes.flatten()
|
|
135
|
-
returns.mean(1).add(1).cumprod().sub(
|
|
136
|
-
|
|
150
|
+
returns.mean(1).add(1).cumprod().sub(
|
|
151
|
+
1).plot(title='The Market', ax=axes[0])
|
|
152
|
+
|
|
137
153
|
for i in range(self.n_portfolios):
|
|
138
|
-
rc = returns.mul(eigen_portfolios.iloc[i]).sum(
|
|
154
|
+
rc = returns.mul(eigen_portfolios.iloc[i]).sum(
|
|
155
|
+
1).add(1).cumprod().sub(1)
|
|
139
156
|
rc.plot(title=f'Portfolio {i+1}', ax=axes[i + 1], lw=1, rot=0)
|
|
140
157
|
|
|
141
158
|
for j in range(self.n_portfolios + 1, len(axes)):
|
|
@@ -143,16 +160,16 @@ class EigenPortfolios(object):
|
|
|
143
160
|
|
|
144
161
|
for i in range(self.n_portfolios + 1):
|
|
145
162
|
axes[i].set_xlabel('')
|
|
146
|
-
|
|
163
|
+
|
|
147
164
|
sns.despine()
|
|
148
165
|
fig.tight_layout()
|
|
149
166
|
plt.show()
|
|
150
|
-
|
|
167
|
+
|
|
151
168
|
def optimize(self, portfolio: int = 1, optimizer: str = 'hrp', prices=None, freq=252, plot=True):
|
|
152
169
|
"""
|
|
153
170
|
Optimizes the chosen eigenportfolio based on a specified optimization method.
|
|
154
|
-
|
|
155
|
-
Parameters
|
|
171
|
+
|
|
172
|
+
Parameters
|
|
156
173
|
----------
|
|
157
174
|
portfolio : int, optional
|
|
158
175
|
Index of the eigenportfolio to optimize (default is 1).
|
|
@@ -164,29 +181,30 @@ class EigenPortfolios(object):
|
|
|
164
181
|
Frequency of returns (e.g., 252 for daily returns).
|
|
165
182
|
plot : bool, optional
|
|
166
183
|
Whether to plot the performance of the optimized portfolio (default is True).
|
|
167
|
-
|
|
168
|
-
Returns
|
|
184
|
+
|
|
185
|
+
Returns
|
|
169
186
|
-------
|
|
170
187
|
dict
|
|
171
188
|
Dictionary of optimized asset weights.
|
|
172
|
-
|
|
173
|
-
Raises
|
|
189
|
+
|
|
190
|
+
Raises
|
|
174
191
|
------
|
|
175
192
|
ValueError
|
|
176
193
|
If an unknown optimizer is specified, or if prices are not provided when using Markowitz optimization.
|
|
177
|
-
|
|
178
|
-
Notes
|
|
194
|
+
|
|
195
|
+
Notes
|
|
179
196
|
-----
|
|
180
197
|
The optimization method varies based on risk-return assumptions, with options for traditional Markowitz optimization,
|
|
181
198
|
Hierarchical Risk Parity, or equal weighting.
|
|
182
199
|
"""
|
|
183
200
|
portfolio = self.get_portfolios().iloc[portfolio - 1]
|
|
184
201
|
returns = self.returns.loc[:, portfolio.index]
|
|
185
|
-
returns =
|
|
202
|
+
returns = returns.loc[:, ~returns.columns.duplicated()]
|
|
186
203
|
returns = returns.loc[~returns.index.duplicated(keep='first')]
|
|
187
204
|
if optimizer == 'markowitz':
|
|
188
205
|
if prices is None:
|
|
189
|
-
raise ValueError(
|
|
206
|
+
raise ValueError(
|
|
207
|
+
"prices must be provided for markowitz optimization")
|
|
190
208
|
prices = prices.loc[:, returns.columns]
|
|
191
209
|
weights = markowitz_weights(prices=prices, freq=freq)
|
|
192
210
|
elif optimizer == 'hrp':
|
|
@@ -202,4 +220,4 @@ class EigenPortfolios(object):
|
|
|
202
220
|
rc.plot(title=f'Optimized {portfolio.name}', lw=1, rot=0)
|
|
203
221
|
sns.despine()
|
|
204
222
|
plt.show()
|
|
205
|
-
return weights
|
|
223
|
+
return weights
|
bbstrader/models/risk.py
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import pickle
|
|
2
|
+
from abc import ABCMeta, abstractmethod
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Dict, Optional
|
|
5
|
+
|
|
2
6
|
import numpy as np
|
|
3
7
|
import pandas as pd
|
|
4
8
|
import seaborn as sns
|
|
5
|
-
from datetime import datetime
|
|
6
9
|
from hmmlearn.hmm import GaussianHMM
|
|
7
|
-
from
|
|
8
|
-
from matplotlib import
|
|
9
|
-
from matplotlib.dates import
|
|
10
|
-
|
|
10
|
+
from matplotlib import cm
|
|
11
|
+
from matplotlib import pyplot as plt
|
|
12
|
+
from matplotlib.dates import MonthLocator, YearLocator
|
|
13
|
+
|
|
11
14
|
from bbstrader.metatrader.rates import Rates
|
|
15
|
+
|
|
12
16
|
sns.set_theme()
|
|
13
17
|
|
|
14
18
|
__all__ = [
|
|
@@ -311,7 +315,7 @@ class HMMRiskManager(RiskModel):
|
|
|
311
315
|
"""
|
|
312
316
|
df = data_frame.copy()
|
|
313
317
|
if 'Returns' or 'returns' not in df.columns:
|
|
314
|
-
if 'Close'
|
|
318
|
+
if 'Close' in df.columns:
|
|
315
319
|
df['Returns'] = df['Close'].pct_change()
|
|
316
320
|
elif 'Adj Close' in df.columns:
|
|
317
321
|
df['Returns'] = df['Adj Close'].pct_change()
|
|
@@ -358,12 +362,13 @@ class HMMRiskManager(RiskModel):
|
|
|
358
362
|
ax.grid(True)
|
|
359
363
|
plt.show()
|
|
360
364
|
|
|
365
|
+
|
|
361
366
|
def build_hmm_models(symbol_list=None, **kwargs
|
|
362
367
|
) -> Dict[str, HMMRiskManager]:
|
|
363
368
|
mt5_data = kwargs.get("use_mt5_data", False)
|
|
364
369
|
data = kwargs.get("hmm_data")
|
|
365
370
|
tf = kwargs.get("time_frame", 'D1')
|
|
366
|
-
hmm_end =
|
|
371
|
+
hmm_end = kwargs.get("hmm_end", 0)
|
|
367
372
|
sd = kwargs.get("session_duration", 23.0)
|
|
368
373
|
hmm_tickers = kwargs.get("hmm_tickers")
|
|
369
374
|
if hmm_tickers is not None:
|
|
@@ -385,10 +390,11 @@ def build_hmm_models(symbol_list=None, **kwargs
|
|
|
385
390
|
hmm_models[symbol] = hmm
|
|
386
391
|
if mt5_data:
|
|
387
392
|
for symbol in symbols:
|
|
388
|
-
rates = Rates(symbol, tf, start_pos=hmm_end,
|
|
393
|
+
rates = Rates(symbol, timeframe=tf, start_pos=hmm_end,
|
|
394
|
+
session_duration=sd, **kwargs)
|
|
389
395
|
data = rates.get_rates_from_pos()
|
|
390
396
|
assert data is not None, f"No data for {symbol}"
|
|
391
397
|
hmm = HMMRiskManager(
|
|
392
398
|
data=data, verbose=True, iterations=1000, **kwargs)
|
|
393
399
|
hmm_models[symbol] = hmm
|
|
394
|
-
return hmm_models
|
|
400
|
+
return hmm_models
|
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 *
|
|
11
|
-
from bbstrader.trading.strategies import *
|
|
10
|
+
from bbstrader.trading.execution import * # noqa: F403
|
|
11
|
+
from bbstrader.trading.strategies import * # noqa: F403
|