bbstrader 0.1.7__py3-none-any.whl → 0.1.9__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 +20 -6
- bbstrader/metatrader/rates.py +6 -9
- bbstrader/metatrader/risk.py +2 -3
- bbstrader/metatrader/trade.py +306 -219
- bbstrader/metatrader/utils.py +37 -29
- bbstrader/trading/execution.py +139 -92
- bbstrader/trading/strategies.py +13 -9
- bbstrader/tseries.py +500 -494
- {bbstrader-0.1.7.dist-info → bbstrader-0.1.9.dist-info}/METADATA +2 -1
- bbstrader-0.1.9.dist-info/RECORD +26 -0
- {bbstrader-0.1.7.dist-info → bbstrader-0.1.9.dist-info}/WHEEL +1 -1
- bbstrader-0.1.7.dist-info/RECORD +0 -26
- {bbstrader-0.1.7.dist-info → bbstrader-0.1.9.dist-info}/LICENSE +0 -0
- {bbstrader-0.1.7.dist-info → bbstrader-0.1.9.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,12 +1,10 @@
|
|
|
1
|
-
from math import log
|
|
2
1
|
import time
|
|
2
|
+
import MetaTrader5 as mt5
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from bbstrader.metatrader.trade import Trade
|
|
5
5
|
from bbstrader.trading.strategies import Strategy
|
|
6
|
-
from
|
|
7
|
-
import
|
|
8
|
-
from bbstrader.metatrader.account import INIT_MSG
|
|
9
|
-
from bbstrader.metatrader.utils import raise_mt5_error
|
|
6
|
+
from bbstrader.metatrader.account import check_mt5_connection
|
|
7
|
+
from typing import Optional, Literal, Tuple, List, Dict
|
|
10
8
|
|
|
11
9
|
|
|
12
10
|
_TF_MAPPING = {
|
|
@@ -22,7 +20,7 @@ _TF_MAPPING = {
|
|
|
22
20
|
'D1': 1440
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
TradingDays = [
|
|
26
24
|
'monday',
|
|
27
25
|
'tuesday',
|
|
28
26
|
'wednesday',
|
|
@@ -30,19 +28,16 @@ TRADING_DAYS = [
|
|
|
30
28
|
'friday'
|
|
31
29
|
]
|
|
32
30
|
|
|
33
|
-
def _check_mt5_connection():
|
|
34
|
-
if not mt5.initialize():
|
|
35
|
-
raise_mt5_error(INIT_MSG)
|
|
36
31
|
|
|
37
32
|
def _mt5_execution(
|
|
38
|
-
|
|
39
|
-
mm,
|
|
40
|
-
|
|
41
|
-
):
|
|
33
|
+
symbol_list, trades_instances, strategy_cls, /,
|
|
34
|
+
mm, trail, stop_trail, trail_after_points, be_plus_points,
|
|
35
|
+
time_frame, iter_time, period, period_end_action, trading_days,
|
|
36
|
+
comment, **kwargs):
|
|
42
37
|
symbols = symbol_list.copy()
|
|
43
38
|
STRATEGY = kwargs.get('strategy_name')
|
|
44
39
|
_max_trades = kwargs.get('max_trades')
|
|
45
|
-
logger =
|
|
40
|
+
logger = trades_instances[symbols[0]].logger
|
|
46
41
|
max_trades = {symbol: _max_trades[symbol] for symbol in symbols}
|
|
47
42
|
if comment is None:
|
|
48
43
|
trade = trades_instances[symbols[0]]
|
|
@@ -54,26 +49,33 @@ def _mt5_execution(
|
|
|
54
49
|
if buys is not None:
|
|
55
50
|
logger.info(
|
|
56
51
|
f"Checking for Break even, SYMBOL={symbol}...STRATEGY={STRATEGY}")
|
|
57
|
-
trades_instances[symbol].break_even(
|
|
52
|
+
trades_instances[symbol].break_even(
|
|
53
|
+
mm=mm, trail=trail, stop_trail=stop_trail,
|
|
54
|
+
trail_after_points=trail_after_points, be_plus_points=be_plus_points)
|
|
58
55
|
if sells is not None:
|
|
59
56
|
logger.info(
|
|
60
57
|
f"Checking for Break even, SYMBOL={symbol}...STRATEGY={STRATEGY}")
|
|
61
|
-
trades_instances[symbol].break_even(
|
|
58
|
+
trades_instances[symbol].break_even(
|
|
59
|
+
mm=mm, trail=trail, stop_trail=stop_trail,
|
|
60
|
+
trail_after_points=trail_after_points, be_plus_points=be_plus_points)
|
|
62
61
|
num_days = 0
|
|
63
62
|
time_intervals = 0
|
|
64
63
|
trade_time = _TF_MAPPING[time_frame]
|
|
65
64
|
|
|
66
65
|
long_market = {symbol: False for symbol in symbols}
|
|
67
66
|
short_market = {symbol: False for symbol in symbols}
|
|
68
|
-
|
|
67
|
+
try:
|
|
68
|
+
check_mt5_connection()
|
|
69
|
+
strategy: Strategy = strategy_cls(symbol_list=symbols, mode='live', **kwargs)
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.error(f"Error initializing strategy, {e}, STRATEGY={STRATEGY}")
|
|
72
|
+
return
|
|
69
73
|
logger.info(
|
|
70
74
|
f'Running {STRATEGY} Strategy on {symbols} in {time_frame} Interval ...')
|
|
71
|
-
|
|
72
|
-
symbol_list=symbols, mode='live', **kwargs)
|
|
73
|
-
|
|
75
|
+
|
|
74
76
|
while True:
|
|
75
77
|
try:
|
|
76
|
-
|
|
78
|
+
check_mt5_connection()
|
|
77
79
|
current_date = datetime.now()
|
|
78
80
|
today = current_date.strftime("%A").lower()
|
|
79
81
|
time.sleep(0.5)
|
|
@@ -98,13 +100,21 @@ def _mt5_execution(
|
|
|
98
100
|
sells[symbol]) >= max_trades[symbol] for symbol in symbols}
|
|
99
101
|
except Exception as e:
|
|
100
102
|
logger.error(f"{e}, STRATEGY={STRATEGY}")
|
|
103
|
+
continue
|
|
101
104
|
time.sleep(0.5)
|
|
105
|
+
try:
|
|
106
|
+
check_mt5_connection()
|
|
107
|
+
signals = strategy.calculate_signals()
|
|
108
|
+
except Exception as e:
|
|
109
|
+
logger.error(f"Calculating signal, {e}, STRATEGY={STRATEGY}")
|
|
110
|
+
continue
|
|
102
111
|
for symbol in symbols:
|
|
103
112
|
try:
|
|
113
|
+
check_mt5_connection()
|
|
104
114
|
trade = trades_instances[symbol]
|
|
105
115
|
logger.info(
|
|
106
116
|
f"Calculating signal... SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
107
|
-
signal =
|
|
117
|
+
signal = signals[symbol]
|
|
108
118
|
if trade.trading_time() and today in trading_days:
|
|
109
119
|
if signal is not None:
|
|
110
120
|
logger.info(
|
|
@@ -148,7 +158,7 @@ def _mt5_execution(
|
|
|
148
158
|
|
|
149
159
|
except Exception as e:
|
|
150
160
|
logger.error(f"{e}, SYMBOL={symbol}, STRATEGY={STRATEGY}")
|
|
151
|
-
|
|
161
|
+
continue
|
|
152
162
|
time.sleep((60 * iter_time) - 1.0)
|
|
153
163
|
if iter_time == 1:
|
|
154
164
|
time_intervals += 1
|
|
@@ -160,74 +170,92 @@ def _mt5_execution(
|
|
|
160
170
|
f"(e.g; if time_frame is 15m, iter_time must be 1.5, 3, 3, 15 etc)"
|
|
161
171
|
)
|
|
162
172
|
print()
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
+
try:
|
|
174
|
+
check_mt5_connection()
|
|
175
|
+
day_end = all(trade.days_end() for trade in trades_instances.values())
|
|
176
|
+
if period.lower() == 'day':
|
|
177
|
+
for symbol in symbols:
|
|
178
|
+
trade = trades_instances[symbol]
|
|
179
|
+
if trade.days_end():
|
|
180
|
+
trade.close_positions(position_type='all', comment=comment)
|
|
181
|
+
logger.info(
|
|
182
|
+
f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
183
|
+
trade.statistics(save=True)
|
|
184
|
+
if day_end:
|
|
185
|
+
if period_end_action == 'break':
|
|
186
|
+
break
|
|
187
|
+
elif period_end_action == 'sleep':
|
|
188
|
+
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
189
|
+
logger.info(f"Sleeping for {sleep_time} minutes ...\n")
|
|
190
|
+
time.sleep(60 * sleep_time)
|
|
191
|
+
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
173
192
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
193
|
+
elif period.lower() == 'week':
|
|
194
|
+
for symbol in symbols:
|
|
195
|
+
trade = trades_instances[symbol]
|
|
196
|
+
if trade.days_end() and today != 'friday':
|
|
197
|
+
logger.info(
|
|
198
|
+
f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
180
199
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
200
|
+
elif trade.days_end() and today == 'friday':
|
|
201
|
+
trade.close_positions(position_type='all', comment=comment)
|
|
202
|
+
logger.info(
|
|
203
|
+
f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
204
|
+
trade.statistics(save=True)
|
|
205
|
+
if day_end and today != 'friday':
|
|
206
|
+
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
207
|
+
logger.info(f"Sleeping for {sleep_time} minutes ...\n")
|
|
208
|
+
time.sleep(60 * sleep_time)
|
|
209
|
+
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
210
|
+
elif day_end and today == 'friday':
|
|
211
|
+
if period_end_action == 'break':
|
|
212
|
+
break
|
|
213
|
+
elif period_end_action == 'sleep':
|
|
214
|
+
sleep_time = trades_instances[symbols[-1]].sleep_time(weekend=True)
|
|
215
|
+
logger.info(f"Sleeping for {sleep_time} minutes ...\n")
|
|
216
|
+
time.sleep(60 * sleep_time)
|
|
217
|
+
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
193
218
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
219
|
+
elif period.lower() == 'month':
|
|
220
|
+
for symbol in symbols:
|
|
221
|
+
trade = trades_instances[symbol]
|
|
222
|
+
if trade.days_end() and today != 'friday':
|
|
223
|
+
logger.info(
|
|
224
|
+
f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
200
225
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
226
|
+
elif trade.days_end() and today == 'friday':
|
|
227
|
+
logger.info(
|
|
228
|
+
f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
229
|
+
elif (
|
|
230
|
+
trade.days_end()
|
|
231
|
+
and today == 'friday'
|
|
232
|
+
and num_days/len(symbols) >= 20
|
|
233
|
+
):
|
|
234
|
+
trade.close_positions(position_type='all', comment=comment)
|
|
235
|
+
logger.info(
|
|
236
|
+
f"End of the Month !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
237
|
+
trade.statistics(save=True)
|
|
238
|
+
if day_end and today != 'friday':
|
|
239
|
+
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
240
|
+
logger.info(f"Sleeping for {sleep_time} minutes ...\n")
|
|
241
|
+
time.sleep(60 * sleep_time)
|
|
242
|
+
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
243
|
+
num_days += 1
|
|
244
|
+
elif day_end and today == 'friday':
|
|
245
|
+
sleep_time = trades_instances[symbols[-1]
|
|
246
|
+
].sleep_time(weekend=True)
|
|
247
|
+
logger.info(f"Sleeping for {sleep_time} minutes ...\n")
|
|
248
|
+
time.sleep(60 * sleep_time)
|
|
249
|
+
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
250
|
+
num_days += 1
|
|
251
|
+
elif (day_end
|
|
252
|
+
and today == 'friday'
|
|
253
|
+
and num_days/len(symbols) >= 20
|
|
254
|
+
):
|
|
255
|
+
break
|
|
256
|
+
except Exception as e:
|
|
257
|
+
logger.error(f"Handling period end actions, {e}, STRATEGY={STRATEGY}")
|
|
258
|
+
continue
|
|
231
259
|
|
|
232
260
|
|
|
233
261
|
def _tws_execution(*args, **kwargs):
|
|
@@ -317,10 +345,15 @@ class ExecutionEngine():
|
|
|
317
345
|
strategy_cls: Strategy,
|
|
318
346
|
/,
|
|
319
347
|
mm: Optional[bool] = True,
|
|
348
|
+
trail: Optional[bool] = True,
|
|
349
|
+
stop_trail: Optional[int] = None,
|
|
350
|
+
trail_after_points: Optional[int] = None,
|
|
351
|
+
be_plus_points: Optional[int] = None,
|
|
320
352
|
time_frame: Optional[str] = '15m',
|
|
321
353
|
iter_time: Optional[int | float] = 5,
|
|
322
354
|
period: Literal['day', 'week', 'month'] = 'week',
|
|
323
|
-
|
|
355
|
+
period_end_action: Literal['break', 'sleep'] = 'break',
|
|
356
|
+
trading_days: Optional[List[str]] = TradingDays,
|
|
324
357
|
comment: Optional[str] = None,
|
|
325
358
|
**kwargs
|
|
326
359
|
):
|
|
@@ -333,18 +366,20 @@ class ExecutionEngine():
|
|
|
333
366
|
time_frame : Time frame to trade. Defaults to '15m'.
|
|
334
367
|
iter_time : Interval to check for signals and `mm`. Defaults to 5.
|
|
335
368
|
period : Period to trade. Defaults to 'week'.
|
|
369
|
+
period_end_action : Action to take at the end of the period. Defaults to 'break',
|
|
370
|
+
this only applies when period is 'day', 'week'.
|
|
336
371
|
trading_days : Trading days in a week. Defaults to monday to friday.
|
|
337
372
|
comment: Comment for trades. Defaults to None.
|
|
338
373
|
**kwargs: Additional keyword arguments
|
|
339
374
|
- strategy_name (Optional[str]): Strategy name. Defaults to None.
|
|
340
375
|
- max_trades (Dict[str, int]): Maximum trades per symbol. Defaults to None.
|
|
341
|
-
- logger (Optional[logging.Logger]): Logger instance. Defaults to None.
|
|
342
376
|
|
|
343
377
|
Note:
|
|
344
|
-
1.
|
|
378
|
+
1. For `trail` , `stop_trail` , `trail_after_points` , `be_plus_points` see `bbstrader.metatrader.trade.Trade.break_even()` .
|
|
379
|
+
2. All Strategies must inherit from `bbstrader.btengine.strategy.Strategy` class
|
|
345
380
|
and have a `calculate_signals` method that returns a dictionary of signals for each symbol in symbol_list.
|
|
346
381
|
|
|
347
|
-
|
|
382
|
+
3. All strategies must have the following arguments in their `__init__` method:
|
|
348
383
|
- bars (DataHandler): DataHandler instance default to None
|
|
349
384
|
- events (Queue): Queue instance default to None
|
|
350
385
|
- symbol_list (List[str]): List of symbols to trade can be none for backtesting
|
|
@@ -354,16 +389,21 @@ class ExecutionEngine():
|
|
|
354
389
|
the `Strategy` class, the `DataHandler` class, the `Portfolio` class and the `ExecutionHandler` class.
|
|
355
390
|
- The `bars` and `events` arguments are used for backtesting only.
|
|
356
391
|
|
|
357
|
-
|
|
392
|
+
4. All strategies must generate signals for backtesting and live trading.
|
|
358
393
|
See the `bbstrader.trading.strategies` module for more information on how to create custom strategies.
|
|
359
394
|
"""
|
|
360
395
|
self.symbol_list = symbol_list
|
|
361
396
|
self.trades_instances = trades_instances
|
|
362
397
|
self.strategy_cls = strategy_cls
|
|
363
398
|
self.mm = mm
|
|
399
|
+
self.trail = trail
|
|
400
|
+
self.stop_trail = stop_trail
|
|
401
|
+
self.trail_after_points = trail_after_points
|
|
402
|
+
self.be_plus_points = be_plus_points
|
|
364
403
|
self.time_frame = time_frame
|
|
365
404
|
self.iter_time = iter_time
|
|
366
405
|
self.period = period
|
|
406
|
+
self.period_end_action = period_end_action
|
|
367
407
|
self.trading_days = trading_days
|
|
368
408
|
self.comment = comment
|
|
369
409
|
self.kwargs = kwargs
|
|
@@ -372,14 +412,21 @@ class ExecutionEngine():
|
|
|
372
412
|
if terminal not in _TERMINALS:
|
|
373
413
|
raise ValueError(
|
|
374
414
|
f"Invalid terminal: {terminal}. Must be either 'MT5' or 'TWS'")
|
|
415
|
+
elif terminal == 'MT5':
|
|
416
|
+
check_mt5_connection()
|
|
375
417
|
_TERMINALS[terminal](
|
|
376
418
|
self.symbol_list,
|
|
377
419
|
self.trades_instances,
|
|
378
420
|
self.strategy_cls,
|
|
379
421
|
mm=self.mm,
|
|
422
|
+
trail=self.trail,
|
|
423
|
+
stop_trail=self.stop_trail,
|
|
424
|
+
trail_after_points=self.trail_after_points,
|
|
425
|
+
be_plus_points=self.be_plus_points,
|
|
380
426
|
time_frame=self.time_frame,
|
|
381
427
|
iter_time=self.iter_time,
|
|
382
428
|
period=self.period,
|
|
429
|
+
period_end_action=self.period_end_action,
|
|
383
430
|
trading_days=self.trading_days,
|
|
384
431
|
comment=self.comment,
|
|
385
432
|
**self.kwargs
|
bbstrader/trading/strategies.py
CHANGED
|
@@ -7,7 +7,6 @@ import pandas as pd
|
|
|
7
7
|
from queue import Queue
|
|
8
8
|
import yfinance as yf
|
|
9
9
|
from datetime import datetime
|
|
10
|
-
from typing import List, Literal, Dict, Union, Optional
|
|
11
10
|
from bbstrader.metatrader.rates import Rates
|
|
12
11
|
from bbstrader.metatrader.account import Account
|
|
13
12
|
from bbstrader.btengine.event import SignalEvent
|
|
@@ -19,18 +18,20 @@ from bbstrader.btengine.strategy import Strategy
|
|
|
19
18
|
from bbstrader.btengine.execution import *
|
|
20
19
|
from bbstrader.btengine.data import *
|
|
21
20
|
from bbstrader.tseries import (
|
|
22
|
-
KalmanFilterModel, ArimaGarchModel
|
|
23
|
-
|
|
21
|
+
KalmanFilterModel, ArimaGarchModel)
|
|
22
|
+
from typing import Union, Optional, Literal, Dict, List
|
|
23
|
+
|
|
24
24
|
__all__ = [
|
|
25
25
|
'SMAStrategy',
|
|
26
26
|
'ArimaGarchStrategy',
|
|
27
27
|
'KalmanFilterStrategy',
|
|
28
28
|
'StockIndexSTBOTrading',
|
|
29
|
-
'test_strategy'
|
|
29
|
+
'test_strategy',
|
|
30
|
+
'get_quantities'
|
|
30
31
|
]
|
|
31
32
|
|
|
32
33
|
|
|
33
|
-
def
|
|
34
|
+
def get_quantities(quantities, symbol_list):
|
|
34
35
|
if isinstance(quantities, dict):
|
|
35
36
|
return quantities
|
|
36
37
|
elif isinstance(quantities, int):
|
|
@@ -84,7 +85,7 @@ class SMAStrategy(Strategy):
|
|
|
84
85
|
self.short_window = kwargs.get("short_window", 50)
|
|
85
86
|
self.long_window = kwargs.get("long_window", 200)
|
|
86
87
|
self.tf = kwargs.get("time_frame", 'D1')
|
|
87
|
-
self.qty =
|
|
88
|
+
self.qty = get_quantities(
|
|
88
89
|
kwargs.get('quantities', 100), self.symbol_list)
|
|
89
90
|
self.sd = kwargs.get("session_duration", 23.0)
|
|
90
91
|
self.risk_models = build_hmm_models(self.symbol_list, **kwargs)
|
|
@@ -246,7 +247,7 @@ class ArimaGarchStrategy(Strategy):
|
|
|
246
247
|
self.symbol_list = self.bars.symbol_list
|
|
247
248
|
self.mode = mode
|
|
248
249
|
|
|
249
|
-
self.qty =
|
|
250
|
+
self.qty = get_quantities(
|
|
250
251
|
kwargs.get('quantities', 100), self.symbol_list)
|
|
251
252
|
self.arima_window = kwargs.get('arima_window', 252)
|
|
252
253
|
self.tf = kwargs.get('time_frame', 'D1')
|
|
@@ -557,6 +558,7 @@ class KalmanFilterStrategy(Strategy):
|
|
|
557
558
|
self.calculate_backtest_signals()
|
|
558
559
|
elif self.mode == 'live':
|
|
559
560
|
return self.calculate_live_signals()
|
|
561
|
+
|
|
560
562
|
|
|
561
563
|
class StockIndexSTBOTrading(Strategy):
|
|
562
564
|
"""
|
|
@@ -617,7 +619,7 @@ class StockIndexSTBOTrading(Strategy):
|
|
|
617
619
|
self.lowerst_price = {index: None for index in symbols}
|
|
618
620
|
|
|
619
621
|
if self.mode == 'backtest':
|
|
620
|
-
self.qty =
|
|
622
|
+
self.qty = get_quantities(quantities, symbols)
|
|
621
623
|
self.num_buys = {index: 0 for index in symbols}
|
|
622
624
|
self.buy_prices = {index: [] for index in symbols}
|
|
623
625
|
|
|
@@ -653,13 +655,15 @@ class StockIndexSTBOTrading(Strategy):
|
|
|
653
655
|
position.price_open for position in positions
|
|
654
656
|
if position.type == 0 and position.magic == self.ID
|
|
655
657
|
]
|
|
658
|
+
if len(buy_prices) == 0:
|
|
659
|
+
continue
|
|
656
660
|
avg_price = sum(buy_prices) / len(buy_prices)
|
|
657
661
|
if self._calculate_pct_change(
|
|
658
662
|
current_price, avg_price) >= (self.expeted_return[index]):
|
|
659
663
|
signals[index] = 'EXIT'
|
|
660
664
|
self.logger.info(
|
|
661
665
|
f"SYMBOL={index} - Hp={self.heightest_price[index]} - "
|
|
662
|
-
f"Lp={self.lowerst_price[index]} - Cp={current_price} - %
|
|
666
|
+
f"Lp={self.lowerst_price[index]} - Cp={current_price} - %chg={round(down_change, 2)}"
|
|
663
667
|
)
|
|
664
668
|
return signals
|
|
665
669
|
|