bbstrader 0.2.4__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.

@@ -0,0 +1,726 @@
1
+ import time
2
+ import traceback
3
+ import MetaTrader5 as MT5
4
+ from datetime import datetime
5
+ from logging import Logger
6
+ from typing import Dict, List, Literal, Optional
7
+
8
+ from bbstrader.btengine.strategy import MT5Strategy, Strategy
9
+ from bbstrader.metatrader.account import check_mt5_connection
10
+ from bbstrader.metatrader.trade import Trade
11
+ from bbstrader.metatrader.account import Account
12
+ from bbstrader.trading.scripts import send_message
13
+ from bbstrader.core.utils import TradeAction
14
+
15
+ __all__ = ["MT5ExecutionEngine", "TWSExecutionEngine"]
16
+
17
+ _TF_MAPPING = {
18
+ "1m": 1,
19
+ "3m": 3,
20
+ "5m": 5,
21
+ "10m": 10,
22
+ "15m": 15,
23
+ "30m": 30,
24
+ "1h": 60,
25
+ "2h": 120,
26
+ "4h": 240,
27
+ "6h": 360,
28
+ "8h": 480,
29
+ "12h": 720,
30
+ "D1": 1440,
31
+ }
32
+
33
+ TradingDays = ["monday", "tuesday", "wednesday", "thursday", "friday"]
34
+
35
+ BUYS = ["BMKT", "BLMT", "BSTP", "BSTPLMT"]
36
+ SELLS = ["SMKT", "SLMT", "SSTP", "SSTPLMT"]
37
+
38
+ ORDERS_TYPES = [
39
+ "orders",
40
+ "buy_stops",
41
+ "sell_stops",
42
+ "buy_limits",
43
+ "sell_limits",
44
+ "buy_stop_limits",
45
+ "sell_stop_limits",
46
+ ]
47
+ POSITIONS_TYPES = ["positions", "buys", "sells", "profitables", "losings"]
48
+
49
+ ACTIONS = ["buys", "sells"]
50
+ STOPS = ["buy_stops", "sell_stops"]
51
+ LIMITS = ["buy_limits", "sell_limits"]
52
+ STOP_LIMITS = ["buy_stop_limits", "sell_stop_limits"]
53
+
54
+ EXIT_SIGNAL_ACTIONS = {
55
+ "EXIT": {a: a[:-1] for a in ACTIONS},
56
+ "EXIT_LONG": {"buys": "buy"},
57
+ "EXIT_SHORT": {"sells": "sell"},
58
+ "EXIT_STOP": {stop: stop for stop in STOPS},
59
+ "EXIT_LONG_STOP": {"buy_stops": "buy_stops"},
60
+ "EXIT_SHORT_STOP": {"sell_stops": "sell_stops"},
61
+ "EXIT_LIMIT": {limit: limit for limit in LIMITS},
62
+ "EXIT_LONG_LIMIT": {"buy_limits": "buy_limits"},
63
+ "EXIT_SHORT_LIMIT": {"sell_limits": "sell_limits"},
64
+ "EXIT_STOP_LIMIT": {sl: sl for sl in STOP_LIMITS},
65
+ "EXIT_LONG_STOP_LIMIT": {STOP_LIMITS[0]: STOP_LIMITS[0]},
66
+ "EXIT_SHORT_STOP_LIMIT": {STOP_LIMITS[1]: STOP_LIMITS[1]},
67
+ "EXIT_PROFITABLES": {"profitables": "profitable"},
68
+ "EXIT_LOSINGS": {"losings": "losing"},
69
+ "EXIT_ALL_POSITIONS": {"positions": "all"},
70
+ "EXIT_ALL_ORDERS": {"orders": "all"},
71
+ }
72
+
73
+ COMMON_RETCODES = [MT5.TRADE_RETCODE_MARKET_CLOSED, MT5.TRADE_RETCODE_CLOSE_ONLY]
74
+
75
+ NON_EXEC_RETCODES = {
76
+ "BMKT": [MT5.TRADE_RETCODE_SHORT_ONLY] + COMMON_RETCODES,
77
+ "SMKT": [MT5.TRADE_RETCODE_LONG_ONLY] + COMMON_RETCODES,
78
+ }
79
+
80
+
81
+ def _mt5_execution(
82
+ symbol_list,
83
+ trades_instances,
84
+ strategy_cls,
85
+ /,
86
+ mm,
87
+ optimizer,
88
+ trail,
89
+ stop_trail,
90
+ trail_after_points,
91
+ be_plus_points,
92
+ show_positions_orders,
93
+ iter_time,
94
+ use_trade_time,
95
+ period,
96
+ period_end_action,
97
+ closing_pnl,
98
+ trading_days,
99
+ comment,
100
+ **kwargs,
101
+ ):
102
+ def _print_exc(dm, msg):
103
+ traceback.print_exc() if dm else logger.error(msg)
104
+
105
+ try:
106
+ symbols = symbol_list.copy()
107
+ time_frame = kwargs.get("time_frame", "15m")
108
+ STRATEGY = kwargs.get("strategy_name")
109
+ mtrades = kwargs.get("max_trades")
110
+ notify = kwargs.get("notify", False)
111
+ signal_tickers = kwargs.get("signal_tickers", symbols)
112
+ debug_mode = kwargs.get("debug_mode", False)
113
+ if notify:
114
+ telegram = kwargs.get("telegram", False)
115
+ bot_token = kwargs.get("bot_token")
116
+ chat_id = kwargs.get("chat_id")
117
+
118
+ expert_ids = kwargs.get("expert_id")
119
+ if expert_ids is None:
120
+ expert_ids = list(
121
+ set([trade.expert_id for trade in trades_instances.values()])
122
+ )
123
+ elif isinstance(expert_ids, int):
124
+ expert_ids = [expert_ids]
125
+
126
+ logger: Logger = kwargs.get("logger")
127
+ if logger is None:
128
+ logger: Logger = trades_instances[symbols[0]].logger
129
+ max_trades = {
130
+ symbol: mtrades[symbol]
131
+ if mtrades is not None and symbol in mtrades
132
+ else trades_instances[symbol].max_trade()
133
+ for symbol in symbols
134
+ }
135
+ if comment is None:
136
+ trade = trades_instances[symbols[0]]
137
+ comment = f"{trade.expert_name}@{trade.version}"
138
+ except Exception:
139
+ _print_exc(debug_mode, f"Initializing Execution Engine, STRATEGY={STRATEGY}")
140
+ return
141
+
142
+ def update_risk(weights):
143
+ if weights is not None:
144
+ for symbol in symbols:
145
+ if symbol not in weights:
146
+ continue
147
+ trade = trades_instances[symbol]
148
+ trade.dailydd = round(weights[symbol], 5)
149
+
150
+ def check_retcode(trade: Trade, position):
151
+ if len(trade.retcodes) > 0:
152
+ for retcode in trade.retcodes:
153
+ if retcode in NON_EXEC_RETCODES[position]:
154
+ return True
155
+ return False
156
+
157
+ def _send_notification(signal, symbol):
158
+ if symbol in signal_tickers:
159
+ send_message(
160
+ message=signal,
161
+ notify_me=notify,
162
+ telegram=telegram,
163
+ token=bot_token,
164
+ chat_id=chat_id,
165
+ )
166
+
167
+ def check(buys, sells, symbol):
168
+ if not mm:
169
+ return
170
+ if buys is not None or sells is not None:
171
+ trades_instances[symbol].break_even(
172
+ mm=mm,
173
+ trail=trail,
174
+ stop_trail=stop_trail,
175
+ trail_after_points=trail_after_points,
176
+ be_plus_points=be_plus_points,
177
+ )
178
+
179
+ try:
180
+ check_mt5_connection(**kwargs)
181
+ strategy: MT5Strategy = strategy_cls(symbol_list=symbols, mode="live", **kwargs)
182
+ except Exception:
183
+ _print_exc(debug_mode, f"Initializing strategy, STRATEGY={STRATEGY}")
184
+ return
185
+ logger.info(f"Running {STRATEGY} Strategy in {time_frame} Interval ...")
186
+
187
+ def run_trade_algorithm(signal, symbol, id, trade: Trade, price, stoplimit):
188
+ signal = "BMKT" if signal == "LONG" or signal == "BUY" else signal
189
+ signal = "SMKT" if signal == "SHORT" or signal == "SELL" else signal
190
+ info = f"SIGNAL = {signal}, SYMBOL={symbol}, STRATEGY={STRATEGY}, TIMEFRAME={time_frame}"
191
+ account = Account(**kwargs)
192
+ symbol_type = account.get_symbol_type(symbol)
193
+ desc = account.get_symbol_info(symbol).description
194
+ sigmsg = (
195
+ f"SIGNAL = {signal}, SYMBOL={symbol}, TYPE={symbol_type}, DESCRIPTION={desc}, "
196
+ f"PRICE={price}, STOPLIMIT={stoplimit}, STRATEGY={STRATEGY}, TIMEFRAME={time_frame}"
197
+ )
198
+ msg = f"Sending {signal} Order ... SYMBOL={symbol}, STRATEGY={STRATEGY}"
199
+ tfmsg = f"Time Frame Not completed !!! SYMBOL={symbol}, STRATEGY={STRATEGY}"
200
+ riskmsg = f"Risk not allowed !!! SYMBOL={symbol}, STRATEGY={STRATEGY}"
201
+ if signal not in EXIT_SIGNAL_ACTIONS:
202
+ if signal in NON_EXEC_RETCODES and not check_retcode(trade, signal):
203
+ logger.info(info)
204
+ elif signal not in NON_EXEC_RETCODES:
205
+ logger.info(info)
206
+ if signal in EXIT_SIGNAL_ACTIONS:
207
+ for exit_signal, actions in EXIT_SIGNAL_ACTIONS.items():
208
+ for position_type, order_type in actions.items():
209
+ clos_func = getattr(
210
+ trades_instances[symbol], f"get_current_{position_type}"
211
+ )
212
+ if clos_func(id=id) is not None:
213
+ if notify:
214
+ _send_notification(sigmsg, symbol)
215
+ if position_type in POSITIONS_TYPES:
216
+ trade.close_positions(position_type=order_type, id=id)
217
+ else:
218
+ trade.close_orders(order_type=order_type, id=id)
219
+ elif signal in BUYS and not long_market[symbol]:
220
+ if use_trade_time:
221
+ if time_intervals % trade_time == 0 or buys[symbol] is None:
222
+ if notify:
223
+ _send_notification(sigmsg, symbol)
224
+ if not check_retcode(trade, "BMKT"):
225
+ logger.info(msg)
226
+ trade.open_buy_position(
227
+ action=signal,
228
+ price=price,
229
+ stoplimit=stoplimit,
230
+ id=id,
231
+ mm=mm,
232
+ comment=comment,
233
+ )
234
+ else:
235
+ logger.info(tfmsg)
236
+ check(buys[symbol], sells[symbol], symbol)
237
+ else:
238
+ if notify:
239
+ _send_notification(sigmsg, symbol)
240
+ if not check_retcode(trade, "BMKT"):
241
+ logger.info(msg)
242
+ trade.open_buy_position(
243
+ action=signal,
244
+ price=price,
245
+ stoplimit=stoplimit,
246
+ id=id,
247
+ mm=mm,
248
+ comment=comment,
249
+ )
250
+
251
+ elif signal in BUYS and long_market[symbol]:
252
+ logger.info(riskmsg)
253
+
254
+ elif signal in SELLS and not short_market[symbol]:
255
+ if use_trade_time:
256
+ if time_intervals % trade_time == 0 or sells[symbol] is None:
257
+ if notify:
258
+ _send_notification(sigmsg, symbol)
259
+ if not check_retcode(trade, "SMKT"):
260
+ logger.info(msg)
261
+ trade.open_sell_position(
262
+ action=signal,
263
+ price=price,
264
+ stoplimit=stoplimit,
265
+ id=id,
266
+ mm=mm,
267
+ comment=comment,
268
+ )
269
+ else:
270
+ logger.info(tfmsg)
271
+ check(buys[symbol], sells[symbol], symbol)
272
+ else:
273
+ if notify:
274
+ _send_notification(sigmsg, symbol)
275
+ if not check_retcode(trade, "SMKT"):
276
+ logger.info(msg)
277
+ trade.open_sell_position(
278
+ action=signal,
279
+ price=price,
280
+ stoplimit=stoplimit,
281
+ id=id,
282
+ mm=mm,
283
+ comment=comment,
284
+ )
285
+
286
+ elif signal in SELLS and short_market[symbol]:
287
+ logger.info(riskmsg)
288
+ else:
289
+ check(buys[symbol], sells[symbol], symbol)
290
+
291
+ num_days = 0
292
+ time_intervals = 0
293
+ trade_time = _TF_MAPPING[time_frame]
294
+
295
+ long_market = {symbol: False for symbol in symbols}
296
+ short_market = {symbol: False for symbol in symbols}
297
+
298
+ while True:
299
+ try:
300
+ check_mt5_connection(**kwargs)
301
+ current_date = datetime.now()
302
+ today = current_date.strftime("%A").lower()
303
+ time.sleep(0.5)
304
+ positions_orders = {}
305
+ for type in POSITIONS_TYPES + ORDERS_TYPES:
306
+ positions_orders[type] = {}
307
+ for symbol in symbols:
308
+ positions_orders[type][symbol] = None
309
+ for id in expert_ids:
310
+ func = getattr(trades_instances[symbol], f"get_current_{type}")
311
+ func_value = func(id=id)
312
+ if func_value is not None:
313
+ if positions_orders[type][symbol] is None:
314
+ positions_orders[type][symbol] = func(id=id)
315
+ else:
316
+ positions_orders[type][symbol] += func(id=id)
317
+ buys = positions_orders["buys"]
318
+ sells = positions_orders["sells"]
319
+ for symbol in symbols:
320
+ for type in POSITIONS_TYPES + ORDERS_TYPES:
321
+ if positions_orders[type][symbol] is not None:
322
+ if show_positions_orders:
323
+ logger.info(
324
+ f"Current {type.upper()} SYMBOL={symbol}: \
325
+ {positions_orders[type][symbol]}, STRATEGY={STRATEGY}"
326
+ )
327
+ long_market = {
328
+ symbol: buys[symbol] is not None
329
+ and len(buys[symbol]) >= max_trades[symbol]
330
+ for symbol in symbols
331
+ }
332
+ short_market = {
333
+ symbol: sells[symbol] is not None
334
+ and len(sells[symbol]) >= max_trades[symbol]
335
+ for symbol in symbols
336
+ }
337
+ except Exception:
338
+ _print_exc(
339
+ debug_mode, f"Checking positions and orders, STRATEGY={STRATEGY}"
340
+ )
341
+ continue
342
+ time.sleep(0.5)
343
+ try:
344
+ check_mt5_connection(**kwargs)
345
+ signals = strategy.calculate_signals()
346
+ weights = (
347
+ strategy.apply_risk_management(optimizer)
348
+ if hasattr(strategy, "apply_risk_management")
349
+ else None
350
+ )
351
+ update_risk(weights)
352
+ except Exception:
353
+ _print_exc(debug_mode, f"Calculating Signals, STRATEGY={STRATEGY}")
354
+ continue
355
+ if len(signals) == 0:
356
+ for symbol in symbols:
357
+ check(buys[symbol], sells[symbol], symbol)
358
+ else:
359
+ try:
360
+ check_mt5_connection(**kwargs)
361
+ for signal in signals:
362
+ symbol = signal.symbol
363
+ trade: Trade = trades_instances[symbol]
364
+ if trade.trading_time() and today in trading_days:
365
+ if signal.action is not None:
366
+ action = (
367
+ signal.action.value
368
+ if isinstance(signal.action, TradeAction)
369
+ else signal.action
370
+ )
371
+ run_trade_algorithm(
372
+ action,
373
+ symbol,
374
+ signal.id,
375
+ trade,
376
+ signal.price,
377
+ signal.stoplimit,
378
+ )
379
+ else:
380
+ if len(symbols) >= 10:
381
+ if symbol == symbols[-1]:
382
+ logger.info(
383
+ f"Not trading Time !!!, STRATEGY={STRATEGY}"
384
+ )
385
+ else:
386
+ logger.info(
387
+ f"Not trading Time !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}"
388
+ )
389
+ check(buys[symbol], sells[symbol], symbol)
390
+
391
+ except Exception:
392
+ msg = f"Handling Signals, SYMBOL={symbol}, STRATEGY={STRATEGY}"
393
+ _print_exc(debug_mode, msg)
394
+ continue
395
+
396
+ time.sleep((60 * iter_time) - 1.0)
397
+ if iter_time == 1:
398
+ time_intervals += 1
399
+ elif trade_time % iter_time == 0:
400
+ time_intervals += iter_time
401
+ else:
402
+ raise ValueError(
403
+ f"iter_time must be a multiple of the {time_frame} !!!"
404
+ f"(e.g., if time_frame is 15m, iter_time must be 1.5, 3, 3, 15 etc)"
405
+ )
406
+ try:
407
+ FRIDAY = "friday"
408
+ check_mt5_connection(**kwargs)
409
+ day_end = all(trade.days_end() for trade in trades_instances.values())
410
+ if closing_pnl is not None:
411
+ closing = all(
412
+ trade.positive_profit(id=trade.expert_id, th=closing_pnl)
413
+ for trade in trades_instances.values()
414
+ )
415
+ else:
416
+ closing = True
417
+
418
+ def logmsg(period, symbol):
419
+ logger.info(
420
+ f"End of the {period} !!! SYMBOL={symbol}, STRATEGY={STRATEGY}"
421
+ )
422
+
423
+ def logmsgif(period, symbol):
424
+ if len(symbols) <= 10:
425
+ logmsg(period, symbol)
426
+ elif len(symbols) > 10 and symbol == symbols[-1]:
427
+ logger.info(f"End of the {period} !!! STRATEGY={STRATEGY}")
428
+
429
+ def sleepmsg(sleep_time):
430
+ logger.info(f"Sleeping for {sleep_time} minutes ...\n")
431
+
432
+ sessionmsg = "STARTING NEW TRADING SESSION ...\n"
433
+ if period.lower() == "day":
434
+ for symbol in symbols:
435
+ trade = trades_instances[symbol]
436
+ if trade.days_end() and closing:
437
+ for id in expert_ids:
438
+ trade.close_positions(
439
+ position_type="all", id=id, comment=comment
440
+ )
441
+ logmsgif("Day", symbol)
442
+ trade.statistics(save=True)
443
+
444
+ if day_end:
445
+ strategy.perform_period_end_checks()
446
+ if period_end_action == "break" and closing:
447
+ break
448
+ elif (
449
+ period_end_action == "sleep" and today != FRIDAY or not closing
450
+ ):
451
+ sleep_time = trades_instances[symbols[-1]].sleep_time()
452
+ sleepmsg(sleep_time)
453
+ time.sleep(60 * sleep_time)
454
+ logger.info(sessionmsg)
455
+ elif period_end_action == "sleep" and today == FRIDAY:
456
+ sleep_time = trades_instances[symbols[-1]].sleep_time(
457
+ weekend=True
458
+ )
459
+ sleepmsg(sleep_time)
460
+ time.sleep(60 * sleep_time)
461
+ logger.info(sessionmsg)
462
+
463
+ elif period.lower() == "week":
464
+ for symbol in symbols:
465
+ trade = trades_instances[symbol]
466
+ if trade.days_end() and today != FRIDAY:
467
+ logmsgif("Day", symbol)
468
+
469
+ elif trade.days_end() and today == FRIDAY and closing:
470
+ for id in expert_ids:
471
+ trade.close_positions(
472
+ position_type="all", id=id, comment=comment
473
+ )
474
+ logmsgif("Week", symbol)
475
+ trade.statistics(save=True)
476
+
477
+ if day_end and today != FRIDAY:
478
+ sleep_time = trades_instances[symbols[-1]].sleep_time()
479
+ sleepmsg(sleep_time)
480
+ time.sleep(60 * sleep_time)
481
+ logger.info(sessionmsg)
482
+ elif day_end and today == FRIDAY:
483
+ strategy.perform_period_end_checks()
484
+ if period_end_action == "break" and closing:
485
+ break
486
+ elif period_end_action == "sleep" or not closing:
487
+ sleep_time = trades_instances[symbols[-1]].sleep_time(
488
+ weekend=True
489
+ )
490
+ sleepmsg(sleep_time)
491
+ time.sleep(60 * sleep_time)
492
+ logger.info(sessionmsg)
493
+
494
+ elif period.lower() == "month":
495
+ for symbol in symbols:
496
+ trade = trades_instances[symbol]
497
+ if trade.days_end() and today != FRIDAY:
498
+ logmsgif("Day", symbol)
499
+ elif trade.days_end() and today == FRIDAY:
500
+ logmsgif("Week", symbol)
501
+ elif (
502
+ trade.days_end()
503
+ and today == FRIDAY
504
+ and num_days / len(symbols) >= 20
505
+ ) and closing:
506
+ for id in expert_ids:
507
+ trade.close_positions(
508
+ position_type="all", id=id, comment=comment
509
+ )
510
+ logmsgif("Month", symbol)
511
+ trade.statistics(save=True)
512
+ if day_end and today != FRIDAY:
513
+ sleep_time = trades_instances[symbols[-1]].sleep_time()
514
+ sleepmsg(sleep_time)
515
+ time.sleep(60 * sleep_time)
516
+ logger.info(sessionmsg)
517
+ num_days += 1
518
+ elif day_end and today == FRIDAY:
519
+ sleep_time = trades_instances[symbols[-1]].sleep_time(weekend=True)
520
+ sleepmsg(sleep_time)
521
+ time.sleep(60 * sleep_time)
522
+ logger.info(sessionmsg)
523
+ num_days += 1
524
+ elif day_end and today == FRIDAY and num_days / len(symbols) >= 20:
525
+ strategy.perform_period_end_checks()
526
+ break
527
+ except Exception:
528
+ msg = f"Handling period end actions, STRATEGY={STRATEGY}"
529
+ _print_exc(debug_mode, msg)
530
+ continue
531
+
532
+
533
+ def _tws_execution(*args, **kwargs):
534
+ raise NotImplementedError("TWS Execution is not yet implemented !!!")
535
+
536
+
537
+ class MT5ExecutionEngine:
538
+ """
539
+ The `MT5ExecutionEngine` class serves as the central hub for executing your trading strategies within the `bbstrader` framework.
540
+ It orchestrates the entire trading process, ensuring seamless interaction between your strategies, market data, and your chosen
541
+ trading platform.
542
+
543
+ Key Features
544
+ ------------
545
+
546
+ - **Strategy Execution:** The `MT5ExecutionEngine` is responsible for running your strategy, retrieving signals, and executing trades based on those signals.
547
+ - **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.
548
+ - **Trade Period Control:** Define whether your strategy runs for a day, a week, or a month, allowing for flexible trading durations.
549
+ - **Money Management:** The engine supports optional money management features, allowing you to control risk and optimize your trading performance.
550
+ - **Trading Day Configuration:** You can customize the days of the week your strategy will execute, providing granular control over your trading schedule.
551
+ - **Platform Integration:** The `MT5ExecutionEngine` is currently designed to work with MT5.
552
+
553
+ Examples
554
+ --------
555
+
556
+ >>> from bbstrader.metatrader import create_trade_instance
557
+ >>> from bbstrader.trading.execution import MT5ExecutionEngine
558
+ >>> from bbstrader.trading.strategies import StockIndexCFDTrading
559
+ >>> from bbstrader.config import config_logger
560
+ >>>
561
+ >>> if __name__ == '__main__':
562
+ >>> logger = config_logger(index_trade.log, console_log=True)
563
+ >>> # Define symbols
564
+ >>> ndx = '[NQ100]'
565
+ >>> spx = '[SP500]'
566
+ >>> dji = '[DJI30]'
567
+ >>> dax = 'GERMANY40'
568
+ >>>
569
+ >>> symbol_list = [spx, dax, dji, ndx]
570
+ >>>
571
+ >>> trade_kwargs = {
572
+ ... 'expert_id': 5134,
573
+ ... 'version': 2.0,
574
+ ... 'time_frame': '15m',
575
+ ... 'var_level': 0.99,
576
+ ... 'start_time': '8:30',
577
+ ... 'finishing_time': '19:30',
578
+ ... 'ending_time': '21:30',
579
+ ... 'max_risk': 5.0,
580
+ ... 'daily_risk': 0.10,
581
+ ... 'pchange_sl': 1.5,
582
+ ... 'rr': 3.0,
583
+ ... 'logger': logger
584
+ ... }
585
+ >>> strategy_kwargs = {
586
+ ... 'max_trades': {ndx: 3, spx: 3, dji: 3, dax: 3},
587
+ ... 'expected_returns': {ndx: 1.5, spx: 1.5, dji: 1.0, dax: 1.0},
588
+ ... 'strategy_name': 'SISTBO',
589
+ ... 'logger': logger,
590
+ ... 'expert_id': 5134
591
+ ... }
592
+ >>> trades_instances = create_trade_instance(
593
+ ... symbol_list, trade_kwargs,
594
+ ... logger=logger,
595
+ ... )
596
+ >>>
597
+ >>> engine = MT5ExecutionEngine(
598
+ ... symbol_list,
599
+ ... trades_instances,
600
+ ... StockIndexCFDTrading,
601
+ ... time_frame='15m',
602
+ ... iter_time=5,
603
+ ... mm=True,
604
+ ... period='week',
605
+ ... comment='bbs_SISTBO_@2.0',
606
+ ... **strategy_kwargs
607
+ ... )
608
+ >>> engine.run()
609
+ """
610
+
611
+ def __init__(
612
+ self,
613
+ symbol_list: List[str],
614
+ trades_instances: Dict[str, Trade],
615
+ strategy_cls: Strategy,
616
+ /,
617
+ mm: bool = True,
618
+ optimizer: str = "equal",
619
+ trail: bool = True,
620
+ stop_trail: Optional[int] = None,
621
+ trail_after_points: Optional[int] = None,
622
+ be_plus_points: Optional[int] = None,
623
+ show_positions_orders: bool = False,
624
+ iter_time: int | float = 5,
625
+ use_trade_time: bool = True,
626
+ period: Literal["day", "week", "month"] = "week",
627
+ period_end_action: Literal["break", "sleep"] = "break",
628
+ closing_pnl: Optional[float] = None,
629
+ trading_days: Optional[List[str]] = TradingDays,
630
+ comment: Optional[str] = None,
631
+ **kwargs,
632
+ ):
633
+ """
634
+ Args:
635
+ symbol_list : List of symbols to trade
636
+ trades_instances : Dictionary of Trade instances
637
+ strategy_cls : Strategy class to use for trading
638
+ mm : Enable Money Management. Defaults to False.
639
+ optimizer : Risk management optimizer. Defaults to 'equal'.
640
+ See `bbstrader.models.optimization` module for more information.
641
+ show_positions_orders : Print open positions and orders. Defaults to False.
642
+ iter_time : Interval to check for signals and `mm`. Defaults to 5.
643
+ use_trade_time : Open trades after the time is completed. Defaults to True.
644
+ period : Period to trade. Defaults to 'week'.
645
+ period_end_action : Action to take at the end of the period. Defaults to 'break',
646
+ this only applies when period is 'day', 'week'.
647
+ closing_pnl : Minimum profit in percentage of target profit to close positions. Defaults to -0.001.
648
+ trading_days : Trading days in a week. Defaults to monday to friday.
649
+ comment: Comment for trades. Defaults to None.
650
+ **kwargs: Additional keyword arguments
651
+ _ time_frame : Time frame to trade. Defaults to '15m'.
652
+ - strategy_name (Optional[str]): Strategy name. Defaults to None.
653
+ - max_trades (Dict[str, int]): Maximum trades per symbol. Defaults to None.
654
+ - notify (bool): Enable notifications. Defaults to False.
655
+ - telegram (bool): Enable telegram notifications. Defaults to False.
656
+ - bot_token (str): Telegram bot token. Defaults to None.
657
+ - chat_id (Union[int, str, List] ): Telegram chat id. Defaults to None.
658
+
659
+ Note:
660
+ 1. For `trail` , `stop_trail` , `trail_after_points` , `be_plus_points` see `bbstrader.metatrader.trade.Trade.break_even()` .
661
+ 2. All Strategies must inherit from `bbstrader.btengine.strategy.Strategy` or `bbstrader.btengine.strategy.MT5Strategy` class
662
+ and have a `calculate_signals` method that returns a dictionary of signals for each symbol in symbol_list.
663
+
664
+ 3. All strategies must have the following arguments in their `__init__` method:
665
+ - bars (DataHandler): DataHandler instance default to None
666
+ - events (Queue): Queue instance default to None
667
+ - symbol_list (List[str]): List of symbols to trade can be none for backtesting
668
+ - mode (str): Mode of the strategy. Must be either 'live' or 'backtest'
669
+ - **kwargs: Additional keyword arguments
670
+ The keyword arguments are all the additional arguments passed to the `MT5ExecutionEngine` class,
671
+ the `Strategy` class, the `DataHandler` class, the `Portfolio` class and the `ExecutionHandler` class.
672
+ - The `bars` and `events` arguments are used for backtesting only.
673
+
674
+ 4. All strategies must generate signals for backtesting and live trading.
675
+ See the `bbstrader.trading.strategies` module for more information on how to create custom strategies.
676
+ See `bbstrader.metatrader.account.check_mt5_connection()` for more details on how to connect to MT5 terminal.
677
+ """
678
+ self.symbol_list = symbol_list
679
+ self.trades_instances = trades_instances
680
+ self.strategy_cls = strategy_cls
681
+ self.mm = mm
682
+ self.optimizer = optimizer
683
+ self.trail = trail
684
+ self.stop_trail = stop_trail
685
+ self.trail_after_points = trail_after_points
686
+ self.be_plus_points = be_plus_points
687
+ self.show_positions_orders = show_positions_orders
688
+ self.iter_time = iter_time
689
+ self.use_trade_time = use_trade_time
690
+ self.period = period
691
+ self.period_end_action = period_end_action
692
+ self.closing_pnl = closing_pnl
693
+ self.trading_days = trading_days
694
+ self.comment = comment
695
+ self.kwargs = kwargs
696
+
697
+ def __repr__(self):
698
+ trades = self.trades_instances.keys()
699
+ s = self.strategy_cls.__name__
700
+ return f"MT5ExecutionEngine(Symbols={list(trades)}, Strategy={s})"
701
+
702
+ def run(self):
703
+ check_mt5_connection(**self.kwargs)
704
+ _mt5_execution(
705
+ self.symbol_list,
706
+ self.trades_instances,
707
+ self.strategy_cls,
708
+ mm=self.mm,
709
+ optimizer=self.optimizer,
710
+ trail=self.trail,
711
+ stop_trail=self.stop_trail,
712
+ trail_after_points=self.trail_after_points,
713
+ be_plus_points=self.be_plus_points,
714
+ show_positions_orders=self.show_positions_orders,
715
+ iter_time=self.iter_time,
716
+ use_trade_time=self.use_trade_time,
717
+ period=self.period,
718
+ period_end_action=self.period_end_action,
719
+ closing_pnl=self.closing_pnl,
720
+ trading_days=self.trading_days,
721
+ comment=self.comment,
722
+ **self.kwargs,
723
+ )
724
+
725
+
726
+ class TWSExecutionEngine: ...