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

Files changed (38) hide show
  1. bbstrader/__ini__.py +9 -9
  2. bbstrader/btengine/__init__.py +7 -7
  3. bbstrader/btengine/backtest.py +30 -26
  4. bbstrader/btengine/data.py +100 -79
  5. bbstrader/btengine/event.py +2 -1
  6. bbstrader/btengine/execution.py +18 -16
  7. bbstrader/btengine/performance.py +11 -7
  8. bbstrader/btengine/portfolio.py +35 -36
  9. bbstrader/btengine/strategy.py +119 -94
  10. bbstrader/config.py +14 -8
  11. bbstrader/core/__init__.py +0 -0
  12. bbstrader/core/data.py +22 -0
  13. bbstrader/core/utils.py +57 -0
  14. bbstrader/ibkr/__init__.py +0 -0
  15. bbstrader/ibkr/utils.py +0 -0
  16. bbstrader/metatrader/__init__.py +5 -5
  17. bbstrader/metatrader/account.py +117 -121
  18. bbstrader/metatrader/rates.py +83 -80
  19. bbstrader/metatrader/risk.py +23 -37
  20. bbstrader/metatrader/trade.py +169 -140
  21. bbstrader/metatrader/utils.py +3 -3
  22. bbstrader/models/__init__.py +5 -5
  23. bbstrader/models/factors.py +280 -0
  24. bbstrader/models/ml.py +1092 -0
  25. bbstrader/models/optimization.py +31 -28
  26. bbstrader/models/{portfolios.py → portfolio.py} +64 -46
  27. bbstrader/models/risk.py +15 -9
  28. bbstrader/trading/__init__.py +2 -2
  29. bbstrader/trading/execution.py +252 -164
  30. bbstrader/trading/scripts.py +8 -4
  31. bbstrader/trading/strategies.py +79 -66
  32. bbstrader/tseries.py +482 -107
  33. {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/LICENSE +1 -1
  34. {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/METADATA +6 -1
  35. bbstrader-0.2.1.dist-info/RECORD +37 -0
  36. bbstrader-0.1.94.dist-info/RECORD +0 -32
  37. {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/WHEEL +0 -0
  38. {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/top_level.txt +0 -0
@@ -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,29 +64,61 @@ 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
- notify = kwargs.get('notify', False)
108
+ mtrades = kwargs.get('max_trades')
109
+ notify = kwargs.get('notify', False)
89
110
  if notify:
90
- telegram = kwargs.get('telegram', False)
111
+ telegram = kwargs.get('telegram', False)
91
112
  bot_token = kwargs.get('bot_token')
92
- chat_id = kwargs.get('chat_id')
93
-
113
+ chat_id = kwargs.get('chat_id')
114
+
115
+ expert_ids = kwargs.get('expert_id')
116
+ if expert_ids is None:
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]
121
+
94
122
  def update_risk(weights):
95
123
  if weights is not None:
96
124
  for symbol in symbols:
@@ -99,11 +127,18 @@ def _mt5_execution(
99
127
  trade = trades_instances[symbol]
100
128
  trade.dailydd = round(weights[symbol], 5)
101
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
+
102
137
  def _send_notification(signal):
103
- send_message(message=signal, notify_me=notify,
104
- telegram=telegram, token=bot_token, chat_id=chat_id)
105
-
106
- 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
107
142
  max_trades = {symbol: mtrades[symbol]
108
143
  if mtrades is not None and symbol in mtrades
109
144
  else trades_instances[symbol].max_trade() for symbol in symbols}
@@ -116,23 +151,98 @@ def _mt5_execution(
116
151
  return
117
152
  if buys is not None or sells is not None:
118
153
  trades_instances[symbol].break_even(
119
- mm=mm, trail=trail, stop_trail=stop_trail,
154
+ mm=mm, trail=trail, stop_trail=stop_trail,
120
155
  trail_after_points=trail_after_points, be_plus_points=be_plus_points)
121
- num_days = 0
122
- time_intervals = 0
123
- trade_time = _TF_MAPPING[time_frame]
156
+ num_days = 0
157
+ time_intervals = 0
158
+ trade_time = _TF_MAPPING[time_frame]
124
159
 
125
- long_market = {symbol: False for symbol in symbols}
126
- 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}
127
162
  try:
128
163
  check_mt5_connection(**kwargs)
129
- strategy: MT5Strategy = strategy_cls(symbol_list=symbols, mode='live', **kwargs)
130
- except Exception as e:
131
- 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()
132
169
  return
133
170
  logger.info(
134
171
  f'Running {STRATEGY} Strategy in {time_frame} Interval ...')
135
-
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
+
136
246
  while True:
137
247
  try:
138
248
  check_mt5_connection(**kwargs)
@@ -144,8 +254,15 @@ def _mt5_execution(
144
254
  positions_orders[type] = {}
145
255
  for symbol in symbols:
146
256
  positions_orders[type][symbol] = None
147
- func = getattr(trades_instances[symbol], f"get_current_{type}")
148
- positions_orders[type][symbol] = func()
257
+ for id in expert_ids:
258
+ func = getattr(
259
+ trades_instances[symbol], f"get_current_{type}")
260
+ func_value = func(id=id)
261
+ if func_value is not None:
262
+ if positions_orders[type][symbol] is None:
263
+ positions_orders[type][symbol] = func(id=id)
264
+ else:
265
+ positions_orders[type][symbol] += func(id=id)
149
266
  buys = positions_orders['buys']
150
267
  sells = positions_orders['sells']
151
268
  for symbol in symbols:
@@ -156,111 +273,59 @@ def _mt5_execution(
156
273
  f"Current {type.upper()} SYMBOL={symbol}: \
157
274
  {positions_orders[type][symbol]}, STRATEGY={STRATEGY}")
158
275
  long_market = {
159
- symbol: buys[symbol] is not None
276
+ symbol: buys[symbol] is not None
160
277
  and len(buys[symbol]) >= max_trades[symbol] for symbol in symbols
161
278
  }
162
279
  short_market = {
163
- symbol: sells[symbol] is not None
280
+ symbol: sells[symbol] is not None
164
281
  and len(sells[symbol]) >= max_trades[symbol] for symbol in symbols
165
282
  }
166
283
 
167
- except Exception as e:
168
- logger.error(f"Handling Positions and Orders, {e}, STRATEGY={STRATEGY}")
284
+ except Exception:
285
+ logger.error(
286
+ f"Handling Positions and Orders, STRATEGY={STRATEGY}")
287
+ traceback.print_exc()
169
288
  continue
170
289
  time.sleep(0.5)
171
290
  try:
172
291
  check_mt5_connection(**kwargs)
173
292
  signals = strategy.calculate_signals()
174
- weights = None
175
- if hasattr(strategy, 'apply_risk_management'):
176
- 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
+ )
177
298
  update_risk(weights)
178
- except Exception as e:
179
- logger.error(f"Calculating signal, {e}, STRATEGY={STRATEGY}")
299
+ except Exception:
300
+ logger.error(f"Calculating signal, STRATEGY={STRATEGY}")
301
+ traceback.print_exc()
180
302
  continue
181
- for symbol in symbols:
303
+ for signal in signals:
182
304
  try:
183
305
  check_mt5_connection(**kwargs)
306
+ symbol = signal.symbol
184
307
  trade: Trade = trades_instances[symbol]
185
- tfmsg = f"Time Frame Not completed !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}"
186
- riskmsg = f"Risk not allowed !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}"
187
- signal = signals[symbol]
188
- if isinstance(signal, dict):
189
- signal = signal['action']
190
- price = signal['price']
191
- stoplimit = signal.get('stoplimit')
192
- elif isinstance(signal, str):
193
- price = None
194
- stoplimit = None
195
308
  if trade.trading_time() and today in trading_days:
196
- if signal is not None:
197
- signal = 'BMKT' if signal == 'LONG' else signal
198
- signal = 'SMKT' if signal == 'SHORT' else signal
199
- info = f"SIGNAL = {signal}, SYMBOL={trade.symbol}, STRATEGY={STRATEGY}, TIMEFRAME={time_frame}"
200
- msg = f"Sending {signal} Order ... SYMBOL={trade.symbol}, STRATEGY={STRATEGY}"
201
- if signal not in EXIT_SIGNAL_ACTIONS:
202
- logger.info(info)
203
- if signal in EXIT_SIGNAL_ACTIONS:
204
- for exit_signal, actions in EXIT_SIGNAL_ACTIONS.items():
205
- for position_type, order_type in actions.items():
206
- if positions_orders[position_type][symbol] is not None:
207
- if notify:
208
- _send_notification(info)
209
- if position_type in POSITIONS_TYPES:
210
- trade.close_positions(position_type=order_type)
211
- else:
212
- trade.close_orders(order_type=order_type)
213
- elif signal in BUYS and not long_market[symbol]:
214
- if use_trade_time:
215
- if time_intervals % trade_time == 0 or buys[symbol] is None:
216
- logger.info(msg)
217
- if notify:
218
- _send_notification(info)
219
- trade.open_buy_position(
220
- action=signal, price=price, stoplimit=stoplimit, mm=mm, comment=comment)
221
- else:
222
- logger.info(tfmsg)
223
- check(buys[symbol], sells[symbol], symbol)
224
- else:
225
- logger.info(msg)
226
- if notify:
227
- _send_notification(info)
228
- trade.open_buy_position(
229
- action=signal, price=price, stoplimit=stoplimit, mm=mm, comment=comment)
230
- elif signal in BUYS and long_market[symbol]:
231
- logger.info(riskmsg)
232
- check(buys[symbol], sells[symbol], symbol)
233
-
234
- elif signal in SELLS and not short_market[symbol]:
235
- if use_trade_time:
236
- if time_intervals % trade_time == 0 or sells[symbol] is None:
237
- logger.info(msg)
238
- if notify:
239
- _send_notification(info)
240
- trade.open_sell_position(
241
- action=signal, price=price, stoplimit=stoplimit, mm=mm, comment=comment)
242
- else:
243
- logger.info(tfmsg)
244
- check(buys[symbol], sells[symbol], symbol)
245
- else:
246
- logger.info(msg)
247
- if notify:
248
- _send_notification(info)
249
- trade.open_sell_position(
250
- action=signal, price=price, stoplimit=stoplimit, mm=mm, comment=comment)
251
- elif signal in SELLS and short_market[symbol]:
252
- logger.info(riskmsg)
253
- check(buys[symbol], sells[symbol], symbol)
254
- else:
255
- 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}")
256
318
  else:
257
319
  logger.info(
258
320
  f"Not trading Time !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
259
- check(buys[symbol], sells[symbol], symbol)
321
+ check(buys[symbol], sells[symbol], symbol)
260
322
 
261
- except Exception as e:
262
- 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()
263
327
  continue
328
+
264
329
  time.sleep((60 * iter_time) - 1.0)
265
330
  if iter_time == 1:
266
331
  time_intervals += 1
@@ -274,23 +339,35 @@ def _mt5_execution(
274
339
  try:
275
340
  FRIDAY = 'friday'
276
341
  check_mt5_connection(**kwargs)
277
- 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())
278
344
  if closing_pnl is not None:
279
- closing = all(trade.positive_profit(id=trade.expert_id, th=closing_pnl)
280
- for trade in trades_instances.values())
281
- else: closing = True
282
- logmsg = lambda period: logger.info(
283
- f"End of the {period} !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
284
- 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(
285
359
  f"Sleeping for {sleep_time} minutes ...\n")
286
- sessionmsg = f"STARTING NEW TRADING SESSION ...\n"
360
+ sessionmsg = "STARTING NEW TRADING SESSION ...\n"
287
361
  if period.lower() == 'day':
288
362
  for symbol in symbols:
289
363
  trade = trades_instances[symbol]
290
364
  if trade.days_end() and closing:
291
- trade.close_positions(position_type='all', comment=comment)
292
- logmsg("Day")
365
+ for id in expert_ids:
366
+ trade.close_positions(
367
+ position_type='all', id=id, comment=comment)
368
+ logmsgif("Day", symbol)
293
369
  trade.statistics(save=True)
370
+ strategy.perform_period_end_checks()
294
371
  if day_end:
295
372
  if period_end_action == 'break' and closing:
296
373
  break
@@ -300,7 +377,8 @@ def _mt5_execution(
300
377
  time.sleep(60 * sleep_time)
301
378
  logger.info(sessionmsg)
302
379
  elif period_end_action == 'sleep' and today == FRIDAY:
303
- sleep_time = trades_instances[symbols[-1]].sleep_time(weekend=True)
380
+ sleep_time = trades_instances[symbols[-1]
381
+ ].sleep_time(weekend=True)
304
382
  sleepmsg(sleep_time)
305
383
  time.sleep(60 * sleep_time)
306
384
  logger.info(sessionmsg)
@@ -309,12 +387,15 @@ def _mt5_execution(
309
387
  for symbol in symbols:
310
388
  trade = trades_instances[symbol]
311
389
  if trade.days_end() and today != FRIDAY:
312
- logmsg("Day")
390
+ logmsgif("Day", symbol)
313
391
 
314
392
  elif trade.days_end() and today == FRIDAY and closing:
315
- trade.close_positions(position_type='all', comment=comment)
316
- logmsg("Week")
393
+ for id in expert_ids:
394
+ trade.close_positions(
395
+ position_type='all', id=id, comment=comment)
396
+ logmsgif("Week", symbol)
317
397
  trade.statistics(save=True)
398
+ strategy.perform_period_end_checks()
318
399
  if day_end and today != FRIDAY:
319
400
  sleep_time = trades_instances[symbols[-1]].sleep_time()
320
401
  sleepmsg(sleep_time)
@@ -324,7 +405,8 @@ def _mt5_execution(
324
405
  if period_end_action == 'break' and closing:
325
406
  break
326
407
  elif period_end_action == 'sleep' or not closing:
327
- sleep_time = trades_instances[symbols[-1]].sleep_time(weekend=True)
408
+ sleep_time = trades_instances[symbols[-1]
409
+ ].sleep_time(weekend=True)
328
410
  sleepmsg(sleep_time)
329
411
  time.sleep(60 * sleep_time)
330
412
  logger.info(sessionmsg)
@@ -333,17 +415,20 @@ def _mt5_execution(
333
415
  for symbol in symbols:
334
416
  trade = trades_instances[symbol]
335
417
  if trade.days_end() and today != FRIDAY:
336
- logmsg("Day")
418
+ logmsgif("Day", symbol)
337
419
  elif trade.days_end() and today == FRIDAY:
338
- logmsg("Week")
420
+ logmsgif("Week", symbol)
339
421
  elif (
340
422
  trade.days_end()
341
423
  and today == FRIDAY
342
424
  and num_days/len(symbols) >= 20
343
425
  ) and closing:
344
- trade.close_positions(position_type='all', comment=comment)
345
- logmsg("Month")
426
+ for id in expert_ids:
427
+ trade.close_positions(
428
+ position_type='all', id=id, comment=comment)
429
+ logmsgif("Month", symbol)
346
430
  trade.statistics(save=True)
431
+ strategy.perform_period_end_checks()
347
432
  if day_end and today != FRIDAY:
348
433
  sleep_time = trades_instances[symbols[-1]].sleep_time()
349
434
  sleepmsg(sleep_time)
@@ -352,7 +437,7 @@ def _mt5_execution(
352
437
  num_days += 1
353
438
  elif day_end and today == FRIDAY:
354
439
  sleep_time = trades_instances[symbols[-1]
355
- ].sleep_time(weekend=True)
440
+ ].sleep_time(weekend=True)
356
441
  sleepmsg(sleep_time)
357
442
  time.sleep(60 * sleep_time)
358
443
  logger.info(sessionmsg)
@@ -360,10 +445,12 @@ def _mt5_execution(
360
445
  elif (day_end
361
446
  and today == FRIDAY
362
447
  and num_days/len(symbols) >= 20
363
- ):
448
+ ):
364
449
  break
365
- except Exception as e:
366
- 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()
367
454
  continue
368
455
 
369
456
 
@@ -389,7 +476,7 @@ class MT5ExecutionEngine():
389
476
 
390
477
  Examples
391
478
  --------
392
-
479
+
393
480
  >>> from bbstrader.metatrader import create_trade_instance
394
481
  >>> from bbstrader.trading.execution import MT5ExecutionEngine
395
482
  >>> from bbstrader.trading.strategies import StockIndexCFDTrading
@@ -465,7 +552,7 @@ class MT5ExecutionEngine():
465
552
  trading_days: Optional[List[str]] = TradingDays,
466
553
  comment: Optional[str] = None,
467
554
  **kwargs
468
- ):
555
+ ):
469
556
  """
470
557
  Args:
471
558
  symbol_list : List of symbols to trade
@@ -496,7 +583,7 @@ class MT5ExecutionEngine():
496
583
  1. For `trail` , `stop_trail` , `trail_after_points` , `be_plus_points` see `bbstrader.metatrader.trade.Trade.break_even()` .
497
584
  2. All Strategies must inherit from `bbstrader.btengine.strategy.Strategy` or `bbstrader.btengine.strategy.MT5Strategy` class
498
585
  and have a `calculate_signals` method that returns a dictionary of signals for each symbol in symbol_list.
499
-
586
+
500
587
  3. All strategies must have the following arguments in their `__init__` method:
501
588
  - bars (DataHandler): DataHandler instance default to None
502
589
  - events (Queue): Queue instance default to None
@@ -506,7 +593,7 @@ class MT5ExecutionEngine():
506
593
  The keyword arguments are all the additional arguments passed to the `MT5ExecutionEngine` class,
507
594
  the `Strategy` class, the `DataHandler` class, the `Portfolio` class and the `ExecutionHandler` class.
508
595
  - The `bars` and `events` arguments are used for backtesting only.
509
-
596
+
510
597
  4. All strategies must generate signals for backtesting and live trading.
511
598
  See the `bbstrader.trading.strategies` module for more information on how to create custom strategies.
512
599
  See `bbstrader.metatrader.account.check_mt5_connection()` for more details on how to connect to MT5 terminal.
@@ -534,7 +621,7 @@ class MT5ExecutionEngine():
534
621
  trades = self.trades_instances.keys()
535
622
  s = self.strategy_cls.__name__
536
623
  return f"MT5ExecutionEngine(Symbols={list(trades)}, Strategy={s})"
537
-
624
+
538
625
  def run(self):
539
626
  check_mt5_connection(**self.kwargs)
540
627
  _mt5_execution(
@@ -559,4 +646,5 @@ class MT5ExecutionEngine():
559
646
  )
560
647
 
561
648
 
562
- 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