bbstrader 0.1.6__py3-none-any.whl → 0.1.7__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.

@@ -1,977 +1,386 @@
1
+ from math import log
1
2
  import time
2
- import pandas as pd
3
- import numpy as np
4
3
  from datetime import datetime
5
- from bbstrader.metatrader.rates import Rates
6
4
  from bbstrader.metatrader.trade import Trade
7
- from bbstrader.trading.utils import tf_mapping
8
- from bbstrader.strategies import (
9
- ArimaGarchStrategy, SMAStrategy, KLFStrategy, OrnsteinUhlenbeck,
10
- )
11
- from bbstrader.models.risk import HMMRiskManager
12
- from bbstrader.metatrader.utils import config_logger
13
- from typing import Optional, Literal, List, Tuple
14
-
15
- logger = config_logger(log_file='trade.log', console_log=False)
16
-
17
- TRADING_DAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']
18
- # ======== SMA TRADING ======================
19
- def sma_trading(
20
- trade: Trade,
21
- tf: Optional[str] = '1h',
22
- sma: Optional[int] = 35,
23
- lma: Optional[int] = 80,
24
- mm: Optional[bool] = True,
25
- max_t: Optional[int] = 1,
26
- iter_time: Optional[int |float] = 30,
27
- risk_manager: str = 'hmm',
28
- period: Literal['day', 'week', 'month'] = 'week',
29
- **kwargs
5
+ from bbstrader.trading.strategies import Strategy
6
+ from typing import Optional, Literal, List, Tuple, Dict
7
+ import MetaTrader5 as mt5
8
+ from bbstrader.metatrader.account import INIT_MSG
9
+ from bbstrader.metatrader.utils import raise_mt5_error
10
+
11
+
12
+ _TF_MAPPING = {
13
+ '1m': 1,
14
+ '3m': 3,
15
+ '5m': 5,
16
+ '10m': 10,
17
+ '15m': 15,
18
+ '30m': 30,
19
+ '1h': 60,
20
+ '2h': 120,
21
+ '4h': 240,
22
+ 'D1': 1440
23
+ }
24
+
25
+ TRADING_DAYS = [
26
+ 'monday',
27
+ 'tuesday',
28
+ 'wednesday',
29
+ 'thursday',
30
+ 'friday'
31
+ ]
32
+
33
+ def _check_mt5_connection():
34
+ if not mt5.initialize():
35
+ raise_mt5_error(INIT_MSG)
36
+
37
+ def _mt5_execution(
38
+ symbol_list, trades_instances, strategy_cls, /,
39
+ mm, time_frame, iter_time, period, trading_days,
40
+ comment, **kwargs
30
41
  ):
31
- """
32
- Executes a Simple Moving Average (SMA) trading strategy
33
- with optional risk management using Hidden Markov Models (HMM).
34
-
35
- Parameters
36
- ==========
37
- trade : Trade
38
- The Trade object that encapsulates trading operations like
39
- opening, closing positions, etc.
40
- tf : str, optional
41
- Time frame for the trading strategy, defaults to '1h' (1 hour).
42
- sma : int, optional
43
- Short Moving Average period, defaults to 35.
44
- lma : int, optional
45
- Long Moving Average period, defaults to 80.
46
- mm : bool, optional
47
- Money management flag to enable/disable money management, defaults to True.
48
- max_t : int, optional
49
- Maximum number of trades allowed, defaults to 1.
50
- iter_time : Union[int, float], optional
51
- Iteration time for the trading loop, defaults to 30 seconds.
52
- risk_manager : str
53
- Specifies the risk management strategy to use,
54
- 'hmm' for Hidden Markov Model.
55
- period : Literal['day', 'week', 'month'], optional
56
- Trading period to reset statistics and close positions,
57
- can be 'day', 'week', or 'month', defaults to 'week'.
58
- **kwargs
59
- Additional keyword arguments for the HMM risk manager.
60
-
61
- Returns
62
- =======
63
- None
64
-
65
- Notes
66
- =====
67
- This function integrates a trading strategy based on simple moving averages
68
- with an optional risk management layer using HMM.
69
- It periodically checks for trading signals and executes buy or sell orders
70
- based on the strategy signals and risk management conditions.
71
- The trading period (day, week, month) determines when to reset statistics
72
- and close all positions.
73
-
74
- This function includes an infinite loop with time delays designed
75
- to run continuously during market hours. Ensure proper exception handling
76
- and resource management when integrating into a live trading environment.
77
- """
78
-
79
- def check(buys: list, sells: list):
80
- if buys is not None or sells is not None:
81
- logger.info(f"Checking for Break even SYMBOL={trade.symbol}...")
82
- trade.break_even()
42
+ symbols = symbol_list.copy()
43
+ STRATEGY = kwargs.get('strategy_name')
44
+ _max_trades = kwargs.get('max_trades')
45
+ logger = kwargs.get('logger')
46
+ max_trades = {symbol: _max_trades[symbol] for symbol in symbols}
47
+ if comment is None:
48
+ trade = trades_instances[symbols[0]]
49
+ comment = f"{trade.expert_name}@{trade.version}"
50
+
51
+ def check(buys: List, sells: List, symbol: str):
52
+ if not mm:
53
+ return
54
+ if buys is not None:
55
+ logger.info(
56
+ f"Checking for Break even, SYMBOL={symbol}...STRATEGY={STRATEGY}")
57
+ trades_instances[symbol].break_even(mm=mm)
58
+ if sells is not None:
59
+ logger.info(
60
+ f"Checking for Break even, SYMBOL={symbol}...STRATEGY={STRATEGY}")
61
+ trades_instances[symbol].break_even(mm=mm)
62
+ num_days = 0
63
+ time_intervals = 0
64
+ trade_time = _TF_MAPPING[time_frame]
83
65
 
84
- time_frame_mapping = tf_mapping()
85
- if tf == 'D1':
86
- trade_time = trade.get_minutes()
87
- else:
88
- trade_time = time_frame_mapping[tf]
66
+ long_market = {symbol: False for symbol in symbols}
67
+ short_market = {symbol: False for symbol in symbols}
89
68
 
90
- rate = Rates(trade.symbol, tf, 0)
91
- data = rate.get_rates_from_pos()
92
- strategy = SMAStrategy(short_window=sma, long_window=lma)
93
- hmm = HMMRiskManager(data=data, verbose=True,
94
- iterations=1000, **kwargs)
95
- time_intervals = 0
96
- long_market = False
97
- short_market = False
98
- num_days = 0
99
69
  logger.info(
100
- f'Running SMA Strategy on {trade.symbol} in {tf} Interval ...\n')
70
+ f'Running {STRATEGY} Strategy on {symbols} in {time_frame} Interval ...')
71
+ strategy: Strategy = strategy_cls(
72
+ symbol_list=symbols, mode='live', **kwargs)
73
+
101
74
  while True:
102
- current_date = datetime.now()
103
- today = current_date.strftime("%A")
104
75
  try:
105
- buys = trade.get_current_buys()
106
- if buys is not None:
107
- logger.info(
108
- f"Current buy positions SYMBOL={trade.symbol}: {buys}, STRATEGY=SMA")
109
- sells = trade.get_current_sells()
110
- if sells is not None:
111
- logger.info(
112
- f"Current sell positions SYMBOL={trade.symbol}: {sells}, STRATEGY=SMA")
113
- long_market = buys is not None and len(buys) >= max_t
114
- short_market = sells is not None and len(sells) >= max_t
115
-
76
+ _check_mt5_connection()
77
+ current_date = datetime.now()
78
+ today = current_date.strftime("%A").lower()
116
79
  time.sleep(0.5)
