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