bbstrader 0.2.96__py3-none-any.whl → 0.2.98__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,16 +1,15 @@
1
1
  import multiprocessing as mp
2
2
  import time
3
- import traceback
4
3
  from datetime import datetime
5
4
  from typing import Dict, List, Literal, Optional
6
5
 
7
6
  from loguru import logger as log
7
+ from logging import Logger
8
8
 
9
9
  from bbstrader.btengine.strategy import MT5Strategy, Strategy
10
10
  from bbstrader.config import BBSTRADER_DIR
11
- from bbstrader.core.utils import TradeAction
12
11
  from bbstrader.metatrader.account import Account, check_mt5_connection
13
- from bbstrader.metatrader.trade import Trade
12
+ from bbstrader.metatrader.trade import Trade, TradeAction
14
13
  from bbstrader.trading.utils import send_message
15
14
 
16
15
  try:
@@ -19,7 +18,7 @@ except ImportError:
19
18
  import bbstrader.compat # noqa: F401
20
19
 
21
20
 
22
- __all__ = ["MT5ExecutionEngine", "TWSExecutionEngine"]
21
+ __all__ = ["Mt5ExecutionEngine", "TWSExecutionEngine"]
23
22
 
24
23
  _TF_MAPPING = {
25
24
  "1m": 1,
@@ -93,536 +92,28 @@ log.add(
93
92
  )
94
93
 
95
94
 
96
- def _mt5_execution(
97
- symbol_list,
98
- trades_instances,
99
- strategy_cls,
100
- /,
101
- mm,
102
- optimizer,
103
- trail,
104
- stop_trail,
105
- trail_after_points,
106
- be_plus_points,
107
- show_positions_orders,
108
- iter_time,
109
- use_trade_time,
110
- period,
111
- period_end_action,
112
- closing_pnl,
113
- trading_days,
114
- comment,
115
- **kwargs,
116
- ):
117
- logger = kwargs.get("logger", log)
118
-
119
- def _print_exc(dm, msg):
120
- traceback.print_exc() if dm else logger.error(msg)
121
-
122
- try:
123
- symbols = symbol_list.copy()
124
- time_frame = kwargs.get("time_frame", "15m")
125
- daily_risk = kwargs.get("daily_risk")
126
- STRATEGY = kwargs.get("strategy_name")
127
- mtrades = kwargs.get("max_trades")
128
- notify = kwargs.get("notify", False)
129
- signal_tickers = kwargs.get("signal_tickers", symbols)
130
- debug_mode = kwargs.get("debug_mode", False)
131
- delay = kwargs.get("delay", 0)
132
- ACCOUNT = kwargs.get("account", "MT5 Account")
133
- if notify:
134
- telegram = kwargs.get("telegram", False)
135
- bot_token = kwargs.get("bot_token")
136
- chat_id = kwargs.get("chat_id")
137
-
138
- expert_ids = kwargs.get("expert_ids")
139
- if expert_ids is None:
140
- expert_ids = list(
141
- set([trade.expert_id for trade in trades_instances.values()])
142
- )
143
- elif isinstance(expert_ids, int):
144
- expert_ids = [expert_ids]
145
-
146
- max_trades = {
147
- symbol: mtrades[symbol]
148
- if mtrades is not None and symbol in mtrades
149
- else trades_instances[symbol].max_trade()
150
- for symbol in symbols
151
- }
152
- if comment is None:
153
- trade = trades_instances[symbols[0]]
154
- comment = f"{trade.expert_name}@{trade.version}"
155
-
156
- if period.lower() == "24/7":
157
- trading_days = WEEK_DAYS
158
- except Exception:
159
- _print_exc(
160
- debug_mode,
161
- f"Initializing Execution Engine, STRATEGY={STRATEGY}, ACCOUNT={ACCOUNT}",
162
- )
163
- return
164
-
165
- def update_risk(weights):
166
- if weights is not None:
167
- for symbol in symbols:
168
- if symbol not in weights:
169
- continue
170
- trade = trades_instances[symbol]
171
- dailydd = round(weights[symbol] * daily_risk, 5)
172
- trade.dailydd = dailydd
173
-
174
- def check_retcode(trade: Trade, position):
175
- if len(trade.retcodes) > 0:
176
- for retcode in trade.retcodes:
177
- if retcode in NON_EXEC_RETCODES[position]:
178
- return True
179
- return False
180
-
181
- def _send_notification(signal, symbol):
182
- if symbol in signal_tickers:
183
- send_message(
184
- message=signal,
185
- notify_me=notify,
186
- telegram=telegram,
187
- token=bot_token,
188
- chat_id=chat_id,
189
- )
190
-
191
- def check(buys, sells, symbol):
192
- if not mm:
193
- return
194
- if buys is not None or sells is not None:
195
- trades_instances[symbol].break_even(
196
- mm=mm,
197
- trail=trail,
198
- stop_trail=stop_trail,
199
- trail_after_points=trail_after_points,
200
- be_plus_points=be_plus_points,
201
- )
202
-
203
- try:
204
- check_mt5_connection(**kwargs)
205
- strategy: MT5Strategy = strategy_cls(symbol_list=symbols, mode="live", **kwargs)
206
- except Exception:
207
- _print_exc(
208
- debug_mode, f"Initializing strategy, STRATEGY={STRATEGY}, ACCOUNT={ACCOUNT}"
209
- )
210
- return
211
- logger.info(
212
- f"Running {STRATEGY} Strategy in {time_frame} Interval ..., ACCOUNT={ACCOUNT}"
213
- )
214
-
215
- def run_trade_algorithm(signal, symbol, id, trade: Trade, price, stoplimit):
216
- signal = "BMKT" if signal == "LONG" or signal == "BUY" else signal
217
- signal = "SMKT" if signal == "SHORT" or signal == "SELL" else signal
218
- info = f"SIGNAL = {signal}, SYMBOL={symbol}, STRATEGY={STRATEGY}, TIMEFRAME={time_frame} , ACCOUNT={ACCOUNT}"
219
- account = Account(**kwargs)
220
- symbol_type = account.get_symbol_type(symbol)
221
- desc = account.get_symbol_info(symbol).description
222
- sigmsg = (
223
- f"SIGNAL = {signal}, \nSYMBOL={symbol}, \nTYPE={symbol_type}, \nDESCRIPTION={desc}, "
224
- f"\nPRICE={price}, \nSTOPLIMIT={stoplimit}, \nSTRATEGY={STRATEGY}, \nTIMEFRAME={time_frame},"
225
- f"\nBROKER={account.broker.name}, \nTIMESTAMP={datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
226
- )
227
- msg = f"Sending {signal} Order ... SYMBOL={symbol}, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
228
- tfmsg = f"Time Frame Not completed !!! SYMBOL={symbol}, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
229
- riskmsg = f"Risk not allowed !!! SYMBOL={symbol}, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
230
- if signal not in EXIT_SIGNAL_ACTIONS:
231
- if signal in NON_EXEC_RETCODES and not check_retcode(trade, signal):
232
- logger.info(info)
233
- elif signal not in NON_EXEC_RETCODES:
234
- logger.info(info)
235
- if signal in EXIT_SIGNAL_ACTIONS:
236
- for exit_signal, actions in EXIT_SIGNAL_ACTIONS.items():
237
- for position_type, order_type in actions.items():
238
- clos_func = getattr(
239
- trades_instances[symbol], f"get_current_{position_type}"
240
- )
241
- if clos_func(id=id) is not None:
242
- if notify:
243
- _send_notification(sigmsg, symbol)
244
- if position_type in POSITIONS_TYPES:
245
- trade.close_positions(position_type=order_type, id=id)
246
- else:
247
- trade.close_orders(order_type=order_type, id=id)
248
- elif signal in BUYS and not long_market[symbol]:
249
- if use_trade_time:
250
- if time_intervals % trade_time == 0 or buys[symbol] is None:
251
- if notify:
252
- _send_notification(sigmsg, symbol)
253
- if not check_retcode(trade, "BMKT"):
254
- logger.info(msg)
255
- trade.open_buy_position(
256
- action=signal,
257
- price=price,
258
- stoplimit=stoplimit,
259
- id=id,
260
- mm=mm,
261
- trail=trail,
262
- comment=comment,
263
- )
264
- else:
265
- logger.info(tfmsg)
266
- check(buys[symbol], sells[symbol], symbol)
267
- else:
268
- if notify:
269
- _send_notification(sigmsg, symbol)
270
- if not check_retcode(trade, "BMKT"):
271
- logger.info(msg)
272
- trade.open_buy_position(
273
- action=signal,
274
- price=price,
275
- stoplimit=stoplimit,
276
- id=id,
277
- mm=mm,
278
- trail=trail,
279
- comment=comment,
280
- )
281
-
282
- elif signal in BUYS and long_market[symbol]:
283
- logger.info(riskmsg)
284
-
285
- elif signal in SELLS and not short_market[symbol]:
286
- if use_trade_time:
287
- if time_intervals % trade_time == 0 or sells[symbol] is None:
288
- if notify:
289
- _send_notification(sigmsg, symbol)
290
- if not check_retcode(trade, "SMKT"):
291
- logger.info(msg)
292
- trade.open_sell_position(
293
- action=signal,
294
- price=price,
295
- stoplimit=stoplimit,
296
- id=id,
297
- mm=mm,
298
- trail=trail,
299
- comment=comment,
300
- )
301
- else:
302
- logger.info(tfmsg)
303
- check(buys[symbol], sells[symbol], symbol)
304
- else:
305
- if notify:
306
- _send_notification(sigmsg, symbol)
307
- if not check_retcode(trade, "SMKT"):
308
- logger.info(msg)
309
- trade.open_sell_position(
310
- action=signal,
311
- price=price,
312
- stoplimit=stoplimit,
313
- id=id,
314
- mm=mm,
315
- trail=trail,
316
- comment=comment,
317
- )
318
-
319
- elif signal in SELLS and short_market[symbol]:
320
- logger.info(riskmsg)
321
- else:
322
- check(buys[symbol], sells[symbol], symbol)
323
-
324
- num_days = 0
325
- time_intervals = 0
326
- trade_time = _TF_MAPPING[time_frame]
327
-
328
- long_market = {symbol: False for symbol in symbols}
329
- short_market = {symbol: False for symbol in symbols}
330
-
331
- while True:
332
- try:
333
- check_mt5_connection(**kwargs)
334
- current_date = datetime.now()
335
- today = current_date.strftime("%A").lower()
336
- time.sleep(0.5)
337
- positions_orders = {}
338
- for type in POSITIONS_TYPES + ORDERS_TYPES:
339
- positions_orders[type] = {}
340
- for symbol in symbols:
341
- positions_orders[type][symbol] = None
342
- for id in expert_ids:
343
- func = getattr(trades_instances[symbol], f"get_current_{type}")
344
- func_value = func(id=id)
345
- if func_value is not None:
346
- if positions_orders[type][symbol] is None:
347
- positions_orders[type][symbol] = func(id=id)
348
- else:
349
- positions_orders[type][symbol] += func(id=id)
350
- buys = positions_orders["buys"]
351
- sells = positions_orders["sells"]
352
- for symbol in symbols:
353
- for type in POSITIONS_TYPES + ORDERS_TYPES:
354
- if positions_orders[type][symbol] is not None:
355
- if show_positions_orders:
356
- logger.info(
357
- f"Current {type.upper()} SYMBOL={symbol}: \
358
- {positions_orders[type][symbol]}, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
359
- )
360
- long_market = {
361
- symbol: buys[symbol] is not None
362
- and len(buys[symbol]) >= max_trades[symbol]
363
- for symbol in symbols
364
- }
365
- short_market = {
366
- symbol: sells[symbol] is not None
367
- and len(sells[symbol]) >= max_trades[symbol]
368
- for symbol in symbols
369
- }
370
- except Exception:
371
- _print_exc(
372
- debug_mode,
373
- f"Checking positions and orders, STRATEGY={STRATEGY}, ACCOUNT={ACCOUNT}",
374
- )
375
- continue
376
- time.sleep(0.5)
377
- try:
378
- check_mt5_connection(**kwargs)
379
- signals = strategy.calculate_signals()
380
- weights = (
381
- strategy.apply_risk_management(optimizer)
382
- if hasattr(strategy, "apply_risk_management")
383
- else None
384
- )
385
- update_risk(weights)
386
- except Exception:
387
- _print_exc(
388
- debug_mode,
389
- f"Calculating Signals, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}",
390
- )
391
- continue
392
- if len(signals) == 0:
393
- for symbol in symbols:
394
- check(buys[symbol], sells[symbol], symbol)
395
- else:
396
- try:
397
- check_mt5_connection(**kwargs)
398
- for signal in signals:
399
- symbol = signal.symbol
400
- trade: Trade = trades_instances[symbol]
401
- if trade.trading_time() and today in trading_days:
402
- if signal.action is not None:
403
- action = (
404
- signal.action.value
405
- if isinstance(signal.action, TradeAction)
406
- else signal.action
407
- )
408
- run_trade_algorithm(
409
- action,
410
- symbol,
411
- signal.id,
412
- trade,
413
- signal.price,
414
- signal.stoplimit,
415
- )
416
- else:
417
- if len(symbols) >= 10:
418
- if symbol == symbols[-1]:
419
- logger.info(
420
- f"Not trading Time !!!, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
421
- )
422
- else:
423
- logger.info(
424
- f"Not trading Time !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
425
- )
426
- check(buys[symbol], sells[symbol], symbol)
427
-
428
- except Exception:
429
- msg = f"Handling Signals, SYMBOL={symbol}, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
430
- _print_exc(debug_mode, msg)
431
- continue
432
-
433
- time.sleep((60 * iter_time) - 1.0)
434
- if iter_time == 1:
435
- time_intervals += 1
436
- elif trade_time % iter_time == 0:
437
- time_intervals += iter_time
438
- else:
439
- if use_trade_time:
440
- raise ValueError(
441
- f"iter_time must be a multiple of the {time_frame} !!!"
442
- f"(e.g., if time_frame is 15m, iter_time must be 1.5, 3, 5, 15 etc)"
443
- )
444
- try:
445
- FRIDAY = "friday"
446
- check_mt5_connection(**kwargs)
447
- day_end = all(trade.days_end() for trade in trades_instances.values())
448
- if closing_pnl is not None:
449
- closing = all(
450
- trade.positive_profit(id=trade.expert_id, th=closing_pnl)
451
- for trade in trades_instances.values()
452
- )
453
- else:
454
- closing = True
455
-
456
- def logmsg(period, symbol):
457
- logger.info(
458
- f"End of the {period} !!! SYMBOL={symbol}, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
459
- )
460
-
461
- def logmsgif(period, symbol):
462
- if len(symbols) <= 10:
463
- logmsg(period, symbol)
464
- elif len(symbols) > 10 and symbol == symbols[-1]:
465
- logger.info(
466
- f"End of the {period} !!! STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
467
- )
468
-
469
- def sleepmsg(sleep_time):
470
- logger.info(f"{ACCOUNT} Sleeping for {sleep_time} minutes ...\n")
471
-
472
- sessionmsg = f"{ACCOUNT} STARTING NEW TRADING SESSION ...\n"
473
- if period.lower() == "24/7": # For Cryptos
474
- for symbol in symbols:
475
- trade = trades_instances[symbol]
476
- if trade.days_end() and closing:
477
- for id in expert_ids:
478
- trade.close_positions(
479
- position_type="all", id=id, comment=comment
480
- )
481
- logmsgif("Day", symbol)
482
- trade.statistics(save=True)
483
- if day_end:
484
- strategy.perform_period_end_checks()
485
- sleep_time = trades_instances[symbols[-1]].sleep_time()
486
- sleepmsg(sleep_time + delay)
487
- time.sleep(60 * sleep_time + delay)
488
- logger.info(sessionmsg)
489
- time_intervals = 0
490
-
491
- elif period.lower() == "day":
492
- for symbol in symbols:
493
- trade = trades_instances[symbol]
494
- if trade.days_end() and closing:
495
- for id in expert_ids:
496
- trade.close_positions(
497
- position_type="all", id=id, comment=comment
498
- )
499
- logmsgif("Day", symbol)
500
- trade.statistics(save=True)
501
-
502
- if day_end:
503
- strategy.perform_period_end_checks()
504
- if period_end_action == "break" and closing:
505
- break
506
- elif (
507
- period_end_action == "sleep" and today != FRIDAY or not closing
508
- ):
509
- sleep_time = trades_instances[symbols[-1]].sleep_time()
510
- sleepmsg(sleep_time + delay)
511
- time.sleep(60 * sleep_time + delay)
512
- logger.info(sessionmsg)
513
- time_intervals = 0
514
- elif period_end_action == "sleep" and today == FRIDAY:
515
- sleep_time = trades_instances[symbols[-1]].sleep_time(
516
- weekend=True
517
- )
518
- sleepmsg(sleep_time + delay)
519
- time.sleep(60 * sleep_time + delay)
520
- logger.info(sessionmsg)
521
- time_intervals = 0
522
-
523
- elif period.lower() == "week":
524
- for symbol in symbols:
525
- trade = trades_instances[symbol]
526
- if trade.days_end() and today != FRIDAY:
527
- logmsgif("Day", symbol)
528
-
529
- elif trade.days_end() and today == FRIDAY and closing:
530
- for id in expert_ids:
531
- trade.close_positions(
532
- position_type="all", id=id, comment=comment
533
- )
534
- logmsgif("Week", symbol)
535
- trade.statistics(save=True)
536
-
537
- if day_end and today != FRIDAY:
538
- sleep_time = trades_instances[symbols[-1]].sleep_time()
539
- sleepmsg(sleep_time + delay)
540
- time.sleep(60 * sleep_time + delay)
541
- logger.info(sessionmsg)
542
- time_intervals = 0
543
- elif day_end and today == FRIDAY:
544
- strategy.perform_period_end_checks()
545
- if period_end_action == "break" and closing:
546
- break
547
- elif period_end_action == "sleep" or not closing:
548
- sleep_time = trades_instances[symbols[-1]].sleep_time(
549
- weekend=True
550
- )
551
- sleepmsg(sleep_time + delay)
552
- time.sleep(60 * sleep_time + delay)
553
- logger.info(sessionmsg)
554
- time_intervals = 0
555
-
556
- elif period.lower() == "month":
557
- for symbol in symbols:
558
- trade = trades_instances[symbol]
559
- if trade.days_end() and today != FRIDAY:
560
- logmsgif("Day", symbol)
561
- elif trade.days_end() and today == FRIDAY:
562
- logmsgif("Week", symbol)
563
- elif (
564
- trade.days_end() and today == FRIDAY and num_days >= 20
565
- ) and closing:
566
- for id in expert_ids:
567
- trade.close_positions(
568
- position_type="all", id=id, comment=comment
569
- )
570
- logmsgif("Month", symbol)
571
- trade.statistics(save=True)
572
- if day_end and today != FRIDAY:
573
- sleep_time = trades_instances[symbols[-1]].sleep_time()
574
- sleepmsg(sleep_time + delay)
575
- time.sleep(60 * sleep_time + delay)
576
- logger.info(sessionmsg)
577
- time_intervals = 0
578
- num_days += 1
579
- elif day_end and today == FRIDAY:
580
- sleep_time = trades_instances[symbols[-1]].sleep_time(weekend=True)
581
- sleepmsg(sleep_time + delay)
582
- time.sleep(60 * sleep_time + delay)
583
- logger.info(sessionmsg)
584
- time_intervals = 0
585
- num_days += 1
586
- elif day_end and today == FRIDAY and num_days >= 20:
587
- strategy.perform_period_end_checks()
588
- break
589
- except Exception:
590
- msg = (
591
- f"Handling period end actions, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
592
- )
593
- _print_exc(debug_mode, msg)
594
- continue
595
- except KeyboardInterrupt:
596
- logger.info("Stopping the Execution Engine ...")
597
- break
598
-
599
-
600
- def _tws_execution(*args, **kwargs):
601
- raise NotImplementedError("TWS Execution is not yet implemented !!!")
602
-
603
-
604
- class MT5ExecutionEngine:
95
+ class Mt5ExecutionEngine:
605
96
  """
606
- The `MT5ExecutionEngine` class serves as the central hub for executing your trading strategies within the `bbstrader` framework.
97
+ The `Mt5ExecutionEngine` class serves as the central hub for executing your trading strategies within the `bbstrader` framework.
607
98
  It orchestrates the entire trading process, ensuring seamless interaction between your strategies, market data, and your chosen
608
99
  trading platform.
609
100
 
610
101
  Key Features
611
102
  ------------
612
103
 
613
- - **Strategy Execution:** The `MT5ExecutionEngine` is responsible for running your strategy, retrieving signals, and executing trades based on those signals.
104
+ - **Strategy Execution:** The `Mt5ExecutionEngine` is responsible for running your strategy, retrieving signals, and executing trades based on those signals.
614
105
  - **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.
615
106
  - **Trade Period Control:** Define whether your strategy runs for a day, a week, or a month, allowing for flexible trading durations.
616
107
  - **Money Management:** The engine supports optional money management features, allowing you to control risk and optimize your trading performance.
617
108
  - **Trading Day Configuration:** You can customize the days of the week your strategy will execute, providing granular control over your trading schedule.
618
- - **Platform Integration:** The `MT5ExecutionEngine` is currently designed to work with MT5.
109
+ - **Platform Integration:** The `Mt5ExecutionEngine` is currently designed to work with MT5.
619
110
 
620
111
  Examples
621
112
  --------
622
113
 
623
114
  >>> from bbstrader.metatrader import create_trade_instance
624
- >>> from bbstrader.trading.execution import MT5ExecutionEngine
625
- >>> from bbstrader.trading.strategies import StockIndexCFDTrading
115
+ >>> from bbstrader.trading.execution import Mt5ExecutionEngine
116
+ >>> from bbstrader.trading.strategies import StockIndexSTBOTrading
626
117
  >>> from bbstrader.config import config_logger
627
118
  >>>
628
119
  >>> if __name__ == '__main__':
@@ -661,7 +152,7 @@ class MT5ExecutionEngine:
661
152
  ... logger=logger,
662
153
  ... )
663
154
  >>>
664
- >>> engine = MT5ExecutionEngine(
155
+ >>> engine = Mt5ExecutionEngine(
665
156
  ... symbol_list,
666
157
  ... trades_instances,
667
158
  ... StockIndexCFDTrading,
@@ -734,7 +225,7 @@ class MT5ExecutionEngine:
734
225
  - symbol_list (List[str]): List of symbols to trade can be none for backtesting
735
226
  - mode (str): Mode of the strategy. Must be either 'live' or 'backtest'
736
227
  - **kwargs: Additional keyword arguments
737
- The keyword arguments are all the additional arguments passed to the `MT5ExecutionEngine` class,
228
+ The keyword arguments are all the additional arguments passed to the `Mt5ExecutionEngine` class,
738
229
  the `Strategy` class, the `DataHandler` class, the `Portfolio` class and the `ExecutionHandler` class.
739
230
  - The `bars` and `events` arguments are used for backtesting only.
740
231
 
@@ -742,7 +233,7 @@ class MT5ExecutionEngine:
742
233
  See the `bbstrader.trading.strategies` module for more information on how to create custom strategies.
743
234
  See `bbstrader.metatrader.account.check_mt5_connection()` for more details on how to connect to MT5 terminal.
744
235
  """
745
- self.symbol_list = symbol_list
236
+ self.symbols = symbol_list.copy()
746
237
  self.trades_instances = trades_instances
747
238
  self.strategy_cls = strategy_cls
748
239
  self.mm = mm
@@ -761,36 +252,686 @@ class MT5ExecutionEngine:
761
252
  self.comment = comment
762
253
  self.kwargs = kwargs
763
254
 
255
+ self.num_days = 0
256
+ self.time_intervals = 0
257
+ self.time_frame = kwargs.get("time_frame", "15m")
258
+ self.trade_time = _TF_MAPPING[self.time_frame]
259
+
260
+ self.long_market = {symbol: False for symbol in self.symbols}
261
+ self.short_market = {symbol: False for symbol in self.symbols}
262
+
263
+ self._initialize_engine(**kwargs)
264
+ self.strategy = self._init_strategy(**kwargs)
265
+
764
266
  def __repr__(self):
765
267
  trades = self.trades_instances.keys()
766
- s = self.strategy_cls.__name__
767
- return f"MT5ExecutionEngine(Symbols={list(trades)}, Strategy={s})"
268
+ strategy = self.strategy_cls.__name__
269
+ return f"Mt5ExecutionEngine(Symbols={list(trades)}, Strategy={strategy})"
768
270
 
769
- def run(self):
770
- check_mt5_connection(**self.kwargs)
771
- _mt5_execution(
772
- self.symbol_list,
773
- self.trades_instances,
774
- self.strategy_cls,
775
- mm=self.mm,
776
- optimizer=self.optimizer,
777
- trail=self.trail,
778
- stop_trail=self.stop_trail,
779
- trail_after_points=self.trail_after_points,
780
- be_plus_points=self.be_plus_points,
781
- show_positions_orders=self.show_positions_orders,
782
- iter_time=self.iter_time,
783
- use_trade_time=self.use_trade_time,
784
- period=self.period,
785
- period_end_action=self.period_end_action,
786
- closing_pnl=self.closing_pnl,
787
- trading_days=self.trading_days,
788
- comment=self.comment,
789
- **self.kwargs,
271
+ def _initialize_engine(self, **kwargs):
272
+ global logger
273
+ logger: Logger = kwargs.get("logger", log)
274
+ try:
275
+ self.daily_risk = kwargs.get("daily_risk")
276
+ self.notify = kwargs.get("notify", False)
277
+ self.debug_mode = kwargs.get("debug_mode", False)
278
+ self.delay = kwargs.get("delay", 0)
279
+
280
+ self.STRATEGY = kwargs.get("strategy_name")
281
+ self.ACCOUNT = kwargs.get("account", "MT5 Account")
282
+ self.signal_tickers = kwargs.get("signal_tickers", self.symbols)
283
+ self.FRIDAY = "friday"
284
+
285
+ self.expert_ids = self._expert_ids(kwargs.get("expert_ids"))
286
+ self.max_trades = self._max_trades(kwargs.get("max_trades"))
287
+ if self.comment is None:
288
+ trade = self.trades_instances[self.symbols[0]]
289
+ self.comment = f"{trade.expert_name}@{trade.version}"
290
+
291
+ if self.period.lower() == "24/7":
292
+ self.trading_days = WEEK_DAYS
293
+ except Exception as e:
294
+ self._print_exc(
295
+ f"Initializing Execution Engine, STRATEGY={self.STRATEGY}, ACCOUNT={self.ACCOUNT}",
296
+ e,
297
+ )
298
+ return
299
+
300
+ def _print_exc(self, msg, e: Exception):
301
+ if isinstance(e, KeyboardInterrupt):
302
+ logger.info("Stopping the Execution Engine ...")
303
+ quit()
304
+ if self.debug_mode:
305
+ raise ValueError(msg).with_traceback(e.__traceback__)
306
+ else:
307
+ logger.error(msg)
308
+
309
+ def _max_trades(self, mtrades):
310
+ max_trades = {
311
+ symbol: mtrades[symbol]
312
+ if mtrades is not None and symbol in mtrades
313
+ else self.trades_instances[symbol].max_trade()
314
+ for symbol in self.symbols
315
+ }
316
+ return max_trades
317
+
318
+ def _expert_ids(self, expert_ids):
319
+ if expert_ids is None:
320
+ expert_ids = list(
321
+ set([trade.expert_id for trade in self.trades_instances.values()])
322
+ )
323
+ elif isinstance(expert_ids, int):
324
+ expert_ids = [expert_ids]
325
+ return expert_ids
326
+
327
+ def _init_strategy(self, **kwargs):
328
+ try:
329
+ check_mt5_connection(**kwargs)
330
+ strategy: MT5Strategy = self.strategy_cls(
331
+ symbol_list=self.symbols, mode="live", **kwargs
332
+ )
333
+ except Exception as e:
334
+ self._print_exc(
335
+ f"Initializing strategy, STRATEGY={self.STRATEGY}, ACCOUNT={self.ACCOUNT}",
336
+ e,
337
+ )
338
+ return
339
+ logger.info(
340
+ f"Running {self.STRATEGY} Strategy in {self.time_frame} Interval ..., ACCOUNT={self.ACCOUNT}"
341
+ )
342
+ return strategy
343
+
344
+ def _get_signal_info(self, signal, symbol, price, stoplimit):
345
+ account = Account(**self.kwargs)
346
+ symbol_info = account.get_symbol_info(symbol)
347
+
348
+ common_data = {
349
+ "signal": signal,
350
+ "symbol": symbol,
351
+ "strategy": self.STRATEGY,
352
+ "timeframe": self.time_frame,
353
+ "account": self.ACCOUNT,
354
+ }
355
+
356
+ info = (
357
+ "SIGNAL = {signal}, SYMBOL={symbol}, STRATEGY={strategy}, "
358
+ "TIMEFRAME={timeframe}, ACCOUNT={account}"
359
+ ).format(**common_data)
360
+
361
+ sigmsg = (
362
+ "SIGNAL = {signal},\n"
363
+ "SYMBOL = {symbol},\n"
364
+ "TYPE = {symbol_type},\n"
365
+ "DESCRIPTION = {description},\n"
366
+ "PRICE = {price},\n"
367
+ "STOPLIMIT = {stoplimit},\n"
368
+ "STRATEGY = {strategy},\n"
369
+ "TIMEFRAME = {timeframe},\n"
370
+ "BROKER = {broker},\n"
371
+ "TIMESTAMP = {timestamp}"
372
+ ).format(
373
+ **common_data,
374
+ symbol_type=account.get_symbol_type(symbol),
375
+ description=symbol_info.description,
376
+ price=price,
377
+ stoplimit=stoplimit,
378
+ broker=account.broker.name,
379
+ timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
380
+ )
381
+
382
+ msg_template = "SYMBOL = {symbol}, STRATEGY = {strategy}, ACCOUNT = {account}"
383
+ msg = f"Sending {signal} Order ... " + msg_template.format(**common_data)
384
+ tfmsg = "Time Frame Not completed !!! " + msg_template.format(**common_data)
385
+ riskmsg = "Risk not allowed !!! " + msg_template.format(**common_data)
386
+
387
+ return info, sigmsg, msg, tfmsg, riskmsg
388
+
389
+ def _check_retcode(self, trade: Trade, position):
390
+ if len(trade.retcodes) > 0:
391
+ for retcode in trade.retcodes:
392
+ if retcode in NON_EXEC_RETCODES[position]:
393
+ return True
394
+ return False
395
+
396
+ def _check_positions_orders(self):
397
+ positions_orders = {}
398
+ try:
399
+ check_mt5_connection(**self.kwargs)
400
+ for order_type in POSITIONS_TYPES + ORDERS_TYPES:
401
+ positions_orders[order_type] = {}
402
+ for symbol in self.symbols:
403
+ positions_orders[order_type][symbol] = None
404
+ for id in self.expert_ids:
405
+ func = getattr(
406
+ self.trades_instances[symbol], f"get_current_{order_type}"
407
+ )
408
+ func_value = func(id=id)
409
+ if func_value is not None:
410
+ if positions_orders[order_type][symbol] is None:
411
+ positions_orders[order_type][symbol] = func_value
412
+ else:
413
+ positions_orders[order_type][symbol] += func_value
414
+ return positions_orders
415
+ except Exception as e:
416
+ self._print_exc(
417
+ f"Checking positions and orders, STRATEGY={self.STRATEGY} , ACCOUNT={self.ACCOUNT}",
418
+ e,
419
+ )
420
+
421
+ def _long_short_market(self, buys, sells):
422
+ long_market = {
423
+ symbol: buys[symbol] is not None
424
+ and len(buys[symbol]) >= self.max_trades[symbol]
425
+ for symbol in self.symbols
426
+ }
427
+ short_market = {
428
+ symbol: sells[symbol] is not None
429
+ and len(sells[symbol]) >= self.max_trades[symbol]
430
+ for symbol in self.symbols
431
+ }
432
+ return long_market, short_market
433
+
434
+ def _display_positions_orders(self, positions_orders):
435
+ for symbol in self.symbols:
436
+ for order_type in POSITIONS_TYPES + ORDERS_TYPES:
437
+ if positions_orders[order_type][symbol] is not None:
438
+ logger.info(
439
+ f"Current {order_type.upper()} SYMBOL={symbol}: \
440
+ {positions_orders[order_type][symbol]}, STRATEGY={self.STRATEGY} , ACCOUNT={self.ACCOUNT}"
441
+ )
442
+
443
+ def _update_risk(self, weights):
444
+ try:
445
+ check_mt5_connection(**self.kwargs)
446
+ if weights is not None:
447
+ for symbol in self.symbols:
448
+ if symbol not in weights:
449
+ continue
450
+ trade = self.trades_instances[symbol]
451
+ assert self.daily_risk is not None
452
+ dailydd = round(weights[symbol] * self.daily_risk, 5)
453
+ trade.dailydd = dailydd
454
+ except Exception as e:
455
+ self._print_exc(
456
+ f"Updating Risk, STRATEGY={self.STRATEGY} , ACCOUNT={self.ACCOUNT}",
457
+ e,
458
+ )
459
+ pass
460
+
461
+ def _send_notification(self, signal, symbol):
462
+ telegram = self.kwargs.get("telegram", False)
463
+ bot_token = self.kwargs.get("bot_token")
464
+ chat_id = self.kwargs.get("chat_id")
465
+ notify = self.kwargs.get("notify", False)
466
+ if symbol in self.signal_tickers:
467
+ send_message(
468
+ message=signal,
469
+ notify_me=notify,
470
+ telegram=telegram,
471
+ token=bot_token,
472
+ chat_id=chat_id,
473
+ )
474
+
475
+ def _logmsg(self, period, symbol):
476
+ logger.info(
477
+ f"End of the {period} !!! SYMBOL={symbol}, STRATEGY={self.STRATEGY} , ACCOUNT={self.ACCOUNT}"
790
478
  )
791
479
 
480
+ def _logmsgif(self, period, symbol):
481
+ if len(self.symbols) <= 10:
482
+ self._logmsg(period, symbol)
483
+ elif len(self.symbols) > 10 and symbol == self.symbols[-1]:
484
+ logger.info(
485
+ f"End of the {period} !!! STRATEGY={self.STRATEGY} , ACCOUNT={self.ACCOUNT}"
486
+ )
487
+
488
+ def _sleepmsg(self, sleep_time):
489
+ logger.info(f"{self.ACCOUNT} Sleeping for {sleep_time} minutes ...\n")
490
+
491
+ def _perform_period_end_actions(
492
+ self,
493
+ today,
494
+ day_end,
495
+ closing,
496
+ sessionmsg,
497
+ ):
498
+ period_type = self.period.lower()
499
+ for symbol in self.symbols:
500
+ trade = self.trades_instances[symbol]
501
+
502
+ if trade.days_end():
503
+ if period_type in ["weekly", "monthly"] and today != self.FRIDAY:
504
+ self._logmsgif("Day", symbol)
505
+ elif period_type in ["weekly", "monthly"] and today == self.FRIDAY:
506
+ self._logmsgif("Week", symbol)
507
+
508
+ if (
509
+ (
510
+ period_type == "monthly"
511
+ and today == self.FRIDAY
512
+ and self.num_days >= 20
513
+ and closing
514
+ )
515
+ or (period_type == "weekly" and today == self.FRIDAY and closing)
516
+ or (period_type == "daily" and closing)
517
+ or (period_type == "24/7" and closing)
518
+ ):
519
+ for id in self.expert_ids:
520
+ trade.close_positions(
521
+ position_type="all", id=id, comment=self.comment
522
+ )
792
523
 
793
- def mt5_engine(account_id: str, **kwargs):
524
+ if period_type == "monthly":
525
+ self._logmsgif("Month", symbol)
526
+ elif period_type == "weekly":
527
+ self._logmsgif("Week", symbol)
528
+ else:
529
+ self._logmsgif("Day", symbol)
530
+
531
+ trade.statistics(save=True)
532
+
533
+ if day_end:
534
+ if period_type == "24/7":
535
+ self.strategy.perform_period_end_checks()
536
+ sleep_time = self.trades_instances[self.symbols[-1]].sleep_time()
537
+ self._sleepmsg(sleep_time + self.delay)
538
+ time.sleep(60 * sleep_time + self.delay)
539
+ logger.info(sessionmsg)
540
+
541
+ elif period_type == "daily":
542
+ self.strategy.perform_period_end_checks()
543
+ if self.period_end_action == "break" and closing:
544
+ exit(0)
545
+ elif (
546
+ self.period_end_action == "sleep"
547
+ and today != self.FRIDAY
548
+ or not closing
549
+ ):
550
+ sleep_time = self.trades_instances[self.symbols[-1]].sleep_time()
551
+ self._sleepmsg(sleep_time + self.delay)
552
+ time.sleep(60 * sleep_time + self.delay)
553
+ logger.info(sessionmsg)
554
+ elif self.period_end_action == "sleep" and today == self.FRIDAY:
555
+ sleep_time = self.trades_instances[self.symbols[-1]].sleep_time(
556
+ weekend=True
557
+ )
558
+ self._sleepmsg(sleep_time + self.delay)
559
+ time.sleep(60 * sleep_time + self.delay)
560
+ logger.info(sessionmsg)
561
+
562
+ elif period_type == "weekly":
563
+ if today != self.FRIDAY:
564
+ sleep_time = self.trades_instances[self.symbols[-1]].sleep_time()
565
+ self._sleepmsg(sleep_time + self.delay)
566
+ time.sleep(60 * sleep_time + self.delay)
567
+ logger.info(sessionmsg)
568
+ elif today == self.FRIDAY:
569
+ self.strategy.perform_period_end_checks()
570
+ if self.period_end_action == "break" and closing:
571
+ exit(0)
572
+ elif self.period_end_action == "sleep" or not closing:
573
+ sleep_time = self.trades_instances[self.symbols[-1]].sleep_time(
574
+ weekend=True
575
+ )
576
+ self._sleepmsg(sleep_time + self.delay)
577
+ time.sleep(60 * sleep_time + self.delay)
578
+ logger.info(sessionmsg)
579
+
580
+ elif period_type == "monthly":
581
+ if today != self.FRIDAY:
582
+ sleep_time = self.trades_instances[self.symbols[-1]].sleep_time()
583
+ self._sleepmsg(sleep_time + self.delay)
584
+ time.sleep(60 * sleep_time + self.delay)
585
+ logger.info(sessionmsg)
586
+ elif today == self.FRIDAY:
587
+ self.strategy.perform_period_end_checks()
588
+ if self.period_end_action == "break" and closing:
589
+ exit(0)
590
+ elif self.period_end_action == "sleep" or not closing:
591
+ sleep_time = self.trades_instances[self.symbols[-1]].sleep_time(
592
+ weekend=True
593
+ )
594
+ self._sleepmsg(sleep_time + self.delay)
595
+ time.sleep(60 * sleep_time + self.delay)
596
+ logger.info(sessionmsg)
597
+
598
+ self.time_intervals = 0
599
+
600
+ def _check(self, buys, sells, symbol):
601
+ if not self.mm:
602
+ return
603
+ if buys is not None or sells is not None:
604
+ self.trades_instances[symbol].break_even(
605
+ mm=self.mm,
606
+ trail=self.trail,
607
+ stop_trail=self.stop_trail,
608
+ trail_after_points=self.trail_after_points,
609
+ be_plus_points=self.be_plus_points,
610
+ )
611
+
612
+ def _open_buy(
613
+ self, signal, symbol, id, trade: Trade, price, stoplimit, sigmsg, msg, comment
614
+ ):
615
+ if self.notify:
616
+ self._send_notification(sigmsg, symbol)
617
+ if not self._check_retcode(trade, "BMKT"):
618
+ logger.info(msg)
619
+ trade.open_buy_position(
620
+ action=signal,
621
+ price=price,
622
+ stoplimit=stoplimit,
623
+ id=id,
624
+ mm=self.mm,
625
+ trail=self.trail,
626
+ comment=comment,
627
+ )
628
+
629
+ def _open_sell(
630
+ self, signal, symbol, id, trade: Trade, price, stoplimit, sigmsg, msg, comment
631
+ ):
632
+ if self.notify:
633
+ self._send_notification(sigmsg, symbol)
634
+ if not self._check_retcode(trade, "SMKT"):
635
+ logger.info(msg)
636
+ trade.open_sell_position(
637
+ action=signal,
638
+ price=price,
639
+ stoplimit=stoplimit,
640
+ id=id,
641
+ mm=self.mm,
642
+ trail=self.trail,
643
+ comment=comment,
644
+ )
645
+
646
+ def _handle_exit_signals(self, signal, symbol, id, trade: Trade, sigmsg, comment):
647
+ for exit_signal, actions in EXIT_SIGNAL_ACTIONS.items():
648
+ if signal == exit_signal:
649
+ for signal_attr, order_type in actions.items():
650
+ clos_func = getattr(
651
+ self.trades_instances[symbol], f"get_current_{signal_attr}"
652
+ )
653
+ if clos_func(id=id) is not None:
654
+ if self.notify:
655
+ self._send_notification(sigmsg, symbol)
656
+ close_method = (
657
+ trade.close_positions
658
+ if signal_attr in POSITIONS_TYPES
659
+ else trade.close_orders
660
+ )
661
+ close_method(order_type, id=id, comment=comment)
662
+
663
+ def _handle_buy_signal(
664
+ self,
665
+ signal,
666
+ symbol,
667
+ id,
668
+ trade,
669
+ price,
670
+ stoplimit,
671
+ buys,
672
+ sells,
673
+ sigmsg,
674
+ msg,
675
+ tfmsg,
676
+ riskmsg,
677
+ comment,
678
+ ):
679
+ if not self.long_market[symbol]:
680
+ if self.use_trade_time:
681
+ if self.time_intervals % self.trade_time == 0 or buys[symbol] is None:
682
+ self._open_buy(
683
+ signal,
684
+ symbol,
685
+ id,
686
+ trade,
687
+ price,
688
+ stoplimit,
689
+ sigmsg,
690
+ msg,
691
+ comment,
692
+ )
693
+ else:
694
+ logger.info(tfmsg)
695
+ self._check(buys[symbol], sells[symbol], symbol)
696
+ else:
697
+ self._open_buy(
698
+ signal, symbol, id, trade, price, stoplimit, sigmsg, msg, comment
699
+ )
700
+ else:
701
+ logger.info(riskmsg)
702
+
703
+ def _handle_sell_signal(
704
+ self,
705
+ signal,
706
+ symbol,
707
+ id,
708
+ trade,
709
+ price,
710
+ stoplimit,
711
+ buys,
712
+ sells,
713
+ sigmsg,
714
+ msg,
715
+ tfmsg,
716
+ riskmsg,
717
+ comment,
718
+ ):
719
+ if not self.short_market[symbol]:
720
+ if self.use_trade_time:
721
+ if self.time_intervals % self.trade_time == 0 or sells[symbol] is None:
722
+ self._open_sell(
723
+ signal,
724
+ symbol,
725
+ id,
726
+ trade,
727
+ price,
728
+ stoplimit,
729
+ sigmsg,
730
+ msg,
731
+ comment,
732
+ )
733
+ else:
734
+ logger.info(tfmsg)
735
+ self._check(buys[symbol], sells[symbol], symbol)
736
+ else:
737
+ self._open_sell(
738
+ signal, symbol, id, trade, price, stoplimit, sigmsg, msg, comment
739
+ )
740
+ else:
741
+ logger.info(riskmsg)
742
+
743
+ def _run_trade_algorithm(
744
+ self,
745
+ signal,
746
+ symbol,
747
+ id,
748
+ trade,
749
+ price,
750
+ stoplimit,
751
+ buys,
752
+ sells,
753
+ comment,
754
+ ):
755
+ signal = {"LONG": "BMKT", "BUY": "BMKT", "SHORT": "SMKT", "SELL": "SMKT"}.get(
756
+ signal, signal
757
+ )
758
+ info, sigmsg, msg, tfmsg, riskmsg = self._get_signal_info(
759
+ signal, symbol, price, stoplimit
760
+ )
761
+
762
+ if signal not in EXIT_SIGNAL_ACTIONS:
763
+ if signal in NON_EXEC_RETCODES and not self._check_retcode(trade, signal):
764
+ logger.info(info)
765
+ elif signal not in NON_EXEC_RETCODES:
766
+ logger.info(info)
767
+
768
+ if signal in EXIT_SIGNAL_ACTIONS:
769
+ self._handle_exit_signals(signal, symbol, id, trade, sigmsg, comment)
770
+ elif signal in BUYS:
771
+ self._handle_buy_signal(
772
+ signal,
773
+ symbol,
774
+ id,
775
+ trade,
776
+ price,
777
+ stoplimit,
778
+ buys,
779
+ sells,
780
+ sigmsg,
781
+ msg,
782
+ tfmsg,
783
+ riskmsg,
784
+ comment,
785
+ )
786
+ elif signal in SELLS:
787
+ self._handle_sell_signal(
788
+ signal,
789
+ symbol,
790
+ id,
791
+ trade,
792
+ price,
793
+ stoplimit,
794
+ buys,
795
+ sells,
796
+ sigmsg,
797
+ msg,
798
+ tfmsg,
799
+ riskmsg,
800
+ comment,
801
+ )
802
+
803
+ def _is_closing(self):
804
+ closing = True
805
+ if self.closing_pnl is not None:
806
+ closing = all(
807
+ trade.positive_profit(id=trade.expert_id, th=self.closing_pnl)
808
+ for trade in self.trades_instances.values()
809
+ )
810
+ return closing
811
+
812
+ def _sleep(self):
813
+ time.sleep((60 * self.iter_time) - 1.0)
814
+ if self.iter_time == 1:
815
+ self.time_intervals += 1
816
+ elif self.trade_time % self.iter_time == 0:
817
+ self.time_intervals += self.iter_time
818
+ else:
819
+ if self.use_trade_time:
820
+ raise ValueError(
821
+ f"iter_time must be a multiple of the {self.time_frame} !!!"
822
+ f"(e.g., if time_frame is 15m, iter_time must be 1.5, 3, 5, 15 etc)"
823
+ )
824
+
825
+ def _get_signals_and_weights(self):
826
+ try:
827
+ check_mt5_connection(**self.kwargs)
828
+ signals = self.strategy.calculate_signals()
829
+ weights = (
830
+ self.strategy.apply_risk_management(self.optimizer)
831
+ if hasattr(self.strategy, "apply_risk_management")
832
+ else None
833
+ )
834
+ return signals, weights
835
+ except Exception as e:
836
+ self._print_exc(
837
+ f"Calculating Signals, STRATEGY={self.STRATEGY} , ACCOUNT={self.ACCOUNT}",
838
+ e,
839
+ )
840
+ pass
841
+
842
+ def _handle_signals(self, today, signals, buys, sells):
843
+ try:
844
+ check_mt5_connection(**self.kwargs)
845
+ for signal in signals:
846
+ symbol = signal.symbol
847
+ trade: Trade = self.trades_instances[symbol]
848
+ if trade.trading_time() and today in self.trading_days:
849
+ if signal.action is not None:
850
+ action = (
851
+ signal.action.value
852
+ if isinstance(signal.action, TradeAction)
853
+ else signal.action
854
+ )
855
+ self._run_trade_algorithm(
856
+ action,
857
+ symbol,
858
+ signal.id,
859
+ trade,
860
+ signal.price,
861
+ signal.stoplimit,
862
+ buys,
863
+ sells,
864
+ signal.comment or self.comment,
865
+ )
866
+ else:
867
+ if len(self.symbols) >= 10:
868
+ if symbol == self.symbols[-1]:
869
+ logger.info(
870
+ f"Not trading Time !!!, STRATEGY={self.STRATEGY} , ACCOUNT={self.ACCOUNT}"
871
+ )
872
+ else:
873
+ logger.info(
874
+ f"Not trading Time !!! SYMBOL={trade.symbol}, STRATEGY={self.STRATEGY} , ACCOUNT={self.ACCOUNT}"
875
+ )
876
+ self._check(buys[symbol], sells[symbol], symbol)
877
+
878
+ except Exception as e:
879
+ msg = f"Handling Signals, SYMBOL={symbol}, STRATEGY={self.STRATEGY} , ACCOUNT={self.ACCOUNT}"
880
+ self._print_exc(msg, e)
881
+ pass
882
+
883
+ def _handle_period_end_actions(self, today):
884
+ try:
885
+ check_mt5_connection(**self.kwargs)
886
+ day_end = all(trade.days_end() for trade in self.trades_instances.values())
887
+ closing = self._is_closing()
888
+ sessionmsg = f"{self.ACCOUNT} STARTING NEW TRADING SESSION ...\n"
889
+ self._perform_period_end_actions(
890
+ today,
891
+ day_end,
892
+ closing,
893
+ sessionmsg,
894
+ )
895
+ except KeyboardInterrupt:
896
+ logger.info("Stopping the Execution Engine ...")
897
+ quit()
898
+ except Exception as e:
899
+ msg = f"Handling period end actions, STRATEGY={self.STRATEGY} , ACCOUNT={self.ACCOUNT}"
900
+ self._print_exc(msg, e)
901
+ pass
902
+
903
+ def run(self):
904
+ while True:
905
+ try:
906
+ check_mt5_connection(**self.kwargs)
907
+ positions_orders = self._check_positions_orders()
908
+ if self.show_positions_orders:
909
+ self._display_positions_orders(positions_orders)
910
+ buys = positions_orders["buys"]
911
+ sells = positions_orders["sells"]
912
+ self.long_market, self.short_market = self._long_short_market(
913
+ buys, sells
914
+ )
915
+ signals, weights = self._get_signals_and_weights()
916
+ self._update_risk(weights)
917
+ today = datetime.now().strftime("%A").lower()
918
+ if len(signals) == 0:
919
+ for symbol in self.symbols:
920
+ self._check(buys[symbol], sells[symbol], symbol)
921
+ else:
922
+ self._handle_signals(today, signals, buys, sells)
923
+ self._sleep()
924
+ self._handle_period_end_actions(today)
925
+ except KeyboardInterrupt:
926
+ logger.info("Stopping the Execution Engine ...")
927
+ break
928
+ except Exception as e:
929
+ msg = f"Running Execution Engine, STRATEGY={self.STRATEGY} , ACCOUNT={self.ACCOUNT}"
930
+ self._print_exc(msg, e)
931
+ continue
932
+
933
+
934
+ def RunMt5Engine(account_id: str, **kwargs):
794
935
  """Starts an MT5 execution engine for a given account.
795
936
  Args:
796
937
  account_id: Account ID to run the execution engine on.
@@ -810,23 +951,23 @@ def mt5_engine(account_id: str, **kwargs):
810
951
  raise ValueError(f"Missing required arguments for account {account_id}")
811
952
 
812
953
  try:
813
- engine = MT5ExecutionEngine(
954
+ engine = Mt5ExecutionEngine(
814
955
  symbol_list, trades_instances, strategy_cls, **kwargs
815
956
  )
816
957
  engine.run()
817
958
  except Exception as e:
818
959
  log.exception(f"Error running execution engine for {account_id}: {e}")
819
960
  finally:
820
- log.info(f"Execution engine for {account_id} completed")
961
+ log.info(f"Execution for {account_id} completed")
821
962
 
822
963
 
823
964
  def RunMt5Engines(accounts: Dict[str, Dict], start_delay: float = 1.0):
824
965
  """Runs multiple MT5 execution engines in parallel using multiprocessing.
825
-
966
+
826
967
  Args:
827
968
  accounts: Dictionary of accounts to run the execution engines on.
828
969
  Keys are the account names or IDs and values are the parameters for the execution engine.
829
- The parameters are the same as the ones passed to the `MT5ExecutionEngine` class.
970
+ The parameters are the same as the ones passed to the `Mt5ExecutionEngine` class.
830
971
  start_delay: Delay in seconds between starting the processes. Defaults to 1.0.
831
972
  """
832
973
 
@@ -834,7 +975,7 @@ def RunMt5Engines(accounts: Dict[str, Dict], start_delay: float = 1.0):
834
975
 
835
976
  for account_id, params in accounts.items():
836
977
  log.info(f"Starting process for {account_id}")
837
- process = mp.Process(target=mt5_engine, args=(account_id,), kwargs=params)
978
+ process = mp.Process(target=RunMt5Engine, args=(account_id,), kwargs=params)
838
979
  process.start()
839
980
  processes[process] = account_id
840
981