117
- sig_rate = Rates(trade.symbol, tf, 0, lma)
118
- hmm_data = sig_rate.get_returns.values
119
- current_regime = hmm.which_trade_allowed(hmm_data)
120
- logger.info(f'CURRENT REGIME = {current_regime}, SYMBOL={trade.symbol}, STRATEGY=SMA')
121
- ma_data = sig_rate.get_close.values
122
- signal = strategy.calculate_signals(ma_data)
123
- logger.info(f"Calculating signal...SYMBOL={trade.symbol}, STRATEGY=SMA")
124
- comment = f"{trade.expert_name}@{trade.version}"
125
- if trade.trading_time() and today in TRADING_DAYS:
126
- if signal is not None:
127
- logger.info(f"SIGNAL = {signal}, SYMBOL={trade.symbol}, STRATEGY=SMA")
128
- if signal == "EXIT" and short_market:
129
- trade.close_positions(position_type='sell')
130
- short_market = False
131
- elif signal == "EXIT" and long_market:
132
- trade.close_positions(position_type='buy')
133
- long_market = False
134
-
135
- if current_regime == 'LONG':
136
- if signal == "LONG" and not long_market:
137
- if time_intervals % trade_time == 0 or buys is None:
138
- logger.info(f"Sending buy Order .... SYMBOL={trade.symbol}, STRATEGY=SMA")
80
+ buys = {
81
+ symbol: trades_instances[symbol].get_current_buys()
82
+ for symbol in symbols
83
+ }
84
+ sells = {
85
+ symbol: trades_instances[symbol].get_current_sells()
86
+ for symbol in symbols
87
+ }
88
+ for symbol in symbols:
89
+ if buys[symbol] is not None:
90
+ logger.info(
91
+ f"Current buy positions SYMBOL={symbol}: {buys[symbol]}, STRATEGY={STRATEGY}")
92
+ if sells[symbol] is not None:
93
+ logger.info(
94
+ f"Current sell positions SYMBOL={symbol}: {sells[symbol]}, STRATEGY={STRATEGY}")
95
+ long_market = {symbol: buys[symbol] is not None and len(
96
+ buys[symbol]) >= max_trades[symbol] for symbol in symbols}
97
+ short_market = {symbol: sells[symbol] is not None and len(
98
+ sells[symbol]) >= max_trades[symbol] for symbol in symbols}
99
+ except Exception as e:
100
+ logger.error(f"{e}, STRATEGY={STRATEGY}")
101
+ time.sleep(0.5)
102
+ for symbol in symbols:
103
+ try:
104
+ trade = trades_instances[symbol]
105
+ logger.info(
106
+ f"Calculating signal... SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
107
+ signal = strategy.calculate_signals()[symbol]
108
+ if trade.trading_time() and today in trading_days:
109
+ if signal is not None:
110
+ logger.info(
111
+ f"SIGNAL = {signal}, SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
112
+ if signal in ("EXIT", "EXIT_LONG") and long_market[symbol]:
113
+ trade.close_positions(position_type='buy')
114
+ elif signal in ("EXIT", "EXIT_SHORT") and short_market[symbol]:
115
+ trade.close_positions(position_type='sell')
116
+ elif signal == "LONG" and not long_market[symbol]:
117
+ if time_intervals % trade_time == 0 or buys[symbol] is None:
118
+ logger.info(
119
+ f"Sending buy Order ... SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
139
120
  trade.open_buy_position(mm=mm, comment=comment)
140
121
  else:
141
- check(buys, sells)
142
- elif signal == "LONG" and long_market:
143
- logger.info(f"Sorry Risk not allowed !!! SYMBOL={trade.symbol}, STRATEGY=SMA")
144
- check(buys, sells)
145
-
146
- elif current_regime == 'SHORT':
147
- if signal == "SHORT" and not short_market:
148
- if time_intervals % trade_time == 0 or sells is None:
149
- logger.info(f"Sending Sell Order .... SYMBOL={trade.symbol}, STRATEGY=SMA")
122
+ check(buys[symbol], sells[symbol], symbol)
123
+ elif signal == "LONG" and long_market[symbol]:
124
+ logger.info(
125
+ f"Sorry Risk not allowed !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
126
+ check(buys[symbol], sells[symbol], symbol)
127
+
128
+ elif signal == "SHORT" and not short_market[symbol]:
129
+ if time_intervals % trade_time == 0 or sells[symbol] is None:
130
+ logger.info(
131
+ f"Sending sell Order ... SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
150
132
  trade.open_sell_position(
151
133
  mm=mm, comment=comment)
152
134
  else:
153
- check(buys, sells)
154
- elif signal == "SHORT" and short_market:
155
- logger.info(f"Sorry Risk not Allowed !!! SYMBOL={trade.symbol}, STRATEGY=SMA")
156
- check(buys, sells)
157
- else:
158
- logger.info(f"There is no signal !! SYMBOL={trade.symbol}, STRATEGY=SMA")
159
- check(buys, sells)
160
- else:
161
- logger.info(f"Sorry It is Not trading Time !!! SYMBOL={trade.symbol}, STRATEGY=SMA")
162
- check(buys, sells)
163
- except Exception as e:
164
- logger.error(f"{e}, SYMBOL={trade.symbol}, STRATEGY=SMA")
165
- time.sleep((60 * iter_time) - 1.5)
166
- if iter_time == 1:
167
- time_intervals += 1
168
- elif trade_time % iter_time == 0:
169
- time_intervals += iter_time
170
- else:
171
- raise ValueError(
172
- f"iter_time must be a multiple of the {tf} !!!"
173
- f"(e.g; if time_frame is 15m, iter_time must be 1.5, 3, 3, 15 etc)"
174
- )
175
- if period.lower() == 'month':
176
- if trade.days_end() and today != 'Friday':
177
- sleep_time = trade.sleep_time()
178
- logger.info(f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY=SMA")
179
- time.sleep(60 * sleep_time)
180
- num_days += 1
181
-
182
- elif trade.days_end() and today == 'Friday':
183
- logger.info(f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY=SMA")
184
- sleep_time = trade.sleep_time(weekend=True)
185
- time.sleep(60 * sleep_time)
186
- num_days += 1
187
-
188
- elif (
189
- trade.days_end()
190
- and today == 'Friday'
191
- and num_days >= 20
192
- ):
193
- trade.close_positions(position_type='all', comment=comment)
194
- logger.info(f"End of the Month !!! SYMBOL={trade.symbol}, STRATEGY=SMA")
195
- trade.statistics(save=True)
196
- break
197
-
198
- elif period.lower() == 'week':
199
- if trade.days_end() and today != 'Friday':
200
- sleep_time = trade.sleep_time()
201
- time.sleep(60 * sleep_time)
202
-
203
- elif trade.days_end() and today == 'Friday':
204
- trade.close_positions(position_type='all', comment=comment)
205
- logger.info(f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY=SMA")
206
- trade.statistics(save=True)
207
- break
208
-
209
- elif period.lower() == 'day':
210
- if trade.days_end():
211
- trade.close_positions(position_type='all', comment=comment)
212
- logger.info(f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY=SMA")
213
- trade.statistics(save=True)
214
- break
215
-
216
-
217
- # ========= PAIR TRADING =====================
218
- def pair_trading(
219
- pair: List[str] | Tuple[str],
220
- p0: Trade,
221
- p1: Trade,
222
- tf: str,
223
- /,
224
- max_t: Optional[int] = 1,
225
- mm: Optional[bool] = True,
226
- iter_time: Optional[int | float] = 30,
227
- risk_manager: Optional[str] = None,
228
- rm_ticker: Optional[str] = None,
229
- rm_window: Optional[int] = None,
230
- period: Literal['day', 'week', 'month'] = 'month',
231
- **kwargs
232
- ):
233
- """
234
- Implements a pair trading strategy with optional risk management
235
- using Hidden Markov Models (HMM). This strategy trades pairs of assets
236
- based on their historical price relationship, seeking to capitalize on converging prices.
237
-
238
- :param pair (list[str] | tuple[str]): The trading pair represented as a list or tuple of symbols (e.g., ['AAPL', 'GOOG']).
239
- :param p0 (Trade): Trade object for the first asset in the pair.
240
- :param p1 (Trade): Trade object for the second asset in the pair.
241
- :param tf (str): Time frame for the trading strategy (e.g., '1h' for 1 hour).
242
- :param max_t (int, optional): Maximum number of trades allowed at any time for each asset in the pair, defaults to 1.
243
-
244
- :param mm (bool, optional): Money management flag to enable/disable money management, defaults to True.
245
- :param iter_time (int | float ,optional): Iteration time (in minutes) for the trading loop, defaults to 30.
246
- :param risk_manager: Specifies the risk management model to use default is None , Hidden Markov Model ('hmm) Can be use.
247
- :param rm_window: Window size for the risk model use for the prediction, defaults to None. Must be specified if `risk_manager` is not None.
248
-
249
- :param period (str, optional): Trading period to reset statistics and close positions, can be 'day', 'week', or 'month'.
250
- :param kwargs: Additional keyword arguments for HMM risk manager.
251
-
252
- This function continuously evaluates the defined pair for trading opportunities
253
- based on the strategy logic, taking into account the specified risk management
254
- approach if applicable. It aims to profit from the mean reversion behavior typically
255
- observed in closely related financial instruments.
256
-
257
- Note:
258
- This function includes an infinite loop with time delays designed to run continuously during market hours.
259
- Proper exception handling and resource management are crucial for live trading environments.
260
- """
261
- regime = False
262
- if risk_manager is not None:
263
- assert rm_ticker is not None
264
- assert rm_window is not None
265
- regime = True
266
-
267
- def p0_check(p0_positions):
268
- if p0_positions is not None:
269
- logger.info(f"Checking for breakeven on {pair[0]} positions...STRATEGY=KLF")
270
- p0.break_even()
271
-
272
- def p1_check(p1_positions):
273
- if p1_positions is not None:
274
- logger.info(f"Checking for breakeven on {pair[1]} positions...STRATEGY=KLF")
275
- p1.break_even()
276
-
277
- time_frame_mapping = tf_mapping()
278
- if tf == 'D1':
279
- trade_time = p0.get_minutes()
280
- else:
281
- trade_time = time_frame_mapping[tf]
282
-
283
- if regime:
284
- if risk_manager == 'hmm':
285
- rate = Rates(rm_ticker, tf, 0)
286
- data = rate.get_rates_from_pos()
287
- hmm = HMMRiskManager(data=data, verbose=True, iterations=5000, **kwargs)
288
-
289
- time_intervals = 0
290
- long_market = False
291
- short_market = False
292
- num_days = 0
293
- logger.info(
294
- f'Running KLF Strategy on {pair[0]} and {pair[1]} in {tf} Interval ...\n')
295
- while True:
296
- current_date = datetime.now()
297
- today = current_date.strftime("%A")
298
- try:
299
- # Data Retrieval
300
- p0_ = Rates(pair[0], tf, 0, 10)
301
- p1_ = Rates(pair[1], tf, 0, 10)
302
-
303
- p0_data = p0_.get_close
304
- p1_data = p1_.get_close
305
- prices = np.array(
306
- [p0_data.values[-1], p1_data.values[-1]]
307
- )
308
- strategy = KLFStrategy(pair)
309
- if regime:
310
- if risk_manager == 'hmm':
311
- hmm_data = Rates(rm_ticker, tf, 0, rm_window)
312
- returns = hmm_data.get_returns.values
313
- current_regime = hmm.which_trade_allowed(returns)
314
- logger.info(f'CURRENT REGIME ={current_regime}, STRATEGY=KLF')
315
- else:
316
- current_regime = None
317
-
318
- p0_positions = p0.get_current_open_positions()
319
- time.sleep(0.5)
320
- p1_positions = p1.get_current_open_positions()
321
- time.sleep(0.5)
322
- p1_buys = p1.get_current_buys()
323
- p0_buys = p0.get_current_buys()
324
- time.sleep(0.5)
325
- if p1_buys is not None:
326
- logger.info(f"Current buy positions on {pair[1]}: {p1_buys}, STRATEGY=KLF")
327
- if p0_buys is not None:
328
- logger.info(f"Current buy positions on {pair[0]}: {p0_buys}, STRATEGY=KLF")
329
- time.sleep(0.5)
330
- p1_sells = p1.get_current_sells()
331
- p0_sells = p0.get_current_sells()
332
- time.sleep(0.5)
333
- if p1_sells is not None:
334
- logger.info(f"Current sell positions on {pair[1]}: {p1_sells}, STRATEGY=KLF")
335
- if p0_sells is not None:
336
- logger.info(f"Current sell positions on {pair[0]}: {p0_sells}, STRATEGY=KLF")
337
-
338
- p1_long_market = p1_buys is not None and len(p1_buys) >= max_t
339
- p0_long_market = p0_buys is not None and len(p0_buys) >= max_t
340
- p1_short_market = p1_sells is not None and len(p1_sells) >= max_t
341
- p0_short_market = p0_sells is not None and len(p0_sells) >= max_t
342
-
343
- logger.info(f"Calculating Signals SYMBOL={pair}...STRATEGY=KLF")
344
- signals = strategy.calculate_signals(prices)
345
- comment = f"{p0.expert_name}@{p0.version}"
346
-
347
- if signals is not None:
348
- logger.info(f'SIGNALS = {signals}, STRATEGY=KLF')
349
- if p0.trading_time() and today in TRADING_DAYS:
350
- p1_signal = signals[pair[1]]
351
- p0_signal = signals[pair[0]]
352
- if p1_signal == "EXIT" and p0_signal == "EXIT":
353
- if p1_positions is not None:
354
- logger.info(f"Exiting Positions On [{pair[1]}], STRATEGY=KLF")
355
- p1.close_positions(position_type='all', comment=comment)
356
- p1_long_market = False
357
- p1_short_market = False
358
- if p0_positions is not None:
359
- logger.info(f"Exiting Positions On [{pair[0]}], STRATEGY=KLF")
360
- p0.close_positions(position_type='all', comment=comment)
361
- p1_long_market = False
362
- p1_short_market = False
363
- if current_regime is not None:
364
- if (
365
- p1_signal == "LONG"
366
- and p0_signal == "SHORT"
367
- and current_regime == 'LONG'
368
- ):
369
- if not p1_long_market:
370
- if time_intervals % trade_time == 0 or p1_buys is None:
371
- logger.info(f"Going LONG on [{pair[1]}], STRATEGY=KLF")
372
- p1.open_buy_position(
373
- mm=mm, comment=comment)
374
- else:
375
- p1_check(p1_positions)
376
- else:
377
- logger.info(f"Sorry Risk Not allowed on [{pair[1]}], STRATEGY=KLF")
378
- p1_check(p1_positions)
379
-
380
- if not p0_short_market:
381
- if time_intervals % trade_time == 0 or p0_sells is None:
382
- logger.info(f"Going SHORT on [{pair[0]}]")
383
- p0.open_sell_position(
384
- mm=mm, comment=comment)
385
- else:
386
- p0_check(p0_positions)
387
- else:
388
- logger.info(
389
- f"Sorry Risk Not allowed on [{pair[0]}], STRATEGY=KLF")
390
- p0_check(p0_positions)
391
- elif (
392
- p1_signal == "SHORT"
393
- and p0_signal == "LONG"
394
- and current_regime == 'SHORT'
395
- ):
396
- if not p1_short_market:
397
- if time_intervals % trade_time == 0 or p1_sells is None:
398
- logger.info(f"Going SHORT on [{pair[1]}], STRATEGY=KLF")
399
- p1.open_sell_position(
400
- mm=mm, comment=comment)
401
- else:
402
- p1_check(p1_positions)
403
- else:
404
- logger.info(f"Sorry Risk Not allowed on [{pair[1]}], STRATEGY=KLF")
405
- p1_check(p1_positions)
406
-
407
- if not p0_long_market:
408
- if time_intervals % trade_time == 0 or p0_buys is None:
409
- logger.info(f"Going LONG on [{pair[0]}], STRATEGY=KLF")
410
- p0.open_buy_position(
411
- mm=mm, comment=comment)
412
- else:
413
- p0_check(p0_positions)
414
- else:
415
- logger.info(
416
- f"Sorry Risk Not allowed on [{pair[0]}], STRATEGY=KLF")
417
- p0_check(p0_positions)
135
+ check(buys[symbol], sells[symbol], symbol)
136
+ elif signal == "SHORT" and short_market[symbol]:
137
+ logger.info(
138
+ f"Sorry Risk not allowed !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
139
+ check(buys[symbol], sells[symbol], symbol)
418
140
  else:
419
- if (
420
- p1_signal == "LONG"
421
- and p0_signal == "SHORT"
422
- ):
423
- if not p1_long_market:
424
- if time_intervals % trade_time == 0 or p1_buys is None:
425
- logger.info(f"Going LONG on [{pair[1]}], STRATEGY=KLF")
426
- p1.open_buy_position(
427
- mm=mm, comment=comment)
428
- else:
429
- p1_check(p1_positions)
430
- else:
431
- logger.info(f"Sorry Risk Not allowed on [{pair[1]}], STRATEGY=KLF")
432
- p1_check(p1_positions)
433
-
434
- if not p0_short_market:
435
- if time_intervals % trade_time == 0 or p0_sells is None:
436
- logger.info(f"Going SHORT on [{pair[0]}], STRATEGY=KLF")
437
- p0.open_sell_position(
438
- mm=mm, comment=comment)
439
- else:
440
- p0_check(p0_positions)
441
- else:
442
- logger.info(
443
- f"Sorry Risk Not allowed on [{pair[0]}], STRATEGY=KLF")
444
- p0_check(p0_positions)
445
- elif (
446
- p1_signal == "SHORT"
447
- and p0_signal == "LONG"
448
- ):
449
- if not p1_short_market:
450
- if time_intervals % trade_time == 0 or p1_sells is None:
451
- logger.info(f"Going SHORT on [{pair[1]}], STRATEGY=KLF")
452
- p1.open_sell_position(
453
- mm=mm, comment=comment)
454
- else:
455
- p1_check(p1_positions)
456
- else:
457
- logger.info(f"Sorry Risk Not allowed on [{pair[1]}], STRATEGY=KLF")
458
- p1_check(p1_positions)
459
-
460
- if not p0_long_market:
461
- if time_intervals % trade_time == 0 or p0_buys is None:
462
- logger.info(f"Going LONG on [{pair[0]}], STRATEGY=KLF")
463
- p0.open_buy_position(
464
- mm=mm, comment=comment)
465
- else:
466
- p0_check(p0_positions)
467
- else:
468
- logger.info(
469
- f"Sorry Risk Not allowed on [{pair[0]}], STRATEGY=KLF")
470
- p0_check(p0_positions)
141
+ logger.info(
142
+ f"There is no signal !! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
143
+ check(buys[symbol], sells[symbol], symbol)
471
144
  else:
472
145
  logger.info(
473
- f"It is Not trading time !!! STRATEGY=KLF, SYMBOLS={pair}")
474
- p0_check(p0_positions)
475
- p1_check(p1_positions)
476
- else:
477
- logger.info(
478
- f"There is no signal !!! STRATEGY=KLF, SYMBOLS={pair}")
479
-
480
- p0_check(p0_positions)
481
- p1_check(p1_positions)
482
-
483
- except Exception as e:
484
- logger.error(f"{e}, STRATEGY=KLF, SYMBOLS={pair}")
146
+ f"Sorry It is Not trading Time !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
147
+ check(buys[symbol], sells[symbol], symbol)
485
148
 
486
- time.sleep((60 * iter_time) - 2.5)
149
+ except Exception as e:
150
+ logger.error(f"{e}, SYMBOL={symbol}, STRATEGY={STRATEGY}")
487
151
 
152
+ time.sleep((60 * iter_time) - 1.0)
488
153
  if iter_time == 1:
489
154
  time_intervals += 1
490
155
  elif trade_time % iter_time == 0:
491
156
  time_intervals += iter_time
492
157
  else:
493
158
  raise ValueError(
494
- f"iter_time must be a multiple of the {tf} !!!"
159
+ f"iter_time must be a multiple of the {time_frame} !!!"
495
160
  f"(e.g; if time_frame is 15m, iter_time must be 1.5, 3, 3, 15 etc)"
496
161
  )
497
-
498
- if period.lower() == 'month':
499
- if p0.days_end() and today != 'Friday':
500
- logger.info(
501
- f"End of the Day !!! STRATEGY=KLF, SYMBOLS={pair}")
502
-
503
- sleep_time = p0.sleep_time()
504
- time.sleep(60 * sleep_time)
505
- num_days += 1
506
-
507
- elif p0.days_end() and today == 'Friday':
508
- logger.info(
509
- f"End of the Week !!! STRATEGY=KLF, SYMBOLS={pair}")
510
- sleep_time = p0.sleep_time(weekend=True)
511
- time.sleep(60 * sleep_time)
512
- num_days += 1
513
-
514
- elif (
515
- p0.days_end()
516
- and today == 'Friday'
517
- and num_days >= 20
518
- ):
519
- p0.close_positions(position_type='all', comment=comment)
520
- p1.close_positions(position_type='all', comment=comment)
521
- logger.info(
522
- f"End of the Month !!! STRATEGY=KLF, SYMBOLS={pair}")
523
- p0.statistics(save=True)
524
- p1.statistics(save=True)
162
+ print()
163
+ if period.lower() == 'day':
164
+ for symbol in symbols:
165
+ trade = trades_instances[symbol]
166
+ if trade.days_end():
167
+ trade.close_positions(position_type='all', comment=comment)
168
+ logger.info(
169
+ f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
170
+ trade.statistics(save=True)
171
+ if trades_instances[symbols[-1]].days_end():
525
172
  break
526
173
 
527
174
  elif period.lower() == 'week':
528
- if p0.days_end() and today != 'Friday':
529
- sleep_time = p0.sleep_time()
530
- time.sleep(60 * sleep_time)
531
-
532
- elif p0.days_end() and today == 'Friday':
533
- p0.close_positions(position_type='all', comment=comment)
534
- p1.close_positions(position_type='all', comment=comment)
535
- logger.info(
536
- f"End of the Week !!! STRATEGY=KLF, SYMBOLS={pair}")
537
- p0.statistics(save=True)
538
- p1.statistics(save=True)
539
- break
175
+ for symbol in symbols:
176
+ trade = trades_instances[symbol]
177
+ if trade.days_end() and today != 'friday':
178
+ logger.info(
179
+ f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
540
180
 
541
- elif period.lower() == 'day':
542
- if p0.days_end():
543
- p0.close_positions(position_type='all', comment=comment)
544
- p1.close_positions(position_type='all', comment=comment)
545
- logger.info(
546
- f"End of the Day !!! STRATEGY=KLF, SYMBOLS={pair}")
547
- p0.statistics(save=True)
548
- p1.statistics(save=True)
181
+ elif trade.days_end() and today == 'friday':
182
+ trade.close_positions(position_type='all', comment=comment)
183
+ logger.info(
184
+ f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
185
+ trade.statistics(save=True)
186
+ if trades_instances[symbols[-1]].days_end() and today != 'friday':
187
+ sleep_time = trades_instances[symbols[-1]].sleep_time()
188
+ logger.info(f"Sleeping for {sleep_time} minutes ...")
189
+ time.sleep(60 * sleep_time)
190
+ logger.info("\nSTARTING NEW TRADING SESSION ...")
191
+ elif trades_instances[symbols[-1]].days_end() and today == 'friday':
549
192
  break
550
193
 
551
-
552
- # ========= ORNSTEIN UHLENBECK TRADING ========
553
- def ou_trading(
554
- trade: Trade,
555
- tf: Optional[str] = '1h',
556
- p: Optional[int] = 20,
557
- n: Optional[int] = 20,
558
- ou_window: Optional[int] = 2000,
559
- max_t: Optional[int] = 1,
560
- mm: Optional[bool] = True,
561
- iter_time: Optional[int | float] = 30,
562
- risk_manager: Optional[str] = None,
563
- rm_window: Optional[int] = None,
564
- period: Literal['day', 'week', 'month'] = 'month',
565
- **kwargs
566
- ):
567
- """
568
- Executes the Ornstein-Uhlenbeck (OU) trading strategy,
569
- incorporating various risk management and trading frequency adjustments.
570
-
571
- :param trade: A `Trade` instance, containing methods and attributes for executing trades.
572
- :param tf: Time frame for the trading strategy, default is '1h'.
573
- :param mm: Boolean indicating if money management is enabled, default is True.
574
- :param max_t: Maximum number of trades allowed at any given time, default is 1.
575
- :param p: Period length for calculating returns, default is 20.
576
- :param n: Window size for the Ornstein-Uhlenbeck strategy calculation, default is 20.
577
- :param iter_time: Iteration time for the trading loop, can be an integer or float.
578
- :param ou_window: Lookback period for the OU strategy, defaults to 2000.
579
- :param risk_manager: Specifies the risk management model to use
580
- default is None , Hidden Markov Model ('hmm) Can be use.
581
- :param rm_window: Window size for the risk model use for the prediction, defaults to None.
582
- Must be specified if `risk_manager` is not None.
583
- :param period: Defines the trading period as 'month', 'week', or 'day'
584
- affecting how and when positions are closed.
585
- :param kwargs: Additional keyword arguments for risk management models or other customizations.
586
-
587
- This function manages trading based on the OU strategy, adjusting for risk and time-based criteria.
588
- It includes handling of trading sessions, buy/sell signal generation, risk management through the HMM model, and period-based
589
- trading evaluation.
590
- """
591
- regime = False
592
- if risk_manager is not None:
593
- if risk_manager.lower() == 'hmm':
594
- assert rm_window is not None
595
- regime = True
596
-
597
- rate = Rates(trade.symbol, tf, 0)
598
- data = rate.get_rates_from_pos()
599
- def check(buys: list, sells: list):
600
- if buys is not None or sells is not None:
601
- logger.info(f"Checking for Break even on {trade.symbol}... STRATEGY=OU")
602
- trade.break_even()
603
-
604
- time_frame_mapping = tf_mapping()
605
- if tf == 'D1':
606
- trade_time = trade.get_minutes()
607
- else:
608
- trade_time = time_frame_mapping[tf]
609
-
610
- if regime:
611
- if risk_manager == 'hmm':
612
- hmm = HMMRiskManager(data=data, verbose=True, **kwargs)
613
- strategy = OrnsteinUhlenbeck(data['Close'].values[-ou_window:], timeframe=tf)
614
-
615
- time_intervals = 0
616
- long_market = False
617
- short_market = False
618
- num_days = 0
619
- logger.info(f'Running OU Strategy on {trade.symbol} in {tf} Interval ...\n')
620
- while True:
621
- current_date = datetime.now()
622
- today = current_date.strftime("%A")
623
- try:
624
- buys = trade.get_current_buys()
625
- if buys is not None:
626
- logger.info(f"Current buy positions on {trade.symbol}: {buys}, STRATEGY=OU")
627
- sells = trade.get_current_sells()
628
- if sells is not None:
629
- logger.info(f"Current sell positions on {trade.symbol}: {sells}, STRATEGY=OU")
630
- long_market = buys is not None and len(buys) >= max_t
631
- short_market = sells is not None and len(sells) >= max_t
632
-
633
- time.sleep(0.5)
634
- if regime:
635
- if risk_manager == 'hmm':
636
- hmm_returns = Rates(trade.symbol, tf, 0, rm_window)
637
- hmm_returns_val = hmm_returns.get_returns.values
638
- current_regime = hmm.which_trade_allowed(hmm_returns_val)
194
+ elif period.lower() == 'month':
195
+ for symbol in symbols:
196
+ trade = trades_instances[symbol]
197
+ if trade.days_end() and today != 'friday':
639
198
  logger.info(
640
- f'CURRENT REGIME = {current_regime}, SYMBOL={trade.symbol}, STRATEGY=OU')
641
- else:
642
- current_regime = None
643
- logger.info(f"Calculating signal... SYMBOL={trade.symbol}, STRATEGY=OU")
644
- ou_returns = Rates(trade.symbol, tf, 0, p)
645
- returns_val = ou_returns.get_returns.values
646
- signal = strategy.calculate_signals(returns_val, p=p, n=n)
647
- comment = f"{trade.expert_name}@{trade.version}"
648
- if trade.trading_time() and today in TRADING_DAYS:
649
- if signal is not None:
650
- logger.info(f"SIGNAL = {signal}, SYMBOL={trade.symbol}, STRATEGY=OU")
651
- if signal == "LONG" and short_market:
652
- trade.close_positions(position_type='sell')
653
- short_market = False
654
- elif signal == "SHORT" and long_market:
655
- trade.close_positions(position_type='buy')
656
- long_market = False
657
- if current_regime is not None:
658
- if current_regime == 'LONG':
659
- if signal == "LONG" and not long_market:
660
- if time_intervals % trade_time == 0 or buys is None:
661
- logger.info(f"Sending buy Order .... SYMBOL={trade.symbol}, STRATEGY=OU")
662
- trade.open_buy_position(
663
- mm=mm, comment=comment)
664
- else:
665
- check(buys, sells)
199
+ f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
666
200
 
667
- elif signal == "LONG" and long_market:
668
- logger.info(f"Sorry Risk not allowed !!! SYMBOL={trade.symbol}, STRATEGY=OU")
669
- check(buys, sells)
670
-
671
- elif current_regime == 'SHORT':
672
- if signal == "SHORT" and not short_market:
673
- if time_intervals % trade_time == 0 or sells is None:
674
- logger.info(f"Sending Sell Order .... SYMBOL={trade.symbol}, STRATEGY=OU")
675
- trade.open_sell_position(
676
- mm=mm, comment=comment)
677
- else:
678
- check(buys, sells)
679
- elif signal == "SHORT" and short_market:
680
- logger.info(f"Sorry Risk not Allowed !!! SYMBOL={trade.symbol}, STRATEGY=OU")
681
- check(buys, sells)
682
- else:
683
- if signal == "LONG" and not long_market:
684
- if time_intervals % trade_time == 0 or buys is None:
685
- logger.info(f"Sending buy Order .... SYMBOL={trade.symbol}, STRATEGY=OU")
686
- trade.open_buy_position(mm=mm, comment=comment)
687
- else:
688
- check(buys, sells)
689
-
690
- elif signal == "LONG" and long_market:
691
- logger.info(f"Sorry Risk not allowed !!! SYMBOL={trade.symbol}, STRATEGY=OU")
692
- check(buys, sells)
693
-
694
- if signal == "SHORT" and not short_market:
695
- if time_intervals % trade_time == 0 or sells is None:
696
- logger.info(f"Sending Sell Order .... SYMBOL={trade.symbol}, STRATEGY=OU")
697
- trade.open_sell_position(
698
- mm=mm, comment=comment)
699
- else:
700
- check(buys, sells)
701
- elif signal == "SHORT" and short_market:
702
- logger.info(f"Sorry Risk not Allowed !!! SYMBOL={trade.symbol}, STRATEGY=OU")
703
- check(buys, sells)
704
- else:
705
- logger.info(f"There is no signal !!! SYMBOL={trade.symbol}, STRATEGY=OU")
706
- check(buys, sells)
707
- else:
708
- logger.info(f"Sorry It is Not trading Time !!! SYMBOL={trade.symbol}, STRATEGY=OU")
709
- check(buys, sells)
710
- except Exception as e:
711
- print(f"{e}, SYMBOL={trade.symbol}, STRATEGY=OU")
712
- time.sleep((60 * iter_time) - 1.5)
713
- if iter_time == 1:
714
- time_intervals += 1
715
- elif trade_time % iter_time == 0:
716
- time_intervals += iter_time
717
- else:
718
- raise ValueError(
719
- f"iter_time must be a multiple of the {tf} !!!"
720
- f"(e.g; if time_frame is 15m, iter_time must be 1.5, 3, 3, 15 etc)"
721
- )
722
-
723
- if period.lower() == 'month':
724
- if trade.days_end() and today != 'Friday':
725
- logger.info(f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY=OU")
726
- sleep_time = trade.sleep_time()
201
+ elif trade.days_end() and today == 'friday':
202
+ logger.info(
203
+ f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
204
+ elif (
205
+ trade.days_end()
206
+ and today == 'friday'
207
+ and num_days/len(symbols) >= 20
208
+ ):
209
+ trade.close_positions(position_type='all', comment=comment)
210
+ logger.info(
211
+ f"End of the Month !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
212
+ trade.statistics(save=True)
213
+ if trades_instances[symbols[-1]].days_end() and today != 'friday':
214
+ sleep_time = trades_instances[symbols[-1]].sleep_time()
215
+ logger.info(f"Sleeping for {sleep_time} minutes ...")
727
216
  time.sleep(60 * sleep_time)
217
+ logger.info("\nSTARTING NEW TRADING SESSION ...")
728
218
  num_days += 1
729
-
730
- elif trade.days_end() and today == 'Friday':
731
- logger.info(f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY=OU")
732
- sleep_time = trade.sleep_time(weekend=True)
219
+ elif trades_instances[symbols[-1]].days_end() and today == 'friday':
220
+ sleep_time = trades_instances[symbols[-1]
221
+ ].sleep_time(weekend=True)
222
+ logger.info(f"Sleeping for {sleep_time} minutes ...")
733
223
  time.sleep(60 * sleep_time)
224
+ logger.info("\nSTARTING NEW TRADING SESSION ...")
734
225
  num_days += 1
735
-
736
- elif (
737
- trade.days_end()
738
- and today == 'Friday'
739
- and num_days >= 20
740
- ):
741
- trade.close_positions(position_type='all', comment=comment)
742
- logger.info(f"End of the Month !!! SYMBOL={trade.symbol}, STRATEGY=OU")
743
- trade.statistics(save=True)
226
+ elif (trades_instances[symbols[-1]].days_end()
227
+ and today == 'friday'
228
+ and num_days/len(symbols) >= 20
229
+ ):
744
230
  break
745
231
 
746
- elif period.lower() == 'week':
747
- if trade.days_end() and today != 'Friday':
748
- sleep_time = trade.sleep_time()
749
- time.sleep(60 * sleep_time)
750
232
 
751
- elif trade.days_end() and today == 'Friday':
752
- trade.close_positions(position_type='all', comment=comment)
753
- logger.info(f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY=OU")
754
- trade.statistics(save=True)
755
- break
233
+ def _tws_execution(*args, **kwargs):
234
+ raise NotImplementedError("TWS Execution is not yet implemented !!!")
756
235
 
757
- elif period.lower() == 'day':
758
- if trade.days_end():
759
- trade.close_positions(position_type='all', comment=comment)
760
- logger.info(f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY=OU")
761
- trade.statistics(save=True)
762
- break
763
-
764
-
765
- # ========= ARIMA + GARCH TRADING =======================
766
- def arch_trading(
767
- trade: Trade,
768
- tf: str = 'D1',
769
- k: int = 500,
770
- max_t: Optional[int] = 1,
771
- mm: Optional[bool] = True,
772
- iter_time: Optional[int | float] = 30,
773
- risk_manager: Optional[str] = None,
774
- rm_window: Optional[int] = None,
775
- period: Literal['day', 'week', 'month'] = 'month',
776
- **kwargs
777
- ):
236
+ _TERMINALS = {
237
+ 'MT5': _mt5_execution,
238
+ 'TWS': _tws_execution
239
+ }
240
+ class ExecutionEngine():
778
241
  """
779
- Executes trading based on the ARCH (Autoregressive Conditional Heteroskedasticity) model, with
780
- the capability to incorporate a risk management strategy, specifically a Hidden Markov Model (HMM),
781
- to adjust trading decisions based on the market regime.
782
-
783
- :param trade: A `Trade` instance, necessary for executing trades and managing positions.
784
- :param tf: Time frame for the trading data, default is 'D1' (daily).
785
- :param k: Number of past points to consider for the ARCH model analysis, default is 500.
786
- :param mm: Boolean flag indicating if money management strategies should be applied, default is True.
787
- :param max_t: Maximum number of trades allowed at any given time, default is 1.
788
- :param iter_time: Time in minutes between each iteration of the trading loop. Can be an integer or float.
789
- :param risk_manager: Specifies the risk management model to use. Default is None.
790
- :param rm_window: Window size for the risk model use for the prediction, defaults to None.
791
- Must be specified if `risk_manager` is not None.
792
- :param period: Trading period to consider for closing positions, options are 'month', 'week', or 'day'.
793
- This affects the frequency at which statistics are calculated and positions are closed.
794
- :param kwargs: Additional keyword arguments for the risk management models or other strategy-specific settings.
795
-
796
- This function is designed to perform trading based on ARCH model predictions, managing risk using an HMM where
797
- applicable, and handling trade executions and position management based on the specified parameters. It includes
798
- considerations for trading times, money management, and periodic evaluation of trading performance.
242
+ The `ExecutionEngine` class serves as the central hub for executing your trading strategies within the `bbstrader` framework.
243
+ It orchestrates the entire trading process, ensuring seamless interaction between your strategies, market data, and your chosen
244
+ trading platform (currently MetaTrader 5 (MT5) and Interactive Brokers TWS).
245
+
246
+ Key Features
247
+ ------------
248
+
249
+ - **Strategy Execution:** The `ExecutionEngine` is responsible for running your strategy, retrieving signals, and executing trades based on those signals.
250
+ - **Time Management:** You can define a specific time frame for your trades and set the frequency with which the engine checks for signals and manages trades.
251
+ - **Trade Period Control:** Define whether your strategy runs for a day, a week, or a month, allowing for flexible trading durations.
252
+ - **Money Management:** The engine supports optional money management features, allowing you to control risk and optimize your trading performance.
253
+ - **Trading Day Configuration:** You can customize the days of the week your strategy will execute, providing granular control over your trading schedule.
254
+ - **Platform Integration:** The `ExecutionEngine` is currently designed to work with both MT5 and TWS platforms, ensuring compatibility and flexibility in your trading environment.
255
+
256
+ Examples
257
+ --------
258
+
259
+ >>> from bbstrader.metatrader import create_trade_instance
260
+ >>> from bbstrader.trading.execution import ExecutionEngine
261
+ >>> from bbstrader.trading.strategies import StockIndexCFDTrading
262
+ >>> from bbstrader.metatrader.utils import config_logger
263
+ >>>
264
+ >>> if __name__ == '__main__':
265
+ >>> logger = config_logger(index_trade.log, console_log=True)
266
+ >>> # Define symbols
267
+ >>> ndx = '[NQ100]'
268
+ >>> spx = '[SP500]'
269
+ >>> dji = '[DJI30]'
270
+ >>> dax = 'GERMANY40'
271
+ >>>
272
+ >>> symbol_list = [spx, dax, dji, ndx]
273
+ >>>
274
+ >>> trade_kwargs = {
275
+ ... 'expert_id': 5134,
276
+ ... 'version': 2.0,
277
+ ... 'time_frame': '15m',
278
+ ... 'var_level': 0.99,
279
+ ... 'start_time': '8:30',
280
+ ... 'finishing_time': '19:30',
281
+ ... 'ending_time': '21:30',
282
+ ... 'max_risk': 5.0,
283
+ ... 'daily_risk': 0.10,
284
+ ... 'pchange_sl': 1.5,
285
+ ... 'rr': 3.0,
286
+ ... 'logger': logger
287
+ ... }
288
+ >>> strategy_kwargs = {
289
+ ... 'max_trades': {ndx: 3, spx: 3, dji: 3, dax: 3},
290
+ ... 'expected_returns': {ndx: 1.5, spx: 1.5, dji: 1.0, dax: 1.0},
291
+ ... 'strategy_name': 'SISTBO',
292
+ ... 'logger': logger,
293
+ ... 'expert_id': 5134
294
+ ... }
295
+ >>> trades_instances = create_trade_instance(
296
+ ... symbol_list, trade_kwargs,
297
+ ... logger=logger,
298
+ ... )
299
+ >>>
300
+ >>> engine = ExecutionEngine(
301
+ ... symbol_list,
302
+ ... trades_instances,
303
+ ... StockIndexCFDTrading,
304
+ ... time_frame='15m',
305
+ ... iter_time=5,
306
+ ... mm=True,
307
+ ... period='week',
308
+ ... comment='bbs_SISTBO_@2.0',
309
+ ... **strategy_kwargs
310
+ ... )
311
+ >>> engine.run(terminal='MT5')
799
312
  """
800
- regime = False
801
- if risk_manager is not None:
802
- if risk_manager.lower() == 'hmm':
803
- assert rm_window is not None
804
- regime = True
805
-
806
- def check(buys: list, sells: list):
807
- if buys is not None or sells is not None:
808
- logger.info(f"Checking for Break even on {trade.symbol}...")
809
- trade.break_even()
810
-
811
- time_frame_mapping = tf_mapping()
812
- if tf == 'D1':
813
- trade_time = trade.get_minutes()
814
- else:
815
- trade_time = time_frame_mapping[tf]
816
-
817
- rate = Rates(trade.symbol, tf, 0)
818
- data = rate.get_rates_from_pos()
819
- strategy = ArimaGarchStrategy(trade.symbol, data, k=k)
820
- if regime:
821
- if risk_manager == 'hmm':
822
- hmm = HMMRiskManager(data=data, verbose=True, iterations=5000, **kwargs)
823
-
824
- time_intervals = 0
825
- long_market = False
826
- short_market = False
827
- num_days = 0
828
- logger.info(
829
- f'Running ARIMA + GARCH Strategy on {trade.symbol} in {tf} Interval ...\n')
830
- while True:
831
- current_date = datetime.now()
832
- today = current_date.strftime("%A")
833
- try:
834
- buys = trade.get_current_buys()
835
- if buys is not None:
836
- logger.info(f"Current buy positions on {trade.symbol}: {buys}, STRATEGY=ARCH")
837
- sells = trade.get_current_sells()
838
- if sells is not None:
839
- logger.info(f"Current sell positions on {trade.symbol}: {sells}, STRATEGY=ARCH")
840
- long_market = buys is not None and len(buys) >= max_t
841
- short_market = sells is not None and len(sells) >= max_t
842
-
843
- time.sleep(0.5)
844
- if regime:
845
- if risk_manager == 'hmm':
846
- hmm_returns = Rates(trade.symbol, tf, 0, rm_window)
847
- hmm_returns_val = hmm_returns.get_returns.values
848
- current_regime = hmm.which_trade_allowed(hmm_returns_val)
849
- logger.info(f'CURRENT REGIME = {current_regime}, SYMBOL={trade.symbol}, STRATEGY=ARCH')
850
- else:
851
- current_regime = None
852
- logger.info(f"Calculating Signal ... SYMBOL={trade.symbol}, STRATEGY=ARCH")
853
- arch_data = Rates(trade.symbol, tf, 0, k)
854
- rates = arch_data.get_rates_from_pos()
855
- arch_returns = strategy.load_and_prepare_data(rates)
856
- window_data = arch_returns['diff_log_return'].iloc[-k:]
857
- signal = strategy.calculate_signals(window_data)
858
-
859
- comment = f"{trade.expert_name}@{trade.version}"
860
- if trade.trading_time() and today in TRADING_DAYS:
861
- if signal is not None:
862
- logger.info(f"SIGNAL = {signal}, SYMBOL={trade.symbol}, STRATEGY=ARCH")
863
- if signal == "LONG" and short_market:
864
- trade.close_positions(position_type='sell')
865
- short_market = False
866
- elif signal == "SHORT" and long_market:
867
- trade.close_positions(position_type='buy')
868
- long_market = False
869
- if current_regime is not None:
870
- if current_regime == 'LONG':
871
- if signal == "LONG" and not long_market:
872
- if time_intervals % trade_time == 0 or buys is None:
873
- logger.info(f"Sending buy Order .... SYMBOL={trade.symbol}, STRATEGY=ARCH")
874
- trade.open_buy_position(
875
- mm=mm, comment=comment)
876
- else:
877
- check(buys, sells)
878
-
879
- elif signal == "LONG" and long_market:
880
- logger.info(f"Sorry Risk not allowed !!! SYMBOL={trade.symbol}, STRATEGY=ARCH")
881
- check(buys, sells)
882
-
883
- elif current_regime == 'SHORT':
884
- if signal == "SHORT" and not short_market:
885
- if time_intervals % trade_time == 0 or sells is None:
886
- logger.info(f"Sending Sell Order .... SYMBOL={trade.symbol}, STRATEGY=ARCH")
887
- trade.open_sell_position(
888
- mm=mm, comment=comment)
889
- else:
890
- check(buys, sells)
891
- elif signal == "SHORT" and short_market:
892
- logger.info(f"Sorry Risk not Allowed !!! SYMBOL={trade.symbol}, STRATEGY=ARCH")
893
- check(buys, sells)
894
- else:
895
- if signal == "LONG" and not long_market:
896
- if time_intervals % trade_time == 0 or buys is None:
897
- logger.info(f"Sending buy Order .... SYMBOL={trade.symbol}, STRATEGY=ARCH")
898
- trade.open_buy_position(mm=mm, comment=comment)
899
- else:
900
- check(buys, sells)
901
-
902
- elif signal == "LONG" and long_market:
903
- logger.info(f"Sorry Risk not allowed !!! SYMBOL={trade.symbol}, STRATEGY=ARCH")
904
- check(buys, sells)
905
-
906
- if signal == "SHORT" and not short_market:
907
- if time_intervals % trade_time == 0 or sells is None:
908
- logger.info(f"Sending Sell Order .... SYMBOL={trade.symbol}, STRATEGY=ARCH")
909
- trade.open_sell_position(
910
- mm=mm, comment=comment)
911
- else:
912
- check(buys, sells)
913
-
914
- elif signal == "SHORT" and short_market:
915
- logger.info(f"Sorry Risk not Allowed !!! SYMBOL={trade.symbol}, STRATEGY=ARCH")
916
- check(buys, sells)
917
- else:
918
- logger.info("There is no signal !!")
919
- check(buys, sells)
920
- else:
921
- logger.info(f"Sorry It is Not trading Time !!! SYMBOL={trade.symbol}, STRATEGY=ARCH")
922
- check(buys, sells)
923
313
 
924
- except Exception as e:
925
- print(f"{e}, SYMBOL={trade.symbol}, STRATEGY=ARCH")
926
-
927
- time.sleep((60 * iter_time) - 1.5)
928
- if iter_time == 1:
929
- time_intervals += 1
930
- elif trade_time % iter_time == 0:
931
- time_intervals += iter_time
932
- else:
314
+ def __init__(self,
315
+ symbol_list: List[str],
316
+ trades_instances: Dict[str, Trade],
317
+ strategy_cls: Strategy,
318
+ /,
319
+ mm: Optional[bool] = True,
320
+ time_frame: Optional[str] = '15m',
321
+ iter_time: Optional[int | float] = 5,
322
+ period: Literal['day', 'week', 'month'] = 'week',
323
+ trading_days: Optional[List[str]] = TRADING_DAYS,
324
+ comment: Optional[str] = None,
325
+ **kwargs
326
+ ):
327
+ """
328
+ Args:
329
+ symbol_list : List of symbols to trade
330
+ trades_instances : Dictionary of Trade instances
331
+ strategy_cls : Strategy class to use for trading
332
+ mm : Enable Money Management. Defaults to False.
333
+ time_frame : Time frame to trade. Defaults to '15m'.
334
+ iter_time : Interval to check for signals and `mm`. Defaults to 5.
335
+ period : Period to trade. Defaults to 'week'.
336
+ trading_days : Trading days in a week. Defaults to monday to friday.
337
+ comment: Comment for trades. Defaults to None.
338
+ **kwargs: Additional keyword arguments
339
+ - strategy_name (Optional[str]): Strategy name. Defaults to None.
340
+ - max_trades (Dict[str, int]): Maximum trades per symbol. Defaults to None.
341
+ - logger (Optional[logging.Logger]): Logger instance. Defaults to None.
342
+
343
+ Note:
344
+ 1. All Strategies must inherit from `bbstrader.btengine.strategy.Strategy` class
345
+ and have a `calculate_signals` method that returns a dictionary of signals for each symbol in symbol_list.
346
+
347
+ 2. All strategies must have the following arguments in their `__init__` method:
348
+ - bars (DataHandler): DataHandler instance default to None
349
+ - events (Queue): Queue instance default to None
350
+ - symbol_list (List[str]): List of symbols to trade can be none for backtesting
351
+ - mode (str): Mode of the strategy. Must be either 'live' or 'backtest'
352
+ - **kwargs: Additional keyword arguments
353
+ The keyword arguments are all the additional arguments passed to the `ExecutionEngine` class,
354
+ the `Strategy` class, the `DataHandler` class, the `Portfolio` class and the `ExecutionHandler` class.
355
+ - The `bars` and `events` arguments are used for backtesting only.
356
+
357
+ 3. All strategies must generate signals for backtesting and live trading.
358
+ See the `bbstrader.trading.strategies` module for more information on how to create custom strategies.
359
+ """
360
+ self.symbol_list = symbol_list
361
+ self.trades_instances = trades_instances
362
+ self.strategy_cls = strategy_cls
363
+ self.mm = mm
364
+ self.time_frame = time_frame
365
+ self.iter_time = iter_time
366
+ self.period = period
367
+ self.trading_days = trading_days
368
+ self.comment = comment
369
+ self.kwargs = kwargs
370
+
371
+ def run(self, terminal: Literal['MT5', 'TWS']):
372
+ if terminal not in _TERMINALS:
933
373
  raise ValueError(
934
- f"iter_time must be a multiple of the {tf} !!!"
935
- f"(e.g; if time_frame is 15m, iter_time must be 1.5, 3, 3, 15 etc)"
374
+ f"Invalid terminal: {terminal}. Must be either 'MT5' or 'TWS'")
375
+ _TERMINALS[terminal](
376
+ self.symbol_list,
377
+ self.trades_instances,
378
+ self.strategy_cls,
379
+ mm=self.mm,
380
+ time_frame=self.time_frame,
381
+ iter_time=self.iter_time,
382
+ period=self.period,
383
+ trading_days=self.trading_days,
384
+ comment=self.comment,
385
+ **self.kwargs
936
386
  )
937
-
938
- if period.lower() == 'month':
939
- if trade.days_end() and today != 'Friday':
940
- logger.info(f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY=ARCH")
941
- sleep_time = trade.sleep_time()
942
- time.sleep(60 * sleep_time)
943
- num_days += 1
944
-
945
- elif trade.days_end() and today == 'Friday':
946
- logger.info(f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY=ARCH")
947
- sleep_time = trade.sleep_time(weekend=True)
948
- time.sleep(60 * sleep_time)
949
- num_days += 1
950
-
951
- elif (
952
- trade.days_end()
953
- and today == 'Friday'
954
- and num_days >= 20
955
- ):
956
- trade.close_positions(position_type='all', comment=comment)
957
- logger.info(f"End of the Month !!! SYMBOL={trade.symbol}, STRATEGY=ARCH")
958
- trade.statistics(save=True)
959
- break
960
-
961
- elif period.lower() == 'week':
962
- if trade.days_end() and today != 'Friday':
963
- sleep_time = trade.sleep_time()
964
- time.sleep(60 * sleep_time)
965
-
966
- elif trade.days_end() and today == 'Friday':
967
- trade.close_positions(position_type='all', comment=comment)
968
- logger.info(f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY=ARCH")
969
- trade.statistics(save=True)
970
- break
971
-
972
- elif period.lower() == 'day':
973
- if trade.days_end():
974
- trade.close_positions(position_type='all', comment=comment)
975
- logger.info(f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY=ARCH")
976
- trade.statistics(save=True)
977
- break