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.

@@ -20,25 +20,17 @@ from datetime import datetime
20
20
  from queue import Queue
21
21
  from typing import Dict, List, Literal, Optional, Union
22
22
 
23
- import numpy as np
24
- import pandas as pd
25
- import yfinance as yf
26
-
27
23
  from bbstrader.btengine.backtest import BacktestEngine
28
24
  from bbstrader.btengine.data import DataHandler, MT5DataHandler, YFDataHandler
29
25
  from bbstrader.btengine.event import Events, SignalEvent
30
26
  from bbstrader.btengine.execution import MT5ExecutionHandler, SimExecutionHandler
31
27
  from bbstrader.btengine.strategy import Strategy
32
28
  from bbstrader.metatrader.account import Account
33
- from bbstrader.metatrader.rates import Rates
29
+
34
30
  from bbstrader.metatrader.trade import TradingMode
35
- from bbstrader.models.risk import build_hmm_models
36
- from bbstrader.tseries import ArimaGarchModel, KalmanFilterModel
31
+
37
32
 
38
33
  __all__ = [
39
- "SMAStrategy",
40
- "ArimaGarchStrategy",
41
- "KalmanFilterStrategy",
42
34
  "StockIndexSTBOTrading",
43
35
  "test_strategy",
44
36
  "get_quantities",
@@ -52,528 +44,6 @@ def get_quantities(quantities, symbol_list):
52
44
  return {symbol: quantities for symbol in symbol_list}
53
45
 
54
46
 
55
- class SMAStrategy(Strategy):
56
- """
57
- Carries out a basic Moving Average Crossover strategy bactesting with a
58
- short/long simple weighted moving average. Default short/long
59
- windows are 50/200 periods respectively and uses Hiden Markov Model
60
- as risk Managment system for filteering signals.
61
-
62
- The trading strategy for this class is exceedingly simple and is used to bettter
63
- understood. The important issue is the risk management aspect (the Hmm model)
64
-
65
- The Long-term trend following strategy is of the classic moving average crossover type.
66
- The rules are simple:
67
- - At every bar calculate the 50-day and 200-day simple moving averages (SMA)
68
- - If the 50-day SMA exceeds the 200-day SMA and the strategy is not invested, then go long
69
- - If the 200-day SMA exceeds the 50-day SMA and the strategy is invested, then close the position
70
- """
71
-
72
- def __init__(
73
- self,
74
- bars: DataHandler = None,
75
- events: Queue = None,
76
- symbol_list: List[str] = None,
77
- mode: TradingMode = TradingMode.BACKTEST,
78
- **kwargs,
79
- ):
80
- """
81
- Args:
82
- bars (DataHandler): A data handler object that provides market data.
83
- events (Queue): An event queue object where generated signals are placed.
84
- symbol_list (List[str]): A list of symbols to consider for trading.
85
- mode TradingMode: The mode of operation for the strategy.
86
- short_window (int, optional): The period for the short moving average.
87
- long_window (int, optional): The period for the long moving average.
88
- time_frame (str, optional): The time frame for the data.
89
- session_duration (float, optional): The duration of the trading session.
90
- risk_window (int, optional): The window size for the risk model.
91
- quantities (int, dict | optional): The default quantities of each asset to trade.
92
- """
93
- self.bars = bars
94
- self.events = events
95
- self.symbol_list = symbol_list or self.bars.symbol_list
96
- self.mode = mode
97
-
98
- self.kwargs = kwargs
99
- self.short_window = kwargs.get("short_window", 50)
100
- self.long_window = kwargs.get("long_window", 200)
101
- self.tf = kwargs.get("time_frame", "D1")
102
- self.qty = get_quantities(kwargs.get("quantities", 100), self.symbol_list)
103
- self.sd = kwargs.get("session_duration", 23.0)
104
- self.risk_models = build_hmm_models(self.symbol_list, **self.kwargs)
105
- self.risk_window = kwargs.get("risk_window", self.long_window)
106
- self.bought = self._calculate_initial_bought()
107
-
108
- def _calculate_initial_bought(self):
109
- bought = {}
110
- for s in self.symbol_list:
111
- bought[s] = "OUT"
112
- return bought
113
-
114
- def get_backtest_data(self):
115
- symbol_data = {symbol: None for symbol in self.symbol_list}
116
- for s in self.symbol_list:
117
- bar_date = self.bars.get_latest_bar_datetime(s)
118
- bars = self.bars.get_latest_bars_values(s, "adj_close", N=self.long_window)
119
- returns_val = self.bars.get_latest_bars_values(
120
- s, "returns", N=self.risk_window
121
- )
122
- if len(bars) >= self.long_window and len(returns_val) >= self.risk_window:
123
- regime = self.risk_models[s].which_trade_allowed(returns_val)
124
-
125
- short_sma = np.mean(bars[-self.short_window :])
126
- long_sma = np.mean(bars[-self.long_window :])
127
-
128
- symbol_data[s] = (short_sma, long_sma, regime, bar_date)
129
- return symbol_data
130
-
131
- def create_backtest_signals(self):
132
- signals = {symbol: None for symbol in self.symbol_list}
133
- symbol_data = self.get_backtest_data()
134
- for s, data in symbol_data.items():
135
- signal = None
136
- if data is not None:
137
- price = self.bars.get_latest_bar_value(s, "adj_close")
138
- short_sma, long_sma, regime, bar_date = data
139
- dt = bar_date
140
- if regime == "LONG":
141
- # Bulliqh regime
142
- if short_sma < long_sma and self.bought[s] == "LONG":
143
- print(f"EXIT: {bar_date}")
144
- signal = SignalEvent(1, s, dt, "EXIT", price=price)
145
- self.bought[s] = "OUT"
146
-
147
- elif short_sma > long_sma and self.bought[s] == "OUT":
148
- print(f"LONG: {bar_date}")
149
- signal = SignalEvent(
150
- 1, s, dt, "LONG", quantity=self.qty[s], price=price
151
- )
152
- self.bought[s] = "LONG"
153
-
154
- elif regime == "SHORT":
155
- # Bearish regime
156
- if short_sma > long_sma and self.bought[s] == "SHORT":
157
- print(f"EXIT: {bar_date}")
158
- signal = SignalEvent(1, s, dt, "EXIT", price=price)
159
- self.bought[s] = "OUT"
160
-
161
- elif short_sma < long_sma and self.bought[s] == "OUT":
162
- print(f"SHORT: {bar_date}")
163
- signal = SignalEvent(
164
- 1, s, dt, "SHORT", quantity=self.qty[s], price=price
165
- )
166
- self.bought[s] = "SHORT"
167
- signals[s] = signal
168
- return signals
169
-
170
- def get_live_data(self):
171
- symbol_data = {symbol: None for symbol in self.symbol_list}
172
- for symbol in self.symbol_list:
173
- sig_rate = Rates(symbol, self.tf, 0, self.risk_window + 2, **self.kwargs)
174
- hmm_data = sig_rate.returns.values
175
- prices = sig_rate.close.values
176
- current_regime = self.risk_models[symbol].which_trade_allowed(hmm_data)
177
- assert len(prices) >= self.long_window and len(hmm_data) >= self.risk_window
178
- short_sma = np.mean(prices[-self.short_window :])
179
- long_sma = np.mean(prices[-self.long_window :])
180
- short_sma, long_sma, current_regime
181
- symbol_data[symbol] = (short_sma, long_sma, current_regime)
182
- return symbol_data
183
-
184
- def create_live_signals(self):
185
- signals = {symbol: None for symbol in self.symbol_list}
186
- symbol_data = self.get_live_data()
187
- for symbol, data in symbol_data.items():
188
- signal = None
189
- short_sma, long_sma, regime = data
190
- if regime == "LONG":
191
- if short_sma > long_sma:
192
- signal = "LONG"
193
- elif regime == "SHORT":
194
- if short_sma < long_sma:
195
- signal = "SHORT"
196
- signals[symbol] = signal
197
- return signals
198
-
199
- def calculate_signals(self, event=None):
200
- if self.mode == TradingMode.BACKTEST and event is not None:
201
- if event.type == Events.MARKET:
202
- signals = self.create_backtest_signals()
203
- for signal in signals.values():
204
- if signal is not None:
205
- self.events.put(signal)
206
- elif self.mode == TradingMode.LIVE:
207
- signals = self.create_live_signals()
208
- return signals
209
-
210
-
211
- class ArimaGarchStrategy(Strategy):
212
- """
213
- The `ArimaGarchStrategy` class extends the `Strategy`
214
- class to implement a backtesting framework for trading strategies based on
215
- ARIMA-GARCH models, incorporating a Hidden Markov Model (HMM) for risk management.
216
-
217
- Features
218
- ========
219
- - **ARIMA-GARCH Model**: Utilizes ARIMA for time series forecasting and GARCH for volatility forecasting, aimed at predicting market movements.
220
-
221
- - **HMM Risk Management**: Employs a Hidden Markov Model to manage risks, determining safe trading regimes.
222
-
223
- - **Event-Driven Backtesting**: Capable of simulating real-time trading conditions by processing market data and signals sequentially.
224
-
225
- - **Live Trading**: Supports real-time trading by generating signals based on live ARIMA-GARCH predictions and HMM risk management.
226
-
227
- Key Methods
228
- ===========
229
- - `get_backtest_data()`: Retrieves historical data for backtesting.
230
- - `create_backtest_signal()`: Generates trading signals based on ARIMA-GARCH predictions and HMM risk management.
231
- - `get_live_data()`: Retrieves live data for real-time trading.
232
- - `create_live_signals()`: Generates trading signals based on live ARIMA-GARCH predictions and HMM risk management.
233
- - `calculate_signals()`: Determines the trading signals based on the mode of operation (backtest or live).
234
-
235
- """
236
-
237
- def __init__(
238
- self,
239
- bars: DataHandler = None,
240
- events: Queue = None,
241
- symbol_list: List[str] = None,
242
- mode: TradingMode = TradingMode.BACKTEST,
243
- **kwargs,
244
- ):
245
- """
246
- Args:
247
- `bars`: A data handler object that provides market data.
248
- `events`: An event queue object where generated signals are placed.
249
- `symbol_list`: A list of symbols to consider for trading.
250
- `mode`: The mode of operation for the strategy.
251
- `arima_window`: The window size for rolling prediction in backtesting.
252
- `time_frame`: The time frame for the data.
253
- `quantities`: Quantity of each assets to trade.
254
- `hmm_window`: Lookback period for HMM.
255
- """
256
- self.bars = bars
257
- self.events = events
258
- self.symbol_list = symbol_list or self.bars.symbol_list
259
- self.mode = mode
260
-
261
- self.qty = get_quantities(kwargs.get("quantities", 100), self.symbol_list)
262
- self.arima_window = kwargs.get("arima_window", 252)
263
- self.tf = kwargs.get("time_frame", "D1")
264
- self.sd = kwargs.get("session_duration", 23.0)
265
- self.risk_window = kwargs.get("hmm_window", 50)
266
- self.risk_models = build_hmm_models(self.symbol_list, **kwargs)
267
- self.arima_models = self._build_arch_models(**kwargs)
268
-
269
- self.long_market = {s: False for s in self.symbol_list}
270
- self.short_market = {s: False for s in self.symbol_list}
271
-
272
- def _build_arch_models(self, **kwargs) -> Dict[str, ArimaGarchModel]:
273
- arch_models = {symbol: None for symbol in self.symbol_list}
274
- for symbol in self.symbol_list:
275
- try:
276
- rates = Rates(symbol, self.tf, 0)
277
- data = rates.get_rates_from_pos()
278
- assert data is not None, f"No data for {symbol}"
279
- except AssertionError:
280
- data = yf.download(symbol, start=kwargs.get("yf_start"))
281
- arch = ArimaGarchModel(symbol, data, k=self.arima_window)
282
- arch_models[symbol] = arch
283
- return arch_models
284
-
285
- def get_backtest_data(self):
286
- symbol_data = {symbol: None for symbol in self.symbol_list}
287
- for symbol in self.symbol_list:
288
- M = self.arima_window
289
- N = self.risk_window
290
- dt = self.bars.get_latest_bar_datetime(symbol)
291
- bars = self.bars.get_latest_bars_values(
292
- symbol, "close", N=self.arima_window
293
- )
294
- returns = self.bars.get_latest_bars_values(
295
- symbol, "returns", N=self.risk_window
296
- )
297
- df = pd.DataFrame()
298
- df["Close"] = bars[-M:]
299
- df = df.dropna()
300
- arch_returns = self.arima_models[symbol].load_and_prepare_data(df)
301
- data = arch_returns["diff_log_return"].iloc[-self.arima_window :]
302
- if len(data) >= M and len(returns) >= N:
303
- symbol_data[symbol] = (data, returns[-N:], dt)
304
- return symbol_data
305
-
306
- def create_backtest_signal(self):
307
- signals = {symbol: None for symbol in self.symbol_list}
308
- for symbol in self.symbol_list:
309
- symbol_data = self.get_backtest_data()[symbol]
310
- if symbol_data is not None:
311
- data, returns, dt = symbol_data
312
- signal = None
313
- prediction = self.arima_models[symbol].get_prediction(data)
314
- regime = self.risk_models[symbol].which_trade_allowed(returns)
315
- price = self.bars.get_latest_bar_value(symbol, "adj_close")
316
-
317
- # If we are short the market, check for an exit
318
- if prediction > 0 and self.short_market[symbol]:
319
- signal = SignalEvent(1, symbol, dt, "EXIT", price=price)
320
- print(f"{dt}: EXIT SHORT")
321
- self.short_market[symbol] = False
322
-
323
- # If we are long the market, check for an exit
324
- elif prediction < 0 and self.long_market[symbol]:
325
- signal = SignalEvent(1, symbol, dt, "EXIT", price=price)
326
- print(f"{dt}: EXIT LONG")
327
- self.long_market[symbol] = False
328
-
329
- if regime == "LONG":
330
- # If we are not in the market, go long
331
- if prediction > 0 and not self.long_market[symbol]:
332
- signal = SignalEvent(
333
- 1,
334
- symbol,
335
- dt,
336
- "LONG",
337
- quantity=self.qty[symbol],
338
- price=price,
339
- )
340
- print(f"{dt}: LONG")
341
- self.long_market[symbol] = True
342
-
343
- elif regime == "SHORT":
344
- # If we are not in the market, go short
345
- if prediction < 0 and not self.short_market[symbol]:
346
- signal = SignalEvent(
347
- 1,
348
- symbol,
349
- dt,
350
- "SHORT",
351
- quantity=self.qty[symbol],
352
- price=price,
353
- )
354
- print(f"{dt}: SHORT")
355
- self.short_market[symbol] = True
356
- signals[symbol] = signal
357
- return signals
358
-
359
- def get_live_data(self):
360
- symbol_data = {symbol: None for symbol in self.symbol_list}
361
- for symbol in self.symbol_list:
362
- arch_data = Rates(symbol, self.tf, 0, self.arima_window)
363
- rates = arch_data.get_rates_from_pos()
364
- arch_returns = self.arima_models[symbol].load_and_prepare_data(rates)
365
- window_data = arch_returns["diff_log_return"].iloc[-self.arima_window :]
366
- hmm_returns = arch_data.returns.values[-self.risk_window :]
367
- symbol_data[symbol] = (window_data, hmm_returns)
368
- return symbol_data
369
-
370
- def create_live_signals(self):
371
- signals = {symbol: None for symbol in self.symbol_list}
372
- data = self.get_live_data()
373
- for symbol in self.symbol_list:
374
- symbol_data = data[symbol]
375
- if symbol_data is not None:
376
- window_data, hmm_returns = symbol_data
377
- prediction = self.arima_models[symbol].get_prediction(window_data)
378
- regime = self.risk_models[symbol].which_trade_allowed(hmm_returns)
379
- if regime == "LONG":
380
- if prediction > 0:
381
- signals[symbol] = "LONG"
382
- elif regime == "SHORT":
383
- if prediction < 0:
384
- signals[symbol] = "SHORT"
385
- return signals
386
-
387
- def calculate_signals(self, event=None):
388
- if self.mode == TradingMode.BACKTEST and event is not None:
389
- if event.type == Events.MARKET:
390
- signals = self.create_backtest_signal()
391
- for signal in signals.values():
392
- if signal is not None:
393
- self.events.put(signal)
394
- elif self.mode == TradingMode.LIVE:
395
- return self.create_live_signals()
396
-
397
-
398
- class KalmanFilterStrategy(Strategy):
399
- """
400
- The `KalmanFilterStrategy` class implements a backtesting framework for a
401
- [pairs trading](https://en.wikipedia.org/wiki/Pairs_trade) strategy using
402
- Kalman Filter for signals and Hidden Markov Models (HMM) for risk management.
403
- This document outlines the structure and usage of the `KalmanFilterStrategy`,
404
- including initialization parameters, main functions, and an example of how to run a backtest.
405
- """
406
-
407
- def __init__(
408
- self,
409
- bars: DataHandler = None,
410
- events: Queue = None,
411
- symbol_list: List[str] = None,
412
- mode: TradingMode = TradingMode.BACKTEST,
413
- **kwargs,
414
- ):
415
- """
416
- Args:
417
- `bars`: `DataHandler` for market data handling.
418
- `events`: A queue for managing events.
419
- `symbol_list`: List of ticker symbols for the pairs trading strategy.
420
- `mode`: Mode of operation for the strategy.
421
- kwargs : Additional keyword arguments including
422
- - `quantity`: Quantity of assets to trade. Default is 100.
423
- - `hmm_window`: Window size for calculating returns for the HMM. Default is 50.
424
- - `hmm_tiker`: Ticker symbol used by the HMM for risk management.
425
- - `time_frame`: Time frame for the data. Default is 'D1'.
426
- - `session_duration`: Duration of the trading session. Default is 6.5.
427
- """
428
- self.bars = bars
429
- self.events_queue = events
430
- self.symbol_list = symbol_list or self.bars.symbol_list
431
- self.mode = mode
432
-
433
- self.hmm_tiker = kwargs.get("hmm_tiker")
434
- self._assert_tikers()
435
- self.account = Account(**kwargs)
436
- self.hmm_window = kwargs.get("hmm_window", 50)
437
- self.qty = kwargs.get("quantity", 100)
438
- self.tf = kwargs.get("time_frame", "D1")
439
- self.sd = kwargs.get("session_duration", 6.5)
440
-
441
- self.risk_model = build_hmm_models(self.symbol_list, **kwargs)
442
- self.kl_model = KalmanFilterModel(self.tickers, **kwargs)
443
-
444
- self.long_market = False
445
- self.short_market = False
446
-
447
- def _assert_tikers(self):
448
- if self.symbol_list is None or len(self.symbol_list) != 2:
449
- raise ValueError("A list of 2 Tickers must be provide for this strategy")
450
- self.tickers = self.symbol_list
451
- if self.hmm_tiker is None:
452
- raise ValueError(
453
- "You need to provide a ticker used by the HMM for risk management"
454
- )
455
-
456
- def calculate_btxy(self, etqt, regime, dt):
457
- # Make sure there is no position open
458
- if etqt is None:
459
- return
460
- et, sqrt_Qt = etqt
461
- theta = self.kl_model.theta
462
- p1 = self.bars.get_latest_bar_value(self.tickers[1], "adj_close")
463
- p0 = self.bars.get_latest_bar_value(self.tickers[0], "adj_close")
464
- if et >= -sqrt_Qt and self.long_market:
465
- print("CLOSING LONG: %s" % dt)
466
- y_signal = SignalEvent(1, self.tickers[1], dt, "EXIT", price=p1)
467
- x_signal = SignalEvent(1, self.tickers[0], dt, "EXIT", price=p0)
468
- self.events_queue.put(y_signal)
469
- self.events_queue.put(x_signal)
470
- self.long_market = False
471
-
472
- elif et <= sqrt_Qt and self.short_market:
473
- print("CLOSING SHORT: %s" % dt)
474
- y_signal = SignalEvent(1, self.tickers[1], dt, "EXIT", price=p1)
475
- x_signal = SignalEvent(1, self.tickers[0], dt, "EXIT", price=p0)
476
- self.events_queue.put(y_signal)
477
- self.events_queue.put(x_signal)
478
- self.short_market = False
479
-
480
- # Long Entry
481
- if regime == "LONG":
482
- if et <= -sqrt_Qt and not self.long_market:
483
- print("LONG: %s" % dt)
484
- y_signal = SignalEvent(
485
- 1, self.tickers[1], dt, "LONG", self.qty, 1.0, price=p1
486
- )
487
- x_signal = SignalEvent(
488
- 1, self.tickers[0], dt, "SHORT", self.qty, theta[0], price=p0
489
- )
490
- self.events_queue.put(y_signal)
491
- self.events_queue.put(x_signal)
492
- self.long_market = True
493
-
494
- # Short Entry
495
- elif regime == "SHORT":
496
- if et >= sqrt_Qt and not self.short_market:
497
- print("SHORT: %s" % dt)
498
- y_signal = SignalEvent(
499
- 1, self.tickers[1], dt, "SHORT", self.qty, 1.0, price=p1
500
- )
501
- x_signal = SignalEvent(
502
- 1, self.tickers[0], "LONG", self.qty, theta[0], price=p0
503
- )
504
- self.events_queue.put(y_signal)
505
- self.events_queue.put(x_signal)
506
- self.short_market = True
507
-
508
- def calculate_livexy(self):
509
- signals = {symbol: None for symbol in self.symbol_list}
510
- p0_price = self.account.get_tick_info(self.tickers[0]).ask
511
- p1_price = self.account.get_tick_info(self.tickers[1]).ask
512
- prices = np.array([p0_price, p1_price])
513
- et_std = self.kl_model.calculate_etqt(prices)
514
- if et_std is not None:
515
- et, std = et_std
516
- y_signal = None
517
- x_signal = None
518
-
519
- if et >= -std or et <= std:
520
- y_signal = "EXIT"
521
- x_signal = "EXIT"
522
-
523
- if et <= -std:
524
- y_signal = "LONG"
525
- x_signal = "SHORT"
526
-
527
- if et >= std:
528
- y_signal = "SHORT"
529
- x_signal = "LONG"
530
-
531
- signals[self.tickers[0]] = x_signal
532
- signals[self.tickers[1]] = y_signal
533
- return signals
534
-
535
- def calculate_backtest_signals(self):
536
- p0, p1 = self.tickers[0], self.tickers[1]
537
- dt = self.bars.get_latest_bar_datetime(p0)
538
- x = self.bars.get_latest_bar_value(p0, "close")
539
- y = self.bars.get_latest_bar_value(p1, "close")
540
- returns = self.bars.get_latest_bars_values(
541
- self.hmm_tiker, "returns", N=self.hmm_window
542
- )
543
- latest_prices = np.array([-1.0, -1.0])
544
- if len(returns) >= self.hmm_window:
545
- latest_prices[0] = x
546
- latest_prices[1] = y
547
- et_qt = self.kl_model.calculate_etqt(latest_prices)
548
- regime = self.risk_model[self.hmm_tiker].which_trade_allowed(returns)
549
- self.calculate_btxy(et_qt, regime, dt)
550
-
551
- def calculate_live_signals(self):
552
- # Data Retrieval
553
- signals = {symbol: None for symbol in self.symbol_list}
554
- initial_signals = self.calculate_livexy()
555
- hmm_data = Rates(self.hmm_ticker, self.tf, 0, self.hmm_window)
556
- returns = hmm_data.returns.values
557
- current_regime = self.risk_model[self.hmm_tiker].which_trade_allowed(returns)
558
- for symbol in self.symbol_list:
559
- if symbol in initial_signals:
560
- signal = initial_signals[symbol]
561
- if signal == "LONG" and current_regime == "LONG":
562
- signals[symbol] = "LONG"
563
- elif signal == "SHORT" and current_regime == "SHORT":
564
- signals[symbol] = "SHORT"
565
- return signals
566
-
567
- def calculate_signals(self, event=None):
568
- """
569
- Calculate the Kalman Filter strategy.
570
- """
571
- if self.mode == TradingMode.BACKTEST and event is not None:
572
- if event.type == Events.MARKET:
573
- self.calculate_backtest_signals()
574
- elif self.mode == TradingMode.LIVE:
575
- return self.calculate_live_signals()
576
-
577
47
 
578
48
  class StockIndexSTBOTrading(Strategy):
579
49
  """
@@ -778,50 +248,6 @@ def _run_backtest(strategy_name: str, capital: float, symbol_list: list, kwargs:
778
248
  engine.simulate_trading()
779
249
 
780
250
 
781
- def _run_arch_backtest(capital: float = 100000.0, quantity: int = 1000):
782
- hmm_data = yf.download("^GSPC", start="1990-01-01", end="2009-12-31")
783
- kwargs = {
784
- "quantity": quantity,
785
- "yf_start": "2010-01-04",
786
- "hmm_data": hmm_data,
787
- "backtester_class": ArimaGarchStrategy,
788
- "data_handler": YFDataHandler,
789
- }
790
- _run_backtest("ARIMA+GARCH & HMM", capital, ["^GSPC"], kwargs)
791
-
792
-
793
- def _run_kf_backtest(capital: float = 100000.0, quantity: int = 2000):
794
- symbol_list = ["IEI", "TLT"]
795
- tlt = yf.download("TLT", end="2008-07-09")
796
- iei = yf.download("IEI", end="2008-07-09")
797
- kwargs = {
798
- "quantity": quantity,
799
- "yf_start": "2009-08-03",
800
- "hmm_data": {"IEI": iei, "TLT": tlt},
801
- "hmm_tiker": "TLT",
802
- "session_duration": 6.5,
803
- "backtester_class": KalmanFilterStrategy,
804
- "data_handler": YFDataHandler,
805
- }
806
- _run_backtest("Kalman Filter & HMM", capital, symbol_list, kwargs)
807
-
808
-
809
- def _run_sma_backtest(capital: float = 100000.0, quantity: int = 1):
810
- spx_data = yf.download("^GSPC", start="1990-01-01", end="2009-12-31")
811
- kwargs = {
812
- "quantities": quantity,
813
- "hmm_end": "2009-12-31",
814
- "yf_start": "2010-01-04",
815
- "hmm_data": spx_data,
816
- "mt5_start": datetime(2010, 1, 1),
817
- "mt5_end": datetime(2023, 1, 1),
818
- "backtester_class": SMAStrategy,
819
- "data_handler": MT5DataHandler,
820
- "exc_handler": MT5ExecutionHandler,
821
- }
822
- _run_backtest("SMA & HMM", capital, ["[SP500]"], kwargs)
823
-
824
-
825
251
  def _run_sistbo_backtest(capital: float = 100000.0, quantity: int = None):
826
252
  ndx = "[NQ100]"
827
253
  spx = "[SP500]"
@@ -845,16 +271,8 @@ def _run_sistbo_backtest(capital: float = 100000.0, quantity: int = None):
845
271
  _run_backtest("Stock Index Short Term Buy Only ", capital, symbol_list, kwargs)
846
272
 
847
273
 
848
- _BACKTESTS = {
849
- "sma": _run_sma_backtest,
850
- "klf": _run_kf_backtest,
851
- "arch": _run_arch_backtest,
852
- "sistbo": _run_sistbo_backtest,
853
- }
854
-
855
-
856
274
  def test_strategy(
857
- strategy: Literal["sma", "klf", "arch", "sistbo"] = "sma",
275
+ strategy: Literal["sistbo"] = "sistbo",
858
276
  quantity: Optional[int] = 100,
859
277
  ):
860
278
  """
@@ -862,14 +280,13 @@ def test_strategy(
862
280
 
863
281
  Args:
864
282
  strategy : The strategy to use in test mode. Default is `sma`.
865
- - `sma` Execute `SMAStrategy`, for more detail see this class documentation.
866
- - `klf` Execute `KalmanFilterStrategy`, for more detail see this class documentation.
867
- - `arch` Execute `ArimaGarchStrategy`, for more detail see this class documentation.
868
283
  - `sistbo` Execute `StockIndexSTBOTrading`, for more detail see this class documentation.
869
284
  quantity : The quantity of assets to be used in the test backtest. Default is 1000.
870
285
 
871
286
  """
872
- if strategy in _BACKTESTS:
873
- _BACKTESTS[strategy](quantity=quantity)
874
- else:
875
- raise ValueError(f"Unknown strategy: {strategy}")
287
+ if strategy != "sistbo":
288
+ raise ValueError(
289
+ "Only 'sistbo' strategy is available for testing at the moment."
290
+ )
291
+ _run_sistbo_backtest(quantity=quantity)
292
+