bbstrader 0.1.9__py3-none-any.whl → 0.1.92__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/__ini__.py +4 -2
- bbstrader/btengine/__init__.py +5 -5
- bbstrader/btengine/backtest.py +60 -12
- bbstrader/btengine/data.py +157 -57
- bbstrader/btengine/event.py +14 -5
- bbstrader/btengine/execution.py +128 -26
- bbstrader/btengine/performance.py +4 -7
- bbstrader/btengine/portfolio.py +83 -36
- bbstrader/btengine/strategy.py +525 -6
- bbstrader/config.py +111 -0
- bbstrader/metatrader/__init__.py +4 -4
- bbstrader/metatrader/account.py +348 -53
- bbstrader/metatrader/rates.py +280 -27
- bbstrader/metatrader/risk.py +34 -23
- bbstrader/metatrader/trade.py +328 -169
- bbstrader/metatrader/utils.py +2 -53
- bbstrader/models/factors.py +0 -0
- bbstrader/models/ml.py +0 -0
- bbstrader/models/optimization.py +170 -0
- bbstrader/models/portfolios.py +202 -0
- bbstrader/trading/__init__.py +1 -1
- bbstrader/trading/execution.py +275 -169
- bbstrader/trading/scripts.py +57 -0
- bbstrader/trading/strategies.py +41 -65
- bbstrader/tseries.py +274 -39
- {bbstrader-0.1.9.dist-info → bbstrader-0.1.92.dist-info}/METADATA +12 -3
- bbstrader-0.1.92.dist-info/RECORD +32 -0
- {bbstrader-0.1.9.dist-info → bbstrader-0.1.92.dist-info}/WHEEL +1 -1
- bbstrader-0.1.9.dist-info/RECORD +0 -26
- {bbstrader-0.1.9.dist-info → bbstrader-0.1.92.dist-info}/LICENSE +0 -0
- {bbstrader-0.1.9.dist-info → bbstrader-0.1.92.dist-info}/top_level.txt +0 -0
bbstrader/trading/execution.py
CHANGED
|
@@ -1,23 +1,39 @@
|
|
|
1
1
|
import time
|
|
2
|
-
import MetaTrader5 as mt5
|
|
3
2
|
from datetime import datetime
|
|
4
3
|
from bbstrader.metatrader.trade import Trade
|
|
5
|
-
from bbstrader.
|
|
4
|
+
from bbstrader.btengine.strategy import(
|
|
5
|
+
Strategy,
|
|
6
|
+
MT5Strategy
|
|
7
|
+
)
|
|
8
|
+
from bbstrader.trading.scripts import send_message
|
|
6
9
|
from bbstrader.metatrader.account import check_mt5_connection
|
|
7
|
-
from typing import
|
|
10
|
+
from typing import (
|
|
11
|
+
Optional,
|
|
12
|
+
Literal,
|
|
13
|
+
Tuple,
|
|
14
|
+
List,
|
|
15
|
+
Dict
|
|
16
|
+
)
|
|
8
17
|
|
|
18
|
+
__all__ = [
|
|
19
|
+
'MT5ExecutionEngine',
|
|
20
|
+
'TWSExecutionEngine'
|
|
21
|
+
]
|
|
9
22
|
|
|
10
23
|
_TF_MAPPING = {
|
|
11
|
-
'1m':
|
|
12
|
-
'3m':
|
|
13
|
-
'5m':
|
|
14
|
-
'10m':
|
|
15
|
-
'15m':
|
|
16
|
-
'30m':
|
|
17
|
-
'1h':
|
|
18
|
-
'2h':
|
|
19
|
-
'4h':
|
|
20
|
-
'
|
|
24
|
+
'1m': 1,
|
|
25
|
+
'3m': 3,
|
|
26
|
+
'5m': 5,
|
|
27
|
+
'10m': 10,
|
|
28
|
+
'15m': 15,
|
|
29
|
+
'30m': 30,
|
|
30
|
+
'1h': 60,
|
|
31
|
+
'2h': 120,
|
|
32
|
+
'4h': 240,
|
|
33
|
+
'6h': 360,
|
|
34
|
+
'8h': 480,
|
|
35
|
+
'12h': 720,
|
|
36
|
+
'D1': 1440
|
|
21
37
|
}
|
|
22
38
|
|
|
23
39
|
TradingDays = [
|
|
@@ -28,50 +44,83 @@ TradingDays = [
|
|
|
28
44
|
'friday'
|
|
29
45
|
]
|
|
30
46
|
|
|
47
|
+
BUYS = ['BMKT', 'BLMT', 'BSTP', 'BSTPLMT']
|
|
48
|
+
SELLS = ['SMKT', 'SLMT', 'SSTP', 'SSTPLMT']
|
|
49
|
+
|
|
50
|
+
ORDERS_TYPES = ["orders","buy_stops", "sell_stops", "buy_limits",
|
|
51
|
+
"sell_limits", "buy_stop_limits", "sell_stop_limits"]
|
|
52
|
+
POSITIONS_TYPES = ["positions", "buys", "sells", "profitables", "losings"]
|
|
53
|
+
|
|
54
|
+
ACTIONS = ["buys", "sells"]
|
|
55
|
+
STOPS = ["buy_stops", "sell_stops"]
|
|
56
|
+
LIMITS = ["buy_limits", "sell_limits"]
|
|
57
|
+
STOP_LIMITS = ["buy_stop_limits", "sell_stop_limits"]
|
|
58
|
+
|
|
59
|
+
EXIT_SIGNAL_ACTIONS = {
|
|
60
|
+
"EXIT": {a: a[:-1] for a in ACTIONS},
|
|
61
|
+
"EXIT_LONG": {"buys": "buy"},
|
|
62
|
+
"EXIT_SHORT": {"sells": "sell"},
|
|
63
|
+
"EXIT_STOP": {stop: stop for stop in STOPS},
|
|
64
|
+
"EXIT_LONG_STOP": {"buy_stops": "buy_stops"},
|
|
65
|
+
"EXIT_SHORT_STOP": {"sell_stops": "sell_stops"},
|
|
66
|
+
"EXIT_LIMIT": {limit: limit for limit in LIMITS},
|
|
67
|
+
"EXIT_LONG_LIMIT": {"buy_limits": "buy_limits"},
|
|
68
|
+
"EXIT_SHORT_LIMIT": {"sell_limits": "sell_limits"},
|
|
69
|
+
"EXIT_STOP_LIMIT": {sl: sl for sl in STOP_LIMITS},
|
|
70
|
+
"EXIT_LONG_STOP_LIMIT": {STOP_LIMITS[0]: STOP_LIMITS[0]},
|
|
71
|
+
"EXIT_SHORT_STOP_LIMIT":{STOP_LIMITS[1]: STOP_LIMITS[1]},
|
|
72
|
+
"EXIT_PROFITABLES": {"profitables": "profitables"},
|
|
73
|
+
"EXIT_LOSINGS": {"losings": "losings"},
|
|
74
|
+
"EXIT_ALL_POSITIONS": {"positions": "all"},
|
|
75
|
+
"EXIT_ALL_ORDERS": {"orders": "all"}
|
|
76
|
+
}
|
|
77
|
+
|
|
31
78
|
|
|
32
79
|
def _mt5_execution(
|
|
33
80
|
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,
|
|
81
|
+
mm, trail, stop_trail, trail_after_points, be_plus_points, show_positions_orders,
|
|
82
|
+
time_frame, iter_time, use_trade_time, period, period_end_action, closing_pnl, trading_days,
|
|
36
83
|
comment, **kwargs):
|
|
37
|
-
symbols
|
|
84
|
+
symbols = symbol_list.copy()
|
|
38
85
|
STRATEGY = kwargs.get('strategy_name')
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
86
|
+
mtrades = kwargs.get('max_trades')
|
|
87
|
+
notify = kwargs.get('notify', False)
|
|
88
|
+
if notify:
|
|
89
|
+
telegram = kwargs.get('telegram', False)
|
|
90
|
+
bot_token = kwargs.get('bot_token')
|
|
91
|
+
chat_id = kwargs.get('chat_id')
|
|
92
|
+
|
|
93
|
+
def _send_notification(signal):
|
|
94
|
+
send_message(message=signal, notify_me=notify,
|
|
95
|
+
telegram=telegram, token=bot_token, chat_id=chat_id)
|
|
96
|
+
|
|
97
|
+
logger = trades_instances[symbols[0]].logger
|
|
98
|
+
max_trades = {symbol: mtrades[symbol] for symbol in symbols}
|
|
42
99
|
if comment is None:
|
|
43
100
|
trade = trades_instances[symbols[0]]
|
|
44
101
|
comment = f"{trade.expert_name}@{trade.version}"
|
|
45
102
|
|
|
46
|
-
def check(buys
|
|
103
|
+
def check(buys, sells, symbol):
|
|
47
104
|
if not mm:
|
|
48
105
|
return
|
|
49
|
-
if buys is not None:
|
|
50
|
-
logger.info(
|
|
51
|
-
f"Checking for Break even, SYMBOL={symbol}...STRATEGY={STRATEGY}")
|
|
106
|
+
if buys is not None or sells is not None:
|
|
52
107
|
trades_instances[symbol].break_even(
|
|
53
108
|
mm=mm, trail=trail, stop_trail=stop_trail,
|
|
54
109
|
trail_after_points=trail_after_points, be_plus_points=be_plus_points)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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)
|
|
61
|
-
num_days = 0
|
|
62
|
-
time_intervals = 0
|
|
63
|
-
trade_time = _TF_MAPPING[time_frame]
|
|
110
|
+
num_days = 0
|
|
111
|
+
time_intervals = 0
|
|
112
|
+
trade_time = _TF_MAPPING[time_frame]
|
|
64
113
|
|
|
65
|
-
long_market
|
|
66
|
-
short_market
|
|
114
|
+
long_market = {symbol: False for symbol in symbols}
|
|
115
|
+
short_market = {symbol: False for symbol in symbols}
|
|
67
116
|
try:
|
|
68
117
|
check_mt5_connection()
|
|
69
|
-
strategy:
|
|
118
|
+
strategy: MT5Strategy = strategy_cls(symbol_list=symbols, mode='live', **kwargs)
|
|
70
119
|
except Exception as e:
|
|
71
|
-
logger.error(f"
|
|
120
|
+
logger.error(f"Initializing strategy, {e}, STRATEGY={STRATEGY}")
|
|
72
121
|
return
|
|
73
122
|
logger.info(
|
|
74
|
-
f'Running {STRATEGY} Strategy
|
|
123
|
+
f'Running {STRATEGY} Strategy in {time_frame} Interval ...')
|
|
75
124
|
|
|
76
125
|
while True:
|
|
77
126
|
try:
|
|
@@ -79,27 +128,33 @@ def _mt5_execution(
|
|
|
79
128
|
current_date = datetime.now()
|
|
80
129
|
today = current_date.strftime("%A").lower()
|
|
81
130
|
time.sleep(0.5)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
131
|
+
positions_orders = {}
|
|
132
|
+
for type in POSITIONS_TYPES + ORDERS_TYPES:
|
|
133
|
+
positions_orders[type] = {}
|
|
134
|
+
for symbol in symbols:
|
|
135
|
+
positions_orders[type][symbol] = None
|
|
136
|
+
func = getattr(trades_instances[symbol], f"get_current_{type}")
|
|
137
|
+
positions_orders[type][symbol] = func()
|
|
138
|
+
buys = positions_orders['buys']
|
|
139
|
+
sells = positions_orders['sells']
|
|
140
|
+
for symbol in symbols:
|
|
141
|
+
for type in POSITIONS_TYPES + ORDERS_TYPES:
|
|
142
|
+
if positions_orders[type][symbol] is not None:
|
|
143
|
+
if show_positions_orders:
|
|
144
|
+
logger.info(
|
|
145
|
+
f"Current {type.upper()} SYMBOL={symbol}: \
|
|
146
|
+
{positions_orders[type][symbol]}, STRATEGY={STRATEGY}")
|
|
147
|
+
long_market = {
|
|
148
|
+
symbol: buys[symbol] is not None
|
|
149
|
+
and len(buys[symbol]) >= max_trades[symbol] for symbol in symbols
|
|
85
150
|
}
|
|
86
|
-
|
|
87
|
-
symbol:
|
|
88
|
-
for symbol in symbols
|
|
151
|
+
short_market = {
|
|
152
|
+
symbol: sells[symbol] is not None
|
|
153
|
+
and len(sells[symbol]) >= max_trades[symbol] for symbol in symbols
|
|
89
154
|
}
|
|
90
|
-
|
|
91
|
-
if buys[symbol] is not None:
|
|
92
|
-
logger.info(
|
|
93
|
-
f"Current buy positions SYMBOL={symbol}: {buys[symbol]}, STRATEGY={STRATEGY}")
|
|
94
|
-
if sells[symbol] is not None:
|
|
95
|
-
logger.info(
|
|
96
|
-
f"Current sell positions SYMBOL={symbol}: {sells[symbol]}, STRATEGY={STRATEGY}")
|
|
97
|
-
long_market = {symbol: buys[symbol] is not None and len(
|
|
98
|
-
buys[symbol]) >= max_trades[symbol] for symbol in symbols}
|
|
99
|
-
short_market = {symbol: sells[symbol] is not None and len(
|
|
100
|
-
sells[symbol]) >= max_trades[symbol] for symbol in symbols}
|
|
155
|
+
|
|
101
156
|
except Exception as e:
|
|
102
|
-
logger.error(f"{e}, STRATEGY={STRATEGY}")
|
|
157
|
+
logger.error(f"Handling Positions and Orders, {e}, STRATEGY={STRATEGY}")
|
|
103
158
|
continue
|
|
104
159
|
time.sleep(0.5)
|
|
105
160
|
try:
|
|
@@ -111,53 +166,85 @@ def _mt5_execution(
|
|
|
111
166
|
for symbol in symbols:
|
|
112
167
|
try:
|
|
113
168
|
check_mt5_connection()
|
|
114
|
-
trade = trades_instances[symbol]
|
|
115
|
-
|
|
116
|
-
|
|
169
|
+
trade: Trade = trades_instances[symbol]
|
|
170
|
+
tfmsg = f"Time Frame Not completed !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}"
|
|
171
|
+
riskmsg = f"Risk not allowed !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}"
|
|
117
172
|
signal = signals[symbol]
|
|
173
|
+
if isinstance(signal, dict):
|
|
174
|
+
signal = signal['action']
|
|
175
|
+
price = signal['price']
|
|
176
|
+
stoplimit = signal.get('stoplimit')
|
|
177
|
+
elif isinstance(signal, str):
|
|
178
|
+
price = None
|
|
179
|
+
stoplimit = None
|
|
118
180
|
if trade.trading_time() and today in trading_days:
|
|
119
181
|
if signal is not None:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
182
|
+
signal = 'BMKT' if signal == 'LONG' else signal
|
|
183
|
+
signal = 'SMKT' if signal == 'SHORT' else signal
|
|
184
|
+
info = f"SIGNAL = {signal}, SYMBOL={trade.symbol}, STRATEGY={STRATEGY}"
|
|
185
|
+
msg = f"Sending {signal} Order ... SYMBOL={trade.symbol}, STRATEGY={STRATEGY}"
|
|
186
|
+
if signal not in EXIT_SIGNAL_ACTIONS:
|
|
187
|
+
logger.info(info)
|
|
188
|
+
if signal in EXIT_SIGNAL_ACTIONS:
|
|
189
|
+
for exit_signal, actions in EXIT_SIGNAL_ACTIONS.items():
|
|
190
|
+
for position_type, order_type in actions.items():
|
|
191
|
+
if positions_orders[position_type][symbol] is not None:
|
|
192
|
+
if notify:
|
|
193
|
+
_send_notification(info)
|
|
194
|
+
if position_type in POSITIONS_TYPES:
|
|
195
|
+
trade.close_positions(position_type=order_type)
|
|
196
|
+
else:
|
|
197
|
+
trade.close_orders(order_type=order_type)
|
|
198
|
+
elif signal in BUYS and not long_market[symbol]:
|
|
199
|
+
if use_trade_time:
|
|
200
|
+
if time_intervals % trade_time == 0 or buys[symbol] is None:
|
|
201
|
+
logger.info(msg)
|
|
202
|
+
if notify:
|
|
203
|
+
_send_notification(info)
|
|
204
|
+
trade.open_buy_position(
|
|
205
|
+
action=signal, price=price, stoplimit=stoplimit, mm=mm, comment=comment)
|
|
206
|
+
else:
|
|
207
|
+
logger.info(tfmsg)
|
|
208
|
+
check(buys[symbol], sells[symbol], symbol)
|
|
131
209
|
else:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
210
|
+
logger.info(msg)
|
|
211
|
+
if notify:
|
|
212
|
+
_send_notification(info)
|
|
213
|
+
trade.open_buy_position(
|
|
214
|
+
action=signal, price=price, stoplimit=stoplimit, mm=mm, comment=comment)
|
|
215
|
+
elif signal in BUYS and long_market[symbol]:
|
|
216
|
+
logger.info(riskmsg)
|
|
136
217
|
check(buys[symbol], sells[symbol], symbol)
|
|
137
218
|
|
|
138
|
-
elif signal
|
|
139
|
-
if
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
219
|
+
elif signal in SELLS and not short_market[symbol]:
|
|
220
|
+
if use_trade_time:
|
|
221
|
+
if time_intervals % trade_time == 0 or sells[symbol] is None:
|
|
222
|
+
logger.info(msg)
|
|
223
|
+
if notify:
|
|
224
|
+
_send_notification(info)
|
|
225
|
+
trade.open_sell_position(
|
|
226
|
+
action=signal, price=price, stoplimit=stoplimit, mm=mm, comment=comment)
|
|
227
|
+
else:
|
|
228
|
+
logger.info(tfmsg)
|
|
229
|
+
check(buys[symbol], sells[symbol], symbol)
|
|
144
230
|
else:
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
231
|
+
logger.info(msg)
|
|
232
|
+
if notify:
|
|
233
|
+
_send_notification(info)
|
|
234
|
+
trade.open_sell_position(
|
|
235
|
+
action=signal, price=price, stoplimit=stoplimit, mm=mm, comment=comment)
|
|
236
|
+
elif signal in SELLS and short_market[symbol]:
|
|
237
|
+
logger.info(riskmsg)
|
|
149
238
|
check(buys[symbol], sells[symbol], symbol)
|
|
150
239
|
else:
|
|
151
|
-
logger.info(
|
|
152
|
-
f"There is no signal !! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
153
240
|
check(buys[symbol], sells[symbol], symbol)
|
|
154
241
|
else:
|
|
155
242
|
logger.info(
|
|
156
|
-
f"
|
|
243
|
+
f"Not trading Time !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
157
244
|
check(buys[symbol], sells[symbol], symbol)
|
|
158
245
|
|
|
159
246
|
except Exception as e:
|
|
160
|
-
logger.error(f"{e}, SYMBOL={symbol}, STRATEGY={STRATEGY}")
|
|
247
|
+
logger.error(f"Handling Signals {e}, SYMBOL={symbol}, STRATEGY={STRATEGY}")
|
|
161
248
|
continue
|
|
162
249
|
time.sleep((60 * iter_time) - 1.0)
|
|
163
250
|
if iter_time == 1:
|
|
@@ -169,87 +256,94 @@ def _mt5_execution(
|
|
|
169
256
|
f"iter_time must be a multiple of the {time_frame} !!!"
|
|
170
257
|
f"(e.g; if time_frame is 15m, iter_time must be 1.5, 3, 3, 15 etc)"
|
|
171
258
|
)
|
|
172
|
-
print()
|
|
173
259
|
try:
|
|
260
|
+
FRIDAY = 'friday'
|
|
174
261
|
check_mt5_connection()
|
|
175
262
|
day_end = all(trade.days_end() for trade in trades_instances.values())
|
|
263
|
+
if closing_pnl is not None:
|
|
264
|
+
closing = all(trade.positive_profit(id=trade.expert_id, th=closing_pnl)
|
|
265
|
+
for trade in trades_instances.values())
|
|
266
|
+
else: closing = True
|
|
267
|
+
logmsg = lambda period: logger.info(
|
|
268
|
+
f"End of the {period} !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
269
|
+
sleepmsg = lambda sleep_time: logger.info(
|
|
270
|
+
f"Sleeping for {sleep_time} minutes ...\n")
|
|
271
|
+
sessionmsg = f"STARTING NEW TRADING SESSION ...\n"
|
|
176
272
|
if period.lower() == 'day':
|
|
177
273
|
for symbol in symbols:
|
|
178
274
|
trade = trades_instances[symbol]
|
|
179
|
-
if trade.days_end():
|
|
275
|
+
if trade.days_end() and closing:
|
|
180
276
|
trade.close_positions(position_type='all', comment=comment)
|
|
181
|
-
|
|
182
|
-
f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
277
|
+
logmsg("Day")
|
|
183
278
|
trade.statistics(save=True)
|
|
184
279
|
if day_end:
|
|
185
|
-
if period_end_action == 'break':
|
|
280
|
+
if period_end_action == 'break' and closing:
|
|
186
281
|
break
|
|
187
|
-
elif period_end_action == 'sleep':
|
|
282
|
+
elif period_end_action == 'sleep' and today != FRIDAY or not closing:
|
|
188
283
|
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
189
|
-
|
|
284
|
+
sleepmsg(sleep_time)
|
|
190
285
|
time.sleep(60 * sleep_time)
|
|
191
|
-
logger.info(
|
|
286
|
+
logger.info(sessionmsg)
|
|
287
|
+
elif period_end_action == 'sleep' and today == FRIDAY:
|
|
288
|
+
sleep_time = trades_instances[symbols[-1]].sleep_time(weekend=True)
|
|
289
|
+
sleepmsg(sleep_time)
|
|
290
|
+
time.sleep(60 * sleep_time)
|
|
291
|
+
logger.info(sessionmsg)
|
|
192
292
|
|
|
193
293
|
elif period.lower() == 'week':
|
|
194
294
|
for symbol in symbols:
|
|
195
295
|
trade = trades_instances[symbol]
|
|
196
|
-
if trade.days_end() and today !=
|
|
197
|
-
|
|
198
|
-
f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
296
|
+
if trade.days_end() and today != FRIDAY:
|
|
297
|
+
logmsg("Day")
|
|
199
298
|
|
|
200
|
-
elif trade.days_end() and today ==
|
|
299
|
+
elif trade.days_end() and today == FRIDAY and closing:
|
|
201
300
|
trade.close_positions(position_type='all', comment=comment)
|
|
202
|
-
|
|
203
|
-
f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
301
|
+
logmsg("Week")
|
|
204
302
|
trade.statistics(save=True)
|
|
205
|
-
if day_end and today !=
|
|
303
|
+
if day_end and today != FRIDAY:
|
|
206
304
|
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
207
|
-
|
|
305
|
+
sleepmsg(sleep_time)
|
|
208
306
|
time.sleep(60 * sleep_time)
|
|
209
|
-
logger.info(
|
|
210
|
-
elif day_end and today ==
|
|
211
|
-
if period_end_action == 'break':
|
|
307
|
+
logger.info(sessionmsg)
|
|
308
|
+
elif day_end and today == FRIDAY:
|
|
309
|
+
if period_end_action == 'break' and closing:
|
|
212
310
|
break
|
|
213
|
-
elif period_end_action == 'sleep':
|
|
311
|
+
elif period_end_action == 'sleep' or not closing:
|
|
214
312
|
sleep_time = trades_instances[symbols[-1]].sleep_time(weekend=True)
|
|
215
|
-
|
|
313
|
+
sleepmsg(sleep_time)
|
|
216
314
|
time.sleep(60 * sleep_time)
|
|
217
|
-
logger.info(
|
|
315
|
+
logger.info(sessionmsg)
|
|
218
316
|
|
|
219
317
|
elif period.lower() == 'month':
|
|
220
318
|
for symbol in symbols:
|
|
221
319
|
trade = trades_instances[symbol]
|
|
222
|
-
if trade.days_end() and today !=
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
elif trade.days_end() and today == 'friday':
|
|
227
|
-
logger.info(
|
|
228
|
-
f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
320
|
+
if trade.days_end() and today != FRIDAY:
|
|
321
|
+
logmsg("Day")
|
|
322
|
+
elif trade.days_end() and today == FRIDAY:
|
|
323
|
+
logmsg("Week")
|
|
229
324
|
elif (
|
|
230
325
|
trade.days_end()
|
|
231
|
-
and today ==
|
|
326
|
+
and today == FRIDAY
|
|
232
327
|
and num_days/len(symbols) >= 20
|
|
233
|
-
):
|
|
328
|
+
) and closing:
|
|
234
329
|
trade.close_positions(position_type='all', comment=comment)
|
|
235
|
-
|
|
236
|
-
f"End of the Month !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
330
|
+
logmsg("Month")
|
|
237
331
|
trade.statistics(save=True)
|
|
238
|
-
if day_end and today !=
|
|
332
|
+
if day_end and today != FRIDAY:
|
|
239
333
|
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
240
|
-
|
|
334
|
+
sleepmsg(sleep_time)
|
|
241
335
|
time.sleep(60 * sleep_time)
|
|
242
|
-
logger.info(
|
|
336
|
+
logger.info(sessionmsg)
|
|
243
337
|
num_days += 1
|
|
244
|
-
elif day_end and today ==
|
|
338
|
+
elif day_end and today == FRIDAY:
|
|
245
339
|
sleep_time = trades_instances[symbols[-1]
|
|
246
340
|
].sleep_time(weekend=True)
|
|
247
|
-
|
|
341
|
+
sleepmsg(sleep_time)
|
|
248
342
|
time.sleep(60 * sleep_time)
|
|
249
|
-
logger.info(
|
|
343
|
+
logger.info(sessionmsg)
|
|
250
344
|
num_days += 1
|
|
251
345
|
elif (day_end
|
|
252
|
-
and today ==
|
|
346
|
+
and today == FRIDAY
|
|
253
347
|
and num_days/len(symbols) >= 20
|
|
254
348
|
):
|
|
255
349
|
break
|
|
@@ -261,33 +355,30 @@ def _mt5_execution(
|
|
|
261
355
|
def _tws_execution(*args, **kwargs):
|
|
262
356
|
raise NotImplementedError("TWS Execution is not yet implemented !!!")
|
|
263
357
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
'TWS': _tws_execution
|
|
267
|
-
}
|
|
268
|
-
class ExecutionEngine():
|
|
358
|
+
|
|
359
|
+
class MT5ExecutionEngine():
|
|
269
360
|
"""
|
|
270
|
-
The `
|
|
361
|
+
The `MT5ExecutionEngine` class serves as the central hub for executing your trading strategies within the `bbstrader` framework.
|
|
271
362
|
It orchestrates the entire trading process, ensuring seamless interaction between your strategies, market data, and your chosen
|
|
272
|
-
trading platform
|
|
363
|
+
trading platform.
|
|
273
364
|
|
|
274
365
|
Key Features
|
|
275
366
|
------------
|
|
276
367
|
|
|
277
|
-
- **Strategy Execution:** The `
|
|
368
|
+
- **Strategy Execution:** The `MT5ExecutionEngine` is responsible for running your strategy, retrieving signals, and executing trades based on those signals.
|
|
278
369
|
- **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.
|
|
279
370
|
- **Trade Period Control:** Define whether your strategy runs for a day, a week, or a month, allowing for flexible trading durations.
|
|
280
371
|
- **Money Management:** The engine supports optional money management features, allowing you to control risk and optimize your trading performance.
|
|
281
372
|
- **Trading Day Configuration:** You can customize the days of the week your strategy will execute, providing granular control over your trading schedule.
|
|
282
|
-
- **Platform Integration:** The `
|
|
373
|
+
- **Platform Integration:** The `MT5ExecutionEngine` is currently designed to work with MT5.
|
|
283
374
|
|
|
284
375
|
Examples
|
|
285
376
|
--------
|
|
286
377
|
|
|
287
378
|
>>> from bbstrader.metatrader import create_trade_instance
|
|
288
|
-
>>> from bbstrader.trading.execution import
|
|
379
|
+
>>> from bbstrader.trading.execution import MT5ExecutionEngine
|
|
289
380
|
>>> from bbstrader.trading.strategies import StockIndexCFDTrading
|
|
290
|
-
>>> from bbstrader.
|
|
381
|
+
>>> from bbstrader.config import config_logger
|
|
291
382
|
>>>
|
|
292
383
|
>>> if __name__ == '__main__':
|
|
293
384
|
>>> logger = config_logger(index_trade.log, console_log=True)
|
|
@@ -325,7 +416,7 @@ class ExecutionEngine():
|
|
|
325
416
|
... logger=logger,
|
|
326
417
|
... )
|
|
327
418
|
>>>
|
|
328
|
-
>>> engine =
|
|
419
|
+
>>> engine = MT5ExecutionEngine(
|
|
329
420
|
... symbol_list,
|
|
330
421
|
... trades_instances,
|
|
331
422
|
... StockIndexCFDTrading,
|
|
@@ -336,7 +427,7 @@ class ExecutionEngine():
|
|
|
336
427
|
... comment='bbs_SISTBO_@2.0',
|
|
337
428
|
... **strategy_kwargs
|
|
338
429
|
... )
|
|
339
|
-
>>> engine.run(
|
|
430
|
+
>>> engine.run()
|
|
340
431
|
"""
|
|
341
432
|
|
|
342
433
|
def __init__(self,
|
|
@@ -344,15 +435,18 @@ class ExecutionEngine():
|
|
|
344
435
|
trades_instances: Dict[str, Trade],
|
|
345
436
|
strategy_cls: Strategy,
|
|
346
437
|
/,
|
|
347
|
-
mm:
|
|
348
|
-
trail:
|
|
438
|
+
mm: bool = True,
|
|
439
|
+
trail: bool = True,
|
|
349
440
|
stop_trail: Optional[int] = None,
|
|
350
441
|
trail_after_points: Optional[int] = None,
|
|
351
442
|
be_plus_points: Optional[int] = None,
|
|
352
|
-
|
|
353
|
-
|
|
443
|
+
show_positions_orders: bool = False,
|
|
444
|
+
time_frame: str = '15m',
|
|
445
|
+
iter_time: int | float = 5,
|
|
446
|
+
use_trade_time: bool = True,
|
|
354
447
|
period: Literal['day', 'week', 'month'] = 'week',
|
|
355
448
|
period_end_action: Literal['break', 'sleep'] = 'break',
|
|
449
|
+
closing_pnl: Optional[float] = None,
|
|
356
450
|
trading_days: Optional[List[str]] = TradingDays,
|
|
357
451
|
comment: Optional[str] = None,
|
|
358
452
|
**kwargs
|
|
@@ -363,20 +457,27 @@ class ExecutionEngine():
|
|
|
363
457
|
trades_instances : Dictionary of Trade instances
|
|
364
458
|
strategy_cls : Strategy class to use for trading
|
|
365
459
|
mm : Enable Money Management. Defaults to False.
|
|
460
|
+
show_positions_orders : Print open positions and orders. Defaults to False.
|
|
366
461
|
time_frame : Time frame to trade. Defaults to '15m'.
|
|
367
462
|
iter_time : Interval to check for signals and `mm`. Defaults to 5.
|
|
463
|
+
use_trade_time : Open trades after the time is completed. Defaults to True.
|
|
368
464
|
period : Period to trade. Defaults to 'week'.
|
|
369
465
|
period_end_action : Action to take at the end of the period. Defaults to 'break',
|
|
370
466
|
this only applies when period is 'day', 'week'.
|
|
467
|
+
closing_pnl : Minimum profit in percentage of target profit to close positions. Defaults to -0.001.
|
|
371
468
|
trading_days : Trading days in a week. Defaults to monday to friday.
|
|
372
469
|
comment: Comment for trades. Defaults to None.
|
|
373
470
|
**kwargs: Additional keyword arguments
|
|
374
471
|
- strategy_name (Optional[str]): Strategy name. Defaults to None.
|
|
375
472
|
- max_trades (Dict[str, int]): Maximum trades per symbol. Defaults to None.
|
|
473
|
+
- notify (bool): Enable notifications. Defaults to False.
|
|
474
|
+
- telegram (bool): Enable telegram notifications. Defaults to False.
|
|
475
|
+
- bot_token (str): Telegram bot token. Defaults to None.
|
|
476
|
+
- chat_id (Union[int, str, List] ): Telegram chat id. Defaults to None.
|
|
376
477
|
|
|
377
478
|
Note:
|
|
378
479
|
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
|
|
480
|
+
2. All Strategies must inherit from `bbstrader.btengine.strategy.Strategy` or `bbstrader.btengine.strategy.MT5Strategy` class
|
|
380
481
|
and have a `calculate_signals` method that returns a dictionary of signals for each symbol in symbol_list.
|
|
381
482
|
|
|
382
483
|
3. All strategies must have the following arguments in their `__init__` method:
|
|
@@ -385,7 +486,7 @@ class ExecutionEngine():
|
|
|
385
486
|
- symbol_list (List[str]): List of symbols to trade can be none for backtesting
|
|
386
487
|
- mode (str): Mode of the strategy. Must be either 'live' or 'backtest'
|
|
387
488
|
- **kwargs: Additional keyword arguments
|
|
388
|
-
The keyword arguments are all the additional arguments passed to the `
|
|
489
|
+
The keyword arguments are all the additional arguments passed to the `MT5ExecutionEngine` class,
|
|
389
490
|
the `Strategy` class, the `DataHandler` class, the `Portfolio` class and the `ExecutionHandler` class.
|
|
390
491
|
- The `bars` and `events` arguments are used for backtesting only.
|
|
391
492
|
|
|
@@ -400,34 +501,39 @@ class ExecutionEngine():
|
|
|
400
501
|
self.stop_trail = stop_trail
|
|
401
502
|
self.trail_after_points = trail_after_points
|
|
402
503
|
self.be_plus_points = be_plus_points
|
|
504
|
+
self.show_positions_orders = show_positions_orders
|
|
403
505
|
self.time_frame = time_frame
|
|
404
506
|
self.iter_time = iter_time
|
|
507
|
+
self.use_trade_time = use_trade_time
|
|
405
508
|
self.period = period
|
|
406
509
|
self.period_end_action = period_end_action
|
|
510
|
+
self.closing_pnl = closing_pnl
|
|
407
511
|
self.trading_days = trading_days
|
|
408
512
|
self.comment = comment
|
|
409
513
|
self.kwargs = kwargs
|
|
410
514
|
|
|
411
|
-
def run(self
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
515
|
+
def run(self):
|
|
516
|
+
check_mt5_connection()
|
|
517
|
+
_mt5_execution(
|
|
518
|
+
self.symbol_list,
|
|
519
|
+
self.trades_instances,
|
|
520
|
+
self.strategy_cls,
|
|
521
|
+
mm=self.mm,
|
|
522
|
+
trail=self.trail,
|
|
523
|
+
stop_trail=self.stop_trail,
|
|
524
|
+
trail_after_points=self.trail_after_points,
|
|
525
|
+
be_plus_points=self.be_plus_points,
|
|
526
|
+
show_positions_orders=self.show_positions_orders,
|
|
527
|
+
time_frame=self.time_frame,
|
|
528
|
+
iter_time=self.iter_time,
|
|
529
|
+
use_trade_time=self.use_trade_time,
|
|
530
|
+
period=self.period,
|
|
531
|
+
period_end_action=self.period_end_action,
|
|
532
|
+
closing_pnl=self.closing_pnl,
|
|
533
|
+
trading_days=self.trading_days,
|
|
534
|
+
comment=self.comment,
|
|
535
|
+
**self.kwargs
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
class TWSExecutionEngine():...
|