bbstrader 0.2.0__py3-none-any.whl → 0.2.1__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,19 +1,15 @@
1
1
  import time
2
+ import traceback
3
+ import MetaTrader5 as MT5
2
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
3
10
  from bbstrader.metatrader.trade import Trade
4
- from bbstrader.btengine.strategy import(
5
- Strategy,
6
- MT5Strategy
7
- )
8
11
  from bbstrader.trading.scripts import send_message
9
- from bbstrader.metatrader.account import check_mt5_connection
10
- from typing import (
11
- Optional,
12
- Literal,
13
- Tuple,
14
- List,
15
- Dict
16
- )
12
+ from bbstrader.core.utils import TradeAction
17
13
 
18
14
  __all__ = [
19
15
  'MT5ExecutionEngine',
@@ -37,23 +33,23 @@ _TF_MAPPING = {
37
33
  }
38
34
 
39
35
  TradingDays = [
40
- 'monday',
41
- 'tuesday',
42
- 'wednesday',
43
- 'thursday',
36
+ 'monday',
37
+ 'tuesday',
38
+ 'wednesday',
39
+ 'thursday',
44
40
  'friday'
45
41
  ]
46
42
 
47
- BUYS = ['BMKT', 'BLMT', 'BSTP', 'BSTPLMT']
48
- SELLS = ['SMKT', 'SLMT', 'SSTP', 'SSTPLMT']
43
+ BUYS = ['BMKT', 'BLMT', 'BSTP', 'BSTPLMT']
44
+ SELLS = ['SMKT', 'SLMT', 'SSTP', 'SSTPLMT']
49
45
 
50
- ORDERS_TYPES = ["orders","buy_stops", "sell_stops", "buy_limits",
51
- "sell_limits", "buy_stop_limits", "sell_stop_limits"]
46
+ ORDERS_TYPES = ["orders", "buy_stops", "sell_stops", "buy_limits",
47
+ "sell_limits", "buy_stop_limits", "sell_stop_limits"]
52
48
  POSITIONS_TYPES = ["positions", "buys", "sells", "profitables", "losings"]
53
49
 
54
- ACTIONS = ["buys", "sells"]
55
- STOPS = ["buy_stops", "sell_stops"]
56
- LIMITS = ["buy_limits", "sell_limits"]
50
+ ACTIONS = ["buys", "sells"]
51
+ STOPS = ["buy_stops", "sell_stops"]
52
+ LIMITS = ["buy_limits", "sell_limits"]
57
53
  STOP_LIMITS = ["buy_stop_limits", "sell_stop_limits"]
58
54
 
59
55
  EXIT_SIGNAL_ACTIONS = {
@@ -68,33 +64,60 @@ EXIT_SIGNAL_ACTIONS = {
68
64
  "EXIT_SHORT_LIMIT": {"sell_limits": "sell_limits"},
69
65
  "EXIT_STOP_LIMIT": {sl: sl for sl in STOP_LIMITS},
70
66
  "EXIT_LONG_STOP_LIMIT": {STOP_LIMITS[0]: STOP_LIMITS[0]},
71
- "EXIT_SHORT_STOP_LIMIT":{STOP_LIMITS[1]: STOP_LIMITS[1]},
72
- "EXIT_PROFITABLES": {"profitables": "profitables"},
73
- "EXIT_LOSINGS": {"losings": "losings"},
67
+ "EXIT_SHORT_STOP_LIMIT": {STOP_LIMITS[1]: STOP_LIMITS[1]},
68
+ "EXIT_PROFITABLES": {"profitables": "profitable"},
69
+ "EXIT_LOSINGS": {"losings": "losing"},
74
70
  "EXIT_ALL_POSITIONS": {"positions": "all"},
75
71
  "EXIT_ALL_ORDERS": {"orders": "all"}
76
72
  }
77
73
 
74
+ COMMON_RETCODES = [
75
+ MT5.TRADE_RETCODE_MARKET_CLOSED,
76
+ MT5.TRADE_RETCODE_CLOSE_ONLY
77
+ ]
78
+
79
+ NON_EXEC_RETCODES = {
80
+ 'BMKT': [MT5.TRADE_RETCODE_SHORT_ONLY] + COMMON_RETCODES,
81
+ 'SMKT': [MT5.TRADE_RETCODE_LONG_ONLY] + COMMON_RETCODES
82
+ }
83
+
78
84
 
79
85
  def _mt5_execution(
80
- symbol_list, trades_instances, strategy_cls, /,
81
- mm, optimizer, trail, stop_trail, trail_after_points, be_plus_points, show_positions_orders,
82
- iter_time, use_trade_time, period, period_end_action, closing_pnl, trading_days,
83
- comment, **kwargs):
84
- symbols = symbol_list.copy()
86
+ symbol_list,
87
+ trades_instances,
88
+ strategy_cls, /,
89
+ mm,
90
+ optimizer,
91
+ trail,
92
+ stop_trail,
93
+ trail_after_points,
94
+ be_plus_points,
95
+ show_positions_orders,
96
+ iter_time,
97
+ use_trade_time,
98
+ period,
99
+ period_end_action,
100
+ closing_pnl,
101
+ trading_days,
102
+ comment,
103
+ **kwargs
104
+ ):
105
+ symbols = symbol_list.copy()
85
106
  time_frame = kwargs.get('time_frame', '15m')
86
107
  STRATEGY = kwargs.get('strategy_name')
87
- mtrades = kwargs.get('max_trades')
88
- check_max_trades = kwargs.get('check_max_trades', True)
89
- notify = kwargs.get('notify', False)
108
+ mtrades = kwargs.get('max_trades')
109
+ notify = kwargs.get('notify', False)
90
110
  if notify:
91
- telegram = kwargs.get('telegram', False)
111
+ telegram = kwargs.get('telegram', False)
92
112
  bot_token = kwargs.get('bot_token')
93
- chat_id = kwargs.get('chat_id')
113
+ chat_id = kwargs.get('chat_id')
94
114
 
95
115
  expert_ids = kwargs.get('expert_id')
96
116
  if expert_ids is None:
97
- expert_ids = set([trade.expert_id for trade in trades_instances.values()])
117
+ expert_ids = list(
118
+ set([trade.expert_id for trade in trades_instances.values()]))
119
+ elif isinstance(expert_ids, int):
120
+ expert_ids = [expert_ids]
98
121
 
99
122
  def update_risk(weights):
100
123
  if weights is not None:
@@ -104,11 +127,18 @@ def _mt5_execution(
104
127
  trade = trades_instances[symbol]
105
128
  trade.dailydd = round(weights[symbol], 5)
106
129
 
130
+ def check_retcode(trade: Trade, position):
131
+ if len(trade.retcodes) > 0:
132
+ for retcode in trade.retcodes:
133
+ if retcode in NON_EXEC_RETCODES[position]:
134
+ return True
135
+ return False
136
+
107
137
  def _send_notification(signal):
108
- send_message(message=signal, notify_me=notify,
109
- telegram=telegram, token=bot_token, chat_id=chat_id)
110
-
111
- logger = trades_instances[symbols[0]].logger
138
+ send_message(message=signal, notify_me=notify,
139
+ telegram=telegram, token=bot_token, chat_id=chat_id)
140
+
141
+ logger: Logger = trades_instances[symbols[0]].logger
112
142
  max_trades = {symbol: mtrades[symbol]
113
143
  if mtrades is not None and symbol in mtrades
114
144
  else trades_instances[symbol].max_trade() for symbol in symbols}
@@ -121,23 +151,98 @@ def _mt5_execution(
121
151
  return
122
152
  if buys is not None or sells is not None:
123
153
  trades_instances[symbol].break_even(
124
- mm=mm, trail=trail, stop_trail=stop_trail,
154
+ mm=mm, trail=trail, stop_trail=stop_trail,
125
155
  trail_after_points=trail_after_points, be_plus_points=be_plus_points)
126
- num_days = 0
127
- time_intervals = 0
128
- trade_time = _TF_MAPPING[time_frame]
156
+ num_days = 0
157
+ time_intervals = 0
158
+ trade_time = _TF_MAPPING[time_frame]
129
159
 
130
- long_market = {symbol: False for symbol in symbols}
131
- short_market = {symbol: False for symbol in symbols}
160
+ long_market = {symbol: False for symbol in symbols}
161
+ short_market = {symbol: False for symbol in symbols}
132
162
  try:
133
163
  check_mt5_connection(**kwargs)
134
- strategy: MT5Strategy = strategy_cls(symbol_list=symbols, mode='live', **kwargs)
135
- except Exception as e:
136
- logger.error(f"Initializing strategy, {e}, STRATEGY={STRATEGY}")
164
+ strategy: MT5Strategy = strategy_cls(
165
+ symbol_list=symbols, mode='live', **kwargs)
166
+ except Exception:
167
+ logger.error(f"Initializing strategy, STRATEGY={STRATEGY}")
168
+ traceback.print_exc()
137
169
  return
138
170
  logger.info(
139
171
  f'Running {STRATEGY} Strategy in {time_frame} Interval ...')
140
-
172
+
173
+ def run_trade_algorithm(signal, symbol, id, trade: Trade, price, stoplimit):
174
+ signal = 'BMKT' if signal == 'LONG' else signal
175
+ signal = 'SMKT' if signal == 'SHORT' else signal
176
+ info = f"SIGNAL = {signal}, SYMBOL={symbol}, STRATEGY={STRATEGY}, TIMEFRAME={time_frame}"
177
+ msg = f"Sending {signal} Order ... SYMBOL={symbol}, STRATEGY={STRATEGY}"
178
+ tfmsg = f"Time Frame Not completed !!! SYMBOL={symbol}, STRATEGY={STRATEGY}"
179
+ riskmsg = f"Risk not allowed !!! SYMBOL={symbol}, STRATEGY={STRATEGY}"
180
+ if signal not in EXIT_SIGNAL_ACTIONS:
181
+ if signal in NON_EXEC_RETCODES and not check_retcode(trade, signal):
182
+ logger.info(info)
183
+ elif signal not in NON_EXEC_RETCODES:
184
+ logger.info(info)
185
+ if signal in EXIT_SIGNAL_ACTIONS:
186
+ for exit_signal, actions in EXIT_SIGNAL_ACTIONS.items():
187
+ for position_type, order_type in actions.items():
188
+ clos_func = getattr(
189
+ trades_instances[symbol], f"get_current_{position_type}")
190
+ if clos_func(id=id) is not None:
191
+ if notify:
192
+ _send_notification(info)
193
+ if position_type in POSITIONS_TYPES:
194
+ trade.close_positions(
195
+ position_type=order_type, id=id)
196
+ else:
197
+ trade.close_orders(order_type=order_type, id=id)
198
+ elif signal in BUYS and not long_market[symbol]:
199
+ if use_trade_time:
200
+ if time_intervals % trade_time == 0 or buys[symbol] is None:
201
+ if notify:
202
+ _send_notification(info)
203
+ if not check_retcode(trade, 'BMKT'):
204
+ logger.info(msg)
205
+ trade.open_buy_position(
206
+ action=signal, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
207
+ else:
208
+ logger.info(tfmsg)
209
+ check(buys[symbol], sells[symbol], symbol)
210
+ else:
211
+ if notify:
212
+ _send_notification(info)
213
+ if not check_retcode(trade, 'BMKT'):
214
+ logger.info(msg)
215
+ trade.open_buy_position(
216
+ action=signal, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
217
+
218
+ elif signal in BUYS and long_market[symbol]:
219
+ logger.info(riskmsg)
220
+
221
+ elif signal in SELLS and not short_market[symbol]:
222
+ if use_trade_time:
223
+ if time_intervals % trade_time == 0 or sells[symbol] is None:
224
+ if notify:
225
+ _send_notification(info)
226
+ if not check_retcode(trade, 'SMKT'):
227
+ logger.info(msg)
228
+ trade.open_sell_position(
229
+ action=signal, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
230
+ else:
231
+ logger.info(tfmsg)
232
+ check(buys[symbol], sells[symbol], symbol)
233
+ else:
234
+ if notify:
235
+ _send_notification(info)
236
+ if not check_retcode(trade, 'SMKT'):
237
+ logger.info(msg)
238
+ trade.open_sell_position(
239
+ action=signal, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
240
+
241
+ elif signal in SELLS and short_market[symbol]:
242
+ logger.info(riskmsg)
243
+ else:
244
+ check(buys[symbol], sells[symbol], symbol)
245
+
141
246
  while True:
142
247
  try:
143
248
  check_mt5_connection(**kwargs)
@@ -150,7 +255,8 @@ def _mt5_execution(
150
255
  for symbol in symbols:
151
256
  positions_orders[type][symbol] = None
152
257
  for id in expert_ids:
153
- func = getattr(trades_instances[symbol], f"get_current_{type}")
258
+ func = getattr(
259
+ trades_instances[symbol], f"get_current_{type}")
154
260
  func_value = func(id=id)
155
261
  if func_value is not None:
156
262
  if positions_orders[type][symbol] is None:
@@ -166,120 +272,60 @@ def _mt5_execution(
166
272
  logger.info(
167
273
  f"Current {type.upper()} SYMBOL={symbol}: \
168
274
  {positions_orders[type][symbol]}, STRATEGY={STRATEGY}")
169
- if check_max_trades:
170
- long_market = {
171
- symbol: buys[symbol] is not None
172
- and len(buys[symbol]) >= max_trades[symbol] for symbol in symbols
173
- }
174
- short_market = {
175
- symbol: sells[symbol] is not None
176
- and len(sells[symbol]) >= max_trades[symbol] for symbol in symbols
177
- }
178
-
179
- except Exception as e:
180
- logger.error(f"Handling Positions and Orders, {e}, STRATEGY={STRATEGY}")
275
+ long_market = {
276
+ symbol: buys[symbol] is not None
277
+ and len(buys[symbol]) >= max_trades[symbol] for symbol in symbols
278
+ }
279
+ short_market = {
280
+ symbol: sells[symbol] is not None
281
+ and len(sells[symbol]) >= max_trades[symbol] for symbol in symbols
282
+ }
283
+
284
+ except Exception:
285
+ logger.error(
286
+ f"Handling Positions and Orders, STRATEGY={STRATEGY}")
287
+ traceback.print_exc()
181
288
  continue
182
289
  time.sleep(0.5)
183
290
  try:
184
291
  check_mt5_connection(**kwargs)
185
292
  signals = strategy.calculate_signals()
186
- weights = None
187
- if hasattr(strategy, 'apply_risk_management'):
188
- weights = strategy.apply_risk_management(optimizer)
293
+ weights = (
294
+ strategy.apply_risk_management(optimizer)
295
+ if hasattr(strategy, 'apply_risk_management')
296
+ else None
297
+ )
189
298
  update_risk(weights)
190
- except Exception as e:
191
- logger.error(f"Calculating signal, {e}, STRATEGY={STRATEGY}")
299
+ except Exception:
300
+ logger.error(f"Calculating signal, STRATEGY={STRATEGY}")
301
+ traceback.print_exc()
192
302
  continue
193
- for symbol in symbols:
303
+ for signal in signals:
194
304
  try:
195
305
  check_mt5_connection(**kwargs)
306
+ symbol = signal.symbol
196
307
  trade: Trade = trades_instances[symbol]
197
- tfmsg = f"Time Frame Not completed !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}"
198
- riskmsg = f"Risk not allowed !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}"
199
- signal = signals[symbol]
200
- if isinstance(signal, dict):
201
- action = signal.get('action')
202
- price = signal.get('price')
203
- id = signal.get('id', trade.expert_id)
204
- stoplimit = signal.get('stoplimit')
205
- signal = action
206
- elif isinstance(signal, str):
207
- price = None
208
- stoplimit = None
209
- id = trade.expert_id
210
308
  if trade.trading_time() and today in trading_days:
211
- if signal is not None:
212
- signal = 'BMKT' if signal == 'LONG' else signal
213
- signal = 'SMKT' if signal == 'SHORT' else signal
214
- info = f"SIGNAL = {signal}, SYMBOL={trade.symbol}, STRATEGY={STRATEGY}, TIMEFRAME={time_frame}"
215
- msg = f"Sending {signal} Order ... SYMBOL={trade.symbol}, STRATEGY={STRATEGY}"
216
- if signal not in EXIT_SIGNAL_ACTIONS:
217
- logger.info(info)
218
- if signal in EXIT_SIGNAL_ACTIONS:
219
- for exit_signal, actions in EXIT_SIGNAL_ACTIONS.items():
220
- for position_type, order_type in actions.items():
221
- if positions_orders[position_type][symbol] is not None:
222
- if notify:
223
- _send_notification(info)
224
- if position_type in POSITIONS_TYPES:
225
- trade.close_positions(position_type=order_type, id=id)
226
- else:
227
- trade.close_orders(order_type=order_type, id=id)
228
- elif signal in BUYS and not long_market[symbol]:
229
- if use_trade_time:
230
- if time_intervals % trade_time == 0 or buys[symbol] is None:
231
- logger.info(msg)
232
- if notify:
233
- _send_notification(info)
234
- trade.open_buy_position(
235
- action=signal, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
236
- else:
237
- logger.info(tfmsg)
238
- check(buys[symbol], sells[symbol], symbol)
239
- else:
240
- logger.info(msg)
241
- if notify:
242
- _send_notification(info)
243
- trade.open_buy_position(
244
- action=signal, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
245
- elif signal in BUYS:
246
- if check_max_trades:
247
- if long_market[symbol]:
248
- logger.info(riskmsg)
249
- check(buys[symbol], sells[symbol], symbol)
250
-
251
- elif signal in SELLS and not short_market[symbol]:
252
- if use_trade_time:
253
- if time_intervals % trade_time == 0 or sells[symbol] is None:
254
- logger.info(msg)
255
- if notify:
256
- _send_notification(info)
257
- trade.open_sell_position(
258
- action=signal, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
259
- else:
260
- logger.info(tfmsg)
261
- check(buys[symbol], sells[symbol], symbol)
262
- else:
263
- logger.info(msg)
264
- if notify:
265
- _send_notification(info)
266
- trade.open_sell_position(
267
- action=signal, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
268
- elif signal in SELLS:
269
- if check_max_trades:
270
- if short_market[symbol]:
271
- logger.info(riskmsg)
272
- check(buys[symbol], sells[symbol], symbol)
273
- else:
274
- check(buys[symbol], sells[symbol], symbol)
309
+ action = (
310
+ signal.action.value if isinstance(signal.action, TradeAction)
311
+ else signal.action
312
+ )
313
+ run_trade_algorithm(
314
+ action, symbol, signal.id, trade, signal.price, signal.stoplimit)
315
+ elif len(symbols) > 10 and symbol == symbols[-1]:
316
+ logger.info(
317
+ f"Not trading Time !!!, STRATEGY={STRATEGY}")
275
318
  else:
276
319
  logger.info(
277
320
  f"Not trading Time !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
278
- check(buys[symbol], sells[symbol], symbol)
321
+ check(buys[symbol], sells[symbol], symbol)
279
322
 
280
- except Exception as e:
281
- logger.error(f"Handling Signals {e}, SYMBOL={symbol}, STRATEGY={STRATEGY}")
323
+ except Exception:
324
+ logger.error(
325
+ f"Handling Signals, SYMBOL={symbol}, STRATEGY={STRATEGY}")
326
+ traceback.print_exc()
282
327
  continue
328
+
283
329
  time.sleep((60 * iter_time) - 1.0)
284
330
  if iter_time == 1:
285
331
  time_intervals += 1
@@ -293,24 +339,35 @@ def _mt5_execution(
293
339
  try:
294
340
  FRIDAY = 'friday'
295
341
  check_mt5_connection(**kwargs)
296
- day_end = all(trade.days_end() for trade in trades_instances.values())
342
+ day_end = all(trade.days_end()
343
+ for trade in trades_instances.values())
297
344
  if closing_pnl is not None:
298
- closing = all(trade.positive_profit(id=trade.expert_id, th=closing_pnl)
299
- for trade in trades_instances.values())
300
- else: closing = True
301
- logmsg = lambda period: logger.info(
302
- f"End of the {period} !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
303
- sleepmsg = lambda sleep_time: logger.info(
345
+ closing = all(trade.positive_profit(id=trade.expert_id, th=closing_pnl)
346
+ for trade in trades_instances.values())
347
+ else:
348
+ closing = True
349
+
350
+ def logmsg(period, symbol): return logger.info(
351
+ f"End of the {period} !!! SYMBOL={symbol}, STRATEGY={STRATEGY}")
352
+
353
+ def logmsgif(period, symbol):
354
+ return (
355
+ logmsg(period, symbol) if len(symbols) <= 10 and symbol == symbols[-1]
356
+ else logger.info(f'Not trading Time !!! , STRATEGY={STRATEGY}')
357
+ )
358
+ def sleepmsg(sleep_time): return logger.info(
304
359
  f"Sleeping for {sleep_time} minutes ...\n")
305
- sessionmsg = f"STARTING NEW TRADING SESSION ...\n"
360
+ sessionmsg = "STARTING NEW TRADING SESSION ...\n"
306
361
  if period.lower() == 'day':
307
362
  for symbol in symbols:
308
363
  trade = trades_instances[symbol]
309
364
  if trade.days_end() and closing:
310
365
  for id in expert_ids:
311
- trade.close_positions(position_type='all', id=id, comment=comment)
312
- logmsg("Day")
366
+ trade.close_positions(
367
+ position_type='all', id=id, comment=comment)
368
+ logmsgif("Day", symbol)
313
369
  trade.statistics(save=True)
370
+ strategy.perform_period_end_checks()
314
371
  if day_end:
315
372
  if period_end_action == 'break' and closing:
316
373
  break
@@ -320,7 +377,8 @@ def _mt5_execution(
320
377
  time.sleep(60 * sleep_time)
321
378
  logger.info(sessionmsg)
322
379
  elif period_end_action == 'sleep' and today == FRIDAY:
323
- sleep_time = trades_instances[symbols[-1]].sleep_time(weekend=True)
380
+ sleep_time = trades_instances[symbols[-1]
381
+ ].sleep_time(weekend=True)
324
382
  sleepmsg(sleep_time)
325
383
  time.sleep(60 * sleep_time)
326
384
  logger.info(sessionmsg)
@@ -329,13 +387,15 @@ def _mt5_execution(
329
387
  for symbol in symbols:
330
388
  trade = trades_instances[symbol]
331
389
  if trade.days_end() and today != FRIDAY:
332
- logmsg("Day")
390
+ logmsgif("Day", symbol)
333
391
 
334
392
  elif trade.days_end() and today == FRIDAY and closing:
335
393
  for id in expert_ids:
336
- trade.close_positions(position_type='all', id=id, comment=comment)
337
- logmsg("Week")
394
+ trade.close_positions(
395
+ position_type='all', id=id, comment=comment)
396
+ logmsgif("Week", symbol)
338
397
  trade.statistics(save=True)
398
+ strategy.perform_period_end_checks()
339
399
  if day_end and today != FRIDAY:
340
400
  sleep_time = trades_instances[symbols[-1]].sleep_time()
341
401
  sleepmsg(sleep_time)
@@ -345,7 +405,8 @@ def _mt5_execution(
345
405
  if period_end_action == 'break' and closing:
346
406
  break
347
407
  elif period_end_action == 'sleep' or not closing:
348
- sleep_time = trades_instances[symbols[-1]].sleep_time(weekend=True)
408
+ sleep_time = trades_instances[symbols[-1]
409
+ ].sleep_time(weekend=True)
349
410
  sleepmsg(sleep_time)
350
411
  time.sleep(60 * sleep_time)
351
412
  logger.info(sessionmsg)
@@ -354,18 +415,20 @@ def _mt5_execution(
354
415
  for symbol in symbols:
355
416
  trade = trades_instances[symbol]
356
417
  if trade.days_end() and today != FRIDAY:
357
- logmsg("Day")
418
+ logmsgif("Day", symbol)
358
419
  elif trade.days_end() and today == FRIDAY:
359
- logmsg("Week")
420
+ logmsgif("Week", symbol)
360
421
  elif (
361
422
  trade.days_end()
362
423
  and today == FRIDAY
363
424
  and num_days/len(symbols) >= 20
364
425
  ) and closing:
365
426
  for id in expert_ids:
366
- trade.close_positions(position_type='all', id=id, comment=comment)
367
- logmsg("Month")
427
+ trade.close_positions(
428
+ position_type='all', id=id, comment=comment)
429
+ logmsgif("Month", symbol)
368
430
  trade.statistics(save=True)
431
+ strategy.perform_period_end_checks()
369
432
  if day_end and today != FRIDAY:
370
433
  sleep_time = trades_instances[symbols[-1]].sleep_time()
371
434
  sleepmsg(sleep_time)
@@ -374,7 +437,7 @@ def _mt5_execution(
374
437
  num_days += 1
375
438
  elif day_end and today == FRIDAY:
376
439
  sleep_time = trades_instances[symbols[-1]
377
- ].sleep_time(weekend=True)
440
+ ].sleep_time(weekend=True)
378
441
  sleepmsg(sleep_time)
379
442
  time.sleep(60 * sleep_time)
380
443
  logger.info(sessionmsg)
@@ -382,10 +445,12 @@ def _mt5_execution(
382
445
  elif (day_end
383
446
  and today == FRIDAY
384
447
  and num_days/len(symbols) >= 20
385
- ):
448
+ ):
386
449
  break
387
- except Exception as e:
388
- logger.error(f"Handling period end actions, {e}, STRATEGY={STRATEGY}")
450
+ except Exception:
451
+ logger.error(
452
+ f"Handling period end actions, STRATEGY={STRATEGY}")
453
+ traceback.print_exc()
389
454
  continue
390
455
 
391
456
 
@@ -411,7 +476,7 @@ class MT5ExecutionEngine():
411
476
 
412
477
  Examples
413
478
  --------
414
-
479
+
415
480
  >>> from bbstrader.metatrader import create_trade_instance
416
481
  >>> from bbstrader.trading.execution import MT5ExecutionEngine
417
482
  >>> from bbstrader.trading.strategies import StockIndexCFDTrading
@@ -487,7 +552,7 @@ class MT5ExecutionEngine():
487
552
  trading_days: Optional[List[str]] = TradingDays,
488
553
  comment: Optional[str] = None,
489
554
  **kwargs
490
- ):
555
+ ):
491
556
  """
492
557
  Args:
493
558
  symbol_list : List of symbols to trade
@@ -518,7 +583,7 @@ class MT5ExecutionEngine():
518
583
  1. For `trail` , `stop_trail` , `trail_after_points` , `be_plus_points` see `bbstrader.metatrader.trade.Trade.break_even()` .
519
584
  2. All Strategies must inherit from `bbstrader.btengine.strategy.Strategy` or `bbstrader.btengine.strategy.MT5Strategy` class
520
585
  and have a `calculate_signals` method that returns a dictionary of signals for each symbol in symbol_list.
521
-
586
+
522
587
  3. All strategies must have the following arguments in their `__init__` method:
523
588
  - bars (DataHandler): DataHandler instance default to None
524
589
  - events (Queue): Queue instance default to None
@@ -528,7 +593,7 @@ class MT5ExecutionEngine():
528
593
  The keyword arguments are all the additional arguments passed to the `MT5ExecutionEngine` class,
529
594
  the `Strategy` class, the `DataHandler` class, the `Portfolio` class and the `ExecutionHandler` class.
530
595
  - The `bars` and `events` arguments are used for backtesting only.
531
-
596
+
532
597
  4. All strategies must generate signals for backtesting and live trading.
533
598
  See the `bbstrader.trading.strategies` module for more information on how to create custom strategies.
534
599
  See `bbstrader.metatrader.account.check_mt5_connection()` for more details on how to connect to MT5 terminal.
@@ -556,7 +621,7 @@ class MT5ExecutionEngine():
556
621
  trades = self.trades_instances.keys()
557
622
  s = self.strategy_cls.__name__
558
623
  return f"MT5ExecutionEngine(Symbols={list(trades)}, Strategy={s})"
559
-
624
+
560
625
  def run(self):
561
626
  check_mt5_connection(**self.kwargs)
562
627
  _mt5_execution(
@@ -581,4 +646,5 @@ class MT5ExecutionEngine():
581
646
  )
582
647
 
583
648
 
584
- class TWSExecutionEngine():...
649
+ class TWSExecutionEngine():
650
+ ...
@@ -1,10 +1,12 @@
1
1
  import asyncio
2
- from telegram import Bot
2
+
3
3
  from notifypy import Notify
4
+ from telegram import Bot
4
5
  from telegram.error import TelegramError
5
6
 
6
7
  __all__ = ['send_telegram_message', 'send_notification', 'send_message']
7
8
 
9
+
8
10
  async def send_telegram_message(token, chat_id, text=''):
9
11
  """
10
12
  Send a message to a telegram chat
@@ -23,6 +25,7 @@ async def send_telegram_message(token, chat_id, text=''):
23
25
  except TelegramError as e:
24
26
  print(f"Error sending message: {e}")
25
27
 
28
+
26
29
  def send_notification(title, message=''):
27
30
  """
28
31
  Send a desktop notification
@@ -35,9 +38,10 @@ def send_notification(title, message=''):
35
38
  notification.title = title
36
39
  notification.message = message
37
40
  notification.send()
38
-
39
- def send_message(title='SIGNAL', message='New signal',
40
- notify_me=False, telegram=False, token=None, chat_id=None):
41
+
42
+
43
+ def send_message(title='SIGNAL', message='New signal',
44
+ notify_me=False, telegram=False, token=None, chat_id=None):
41
45
  """
42
46
  Send a message to the user
43
47