bbstrader 0.1.94__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 +9 -9
- bbstrader/btengine/__init__.py +7 -7
- bbstrader/btengine/backtest.py +30 -26
- bbstrader/btengine/data.py +100 -79
- 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 +119 -94
- bbstrader/config.py +14 -8
- bbstrader/core/__init__.py +0 -0
- bbstrader/core/data.py +22 -0
- bbstrader/core/utils.py +57 -0
- bbstrader/ibkr/__init__.py +0 -0
- bbstrader/ibkr/utils.py +0 -0
- bbstrader/metatrader/__init__.py +5 -5
- bbstrader/metatrader/account.py +117 -121
- bbstrader/metatrader/rates.py +83 -80
- bbstrader/metatrader/risk.py +23 -37
- bbstrader/metatrader/trade.py +169 -140
- bbstrader/metatrader/utils.py +3 -3
- bbstrader/models/__init__.py +5 -5
- bbstrader/models/factors.py +280 -0
- bbstrader/models/ml.py +1092 -0
- bbstrader/models/optimization.py +31 -28
- bbstrader/models/{portfolios.py → portfolio.py} +64 -46
- bbstrader/models/risk.py +15 -9
- bbstrader/trading/__init__.py +2 -2
- bbstrader/trading/execution.py +252 -164
- bbstrader/trading/scripts.py +8 -4
- bbstrader/trading/strategies.py +79 -66
- bbstrader/tseries.py +482 -107
- {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/LICENSE +1 -1
- {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/METADATA +6 -1
- bbstrader-0.2.1.dist-info/RECORD +37 -0
- bbstrader-0.1.94.dist-info/RECORD +0 -32
- {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/WHEEL +0 -0
- {bbstrader-0.1.94.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,29 +64,61 @@ 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
|
-
notify
|
|
108
|
+
mtrades = kwargs.get('max_trades')
|
|
109
|
+
notify = kwargs.get('notify', False)
|
|
89
110
|
if notify:
|
|
90
|
-
telegram
|
|
111
|
+
telegram = kwargs.get('telegram', False)
|
|
91
112
|
bot_token = kwargs.get('bot_token')
|
|
92
|
-
chat_id
|
|
93
|
-
|
|
113
|
+
chat_id = kwargs.get('chat_id')
|
|
114
|
+
|
|
115
|
+
expert_ids = kwargs.get('expert_id')
|
|
116
|
+
if expert_ids is None:
|
|
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]
|
|
121
|
+
|
|
94
122
|
def update_risk(weights):
|
|
95
123
|
if weights is not None:
|
|
96
124
|
for symbol in symbols:
|
|
@@ -99,11 +127,18 @@ def _mt5_execution(
|
|
|
99
127
|
trade = trades_instances[symbol]
|
|
100
128
|
trade.dailydd = round(weights[symbol], 5)
|
|
101
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
|
+
|
|
102
137
|
def _send_notification(signal):
|
|
103
|
-
send_message(message=signal, notify_me=notify,
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
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
|
|
107
142
|
max_trades = {symbol: mtrades[symbol]
|
|
108
143
|
if mtrades is not None and symbol in mtrades
|
|
109
144
|
else trades_instances[symbol].max_trade() for symbol in symbols}
|
|
@@ -116,23 +151,98 @@ def _mt5_execution(
|
|
|
116
151
|
return
|
|
117
152
|
if buys is not None or sells is not None:
|
|
118
153
|
trades_instances[symbol].break_even(
|
|
119
|
-
mm=mm, trail=trail, stop_trail=stop_trail,
|
|
154
|
+
mm=mm, trail=trail, stop_trail=stop_trail,
|
|
120
155
|
trail_after_points=trail_after_points, be_plus_points=be_plus_points)
|
|
121
|
-
num_days
|
|
122
|
-
time_intervals
|
|
123
|
-
trade_time
|
|
156
|
+
num_days = 0
|
|
157
|
+
time_intervals = 0
|
|
158
|
+
trade_time = _TF_MAPPING[time_frame]
|
|
124
159
|
|
|
125
|
-
long_market
|
|
126
|
-
short_market
|
|
160
|
+
long_market = {symbol: False for symbol in symbols}
|
|
161
|
+
short_market = {symbol: False for symbol in symbols}
|
|
127
162
|
try:
|
|
128
163
|
check_mt5_connection(**kwargs)
|
|
129
|
-
strategy: MT5Strategy = strategy_cls(
|
|
130
|
-
|
|
131
|
-
|
|
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()
|
|
132
169
|
return
|
|
133
170
|
logger.info(
|
|
134
171
|
f'Running {STRATEGY} Strategy in {time_frame} Interval ...')
|
|
135
|
-
|
|
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
|
+
|
|
136
246
|
while True:
|
|
137
247
|
try:
|
|
138
248
|
check_mt5_connection(**kwargs)
|
|
@@ -144,8 +254,15 @@ def _mt5_execution(
|
|
|
144
254
|
positions_orders[type] = {}
|
|
145
255
|
for symbol in symbols:
|
|
146
256
|
positions_orders[type][symbol] = None
|
|
147
|
-
|
|
148
|
-
|
|
257
|
+
for id in expert_ids:
|
|
258
|
+
func = getattr(
|
|
259
|
+
trades_instances[symbol], f"get_current_{type}")
|
|
260
|
+
func_value = func(id=id)
|
|
261
|
+
if func_value is not None:
|
|
262
|
+
if positions_orders[type][symbol] is None:
|
|
263
|
+
positions_orders[type][symbol] = func(id=id)
|
|
264
|
+
else:
|
|
265
|
+
positions_orders[type][symbol] += func(id=id)
|
|
149
266
|
buys = positions_orders['buys']
|
|
150
267
|
sells = positions_orders['sells']
|
|
151
268
|
for symbol in symbols:
|
|
@@ -156,111 +273,59 @@ def _mt5_execution(
|
|
|
156
273
|
f"Current {type.upper()} SYMBOL={symbol}: \
|
|
157
274
|
{positions_orders[type][symbol]}, STRATEGY={STRATEGY}")
|
|
158
275
|
long_market = {
|
|
159
|
-
symbol: buys[symbol] is not None
|
|
276
|
+
symbol: buys[symbol] is not None
|
|
160
277
|
and len(buys[symbol]) >= max_trades[symbol] for symbol in symbols
|
|
161
278
|
}
|
|
162
279
|
short_market = {
|
|
163
|
-
symbol: sells[symbol] is not None
|
|
280
|
+
symbol: sells[symbol] is not None
|
|
164
281
|
and len(sells[symbol]) >= max_trades[symbol] for symbol in symbols
|
|
165
282
|
}
|
|
166
283
|
|
|
167
|
-
except Exception
|
|
168
|
-
logger.error(
|
|
284
|
+
except Exception:
|
|
285
|
+
logger.error(
|
|
286
|
+
f"Handling Positions and Orders, STRATEGY={STRATEGY}")
|
|
287
|
+
traceback.print_exc()
|
|
169
288
|
continue
|
|
170
289
|
time.sleep(0.5)
|
|
171
290
|
try:
|
|
172
291
|
check_mt5_connection(**kwargs)
|
|
173
292
|
signals = strategy.calculate_signals()
|
|
174
|
-
weights =
|
|
175
|
-
|
|
176
|
-
|
|
293
|
+
weights = (
|
|
294
|
+
strategy.apply_risk_management(optimizer)
|
|
295
|
+
if hasattr(strategy, 'apply_risk_management')
|
|
296
|
+
else None
|
|
297
|
+
)
|
|
177
298
|
update_risk(weights)
|
|
178
|
-
except Exception
|
|
179
|
-
logger.error(f"Calculating signal,
|
|
299
|
+
except Exception:
|
|
300
|
+
logger.error(f"Calculating signal, STRATEGY={STRATEGY}")
|
|
301
|
+
traceback.print_exc()
|
|
180
302
|
continue
|
|
181
|
-
for
|
|
303
|
+
for signal in signals:
|
|
182
304
|
try:
|
|
183
305
|
check_mt5_connection(**kwargs)
|
|
306
|
+
symbol = signal.symbol
|
|
184
307
|
trade: Trade = trades_instances[symbol]
|
|
185
|
-
tfmsg = f"Time Frame Not completed !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}"
|
|
186
|
-
riskmsg = f"Risk not allowed !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}"
|
|
187
|
-
signal = signals[symbol]
|
|
188
|
-
if isinstance(signal, dict):
|
|
189
|
-
signal = signal['action']
|
|
190
|
-
price = signal['price']
|
|
191
|
-
stoplimit = signal.get('stoplimit')
|
|
192
|
-
elif isinstance(signal, str):
|
|
193
|
-
price = None
|
|
194
|
-
stoplimit = None
|
|
195
308
|
if trade.trading_time() and today in trading_days:
|
|
196
|
-
|
|
197
|
-
signal
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
for position_type, order_type in actions.items():
|
|
206
|
-
if positions_orders[position_type][symbol] is not None:
|
|
207
|
-
if notify:
|
|
208
|
-
_send_notification(info)
|
|
209
|
-
if position_type in POSITIONS_TYPES:
|
|
210
|
-
trade.close_positions(position_type=order_type)
|
|
211
|
-
else:
|
|
212
|
-
trade.close_orders(order_type=order_type)
|
|
213
|
-
elif signal in BUYS and not long_market[symbol]:
|
|
214
|
-
if use_trade_time:
|
|
215
|
-
if time_intervals % trade_time == 0 or buys[symbol] is None:
|
|
216
|
-
logger.info(msg)
|
|
217
|
-
if notify:
|
|
218
|
-
_send_notification(info)
|
|
219
|
-
trade.open_buy_position(
|
|
220
|
-
action=signal, price=price, stoplimit=stoplimit, mm=mm, comment=comment)
|
|
221
|
-
else:
|
|
222
|
-
logger.info(tfmsg)
|
|
223
|
-
check(buys[symbol], sells[symbol], symbol)
|
|
224
|
-
else:
|
|
225
|
-
logger.info(msg)
|
|
226
|
-
if notify:
|
|
227
|
-
_send_notification(info)
|
|
228
|
-
trade.open_buy_position(
|
|
229
|
-
action=signal, price=price, stoplimit=stoplimit, mm=mm, comment=comment)
|
|
230
|
-
elif signal in BUYS and long_market[symbol]:
|
|
231
|
-
logger.info(riskmsg)
|
|
232
|
-
check(buys[symbol], sells[symbol], symbol)
|
|
233
|
-
|
|
234
|
-
elif signal in SELLS and not short_market[symbol]:
|
|
235
|
-
if use_trade_time:
|
|
236
|
-
if time_intervals % trade_time == 0 or sells[symbol] is None:
|
|
237
|
-
logger.info(msg)
|
|
238
|
-
if notify:
|
|
239
|
-
_send_notification(info)
|
|
240
|
-
trade.open_sell_position(
|
|
241
|
-
action=signal, price=price, stoplimit=stoplimit, mm=mm, comment=comment)
|
|
242
|
-
else:
|
|
243
|
-
logger.info(tfmsg)
|
|
244
|
-
check(buys[symbol], sells[symbol], symbol)
|
|
245
|
-
else:
|
|
246
|
-
logger.info(msg)
|
|
247
|
-
if notify:
|
|
248
|
-
_send_notification(info)
|
|
249
|
-
trade.open_sell_position(
|
|
250
|
-
action=signal, price=price, stoplimit=stoplimit, mm=mm, comment=comment)
|
|
251
|
-
elif signal in SELLS and short_market[symbol]:
|
|
252
|
-
logger.info(riskmsg)
|
|
253
|
-
check(buys[symbol], sells[symbol], symbol)
|
|
254
|
-
else:
|
|
255
|
-
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}")
|
|
256
318
|
else:
|
|
257
319
|
logger.info(
|
|
258
320
|
f"Not trading Time !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
259
|
-
|
|
321
|
+
check(buys[symbol], sells[symbol], symbol)
|
|
260
322
|
|
|
261
|
-
except Exception
|
|
262
|
-
logger.error(
|
|
323
|
+
except Exception:
|
|
324
|
+
logger.error(
|
|
325
|
+
f"Handling Signals, SYMBOL={symbol}, STRATEGY={STRATEGY}")
|
|
326
|
+
traceback.print_exc()
|
|
263
327
|
continue
|
|
328
|
+
|
|
264
329
|
time.sleep((60 * iter_time) - 1.0)
|
|
265
330
|
if iter_time == 1:
|
|
266
331
|
time_intervals += 1
|
|
@@ -274,23 +339,35 @@ def _mt5_execution(
|
|
|
274
339
|
try:
|
|
275
340
|
FRIDAY = 'friday'
|
|
276
341
|
check_mt5_connection(**kwargs)
|
|
277
|
-
day_end = all(trade.days_end()
|
|
342
|
+
day_end = all(trade.days_end()
|
|
343
|
+
for trade in trades_instances.values())
|
|
278
344
|
if closing_pnl is not None:
|
|
279
|
-
closing = all(trade.positive_profit(id=trade.expert_id, th=closing_pnl)
|
|
280
|
-
|
|
281
|
-
else:
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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(
|
|
285
359
|
f"Sleeping for {sleep_time} minutes ...\n")
|
|
286
|
-
sessionmsg =
|
|
360
|
+
sessionmsg = "STARTING NEW TRADING SESSION ...\n"
|
|
287
361
|
if period.lower() == 'day':
|
|
288
362
|
for symbol in symbols:
|
|
289
363
|
trade = trades_instances[symbol]
|
|
290
364
|
if trade.days_end() and closing:
|
|
291
|
-
|
|
292
|
-
|
|
365
|
+
for id in expert_ids:
|
|
366
|
+
trade.close_positions(
|
|
367
|
+
position_type='all', id=id, comment=comment)
|
|
368
|
+
logmsgif("Day", symbol)
|
|
293
369
|
trade.statistics(save=True)
|
|
370
|
+
strategy.perform_period_end_checks()
|
|
294
371
|
if day_end:
|
|
295
372
|
if period_end_action == 'break' and closing:
|
|
296
373
|
break
|
|
@@ -300,7 +377,8 @@ def _mt5_execution(
|
|
|
300
377
|
time.sleep(60 * sleep_time)
|
|
301
378
|
logger.info(sessionmsg)
|
|
302
379
|
elif period_end_action == 'sleep' and today == FRIDAY:
|
|
303
|
-
sleep_time = trades_instances[symbols[-1]
|
|
380
|
+
sleep_time = trades_instances[symbols[-1]
|
|
381
|
+
].sleep_time(weekend=True)
|
|
304
382
|
sleepmsg(sleep_time)
|
|
305
383
|
time.sleep(60 * sleep_time)
|
|
306
384
|
logger.info(sessionmsg)
|
|
@@ -309,12 +387,15 @@ def _mt5_execution(
|
|
|
309
387
|
for symbol in symbols:
|
|
310
388
|
trade = trades_instances[symbol]
|
|
311
389
|
if trade.days_end() and today != FRIDAY:
|
|
312
|
-
|
|
390
|
+
logmsgif("Day", symbol)
|
|
313
391
|
|
|
314
392
|
elif trade.days_end() and today == FRIDAY and closing:
|
|
315
|
-
|
|
316
|
-
|
|
393
|
+
for id in expert_ids:
|
|
394
|
+
trade.close_positions(
|
|
395
|
+
position_type='all', id=id, comment=comment)
|
|
396
|
+
logmsgif("Week", symbol)
|
|
317
397
|
trade.statistics(save=True)
|
|
398
|
+
strategy.perform_period_end_checks()
|
|
318
399
|
if day_end and today != FRIDAY:
|
|
319
400
|
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
320
401
|
sleepmsg(sleep_time)
|
|
@@ -324,7 +405,8 @@ def _mt5_execution(
|
|
|
324
405
|
if period_end_action == 'break' and closing:
|
|
325
406
|
break
|
|
326
407
|
elif period_end_action == 'sleep' or not closing:
|
|
327
|
-
sleep_time = trades_instances[symbols[-1]
|
|
408
|
+
sleep_time = trades_instances[symbols[-1]
|
|
409
|
+
].sleep_time(weekend=True)
|
|
328
410
|
sleepmsg(sleep_time)
|
|
329
411
|
time.sleep(60 * sleep_time)
|
|
330
412
|
logger.info(sessionmsg)
|
|
@@ -333,17 +415,20 @@ def _mt5_execution(
|
|
|
333
415
|
for symbol in symbols:
|
|
334
416
|
trade = trades_instances[symbol]
|
|
335
417
|
if trade.days_end() and today != FRIDAY:
|
|
336
|
-
|
|
418
|
+
logmsgif("Day", symbol)
|
|
337
419
|
elif trade.days_end() and today == FRIDAY:
|
|
338
|
-
|
|
420
|
+
logmsgif("Week", symbol)
|
|
339
421
|
elif (
|
|
340
422
|
trade.days_end()
|
|
341
423
|
and today == FRIDAY
|
|
342
424
|
and num_days/len(symbols) >= 20
|
|
343
425
|
) and closing:
|
|
344
|
-
|
|
345
|
-
|
|
426
|
+
for id in expert_ids:
|
|
427
|
+
trade.close_positions(
|
|
428
|
+
position_type='all', id=id, comment=comment)
|
|
429
|
+
logmsgif("Month", symbol)
|
|
346
430
|
trade.statistics(save=True)
|
|
431
|
+
strategy.perform_period_end_checks()
|
|
347
432
|
if day_end and today != FRIDAY:
|
|
348
433
|
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
349
434
|
sleepmsg(sleep_time)
|
|
@@ -352,7 +437,7 @@ def _mt5_execution(
|
|
|
352
437
|
num_days += 1
|
|
353
438
|
elif day_end and today == FRIDAY:
|
|
354
439
|
sleep_time = trades_instances[symbols[-1]
|
|
355
|
-
|
|
440
|
+
].sleep_time(weekend=True)
|
|
356
441
|
sleepmsg(sleep_time)
|
|
357
442
|
time.sleep(60 * sleep_time)
|
|
358
443
|
logger.info(sessionmsg)
|
|
@@ -360,10 +445,12 @@ def _mt5_execution(
|
|
|
360
445
|
elif (day_end
|
|
361
446
|
and today == FRIDAY
|
|
362
447
|
and num_days/len(symbols) >= 20
|
|
363
|
-
|
|
448
|
+
):
|
|
364
449
|
break
|
|
365
|
-
except Exception
|
|
366
|
-
logger.error(
|
|
450
|
+
except Exception:
|
|
451
|
+
logger.error(
|
|
452
|
+
f"Handling period end actions, STRATEGY={STRATEGY}")
|
|
453
|
+
traceback.print_exc()
|
|
367
454
|
continue
|
|
368
455
|
|
|
369
456
|
|
|
@@ -389,7 +476,7 @@ class MT5ExecutionEngine():
|
|
|
389
476
|
|
|
390
477
|
Examples
|
|
391
478
|
--------
|
|
392
|
-
|
|
479
|
+
|
|
393
480
|
>>> from bbstrader.metatrader import create_trade_instance
|
|
394
481
|
>>> from bbstrader.trading.execution import MT5ExecutionEngine
|
|
395
482
|
>>> from bbstrader.trading.strategies import StockIndexCFDTrading
|
|
@@ -465,7 +552,7 @@ class MT5ExecutionEngine():
|
|
|
465
552
|
trading_days: Optional[List[str]] = TradingDays,
|
|
466
553
|
comment: Optional[str] = None,
|
|
467
554
|
**kwargs
|
|
468
|
-
|
|
555
|
+
):
|
|
469
556
|
"""
|
|
470
557
|
Args:
|
|
471
558
|
symbol_list : List of symbols to trade
|
|
@@ -496,7 +583,7 @@ class MT5ExecutionEngine():
|
|
|
496
583
|
1. For `trail` , `stop_trail` , `trail_after_points` , `be_plus_points` see `bbstrader.metatrader.trade.Trade.break_even()` .
|
|
497
584
|
2. All Strategies must inherit from `bbstrader.btengine.strategy.Strategy` or `bbstrader.btengine.strategy.MT5Strategy` class
|
|
498
585
|
and have a `calculate_signals` method that returns a dictionary of signals for each symbol in symbol_list.
|
|
499
|
-
|
|
586
|
+
|
|
500
587
|
3. All strategies must have the following arguments in their `__init__` method:
|
|
501
588
|
- bars (DataHandler): DataHandler instance default to None
|
|
502
589
|
- events (Queue): Queue instance default to None
|
|
@@ -506,7 +593,7 @@ class MT5ExecutionEngine():
|
|
|
506
593
|
The keyword arguments are all the additional arguments passed to the `MT5ExecutionEngine` class,
|
|
507
594
|
the `Strategy` class, the `DataHandler` class, the `Portfolio` class and the `ExecutionHandler` class.
|
|
508
595
|
- The `bars` and `events` arguments are used for backtesting only.
|
|
509
|
-
|
|
596
|
+
|
|
510
597
|
4. All strategies must generate signals for backtesting and live trading.
|
|
511
598
|
See the `bbstrader.trading.strategies` module for more information on how to create custom strategies.
|
|
512
599
|
See `bbstrader.metatrader.account.check_mt5_connection()` for more details on how to connect to MT5 terminal.
|
|
@@ -534,7 +621,7 @@ class MT5ExecutionEngine():
|
|
|
534
621
|
trades = self.trades_instances.keys()
|
|
535
622
|
s = self.strategy_cls.__name__
|
|
536
623
|
return f"MT5ExecutionEngine(Symbols={list(trades)}, Strategy={s})"
|
|
537
|
-
|
|
624
|
+
|
|
538
625
|
def run(self):
|
|
539
626
|
check_mt5_connection(**self.kwargs)
|
|
540
627
|
_mt5_execution(
|
|
@@ -559,4 +646,5 @@ class MT5ExecutionEngine():
|
|
|
559
646
|
)
|
|
560
647
|
|
|
561
648
|
|
|
562
|
-
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
|
|