bbstrader 0.1.7__py3-none-any.whl → 0.1.8__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/btengine/__init__.py +1 -0
- bbstrader/btengine/backtest.py +11 -9
- bbstrader/btengine/performance.py +9 -30
- bbstrader/btengine/portfolio.py +18 -10
- bbstrader/metatrader/account.py +11 -4
- bbstrader/metatrader/rates.py +4 -5
- bbstrader/metatrader/risk.py +1 -2
- bbstrader/metatrader/trade.py +269 -214
- bbstrader/metatrader/utils.py +37 -29
- bbstrader/trading/execution.py +55 -18
- bbstrader/trading/strategies.py +5 -3
- bbstrader/tseries.py +500 -494
- {bbstrader-0.1.7.dist-info → bbstrader-0.1.8.dist-info}/METADATA +2 -1
- bbstrader-0.1.8.dist-info/RECORD +26 -0
- bbstrader-0.1.7.dist-info/RECORD +0 -26
- {bbstrader-0.1.7.dist-info → bbstrader-0.1.8.dist-info}/LICENSE +0 -0
- {bbstrader-0.1.7.dist-info → bbstrader-0.1.8.dist-info}/WHEEL +0 -0
- {bbstrader-0.1.7.dist-info → bbstrader-0.1.8.dist-info}/top_level.txt +0 -0
bbstrader/metatrader/utils.py
CHANGED
|
@@ -4,6 +4,38 @@ import logging
|
|
|
4
4
|
from typing import List, NamedTuple, Optional
|
|
5
5
|
from enum import Enum
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"TIMEFRAMES",
|
|
10
|
+
"TimeFrame",
|
|
11
|
+
"TerminalInfo",
|
|
12
|
+
"AccountInfo",
|
|
13
|
+
"SymbolInfo",
|
|
14
|
+
"TickInfo",
|
|
15
|
+
"TradeRequest",
|
|
16
|
+
"OrderCheckResult",
|
|
17
|
+
"OrderSentResult",
|
|
18
|
+
"TradeOrder",
|
|
19
|
+
"TradePosition",
|
|
20
|
+
"TradeDeal",
|
|
21
|
+
"InvalidBroker",
|
|
22
|
+
"GenericFail",
|
|
23
|
+
"InvalidParams",
|
|
24
|
+
"HistoryNotFound",
|
|
25
|
+
"InvalidVersion",
|
|
26
|
+
"AuthFailed",
|
|
27
|
+
"UnsupportedMethod",
|
|
28
|
+
"AutoTradingDisabled",
|
|
29
|
+
"InternalFailSend",
|
|
30
|
+
"InternalFailReceive",
|
|
31
|
+
"InternalFailInit",
|
|
32
|
+
"InternalFailConnect",
|
|
33
|
+
"InternalFailTimeout",
|
|
34
|
+
"trade_retcode_message",
|
|
35
|
+
"raise_mt5_error",
|
|
36
|
+
"config_logger",
|
|
37
|
+
]
|
|
38
|
+
|
|
7
39
|
def config_logger(log_file: str, console_log=True):
|
|
8
40
|
# Configure the logger
|
|
9
41
|
logger = logging.getLogger(__name__)
|
|
@@ -55,34 +87,6 @@ class LogLevelFilter(logging.Filter):
|
|
|
55
87
|
return record.levelno in self.levels
|
|
56
88
|
|
|
57
89
|
|
|
58
|
-
__all__ = [
|
|
59
|
-
"TIMEFRAMES",
|
|
60
|
-
"TimeFrame",
|
|
61
|
-
"TerminalInfo",
|
|
62
|
-
"AccountInfo",
|
|
63
|
-
"SymbolInfo",
|
|
64
|
-
"TickInfo",
|
|
65
|
-
"TradeRequest",
|
|
66
|
-
"OrderCheckResult",
|
|
67
|
-
"OrderSentResult",
|
|
68
|
-
"TradeOrder",
|
|
69
|
-
"TradePosition",
|
|
70
|
-
"TradeDeal",
|
|
71
|
-
"GenericFail",
|
|
72
|
-
"InvalidParams",
|
|
73
|
-
"HistoryNotFound",
|
|
74
|
-
"InvalidVersion",
|
|
75
|
-
"AuthFailed",
|
|
76
|
-
"UnsupportedMethod",
|
|
77
|
-
"AutoTradingDisabled",
|
|
78
|
-
"InternalFailSend",
|
|
79
|
-
"InternalFailReceive",
|
|
80
|
-
"InternalFailInit",
|
|
81
|
-
"InternalFailConnect",
|
|
82
|
-
"InternalFailTimeout",
|
|
83
|
-
"trade_retcode_message",
|
|
84
|
-
"raise_mt5_error",
|
|
85
|
-
]
|
|
86
90
|
|
|
87
91
|
# TIMEFRAME is an enumeration with possible chart period values
|
|
88
92
|
# See https://www.mql5.com/en/docs/python_metatrader5/mt5copyratesfrom_py#timeframe
|
|
@@ -137,7 +141,6 @@ class TimeFrame(Enum):
|
|
|
137
141
|
W1 = "W1"
|
|
138
142
|
MN1 = "MN1"
|
|
139
143
|
|
|
140
|
-
|
|
141
144
|
class TerminalInfo(NamedTuple):
|
|
142
145
|
"""
|
|
143
146
|
Represents general information about the trading terminal.
|
|
@@ -467,6 +470,11 @@ class TradeDeal(NamedTuple):
|
|
|
467
470
|
comment: str
|
|
468
471
|
external_id: str
|
|
469
472
|
|
|
473
|
+
class InvalidBroker(Exception):
|
|
474
|
+
"""Exception raised for invalid broker errors."""
|
|
475
|
+
def __init__(self, message="Invalid broker."):
|
|
476
|
+
super().__init__(message)
|
|
477
|
+
|
|
470
478
|
|
|
471
479
|
class MT5TerminalError(Exception):
|
|
472
480
|
"""Base exception class for trading-related errors."""
|
bbstrader/trading/execution.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from math import log
|
|
2
1
|
import time
|
|
3
2
|
from datetime import datetime
|
|
4
3
|
from bbstrader.metatrader.trade import Trade
|
|
@@ -22,7 +21,7 @@ _TF_MAPPING = {
|
|
|
22
21
|
'D1': 1440
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
TradingDays = [
|
|
26
25
|
'monday',
|
|
27
26
|
'tuesday',
|
|
28
27
|
'wednesday',
|
|
@@ -31,14 +30,18 @@ TRADING_DAYS = [
|
|
|
31
30
|
]
|
|
32
31
|
|
|
33
32
|
def _check_mt5_connection():
|
|
34
|
-
|
|
33
|
+
try:
|
|
34
|
+
init = mt5.initialize()
|
|
35
|
+
if not init:
|
|
36
|
+
raise_mt5_error(INIT_MSG)
|
|
37
|
+
except Exception:
|
|
35
38
|
raise_mt5_error(INIT_MSG)
|
|
36
39
|
|
|
37
40
|
def _mt5_execution(
|
|
38
|
-
|
|
39
|
-
mm,
|
|
40
|
-
|
|
41
|
-
):
|
|
41
|
+
symbol_list, trades_instances, strategy_cls, /,
|
|
42
|
+
mm, trail, stop_trail, trail_after_points, be_plus_points,
|
|
43
|
+
time_frame, iter_time, period, period_end_action, trading_days,
|
|
44
|
+
comment, **kwargs):
|
|
42
45
|
symbols = symbol_list.copy()
|
|
43
46
|
STRATEGY = kwargs.get('strategy_name')
|
|
44
47
|
_max_trades = kwargs.get('max_trades')
|
|
@@ -54,11 +57,15 @@ def _mt5_execution(
|
|
|
54
57
|
if buys is not None:
|
|
55
58
|
logger.info(
|
|
56
59
|
f"Checking for Break even, SYMBOL={symbol}...STRATEGY={STRATEGY}")
|
|
57
|
-
trades_instances[symbol].break_even(
|
|
60
|
+
trades_instances[symbol].break_even(
|
|
61
|
+
mm=mm, trail=trail, stop_trail=stop_trail,
|
|
62
|
+
trail_after_points=trail_after_points, be_plus_points=be_plus_points)
|
|
58
63
|
if sells is not None:
|
|
59
64
|
logger.info(
|
|
60
65
|
f"Checking for Break even, SYMBOL={symbol}...STRATEGY={STRATEGY}")
|
|
61
|
-
trades_instances[symbol].break_even(
|
|
66
|
+
trades_instances[symbol].break_even(
|
|
67
|
+
mm=mm, trail=trail, stop_trail=stop_trail,
|
|
68
|
+
trail_after_points=trail_after_points, be_plus_points=be_plus_points)
|
|
62
69
|
num_days = 0
|
|
63
70
|
time_intervals = 0
|
|
64
71
|
trade_time = _TF_MAPPING[time_frame]
|
|
@@ -169,7 +176,13 @@ def _mt5_execution(
|
|
|
169
176
|
f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
170
177
|
trade.statistics(save=True)
|
|
171
178
|
if trades_instances[symbols[-1]].days_end():
|
|
172
|
-
break
|
|
179
|
+
if period_end_action == 'break':
|
|
180
|
+
break
|
|
181
|
+
elif period_end_action == 'sleep':
|
|
182
|
+
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
183
|
+
logger.info(f"Sleeping for {sleep_time} minutes ...")
|
|
184
|
+
time.sleep(60 * sleep_time)
|
|
185
|
+
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
173
186
|
|
|
174
187
|
elif period.lower() == 'week':
|
|
175
188
|
for symbol in symbols:
|
|
@@ -187,9 +200,15 @@ def _mt5_execution(
|
|
|
187
200
|
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
188
201
|
logger.info(f"Sleeping for {sleep_time} minutes ...")
|
|
189
202
|
time.sleep(60 * sleep_time)
|
|
190
|
-
logger.info("
|
|
203
|
+
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
191
204
|
elif trades_instances[symbols[-1]].days_end() and today == 'friday':
|
|
192
|
-
break
|
|
205
|
+
if period_end_action == 'break':
|
|
206
|
+
break
|
|
207
|
+
elif period_end_action == 'sleep':
|
|
208
|
+
sleep_time = trades_instances[symbols[-1]].sleep_time(weekend=True)
|
|
209
|
+
logger.info(f"Sleeping for {sleep_time} minutes ...")
|
|
210
|
+
time.sleep(60 * sleep_time)
|
|
211
|
+
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
193
212
|
|
|
194
213
|
elif period.lower() == 'month':
|
|
195
214
|
for symbol in symbols:
|
|
@@ -214,14 +233,14 @@ def _mt5_execution(
|
|
|
214
233
|
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
215
234
|
logger.info(f"Sleeping for {sleep_time} minutes ...")
|
|
216
235
|
time.sleep(60 * sleep_time)
|
|
217
|
-
logger.info("
|
|
236
|
+
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
218
237
|
num_days += 1
|
|
219
238
|
elif trades_instances[symbols[-1]].days_end() and today == 'friday':
|
|
220
239
|
sleep_time = trades_instances[symbols[-1]
|
|
221
240
|
].sleep_time(weekend=True)
|
|
222
241
|
logger.info(f"Sleeping for {sleep_time} minutes ...")
|
|
223
242
|
time.sleep(60 * sleep_time)
|
|
224
|
-
logger.info("
|
|
243
|
+
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
225
244
|
num_days += 1
|
|
226
245
|
elif (trades_instances[symbols[-1]].days_end()
|
|
227
246
|
and today == 'friday'
|
|
@@ -317,10 +336,15 @@ class ExecutionEngine():
|
|
|
317
336
|
strategy_cls: Strategy,
|
|
318
337
|
/,
|
|
319
338
|
mm: Optional[bool] = True,
|
|
339
|
+
trail: Optional[bool] = True,
|
|
340
|
+
stop_trail: Optional[int] = None,
|
|
341
|
+
trail_after_points: Optional[int] = None,
|
|
342
|
+
be_plus_points: Optional[int] = None,
|
|
320
343
|
time_frame: Optional[str] = '15m',
|
|
321
344
|
iter_time: Optional[int | float] = 5,
|
|
322
345
|
period: Literal['day', 'week', 'month'] = 'week',
|
|
323
|
-
|
|
346
|
+
period_end_action: Literal['break', 'sleep'] = 'break',
|
|
347
|
+
trading_days: Optional[List[str]] = TradingDays,
|
|
324
348
|
comment: Optional[str] = None,
|
|
325
349
|
**kwargs
|
|
326
350
|
):
|
|
@@ -333,6 +357,8 @@ class ExecutionEngine():
|
|
|
333
357
|
time_frame : Time frame to trade. Defaults to '15m'.
|
|
334
358
|
iter_time : Interval to check for signals and `mm`. Defaults to 5.
|
|
335
359
|
period : Period to trade. Defaults to 'week'.
|
|
360
|
+
period_end_action : Action to take at the end of the period. Defaults to 'break',
|
|
361
|
+
this only applies when period is 'day', 'week'.
|
|
336
362
|
trading_days : Trading days in a week. Defaults to monday to friday.
|
|
337
363
|
comment: Comment for trades. Defaults to None.
|
|
338
364
|
**kwargs: Additional keyword arguments
|
|
@@ -341,10 +367,11 @@ class ExecutionEngine():
|
|
|
341
367
|
- logger (Optional[logging.Logger]): Logger instance. Defaults to None.
|
|
342
368
|
|
|
343
369
|
Note:
|
|
344
|
-
1.
|
|
370
|
+
1. For `trail` , `stop_trail` , `trail_after_points` , `be_plus_points` see `bbstrader.metatrader.trade.Trade.break_even()` .
|
|
371
|
+
2. All Strategies must inherit from `bbstrader.btengine.strategy.Strategy` class
|
|
345
372
|
and have a `calculate_signals` method that returns a dictionary of signals for each symbol in symbol_list.
|
|
346
373
|
|
|
347
|
-
|
|
374
|
+
3. All strategies must have the following arguments in their `__init__` method:
|
|
348
375
|
- bars (DataHandler): DataHandler instance default to None
|
|
349
376
|
- events (Queue): Queue instance default to None
|
|
350
377
|
- symbol_list (List[str]): List of symbols to trade can be none for backtesting
|
|
@@ -354,16 +381,21 @@ class ExecutionEngine():
|
|
|
354
381
|
the `Strategy` class, the `DataHandler` class, the `Portfolio` class and the `ExecutionHandler` class.
|
|
355
382
|
- The `bars` and `events` arguments are used for backtesting only.
|
|
356
383
|
|
|
357
|
-
|
|
384
|
+
4. All strategies must generate signals for backtesting and live trading.
|
|
358
385
|
See the `bbstrader.trading.strategies` module for more information on how to create custom strategies.
|
|
359
386
|
"""
|
|
360
387
|
self.symbol_list = symbol_list
|
|
361
388
|
self.trades_instances = trades_instances
|
|
362
389
|
self.strategy_cls = strategy_cls
|
|
363
390
|
self.mm = mm
|
|
391
|
+
self.trail = trail
|
|
392
|
+
self.stop_trail = stop_trail
|
|
393
|
+
self.trail_after_points = trail_after_points
|
|
394
|
+
self.be_plus_points = be_plus_points
|
|
364
395
|
self.time_frame = time_frame
|
|
365
396
|
self.iter_time = iter_time
|
|
366
397
|
self.period = period
|
|
398
|
+
self.period_end_action = period_end_action
|
|
367
399
|
self.trading_days = trading_days
|
|
368
400
|
self.comment = comment
|
|
369
401
|
self.kwargs = kwargs
|
|
@@ -377,9 +409,14 @@ class ExecutionEngine():
|
|
|
377
409
|
self.trades_instances,
|
|
378
410
|
self.strategy_cls,
|
|
379
411
|
mm=self.mm,
|
|
412
|
+
trail=self.trail,
|
|
413
|
+
stop_trail=self.stop_trail,
|
|
414
|
+
trail_after_points=self.trail_after_points,
|
|
415
|
+
be_plus_points=self.be_plus_points,
|
|
380
416
|
time_frame=self.time_frame,
|
|
381
417
|
iter_time=self.iter_time,
|
|
382
418
|
period=self.period,
|
|
419
|
+
period_end_action=self.period_end_action,
|
|
383
420
|
trading_days=self.trading_days,
|
|
384
421
|
comment=self.comment,
|
|
385
422
|
**self.kwargs
|
bbstrader/trading/strategies.py
CHANGED
|
@@ -19,8 +19,8 @@ from bbstrader.btengine.strategy import Strategy
|
|
|
19
19
|
from bbstrader.btengine.execution import *
|
|
20
20
|
from bbstrader.btengine.data import *
|
|
21
21
|
from bbstrader.tseries import (
|
|
22
|
-
KalmanFilterModel, ArimaGarchModel
|
|
23
|
-
|
|
22
|
+
KalmanFilterModel, ArimaGarchModel)
|
|
23
|
+
|
|
24
24
|
__all__ = [
|
|
25
25
|
'SMAStrategy',
|
|
26
26
|
'ArimaGarchStrategy',
|
|
@@ -653,13 +653,15 @@ class StockIndexSTBOTrading(Strategy):
|
|
|
653
653
|
position.price_open for position in positions
|
|
654
654
|
if position.type == 0 and position.magic == self.ID
|
|
655
655
|
]
|
|
656
|
+
if len(buy_prices) == 0:
|
|
657
|
+
continue
|
|
656
658
|
avg_price = sum(buy_prices) / len(buy_prices)
|
|
657
659
|
if self._calculate_pct_change(
|
|
658
660
|
current_price, avg_price) >= (self.expeted_return[index]):
|
|
659
661
|
signals[index] = 'EXIT'
|
|
660
662
|
self.logger.info(
|
|
661
663
|
f"SYMBOL={index} - Hp={self.heightest_price[index]} - "
|
|
662
|
-
f"Lp={self.lowerst_price[index]} - Cp={current_price} - %
|
|
664
|
+
f"Lp={self.lowerst_price[index]} - Cp={current_price} - %chg={round(down_change, 2)}"
|
|
663
665
|
)
|
|
664
666
|
return signals
|
|
665
667
|
|