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/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
- from bbstrader.metatrader.rates import Rates
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: Optional[pd.DataFrame] = None,
104
- states: int = 2,
105
- iterations: int = 100,
106
- end_date: Optional[datetime] = None,
107
- csv_filepath: Optional[str] = None,
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
- if self.verbose:
180
- print(f"Hidden Markov model (HMM) Score: {hmm_model.score(returns)}")
181
- if self.model_filename is not None:
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
- def build_hmm_models(symbol_list=None, **kwargs) -> Dict[str, HMMRiskManager]:
368
- mt5_data = kwargs.get("use_mt5_data", False)
369
- data = kwargs.get("hmm_data")
370
- tf = kwargs.get("time_frame", "D1")
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
+ )