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