bbstrader 0.3.1__py3-none-any.whl → 0.3.3__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/__init__.py +1 -1
- bbstrader/__main__.py +7 -5
- bbstrader/btengine/backtest.py +7 -8
- bbstrader/btengine/data.py +3 -3
- bbstrader/btengine/execution.py +2 -2
- bbstrader/btengine/strategy.py +70 -17
- bbstrader/config.py +2 -2
- bbstrader/core/data.py +3 -1
- bbstrader/core/scripts.py +62 -19
- bbstrader/metatrader/account.py +108 -23
- bbstrader/metatrader/copier.py +753 -280
- bbstrader/metatrader/rates.py +2 -2
- bbstrader/metatrader/risk.py +1 -0
- bbstrader/metatrader/scripts.py +35 -9
- bbstrader/metatrader/trade.py +60 -43
- bbstrader/metatrader/utils.py +3 -5
- bbstrader/models/__init__.py +0 -1
- bbstrader/models/ml.py +55 -26
- bbstrader/models/nlp.py +159 -89
- bbstrader/models/optimization.py +1 -1
- bbstrader/models/risk.py +16 -386
- bbstrader/trading/execution.py +109 -50
- bbstrader/trading/strategies.py +9 -592
- bbstrader/tseries.py +39 -711
- {bbstrader-0.3.1.dist-info → bbstrader-0.3.3.dist-info}/METADATA +36 -41
- bbstrader-0.3.3.dist-info/RECORD +47 -0
- bbstrader-0.3.1.dist-info/RECORD +0 -47
- {bbstrader-0.3.1.dist-info → bbstrader-0.3.3.dist-info}/WHEEL +0 -0
- {bbstrader-0.3.1.dist-info → bbstrader-0.3.3.dist-info}/entry_points.txt +0 -0
- {bbstrader-0.3.1.dist-info → bbstrader-0.3.3.dist-info}/licenses/LICENSE +0 -0
- {bbstrader-0.3.1.dist-info → bbstrader-0.3.3.dist-info}/top_level.txt +0 -0
bbstrader/models/risk.py
CHANGED
|
@@ -1,398 +1,28 @@
|
|
|
1
|
-
import pickle
|
|
2
|
-
from abc import ABCMeta, abstractmethod
|
|
3
|
-
from datetime import datetime
|
|
4
|
-
from typing import Dict, Optional
|
|
5
1
|
|
|
6
|
-
import numpy as np
|
|
7
|
-
import pandas as pd
|
|
8
|
-
import seaborn as sns
|
|
9
|
-
from hmmlearn.hmm import GaussianHMM
|
|
10
|
-
from matplotlib import cm
|
|
11
|
-
from matplotlib import pyplot as plt
|
|
12
|
-
from matplotlib.dates import MonthLocator, YearLocator
|
|
13
2
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
sns.set_theme()
|
|
17
|
-
|
|
18
|
-
__all__ = ["RiskModel", "HMMRiskManager", "build_hmm_models"]
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class RiskModel(metaclass=ABCMeta):
|
|
22
|
-
"""
|
|
23
|
-
The RiskModel class serves as an abstract base for implementing
|
|
24
|
-
risk management strategies in financial markets. It is designed
|
|
25
|
-
to assist in the decision-making process regarding which trades
|
|
26
|
-
are permissible under current market conditions and how to allocate
|
|
27
|
-
assets effectively to optimize the risk-reward ratio.
|
|
28
|
-
|
|
29
|
-
Risk management is a critical component in trading and investment
|
|
30
|
-
strategies, aiming to minimize potential losses without significantly
|
|
31
|
-
reducing the potential for gains. This class encapsulates the core
|
|
32
|
-
principles of risk management by providing a structured approach to
|
|
33
|
-
evaluate market conditions and manage asset allocation.
|
|
34
|
-
|
|
35
|
-
Implementing classes are required to define two key methods:
|
|
36
|
-
|
|
37
|
-
- `which_trade_allowed`:
|
|
38
|
-
Determines the types of trades that are permissible based on the
|
|
39
|
-
analysis of current market conditions and the risk profile of the portfolio.
|
|
40
|
-
This method should analyze the provided `returns_val` parameter,
|
|
41
|
-
which could represent historical returns, volatility measures,
|
|
42
|
-
or other financial metrics, to decide on the suitability of executing
|
|
43
|
-
certain trades.
|
|
44
|
-
|
|
45
|
-
- `which_quantity_allowed`:
|
|
46
|
-
Defines how assets should be allocated across the portfolio to maintain
|
|
47
|
-
an optimal balance between risk and return. This involves determining
|
|
48
|
-
the quantity of each asset that can be held, considering factors such as
|
|
49
|
-
diversification, liquidity, and the asset's volatility. This method ensures
|
|
50
|
-
that the portfolio adheres to predefined risk tolerance levels and
|
|
51
|
-
investment objectives.
|
|
52
|
-
|
|
53
|
-
Note:
|
|
54
|
-
Implementing these methods requires a deep understanding of risk
|
|
55
|
-
management theories, market analysis, and portfolio management principles.
|
|
56
|
-
The implementation should be tailored to the specific needs of the
|
|
57
|
-
investment strategy and the risk tolerance of the investor or the fund
|
|
58
|
-
being managed.
|
|
59
|
-
"""
|
|
60
|
-
|
|
61
|
-
@abstractmethod
|
|
62
|
-
def which_trade_allowed(self, returns_val):
|
|
63
|
-
"""
|
|
64
|
-
Determines the types of trades permissible under current market conditions.
|
|
65
|
-
|
|
66
|
-
Parameters:
|
|
67
|
-
returns_val: A parameter representing financial metrics
|
|
68
|
-
such as historical returns or volatility, used to
|
|
69
|
-
assess market conditions.
|
|
70
|
-
"""
|
|
71
|
-
pass
|
|
72
|
-
|
|
73
|
-
@abstractmethod
|
|
74
|
-
def which_quantity_allowed(self):
|
|
75
|
-
"""
|
|
76
|
-
Defines the strategy for asset allocation within
|
|
77
|
-
the portfolio to optimize risk-reward ratio.
|
|
78
|
-
"""
|
|
79
|
-
pass
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
class HMMRiskManager(RiskModel):
|
|
83
|
-
"""
|
|
84
|
-
This class represents a risk management model using Hidden Markov Models (HMM)
|
|
85
|
-
to identify and manage market risks. It inherits from a generic RiskModel class
|
|
86
|
-
and utilizes Gaussian HMM to model the financial market's hidden states. These states
|
|
87
|
-
are used to make decisions on permissible trading actions based on the identified market
|
|
88
|
-
trends, thus facilitating a risk-aware trading strategy.
|
|
89
|
-
|
|
90
|
-
To learn more about about the Hidden Markov model,
|
|
91
|
-
See https://en.wikipedia.org/wiki/Hidden_Markov_model
|
|
92
|
-
|
|
93
|
-
Exemple:
|
|
94
|
-
>>> # Assuming `data` is a DataFrame containing your market data
|
|
95
|
-
>>> risk_manager = HMMRiskManager(data=data, states=3, iterations=200, verbose=True)
|
|
96
|
-
>>> current_regime = risk_manager.get_current_regime(data['Returns'].values)
|
|
97
|
-
>>> print(f"Current Market Regime: {current_regime}")
|
|
98
|
-
|
|
99
|
-
"""
|
|
3
|
+
class HMMRiskManager():
|
|
100
4
|
|
|
101
5
|
def __init__(
|
|
102
6
|
self,
|
|
103
|
-
data
|
|
104
|
-
states
|
|
105
|
-
iterations
|
|
106
|
-
end_date
|
|
107
|
-
csv_filepath
|
|
7
|
+
data,
|
|
8
|
+
states,
|
|
9
|
+
iterations,
|
|
10
|
+
end_date,
|
|
11
|
+
csv_filepath,
|
|
108
12
|
**kwargs,
|
|
109
13
|
):
|
|
110
|
-
|
|
111
|
-
Initializes the HMMRiskManager with market data and model parameters.
|
|
112
|
-
|
|
113
|
-
Args:
|
|
114
|
-
`data` : DataFrame containing market data.
|
|
115
|
-
If not provided, data must be loaded via csv_filepath.
|
|
116
|
-
|
|
117
|
-
`states` (int): The number of hidden states in the HMM.
|
|
118
|
-
`iterations` (int): The number of iterations for the HMM to converge.
|
|
119
|
-
`end_date` (datetime): The end date for the market data to be considered.
|
|
120
|
-
|
|
121
|
-
`csv_filepath` (str): Path to the CSV file containing
|
|
122
|
-
market data if data is not provided directly.
|
|
123
|
-
|
|
124
|
-
kwarg (dict): Additional arguments
|
|
125
|
-
- `model_filename` (str): Filename to save the trained HMM model.
|
|
126
|
-
- `verbose` (bool): If True, prints additional model information.
|
|
127
|
-
|
|
128
|
-
- `cov_variance` (str): Type of covariance to use in the HMM.
|
|
129
|
-
possibles values are "spherical", "tied", "diag", "full".
|
|
130
|
-
see https://hmmlearn.readthedocs.io/en/latest/api.html#gaussianhmm for more details.
|
|
131
|
-
|
|
132
|
-
"""
|
|
133
|
-
self.data = data
|
|
134
|
-
self.states = states
|
|
135
|
-
self.iterations = iterations
|
|
136
|
-
self.end_date = end_date
|
|
137
|
-
self.csv_filepath = csv_filepath
|
|
138
|
-
|
|
139
|
-
self.model_filename = kwargs.get("model_filename")
|
|
140
|
-
self.verbose = kwargs.get("verbose", True)
|
|
141
|
-
self.cov_variance = kwargs.get("cov_variance", "diag")
|
|
142
|
-
|
|
143
|
-
self.df = self._get_data()
|
|
144
|
-
self.hmm_model = self._fit_model()
|
|
145
|
-
if self.verbose:
|
|
146
|
-
self.show_hidden_states()
|
|
147
|
-
trends = self.identify_market_trends()
|
|
148
|
-
self.bullish_state = trends["bullish"]
|
|
149
|
-
self.bearish_state = trends["bearish"]
|
|
150
|
-
self.allowed_regimes = [s for s in trends.values()]
|
|
151
|
-
|
|
152
|
-
def _get_data(self):
|
|
153
|
-
"""
|
|
154
|
-
Retrieves market data for the model either
|
|
155
|
-
from a provided DataFrame or a CSV file.
|
|
156
|
-
"""
|
|
157
|
-
if self.data is not None:
|
|
158
|
-
return self.data
|
|
159
|
-
elif self.csv_filepath is not None:
|
|
160
|
-
return self.read_csv_file(self.csv_filepath)
|
|
161
|
-
else:
|
|
162
|
-
raise ValueError("No data source provided.")
|
|
163
|
-
|
|
164
|
-
def _fit_model(self):
|
|
165
|
-
"""
|
|
166
|
-
Fits the HMM model to the market data
|
|
167
|
-
and saves the model if a filename is provided.
|
|
168
|
-
"""
|
|
169
|
-
df = self.df.copy()
|
|
170
|
-
data = self.obtain_prices_df(df, end=self.end_date)
|
|
171
|
-
returns = np.column_stack([data["Returns"]])
|
|
172
|
-
|
|
173
|
-
hmm_model = GaussianHMM(
|
|
174
|
-
n_components=self.states,
|
|
175
|
-
covariance_type=self.cov_variance,
|
|
176
|
-
n_iter=self.iterations,
|
|
177
|
-
).fit(returns)
|
|
14
|
+
import warnings
|
|
178
15
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
self.save_hmm_model(hmm_model, self.model_filename)
|
|
183
|
-
|
|
184
|
-
return hmm_model
|
|
185
|
-
|
|
186
|
-
def get_states(self):
|
|
187
|
-
"""
|
|
188
|
-
Predicts the hidden states for the market data
|
|
189
|
-
and calculates the mean returns and volatility for each state.
|
|
190
|
-
|
|
191
|
-
Returns:
|
|
192
|
-
DataFrame containing mean returns
|
|
193
|
-
and volatility for each hidden state.
|
|
194
|
-
"""
|
|
195
|
-
df = self.df.copy()
|
|
196
|
-
data = self.obtain_prices_df(df, end=self.end_date)
|
|
197
|
-
returns = np.column_stack([data["Returns"]])
|
|
198
|
-
states = self.hmm_model.predict(returns)
|
|
199
|
-
data["State"] = states
|
|
200
|
-
# Calculating mean and volatility for each state
|
|
201
|
-
state_stats = (
|
|
202
|
-
data.groupby("State")["Returns"]
|
|
203
|
-
.agg(["mean", "std"])
|
|
204
|
-
.rename(columns={"mean": "Mean Returns", "std": "Volatility"})
|
|
205
|
-
)
|
|
206
|
-
return state_stats
|
|
207
|
-
|
|
208
|
-
def identify_market_trends(self):
|
|
209
|
-
"""
|
|
210
|
-
Identifies bullish and bearish market trends
|
|
211
|
-
based on the mean returns and volatility of each state.
|
|
212
|
-
|
|
213
|
-
Returns:
|
|
214
|
-
A dictionary with keys 'bullish' and 'bearish'
|
|
215
|
-
indicating the identified states.
|
|
216
|
-
"""
|
|
217
|
-
df = self.get_states()
|
|
218
|
-
# Sort the df based on Mean Returns and then by lower Volatility
|
|
219
|
-
sorted_df = df.sort_values(
|
|
220
|
-
by=["Mean Returns", "Volatility"], ascending=[False, True]
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
# The first row after sorting will be the bullish state
|
|
224
|
-
# (highest mean return, lower volatility preferred)
|
|
225
|
-
bullish_state = sorted_df.index[0]
|
|
226
|
-
|
|
227
|
-
# The last row will be the bearish state
|
|
228
|
-
# (as it has the lowest mean return)
|
|
229
|
-
bearish_state = sorted_df.index[-1]
|
|
230
|
-
|
|
231
|
-
return {"bullish": bullish_state, "bearish": bearish_state}
|
|
232
|
-
|
|
233
|
-
def get_current_regime(self, returns_val):
|
|
234
|
-
"""
|
|
235
|
-
Determines the current market regime based on the latest returns.
|
|
236
|
-
|
|
237
|
-
Args:
|
|
238
|
-
returns_val : Array of recent return values
|
|
239
|
-
to predict the current state.
|
|
240
|
-
|
|
241
|
-
Returns:
|
|
242
|
-
The predicted current market state.
|
|
243
|
-
"""
|
|
244
|
-
returns = returns_val[~(np.isnan(returns_val) | np.isinf(returns_val))]
|
|
245
|
-
features = np.array(returns).reshape(-1, 1)
|
|
246
|
-
current_regime = self.hmm_model.predict(features)[-1]
|
|
247
|
-
return current_regime
|
|
248
|
-
|
|
249
|
-
def which_trade_allowed(self, returns_val):
|
|
250
|
-
"""
|
|
251
|
-
Decides whether a long or short trade
|
|
252
|
-
is allowed based on the current market regime.
|
|
253
|
-
This method override the which_trade_allowed() from
|
|
254
|
-
RiskModel class .
|
|
255
|
-
|
|
256
|
-
Args:
|
|
257
|
-
returns_val : Array of recent return values
|
|
258
|
-
to assess the permissible trade.
|
|
259
|
-
|
|
260
|
-
Returns:
|
|
261
|
-
A string indicating "LONG" or "SHORT"
|
|
262
|
-
if a trade is allowed, or None if no trade is permitted.
|
|
263
|
-
"""
|
|
264
|
-
state = self.get_current_regime(returns_val)
|
|
265
|
-
if state in self.allowed_regimes:
|
|
266
|
-
trade = "LONG" if state == self.bullish_state else "SHORT"
|
|
267
|
-
return trade
|
|
268
|
-
else:
|
|
269
|
-
return None
|
|
270
|
-
|
|
271
|
-
def which_quantity_allowed(self): ...
|
|
272
|
-
|
|
273
|
-
def save_hmm_model(self, hmm_model, filename):
|
|
274
|
-
"""
|
|
275
|
-
Saves the trained HMM model to a pickle file.
|
|
276
|
-
|
|
277
|
-
Args:
|
|
278
|
-
hmm_model : The trained GaussianHMM model to be saved.
|
|
279
|
-
filename : The filename under which to save the model.
|
|
280
|
-
"""
|
|
281
|
-
print("Pickling HMM model...")
|
|
282
|
-
pickle.dump(hmm_model, open(f"{filename}.pkl", "wb"))
|
|
283
|
-
print("...HMM model pickled.")
|
|
284
|
-
|
|
285
|
-
def read_csv_file(self, csv_filepath):
|
|
286
|
-
"""
|
|
287
|
-
Reads market data from a CSV file.
|
|
288
|
-
|
|
289
|
-
Args:
|
|
290
|
-
csv_filepath : Path to the CSV file containing the market data.
|
|
291
|
-
|
|
292
|
-
Returns:
|
|
293
|
-
DataFrame containing the parsed market data.
|
|
294
|
-
"""
|
|
295
|
-
df = pd.read_csv(
|
|
296
|
-
csv_filepath,
|
|
297
|
-
header=0,
|
|
298
|
-
names=["Date", "Open", "High", "Low", "Close", "Adj Close", "Volume"],
|
|
299
|
-
index_col="Date",
|
|
300
|
-
parse_dates=True,
|
|
16
|
+
warnings.warn(
|
|
17
|
+
"The class 'HMMRiskManager' is deprecated. ",
|
|
18
|
+
DeprecationWarning,
|
|
301
19
|
)
|
|
302
|
-
return df
|
|
303
|
-
|
|
304
|
-
def obtain_prices_df(self, data_frame, end=None):
|
|
305
|
-
"""
|
|
306
|
-
Processes the market data to calculate returns
|
|
307
|
-
and optionally filters data up to a specified end date.
|
|
308
|
-
|
|
309
|
-
Args:
|
|
310
|
-
data_frame : DataFrame containing the market data.
|
|
311
|
-
end : Optional datetime object specifying the end date for the data.
|
|
312
|
-
|
|
313
|
-
Returns:
|
|
314
|
-
DataFrame with returns calculated
|
|
315
|
-
and data filtered up to the end date if specified.
|
|
316
|
-
"""
|
|
317
|
-
df = data_frame.copy()
|
|
318
|
-
if "Returns" or "returns" not in df.columns:
|
|
319
|
-
if "Close" in df.columns:
|
|
320
|
-
df["Returns"] = df["Close"].pct_change()
|
|
321
|
-
elif "Adj Close" in df.columns:
|
|
322
|
-
df["Returns"] = df["Adj Close"].pct_change()
|
|
323
|
-
else:
|
|
324
|
-
raise ValueError("No 'Close' or 'Adj Close' columns found.")
|
|
325
|
-
elif "returns" in df.columns:
|
|
326
|
-
df.rename(columns={"returns": "Returns"}, inplace=True)
|
|
327
|
-
if end is not None:
|
|
328
|
-
df = df[: end.strftime("%Y-%m-%d")]
|
|
329
|
-
df.dropna(inplace=True)
|
|
330
|
-
return df
|
|
331
|
-
|
|
332
|
-
def show_hidden_states(self):
|
|
333
|
-
"""
|
|
334
|
-
Visualizes the market data
|
|
335
|
-
and the predicted hidden states from the HMM model.
|
|
336
|
-
"""
|
|
337
|
-
data = self.df.copy()
|
|
338
|
-
df = self.obtain_prices_df(data, end=self.end_date)
|
|
339
|
-
self.plot_hidden_states(self.hmm_model, df)
|
|
340
|
-
|
|
341
|
-
def plot_hidden_states(self, hmm_model, df):
|
|
342
|
-
"""
|
|
343
|
-
Plots the adjusted closing prices masked
|
|
344
|
-
by the in-sample hidden states as a mechanism
|
|
345
|
-
to understand the market regimes.
|
|
346
|
-
|
|
347
|
-
Args:
|
|
348
|
-
hmm_model : The trained GaussianHMM model used for predicting states.
|
|
349
|
-
df : DataFrame containing the market data with calculated returns.
|
|
350
|
-
"""
|
|
351
|
-
hidden_states = hmm_model.predict(df[["Returns"]])
|
|
352
|
-
fig, axs = plt.subplots(hmm_model.n_components, sharex=True, sharey=True)
|
|
353
|
-
colours = cm.rainbow(np.linspace(0, 1, hmm_model.n_components))
|
|
354
20
|
|
|
355
|
-
for i, (ax, colour) in enumerate(zip(axs, colours)):
|
|
356
|
-
mask = hidden_states == i
|
|
357
|
-
ax.plot_date(
|
|
358
|
-
df.index[mask], df["Adj Close"][mask], ".", linestyle="none", c=colour
|
|
359
|
-
)
|
|
360
|
-
ax.set_title(f"Hidden State #{i}")
|
|
361
|
-
ax.xaxis.set_major_locator(YearLocator())
|
|
362
|
-
ax.xaxis.set_minor_locator(MonthLocator())
|
|
363
|
-
ax.grid(True)
|
|
364
|
-
plt.show()
|
|
365
21
|
|
|
22
|
+
def build_hmm_models(symbol_list=None, **kwargs):
|
|
23
|
+
import warnings
|
|
366
24
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
hmm_end = kwargs.get("hmm_end", 0)
|
|
372
|
-
sd = kwargs.get("session_duration", 23.0)
|
|
373
|
-
hmm_tickers = kwargs.get("hmm_tickers")
|
|
374
|
-
if hmm_tickers is not None:
|
|
375
|
-
symbols = hmm_tickers
|
|
376
|
-
else:
|
|
377
|
-
symbols = symbol_list
|
|
378
|
-
hmm_models = {symbol: None for symbol in symbols}
|
|
379
|
-
if data is not None:
|
|
380
|
-
if isinstance(data, pd.DataFrame):
|
|
381
|
-
hmm_data = data
|
|
382
|
-
hmm = HMMRiskManager(data=hmm_data, verbose=True, iterations=1000, **kwargs)
|
|
383
|
-
for symbol in symbols:
|
|
384
|
-
hmm_models[symbol] = hmm
|
|
385
|
-
elif isinstance(data, dict):
|
|
386
|
-
for symbol, data in data.items():
|
|
387
|
-
hmm = HMMRiskManager(data=data, verbose=True, iterations=1000, **kwargs)
|
|
388
|
-
hmm_models[symbol] = hmm
|
|
389
|
-
if mt5_data:
|
|
390
|
-
for symbol in symbols:
|
|
391
|
-
rates = Rates(
|
|
392
|
-
symbol, timeframe=tf, start_pos=hmm_end, session_duration=sd, **kwargs
|
|
393
|
-
)
|
|
394
|
-
data = rates.get_rates_from_pos()
|
|
395
|
-
assert data is not None, f"No data for {symbol}"
|
|
396
|
-
hmm = HMMRiskManager(data=data, verbose=True, iterations=1000, **kwargs)
|
|
397
|
-
hmm_models[symbol] = hmm
|
|
398
|
-
return hmm_models
|
|
25
|
+
warnings.warn(
|
|
26
|
+
"The function 'build_hmm_models' is deprecated.",
|
|
27
|
+
DeprecationWarning,
|
|
28
|
+
)
|