bbstrader 0.1.6__py3-none-any.whl → 0.1.8__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 +0 -1
- bbstrader/btengine/__init__.py +13 -9
- bbstrader/btengine/backtest.py +99 -702
- bbstrader/btengine/data.py +3 -1
- bbstrader/btengine/event.py +15 -9
- bbstrader/btengine/execution.py +66 -6
- bbstrader/btengine/performance.py +32 -20
- bbstrader/btengine/portfolio.py +33 -15
- bbstrader/btengine/strategy.py +1 -2
- bbstrader/metatrader/account.py +15 -8
- bbstrader/metatrader/rates.py +10 -6
- bbstrader/metatrader/risk.py +1 -2
- bbstrader/metatrader/trade.py +307 -239
- bbstrader/metatrader/utils.py +37 -29
- bbstrader/models/risk.py +39 -2
- bbstrader/trading/__init__.py +8 -1
- bbstrader/trading/execution.py +378 -932
- bbstrader/trading/strategies.py +840 -0
- bbstrader/tseries.py +613 -23
- {bbstrader-0.1.6.dist-info → bbstrader-0.1.8.dist-info}/METADATA +16 -7
- bbstrader-0.1.8.dist-info/RECORD +26 -0
- {bbstrader-0.1.6.dist-info → bbstrader-0.1.8.dist-info}/WHEEL +1 -1
- bbstrader/strategies.py +0 -681
- bbstrader/trading/run.py +0 -131
- bbstrader/trading/utils.py +0 -153
- bbstrader-0.1.6.dist-info/RECORD +0 -28
- {bbstrader-0.1.6.dist-info → bbstrader-0.1.8.dist-info}/LICENSE +0 -0
- {bbstrader-0.1.6.dist-info → bbstrader-0.1.8.dist-info}/top_level.txt +0 -0
bbstrader/trading/execution.py
CHANGED
|
@@ -1,977 +1,423 @@
|
|
|
1
1
|
import time
|
|
2
|
-
import pandas as pd
|
|
3
|
-
import numpy as np
|
|
4
2
|
from datetime import datetime
|
|
5
|
-
from bbstrader.metatrader.rates import Rates
|
|
6
3
|
from bbstrader.metatrader.trade import Trade
|
|
7
|
-
from bbstrader.trading.
|
|
8
|
-
from
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from bbstrader.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
to run continuously during market hours. Ensure proper exception handling
|
|
76
|
-
and resource management when integrating into a live trading environment.
|
|
77
|
-
"""
|
|
78
|
-
|
|
79
|
-
def check(buys: list, sells: list):
|
|
80
|
-
if buys is not None or sells is not None:
|
|
81
|
-
logger.info(f"Checking for Break even SYMBOL={trade.symbol}...")
|
|
82
|
-
trade.break_even()
|
|
4
|
+
from bbstrader.trading.strategies import Strategy
|
|
5
|
+
from typing import Optional, Literal, List, Tuple, Dict
|
|
6
|
+
import MetaTrader5 as mt5
|
|
7
|
+
from bbstrader.metatrader.account import INIT_MSG
|
|
8
|
+
from bbstrader.metatrader.utils import raise_mt5_error
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_TF_MAPPING = {
|
|
12
|
+
'1m': 1,
|
|
13
|
+
'3m': 3,
|
|
14
|
+
'5m': 5,
|
|
15
|
+
'10m': 10,
|
|
16
|
+
'15m': 15,
|
|
17
|
+
'30m': 30,
|
|
18
|
+
'1h': 60,
|
|
19
|
+
'2h': 120,
|
|
20
|
+
'4h': 240,
|
|
21
|
+
'D1': 1440
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
TradingDays = [
|
|
25
|
+
'monday',
|
|
26
|
+
'tuesday',
|
|
27
|
+
'wednesday',
|
|
28
|
+
'thursday',
|
|
29
|
+
'friday'
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
def _check_mt5_connection():
|
|
33
|
+
try:
|
|
34
|
+
init = mt5.initialize()
|
|
35
|
+
if not init:
|
|
36
|
+
raise_mt5_error(INIT_MSG)
|
|
37
|
+
except Exception:
|
|
38
|
+
raise_mt5_error(INIT_MSG)
|
|
39
|
+
|
|
40
|
+
def _mt5_execution(
|
|
41
|
+
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,
|
|
44
|
+
comment, **kwargs):
|
|
45
|
+
symbols = symbol_list.copy()
|
|
46
|
+
STRATEGY = kwargs.get('strategy_name')
|
|
47
|
+
_max_trades = kwargs.get('max_trades')
|
|
48
|
+
logger = kwargs.get('logger')
|
|
49
|
+
max_trades = {symbol: _max_trades[symbol] for symbol in symbols}
|
|
50
|
+
if comment is None:
|
|
51
|
+
trade = trades_instances[symbols[0]]
|
|
52
|
+
comment = f"{trade.expert_name}@{trade.version}"
|
|
53
|
+
|
|
54
|
+
def check(buys: List, sells: List, symbol: str):
|
|
55
|
+
if not mm:
|
|
56
|
+
return
|
|
57
|
+
if buys is not None:
|
|
58
|
+
logger.info(
|
|
59
|
+
f"Checking for Break even, SYMBOL={symbol}...STRATEGY={STRATEGY}")
|
|
60
|
+
trades_instances[symbol].break_even(
|
|
61
|
+
mm=mm, trail=trail, stop_trail=stop_trail,
|
|
62
|
+
trail_after_points=trail_after_points, be_plus_points=be_plus_points)
|
|
63
|
+
if sells is not None:
|
|
64
|
+
logger.info(
|
|
65
|
+
f"Checking for Break even, SYMBOL={symbol}...STRATEGY={STRATEGY}")
|
|
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]
|
|
83
72
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
trade_time = trade.get_minutes()
|
|
87
|
-
else:
|
|
88
|
-
trade_time = time_frame_mapping[tf]
|
|
73
|
+
long_market = {symbol: False for symbol in symbols}
|
|
74
|
+
short_market = {symbol: False for symbol in symbols}
|
|
89
75
|
|
|
90
|
-
rate = Rates(trade.symbol, tf, 0)
|
|
91
|
-
data = rate.get_rates_from_pos()
|
|
92
|
-
strategy = SMAStrategy(short_window=sma, long_window=lma)
|
|
93
|
-
hmm = HMMRiskManager(data=data, verbose=True,
|
|
94
|
-
iterations=1000, **kwargs)
|
|
95
|
-
time_intervals = 0
|
|
96
|
-
long_market = False
|
|
97
|
-
short_market = False
|
|
98
|
-
num_days = 0
|
|
99
76
|
logger.info(
|
|
100
|
-
f'Running
|
|
77
|
+
f'Running {STRATEGY} Strategy on {symbols} in {time_frame} Interval ...')
|
|
78
|
+
strategy: Strategy = strategy_cls(
|
|
79
|
+
symbol_list=symbols, mode='live', **kwargs)
|
|
80
|
+
|
|
101
81
|
while True:
|
|
102
|
-
current_date = datetime.now()
|
|
103
|
-
today = current_date.strftime("%A")
|
|
104
82
|
try:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
f"Current buy positions SYMBOL={trade.symbol}: {buys}, STRATEGY=SMA")
|
|
109
|
-
sells = trade.get_current_sells()
|
|
110
|
-
if sells is not None:
|
|
111
|
-
logger.info(
|
|
112
|
-
f"Current sell positions SYMBOL={trade.symbol}: {sells}, STRATEGY=SMA")
|
|
113
|
-
long_market = buys is not None and len(buys) >= max_t
|
|
114
|
-
short_market = sells is not None and len(sells) >= max_t
|
|
115
|
-
|
|
83
|
+
_check_mt5_connection()
|
|
84
|
+
current_date = datetime.now()
|
|
85
|
+
today = current_date.strftime("%A").lower()
|
|
116
86
|
time.sleep(0.5)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if
|
|
127
|
-
logger.info(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
87
|
+
buys = {
|
|
88
|
+
symbol: trades_instances[symbol].get_current_buys()
|
|
89
|
+
for symbol in symbols
|
|
90
|
+
}
|
|
91
|
+
sells = {
|
|
92
|
+
symbol: trades_instances[symbol].get_current_sells()
|
|
93
|
+
for symbol in symbols
|
|
94
|
+
}
|
|
95
|
+
for symbol in symbols:
|
|
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}
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.error(f"{e}, STRATEGY={STRATEGY}")
|
|
108
|
+
time.sleep(0.5)
|
|
109
|
+
for symbol in symbols:
|
|
110
|
+
try:
|
|
111
|
+
trade = trades_instances[symbol]
|
|
112
|
+
logger.info(
|
|
113
|
+
f"Calculating signal... SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
114
|
+
signal = strategy.calculate_signals()[symbol]
|
|
115
|
+
if trade.trading_time() and today in trading_days:
|
|
116
|
+
if signal is not None:
|
|
117
|
+
logger.info(
|
|
118
|
+
f"SIGNAL = {signal}, SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
119
|
+
if signal in ("EXIT", "EXIT_LONG") and long_market[symbol]:
|
|
120
|
+
trade.close_positions(position_type='buy')
|
|
121
|
+
elif signal in ("EXIT", "EXIT_SHORT") and short_market[symbol]:
|
|
122
|
+
trade.close_positions(position_type='sell')
|
|
123
|
+
elif signal == "LONG" and not long_market[symbol]:
|
|
124
|
+
if time_intervals % trade_time == 0 or buys[symbol] is None:
|
|
125
|
+
logger.info(
|
|
126
|
+
f"Sending buy Order ... SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
139
127
|
trade.open_buy_position(mm=mm, comment=comment)
|
|
140
128
|
else:
|
|
141
|
-
check(buys, sells)
|
|
142
|
-
elif signal == "LONG" and long_market:
|
|
143
|
-
logger.info(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if time_intervals % trade_time == 0 or sells is None:
|
|
149
|
-
logger.info(
|
|
129
|
+
check(buys[symbol], sells[symbol], symbol)
|
|
130
|
+
elif signal == "LONG" and long_market[symbol]:
|
|
131
|
+
logger.info(
|
|
132
|
+
f"Sorry Risk not allowed !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
133
|
+
check(buys[symbol], sells[symbol], symbol)
|
|
134
|
+
|
|
135
|
+
elif signal == "SHORT" and not short_market[symbol]:
|
|
136
|
+
if time_intervals % trade_time == 0 or sells[symbol] is None:
|
|
137
|
+
logger.info(
|
|
138
|
+
f"Sending sell Order ... SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
150
139
|
trade.open_sell_position(
|
|
151
140
|
mm=mm, comment=comment)
|
|
152
141
|
else:
|
|
153
|
-
check(buys, sells)
|
|
154
|
-
elif signal == "SHORT" and short_market:
|
|
155
|
-
logger.info(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
logger.info(f"There is no signal !! SYMBOL={trade.symbol}, STRATEGY=SMA")
|
|
159
|
-
check(buys, sells)
|
|
160
|
-
else:
|
|
161
|
-
logger.info(f"Sorry It is Not trading Time !!! SYMBOL={trade.symbol}, STRATEGY=SMA")
|
|
162
|
-
check(buys, sells)
|
|
163
|
-
except Exception as e:
|
|
164
|
-
logger.error(f"{e}, SYMBOL={trade.symbol}, STRATEGY=SMA")
|
|
165
|
-
time.sleep((60 * iter_time) - 1.5)
|
|
166
|
-
if iter_time == 1:
|
|
167
|
-
time_intervals += 1
|
|
168
|
-
elif trade_time % iter_time == 0:
|
|
169
|
-
time_intervals += iter_time
|
|
170
|
-
else:
|
|
171
|
-
raise ValueError(
|
|
172
|
-
f"iter_time must be a multiple of the {tf} !!!"
|
|
173
|
-
f"(e.g; if time_frame is 15m, iter_time must be 1.5, 3, 3, 15 etc)"
|
|
174
|
-
)
|
|
175
|
-
if period.lower() == 'month':
|
|
176
|
-
if trade.days_end() and today != 'Friday':
|
|
177
|
-
sleep_time = trade.sleep_time()
|
|
178
|
-
logger.info(f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY=SMA")
|
|
179
|
-
time.sleep(60 * sleep_time)
|
|
180
|
-
num_days += 1
|
|
181
|
-
|
|
182
|
-
elif trade.days_end() and today == 'Friday':
|
|
183
|
-
logger.info(f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY=SMA")
|
|
184
|
-
sleep_time = trade.sleep_time(weekend=True)
|
|
185
|
-
time.sleep(60 * sleep_time)
|
|
186
|
-
num_days += 1
|
|
187
|
-
|
|
188
|
-
elif (
|
|
189
|
-
trade.days_end()
|
|
190
|
-
and today == 'Friday'
|
|
191
|
-
and num_days >= 20
|
|
192
|
-
):
|
|
193
|
-
trade.close_positions(position_type='all', comment=comment)
|
|
194
|
-
logger.info(f"End of the Month !!! SYMBOL={trade.symbol}, STRATEGY=SMA")
|
|
195
|
-
trade.statistics(save=True)
|
|
196
|
-
break
|
|
197
|
-
|
|
198
|
-
elif period.lower() == 'week':
|
|
199
|
-
if trade.days_end() and today != 'Friday':
|
|
200
|
-
sleep_time = trade.sleep_time()
|
|
201
|
-
time.sleep(60 * sleep_time)
|
|
202
|
-
|
|
203
|
-
elif trade.days_end() and today == 'Friday':
|
|
204
|
-
trade.close_positions(position_type='all', comment=comment)
|
|
205
|
-
logger.info(f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY=SMA")
|
|
206
|
-
trade.statistics(save=True)
|
|
207
|
-
break
|
|
208
|
-
|
|
209
|
-
elif period.lower() == 'day':
|
|
210
|
-
if trade.days_end():
|
|
211
|
-
trade.close_positions(position_type='all', comment=comment)
|
|
212
|
-
logger.info(f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY=SMA")
|
|
213
|
-
trade.statistics(save=True)
|
|
214
|
-
break
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
# ========= PAIR TRADING =====================
|
|
218
|
-
def pair_trading(
|
|
219
|
-
pair: List[str] | Tuple[str],
|
|
220
|
-
p0: Trade,
|
|
221
|
-
p1: Trade,
|
|
222
|
-
tf: str,
|
|
223
|
-
/,
|
|
224
|
-
max_t: Optional[int] = 1,
|
|
225
|
-
mm: Optional[bool] = True,
|
|
226
|
-
iter_time: Optional[int | float] = 30,
|
|
227
|
-
risk_manager: Optional[str] = None,
|
|
228
|
-
rm_ticker: Optional[str] = None,
|
|
229
|
-
rm_window: Optional[int] = None,
|
|
230
|
-
period: Literal['day', 'week', 'month'] = 'month',
|
|
231
|
-
**kwargs
|
|
232
|
-
):
|
|
233
|
-
"""
|
|
234
|
-
Implements a pair trading strategy with optional risk management
|
|
235
|
-
using Hidden Markov Models (HMM). This strategy trades pairs of assets
|
|
236
|
-
based on their historical price relationship, seeking to capitalize on converging prices.
|
|
237
|
-
|
|
238
|
-
:param pair (list[str] | tuple[str]): The trading pair represented as a list or tuple of symbols (e.g., ['AAPL', 'GOOG']).
|
|
239
|
-
:param p0 (Trade): Trade object for the first asset in the pair.
|
|
240
|
-
:param p1 (Trade): Trade object for the second asset in the pair.
|
|
241
|
-
:param tf (str): Time frame for the trading strategy (e.g., '1h' for 1 hour).
|
|
242
|
-
:param max_t (int, optional): Maximum number of trades allowed at any time for each asset in the pair, defaults to 1.
|
|
243
|
-
|
|
244
|
-
:param mm (bool, optional): Money management flag to enable/disable money management, defaults to True.
|
|
245
|
-
:param iter_time (int | float ,optional): Iteration time (in minutes) for the trading loop, defaults to 30.
|
|
246
|
-
:param risk_manager: Specifies the risk management model to use default is None , Hidden Markov Model ('hmm) Can be use.
|
|
247
|
-
:param rm_window: Window size for the risk model use for the prediction, defaults to None. Must be specified if `risk_manager` is not None.
|
|
248
|
-
|
|
249
|
-
:param period (str, optional): Trading period to reset statistics and close positions, can be 'day', 'week', or 'month'.
|
|
250
|
-
:param kwargs: Additional keyword arguments for HMM risk manager.
|
|
251
|
-
|
|
252
|
-
This function continuously evaluates the defined pair for trading opportunities
|
|
253
|
-
based on the strategy logic, taking into account the specified risk management
|
|
254
|
-
approach if applicable. It aims to profit from the mean reversion behavior typically
|
|
255
|
-
observed in closely related financial instruments.
|
|
256
|
-
|
|
257
|
-
Note:
|
|
258
|
-
This function includes an infinite loop with time delays designed to run continuously during market hours.
|
|
259
|
-
Proper exception handling and resource management are crucial for live trading environments.
|
|
260
|
-
"""
|
|
261
|
-
regime = False
|
|
262
|
-
if risk_manager is not None:
|
|
263
|
-
assert rm_ticker is not None
|
|
264
|
-
assert rm_window is not None
|
|
265
|
-
regime = True
|
|
266
|
-
|
|
267
|
-
def p0_check(p0_positions):
|
|
268
|
-
if p0_positions is not None:
|
|
269
|
-
logger.info(f"Checking for breakeven on {pair[0]} positions...STRATEGY=KLF")
|
|
270
|
-
p0.break_even()
|
|
271
|
-
|
|
272
|
-
def p1_check(p1_positions):
|
|
273
|
-
if p1_positions is not None:
|
|
274
|
-
logger.info(f"Checking for breakeven on {pair[1]} positions...STRATEGY=KLF")
|
|
275
|
-
p1.break_even()
|
|
276
|
-
|
|
277
|
-
time_frame_mapping = tf_mapping()
|
|
278
|
-
if tf == 'D1':
|
|
279
|
-
trade_time = p0.get_minutes()
|
|
280
|
-
else:
|
|
281
|
-
trade_time = time_frame_mapping[tf]
|
|
282
|
-
|
|
283
|
-
if regime:
|
|
284
|
-
if risk_manager == 'hmm':
|
|
285
|
-
rate = Rates(rm_ticker, tf, 0)
|
|
286
|
-
data = rate.get_rates_from_pos()
|
|
287
|
-
hmm = HMMRiskManager(data=data, verbose=True, iterations=5000, **kwargs)
|
|
288
|
-
|
|
289
|
-
time_intervals = 0
|
|
290
|
-
long_market = False
|
|
291
|
-
short_market = False
|
|
292
|
-
num_days = 0
|
|
293
|
-
logger.info(
|
|
294
|
-
f'Running KLF Strategy on {pair[0]} and {pair[1]} in {tf} Interval ...\n')
|
|
295
|
-
while True:
|
|
296
|
-
current_date = datetime.now()
|
|
297
|
-
today = current_date.strftime("%A")
|
|
298
|
-
try:
|
|
299
|
-
# Data Retrieval
|
|
300
|
-
p0_ = Rates(pair[0], tf, 0, 10)
|
|
301
|
-
p1_ = Rates(pair[1], tf, 0, 10)
|
|
302
|
-
|
|
303
|
-
p0_data = p0_.get_close
|
|
304
|
-
p1_data = p1_.get_close
|
|
305
|
-
prices = np.array(
|
|
306
|
-
[p0_data.values[-1], p1_data.values[-1]]
|
|
307
|
-
)
|
|
308
|
-
strategy = KLFStrategy(pair)
|
|
309
|
-
if regime:
|
|
310
|
-
if risk_manager == 'hmm':
|
|
311
|
-
hmm_data = Rates(rm_ticker, tf, 0, rm_window)
|
|
312
|
-
returns = hmm_data.get_returns.values
|
|
313
|
-
current_regime = hmm.which_trade_allowed(returns)
|
|
314
|
-
logger.info(f'CURRENT REGIME ={current_regime}, STRATEGY=KLF')
|
|
315
|
-
else:
|
|
316
|
-
current_regime = None
|
|
317
|
-
|
|
318
|
-
p0_positions = p0.get_current_open_positions()
|
|
319
|
-
time.sleep(0.5)
|
|
320
|
-
p1_positions = p1.get_current_open_positions()
|
|
321
|
-
time.sleep(0.5)
|
|
322
|
-
p1_buys = p1.get_current_buys()
|
|
323
|
-
p0_buys = p0.get_current_buys()
|
|
324
|
-
time.sleep(0.5)
|
|
325
|
-
if p1_buys is not None:
|
|
326
|
-
logger.info(f"Current buy positions on {pair[1]}: {p1_buys}, STRATEGY=KLF")
|
|
327
|
-
if p0_buys is not None:
|
|
328
|
-
logger.info(f"Current buy positions on {pair[0]}: {p0_buys}, STRATEGY=KLF")
|
|
329
|
-
time.sleep(0.5)
|
|
330
|
-
p1_sells = p1.get_current_sells()
|
|
331
|
-
p0_sells = p0.get_current_sells()
|
|
332
|
-
time.sleep(0.5)
|
|
333
|
-
if p1_sells is not None:
|
|
334
|
-
logger.info(f"Current sell positions on {pair[1]}: {p1_sells}, STRATEGY=KLF")
|
|
335
|
-
if p0_sells is not None:
|
|
336
|
-
logger.info(f"Current sell positions on {pair[0]}: {p0_sells}, STRATEGY=KLF")
|
|
337
|
-
|
|
338
|
-
p1_long_market = p1_buys is not None and len(p1_buys) >= max_t
|
|
339
|
-
p0_long_market = p0_buys is not None and len(p0_buys) >= max_t
|
|
340
|
-
p1_short_market = p1_sells is not None and len(p1_sells) >= max_t
|
|
341
|
-
p0_short_market = p0_sells is not None and len(p0_sells) >= max_t
|
|
342
|
-
|
|
343
|
-
logger.info(f"Calculating Signals SYMBOL={pair}...STRATEGY=KLF")
|
|
344
|
-
signals = strategy.calculate_signals(prices)
|
|
345
|
-
comment = f"{p0.expert_name}@{p0.version}"
|
|
346
|
-
|
|
347
|
-
if signals is not None:
|
|
348
|
-
logger.info(f'SIGNALS = {signals}, STRATEGY=KLF')
|
|
349
|
-
if p0.trading_time() and today in TRADING_DAYS:
|
|
350
|
-
p1_signal = signals[pair[1]]
|
|
351
|
-
p0_signal = signals[pair[0]]
|
|
352
|
-
if p1_signal == "EXIT" and p0_signal == "EXIT":
|
|
353
|
-
if p1_positions is not None:
|
|
354
|
-
logger.info(f"Exiting Positions On [{pair[1]}], STRATEGY=KLF")
|
|
355
|
-
p1.close_positions(position_type='all', comment=comment)
|
|
356
|
-
p1_long_market = False
|
|
357
|
-
p1_short_market = False
|
|
358
|
-
if p0_positions is not None:
|
|
359
|
-
logger.info(f"Exiting Positions On [{pair[0]}], STRATEGY=KLF")
|
|
360
|
-
p0.close_positions(position_type='all', comment=comment)
|
|
361
|
-
p1_long_market = False
|
|
362
|
-
p1_short_market = False
|
|
363
|
-
if current_regime is not None:
|
|
364
|
-
if (
|
|
365
|
-
p1_signal == "LONG"
|
|
366
|
-
and p0_signal == "SHORT"
|
|
367
|
-
and current_regime == 'LONG'
|
|
368
|
-
):
|
|
369
|
-
if not p1_long_market:
|
|
370
|
-
if time_intervals % trade_time == 0 or p1_buys is None:
|
|
371
|
-
logger.info(f"Going LONG on [{pair[1]}], STRATEGY=KLF")
|
|
372
|
-
p1.open_buy_position(
|
|
373
|
-
mm=mm, comment=comment)
|
|
374
|
-
else:
|
|
375
|
-
p1_check(p1_positions)
|
|
376
|
-
else:
|
|
377
|
-
logger.info(f"Sorry Risk Not allowed on [{pair[1]}], STRATEGY=KLF")
|
|
378
|
-
p1_check(p1_positions)
|
|
379
|
-
|
|
380
|
-
if not p0_short_market:
|
|
381
|
-
if time_intervals % trade_time == 0 or p0_sells is None:
|
|
382
|
-
logger.info(f"Going SHORT on [{pair[0]}]")
|
|
383
|
-
p0.open_sell_position(
|
|
384
|
-
mm=mm, comment=comment)
|
|
385
|
-
else:
|
|
386
|
-
p0_check(p0_positions)
|
|
387
|
-
else:
|
|
388
|
-
logger.info(
|
|
389
|
-
f"Sorry Risk Not allowed on [{pair[0]}], STRATEGY=KLF")
|
|
390
|
-
p0_check(p0_positions)
|
|
391
|
-
elif (
|
|
392
|
-
p1_signal == "SHORT"
|
|
393
|
-
and p0_signal == "LONG"
|
|
394
|
-
and current_regime == 'SHORT'
|
|
395
|
-
):
|
|
396
|
-
if not p1_short_market:
|
|
397
|
-
if time_intervals % trade_time == 0 or p1_sells is None:
|
|
398
|
-
logger.info(f"Going SHORT on [{pair[1]}], STRATEGY=KLF")
|
|
399
|
-
p1.open_sell_position(
|
|
400
|
-
mm=mm, comment=comment)
|
|
401
|
-
else:
|
|
402
|
-
p1_check(p1_positions)
|
|
403
|
-
else:
|
|
404
|
-
logger.info(f"Sorry Risk Not allowed on [{pair[1]}], STRATEGY=KLF")
|
|
405
|
-
p1_check(p1_positions)
|
|
406
|
-
|
|
407
|
-
if not p0_long_market:
|
|
408
|
-
if time_intervals % trade_time == 0 or p0_buys is None:
|
|
409
|
-
logger.info(f"Going LONG on [{pair[0]}], STRATEGY=KLF")
|
|
410
|
-
p0.open_buy_position(
|
|
411
|
-
mm=mm, comment=comment)
|
|
412
|
-
else:
|
|
413
|
-
p0_check(p0_positions)
|
|
414
|
-
else:
|
|
415
|
-
logger.info(
|
|
416
|
-
f"Sorry Risk Not allowed on [{pair[0]}], STRATEGY=KLF")
|
|
417
|
-
p0_check(p0_positions)
|
|
142
|
+
check(buys[symbol], sells[symbol], symbol)
|
|
143
|
+
elif signal == "SHORT" and short_market[symbol]:
|
|
144
|
+
logger.info(
|
|
145
|
+
f"Sorry Risk not allowed !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
146
|
+
check(buys[symbol], sells[symbol], symbol)
|
|
418
147
|
else:
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
):
|
|
423
|
-
if not p1_long_market:
|
|
424
|
-
if time_intervals % trade_time == 0 or p1_buys is None:
|
|
425
|
-
logger.info(f"Going LONG on [{pair[1]}], STRATEGY=KLF")
|
|
426
|
-
p1.open_buy_position(
|
|
427
|
-
mm=mm, comment=comment)
|
|
428
|
-
else:
|
|
429
|
-
p1_check(p1_positions)
|
|
430
|
-
else:
|
|
431
|
-
logger.info(f"Sorry Risk Not allowed on [{pair[1]}], STRATEGY=KLF")
|
|
432
|
-
p1_check(p1_positions)
|
|
433
|
-
|
|
434
|
-
if not p0_short_market:
|
|
435
|
-
if time_intervals % trade_time == 0 or p0_sells is None:
|
|
436
|
-
logger.info(f"Going SHORT on [{pair[0]}], STRATEGY=KLF")
|
|
437
|
-
p0.open_sell_position(
|
|
438
|
-
mm=mm, comment=comment)
|
|
439
|
-
else:
|
|
440
|
-
p0_check(p0_positions)
|
|
441
|
-
else:
|
|
442
|
-
logger.info(
|
|
443
|
-
f"Sorry Risk Not allowed on [{pair[0]}], STRATEGY=KLF")
|
|
444
|
-
p0_check(p0_positions)
|
|
445
|
-
elif (
|
|
446
|
-
p1_signal == "SHORT"
|
|
447
|
-
and p0_signal == "LONG"
|
|
448
|
-
):
|
|
449
|
-
if not p1_short_market:
|
|
450
|
-
if time_intervals % trade_time == 0 or p1_sells is None:
|
|
451
|
-
logger.info(f"Going SHORT on [{pair[1]}], STRATEGY=KLF")
|
|
452
|
-
p1.open_sell_position(
|
|
453
|
-
mm=mm, comment=comment)
|
|
454
|
-
else:
|
|
455
|
-
p1_check(p1_positions)
|
|
456
|
-
else:
|
|
457
|
-
logger.info(f"Sorry Risk Not allowed on [{pair[1]}], STRATEGY=KLF")
|
|
458
|
-
p1_check(p1_positions)
|
|
459
|
-
|
|
460
|
-
if not p0_long_market:
|
|
461
|
-
if time_intervals % trade_time == 0 or p0_buys is None:
|
|
462
|
-
logger.info(f"Going LONG on [{pair[0]}], STRATEGY=KLF")
|
|
463
|
-
p0.open_buy_position(
|
|
464
|
-
mm=mm, comment=comment)
|
|
465
|
-
else:
|
|
466
|
-
p0_check(p0_positions)
|
|
467
|
-
else:
|
|
468
|
-
logger.info(
|
|
469
|
-
f"Sorry Risk Not allowed on [{pair[0]}], STRATEGY=KLF")
|
|
470
|
-
p0_check(p0_positions)
|
|
148
|
+
logger.info(
|
|
149
|
+
f"There is no signal !! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
150
|
+
check(buys[symbol], sells[symbol], symbol)
|
|
471
151
|
else:
|
|
472
152
|
logger.info(
|
|
473
|
-
f"It is Not trading
|
|
474
|
-
|
|
475
|
-
p1_check(p1_positions)
|
|
476
|
-
else:
|
|
477
|
-
logger.info(
|
|
478
|
-
f"There is no signal !!! STRATEGY=KLF, SYMBOLS={pair}")
|
|
479
|
-
|
|
480
|
-
p0_check(p0_positions)
|
|
481
|
-
p1_check(p1_positions)
|
|
153
|
+
f"Sorry It is Not trading Time !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
154
|
+
check(buys[symbol], sells[symbol], symbol)
|
|
482
155
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
time.sleep((60 * iter_time) - 2.5)
|
|
156
|
+
except Exception as e:
|
|
157
|
+
logger.error(f"{e}, SYMBOL={symbol}, STRATEGY={STRATEGY}")
|
|
487
158
|
|
|
159
|
+
time.sleep((60 * iter_time) - 1.0)
|
|
488
160
|
if iter_time == 1:
|
|
489
161
|
time_intervals += 1
|
|
490
162
|
elif trade_time % iter_time == 0:
|
|
491
163
|
time_intervals += iter_time
|
|
492
164
|
else:
|
|
493
165
|
raise ValueError(
|
|
494
|
-
f"iter_time must be a multiple of the {
|
|
166
|
+
f"iter_time must be a multiple of the {time_frame} !!!"
|
|
495
167
|
f"(e.g; if time_frame is 15m, iter_time must be 1.5, 3, 3, 15 etc)"
|
|
496
168
|
)
|
|
497
|
-
|
|
498
|
-
if period.lower() == '
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
elif (
|
|
515
|
-
p0.days_end()
|
|
516
|
-
and today == 'Friday'
|
|
517
|
-
and num_days >= 20
|
|
518
|
-
):
|
|
519
|
-
p0.close_positions(position_type='all', comment=comment)
|
|
520
|
-
p1.close_positions(position_type='all', comment=comment)
|
|
521
|
-
logger.info(
|
|
522
|
-
f"End of the Month !!! STRATEGY=KLF, SYMBOLS={pair}")
|
|
523
|
-
p0.statistics(save=True)
|
|
524
|
-
p1.statistics(save=True)
|
|
525
|
-
break
|
|
169
|
+
print()
|
|
170
|
+
if period.lower() == 'day':
|
|
171
|
+
for symbol in symbols:
|
|
172
|
+
trade = trades_instances[symbol]
|
|
173
|
+
if trade.days_end():
|
|
174
|
+
trade.close_positions(position_type='all', comment=comment)
|
|
175
|
+
logger.info(
|
|
176
|
+
f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
177
|
+
trade.statistics(save=True)
|
|
178
|
+
if trades_instances[symbols[-1]].days_end():
|
|
179
|
+
if period_end_action == 'break':
|
|
180
|
+
break
|
|
181
|
+
elif period_end_action == 'sleep':
|
|
182
|
+
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
183
|
+
logger.info(f"Sleeping for {sleep_time} minutes ...")
|
|
184
|
+
time.sleep(60 * sleep_time)
|
|
185
|
+
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
526
186
|
|
|
527
187
|
elif period.lower() == 'week':
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
elif p0.days_end() and today == 'Friday':
|
|
533
|
-
p0.close_positions(position_type='all', comment=comment)
|
|
534
|
-
p1.close_positions(position_type='all', comment=comment)
|
|
535
|
-
logger.info(
|
|
536
|
-
f"End of the Week !!! STRATEGY=KLF, SYMBOLS={pair}")
|
|
537
|
-
p0.statistics(save=True)
|
|
538
|
-
p1.statistics(save=True)
|
|
539
|
-
break
|
|
540
|
-
|
|
541
|
-
elif period.lower() == 'day':
|
|
542
|
-
if p0.days_end():
|
|
543
|
-
p0.close_positions(position_type='all', comment=comment)
|
|
544
|
-
p1.close_positions(position_type='all', comment=comment)
|
|
545
|
-
logger.info(
|
|
546
|
-
f"End of the Day !!! STRATEGY=KLF, SYMBOLS={pair}")
|
|
547
|
-
p0.statistics(save=True)
|
|
548
|
-
p1.statistics(save=True)
|
|
549
|
-
break
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
# ========= ORNSTEIN UHLENBECK TRADING ========
|
|
553
|
-
def ou_trading(
|
|
554
|
-
trade: Trade,
|
|
555
|
-
tf: Optional[str] = '1h',
|
|
556
|
-
p: Optional[int] = 20,
|
|
557
|
-
n: Optional[int] = 20,
|
|
558
|
-
ou_window: Optional[int] = 2000,
|
|
559
|
-
max_t: Optional[int] = 1,
|
|
560
|
-
mm: Optional[bool] = True,
|
|
561
|
-
iter_time: Optional[int | float] = 30,
|
|
562
|
-
risk_manager: Optional[str] = None,
|
|
563
|
-
rm_window: Optional[int] = None,
|
|
564
|
-
period: Literal['day', 'week', 'month'] = 'month',
|
|
565
|
-
**kwargs
|
|
566
|
-
):
|
|
567
|
-
"""
|
|
568
|
-
Executes the Ornstein-Uhlenbeck (OU) trading strategy,
|
|
569
|
-
incorporating various risk management and trading frequency adjustments.
|
|
570
|
-
|
|
571
|
-
:param trade: A `Trade` instance, containing methods and attributes for executing trades.
|
|
572
|
-
:param tf: Time frame for the trading strategy, default is '1h'.
|
|
573
|
-
:param mm: Boolean indicating if money management is enabled, default is True.
|
|
574
|
-
:param max_t: Maximum number of trades allowed at any given time, default is 1.
|
|
575
|
-
:param p: Period length for calculating returns, default is 20.
|
|
576
|
-
:param n: Window size for the Ornstein-Uhlenbeck strategy calculation, default is 20.
|
|
577
|
-
:param iter_time: Iteration time for the trading loop, can be an integer or float.
|
|
578
|
-
:param ou_window: Lookback period for the OU strategy, defaults to 2000.
|
|
579
|
-
:param risk_manager: Specifies the risk management model to use
|
|
580
|
-
default is None , Hidden Markov Model ('hmm) Can be use.
|
|
581
|
-
:param rm_window: Window size for the risk model use for the prediction, defaults to None.
|
|
582
|
-
Must be specified if `risk_manager` is not None.
|
|
583
|
-
:param period: Defines the trading period as 'month', 'week', or 'day'
|
|
584
|
-
affecting how and when positions are closed.
|
|
585
|
-
:param kwargs: Additional keyword arguments for risk management models or other customizations.
|
|
586
|
-
|
|
587
|
-
This function manages trading based on the OU strategy, adjusting for risk and time-based criteria.
|
|
588
|
-
It includes handling of trading sessions, buy/sell signal generation, risk management through the HMM model, and period-based
|
|
589
|
-
trading evaluation.
|
|
590
|
-
"""
|
|
591
|
-
regime = False
|
|
592
|
-
if risk_manager is not None:
|
|
593
|
-
if risk_manager.lower() == 'hmm':
|
|
594
|
-
assert rm_window is not None
|
|
595
|
-
regime = True
|
|
596
|
-
|
|
597
|
-
rate = Rates(trade.symbol, tf, 0)
|
|
598
|
-
data = rate.get_rates_from_pos()
|
|
599
|
-
def check(buys: list, sells: list):
|
|
600
|
-
if buys is not None or sells is not None:
|
|
601
|
-
logger.info(f"Checking for Break even on {trade.symbol}... STRATEGY=OU")
|
|
602
|
-
trade.break_even()
|
|
603
|
-
|
|
604
|
-
time_frame_mapping = tf_mapping()
|
|
605
|
-
if tf == 'D1':
|
|
606
|
-
trade_time = trade.get_minutes()
|
|
607
|
-
else:
|
|
608
|
-
trade_time = time_frame_mapping[tf]
|
|
609
|
-
|
|
610
|
-
if regime:
|
|
611
|
-
if risk_manager == 'hmm':
|
|
612
|
-
hmm = HMMRiskManager(data=data, verbose=True, **kwargs)
|
|
613
|
-
strategy = OrnsteinUhlenbeck(data['Close'].values[-ou_window:], timeframe=tf)
|
|
614
|
-
|
|
615
|
-
time_intervals = 0
|
|
616
|
-
long_market = False
|
|
617
|
-
short_market = False
|
|
618
|
-
num_days = 0
|
|
619
|
-
logger.info(f'Running OU Strategy on {trade.symbol} in {tf} Interval ...\n')
|
|
620
|
-
while True:
|
|
621
|
-
current_date = datetime.now()
|
|
622
|
-
today = current_date.strftime("%A")
|
|
623
|
-
try:
|
|
624
|
-
buys = trade.get_current_buys()
|
|
625
|
-
if buys is not None:
|
|
626
|
-
logger.info(f"Current buy positions on {trade.symbol}: {buys}, STRATEGY=OU")
|
|
627
|
-
sells = trade.get_current_sells()
|
|
628
|
-
if sells is not None:
|
|
629
|
-
logger.info(f"Current sell positions on {trade.symbol}: {sells}, STRATEGY=OU")
|
|
630
|
-
long_market = buys is not None and len(buys) >= max_t
|
|
631
|
-
short_market = sells is not None and len(sells) >= max_t
|
|
632
|
-
|
|
633
|
-
time.sleep(0.5)
|
|
634
|
-
if regime:
|
|
635
|
-
if risk_manager == 'hmm':
|
|
636
|
-
hmm_returns = Rates(trade.symbol, tf, 0, rm_window)
|
|
637
|
-
hmm_returns_val = hmm_returns.get_returns.values
|
|
638
|
-
current_regime = hmm.which_trade_allowed(hmm_returns_val)
|
|
188
|
+
for symbol in symbols:
|
|
189
|
+
trade = trades_instances[symbol]
|
|
190
|
+
if trade.days_end() and today != 'friday':
|
|
639
191
|
logger.info(
|
|
640
|
-
f
|
|
641
|
-
else:
|
|
642
|
-
current_regime = None
|
|
643
|
-
logger.info(f"Calculating signal... SYMBOL={trade.symbol}, STRATEGY=OU")
|
|
644
|
-
ou_returns = Rates(trade.symbol, tf, 0, p)
|
|
645
|
-
returns_val = ou_returns.get_returns.values
|
|
646
|
-
signal = strategy.calculate_signals(returns_val, p=p, n=n)
|
|
647
|
-
comment = f"{trade.expert_name}@{trade.version}"
|
|
648
|
-
if trade.trading_time() and today in TRADING_DAYS:
|
|
649
|
-
if signal is not None:
|
|
650
|
-
logger.info(f"SIGNAL = {signal}, SYMBOL={trade.symbol}, STRATEGY=OU")
|
|
651
|
-
if signal == "LONG" and short_market:
|
|
652
|
-
trade.close_positions(position_type='sell')
|
|
653
|
-
short_market = False
|
|
654
|
-
elif signal == "SHORT" and long_market:
|
|
655
|
-
trade.close_positions(position_type='buy')
|
|
656
|
-
long_market = False
|
|
657
|
-
if current_regime is not None:
|
|
658
|
-
if current_regime == 'LONG':
|
|
659
|
-
if signal == "LONG" and not long_market:
|
|
660
|
-
if time_intervals % trade_time == 0 or buys is None:
|
|
661
|
-
logger.info(f"Sending buy Order .... SYMBOL={trade.symbol}, STRATEGY=OU")
|
|
662
|
-
trade.open_buy_position(
|
|
663
|
-
mm=mm, comment=comment)
|
|
664
|
-
else:
|
|
665
|
-
check(buys, sells)
|
|
666
|
-
|
|
667
|
-
elif signal == "LONG" and long_market:
|
|
668
|
-
logger.info(f"Sorry Risk not allowed !!! SYMBOL={trade.symbol}, STRATEGY=OU")
|
|
669
|
-
check(buys, sells)
|
|
192
|
+
f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
670
193
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
elif signal == "SHORT" and short_market:
|
|
680
|
-
logger.info(f"Sorry Risk not Allowed !!! SYMBOL={trade.symbol}, STRATEGY=OU")
|
|
681
|
-
check(buys, sells)
|
|
682
|
-
else:
|
|
683
|
-
if signal == "LONG" and not long_market:
|
|
684
|
-
if time_intervals % trade_time == 0 or buys is None:
|
|
685
|
-
logger.info(f"Sending buy Order .... SYMBOL={trade.symbol}, STRATEGY=OU")
|
|
686
|
-
trade.open_buy_position(mm=mm, comment=comment)
|
|
687
|
-
else:
|
|
688
|
-
check(buys, sells)
|
|
689
|
-
|
|
690
|
-
elif signal == "LONG" and long_market:
|
|
691
|
-
logger.info(f"Sorry Risk not allowed !!! SYMBOL={trade.symbol}, STRATEGY=OU")
|
|
692
|
-
check(buys, sells)
|
|
693
|
-
|
|
694
|
-
if signal == "SHORT" and not short_market:
|
|
695
|
-
if time_intervals % trade_time == 0 or sells is None:
|
|
696
|
-
logger.info(f"Sending Sell Order .... SYMBOL={trade.symbol}, STRATEGY=OU")
|
|
697
|
-
trade.open_sell_position(
|
|
698
|
-
mm=mm, comment=comment)
|
|
699
|
-
else:
|
|
700
|
-
check(buys, sells)
|
|
701
|
-
elif signal == "SHORT" and short_market:
|
|
702
|
-
logger.info(f"Sorry Risk not Allowed !!! SYMBOL={trade.symbol}, STRATEGY=OU")
|
|
703
|
-
check(buys, sells)
|
|
704
|
-
else:
|
|
705
|
-
logger.info(f"There is no signal !!! SYMBOL={trade.symbol}, STRATEGY=OU")
|
|
706
|
-
check(buys, sells)
|
|
707
|
-
else:
|
|
708
|
-
logger.info(f"Sorry It is Not trading Time !!! SYMBOL={trade.symbol}, STRATEGY=OU")
|
|
709
|
-
check(buys, sells)
|
|
710
|
-
except Exception as e:
|
|
711
|
-
print(f"{e}, SYMBOL={trade.symbol}, STRATEGY=OU")
|
|
712
|
-
time.sleep((60 * iter_time) - 1.5)
|
|
713
|
-
if iter_time == 1:
|
|
714
|
-
time_intervals += 1
|
|
715
|
-
elif trade_time % iter_time == 0:
|
|
716
|
-
time_intervals += iter_time
|
|
717
|
-
else:
|
|
718
|
-
raise ValueError(
|
|
719
|
-
f"iter_time must be a multiple of the {tf} !!!"
|
|
720
|
-
f"(e.g; if time_frame is 15m, iter_time must be 1.5, 3, 3, 15 etc)"
|
|
721
|
-
)
|
|
722
|
-
|
|
723
|
-
if period.lower() == 'month':
|
|
724
|
-
if trade.days_end() and today != 'Friday':
|
|
725
|
-
logger.info(f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY=OU")
|
|
726
|
-
sleep_time = trade.sleep_time()
|
|
194
|
+
elif trade.days_end() and today == 'friday':
|
|
195
|
+
trade.close_positions(position_type='all', comment=comment)
|
|
196
|
+
logger.info(
|
|
197
|
+
f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
198
|
+
trade.statistics(save=True)
|
|
199
|
+
if trades_instances[symbols[-1]].days_end() and today != 'friday':
|
|
200
|
+
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
201
|
+
logger.info(f"Sleeping for {sleep_time} minutes ...")
|
|
727
202
|
time.sleep(60 * sleep_time)
|
|
728
|
-
|
|
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 ...")
|
|
210
|
+
time.sleep(60 * sleep_time)
|
|
211
|
+
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
212
|
+
|
|
213
|
+
elif period.lower() == 'month':
|
|
214
|
+
for symbol in symbols:
|
|
215
|
+
trade = trades_instances[symbol]
|
|
216
|
+
if trade.days_end() and today != 'friday':
|
|
217
|
+
logger.info(
|
|
218
|
+
f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
729
219
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
220
|
+
elif trade.days_end() and today == 'friday':
|
|
221
|
+
logger.info(
|
|
222
|
+
f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
223
|
+
elif (
|
|
224
|
+
trade.days_end()
|
|
225
|
+
and today == 'friday'
|
|
226
|
+
and num_days/len(symbols) >= 20
|
|
227
|
+
):
|
|
228
|
+
trade.close_positions(position_type='all', comment=comment)
|
|
229
|
+
logger.info(
|
|
230
|
+
f"End of the Month !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
231
|
+
trade.statistics(save=True)
|
|
232
|
+
if trades_instances[symbols[-1]].days_end() and today != 'friday':
|
|
233
|
+
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
234
|
+
logger.info(f"Sleeping for {sleep_time} minutes ...")
|
|
733
235
|
time.sleep(60 * sleep_time)
|
|
236
|
+
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
734
237
|
num_days += 1
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
and num_days >= 20
|
|
740
|
-
):
|
|
741
|
-
trade.close_positions(position_type='all', comment=comment)
|
|
742
|
-
logger.info(f"End of the Month !!! SYMBOL={trade.symbol}, STRATEGY=OU")
|
|
743
|
-
trade.statistics(save=True)
|
|
744
|
-
break
|
|
745
|
-
|
|
746
|
-
elif period.lower() == 'week':
|
|
747
|
-
if trade.days_end() and today != 'Friday':
|
|
748
|
-
sleep_time = trade.sleep_time()
|
|
238
|
+
elif trades_instances[symbols[-1]].days_end() and today == 'friday':
|
|
239
|
+
sleep_time = trades_instances[symbols[-1]
|
|
240
|
+
].sleep_time(weekend=True)
|
|
241
|
+
logger.info(f"Sleeping for {sleep_time} minutes ...")
|
|
749
242
|
time.sleep(60 * sleep_time)
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
243
|
+
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
244
|
+
num_days += 1
|
|
245
|
+
elif (trades_instances[symbols[-1]].days_end()
|
|
246
|
+
and today == 'friday'
|
|
247
|
+
and num_days/len(symbols) >= 20
|
|
248
|
+
):
|
|
755
249
|
break
|
|
756
250
|
|
|
757
|
-
elif period.lower() == 'day':
|
|
758
|
-
if trade.days_end():
|
|
759
|
-
trade.close_positions(position_type='all', comment=comment)
|
|
760
|
-
logger.info(f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY=OU")
|
|
761
|
-
trade.statistics(save=True)
|
|
762
|
-
break
|
|
763
251
|
|
|
252
|
+
def _tws_execution(*args, **kwargs):
|
|
253
|
+
raise NotImplementedError("TWS Execution is not yet implemented !!!")
|
|
764
254
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
max_t: Optional[int] = 1,
|
|
771
|
-
mm: Optional[bool] = True,
|
|
772
|
-
iter_time: Optional[int | float] = 30,
|
|
773
|
-
risk_manager: Optional[str] = None,
|
|
774
|
-
rm_window: Optional[int] = None,
|
|
775
|
-
period: Literal['day', 'week', 'month'] = 'month',
|
|
776
|
-
**kwargs
|
|
777
|
-
):
|
|
255
|
+
_TERMINALS = {
|
|
256
|
+
'MT5': _mt5_execution,
|
|
257
|
+
'TWS': _tws_execution
|
|
258
|
+
}
|
|
259
|
+
class ExecutionEngine():
|
|
778
260
|
"""
|
|
779
|
-
|
|
780
|
-
the
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
261
|
+
The `ExecutionEngine` class serves as the central hub for executing your trading strategies within the `bbstrader` framework.
|
|
262
|
+
It orchestrates the entire trading process, ensuring seamless interaction between your strategies, market data, and your chosen
|
|
263
|
+
trading platform (currently MetaTrader 5 (MT5) and Interactive Brokers TWS).
|
|
264
|
+
|
|
265
|
+
Key Features
|
|
266
|
+
------------
|
|
267
|
+
|
|
268
|
+
- **Strategy Execution:** The `ExecutionEngine` is responsible for running your strategy, retrieving signals, and executing trades based on those signals.
|
|
269
|
+
- **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
|
+
- **Trade Period Control:** Define whether your strategy runs for a day, a week, or a month, allowing for flexible trading durations.
|
|
271
|
+
- **Money Management:** The engine supports optional money management features, allowing you to control risk and optimize your trading performance.
|
|
272
|
+
- **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 `ExecutionEngine` is currently designed to work with both MT5 and TWS platforms, ensuring compatibility and flexibility in your trading environment.
|
|
274
|
+
|
|
275
|
+
Examples
|
|
276
|
+
--------
|
|
277
|
+
|
|
278
|
+
>>> from bbstrader.metatrader import create_trade_instance
|
|
279
|
+
>>> from bbstrader.trading.execution import ExecutionEngine
|
|
280
|
+
>>> from bbstrader.trading.strategies import StockIndexCFDTrading
|
|
281
|
+
>>> from bbstrader.metatrader.utils import config_logger
|
|
282
|
+
>>>
|
|
283
|
+
>>> if __name__ == '__main__':
|
|
284
|
+
>>> logger = config_logger(index_trade.log, console_log=True)
|
|
285
|
+
>>> # Define symbols
|
|
286
|
+
>>> ndx = '[NQ100]'
|
|
287
|
+
>>> spx = '[SP500]'
|
|
288
|
+
>>> dji = '[DJI30]'
|
|
289
|
+
>>> dax = 'GERMANY40'
|
|
290
|
+
>>>
|
|
291
|
+
>>> symbol_list = [spx, dax, dji, ndx]
|
|
292
|
+
>>>
|
|
293
|
+
>>> trade_kwargs = {
|
|
294
|
+
... 'expert_id': 5134,
|
|
295
|
+
... 'version': 2.0,
|
|
296
|
+
... 'time_frame': '15m',
|
|
297
|
+
... 'var_level': 0.99,
|
|
298
|
+
... 'start_time': '8:30',
|
|
299
|
+
... 'finishing_time': '19:30',
|
|
300
|
+
... 'ending_time': '21:30',
|
|
301
|
+
... 'max_risk': 5.0,
|
|
302
|
+
... 'daily_risk': 0.10,
|
|
303
|
+
... 'pchange_sl': 1.5,
|
|
304
|
+
... 'rr': 3.0,
|
|
305
|
+
... 'logger': logger
|
|
306
|
+
... }
|
|
307
|
+
>>> strategy_kwargs = {
|
|
308
|
+
... 'max_trades': {ndx: 3, spx: 3, dji: 3, dax: 3},
|
|
309
|
+
... 'expected_returns': {ndx: 1.5, spx: 1.5, dji: 1.0, dax: 1.0},
|
|
310
|
+
... 'strategy_name': 'SISTBO',
|
|
311
|
+
... 'logger': logger,
|
|
312
|
+
... 'expert_id': 5134
|
|
313
|
+
... }
|
|
314
|
+
>>> trades_instances = create_trade_instance(
|
|
315
|
+
... symbol_list, trade_kwargs,
|
|
316
|
+
... logger=logger,
|
|
317
|
+
... )
|
|
318
|
+
>>>
|
|
319
|
+
>>> engine = ExecutionEngine(
|
|
320
|
+
... symbol_list,
|
|
321
|
+
... trades_instances,
|
|
322
|
+
... StockIndexCFDTrading,
|
|
323
|
+
... time_frame='15m',
|
|
324
|
+
... iter_time=5,
|
|
325
|
+
... mm=True,
|
|
326
|
+
... period='week',
|
|
327
|
+
... comment='bbs_SISTBO_@2.0',
|
|
328
|
+
... **strategy_kwargs
|
|
329
|
+
... )
|
|
330
|
+
>>> engine.run(terminal='MT5')
|
|
799
331
|
"""
|
|
800
|
-
regime = False
|
|
801
|
-
if risk_manager is not None:
|
|
802
|
-
if risk_manager.lower() == 'hmm':
|
|
803
|
-
assert rm_window is not None
|
|
804
|
-
regime = True
|
|
805
|
-
|
|
806
|
-
def check(buys: list, sells: list):
|
|
807
|
-
if buys is not None or sells is not None:
|
|
808
|
-
logger.info(f"Checking for Break even on {trade.symbol}...")
|
|
809
|
-
trade.break_even()
|
|
810
|
-
|
|
811
|
-
time_frame_mapping = tf_mapping()
|
|
812
|
-
if tf == 'D1':
|
|
813
|
-
trade_time = trade.get_minutes()
|
|
814
|
-
else:
|
|
815
|
-
trade_time = time_frame_mapping[tf]
|
|
816
|
-
|
|
817
|
-
rate = Rates(trade.symbol, tf, 0)
|
|
818
|
-
data = rate.get_rates_from_pos()
|
|
819
|
-
strategy = ArimaGarchStrategy(trade.symbol, data, k=k)
|
|
820
|
-
if regime:
|
|
821
|
-
if risk_manager == 'hmm':
|
|
822
|
-
hmm = HMMRiskManager(data=data, verbose=True, iterations=5000, **kwargs)
|
|
823
|
-
|
|
824
|
-
time_intervals = 0
|
|
825
|
-
long_market = False
|
|
826
|
-
short_market = False
|
|
827
|
-
num_days = 0
|
|
828
|
-
logger.info(
|
|
829
|
-
f'Running ARIMA + GARCH Strategy on {trade.symbol} in {tf} Interval ...\n')
|
|
830
|
-
while True:
|
|
831
|
-
current_date = datetime.now()
|
|
832
|
-
today = current_date.strftime("%A")
|
|
833
|
-
try:
|
|
834
|
-
buys = trade.get_current_buys()
|
|
835
|
-
if buys is not None:
|
|
836
|
-
logger.info(f"Current buy positions on {trade.symbol}: {buys}, STRATEGY=ARCH")
|
|
837
|
-
sells = trade.get_current_sells()
|
|
838
|
-
if sells is not None:
|
|
839
|
-
logger.info(f"Current sell positions on {trade.symbol}: {sells}, STRATEGY=ARCH")
|
|
840
|
-
long_market = buys is not None and len(buys) >= max_t
|
|
841
|
-
short_market = sells is not None and len(sells) >= max_t
|
|
842
|
-
|
|
843
|
-
time.sleep(0.5)
|
|
844
|
-
if regime:
|
|
845
|
-
if risk_manager == 'hmm':
|
|
846
|
-
hmm_returns = Rates(trade.symbol, tf, 0, rm_window)
|
|
847
|
-
hmm_returns_val = hmm_returns.get_returns.values
|
|
848
|
-
current_regime = hmm.which_trade_allowed(hmm_returns_val)
|
|
849
|
-
logger.info(f'CURRENT REGIME = {current_regime}, SYMBOL={trade.symbol}, STRATEGY=ARCH')
|
|
850
|
-
else:
|
|
851
|
-
current_regime = None
|
|
852
|
-
logger.info(f"Calculating Signal ... SYMBOL={trade.symbol}, STRATEGY=ARCH")
|
|
853
|
-
arch_data = Rates(trade.symbol, tf, 0, k)
|
|
854
|
-
rates = arch_data.get_rates_from_pos()
|
|
855
|
-
arch_returns = strategy.load_and_prepare_data(rates)
|
|
856
|
-
window_data = arch_returns['diff_log_return'].iloc[-k:]
|
|
857
|
-
signal = strategy.calculate_signals(window_data)
|
|
858
|
-
|
|
859
|
-
comment = f"{trade.expert_name}@{trade.version}"
|
|
860
|
-
if trade.trading_time() and today in TRADING_DAYS:
|
|
861
|
-
if signal is not None:
|
|
862
|
-
logger.info(f"SIGNAL = {signal}, SYMBOL={trade.symbol}, STRATEGY=ARCH")
|
|
863
|
-
if signal == "LONG" and short_market:
|
|
864
|
-
trade.close_positions(position_type='sell')
|
|
865
|
-
short_market = False
|
|
866
|
-
elif signal == "SHORT" and long_market:
|
|
867
|
-
trade.close_positions(position_type='buy')
|
|
868
|
-
long_market = False
|
|
869
|
-
if current_regime is not None:
|
|
870
|
-
if current_regime == 'LONG':
|
|
871
|
-
if signal == "LONG" and not long_market:
|
|
872
|
-
if time_intervals % trade_time == 0 or buys is None:
|
|
873
|
-
logger.info(f"Sending buy Order .... SYMBOL={trade.symbol}, STRATEGY=ARCH")
|
|
874
|
-
trade.open_buy_position(
|
|
875
|
-
mm=mm, comment=comment)
|
|
876
|
-
else:
|
|
877
|
-
check(buys, sells)
|
|
878
|
-
|
|
879
|
-
elif signal == "LONG" and long_market:
|
|
880
|
-
logger.info(f"Sorry Risk not allowed !!! SYMBOL={trade.symbol}, STRATEGY=ARCH")
|
|
881
|
-
check(buys, sells)
|
|
882
|
-
|
|
883
|
-
elif current_regime == 'SHORT':
|
|
884
|
-
if signal == "SHORT" and not short_market:
|
|
885
|
-
if time_intervals % trade_time == 0 or sells is None:
|
|
886
|
-
logger.info(f"Sending Sell Order .... SYMBOL={trade.symbol}, STRATEGY=ARCH")
|
|
887
|
-
trade.open_sell_position(
|
|
888
|
-
mm=mm, comment=comment)
|
|
889
|
-
else:
|
|
890
|
-
check(buys, sells)
|
|
891
|
-
elif signal == "SHORT" and short_market:
|
|
892
|
-
logger.info(f"Sorry Risk not Allowed !!! SYMBOL={trade.symbol}, STRATEGY=ARCH")
|
|
893
|
-
check(buys, sells)
|
|
894
|
-
else:
|
|
895
|
-
if signal == "LONG" and not long_market:
|
|
896
|
-
if time_intervals % trade_time == 0 or buys is None:
|
|
897
|
-
logger.info(f"Sending buy Order .... SYMBOL={trade.symbol}, STRATEGY=ARCH")
|
|
898
|
-
trade.open_buy_position(mm=mm, comment=comment)
|
|
899
|
-
else:
|
|
900
|
-
check(buys, sells)
|
|
901
|
-
|
|
902
|
-
elif signal == "LONG" and long_market:
|
|
903
|
-
logger.info(f"Sorry Risk not allowed !!! SYMBOL={trade.symbol}, STRATEGY=ARCH")
|
|
904
|
-
check(buys, sells)
|
|
905
|
-
|
|
906
|
-
if signal == "SHORT" and not short_market:
|
|
907
|
-
if time_intervals % trade_time == 0 or sells is None:
|
|
908
|
-
logger.info(f"Sending Sell Order .... SYMBOL={trade.symbol}, STRATEGY=ARCH")
|
|
909
|
-
trade.open_sell_position(
|
|
910
|
-
mm=mm, comment=comment)
|
|
911
|
-
else:
|
|
912
|
-
check(buys, sells)
|
|
913
|
-
|
|
914
|
-
elif signal == "SHORT" and short_market:
|
|
915
|
-
logger.info(f"Sorry Risk not Allowed !!! SYMBOL={trade.symbol}, STRATEGY=ARCH")
|
|
916
|
-
check(buys, sells)
|
|
917
|
-
else:
|
|
918
|
-
logger.info("There is no signal !!")
|
|
919
|
-
check(buys, sells)
|
|
920
|
-
else:
|
|
921
|
-
logger.info(f"Sorry It is Not trading Time !!! SYMBOL={trade.symbol}, STRATEGY=ARCH")
|
|
922
|
-
check(buys, sells)
|
|
923
332
|
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
333
|
+
def __init__(self,
|
|
334
|
+
symbol_list: List[str],
|
|
335
|
+
trades_instances: Dict[str, Trade],
|
|
336
|
+
strategy_cls: Strategy,
|
|
337
|
+
/,
|
|
338
|
+
mm: Optional[bool] = True,
|
|
339
|
+
trail: Optional[bool] = True,
|
|
340
|
+
stop_trail: Optional[int] = None,
|
|
341
|
+
trail_after_points: Optional[int] = None,
|
|
342
|
+
be_plus_points: Optional[int] = None,
|
|
343
|
+
time_frame: Optional[str] = '15m',
|
|
344
|
+
iter_time: Optional[int | float] = 5,
|
|
345
|
+
period: Literal['day', 'week', 'month'] = 'week',
|
|
346
|
+
period_end_action: Literal['break', 'sleep'] = 'break',
|
|
347
|
+
trading_days: Optional[List[str]] = TradingDays,
|
|
348
|
+
comment: Optional[str] = None,
|
|
349
|
+
**kwargs
|
|
350
|
+
):
|
|
351
|
+
"""
|
|
352
|
+
Args:
|
|
353
|
+
symbol_list : List of symbols to trade
|
|
354
|
+
trades_instances : Dictionary of Trade instances
|
|
355
|
+
strategy_cls : Strategy class to use for trading
|
|
356
|
+
mm : Enable Money Management. Defaults to False.
|
|
357
|
+
time_frame : Time frame to trade. Defaults to '15m'.
|
|
358
|
+
iter_time : Interval to check for signals and `mm`. Defaults to 5.
|
|
359
|
+
period : Period to trade. Defaults to 'week'.
|
|
360
|
+
period_end_action : Action to take at the end of the period. Defaults to 'break',
|
|
361
|
+
this only applies when period is 'day', 'week'.
|
|
362
|
+
trading_days : Trading days in a week. Defaults to monday to friday.
|
|
363
|
+
comment: Comment for trades. Defaults to None.
|
|
364
|
+
**kwargs: Additional keyword arguments
|
|
365
|
+
- strategy_name (Optional[str]): Strategy name. Defaults to None.
|
|
366
|
+
- max_trades (Dict[str, int]): Maximum trades per symbol. Defaults to None.
|
|
367
|
+
- logger (Optional[logging.Logger]): Logger instance. Defaults to None.
|
|
368
|
+
|
|
369
|
+
Note:
|
|
370
|
+
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
|
|
372
|
+
and have a `calculate_signals` method that returns a dictionary of signals for each symbol in symbol_list.
|
|
373
|
+
|
|
374
|
+
3. All strategies must have the following arguments in their `__init__` method:
|
|
375
|
+
- bars (DataHandler): DataHandler instance default to None
|
|
376
|
+
- events (Queue): Queue instance default to None
|
|
377
|
+
- symbol_list (List[str]): List of symbols to trade can be none for backtesting
|
|
378
|
+
- mode (str): Mode of the strategy. Must be either 'live' or 'backtest'
|
|
379
|
+
- **kwargs: Additional keyword arguments
|
|
380
|
+
The keyword arguments are all the additional arguments passed to the `ExecutionEngine` class,
|
|
381
|
+
the `Strategy` class, the `DataHandler` class, the `Portfolio` class and the `ExecutionHandler` class.
|
|
382
|
+
- The `bars` and `events` arguments are used for backtesting only.
|
|
383
|
+
|
|
384
|
+
4. All strategies must generate signals for backtesting and live trading.
|
|
385
|
+
See the `bbstrader.trading.strategies` module for more information on how to create custom strategies.
|
|
386
|
+
"""
|
|
387
|
+
self.symbol_list = symbol_list
|
|
388
|
+
self.trades_instances = trades_instances
|
|
389
|
+
self.strategy_cls = strategy_cls
|
|
390
|
+
self.mm = mm
|
|
391
|
+
self.trail = trail
|
|
392
|
+
self.stop_trail = stop_trail
|
|
393
|
+
self.trail_after_points = trail_after_points
|
|
394
|
+
self.be_plus_points = be_plus_points
|
|
395
|
+
self.time_frame = time_frame
|
|
396
|
+
self.iter_time = iter_time
|
|
397
|
+
self.period = period
|
|
398
|
+
self.period_end_action = period_end_action
|
|
399
|
+
self.trading_days = trading_days
|
|
400
|
+
self.comment = comment
|
|
401
|
+
self.kwargs = kwargs
|
|
402
|
+
|
|
403
|
+
def run(self, terminal: Literal['MT5', 'TWS']):
|
|
404
|
+
if terminal not in _TERMINALS:
|
|
933
405
|
raise ValueError(
|
|
934
|
-
f"
|
|
935
|
-
|
|
406
|
+
f"Invalid terminal: {terminal}. Must be either 'MT5' or 'TWS'")
|
|
407
|
+
_TERMINALS[terminal](
|
|
408
|
+
self.symbol_list,
|
|
409
|
+
self.trades_instances,
|
|
410
|
+
self.strategy_cls,
|
|
411
|
+
mm=self.mm,
|
|
412
|
+
trail=self.trail,
|
|
413
|
+
stop_trail=self.stop_trail,
|
|
414
|
+
trail_after_points=self.trail_after_points,
|
|
415
|
+
be_plus_points=self.be_plus_points,
|
|
416
|
+
time_frame=self.time_frame,
|
|
417
|
+
iter_time=self.iter_time,
|
|
418
|
+
period=self.period,
|
|
419
|
+
period_end_action=self.period_end_action,
|
|
420
|
+
trading_days=self.trading_days,
|
|
421
|
+
comment=self.comment,
|
|
422
|
+
**self.kwargs
|
|
936
423
|
)
|
|
937
|
-
|
|
938
|
-
if period.lower() == 'month':
|
|
939
|
-
if trade.days_end() and today != 'Friday':
|
|
940
|
-
logger.info(f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY=ARCH")
|
|
941
|
-
sleep_time = trade.sleep_time()
|
|
942
|
-
time.sleep(60 * sleep_time)
|
|
943
|
-
num_days += 1
|
|
944
|
-
|
|
945
|
-
elif trade.days_end() and today == 'Friday':
|
|
946
|
-
logger.info(f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY=ARCH")
|
|
947
|
-
sleep_time = trade.sleep_time(weekend=True)
|
|
948
|
-
time.sleep(60 * sleep_time)
|
|
949
|
-
num_days += 1
|
|
950
|
-
|
|
951
|
-
elif (
|
|
952
|
-
trade.days_end()
|
|
953
|
-
and today == 'Friday'
|
|
954
|
-
and num_days >= 20
|
|
955
|
-
):
|
|
956
|
-
trade.close_positions(position_type='all', comment=comment)
|
|
957
|
-
logger.info(f"End of the Month !!! SYMBOL={trade.symbol}, STRATEGY=ARCH")
|
|
958
|
-
trade.statistics(save=True)
|
|
959
|
-
break
|
|
960
|
-
|
|
961
|
-
elif period.lower() == 'week':
|
|
962
|
-
if trade.days_end() and today != 'Friday':
|
|
963
|
-
sleep_time = trade.sleep_time()
|
|
964
|
-
time.sleep(60 * sleep_time)
|
|
965
|
-
|
|
966
|
-
elif trade.days_end() and today == 'Friday':
|
|
967
|
-
trade.close_positions(position_type='all', comment=comment)
|
|
968
|
-
logger.info(f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY=ARCH")
|
|
969
|
-
trade.statistics(save=True)
|
|
970
|
-
break
|
|
971
|
-
|
|
972
|
-
elif period.lower() == 'day':
|
|
973
|
-
if trade.days_end():
|
|
974
|
-
trade.close_positions(position_type='all', comment=comment)
|
|
975
|
-
logger.info(f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY=ARCH")
|
|
976
|
-
trade.statistics(save=True)
|
|
977
|
-
break
|