bbstrader 0.2.98__py3-none-any.whl → 0.2.991__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/__init__.py +19 -0
- bbstrader/btengine/backtest.py +7 -7
- bbstrader/btengine/event.py +27 -18
- bbstrader/btengine/execution.py +3 -3
- bbstrader/btengine/portfolio.py +3 -3
- bbstrader/btengine/strategy.py +8 -2
- bbstrader/core/data.py +98 -2
- bbstrader/metatrader/account.py +1 -1
- bbstrader/metatrader/analysis.py +98 -0
- bbstrader/metatrader/copier.py +71 -46
- bbstrader/metatrader/trade.py +47 -55
- bbstrader/models/factors.py +97 -97
- bbstrader/models/nlp.py +9 -2
- bbstrader/trading/execution.py +144 -157
- bbstrader/trading/strategies.py +13 -5
- {bbstrader-0.2.98.dist-info → bbstrader-0.2.991.dist-info}/METADATA +1 -3
- {bbstrader-0.2.98.dist-info → bbstrader-0.2.991.dist-info}/RECORD +21 -19
- {bbstrader-0.2.98.dist-info → bbstrader-0.2.991.dist-info}/WHEEL +1 -1
- {bbstrader-0.2.98.dist-info → bbstrader-0.2.991.dist-info}/entry_points.txt +0 -0
- {bbstrader-0.2.98.dist-info → bbstrader-0.2.991.dist-info}/licenses/LICENSE +0 -0
- {bbstrader-0.2.98.dist-info → bbstrader-0.2.991.dist-info}/top_level.txt +0 -0
bbstrader/trading/execution.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import multiprocessing as mp
|
|
2
2
|
import time
|
|
3
|
-
from datetime import datetime
|
|
3
|
+
from datetime import date, datetime
|
|
4
4
|
from typing import Dict, List, Literal, Optional
|
|
5
5
|
|
|
6
|
+
import pandas as pd
|
|
6
7
|
from loguru import logger as log
|
|
7
|
-
from logging import Logger
|
|
8
8
|
|
|
9
9
|
from bbstrader.btengine.strategy import MT5Strategy, Strategy
|
|
10
10
|
from bbstrader.config import BBSTRADER_DIR
|
|
@@ -18,7 +18,7 @@ except ImportError:
|
|
|
18
18
|
import bbstrader.compat # noqa: F401
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
__all__ = ["Mt5ExecutionEngine", "TWSExecutionEngine"]
|
|
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"]
|
|
@@ -181,12 +182,12 @@ class Mt5ExecutionEngine:
|
|
|
181
182
|
show_positions_orders: bool = False,
|
|
182
183
|
iter_time: int | float = 5,
|
|
183
184
|
use_trade_time: bool = True,
|
|
184
|
-
period: Literal["day", "week", "month"] = "week",
|
|
185
|
+
period: Literal["24/7", "day", "week", "month"] = "week",
|
|
185
186
|
period_end_action: Literal["break", "sleep"] = "break",
|
|
186
187
|
closing_pnl: Optional[float] = None,
|
|
187
|
-
trading_days: Optional[List[str]] =
|
|
188
|
+
trading_days: Optional[List[str]] = None,
|
|
188
189
|
comment: Optional[str] = None,
|
|
189
|
-
**kwargs
|
|
190
|
+
**kwargs
|
|
190
191
|
):
|
|
191
192
|
"""
|
|
192
193
|
Args:
|
|
@@ -199,8 +200,8 @@ class Mt5ExecutionEngine:
|
|
|
199
200
|
show_positions_orders : Print open positions and orders. Defaults to False.
|
|
200
201
|
iter_time : Interval to check for signals and `mm`. Defaults to 5.
|
|
201
202
|
use_trade_time : Open trades after the time is completed. Defaults to True.
|
|
202
|
-
period : Period to trade. Defaults to 'week'.
|
|
203
|
-
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',
|
|
204
205
|
this only applies when period is 'day', 'week'.
|
|
205
206
|
closing_pnl : Minimum profit in percentage of target profit to close positions. Defaults to -0.001.
|
|
206
207
|
trading_days : Trading days in a week. Defaults to monday to friday.
|
|
@@ -213,11 +214,12 @@ class Mt5ExecutionEngine:
|
|
|
213
214
|
- telegram (bool): Enable telegram notifications. Defaults to False.
|
|
214
215
|
- bot_token (str): Telegram bot token. Defaults to None.
|
|
215
216
|
- chat_id (Union[int, str, List] ): Telegram chat id. Defaults to None.
|
|
217
|
+
- MT5 connection arguments.
|
|
216
218
|
|
|
217
219
|
Note:
|
|
218
220
|
1. For `trail` , `stop_trail` , `trail_after_points` , `be_plus_points` see `bbstrader.metatrader.trade.Trade.break_even()` .
|
|
219
|
-
2. All Strategies must inherit from `bbstrader.btengine.strategy.
|
|
220
|
-
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``.
|
|
221
223
|
|
|
222
224
|
3. All strategies must have the following arguments in their `__init__` method:
|
|
223
225
|
- bars (DataHandler): DataHandler instance default to None
|
|
@@ -245,14 +247,12 @@ class Mt5ExecutionEngine:
|
|
|
245
247
|
self.show_positions_orders = show_positions_orders
|
|
246
248
|
self.iter_time = iter_time
|
|
247
249
|
self.use_trade_time = use_trade_time
|
|
248
|
-
self.period = period
|
|
250
|
+
self.period = period.strip()
|
|
249
251
|
self.period_end_action = period_end_action
|
|
250
252
|
self.closing_pnl = closing_pnl
|
|
251
|
-
self.trading_days = trading_days
|
|
252
253
|
self.comment = comment
|
|
253
254
|
self.kwargs = kwargs
|
|
254
255
|
|
|
255
|
-
self.num_days = 0
|
|
256
256
|
self.time_intervals = 0
|
|
257
257
|
self.time_frame = kwargs.get("time_frame", "15m")
|
|
258
258
|
self.trade_time = _TF_MAPPING[self.time_frame]
|
|
@@ -270,7 +270,7 @@ class Mt5ExecutionEngine:
|
|
|
270
270
|
|
|
271
271
|
def _initialize_engine(self, **kwargs):
|
|
272
272
|
global logger
|
|
273
|
-
logger
|
|
273
|
+
logger = kwargs.get("logger", log)
|
|
274
274
|
try:
|
|
275
275
|
self.daily_risk = kwargs.get("daily_risk")
|
|
276
276
|
self.notify = kwargs.get("notify", False)
|
|
@@ -280,16 +280,19 @@ class Mt5ExecutionEngine:
|
|
|
280
280
|
self.STRATEGY = kwargs.get("strategy_name")
|
|
281
281
|
self.ACCOUNT = kwargs.get("account", "MT5 Account")
|
|
282
282
|
self.signal_tickers = kwargs.get("signal_tickers", self.symbols)
|
|
283
|
-
self.FRIDAY = "friday"
|
|
284
283
|
|
|
285
284
|
self.expert_ids = self._expert_ids(kwargs.get("expert_ids"))
|
|
286
285
|
self.max_trades = self._max_trades(kwargs.get("max_trades"))
|
|
287
286
|
if self.comment is None:
|
|
288
287
|
trade = self.trades_instances[self.symbols[0]]
|
|
289
288
|
self.comment = f"{trade.expert_name}@{trade.version}"
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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")
|
|
293
296
|
except Exception as e:
|
|
294
297
|
self._print_exc(
|
|
295
298
|
f"Initializing Execution Engine, STRATEGY={self.STRATEGY}, ACCOUNT={self.ACCOUNT}",
|
|
@@ -440,24 +443,6 @@ class Mt5ExecutionEngine:
|
|
|
440
443
|
{positions_orders[order_type][symbol]}, STRATEGY={self.STRATEGY} , ACCOUNT={self.ACCOUNT}"
|
|
441
444
|
)
|
|
442
445
|
|
|
443
|
-
def _update_risk(self, weights):
|
|
444
|
-
try:
|
|
445
|
-
check_mt5_connection(**self.kwargs)
|
|
446
|
-
if weights is not None:
|
|
447
|
-
for symbol in self.symbols:
|
|
448
|
-
if symbol not in weights:
|
|
449
|
-
continue
|
|
450
|
-
trade = self.trades_instances[symbol]
|
|
451
|
-
assert self.daily_risk is not None
|
|
452
|
-
dailydd = round(weights[symbol] * self.daily_risk, 5)
|
|
453
|
-
trade.dailydd = dailydd
|
|
454
|
-
except Exception as e:
|
|
455
|
-
self._print_exc(
|
|
456
|
-
f"Updating Risk, STRATEGY={self.STRATEGY} , ACCOUNT={self.ACCOUNT}",
|
|
457
|
-
e,
|
|
458
|
-
)
|
|
459
|
-
pass
|
|
460
|
-
|
|
461
446
|
def _send_notification(self, signal, symbol):
|
|
462
447
|
telegram = self.kwargs.get("telegram", False)
|
|
463
448
|
bot_token = self.kwargs.get("bot_token")
|
|
@@ -488,6 +473,73 @@ class Mt5ExecutionEngine:
|
|
|
488
473
|
def _sleepmsg(self, sleep_time):
|
|
489
474
|
logger.info(f"{self.ACCOUNT} Sleeping for {sleep_time} minutes ...\n")
|
|
490
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
|
+
|
|
491
543
|
def _perform_period_end_actions(
|
|
492
544
|
self,
|
|
493
545
|
today,
|
|
@@ -495,107 +547,27 @@ class Mt5ExecutionEngine:
|
|
|
495
547
|
closing,
|
|
496
548
|
sessionmsg,
|
|
497
549
|
):
|
|
498
|
-
|
|
499
|
-
for symbol in self.
|
|
500
|
-
trade
|
|
501
|
-
|
|
502
|
-
if trade.days_end():
|
|
503
|
-
if period_type in ["weekly", "monthly"] and today != self.FRIDAY:
|
|
504
|
-
self._logmsgif("Day", symbol)
|
|
505
|
-
elif period_type in ["weekly", "monthly"] and today == self.FRIDAY:
|
|
506
|
-
self._logmsgif("Week", symbol)
|
|
507
|
-
|
|
508
|
-
if (
|
|
509
|
-
(
|
|
510
|
-
period_type == "monthly"
|
|
511
|
-
and today == self.FRIDAY
|
|
512
|
-
and self.num_days >= 20
|
|
513
|
-
and closing
|
|
514
|
-
)
|
|
515
|
-
or (period_type == "weekly" and today == self.FRIDAY and closing)
|
|
516
|
-
or (period_type == "daily" and closing)
|
|
517
|
-
or (period_type == "24/7" and closing)
|
|
518
|
-
):
|
|
519
|
-
for id in self.expert_ids:
|
|
520
|
-
trade.close_positions(
|
|
521
|
-
position_type="all", id=id, comment=self.comment
|
|
522
|
-
)
|
|
523
|
-
|
|
524
|
-
if period_type == "monthly":
|
|
525
|
-
self._logmsgif("Month", symbol)
|
|
526
|
-
elif period_type == "weekly":
|
|
527
|
-
self._logmsgif("Week", symbol)
|
|
528
|
-
else:
|
|
529
|
-
self._logmsgif("Day", symbol)
|
|
530
|
-
|
|
531
|
-
trade.statistics(save=True)
|
|
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)
|
|
532
553
|
|
|
533
554
|
if day_end:
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
self._sleepmsg(sleep_time + self.delay)
|
|
538
|
-
time.sleep(60 * sleep_time + self.delay)
|
|
539
|
-
logger.info(sessionmsg)
|
|
540
|
-
|
|
541
|
-
elif period_type == "daily":
|
|
542
|
-
self.strategy.perform_period_end_checks()
|
|
543
|
-
if self.period_end_action == "break" and closing:
|
|
544
|
-
exit(0)
|
|
545
|
-
elif (
|
|
546
|
-
self.period_end_action == "sleep"
|
|
547
|
-
and today != self.FRIDAY
|
|
548
|
-
or not closing
|
|
549
|
-
):
|
|
550
|
-
sleep_time = self.trades_instances[self.symbols[-1]].sleep_time()
|
|
551
|
-
self._sleepmsg(sleep_time + self.delay)
|
|
552
|
-
time.sleep(60 * sleep_time + self.delay)
|
|
553
|
-
logger.info(sessionmsg)
|
|
554
|
-
elif self.period_end_action == "sleep" and today == self.FRIDAY:
|
|
555
|
-
sleep_time = self.trades_instances[self.symbols[-1]].sleep_time(
|
|
556
|
-
weekend=True
|
|
557
|
-
)
|
|
558
|
-
self._sleepmsg(sleep_time + self.delay)
|
|
559
|
-
time.sleep(60 * sleep_time + self.delay)
|
|
560
|
-
logger.info(sessionmsg)
|
|
561
|
-
|
|
562
|
-
elif period_type == "weekly":
|
|
563
|
-
if today != self.FRIDAY:
|
|
564
|
-
sleep_time = self.trades_instances[self.symbols[-1]].sleep_time()
|
|
565
|
-
self._sleepmsg(sleep_time + self.delay)
|
|
566
|
-
time.sleep(60 * sleep_time + self.delay)
|
|
567
|
-
logger.info(sessionmsg)
|
|
568
|
-
elif today == self.FRIDAY:
|
|
569
|
-
self.strategy.perform_period_end_checks()
|
|
570
|
-
if self.period_end_action == "break" and closing:
|
|
571
|
-
exit(0)
|
|
572
|
-
elif self.period_end_action == "sleep" or not closing:
|
|
573
|
-
sleep_time = self.trades_instances[self.symbols[-1]].sleep_time(
|
|
574
|
-
weekend=True
|
|
575
|
-
)
|
|
576
|
-
self._sleepmsg(sleep_time + self.delay)
|
|
577
|
-
time.sleep(60 * sleep_time + self.delay)
|
|
578
|
-
logger.info(sessionmsg)
|
|
579
|
-
|
|
580
|
-
elif period_type == "monthly":
|
|
581
|
-
if today != self.FRIDAY:
|
|
582
|
-
sleep_time = self.trades_instances[self.symbols[-1]].sleep_time()
|
|
583
|
-
self._sleepmsg(sleep_time + self.delay)
|
|
584
|
-
time.sleep(60 * sleep_time + self.delay)
|
|
585
|
-
logger.info(sessionmsg)
|
|
586
|
-
elif today == self.FRIDAY:
|
|
555
|
+
self.time_intervals = 0
|
|
556
|
+
match period:
|
|
557
|
+
case "24/7":
|
|
587
558
|
self.strategy.perform_period_end_checks()
|
|
588
|
-
|
|
589
|
-
exit(0)
|
|
590
|
-
elif self.period_end_action == "sleep" or not closing:
|
|
591
|
-
sleep_time = self.trades_instances[self.symbols[-1]].sleep_time(
|
|
592
|
-
weekend=True
|
|
593
|
-
)
|
|
594
|
-
self._sleepmsg(sleep_time + self.delay)
|
|
595
|
-
time.sleep(60 * sleep_time + self.delay)
|
|
596
|
-
logger.info(sessionmsg)
|
|
559
|
+
self._sleep_over_night(sessionmsg)
|
|
597
560
|
|
|
598
|
-
|
|
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}")
|
|
599
571
|
|
|
600
572
|
def _check(self, buys, sells, symbol):
|
|
601
573
|
if not self.mm:
|
|
@@ -609,6 +581,41 @@ class Mt5ExecutionEngine:
|
|
|
609
581
|
be_plus_points=self.be_plus_points,
|
|
610
582
|
)
|
|
611
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
|
+
|
|
612
619
|
def _open_buy(
|
|
613
620
|
self, signal, symbol, id, trade: Trade, price, stoplimit, sigmsg, msg, comment
|
|
614
621
|
):
|
|
@@ -822,23 +829,6 @@ class Mt5ExecutionEngine:
|
|
|
822
829
|
f"(e.g., if time_frame is 15m, iter_time must be 1.5, 3, 5, 15 etc)"
|
|
823
830
|
)
|
|
824
831
|
|
|
825
|
-
def _get_signals_and_weights(self):
|
|
826
|
-
try:
|
|
827
|
-
check_mt5_connection(**self.kwargs)
|
|
828
|
-
signals = self.strategy.calculate_signals()
|
|
829
|
-
weights = (
|
|
830
|
-
self.strategy.apply_risk_management(self.optimizer)
|
|
831
|
-
if hasattr(self.strategy, "apply_risk_management")
|
|
832
|
-
else None
|
|
833
|
-
)
|
|
834
|
-
return signals, weights
|
|
835
|
-
except Exception as e:
|
|
836
|
-
self._print_exc(
|
|
837
|
-
f"Calculating Signals, STRATEGY={self.STRATEGY} , ACCOUNT={self.ACCOUNT}",
|
|
838
|
-
e,
|
|
839
|
-
)
|
|
840
|
-
pass
|
|
841
|
-
|
|
842
832
|
def _handle_signals(self, today, signals, buys, sells):
|
|
843
833
|
try:
|
|
844
834
|
check_mt5_connection(**self.kwargs)
|
|
@@ -892,9 +882,6 @@ class Mt5ExecutionEngine:
|
|
|
892
882
|
closing,
|
|
893
883
|
sessionmsg,
|
|
894
884
|
)
|
|
895
|
-
except KeyboardInterrupt:
|
|
896
|
-
logger.info("Stopping the Execution Engine ...")
|
|
897
|
-
quit()
|
|
898
885
|
except Exception as e:
|
|
899
886
|
msg = f"Handling period end actions, STRATEGY={self.STRATEGY} , ACCOUNT={self.ACCOUNT}"
|
|
900
887
|
self._print_exc(msg, e)
|
|
@@ -907,18 +894,18 @@ class Mt5ExecutionEngine:
|
|
|
907
894
|
positions_orders = self._check_positions_orders()
|
|
908
895
|
if self.show_positions_orders:
|
|
909
896
|
self._display_positions_orders(positions_orders)
|
|
910
|
-
buys = positions_orders
|
|
911
|
-
sells = positions_orders
|
|
897
|
+
buys = positions_orders.get("buys")
|
|
898
|
+
sells = positions_orders.get("sells")
|
|
912
899
|
self.long_market, self.short_market = self._long_short_market(
|
|
913
900
|
buys, sells
|
|
914
901
|
)
|
|
915
|
-
signals, weights = self._get_signals_and_weights()
|
|
916
|
-
self._update_risk(weights)
|
|
917
902
|
today = datetime.now().strftime("%A").lower()
|
|
903
|
+
signals, weights = self._get_signals_and_weights()
|
|
918
904
|
if len(signals) == 0:
|
|
919
905
|
for symbol in self.symbols:
|
|
920
906
|
self._check(buys[symbol], sells[symbol], symbol)
|
|
921
907
|
else:
|
|
908
|
+
self._update_risk(weights)
|
|
922
909
|
self._handle_signals(today, signals, buys, sells)
|
|
923
910
|
self._sleep()
|
|
924
911
|
self._handle_period_end_actions(today)
|
bbstrader/trading/strategies.py
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Strategies module for trading strategies backtesting and execution.
|
|
3
3
|
|
|
4
|
+
# NOTE
|
|
5
|
+
These strategies inherit from the Strategy class, not from MT5Strategy, because we chose to demonstrate the modular approach to building and backtesting strategies using the bbstrader framework.
|
|
6
|
+
|
|
7
|
+
If these strategies need to be sent to the Mt5ExecutionEngine,
|
|
8
|
+
they must return signals as a list of bbstrader.metatrader.trade.TradingSignal objects.
|
|
9
|
+
|
|
10
|
+
Later, we will implement the Execution Engine for the Interactive Brokers TWS platform.
|
|
11
|
+
|
|
4
12
|
DISCLAIMER:
|
|
5
13
|
This module is for educational purposes only and should not be
|
|
6
14
|
considered as financial advice. Always consult with a qualified financial advisor before making any investment decisions.
|
|
@@ -18,7 +26,7 @@ import yfinance as yf
|
|
|
18
26
|
|
|
19
27
|
from bbstrader.btengine.backtest import BacktestEngine
|
|
20
28
|
from bbstrader.btengine.data import DataHandler, MT5DataHandler, YFDataHandler
|
|
21
|
-
from bbstrader.btengine.event import SignalEvent
|
|
29
|
+
from bbstrader.btengine.event import Events, SignalEvent
|
|
22
30
|
from bbstrader.btengine.execution import MT5ExecutionHandler, SimExecutionHandler
|
|
23
31
|
from bbstrader.btengine.strategy import Strategy
|
|
24
32
|
from bbstrader.metatrader.account import Account
|
|
@@ -189,7 +197,7 @@ class SMAStrategy(Strategy):
|
|
|
189
197
|
|
|
190
198
|
def calculate_signals(self, event=None):
|
|
191
199
|
if self.mode == "backtest" and event is not None:
|
|
192
|
-
if event.type ==
|
|
200
|
+
if event.type == Events.MARKET:
|
|
193
201
|
signals = self.create_backtest_signals()
|
|
194
202
|
for signal in signals.values():
|
|
195
203
|
if signal is not None:
|
|
@@ -377,7 +385,7 @@ class ArimaGarchStrategy(Strategy):
|
|
|
377
385
|
|
|
378
386
|
def calculate_signals(self, event=None):
|
|
379
387
|
if self.mode == "backtest" and event is not None:
|
|
380
|
-
if event.type ==
|
|
388
|
+
if event.type == Events.MARKET:
|
|
381
389
|
signals = self.create_backtest_signal()
|
|
382
390
|
for signal in signals.values():
|
|
383
391
|
if signal is not None:
|
|
@@ -560,7 +568,7 @@ class KalmanFilterStrategy(Strategy):
|
|
|
560
568
|
Calculate the Kalman Filter strategy.
|
|
561
569
|
"""
|
|
562
570
|
if self.mode == "backtest" and event is not None:
|
|
563
|
-
if event.type ==
|
|
571
|
+
if event.type == Events.MARKET:
|
|
564
572
|
self.calculate_backtest_signals()
|
|
565
573
|
elif self.mode == "live":
|
|
566
574
|
return self.calculate_live_signals()
|
|
@@ -744,7 +752,7 @@ class StockIndexSTBOTrading(Strategy):
|
|
|
744
752
|
|
|
745
753
|
def calculate_signals(self, event=None) -> Dict[str, Union[str, None]]:
|
|
746
754
|
if self.mode == "backtest" and event is not None:
|
|
747
|
-
if event.type ==
|
|
755
|
+
if event.type == Events.MARKET:
|
|
748
756
|
self.calculate_backtest_signals()
|
|
749
757
|
elif self.mode == "live":
|
|
750
758
|
return self.calculate_live_signals()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bbstrader
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.991
|
|
4
4
|
Summary: Simplified Investment & Trading Toolkit
|
|
5
5
|
Home-page: https://github.com/bbalouki/bbstrader
|
|
6
6
|
Download-URL: https://pypi.org/project/bbstrader/
|
|
@@ -81,8 +81,6 @@ Dynamic: requires-dist
|
|
|
81
81
|
Dynamic: summary
|
|
82
82
|
|
|
83
83
|
# Simplified Investment & Trading Toolkit
|
|
84
|
-

