bbstrader 0.1.8__py3-none-any.whl → 0.1.9__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/btengine/portfolio.py +1 -1
- bbstrader/metatrader/account.py +9 -2
- bbstrader/metatrader/rates.py +2 -4
- bbstrader/metatrader/risk.py +1 -1
- bbstrader/metatrader/trade.py +51 -19
- bbstrader/trading/execution.py +103 -93
- bbstrader/trading/strategies.py +8 -6
- {bbstrader-0.1.8.dist-info → bbstrader-0.1.9.dist-info}/METADATA +1 -1
- {bbstrader-0.1.8.dist-info → bbstrader-0.1.9.dist-info}/RECORD +12 -12
- {bbstrader-0.1.8.dist-info → bbstrader-0.1.9.dist-info}/WHEEL +1 -1
- {bbstrader-0.1.8.dist-info → bbstrader-0.1.9.dist-info}/LICENSE +0 -0
- {bbstrader-0.1.8.dist-info → bbstrader-0.1.9.dist-info}/top_level.txt +0 -0
bbstrader/btengine/portfolio.py
CHANGED
|
@@ -325,7 +325,7 @@ class Portfolio(object):
|
|
|
325
325
|
]
|
|
326
326
|
now = datetime.now().strftime('%Y%m%d%H%M%S')
|
|
327
327
|
strategy_name = self.strategy_name.replace(' ', '_')
|
|
328
|
-
results_dir = Path('
|
|
328
|
+
results_dir = Path('.backtests') / strategy_name
|
|
329
329
|
results_dir.mkdir(parents=True, exist_ok=True)
|
|
330
330
|
|
|
331
331
|
csv_file = f"{strategy_name}_{now}_equity.csv"
|
bbstrader/metatrader/account.py
CHANGED
|
@@ -54,6 +54,14 @@ _SYMBOLS_TYPE_ = {
|
|
|
54
54
|
"CRYPTO": r'\b(Cryptos?)\b'
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
def check_mt5_connection():
|
|
58
|
+
try:
|
|
59
|
+
init = mt5.initialize()
|
|
60
|
+
if not init:
|
|
61
|
+
raise_mt5_error(INIT_MSG)
|
|
62
|
+
except Exception:
|
|
63
|
+
raise_mt5_error(INIT_MSG)
|
|
64
|
+
|
|
57
65
|
class Account(object):
|
|
58
66
|
"""
|
|
59
67
|
The `Account` class is utilized to retrieve information about
|
|
@@ -91,8 +99,7 @@ class Account(object):
|
|
|
91
99
|
"""
|
|
92
100
|
|
|
93
101
|
def __init__(self):
|
|
94
|
-
|
|
95
|
-
raise_mt5_error(message=INIT_MSG)
|
|
102
|
+
check_mt5_connection()
|
|
96
103
|
self._check_brokers()
|
|
97
104
|
|
|
98
105
|
def _check_brokers(self):
|
bbstrader/metatrader/rates.py
CHANGED
|
@@ -4,7 +4,7 @@ from datetime import datetime
|
|
|
4
4
|
from typing import Union, Optional
|
|
5
5
|
from bbstrader.metatrader.utils import (
|
|
6
6
|
raise_mt5_error, TimeFrame, TIMEFRAMES)
|
|
7
|
-
from bbstrader.metatrader.account import
|
|
7
|
+
from bbstrader.metatrader.account import check_mt5_connection
|
|
8
8
|
from pandas.tseries.offsets import CustomBusinessDay
|
|
9
9
|
from pandas.tseries.holiday import USFederalHolidayCalendar
|
|
10
10
|
|
|
@@ -74,9 +74,7 @@ class Rates(object):
|
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
def _mt5_initialized(self):
|
|
77
|
-
|
|
78
|
-
if not Mt5.initialize():
|
|
79
|
-
raise_mt5_error(message=INIT_MSG)
|
|
77
|
+
check_mt5_connection()
|
|
80
78
|
|
|
81
79
|
def _get_start_pos(self, index, time_frame):
|
|
82
80
|
if isinstance(index, int):
|
bbstrader/metatrader/risk.py
CHANGED
|
@@ -341,7 +341,7 @@ class RiskManagement(Account):
|
|
|
341
341
|
"""
|
|
342
342
|
P = self.get_account_info().margin_free
|
|
343
343
|
trade_risk = self.get_trade_risk()
|
|
344
|
-
loss_allowed = P * trade_risk
|
|
344
|
+
loss_allowed = P * trade_risk / 100
|
|
345
345
|
var = self.calculate_var(c=self.var_level, tf=self.var_tf)
|
|
346
346
|
return min(var, loss_allowed)
|
|
347
347
|
|
bbstrader/metatrader/trade.py
CHANGED
|
@@ -9,7 +9,7 @@ from tabulate import tabulate
|
|
|
9
9
|
from typing import List, Tuple, Dict, Any, Optional, Literal
|
|
10
10
|
from bbstrader.btengine.performance import create_sharpe_ratio
|
|
11
11
|
from bbstrader.metatrader.risk import RiskManagement
|
|
12
|
-
from bbstrader.metatrader.account import INIT_MSG
|
|
12
|
+
from bbstrader.metatrader.account import check_mt5_connection, INIT_MSG
|
|
13
13
|
from bbstrader.metatrader.utils import (
|
|
14
14
|
TimeFrame, TradePosition, TickInfo,
|
|
15
15
|
raise_mt5_error, trade_retcode_message, config_logger)
|
|
@@ -75,7 +75,6 @@ class Trade(RiskManagement):
|
|
|
75
75
|
expert_id: int = 9818,
|
|
76
76
|
version: str = '1.0',
|
|
77
77
|
target: float = 5.0,
|
|
78
|
-
be_on_trade_open: bool = True,
|
|
79
78
|
start_time: str = "1:00",
|
|
80
79
|
finishing_time: str = "23:00",
|
|
81
80
|
ending_time: str = "23:30",
|
|
@@ -93,8 +92,7 @@ class Trade(RiskManagement):
|
|
|
93
92
|
expert_id (int): The `unique ID` used to identify the expert advisor
|
|
94
93
|
or the strategy used on the symbol.
|
|
95
94
|
version (str): The `version` of the expert advisor.
|
|
96
|
-
target (float): `Trading period (day, week, month) profit target` in percentage
|
|
97
|
-
be_on_trade_open (bool): Whether to check for break-even when opening a trade.
|
|
95
|
+
target (float): `Trading period (day, week, month) profit target` in percentage.
|
|
98
96
|
start_time (str): The` hour and minutes` that the expert advisor is able to start to run.
|
|
99
97
|
finishing_time (str): The time after which no new position can be opened.
|
|
100
98
|
ending_time (str): The time after which any open position will be closed.
|
|
@@ -131,7 +129,6 @@ class Trade(RiskManagement):
|
|
|
131
129
|
self.expert_id = expert_id
|
|
132
130
|
self.version = version
|
|
133
131
|
self.target = target
|
|
134
|
-
self.be_on_trade_open = be_on_trade_open
|
|
135
132
|
self.verbose = verbose
|
|
136
133
|
self.start = start_time
|
|
137
134
|
self.end = ending_time
|
|
@@ -151,7 +148,7 @@ class Trade(RiskManagement):
|
|
|
151
148
|
self.opened_orders = []
|
|
152
149
|
self.break_even_status = []
|
|
153
150
|
self.break_even_points = {}
|
|
154
|
-
self.trail_after_points =
|
|
151
|
+
self.trail_after_points = []
|
|
155
152
|
|
|
156
153
|
self.initialize()
|
|
157
154
|
self.select_symbol()
|
|
@@ -168,7 +165,7 @@ class Trade(RiskManagement):
|
|
|
168
165
|
def _get_logger(self, logger: str | Logger, consol_log: bool) -> Logger:
|
|
169
166
|
"""Get the logger object"""
|
|
170
167
|
if isinstance(logger, str):
|
|
171
|
-
return config_logger(logger, consol_log
|
|
168
|
+
return config_logger(logger, consol_log)
|
|
172
169
|
return logger
|
|
173
170
|
|
|
174
171
|
def initialize(self):
|
|
@@ -184,8 +181,7 @@ class Trade(RiskManagement):
|
|
|
184
181
|
try:
|
|
185
182
|
if self.verbose:
|
|
186
183
|
print("\nInitializing the basics.")
|
|
187
|
-
|
|
188
|
-
raise_mt5_error(message=INIT_MSG)
|
|
184
|
+
check_mt5_connection()
|
|
189
185
|
if self.verbose:
|
|
190
186
|
print(
|
|
191
187
|
f"You are running the @{self.expert_name} Expert advisor,"
|
|
@@ -363,7 +359,7 @@ class Trade(RiskManagement):
|
|
|
363
359
|
}
|
|
364
360
|
# Create the directory if it doesn't exist
|
|
365
361
|
if dir is None:
|
|
366
|
-
dir = f"{self.expert_name}_session_stats"
|
|
362
|
+
dir = f".{self.expert_name}_session_stats"
|
|
367
363
|
os.makedirs(dir, exist_ok=True)
|
|
368
364
|
if '.' in self.symbol:
|
|
369
365
|
symbol = self.symbol.split('.')[0]
|
|
@@ -435,8 +431,7 @@ class Trade(RiskManagement):
|
|
|
435
431
|
if action != 'BMKT':
|
|
436
432
|
request["action"] = Mt5.TRADE_ACTION_PENDING
|
|
437
433
|
request["type"] = self._order_type()[action][0]
|
|
438
|
-
|
|
439
|
-
self.break_even(mm=mm, id=Id)
|
|
434
|
+
self.break_even(mm=mm, id=Id)
|
|
440
435
|
if self.check(comment):
|
|
441
436
|
self.request_result(_price, request, action),
|
|
442
437
|
|
|
@@ -505,8 +500,7 @@ class Trade(RiskManagement):
|
|
|
505
500
|
if action != 'SMKT':
|
|
506
501
|
request["action"] = Mt5.TRADE_ACTION_PENDING
|
|
507
502
|
request["type"] = self._order_type()[action][0]
|
|
508
|
-
|
|
509
|
-
self.break_even(mm=mm, id=Id)
|
|
503
|
+
self.break_even(mm=mm, id=Id)
|
|
510
504
|
if self.check(comment):
|
|
511
505
|
self.request_result(_price, request, action)
|
|
512
506
|
|
|
@@ -862,7 +856,7 @@ class Trade(RiskManagement):
|
|
|
862
856
|
# This ensures that the position rich the minimum points required
|
|
863
857
|
# before the trail can be set
|
|
864
858
|
new_be = trail_after_points - be
|
|
865
|
-
self.trail_after_points
|
|
859
|
+
self.trail_after_points.append(position.ticket)
|
|
866
860
|
new_be_points = self.break_even_points[position.ticket] + new_be
|
|
867
861
|
favorable_move = float(points/point) >= new_be_points
|
|
868
862
|
if favorable_move:
|
|
@@ -1342,30 +1336,68 @@ class Trade(RiskManagement):
|
|
|
1342
1336
|
def create_trade_instance(
|
|
1343
1337
|
symbols: List[str],
|
|
1344
1338
|
params: Dict[str, Any],
|
|
1345
|
-
|
|
1339
|
+
daily_risk: Optional[Dict[str, float]] = None,
|
|
1340
|
+
max_risk: Optional[Dict[str, float]] = None,
|
|
1341
|
+
pchange_sl: Optional[Dict[str, float] | float] = None,
|
|
1342
|
+
logger: Logger = None) -> Dict[str, Trade]:
|
|
1346
1343
|
"""
|
|
1347
1344
|
Creates Trade instances for each symbol provided.
|
|
1348
1345
|
|
|
1349
1346
|
Args:
|
|
1350
1347
|
symbols: A list of trading symbols (e.g., ['AAPL', 'MSFT']).
|
|
1351
1348
|
params: A dictionary containing parameters for the Trade instance.
|
|
1349
|
+
daily_risk: A dictionary containing daily risk weight for each symbol.
|
|
1350
|
+
max_risk: A dictionary containing maximum risk weight for each symbol.
|
|
1351
|
+
logger: A logger instance.
|
|
1352
1352
|
|
|
1353
1353
|
Returns:
|
|
1354
1354
|
A dictionary where keys are symbols and values are corresponding Trade instances.
|
|
1355
1355
|
|
|
1356
1356
|
Raises:
|
|
1357
1357
|
ValueError: If the 'symbols' list is empty or the 'params' dictionary is missing required keys.
|
|
1358
|
+
|
|
1359
|
+
Note:
|
|
1360
|
+
`daily_risk` and `max_risk` can be used to manage the risk of each symbol
|
|
1361
|
+
based on the importance of the symbol in the portfolio or strategy.
|
|
1358
1362
|
"""
|
|
1359
1363
|
instances = {}
|
|
1360
1364
|
if not symbols:
|
|
1361
1365
|
raise ValueError("The 'symbols' list cannot be empty.")
|
|
1366
|
+
if not params:
|
|
1367
|
+
raise ValueError("The 'params' dictionary cannot be empty.")
|
|
1368
|
+
|
|
1369
|
+
if daily_risk is not None:
|
|
1370
|
+
for symbol in symbols:
|
|
1371
|
+
if symbol not in daily_risk:
|
|
1372
|
+
raise ValueError(f"Missing daily risk weight for symbol '{symbol}'.")
|
|
1373
|
+
if max_risk is not None:
|
|
1374
|
+
for symbol in symbols:
|
|
1375
|
+
if symbol not in max_risk:
|
|
1376
|
+
raise ValueError(f"Missing maximum risk percentage for symbol '{symbol}'.")
|
|
1377
|
+
if pchange_sl is not None:
|
|
1378
|
+
if isinstance(pchange_sl, dict):
|
|
1379
|
+
for symbol in symbols:
|
|
1380
|
+
if symbol not in pchange_sl:
|
|
1381
|
+
raise ValueError(f"Missing percentage change for symbol '{symbol}'.")
|
|
1382
|
+
|
|
1362
1383
|
for symbol in symbols:
|
|
1363
1384
|
try:
|
|
1364
|
-
|
|
1385
|
+
params['symbol'] = symbol
|
|
1386
|
+
params['pchange_sl'] = (
|
|
1387
|
+
pchange_sl[symbol] if pchange_sl is not None
|
|
1388
|
+
and isinstance(pchange_sl, dict) else pchange_sl
|
|
1389
|
+
)
|
|
1390
|
+
params['daily_risk'] = daily_risk[symbol] if daily_risk is not None else params['daily_risk']
|
|
1391
|
+
params['max_risk'] = max_risk[symbol] if max_risk is not None else params['max_risk']
|
|
1392
|
+
instances[symbol] = Trade(**params)
|
|
1365
1393
|
except Exception as e:
|
|
1366
1394
|
logger.error(f"Creating Trade instance, SYMBOL={symbol} {e}")
|
|
1395
|
+
|
|
1367
1396
|
if len(instances) != len(symbols):
|
|
1368
1397
|
for symbol in symbols:
|
|
1369
1398
|
if symbol not in instances:
|
|
1370
|
-
logger
|
|
1371
|
-
|
|
1399
|
+
if logger is not None:
|
|
1400
|
+
logger.error(f"Failed to create Trade instance for SYMBOL={symbol}")
|
|
1401
|
+
else:
|
|
1402
|
+
raise ValueError(f"Failed to create Trade instance for SYMBOL={symbol}")
|
|
1403
|
+
return instances
|
bbstrader/trading/execution.py
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import time
|
|
2
|
+
import MetaTrader5 as mt5
|
|
2
3
|
from datetime import datetime
|
|
3
4
|
from bbstrader.metatrader.trade import Trade
|
|
4
5
|
from bbstrader.trading.strategies import Strategy
|
|
5
|
-
from
|
|
6
|
-
import
|
|
7
|
-
from bbstrader.metatrader.account import INIT_MSG
|
|
8
|
-
from bbstrader.metatrader.utils import raise_mt5_error
|
|
6
|
+
from bbstrader.metatrader.account import check_mt5_connection
|
|
7
|
+
from typing import Optional, Literal, Tuple, List, Dict
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
_TF_MAPPING = {
|
|
@@ -29,13 +28,6 @@ TradingDays = [
|
|
|
29
28
|
'friday'
|
|
30
29
|
]
|
|
31
30
|
|
|
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
31
|
|
|
40
32
|
def _mt5_execution(
|
|
41
33
|
symbol_list, trades_instances, strategy_cls, /,
|
|
@@ -45,7 +37,7 @@ def _mt5_execution(
|
|
|
45
37
|
symbols = symbol_list.copy()
|
|
46
38
|
STRATEGY = kwargs.get('strategy_name')
|
|
47
39
|
_max_trades = kwargs.get('max_trades')
|
|
48
|
-
logger =
|
|
40
|
+
logger = trades_instances[symbols[0]].logger
|
|
49
41
|
max_trades = {symbol: _max_trades[symbol] for symbol in symbols}
|
|
50
42
|
if comment is None:
|
|
51
43
|
trade = trades_instances[symbols[0]]
|
|
@@ -72,15 +64,18 @@ def _mt5_execution(
|
|
|
72
64
|
|
|
73
65
|
long_market = {symbol: False for symbol in symbols}
|
|
74
66
|
short_market = {symbol: False for symbol in symbols}
|
|
75
|
-
|
|
67
|
+
try:
|
|
68
|
+
check_mt5_connection()
|
|
69
|
+
strategy: Strategy = strategy_cls(symbol_list=symbols, mode='live', **kwargs)
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.error(f"Error initializing strategy, {e}, STRATEGY={STRATEGY}")
|
|
72
|
+
return
|
|
76
73
|
logger.info(
|
|
77
74
|
f'Running {STRATEGY} Strategy on {symbols} in {time_frame} Interval ...')
|
|
78
|
-
|
|
79
|
-
symbol_list=symbols, mode='live', **kwargs)
|
|
80
|
-
|
|
75
|
+
|
|
81
76
|
while True:
|
|
82
77
|
try:
|
|
83
|
-
|
|
78
|
+
check_mt5_connection()
|
|
84
79
|
current_date = datetime.now()
|
|
85
80
|
today = current_date.strftime("%A").lower()
|
|
86
81
|
time.sleep(0.5)
|
|
@@ -105,13 +100,21 @@ def _mt5_execution(
|
|
|
105
100
|
sells[symbol]) >= max_trades[symbol] for symbol in symbols}
|
|
106
101
|
except Exception as e:
|
|
107
102
|
logger.error(f"{e}, STRATEGY={STRATEGY}")
|
|
103
|
+
continue
|
|
108
104
|
time.sleep(0.5)
|
|
105
|
+
try:
|
|
106
|
+
check_mt5_connection()
|
|
107
|
+
signals = strategy.calculate_signals()
|
|
108
|
+
except Exception as e:
|
|
109
|
+
logger.error(f"Calculating signal, {e}, STRATEGY={STRATEGY}")
|
|
110
|
+
continue
|
|
109
111
|
for symbol in symbols:
|
|
110
112
|
try:
|
|
113
|
+
check_mt5_connection()
|
|
111
114
|
trade = trades_instances[symbol]
|
|
112
115
|
logger.info(
|
|
113
116
|
f"Calculating signal... SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
114
|
-
signal =
|
|
117
|
+
signal = signals[symbol]
|
|
115
118
|
if trade.trading_time() and today in trading_days:
|
|
116
119
|
if signal is not None:
|
|
117
120
|
logger.info(
|
|
@@ -155,7 +158,7 @@ def _mt5_execution(
|
|
|
155
158
|
|
|
156
159
|
except Exception as e:
|
|
157
160
|
logger.error(f"{e}, SYMBOL={symbol}, STRATEGY={STRATEGY}")
|
|
158
|
-
|
|
161
|
+
continue
|
|
159
162
|
time.sleep((60 * iter_time) - 1.0)
|
|
160
163
|
if iter_time == 1:
|
|
161
164
|
time_intervals += 1
|
|
@@ -167,86 +170,92 @@ def _mt5_execution(
|
|
|
167
170
|
f"(e.g; if time_frame is 15m, iter_time must be 1.5, 3, 3, 15 etc)"
|
|
168
171
|
)
|
|
169
172
|
print()
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
173
|
+
try:
|
|
174
|
+
check_mt5_connection()
|
|
175
|
+
day_end = all(trade.days_end() for trade in trades_instances.values())
|
|
176
|
+
if period.lower() == 'day':
|
|
177
|
+
for symbol in symbols:
|
|
178
|
+
trade = trades_instances[symbol]
|
|
179
|
+
if trade.days_end():
|
|
180
|
+
trade.close_positions(position_type='all', comment=comment)
|
|
181
|
+
logger.info(
|
|
182
|
+
f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
183
|
+
trade.statistics(save=True)
|
|
184
|
+
if day_end:
|
|
185
|
+
if period_end_action == 'break':
|
|
186
|
+
break
|
|
187
|
+
elif period_end_action == 'sleep':
|
|
188
|
+
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
189
|
+
logger.info(f"Sleeping for {sleep_time} minutes ...\n")
|
|
190
|
+
time.sleep(60 * sleep_time)
|
|
191
|
+
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
192
|
+
|
|
193
|
+
elif period.lower() == 'week':
|
|
194
|
+
for symbol in symbols:
|
|
195
|
+
trade = trades_instances[symbol]
|
|
196
|
+
if trade.days_end() and today != 'friday':
|
|
197
|
+
logger.info(
|
|
198
|
+
f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
199
|
+
|
|
200
|
+
elif trade.days_end() and today == 'friday':
|
|
201
|
+
trade.close_positions(position_type='all', comment=comment)
|
|
202
|
+
logger.info(
|
|
203
|
+
f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
204
|
+
trade.statistics(save=True)
|
|
205
|
+
if day_end and today != 'friday':
|
|
182
206
|
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
183
|
-
logger.info(f"Sleeping for {sleep_time} minutes
|
|
207
|
+
logger.info(f"Sleeping for {sleep_time} minutes ...\n")
|
|
184
208
|
time.sleep(60 * sleep_time)
|
|
185
209
|
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
210
|
+
elif day_end and today == 'friday':
|
|
211
|
+
if period_end_action == 'break':
|
|
212
|
+
break
|
|
213
|
+
elif period_end_action == 'sleep':
|
|
214
|
+
sleep_time = trades_instances[symbols[-1]].sleep_time(weekend=True)
|
|
215
|
+
logger.info(f"Sleeping for {sleep_time} minutes ...\n")
|
|
216
|
+
time.sleep(60 * sleep_time)
|
|
217
|
+
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
186
218
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
219
|
+
elif period.lower() == 'month':
|
|
220
|
+
for symbol in symbols:
|
|
221
|
+
trade = trades_instances[symbol]
|
|
222
|
+
if trade.days_end() and today != 'friday':
|
|
223
|
+
logger.info(
|
|
224
|
+
f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
193
225
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
sleep_time
|
|
209
|
-
logger.info(f"Sleeping for {sleep_time} minutes ...")
|
|
226
|
+
elif trade.days_end() and today == 'friday':
|
|
227
|
+
logger.info(
|
|
228
|
+
f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
229
|
+
elif (
|
|
230
|
+
trade.days_end()
|
|
231
|
+
and today == 'friday'
|
|
232
|
+
and num_days/len(symbols) >= 20
|
|
233
|
+
):
|
|
234
|
+
trade.close_positions(position_type='all', comment=comment)
|
|
235
|
+
logger.info(
|
|
236
|
+
f"End of the Month !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
|
|
237
|
+
trade.statistics(save=True)
|
|
238
|
+
if day_end and today != 'friday':
|
|
239
|
+
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
240
|
+
logger.info(f"Sleeping for {sleep_time} minutes ...\n")
|
|
210
241
|
time.sleep(60 * sleep_time)
|
|
211
242
|
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
elif
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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 ...")
|
|
235
|
-
time.sleep(60 * sleep_time)
|
|
236
|
-
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
237
|
-
num_days += 1
|
|
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 ...")
|
|
242
|
-
time.sleep(60 * sleep_time)
|
|
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
|
-
):
|
|
249
|
-
break
|
|
243
|
+
num_days += 1
|
|
244
|
+
elif day_end and today == 'friday':
|
|
245
|
+
sleep_time = trades_instances[symbols[-1]
|
|
246
|
+
].sleep_time(weekend=True)
|
|
247
|
+
logger.info(f"Sleeping for {sleep_time} minutes ...\n")
|
|
248
|
+
time.sleep(60 * sleep_time)
|
|
249
|
+
logger.info("STARTING NEW TRADING SESSION ...\n")
|
|
250
|
+
num_days += 1
|
|
251
|
+
elif (day_end
|
|
252
|
+
and today == 'friday'
|
|
253
|
+
and num_days/len(symbols) >= 20
|
|
254
|
+
):
|
|
255
|
+
break
|
|
256
|
+
except Exception as e:
|
|
257
|
+
logger.error(f"Handling period end actions, {e}, STRATEGY={STRATEGY}")
|
|
258
|
+
continue
|
|
250
259
|
|
|
251
260
|
|
|
252
261
|
def _tws_execution(*args, **kwargs):
|
|
@@ -364,7 +373,6 @@ class ExecutionEngine():
|
|
|
364
373
|
**kwargs: Additional keyword arguments
|
|
365
374
|
- strategy_name (Optional[str]): Strategy name. Defaults to None.
|
|
366
375
|
- max_trades (Dict[str, int]): Maximum trades per symbol. Defaults to None.
|
|
367
|
-
- logger (Optional[logging.Logger]): Logger instance. Defaults to None.
|
|
368
376
|
|
|
369
377
|
Note:
|
|
370
378
|
1. For `trail` , `stop_trail` , `trail_after_points` , `be_plus_points` see `bbstrader.metatrader.trade.Trade.break_even()` .
|
|
@@ -404,6 +412,8 @@ class ExecutionEngine():
|
|
|
404
412
|
if terminal not in _TERMINALS:
|
|
405
413
|
raise ValueError(
|
|
406
414
|
f"Invalid terminal: {terminal}. Must be either 'MT5' or 'TWS'")
|
|
415
|
+
elif terminal == 'MT5':
|
|
416
|
+
check_mt5_connection()
|
|
407
417
|
_TERMINALS[terminal](
|
|
408
418
|
self.symbol_list,
|
|
409
419
|
self.trades_instances,
|
bbstrader/trading/strategies.py
CHANGED
|
@@ -7,7 +7,6 @@ import pandas as pd
|
|
|
7
7
|
from queue import Queue
|
|
8
8
|
import yfinance as yf
|
|
9
9
|
from datetime import datetime
|
|
10
|
-
from typing import List, Literal, Dict, Union, Optional
|
|
11
10
|
from bbstrader.metatrader.rates import Rates
|
|
12
11
|
from bbstrader.metatrader.account import Account
|
|
13
12
|
from bbstrader.btengine.event import SignalEvent
|
|
@@ -20,17 +19,19 @@ from bbstrader.btengine.execution import *
|
|
|
20
19
|
from bbstrader.btengine.data import *
|
|
21
20
|
from bbstrader.tseries import (
|
|
22
21
|
KalmanFilterModel, ArimaGarchModel)
|
|
22
|
+
from typing import Union, Optional, Literal, Dict, List
|
|
23
23
|
|
|
24
24
|
__all__ = [
|
|
25
25
|
'SMAStrategy',
|
|
26
26
|
'ArimaGarchStrategy',
|
|
27
27
|
'KalmanFilterStrategy',
|
|
28
28
|
'StockIndexSTBOTrading',
|
|
29
|
-
'test_strategy'
|
|
29
|
+
'test_strategy',
|
|
30
|
+
'get_quantities'
|
|
30
31
|
]
|
|
31
32
|
|
|
32
33
|
|
|
33
|
-
def
|
|
34
|
+
def get_quantities(quantities, symbol_list):
|
|
34
35
|
if isinstance(quantities, dict):
|
|
35
36
|
return quantities
|
|
36
37
|
elif isinstance(quantities, int):
|
|
@@ -84,7 +85,7 @@ class SMAStrategy(Strategy):
|
|
|
84
85
|
self.short_window = kwargs.get("short_window", 50)
|
|
85
86
|
self.long_window = kwargs.get("long_window", 200)
|
|
86
87
|
self.tf = kwargs.get("time_frame", 'D1')
|
|
87
|
-
self.qty =
|
|
88
|
+
self.qty = get_quantities(
|
|
88
89
|
kwargs.get('quantities', 100), self.symbol_list)
|
|
89
90
|
self.sd = kwargs.get("session_duration", 23.0)
|
|
90
91
|
self.risk_models = build_hmm_models(self.symbol_list, **kwargs)
|
|
@@ -246,7 +247,7 @@ class ArimaGarchStrategy(Strategy):
|
|
|
246
247
|
self.symbol_list = self.bars.symbol_list
|
|
247
248
|
self.mode = mode
|
|
248
249
|
|
|
249
|
-
self.qty =
|
|
250
|
+
self.qty = get_quantities(
|
|
250
251
|
kwargs.get('quantities', 100), self.symbol_list)
|
|
251
252
|
self.arima_window = kwargs.get('arima_window', 252)
|
|
252
253
|
self.tf = kwargs.get('time_frame', 'D1')
|
|
@@ -557,6 +558,7 @@ class KalmanFilterStrategy(Strategy):
|
|
|
557
558
|
self.calculate_backtest_signals()
|
|
558
559
|
elif self.mode == 'live':
|
|
559
560
|
return self.calculate_live_signals()
|
|
561
|
+
|
|
560
562
|
|
|
561
563
|
class StockIndexSTBOTrading(Strategy):
|
|
562
564
|
"""
|
|
@@ -617,7 +619,7 @@ class StockIndexSTBOTrading(Strategy):
|
|
|
617
619
|
self.lowerst_price = {index: None for index in symbols}
|
|
618
620
|
|
|
619
621
|
if self.mode == 'backtest':
|
|
620
|
-
self.qty =
|
|
622
|
+
self.qty = get_quantities(quantities, symbols)
|
|
621
623
|
self.num_buys = {index: 0 for index in symbols}
|
|
622
624
|
self.buy_prices = {index: [] for index in symbols}
|
|
623
625
|
|
|
@@ -6,21 +6,21 @@ bbstrader/btengine/data.py,sha256=B2cqkZZGIhedqMHeZBNB6RHGyeP-TFGOSlMoZq4uFUw,14
|
|
|
6
6
|
bbstrader/btengine/event.py,sha256=dY2DFwRPDCEdsX7GnLiwpth73tOpnbjgqIqbkNMZu1U,8286
|
|
7
7
|
bbstrader/btengine/execution.py,sha256=noat6ZxhbBYZQ0GLFYw78enp0vZCbeoGyOok4aSjWCA,5077
|
|
8
8
|
bbstrader/btengine/performance.py,sha256=STb5xJQiTa25xZdYYmjpKW46iWWcB_-6wB6PCPbhREI,10745
|
|
9
|
-
bbstrader/btengine/portfolio.py,sha256=
|
|
9
|
+
bbstrader/btengine/portfolio.py,sha256=LFsl38GbdYTJwd08ep1n4_1FPsRodQz1DHDPVSj-rBU,14882
|
|
10
10
|
bbstrader/btengine/strategy.py,sha256=uLQdsEzI8-86Cj7HWkL8blVFxT0sNxEcLVnkBil6ZLk,1550
|
|
11
11
|
bbstrader/metatrader/__init__.py,sha256=y4JLz05esm3PKerHMgtd3dmRpa7yUvWVuj_xOnlhXSA,261
|
|
12
|
-
bbstrader/metatrader/account.py,sha256=
|
|
13
|
-
bbstrader/metatrader/rates.py,sha256=
|
|
14
|
-
bbstrader/metatrader/risk.py,sha256=
|
|
15
|
-
bbstrader/metatrader/trade.py,sha256=
|
|
12
|
+
bbstrader/metatrader/account.py,sha256=5f6emtVEvXmxUnlmqFpC4hhL8sxOzOrdp7_pwJhXPhw,44716
|
|
13
|
+
bbstrader/metatrader/rates.py,sha256=FyG4WWq1NaCdHNxEW_8u3eOt0Wms7E_U4FIOIcUDWco,9605
|
|
14
|
+
bbstrader/metatrader/risk.py,sha256=Nz3RqkVFNA5FeXIqINjmGUaj88YaYGw23XsVOvaHjB8,25831
|
|
15
|
+
bbstrader/metatrader/trade.py,sha256=M5ho8x_NRVCEWO_dmDCiJrhhbcfpnia6tOmQRfV7p4U,62967
|
|
16
16
|
bbstrader/metatrader/utils.py,sha256=xc5VYQ-eejpHIPJIhLi8vxAF5zjix5NSXywXu2Eb7LI,19186
|
|
17
17
|
bbstrader/models/__init__.py,sha256=6tAj9V9vgwesgPVMKznwRB3k8-Ec8Q73Di5p2UO0qlA,274
|
|
18
18
|
bbstrader/models/risk.py,sha256=Pm_WoGI-vtPW75fwo_7ptF2Br-xQYBwrAAOIgqDQmy8,15120
|
|
19
19
|
bbstrader/trading/__init__.py,sha256=3_PeIcuQ7znoCPdq8FqRmbCc0czNiernpIRkBXJZvg0,452
|
|
20
|
-
bbstrader/trading/execution.py,sha256=
|
|
21
|
-
bbstrader/trading/strategies.py,sha256=
|
|
22
|
-
bbstrader-0.1.
|
|
23
|
-
bbstrader-0.1.
|
|
24
|
-
bbstrader-0.1.
|
|
25
|
-
bbstrader-0.1.
|
|
26
|
-
bbstrader-0.1.
|
|
20
|
+
bbstrader/trading/execution.py,sha256=Y3iKLbgCNVLw2XTDUM8Mv6hlcO8H83VyMKlZgjAKX2c,20744
|
|
21
|
+
bbstrader/trading/strategies.py,sha256=uYVeFeqVHHYrkgk4Eiziivj0iIO0RQKXCAoOSQ4XVRE,36972
|
|
22
|
+
bbstrader-0.1.9.dist-info/LICENSE,sha256=1EudjwwP2oTJy8Vh0e-Kzv8VZZU95y-t6c3DYhR51uc,1115
|
|
23
|
+
bbstrader-0.1.9.dist-info/METADATA,sha256=aJILSlgr7yOdyozYyZPseOUUy7WkJcgHUv-TnudXSvY,9660
|
|
24
|
+
bbstrader-0.1.9.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
25
|
+
bbstrader-0.1.9.dist-info/top_level.txt,sha256=Wwj322jZmxGZ6gD_TdaPiPLjED5ReObm5omerwlmZIg,10
|
|
26
|
+
bbstrader-0.1.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|