bbstrader 0.1.8__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 +357 -55
- bbstrader/metatrader/rates.py +234 -31
- bbstrader/metatrader/risk.py +35 -24
- bbstrader/metatrader/trade.py +361 -173
- 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 +329 -215
- bbstrader/trading/scripts.py +57 -0
- bbstrader/trading/strategies.py +49 -71
- bbstrader/tseries.py +274 -39
- {bbstrader-0.1.8.dist-info → bbstrader-0.1.91.dist-info}/METADATA +11 -3
- bbstrader-0.1.91.dist-info/RECORD +31 -0
- {bbstrader-0.1.8.dist-info → bbstrader-0.1.91.dist-info}/WHEEL +1 -1
- bbstrader-0.1.8.dist-info/RECORD +0 -26
- {bbstrader-0.1.8.dist-info → bbstrader-0.1.91.dist-info}/LICENSE +0 -0
- {bbstrader-0.1.8.dist-info → bbstrader-0.1.91.dist-info}/top_level.txt +0 -0
bbstrader/trading/execution.py
CHANGED
|
@@ -1,24 +1,39 @@
|
|
|
1
1
|
import time
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
from bbstrader.metatrader.trade import Trade
|
|
4
|
-
from bbstrader.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
from bbstrader.
|
|
4
|
+
from bbstrader.btengine.strategy import(
|
|
5
|
+
Strategy,
|
|
6
|
+
MT5Strategy
|
|
7
|
+
)
|
|
8
|
+
from bbstrader.trading.scripts import send_message
|
|
9
|
+
from bbstrader.metatrader.account import check_mt5_connection
|
|
10
|
+
from typing import (
|
|
11
|
+
Optional,
|
|
12
|
+
Literal,
|
|
13
|
+
Tuple,
|
|
14
|
+
List,
|
|
15
|
+
Dict
|
|
16
|
+
)
|
|
9
17
|
|
|
18
|
+
__all__ = [
|
|
19
|
+
'MT5ExecutionEngine',
|
|
20
|
+
'TWSExecutionEngine'
|
|
21
|
+
]
|
|
10
22
|
|
|
11
23
|
_TF_MAPPING = {
|
|
12
|
-
'1m':
|
|
13
|
-
'3m':
|
|
14
|
-
'5m':
|
|
15
|
-
'10m':
|
|
16
|
-
'15m':
|
|
17
|
-
'30m':
|
|
18
|
-
'1h':
|
|
19
|
-
'2h':
|
|
20
|
-
'4h':
|
|
21
|
-
'
|
|
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
|
|
22
37
|
}
|
|
23
38
|
|
|
24
39
|
TradingDays = [
|
|
@@ -29,133 +44,205 @@ TradingDays = [
|
|
|
29
44
|
'friday'
|
|
30
45
|
]
|
|
31
46
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
+
|
|
39
78
|
|
|
40
79
|
def _mt5_execution(
|
|
41
80
|
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,
|
|
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,
|
|
44
83
|
comment, **kwargs):
|
|
45
|
-
symbols
|
|
84
|
+
symbols = symbol_list.copy()
|
|
46
85
|
STRATEGY = kwargs.get('strategy_name')
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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}
|
|
50
99
|
if comment is None:
|
|
51
100
|
trade = trades_instances[symbols[0]]
|
|
52
101
|
comment = f"{trade.expert_name}@{trade.version}"
|
|
53
102
|
|
|
54
|
-
def check(buys
|
|
103
|
+
def check(buys, sells, symbol):
|
|
55
104
|
if not mm:
|
|
56
105
|
return
|
|
57
|
-
if buys is not None:
|
|
106
|
+
if buys is not None or sells is not None:
|
|
58
107
|
logger.info(
|
|
59
108
|
f"Checking for Break even, SYMBOL={symbol}...STRATEGY={STRATEGY}")
|
|
60
109
|
trades_instances[symbol].break_even(
|
|
61
110
|
mm=mm, trail=trail, stop_trail=stop_trail,
|
|
62
111
|
trail_after_points=trail_after_points, be_plus_points=be_plus_points)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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)
|
|
69
|
-
num_days = 0
|
|
70
|
-
time_intervals = 0
|
|
71
|
-
trade_time = _TF_MAPPING[time_frame]
|
|
72
|
-
|
|
73
|
-
long_market = {symbol: False for symbol in symbols}
|
|
74
|
-
short_market = {symbol: False for symbol in symbols}
|
|
112
|
+
num_days = 0
|
|
113
|
+
time_intervals = 0
|
|
114
|
+
trade_time = _TF_MAPPING[time_frame]
|
|
75
115
|
|
|
116
|
+
long_market = {symbol: False for symbol in symbols}
|
|
117
|
+
short_market = {symbol: False for symbol in symbols}
|
|
118
|
+
try:
|
|
119
|
+
check_mt5_connection()
|
|
120
|
+
strategy: MT5Strategy = strategy_cls(symbol_list=symbols, mode='live', **kwargs)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.error(f"Error initializing strategy, {e}, STRATEGY={STRATEGY}")
|
|
123
|
+
return
|
|
76
124
|
logger.info(
|
|
77
125
|
f'Running {STRATEGY} Strategy on {symbols} in {time_frame} Interval ...')
|
|
78
|
-
|
|
79
|
-
symbol_list=symbols, mode='live', **kwargs)
|
|
80
|
-
|
|
126
|
+
|
|
81
127
|
while True:
|
|
82
128
|
try:
|
|
83
|
-
|
|
129
|
+
check_mt5_connection()
|
|
84
130
|
current_date = datetime.now()
|
|
85
131
|
today = current_date.strftime("%A").lower()
|
|
86
132
|
time.sleep(0.5)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
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
|
|
90
150
|
}
|
|
91
|
-
|
|
92
|
-
symbol:
|
|
93
|
-
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
|
|
94
154
|
}
|
|
95
|
-
|
|
96
|
-
if buys[symbol] is not None:
|
|
97
|
-
logger.info(
|
|
98
|
-
f"Current buy positions SYMBOL={symbol}: {buys[symbol]}, STRATEGY={STRATEGY}")
|
|
99
|
-
if sells[symbol] is not None:
|
|
100
|
-
logger.info(
|
|
101
|
-
f"Current sell positions SYMBOL={symbol}: {sells[symbol]}, STRATEGY={STRATEGY}")
|
|
102
|
-
long_market = {symbol: buys[symbol] is not None and len(
|
|
103
|
-
buys[symbol]) >= max_trades[symbol] for symbol in symbols}
|
|
104
|
-
short_market = {symbol: sells[symbol] is not None and len(
|
|
105
|
-
sells[symbol]) >= max_trades[symbol] for symbol in symbols}
|
|
155
|
+
|
|
106
156
|
except Exception as e:
|
|
107
157
|
logger.error(f"{e}, STRATEGY={STRATEGY}")
|
|
158
|
+
continue
|
|
108
159
|
time.sleep(0.5)
|
|
160
|
+
try:
|
|
161
|
+
check_mt5_connection()
|
|
162
|
+
signals = strategy.calculate_signals()
|
|
163
|
+
except Exception as e:
|
|
164
|
+
logger.error(f"Calculating signal, {e}, STRATEGY={STRATEGY}")
|
|
165
|
+
continue
|
|
109
166
|
for symbol in symbols:
|
|
110
167
|
try:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
168
|
+
check_mt5_connection()
|
|
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}"
|
|
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
|
|
115
180
|
if trade.trading_time() and today in trading_days:
|
|
116
181
|
if signal is not None:
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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)
|
|
128
208
|
else:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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)
|
|
133
216
|
check(buys[symbol], sells[symbol], symbol)
|
|
134
217
|
|
|
135
|
-
elif signal
|
|
136
|
-
if
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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)
|
|
141
229
|
else:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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)
|
|
146
237
|
check(buys[symbol], sells[symbol], symbol)
|
|
147
|
-
else:
|
|
148
|
-
logger.info(
|
|
149
|
-
f"There is no signal !! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
150
|
-
check(buys[symbol], sells[symbol], symbol)
|
|
151
238
|
else:
|
|
152
239
|
logger.info(
|
|
153
|
-
f"
|
|
240
|
+
f"Not trading Time !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
154
241
|
check(buys[symbol], sells[symbol], symbol)
|
|
155
242
|
|
|
156
243
|
except Exception as e:
|
|
157
244
|
logger.error(f"{e}, SYMBOL={symbol}, STRATEGY={STRATEGY}")
|
|
158
|
-
|
|
245
|
+
continue
|
|
159
246
|
time.sleep((60 * iter_time) - 1.0)
|
|
160
247
|
if iter_time == 1:
|
|
161
248
|
time_intervals += 1
|
|
@@ -167,118 +254,129 @@ def _mt5_execution(
|
|
|
167
254
|
f"(e.g; if time_frame is 15m, iter_time must be 1.5, 3, 3, 15 etc)"
|
|
168
255
|
)
|
|
169
256
|
print()
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
257
|
+
try:
|
|
258
|
+
FRIDAY = 'friday'
|
|
259
|
+
check_mt5_connection()
|
|
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"
|
|
270
|
+
if period.lower() == 'day':
|
|
271
|
+
for symbol in symbols:
|
|
272
|
+
trade = trades_instances[symbol]
|
|
273
|
+
if trade.days_end() and closing:
|
|
274
|
+
trade.close_positions(position_type='all', comment=comment)
|
|
275
|
+
logmsg("Day")
|
|
276
|
+
trade.statistics(save=True)
|
|
277
|
+
if day_end:
|
|
278
|
+
if period_end_action == 'break' and closing:
|
|
279
|
+
break
|
|
280
|
+
elif period_end_action == 'sleep' and today != FRIDAY or not closing:
|
|
281
|
+
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
282
|
+
sleepmsg(sleep_time)
|
|
283
|
+
time.sleep(60 * sleep_time)
|
|
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)
|
|
186
290
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
291
|
+
elif period.lower() == 'week':
|
|
292
|
+
for symbol in symbols:
|
|
293
|
+
trade = trades_instances[symbol]
|
|
294
|
+
if trade.days_end() and today != FRIDAY:
|
|
295
|
+
logmsg("Day")
|
|
193
296
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
logger.info(f"Sleeping for {sleep_time} minutes ...")
|
|
202
|
-
time.sleep(60 * sleep_time)
|
|
203
|
-
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
204
|
-
elif trades_instances[symbols[-1]].days_end() and today == 'friday':
|
|
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 ...")
|
|
297
|
+
elif trade.days_end() and today == FRIDAY and closing:
|
|
298
|
+
trade.close_positions(position_type='all', comment=comment)
|
|
299
|
+
logmsg("Week")
|
|
300
|
+
trade.statistics(save=True)
|
|
301
|
+
if day_end and today != FRIDAY:
|
|
302
|
+
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
303
|
+
sleepmsg(sleep_time)
|
|
210
304
|
time.sleep(60 * sleep_time)
|
|
211
|
-
logger.info(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
305
|
+
logger.info(sessionmsg)
|
|
306
|
+
elif day_end and today == FRIDAY:
|
|
307
|
+
if period_end_action == 'break' and closing:
|
|
308
|
+
break
|
|
309
|
+
elif period_end_action == 'sleep' or not closing:
|
|
310
|
+
sleep_time = trades_instances[symbols[-1]].sleep_time(weekend=True)
|
|
311
|
+
sleepmsg(sleep_time)
|
|
312
|
+
time.sleep(60 * sleep_time)
|
|
313
|
+
logger.info(sessionmsg)
|
|
219
314
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
and today ==
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
315
|
+
elif period.lower() == 'month':
|
|
316
|
+
for symbol in symbols:
|
|
317
|
+
trade = trades_instances[symbol]
|
|
318
|
+
if trade.days_end() and today != FRIDAY:
|
|
319
|
+
logmsg("Day")
|
|
320
|
+
elif trade.days_end() and today == FRIDAY:
|
|
321
|
+
logmsg("Week")
|
|
322
|
+
elif (
|
|
323
|
+
trade.days_end()
|
|
324
|
+
and today == FRIDAY
|
|
325
|
+
and num_days/len(symbols) >= 20
|
|
326
|
+
) and closing:
|
|
327
|
+
trade.close_positions(position_type='all', comment=comment)
|
|
328
|
+
logmsg("Month")
|
|
329
|
+
trade.statistics(save=True)
|
|
330
|
+
if day_end and today != FRIDAY:
|
|
331
|
+
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
332
|
+
sleepmsg(sleep_time)
|
|
333
|
+
time.sleep(60 * sleep_time)
|
|
334
|
+
logger.info(sessionmsg)
|
|
335
|
+
num_days += 1
|
|
336
|
+
elif day_end and today == FRIDAY:
|
|
337
|
+
sleep_time = trades_instances[symbols[-1]
|
|
338
|
+
].sleep_time(weekend=True)
|
|
339
|
+
sleepmsg(sleep_time)
|
|
340
|
+
time.sleep(60 * sleep_time)
|
|
341
|
+
logger.info(sessionmsg)
|
|
342
|
+
num_days += 1
|
|
343
|
+
elif (day_end
|
|
344
|
+
and today == FRIDAY
|
|
345
|
+
and num_days/len(symbols) >= 20
|
|
346
|
+
):
|
|
347
|
+
break
|
|
348
|
+
except Exception as e:
|
|
349
|
+
logger.error(f"Handling period end actions, {e}, STRATEGY={STRATEGY}")
|
|
350
|
+
continue
|
|
250
351
|
|
|
251
352
|
|
|
252
353
|
def _tws_execution(*args, **kwargs):
|
|
253
354
|
raise NotImplementedError("TWS Execution is not yet implemented !!!")
|
|
254
355
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
'TWS': _tws_execution
|
|
258
|
-
}
|
|
259
|
-
class ExecutionEngine():
|
|
356
|
+
|
|
357
|
+
class MT5ExecutionEngine():
|
|
260
358
|
"""
|
|
261
|
-
The `
|
|
359
|
+
The `MT5ExecutionEngine` class serves as the central hub for executing your trading strategies within the `bbstrader` framework.
|
|
262
360
|
It orchestrates the entire trading process, ensuring seamless interaction between your strategies, market data, and your chosen
|
|
263
|
-
trading platform
|
|
361
|
+
trading platform.
|
|
264
362
|
|
|
265
363
|
Key Features
|
|
266
364
|
------------
|
|
267
365
|
|
|
268
|
-
- **Strategy Execution:** The `
|
|
366
|
+
- **Strategy Execution:** The `MT5ExecutionEngine` is responsible for running your strategy, retrieving signals, and executing trades based on those signals.
|
|
269
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.
|
|
270
368
|
- **Trade Period Control:** Define whether your strategy runs for a day, a week, or a month, allowing for flexible trading durations.
|
|
271
369
|
- **Money Management:** The engine supports optional money management features, allowing you to control risk and optimize your trading performance.
|
|
272
370
|
- **Trading Day Configuration:** You can customize the days of the week your strategy will execute, providing granular control over your trading schedule.
|
|
273
|
-
- **Platform Integration:** The `
|
|
371
|
+
- **Platform Integration:** The `MT5ExecutionEngine` is currently designed to work with MT5.
|
|
274
372
|
|
|
275
373
|
Examples
|
|
276
374
|
--------
|
|
277
375
|
|
|
278
376
|
>>> from bbstrader.metatrader import create_trade_instance
|
|
279
|
-
>>> from bbstrader.trading.execution import
|
|
377
|
+
>>> from bbstrader.trading.execution import MT5ExecutionEngine
|
|
280
378
|
>>> from bbstrader.trading.strategies import StockIndexCFDTrading
|
|
281
|
-
>>> from bbstrader.
|
|
379
|
+
>>> from bbstrader.config import config_logger
|
|
282
380
|
>>>
|
|
283
381
|
>>> if __name__ == '__main__':
|
|
284
382
|
>>> logger = config_logger(index_trade.log, console_log=True)
|
|
@@ -316,7 +414,7 @@ class ExecutionEngine():
|
|
|
316
414
|
... logger=logger,
|
|
317
415
|
... )
|
|
318
416
|
>>>
|
|
319
|
-
>>> engine =
|
|
417
|
+
>>> engine = MT5ExecutionEngine(
|
|
320
418
|
... symbol_list,
|
|
321
419
|
... trades_instances,
|
|
322
420
|
... StockIndexCFDTrading,
|
|
@@ -327,7 +425,7 @@ class ExecutionEngine():
|
|
|
327
425
|
... comment='bbs_SISTBO_@2.0',
|
|
328
426
|
... **strategy_kwargs
|
|
329
427
|
... )
|
|
330
|
-
>>> engine.run(
|
|
428
|
+
>>> engine.run()
|
|
331
429
|
"""
|
|
332
430
|
|
|
333
431
|
def __init__(self,
|
|
@@ -335,15 +433,18 @@ class ExecutionEngine():
|
|
|
335
433
|
trades_instances: Dict[str, Trade],
|
|
336
434
|
strategy_cls: Strategy,
|
|
337
435
|
/,
|
|
338
|
-
mm:
|
|
339
|
-
trail:
|
|
436
|
+
mm: bool = True,
|
|
437
|
+
trail: bool = True,
|
|
340
438
|
stop_trail: Optional[int] = None,
|
|
341
439
|
trail_after_points: Optional[int] = None,
|
|
342
440
|
be_plus_points: Optional[int] = None,
|
|
343
|
-
|
|
344
|
-
|
|
441
|
+
show_positions_orders: bool = False,
|
|
442
|
+
time_frame: str = '15m',
|
|
443
|
+
iter_time: int | float = 5,
|
|
444
|
+
use_trade_time: bool = True,
|
|
345
445
|
period: Literal['day', 'week', 'month'] = 'week',
|
|
346
446
|
period_end_action: Literal['break', 'sleep'] = 'break',
|
|
447
|
+
closing_pnl: Optional[float] = None,
|
|
347
448
|
trading_days: Optional[List[str]] = TradingDays,
|
|
348
449
|
comment: Optional[str] = None,
|
|
349
450
|
**kwargs
|
|
@@ -354,21 +455,27 @@ class ExecutionEngine():
|
|
|
354
455
|
trades_instances : Dictionary of Trade instances
|
|
355
456
|
strategy_cls : Strategy class to use for trading
|
|
356
457
|
mm : Enable Money Management. Defaults to False.
|
|
458
|
+
show_positions_orders : Print open positions and orders. Defaults to False.
|
|
357
459
|
time_frame : Time frame to trade. Defaults to '15m'.
|
|
358
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.
|
|
359
462
|
period : Period to trade. Defaults to 'week'.
|
|
360
463
|
period_end_action : Action to take at the end of the period. Defaults to 'break',
|
|
361
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.
|
|
362
466
|
trading_days : Trading days in a week. Defaults to monday to friday.
|
|
363
467
|
comment: Comment for trades. Defaults to None.
|
|
364
468
|
**kwargs: Additional keyword arguments
|
|
365
469
|
- strategy_name (Optional[str]): Strategy name. Defaults to None.
|
|
366
470
|
- max_trades (Dict[str, int]): Maximum trades per symbol. Defaults to None.
|
|
367
|
-
-
|
|
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.
|
|
368
475
|
|
|
369
476
|
Note:
|
|
370
477
|
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
|
|
478
|
+
2. All Strategies must inherit from `bbstrader.btengine.strategy.Strategy` or `bbstrader.btengine.strategy.MT5Strategy` class
|
|
372
479
|
and have a `calculate_signals` method that returns a dictionary of signals for each symbol in symbol_list.
|
|
373
480
|
|
|
374
481
|
3. All strategies must have the following arguments in their `__init__` method:
|
|
@@ -377,7 +484,7 @@ class ExecutionEngine():
|
|
|
377
484
|
- symbol_list (List[str]): List of symbols to trade can be none for backtesting
|
|
378
485
|
- mode (str): Mode of the strategy. Must be either 'live' or 'backtest'
|
|
379
486
|
- **kwargs: Additional keyword arguments
|
|
380
|
-
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,
|
|
381
488
|
the `Strategy` class, the `DataHandler` class, the `Portfolio` class and the `ExecutionHandler` class.
|
|
382
489
|
- The `bars` and `events` arguments are used for backtesting only.
|
|
383
490
|
|
|
@@ -392,32 +499,39 @@ class ExecutionEngine():
|
|
|
392
499
|
self.stop_trail = stop_trail
|
|
393
500
|
self.trail_after_points = trail_after_points
|
|
394
501
|
self.be_plus_points = be_plus_points
|
|
502
|
+
self.show_positions_orders = show_positions_orders
|
|
395
503
|
self.time_frame = time_frame
|
|
396
504
|
self.iter_time = iter_time
|
|
505
|
+
self.use_trade_time = use_trade_time
|
|
397
506
|
self.period = period
|
|
398
507
|
self.period_end_action = period_end_action
|
|
508
|
+
self.closing_pnl = closing_pnl
|
|
399
509
|
self.trading_days = trading_days
|
|
400
510
|
self.comment = comment
|
|
401
511
|
self.kwargs = kwargs
|
|
402
512
|
|
|
403
|
-
def run(self
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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():...
|