bbstrader 0.2.0__py3-none-any.whl → 0.2.1__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 +7 -7
- bbstrader/btengine/__init__.py +7 -7
- bbstrader/btengine/backtest.py +30 -26
- bbstrader/btengine/data.py +92 -81
- bbstrader/btengine/event.py +2 -1
- bbstrader/btengine/execution.py +18 -16
- bbstrader/btengine/performance.py +11 -7
- bbstrader/btengine/portfolio.py +35 -36
- bbstrader/btengine/strategy.py +113 -92
- bbstrader/config.py +12 -10
- bbstrader/core/data.py +4 -5
- bbstrader/core/utils.py +57 -0
- bbstrader/ibkr/utils.py +0 -0
- bbstrader/metatrader/__init__.py +5 -5
- bbstrader/metatrader/account.py +117 -121
- bbstrader/metatrader/rates.py +81 -78
- bbstrader/metatrader/risk.py +23 -37
- bbstrader/metatrader/trade.py +154 -138
- bbstrader/metatrader/utils.py +3 -3
- bbstrader/models/__init__.py +5 -5
- bbstrader/models/factors.py +17 -12
- bbstrader/models/ml.py +371 -305
- bbstrader/models/optimization.py +14 -12
- bbstrader/models/portfolio.py +44 -35
- bbstrader/models/risk.py +15 -9
- bbstrader/trading/__init__.py +2 -2
- bbstrader/trading/execution.py +245 -179
- bbstrader/trading/scripts.py +8 -4
- bbstrader/trading/strategies.py +78 -65
- bbstrader/tseries.py +124 -98
- {bbstrader-0.2.0.dist-info → bbstrader-0.2.1.dist-info}/LICENSE +1 -1
- {bbstrader-0.2.0.dist-info → bbstrader-0.2.1.dist-info}/METADATA +2 -1
- bbstrader-0.2.1.dist-info/RECORD +37 -0
- bbstrader-0.2.0.dist-info/RECORD +0 -36
- {bbstrader-0.2.0.dist-info → bbstrader-0.2.1.dist-info}/WHEEL +0 -0
- {bbstrader-0.2.0.dist-info → bbstrader-0.2.1.dist-info}/top_level.txt +0 -0
bbstrader/trading/execution.py
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
import time
|
|
2
|
+
import traceback
|
|
3
|
+
import MetaTrader5 as MT5
|
|
2
4
|
from datetime import datetime
|
|
5
|
+
from logging import Logger
|
|
6
|
+
from typing import Dict, List, Literal, Optional
|
|
7
|
+
|
|
8
|
+
from bbstrader.btengine.strategy import MT5Strategy, Strategy
|
|
9
|
+
from bbstrader.metatrader.account import check_mt5_connection
|
|
3
10
|
from bbstrader.metatrader.trade import Trade
|
|
4
|
-
from bbstrader.btengine.strategy import(
|
|
5
|
-
Strategy,
|
|
6
|
-
MT5Strategy
|
|
7
|
-
)
|
|
8
11
|
from bbstrader.trading.scripts import send_message
|
|
9
|
-
from bbstrader.
|
|
10
|
-
from typing import (
|
|
11
|
-
Optional,
|
|
12
|
-
Literal,
|
|
13
|
-
Tuple,
|
|
14
|
-
List,
|
|
15
|
-
Dict
|
|
16
|
-
)
|
|
12
|
+
from bbstrader.core.utils import TradeAction
|
|
17
13
|
|
|
18
14
|
__all__ = [
|
|
19
15
|
'MT5ExecutionEngine',
|
|
@@ -37,23 +33,23 @@ _TF_MAPPING = {
|
|
|
37
33
|
}
|
|
38
34
|
|
|
39
35
|
TradingDays = [
|
|
40
|
-
'monday',
|
|
41
|
-
'tuesday',
|
|
42
|
-
'wednesday',
|
|
43
|
-
'thursday',
|
|
36
|
+
'monday',
|
|
37
|
+
'tuesday',
|
|
38
|
+
'wednesday',
|
|
39
|
+
'thursday',
|
|
44
40
|
'friday'
|
|
45
41
|
]
|
|
46
42
|
|
|
47
|
-
BUYS
|
|
48
|
-
SELLS
|
|
43
|
+
BUYS = ['BMKT', 'BLMT', 'BSTP', 'BSTPLMT']
|
|
44
|
+
SELLS = ['SMKT', 'SLMT', 'SSTP', 'SSTPLMT']
|
|
49
45
|
|
|
50
|
-
ORDERS_TYPES
|
|
51
|
-
|
|
46
|
+
ORDERS_TYPES = ["orders", "buy_stops", "sell_stops", "buy_limits",
|
|
47
|
+
"sell_limits", "buy_stop_limits", "sell_stop_limits"]
|
|
52
48
|
POSITIONS_TYPES = ["positions", "buys", "sells", "profitables", "losings"]
|
|
53
49
|
|
|
54
|
-
ACTIONS
|
|
55
|
-
STOPS
|
|
56
|
-
LIMITS
|
|
50
|
+
ACTIONS = ["buys", "sells"]
|
|
51
|
+
STOPS = ["buy_stops", "sell_stops"]
|
|
52
|
+
LIMITS = ["buy_limits", "sell_limits"]
|
|
57
53
|
STOP_LIMITS = ["buy_stop_limits", "sell_stop_limits"]
|
|
58
54
|
|
|
59
55
|
EXIT_SIGNAL_ACTIONS = {
|
|
@@ -68,33 +64,60 @@ EXIT_SIGNAL_ACTIONS = {
|
|
|
68
64
|
"EXIT_SHORT_LIMIT": {"sell_limits": "sell_limits"},
|
|
69
65
|
"EXIT_STOP_LIMIT": {sl: sl for sl in STOP_LIMITS},
|
|
70
66
|
"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": "
|
|
73
|
-
"EXIT_LOSINGS": {"losings": "
|
|
67
|
+
"EXIT_SHORT_STOP_LIMIT": {STOP_LIMITS[1]: STOP_LIMITS[1]},
|
|
68
|
+
"EXIT_PROFITABLES": {"profitables": "profitable"},
|
|
69
|
+
"EXIT_LOSINGS": {"losings": "losing"},
|
|
74
70
|
"EXIT_ALL_POSITIONS": {"positions": "all"},
|
|
75
71
|
"EXIT_ALL_ORDERS": {"orders": "all"}
|
|
76
72
|
}
|
|
77
73
|
|
|
74
|
+
COMMON_RETCODES = [
|
|
75
|
+
MT5.TRADE_RETCODE_MARKET_CLOSED,
|
|
76
|
+
MT5.TRADE_RETCODE_CLOSE_ONLY
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
NON_EXEC_RETCODES = {
|
|
80
|
+
'BMKT': [MT5.TRADE_RETCODE_SHORT_ONLY] + COMMON_RETCODES,
|
|
81
|
+
'SMKT': [MT5.TRADE_RETCODE_LONG_ONLY] + COMMON_RETCODES
|
|
82
|
+
}
|
|
83
|
+
|
|
78
84
|
|
|
79
85
|
def _mt5_execution(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
symbol_list,
|
|
87
|
+
trades_instances,
|
|
88
|
+
strategy_cls, /,
|
|
89
|
+
mm,
|
|
90
|
+
optimizer,
|
|
91
|
+
trail,
|
|
92
|
+
stop_trail,
|
|
93
|
+
trail_after_points,
|
|
94
|
+
be_plus_points,
|
|
95
|
+
show_positions_orders,
|
|
96
|
+
iter_time,
|
|
97
|
+
use_trade_time,
|
|
98
|
+
period,
|
|
99
|
+
period_end_action,
|
|
100
|
+
closing_pnl,
|
|
101
|
+
trading_days,
|
|
102
|
+
comment,
|
|
103
|
+
**kwargs
|
|
104
|
+
):
|
|
105
|
+
symbols = symbol_list.copy()
|
|
85
106
|
time_frame = kwargs.get('time_frame', '15m')
|
|
86
107
|
STRATEGY = kwargs.get('strategy_name')
|
|
87
|
-
mtrades
|
|
88
|
-
|
|
89
|
-
notify = kwargs.get('notify', False)
|
|
108
|
+
mtrades = kwargs.get('max_trades')
|
|
109
|
+
notify = kwargs.get('notify', False)
|
|
90
110
|
if notify:
|
|
91
|
-
telegram
|
|
111
|
+
telegram = kwargs.get('telegram', False)
|
|
92
112
|
bot_token = kwargs.get('bot_token')
|
|
93
|
-
chat_id
|
|
113
|
+
chat_id = kwargs.get('chat_id')
|
|
94
114
|
|
|
95
115
|
expert_ids = kwargs.get('expert_id')
|
|
96
116
|
if expert_ids is None:
|
|
97
|
-
expert_ids =
|
|
117
|
+
expert_ids = list(
|
|
118
|
+
set([trade.expert_id for trade in trades_instances.values()]))
|
|
119
|
+
elif isinstance(expert_ids, int):
|
|
120
|
+
expert_ids = [expert_ids]
|
|
98
121
|
|
|
99
122
|
def update_risk(weights):
|
|
100
123
|
if weights is not None:
|
|
@@ -104,11 +127,18 @@ def _mt5_execution(
|
|
|
104
127
|
trade = trades_instances[symbol]
|
|
105
128
|
trade.dailydd = round(weights[symbol], 5)
|
|
106
129
|
|
|
130
|
+
def check_retcode(trade: Trade, position):
|
|
131
|
+
if len(trade.retcodes) > 0:
|
|
132
|
+
for retcode in trade.retcodes:
|
|
133
|
+
if retcode in NON_EXEC_RETCODES[position]:
|
|
134
|
+
return True
|
|
135
|
+
return False
|
|
136
|
+
|
|
107
137
|
def _send_notification(signal):
|
|
108
|
-
send_message(message=signal, notify_me=notify,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
logger
|
|
138
|
+
send_message(message=signal, notify_me=notify,
|
|
139
|
+
telegram=telegram, token=bot_token, chat_id=chat_id)
|
|
140
|
+
|
|
141
|
+
logger: Logger = trades_instances[symbols[0]].logger
|
|
112
142
|
max_trades = {symbol: mtrades[symbol]
|
|
113
143
|
if mtrades is not None and symbol in mtrades
|
|
114
144
|
else trades_instances[symbol].max_trade() for symbol in symbols}
|
|
@@ -121,23 +151,98 @@ def _mt5_execution(
|
|
|
121
151
|
return
|
|
122
152
|
if buys is not None or sells is not None:
|
|
123
153
|
trades_instances[symbol].break_even(
|
|
124
|
-
mm=mm, trail=trail, stop_trail=stop_trail,
|
|
154
|
+
mm=mm, trail=trail, stop_trail=stop_trail,
|
|
125
155
|
trail_after_points=trail_after_points, be_plus_points=be_plus_points)
|
|
126
|
-
num_days
|
|
127
|
-
time_intervals
|
|
128
|
-
trade_time
|
|
156
|
+
num_days = 0
|
|
157
|
+
time_intervals = 0
|
|
158
|
+
trade_time = _TF_MAPPING[time_frame]
|
|
129
159
|
|
|
130
|
-
long_market
|
|
131
|
-
short_market
|
|
160
|
+
long_market = {symbol: False for symbol in symbols}
|
|
161
|
+
short_market = {symbol: False for symbol in symbols}
|
|
132
162
|
try:
|
|
133
163
|
check_mt5_connection(**kwargs)
|
|
134
|
-
strategy: MT5Strategy = strategy_cls(
|
|
135
|
-
|
|
136
|
-
|
|
164
|
+
strategy: MT5Strategy = strategy_cls(
|
|
165
|
+
symbol_list=symbols, mode='live', **kwargs)
|
|
166
|
+
except Exception:
|
|
167
|
+
logger.error(f"Initializing strategy, STRATEGY={STRATEGY}")
|
|
168
|
+
traceback.print_exc()
|
|
137
169
|
return
|
|
138
170
|
logger.info(
|
|
139
171
|
f'Running {STRATEGY} Strategy in {time_frame} Interval ...')
|
|
140
|
-
|
|
172
|
+
|
|
173
|
+
def run_trade_algorithm(signal, symbol, id, trade: Trade, price, stoplimit):
|
|
174
|
+
signal = 'BMKT' if signal == 'LONG' else signal
|
|
175
|
+
signal = 'SMKT' if signal == 'SHORT' else signal
|
|
176
|
+
info = f"SIGNAL = {signal}, SYMBOL={symbol}, STRATEGY={STRATEGY}, TIMEFRAME={time_frame}"
|
|
177
|
+
msg = f"Sending {signal} Order ... SYMBOL={symbol}, STRATEGY={STRATEGY}"
|
|
178
|
+
tfmsg = f"Time Frame Not completed !!! SYMBOL={symbol}, STRATEGY={STRATEGY}"
|
|
179
|
+
riskmsg = f"Risk not allowed !!! SYMBOL={symbol}, STRATEGY={STRATEGY}"
|
|
180
|
+
if signal not in EXIT_SIGNAL_ACTIONS:
|
|
181
|
+
if signal in NON_EXEC_RETCODES and not check_retcode(trade, signal):
|
|
182
|
+
logger.info(info)
|
|
183
|
+
elif signal not in NON_EXEC_RETCODES:
|
|
184
|
+
logger.info(info)
|
|
185
|
+
if signal in EXIT_SIGNAL_ACTIONS:
|
|
186
|
+
for exit_signal, actions in EXIT_SIGNAL_ACTIONS.items():
|
|
187
|
+
for position_type, order_type in actions.items():
|
|
188
|
+
clos_func = getattr(
|
|
189
|
+
trades_instances[symbol], f"get_current_{position_type}")
|
|
190
|
+
if clos_func(id=id) is not None:
|
|
191
|
+
if notify:
|
|
192
|
+
_send_notification(info)
|
|
193
|
+
if position_type in POSITIONS_TYPES:
|
|
194
|
+
trade.close_positions(
|
|
195
|
+
position_type=order_type, id=id)
|
|
196
|
+
else:
|
|
197
|
+
trade.close_orders(order_type=order_type, id=id)
|
|
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
|
+
if notify:
|
|
202
|
+
_send_notification(info)
|
|
203
|
+
if not check_retcode(trade, 'BMKT'):
|
|
204
|
+
logger.info(msg)
|
|
205
|
+
trade.open_buy_position(
|
|
206
|
+
action=signal, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
|
|
207
|
+
else:
|
|
208
|
+
logger.info(tfmsg)
|
|
209
|
+
check(buys[symbol], sells[symbol], symbol)
|
|
210
|
+
else:
|
|
211
|
+
if notify:
|
|
212
|
+
_send_notification(info)
|
|
213
|
+
if not check_retcode(trade, 'BMKT'):
|
|
214
|
+
logger.info(msg)
|
|
215
|
+
trade.open_buy_position(
|
|
216
|
+
action=signal, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
|
|
217
|
+
|
|
218
|
+
elif signal in BUYS and long_market[symbol]:
|
|
219
|
+
logger.info(riskmsg)
|
|
220
|
+
|
|
221
|
+
elif signal in SELLS and not short_market[symbol]:
|
|
222
|
+
if use_trade_time:
|
|
223
|
+
if time_intervals % trade_time == 0 or sells[symbol] is None:
|
|
224
|
+
if notify:
|
|
225
|
+
_send_notification(info)
|
|
226
|
+
if not check_retcode(trade, 'SMKT'):
|
|
227
|
+
logger.info(msg)
|
|
228
|
+
trade.open_sell_position(
|
|
229
|
+
action=signal, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
|
|
230
|
+
else:
|
|
231
|
+
logger.info(tfmsg)
|
|
232
|
+
check(buys[symbol], sells[symbol], symbol)
|
|
233
|
+
else:
|
|
234
|
+
if notify:
|
|
235
|
+
_send_notification(info)
|
|
236
|
+
if not check_retcode(trade, 'SMKT'):
|
|
237
|
+
logger.info(msg)
|
|
238
|
+
trade.open_sell_position(
|
|
239
|
+
action=signal, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
|
|
240
|
+
|
|
241
|
+
elif signal in SELLS and short_market[symbol]:
|
|
242
|
+
logger.info(riskmsg)
|
|
243
|
+
else:
|
|
244
|
+
check(buys[symbol], sells[symbol], symbol)
|
|
245
|
+
|
|
141
246
|
while True:
|
|
142
247
|
try:
|
|
143
248
|
check_mt5_connection(**kwargs)
|
|
@@ -150,7 +255,8 @@ def _mt5_execution(
|
|
|
150
255
|
for symbol in symbols:
|
|
151
256
|
positions_orders[type][symbol] = None
|
|
152
257
|
for id in expert_ids:
|
|
153
|
-
func = getattr(
|
|
258
|
+
func = getattr(
|
|
259
|
+
trades_instances[symbol], f"get_current_{type}")
|
|
154
260
|
func_value = func(id=id)
|
|
155
261
|
if func_value is not None:
|
|
156
262
|
if positions_orders[type][symbol] is None:
|
|
@@ -166,120 +272,60 @@ def _mt5_execution(
|
|
|
166
272
|
logger.info(
|
|
167
273
|
f"Current {type.upper()} SYMBOL={symbol}: \
|
|
168
274
|
{positions_orders[type][symbol]}, STRATEGY={STRATEGY}")
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
275
|
+
long_market = {
|
|
276
|
+
symbol: buys[symbol] is not None
|
|
277
|
+
and len(buys[symbol]) >= max_trades[symbol] for symbol in symbols
|
|
278
|
+
}
|
|
279
|
+
short_market = {
|
|
280
|
+
symbol: sells[symbol] is not None
|
|
281
|
+
and len(sells[symbol]) >= max_trades[symbol] for symbol in symbols
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
except Exception:
|
|
285
|
+
logger.error(
|
|
286
|
+
f"Handling Positions and Orders, STRATEGY={STRATEGY}")
|
|
287
|
+
traceback.print_exc()
|
|
181
288
|
continue
|
|
182
289
|
time.sleep(0.5)
|
|
183
290
|
try:
|
|
184
291
|
check_mt5_connection(**kwargs)
|
|
185
292
|
signals = strategy.calculate_signals()
|
|
186
|
-
weights =
|
|
187
|
-
|
|
188
|
-
|
|
293
|
+
weights = (
|
|
294
|
+
strategy.apply_risk_management(optimizer)
|
|
295
|
+
if hasattr(strategy, 'apply_risk_management')
|
|
296
|
+
else None
|
|
297
|
+
)
|
|
189
298
|
update_risk(weights)
|
|
190
|
-
except Exception
|
|
191
|
-
logger.error(f"Calculating signal,
|
|
299
|
+
except Exception:
|
|
300
|
+
logger.error(f"Calculating signal, STRATEGY={STRATEGY}")
|
|
301
|
+
traceback.print_exc()
|
|
192
302
|
continue
|
|
193
|
-
for
|
|
303
|
+
for signal in signals:
|
|
194
304
|
try:
|
|
195
305
|
check_mt5_connection(**kwargs)
|
|
306
|
+
symbol = signal.symbol
|
|
196
307
|
trade: Trade = trades_instances[symbol]
|
|
197
|
-
tfmsg = f"Time Frame Not completed !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}"
|
|
198
|
-
riskmsg = f"Risk not allowed !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}"
|
|
199
|
-
signal = signals[symbol]
|
|
200
|
-
if isinstance(signal, dict):
|
|
201
|
-
action = signal.get('action')
|
|
202
|
-
price = signal.get('price')
|
|
203
|
-
id = signal.get('id', trade.expert_id)
|
|
204
|
-
stoplimit = signal.get('stoplimit')
|
|
205
|
-
signal = action
|
|
206
|
-
elif isinstance(signal, str):
|
|
207
|
-
price = None
|
|
208
|
-
stoplimit = None
|
|
209
|
-
id = trade.expert_id
|
|
210
308
|
if trade.trading_time() and today in trading_days:
|
|
211
|
-
|
|
212
|
-
signal
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
for position_type, order_type in actions.items():
|
|
221
|
-
if positions_orders[position_type][symbol] is not None:
|
|
222
|
-
if notify:
|
|
223
|
-
_send_notification(info)
|
|
224
|
-
if position_type in POSITIONS_TYPES:
|
|
225
|
-
trade.close_positions(position_type=order_type, id=id)
|
|
226
|
-
else:
|
|
227
|
-
trade.close_orders(order_type=order_type, id=id)
|
|
228
|
-
elif signal in BUYS and not long_market[symbol]:
|
|
229
|
-
if use_trade_time:
|
|
230
|
-
if time_intervals % trade_time == 0 or buys[symbol] is None:
|
|
231
|
-
logger.info(msg)
|
|
232
|
-
if notify:
|
|
233
|
-
_send_notification(info)
|
|
234
|
-
trade.open_buy_position(
|
|
235
|
-
action=signal, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
|
|
236
|
-
else:
|
|
237
|
-
logger.info(tfmsg)
|
|
238
|
-
check(buys[symbol], sells[symbol], symbol)
|
|
239
|
-
else:
|
|
240
|
-
logger.info(msg)
|
|
241
|
-
if notify:
|
|
242
|
-
_send_notification(info)
|
|
243
|
-
trade.open_buy_position(
|
|
244
|
-
action=signal, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
|
|
245
|
-
elif signal in BUYS:
|
|
246
|
-
if check_max_trades:
|
|
247
|
-
if long_market[symbol]:
|
|
248
|
-
logger.info(riskmsg)
|
|
249
|
-
check(buys[symbol], sells[symbol], symbol)
|
|
250
|
-
|
|
251
|
-
elif signal in SELLS and not short_market[symbol]:
|
|
252
|
-
if use_trade_time:
|
|
253
|
-
if time_intervals % trade_time == 0 or sells[symbol] is None:
|
|
254
|
-
logger.info(msg)
|
|
255
|
-
if notify:
|
|
256
|
-
_send_notification(info)
|
|
257
|
-
trade.open_sell_position(
|
|
258
|
-
action=signal, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
|
|
259
|
-
else:
|
|
260
|
-
logger.info(tfmsg)
|
|
261
|
-
check(buys[symbol], sells[symbol], symbol)
|
|
262
|
-
else:
|
|
263
|
-
logger.info(msg)
|
|
264
|
-
if notify:
|
|
265
|
-
_send_notification(info)
|
|
266
|
-
trade.open_sell_position(
|
|
267
|
-
action=signal, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
|
|
268
|
-
elif signal in SELLS:
|
|
269
|
-
if check_max_trades:
|
|
270
|
-
if short_market[symbol]:
|
|
271
|
-
logger.info(riskmsg)
|
|
272
|
-
check(buys[symbol], sells[symbol], symbol)
|
|
273
|
-
else:
|
|
274
|
-
check(buys[symbol], sells[symbol], symbol)
|
|
309
|
+
action = (
|
|
310
|
+
signal.action.value if isinstance(signal.action, TradeAction)
|
|
311
|
+
else signal.action
|
|
312
|
+
)
|
|
313
|
+
run_trade_algorithm(
|
|
314
|
+
action, symbol, signal.id, trade, signal.price, signal.stoplimit)
|
|
315
|
+
elif len(symbols) > 10 and symbol == symbols[-1]:
|
|
316
|
+
logger.info(
|
|
317
|
+
f"Not trading Time !!!, STRATEGY={STRATEGY}")
|
|
275
318
|
else:
|
|
276
319
|
logger.info(
|
|
277
320
|
f"Not trading Time !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
278
|
-
|
|
321
|
+
check(buys[symbol], sells[symbol], symbol)
|
|
279
322
|
|
|
280
|
-
except Exception
|
|
281
|
-
logger.error(
|
|
323
|
+
except Exception:
|
|
324
|
+
logger.error(
|
|
325
|
+
f"Handling Signals, SYMBOL={symbol}, STRATEGY={STRATEGY}")
|
|
326
|
+
traceback.print_exc()
|
|
282
327
|
continue
|
|
328
|
+
|
|
283
329
|
time.sleep((60 * iter_time) - 1.0)
|
|
284
330
|
if iter_time == 1:
|
|
285
331
|
time_intervals += 1
|
|
@@ -293,24 +339,35 @@ def _mt5_execution(
|
|
|
293
339
|
try:
|
|
294
340
|
FRIDAY = 'friday'
|
|
295
341
|
check_mt5_connection(**kwargs)
|
|
296
|
-
day_end = all(trade.days_end()
|
|
342
|
+
day_end = all(trade.days_end()
|
|
343
|
+
for trade in trades_instances.values())
|
|
297
344
|
if closing_pnl is not None:
|
|
298
|
-
closing = all(trade.positive_profit(id=trade.expert_id, th=closing_pnl)
|
|
299
|
-
|
|
300
|
-
else:
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
345
|
+
closing = all(trade.positive_profit(id=trade.expert_id, th=closing_pnl)
|
|
346
|
+
for trade in trades_instances.values())
|
|
347
|
+
else:
|
|
348
|
+
closing = True
|
|
349
|
+
|
|
350
|
+
def logmsg(period, symbol): return logger.info(
|
|
351
|
+
f"End of the {period} !!! SYMBOL={symbol}, STRATEGY={STRATEGY}")
|
|
352
|
+
|
|
353
|
+
def logmsgif(period, symbol):
|
|
354
|
+
return (
|
|
355
|
+
logmsg(period, symbol) if len(symbols) <= 10 and symbol == symbols[-1]
|
|
356
|
+
else logger.info(f'Not trading Time !!! , STRATEGY={STRATEGY}')
|
|
357
|
+
)
|
|
358
|
+
def sleepmsg(sleep_time): return logger.info(
|
|
304
359
|
f"Sleeping for {sleep_time} minutes ...\n")
|
|
305
|
-
sessionmsg =
|
|
360
|
+
sessionmsg = "STARTING NEW TRADING SESSION ...\n"
|
|
306
361
|
if period.lower() == 'day':
|
|
307
362
|
for symbol in symbols:
|
|
308
363
|
trade = trades_instances[symbol]
|
|
309
364
|
if trade.days_end() and closing:
|
|
310
365
|
for id in expert_ids:
|
|
311
|
-
trade.close_positions(
|
|
312
|
-
|
|
366
|
+
trade.close_positions(
|
|
367
|
+
position_type='all', id=id, comment=comment)
|
|
368
|
+
logmsgif("Day", symbol)
|
|
313
369
|
trade.statistics(save=True)
|
|
370
|
+
strategy.perform_period_end_checks()
|
|
314
371
|
if day_end:
|
|
315
372
|
if period_end_action == 'break' and closing:
|
|
316
373
|
break
|
|
@@ -320,7 +377,8 @@ def _mt5_execution(
|
|
|
320
377
|
time.sleep(60 * sleep_time)
|
|
321
378
|
logger.info(sessionmsg)
|
|
322
379
|
elif period_end_action == 'sleep' and today == FRIDAY:
|
|
323
|
-
sleep_time = trades_instances[symbols[-1]
|
|
380
|
+
sleep_time = trades_instances[symbols[-1]
|
|
381
|
+
].sleep_time(weekend=True)
|
|
324
382
|
sleepmsg(sleep_time)
|
|
325
383
|
time.sleep(60 * sleep_time)
|
|
326
384
|
logger.info(sessionmsg)
|
|
@@ -329,13 +387,15 @@ def _mt5_execution(
|
|
|
329
387
|
for symbol in symbols:
|
|
330
388
|
trade = trades_instances[symbol]
|
|
331
389
|
if trade.days_end() and today != FRIDAY:
|
|
332
|
-
|
|
390
|
+
logmsgif("Day", symbol)
|
|
333
391
|
|
|
334
392
|
elif trade.days_end() and today == FRIDAY and closing:
|
|
335
393
|
for id in expert_ids:
|
|
336
|
-
trade.close_positions(
|
|
337
|
-
|
|
394
|
+
trade.close_positions(
|
|
395
|
+
position_type='all', id=id, comment=comment)
|
|
396
|
+
logmsgif("Week", symbol)
|
|
338
397
|
trade.statistics(save=True)
|
|
398
|
+
strategy.perform_period_end_checks()
|
|
339
399
|
if day_end and today != FRIDAY:
|
|
340
400
|
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
341
401
|
sleepmsg(sleep_time)
|
|
@@ -345,7 +405,8 @@ def _mt5_execution(
|
|
|
345
405
|
if period_end_action == 'break' and closing:
|
|
346
406
|
break
|
|
347
407
|
elif period_end_action == 'sleep' or not closing:
|
|
348
|
-
sleep_time = trades_instances[symbols[-1]
|
|
408
|
+
sleep_time = trades_instances[symbols[-1]
|
|
409
|
+
].sleep_time(weekend=True)
|
|
349
410
|
sleepmsg(sleep_time)
|
|
350
411
|
time.sleep(60 * sleep_time)
|
|
351
412
|
logger.info(sessionmsg)
|
|
@@ -354,18 +415,20 @@ def _mt5_execution(
|
|
|
354
415
|
for symbol in symbols:
|
|
355
416
|
trade = trades_instances[symbol]
|
|
356
417
|
if trade.days_end() and today != FRIDAY:
|
|
357
|
-
|
|
418
|
+
logmsgif("Day", symbol)
|
|
358
419
|
elif trade.days_end() and today == FRIDAY:
|
|
359
|
-
|
|
420
|
+
logmsgif("Week", symbol)
|
|
360
421
|
elif (
|
|
361
422
|
trade.days_end()
|
|
362
423
|
and today == FRIDAY
|
|
363
424
|
and num_days/len(symbols) >= 20
|
|
364
425
|
) and closing:
|
|
365
426
|
for id in expert_ids:
|
|
366
|
-
trade.close_positions(
|
|
367
|
-
|
|
427
|
+
trade.close_positions(
|
|
428
|
+
position_type='all', id=id, comment=comment)
|
|
429
|
+
logmsgif("Month", symbol)
|
|
368
430
|
trade.statistics(save=True)
|
|
431
|
+
strategy.perform_period_end_checks()
|
|
369
432
|
if day_end and today != FRIDAY:
|
|
370
433
|
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
371
434
|
sleepmsg(sleep_time)
|
|
@@ -374,7 +437,7 @@ def _mt5_execution(
|
|
|
374
437
|
num_days += 1
|
|
375
438
|
elif day_end and today == FRIDAY:
|
|
376
439
|
sleep_time = trades_instances[symbols[-1]
|
|
377
|
-
|
|
440
|
+
].sleep_time(weekend=True)
|
|
378
441
|
sleepmsg(sleep_time)
|
|
379
442
|
time.sleep(60 * sleep_time)
|
|
380
443
|
logger.info(sessionmsg)
|
|
@@ -382,10 +445,12 @@ def _mt5_execution(
|
|
|
382
445
|
elif (day_end
|
|
383
446
|
and today == FRIDAY
|
|
384
447
|
and num_days/len(symbols) >= 20
|
|
385
|
-
|
|
448
|
+
):
|
|
386
449
|
break
|
|
387
|
-
except Exception
|
|
388
|
-
logger.error(
|
|
450
|
+
except Exception:
|
|
451
|
+
logger.error(
|
|
452
|
+
f"Handling period end actions, STRATEGY={STRATEGY}")
|
|
453
|
+
traceback.print_exc()
|
|
389
454
|
continue
|
|
390
455
|
|
|
391
456
|
|
|
@@ -411,7 +476,7 @@ class MT5ExecutionEngine():
|
|
|
411
476
|
|
|
412
477
|
Examples
|
|
413
478
|
--------
|
|
414
|
-
|
|
479
|
+
|
|
415
480
|
>>> from bbstrader.metatrader import create_trade_instance
|
|
416
481
|
>>> from bbstrader.trading.execution import MT5ExecutionEngine
|
|
417
482
|
>>> from bbstrader.trading.strategies import StockIndexCFDTrading
|
|
@@ -487,7 +552,7 @@ class MT5ExecutionEngine():
|
|
|
487
552
|
trading_days: Optional[List[str]] = TradingDays,
|
|
488
553
|
comment: Optional[str] = None,
|
|
489
554
|
**kwargs
|
|
490
|
-
|
|
555
|
+
):
|
|
491
556
|
"""
|
|
492
557
|
Args:
|
|
493
558
|
symbol_list : List of symbols to trade
|
|
@@ -518,7 +583,7 @@ class MT5ExecutionEngine():
|
|
|
518
583
|
1. For `trail` , `stop_trail` , `trail_after_points` , `be_plus_points` see `bbstrader.metatrader.trade.Trade.break_even()` .
|
|
519
584
|
2. All Strategies must inherit from `bbstrader.btengine.strategy.Strategy` or `bbstrader.btengine.strategy.MT5Strategy` class
|
|
520
585
|
and have a `calculate_signals` method that returns a dictionary of signals for each symbol in symbol_list.
|
|
521
|
-
|
|
586
|
+
|
|
522
587
|
3. All strategies must have the following arguments in their `__init__` method:
|
|
523
588
|
- bars (DataHandler): DataHandler instance default to None
|
|
524
589
|
- events (Queue): Queue instance default to None
|
|
@@ -528,7 +593,7 @@ class MT5ExecutionEngine():
|
|
|
528
593
|
The keyword arguments are all the additional arguments passed to the `MT5ExecutionEngine` class,
|
|
529
594
|
the `Strategy` class, the `DataHandler` class, the `Portfolio` class and the `ExecutionHandler` class.
|
|
530
595
|
- The `bars` and `events` arguments are used for backtesting only.
|
|
531
|
-
|
|
596
|
+
|
|
532
597
|
4. All strategies must generate signals for backtesting and live trading.
|
|
533
598
|
See the `bbstrader.trading.strategies` module for more information on how to create custom strategies.
|
|
534
599
|
See `bbstrader.metatrader.account.check_mt5_connection()` for more details on how to connect to MT5 terminal.
|
|
@@ -556,7 +621,7 @@ class MT5ExecutionEngine():
|
|
|
556
621
|
trades = self.trades_instances.keys()
|
|
557
622
|
s = self.strategy_cls.__name__
|
|
558
623
|
return f"MT5ExecutionEngine(Symbols={list(trades)}, Strategy={s})"
|
|
559
|
-
|
|
624
|
+
|
|
560
625
|
def run(self):
|
|
561
626
|
check_mt5_connection(**self.kwargs)
|
|
562
627
|
_mt5_execution(
|
|
@@ -581,4 +646,5 @@ class MT5ExecutionEngine():
|
|
|
581
646
|
)
|
|
582
647
|
|
|
583
648
|
|
|
584
|
-
class TWSExecutionEngine()
|
|
649
|
+
class TWSExecutionEngine():
|
|
650
|
+
...
|
bbstrader/trading/scripts.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
from notifypy import Notify
|
|
4
|
+
from telegram import Bot
|
|
4
5
|
from telegram.error import TelegramError
|
|
5
6
|
|
|
6
7
|
__all__ = ['send_telegram_message', 'send_notification', 'send_message']
|
|
7
8
|
|
|
9
|
+
|
|
8
10
|
async def send_telegram_message(token, chat_id, text=''):
|
|
9
11
|
"""
|
|
10
12
|
Send a message to a telegram chat
|
|
@@ -23,6 +25,7 @@ async def send_telegram_message(token, chat_id, text=''):
|
|
|
23
25
|
except TelegramError as e:
|
|
24
26
|
print(f"Error sending message: {e}")
|
|
25
27
|
|
|
28
|
+
|
|
26
29
|
def send_notification(title, message=''):
|
|
27
30
|
"""
|
|
28
31
|
Send a desktop notification
|
|
@@ -35,9 +38,10 @@ def send_notification(title, message=''):
|
|
|
35
38
|
notification.title = title
|
|
36
39
|
notification.message = message
|
|
37
40
|
notification.send()
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def send_message(title='SIGNAL', message='New signal',
|
|
44
|
+
notify_me=False, telegram=False, token=None, chat_id=None):
|
|
41
45
|
"""
|
|
42
46
|
Send a message to the user
|
|
43
47
|
|