|
|
85
|
-
|
|
86
84
|
[](https://bbstrader.readthedocs.io/en/latest/?badge=latest)
|
|
87
85
|
[](https://pypi.org/project/bbstrader/)
|
|
88
86
|
[](https://pypi.python.org/pypi/bbstrader)
|
|
@@ -1,45 +1,47 @@
|
|
|
1
1
|
bbstrader/__ini__.py,sha256=v6zyJHj5FMRL-_P7AwnTGbCF-riMqhqlTvDgfulj7go,621
|
|
2
|
+
bbstrader/__init__.py,sha256=5q_NaxJHcGYUVhC8cMWO3ZDJhjta7MrphQTYqganepY,581
|
|
2
3
|
bbstrader/__main__.py,sha256=sDdjoiaHSPrVqdjU_zOLajX3rD_3Ha6dHwIivYc_vp4,1525
|
|
3
4
|
bbstrader/compat.py,sha256=djbHMvTvy0HYm1zyZ6Ttp_LMwP2PqTSVw1r7pqbz7So,487
|
|
4
5
|
bbstrader/config.py,sha256=c2nCUw-bYWf5kkyFls5Nqld8HdMczexSilTni7rYUBw,3973
|
|
5
6
|
bbstrader/tseries.py,sha256=H4D_A966HdN8YjBfuCcF8QBQdhjOrTcidR98wP2KN_I,68339
|
|
6
7
|
bbstrader/btengine/__init__.py,sha256=y1btjaEfhWsH8vuE7mBRpP9Tu-Azt9REhuVYsPCAfBU,2955
|
|
7
|
-
bbstrader/btengine/backtest.py,sha256=
|
|
8
|
+
bbstrader/btengine/backtest.py,sha256=UiOmtqYSh72KZz0DCXz8iKrqTCOkx9Er3XjqK6H9Do8,14765
|
|
8
9
|
bbstrader/btengine/data.py,sha256=Tuc6M8itbGpPjsfRpZBB8v0FJpPt7-hUkP6I5meP0Sg,26927
|
|
9
|
-
bbstrader/btengine/event.py,sha256=
|
|
10
|
-
bbstrader/btengine/execution.py,sha256=
|
|
10
|
+
bbstrader/btengine/event.py,sha256=Ydl1avAXp9WAWOBXDAckcb9g1UkcnCO0rRzcJZwIq20,8714
|
|
11
|
+
bbstrader/btengine/execution.py,sha256=tN7QIx0PHiQmJcn3MdSQiwwAYCDxFDn3C4i5tlM5xoY,10605
|
|
11
12
|
bbstrader/btengine/performance.py,sha256=1ecWrTzHBQbk4ORvbTEKxwCzlL1brcXOEUwgbnjAwx4,12470
|
|
12
|
-
bbstrader/btengine/portfolio.py,sha256=
|
|
13
|
+
bbstrader/btengine/portfolio.py,sha256=z98M65HQeCyma8gMZkAxspxBA9jtIhzxMyJUHPPj34c,16128
|
|
13
14
|
bbstrader/btengine/scripts.py,sha256=8o66dq4Ex4DsH4s8xvJqUOFjLzZJSnbBvvNBzohtzoE,4837
|
|
14
|
-
bbstrader/btengine/strategy.py,sha256=
|
|
15
|
+
bbstrader/btengine/strategy.py,sha256=FrYoB8ogHODTfRDZf2gW-CGdTxdVoIYwxko1cYgwcgg,31631
|
|
15
16
|
bbstrader/core/__init__.py,sha256=GIFzFSStPfE0XM2j7mDeZZQeMTh_AwPsDOQXwMVJLgw,97
|
|
16
|
-
bbstrader/core/data.py,sha256=
|
|
17
|
+
bbstrader/core/data.py,sha256=hStrx-QTYFjpJdZOJVtV2xmIyywj6T9_Rn3hhNYtFwU,22581
|
|
17
18
|
bbstrader/core/utils.py,sha256=WjuabzBjhY65ku2KL-f7CMalE2x-wrX-6mCA_qhhFPE,2728
|
|
18
19
|
bbstrader/ibkr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
20
|
bbstrader/ibkr/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
21
|
bbstrader/metatrader/__init__.py,sha256=A5Ye9tpc2sp9Xk5qjKw-EfYsoRcZtAt8nqvC3tCtZs8,333
|
|
21
|
-
bbstrader/metatrader/account.py,sha256=
|
|
22
|
-
bbstrader/metatrader/
|
|
22
|
+
bbstrader/metatrader/account.py,sha256=kSEJChls4o2smMWa-FeHoxS5gd6yyOPfzoU_6jmzxTg,57374
|
|
23
|
+
bbstrader/metatrader/analysis.py,sha256=pnZWdvEnf9wkPT0vhEIbZipnN5EUf6TaQftplYP9jRs,3564
|
|
24
|
+
bbstrader/metatrader/copier.py,sha256=pCZkRfkUWxufnmiO0cc1Kz6UF59HTUUGs-c60LYNUbE,32307
|
|
23
25
|
bbstrader/metatrader/rates.py,sha256=0bKyjGnafZ19DwC0-IxXUBJzOdAo-cNmhabwhKLxcos,20585
|
|
24
26
|
bbstrader/metatrader/risk.py,sha256=pwG4q1_uPGgPlokDGVNtd04O6p28yIbgT-evvHuo-Qc,27029
|
|
25
27
|
bbstrader/metatrader/scripts.py,sha256=Yjp7Un-wDTInptHS_rPFpXNKWbVM871VEkaHxsO2MPQ,2115
|
|
26
|
-
bbstrader/metatrader/trade.py,sha256=
|
|
28
|
+
bbstrader/metatrader/trade.py,sha256=uDlYCHqXk4AB0Nx8CHQfYUj4OODhm7W39es0r6Yuc8Q,79922
|
|
27
29
|
bbstrader/metatrader/utils.py,sha256=szORxKJW9OG-H6nof_ovOhToSx_n8EtVwe0mkLt3eFg,17424
|
|
28
30
|
bbstrader/models/__init__.py,sha256=s2mJrtKePXQaw_PvcrtPCD2mPCdVXP4Myzg0MlLVipo,547
|
|
29
|
-
bbstrader/models/factors.py,sha256
|
|
31
|
+
bbstrader/models/factors.py,sha256=-ODeD2GK4nmxFQAIh5MF3SFM9ofppi2pW6Q728LzLv8,12701
|
|
30
32
|
bbstrader/models/ml.py,sha256=tCr7YyODl0CDoOUpYqJ1q12ls86Sc-_Fu3b2Y0Z7TJ8,47551
|
|
31
|
-
bbstrader/models/nlp.py,sha256=
|
|
33
|
+
bbstrader/models/nlp.py,sha256=eN4VnexOt1intfX1T-GkaReqBoWPjoqRSWjXIa67ib8,28385
|
|
32
34
|
bbstrader/models/optimization.py,sha256=vnks6uxFZdqXgxaZJNxq8z0IH45KZ8yaXo38JhIVKGc,6399
|
|
33
35
|
bbstrader/models/portfolio.py,sha256=r-47Zrn2r7iKCHm5YVtwkbBJXAZGM3QYy-rXCWY9-Bg,8079
|
|
34
36
|
bbstrader/models/risk.py,sha256=JQOXPshMOru6Eb0AMU6oKCNzg6mlGfL6_tN90lWcVBE,14878
|
|
35
37
|
bbstrader/trading/__init__.py,sha256=ycLyuuxN5SujqtzR9X0Q74UQfK93q2va-GGAXdr-KS8,457
|
|
36
|
-
bbstrader/trading/execution.py,sha256=
|
|
38
|
+
bbstrader/trading/execution.py,sha256=ukOiv1O-a_SdKp35QlU_3_Wt_7CTgh4pLPN9s7qPEdw,37117
|
|
37
39
|
bbstrader/trading/scripts.py,sha256=Tf5q33WqqygjpIv43_8nA82VZ3GM0qgb4Ggo3fHJ_wg,5744
|
|
38
|
-
bbstrader/trading/strategies.py,sha256=
|
|
40
|
+
bbstrader/trading/strategies.py,sha256=rRNPZM1Z9twzO5UWd5qI3FgkZmOhF3Dd2h7XRn7EoDs,37011
|
|
39
41
|
bbstrader/trading/utils.py,sha256=57dKF9dcRu04oU2VRqydRrzW39dCW2wlDWhVt-sZdRw,1857
|
|
40
|
-
bbstrader-0.2.
|
|
41
|
-
bbstrader-0.2.
|
|
42
|
-
bbstrader-0.2.
|
|
43
|
-
bbstrader-0.2.
|
|
44
|
-
bbstrader-0.2.
|
|
45
|
-
bbstrader-0.2.
|
|
42
|
+
bbstrader-0.2.991.dist-info/licenses/LICENSE,sha256=ZwC_RqqGmOPBUiMDKqLyJZ5HBeHq53LpL7TMRzrJY8c,1094
|
|
43
|
+
bbstrader-0.2.991.dist-info/METADATA,sha256=p3lY0rHksdL6iOhw5Qunyyn_vWPQc8b5CQKrP3mErnM,11411
|
|
44
|
+
bbstrader-0.2.991.dist-info/WHEEL,sha256=GHB6lJx2juba1wDgXDNlMTyM13ckjBMKf-OnwgKOCtA,91
|
|
45
|
+
bbstrader-0.2.991.dist-info/entry_points.txt,sha256=0yDCbhbgHswOzJnY5wRSM_FjjyMHGvY7lJpSSVh0xtI,54
|
|
46
|
+
bbstrader-0.2.991.dist-info/top_level.txt,sha256=Wwj322jZmxGZ6gD_TdaPiPLjED5ReObm5omerwlmZIg,10
|
|
47
|
+
bbstrader-0.2.991.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|