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/metatrader/rates.py
CHANGED
|
@@ -1,26 +1,21 @@
|
|
|
1
|
-
import pandas as pd
|
|
2
|
-
import MetaTrader5 as Mt5
|
|
3
1
|
from datetime import datetime
|
|
4
|
-
from typing import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
)
|
|
10
|
-
from bbstrader.metatrader.account import Account
|
|
11
|
-
from bbstrader.metatrader.account import AMG_EXCHANGES
|
|
12
|
-
from bbstrader.metatrader.account import check_mt5_connection
|
|
13
|
-
from pandas.tseries.offsets import CustomBusinessDay
|
|
2
|
+
from typing import Optional, Union
|
|
3
|
+
|
|
4
|
+
import MetaTrader5 as Mt5
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from exchange_calendars import get_calendar, get_calendar_names
|
|
14
7
|
from pandas.tseries.holiday import USFederalHolidayCalendar
|
|
15
|
-
from
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
8
|
+
from pandas.tseries.offsets import CustomBusinessDay
|
|
9
|
+
|
|
10
|
+
from bbstrader.metatrader.account import AMG_EXCHANGES, Account, check_mt5_connection
|
|
11
|
+
from bbstrader.metatrader.utils import TIMEFRAMES, TimeFrame, raise_mt5_error
|
|
19
12
|
|
|
20
13
|
__all__ = [
|
|
21
|
-
'Rates',
|
|
22
|
-
'download_historical_data',
|
|
23
|
-
'get_data_from_pos'
|
|
14
|
+
'Rates',
|
|
15
|
+
'download_historical_data',
|
|
16
|
+
'get_data_from_pos',
|
|
17
|
+
'get_data_from_date'
|
|
18
|
+
|
|
24
19
|
]
|
|
25
20
|
|
|
26
21
|
MAX_BARS = 10_000_000
|
|
@@ -40,20 +35,20 @@ IDX_CALENDARS = {
|
|
|
40
35
|
}
|
|
41
36
|
|
|
42
37
|
COMD_CALENDARS = {
|
|
43
|
-
"Energies"
|
|
44
|
-
"Metals"
|
|
45
|
-
"Agricultures"
|
|
46
|
-
"Bonds": {"USD"
|
|
38
|
+
"Energies": "us_futures",
|
|
39
|
+
"Metals": "us_futures",
|
|
40
|
+
"Agricultures": "CBOT",
|
|
41
|
+
"Bonds": {"USD": "CBOT", "EUR": "EUREX"},
|
|
47
42
|
}
|
|
48
43
|
|
|
49
44
|
CALENDARS = {
|
|
50
|
-
"FX"
|
|
51
|
-
"STK"
|
|
52
|
-
"ETF"
|
|
53
|
-
"IDX"
|
|
54
|
-
"COMD"
|
|
45
|
+
"FX": "us_futures",
|
|
46
|
+
"STK": AMG_EXCHANGES,
|
|
47
|
+
"ETF": AMG_EXCHANGES,
|
|
48
|
+
"IDX": IDX_CALENDARS,
|
|
49
|
+
"COMD": COMD_CALENDARS,
|
|
55
50
|
"CRYPTO": "24/7",
|
|
56
|
-
"FUT"
|
|
51
|
+
"FUT": None,
|
|
57
52
|
}
|
|
58
53
|
|
|
59
54
|
SESSION_TIMEFRAMES = [
|
|
@@ -78,7 +73,7 @@ class Rates(object):
|
|
|
78
73
|
is set to a value that is greater than the number of bars you want to retrieve
|
|
79
74
|
or just set it to Unlimited.
|
|
80
75
|
In your MT5 terminal, go to `Tools` -> `Options` -> `Charts` -> `Max bars in chart`.
|
|
81
|
-
|
|
76
|
+
|
|
82
77
|
2. The `open, high, low, close, adjclose, returns,
|
|
83
78
|
volume` properties returns data in Broker's timezone by default.
|
|
84
79
|
|
|
@@ -97,7 +92,7 @@ class Rates(object):
|
|
|
97
92
|
self,
|
|
98
93
|
symbol: str,
|
|
99
94
|
timeframe: TimeFrame = 'D1',
|
|
100
|
-
start_pos: Union[int
|
|
95
|
+
start_pos: Union[int, str] = 0,
|
|
101
96
|
count: Optional[int] = MAX_BARS,
|
|
102
97
|
session_duration: Optional[float] = None,
|
|
103
98
|
**kwargs
|
|
@@ -121,7 +116,6 @@ class Rates(object):
|
|
|
121
116
|
For `session_duration` check your broker symbols details
|
|
122
117
|
"""
|
|
123
118
|
self.symbol = symbol
|
|
124
|
-
tf = kwargs.get('time_frame')
|
|
125
119
|
self.time_frame = self._validate_time_frame(timeframe)
|
|
126
120
|
self.sd = session_duration
|
|
127
121
|
self.start_pos = self._get_start_pos(start_pos, timeframe)
|
|
@@ -132,7 +126,7 @@ class Rates(object):
|
|
|
132
126
|
|
|
133
127
|
def _mt5_initialized(self, **kwargs):
|
|
134
128
|
check_mt5_connection(**kwargs)
|
|
135
|
-
|
|
129
|
+
|
|
136
130
|
def _get_start_pos(self, index, time_frame):
|
|
137
131
|
if isinstance(index, int):
|
|
138
132
|
start_pos = index
|
|
@@ -182,9 +176,9 @@ class Rates(object):
|
|
|
182
176
|
return TIMEFRAMES[time_frame]
|
|
183
177
|
|
|
184
178
|
def _fetch_data(
|
|
185
|
-
self,
|
|
179
|
+
self,
|
|
186
180
|
start: Union[int, datetime, pd.Timestamp],
|
|
187
|
-
count: Union[int, datetime, pd.Timestamp],
|
|
181
|
+
count: Union[int, datetime, pd.Timestamp],
|
|
188
182
|
lower_colnames=False, utc=False,
|
|
189
183
|
) -> Union[pd.DataFrame, None]:
|
|
190
184
|
"""Fetches data from MT5 and returns a DataFrame or None."""
|
|
@@ -193,17 +187,17 @@ class Rates(object):
|
|
|
193
187
|
rates = Mt5.copy_rates_from_pos(
|
|
194
188
|
self.symbol, self.time_frame, start, count
|
|
195
189
|
)
|
|
196
|
-
elif (
|
|
197
|
-
isinstance(start, (datetime, pd.Timestamp)) and
|
|
190
|
+
elif (
|
|
191
|
+
isinstance(start, (datetime, pd.Timestamp)) and
|
|
198
192
|
isinstance(count, int)
|
|
199
|
-
|
|
193
|
+
):
|
|
200
194
|
rates = Mt5.copy_rates_from(
|
|
201
195
|
self.symbol, self.time_frame, start, count
|
|
202
196
|
)
|
|
203
197
|
elif (
|
|
204
|
-
isinstance(start, (datetime, pd.Timestamp)) and
|
|
198
|
+
isinstance(start, (datetime, pd.Timestamp)) and
|
|
205
199
|
isinstance(count, (datetime, pd.Timestamp))
|
|
206
|
-
|
|
200
|
+
):
|
|
207
201
|
rates = Mt5.copy_rates_range(
|
|
208
202
|
self.symbol, self.time_frame, start, count
|
|
209
203
|
)
|
|
@@ -215,7 +209,7 @@ class Rates(object):
|
|
|
215
209
|
except Exception as e:
|
|
216
210
|
raise_mt5_error(e)
|
|
217
211
|
|
|
218
|
-
def _format_dataframe(self, df: pd.DataFrame,
|
|
212
|
+
def _format_dataframe(self, df: pd.DataFrame,
|
|
219
213
|
lower_colnames=False, utc=False) -> pd.DataFrame:
|
|
220
214
|
"""Formats the raw MT5 data into a standardized DataFrame."""
|
|
221
215
|
df = df.copy()
|
|
@@ -223,7 +217,6 @@ class Rates(object):
|
|
|
223
217
|
df.columns = ['Date', 'Open', 'High', 'Low', 'Close', 'Volume']
|
|
224
218
|
df['Adj Close'] = df['Close']
|
|
225
219
|
df = df[['Date', 'Open', 'High', 'Low', 'Close', 'Adj Close', 'Volume']]
|
|
226
|
-
#df = df.columns.rename(str.lower).str.replace(' ', '_')
|
|
227
220
|
df['Date'] = pd.to_datetime(df['Date'], unit='s', utc=utc)
|
|
228
221
|
df.set_index('Date', inplace=True)
|
|
229
222
|
if lower_colnames:
|
|
@@ -246,19 +239,23 @@ class Rates(object):
|
|
|
246
239
|
calendar = get_calendar(exchange, side='right')
|
|
247
240
|
break
|
|
248
241
|
elif symbol_type == 'IDX':
|
|
249
|
-
calendar = get_calendar(
|
|
242
|
+
calendar = get_calendar(
|
|
243
|
+
CALENDARS[symbol_type][currencies['mc']], side='right')
|
|
250
244
|
elif symbol_type == 'COMD':
|
|
251
245
|
for commodity in CALENDARS[symbol_type]:
|
|
252
246
|
if commodity in s_info.path:
|
|
253
|
-
calendar = get_calendar(
|
|
247
|
+
calendar = get_calendar(
|
|
248
|
+
CALENDARS[symbol_type][commodity], side='right')
|
|
254
249
|
elif symbol_type == 'FUT':
|
|
255
250
|
if 'Index' in s_info.path:
|
|
256
|
-
calendar = get_calendar(
|
|
251
|
+
calendar = get_calendar(
|
|
252
|
+
CALENDARS['IDX'][currencies['mc']], side='right')
|
|
257
253
|
else:
|
|
258
254
|
for commodity, cal in COMD_CALENDARS.items():
|
|
259
255
|
if self.symbol in self.__account.get_future_symbols(category=commodity):
|
|
260
256
|
if commodity == 'Bonds':
|
|
261
|
-
calendar = get_calendar(
|
|
257
|
+
calendar = get_calendar(
|
|
258
|
+
cal[currencies['mc']], side='right')
|
|
262
259
|
else:
|
|
263
260
|
calendar = get_calendar(cal, side='right')
|
|
264
261
|
else:
|
|
@@ -274,7 +271,7 @@ class Rates(object):
|
|
|
274
271
|
index_name = df.index.name
|
|
275
272
|
if fill_na:
|
|
276
273
|
if isinstance(fill_na, bool):
|
|
277
|
-
method =
|
|
274
|
+
method = 'nearest'
|
|
278
275
|
if isinstance(fill_na, str):
|
|
279
276
|
method = fill_na
|
|
280
277
|
df = df.reindex(valid_sessions, method=method)
|
|
@@ -284,15 +281,15 @@ class Rates(object):
|
|
|
284
281
|
else:
|
|
285
282
|
df = df[df.index.isin(valid_sessions)]
|
|
286
283
|
return df
|
|
287
|
-
|
|
284
|
+
|
|
288
285
|
def _check_filter(self, filter, utc):
|
|
289
286
|
if filter and self.time_frame not in SESSION_TIMEFRAMES and not utc:
|
|
290
287
|
utc = True
|
|
291
288
|
elif filter and self.time_frame in SESSION_TIMEFRAMES and utc:
|
|
292
289
|
utc = False
|
|
293
290
|
return utc
|
|
294
|
-
|
|
295
|
-
def get_rates_from_pos(self, filter=False, fill_na=False,
|
|
291
|
+
|
|
292
|
+
def get_rates_from_pos(self, filter=False, fill_na=False,
|
|
296
293
|
lower_colnames=False, utc=False
|
|
297
294
|
) -> Union[pd.DataFrame, None]:
|
|
298
295
|
"""
|
|
@@ -311,7 +308,7 @@ class Rates(object):
|
|
|
311
308
|
Returns:
|
|
312
309
|
Union[pd.DataFrame, None]: A DataFrame containing historical
|
|
313
310
|
data if successful, otherwise None.
|
|
314
|
-
|
|
311
|
+
|
|
315
312
|
Raises:
|
|
316
313
|
ValueError: If `start_pos` or `count` is not provided during
|
|
317
314
|
initialization.
|
|
@@ -332,8 +329,8 @@ class Rates(object):
|
|
|
332
329
|
if filter:
|
|
333
330
|
return self._filter_data(df, fill_na=fill_na)
|
|
334
331
|
return df
|
|
335
|
-
|
|
336
|
-
def get_rates_from(self, date_from: datetime | pd.Timestamp, count: int=MAX_BARS,
|
|
332
|
+
|
|
333
|
+
def get_rates_from(self, date_from: datetime | pd.Timestamp, count: int = MAX_BARS,
|
|
337
334
|
filter=False, fill_na=False, lower_colnames=False, utc=False) -> Union[pd.DataFrame, None]:
|
|
338
335
|
"""
|
|
339
336
|
Retrieves historical data within a specified date range.
|
|
@@ -341,9 +338,9 @@ class Rates(object):
|
|
|
341
338
|
Args:
|
|
342
339
|
date_from : Starting date for data retrieval.
|
|
343
340
|
The data will be retrieved from this date going to the past.
|
|
344
|
-
|
|
341
|
+
|
|
345
342
|
count : Number of bars to retrieve.
|
|
346
|
-
|
|
343
|
+
|
|
347
344
|
filter : See `Rates.get_historical_data` for more details.
|
|
348
345
|
fill_na : See `Rates.get_historical_data` for more details.
|
|
349
346
|
lower_colnames : If True, the column names will be converted to lowercase.
|
|
@@ -355,7 +352,8 @@ class Rates(object):
|
|
|
355
352
|
data if successful, otherwise None.
|
|
356
353
|
"""
|
|
357
354
|
utc = self._check_filter(filter, utc)
|
|
358
|
-
df = self._fetch_data(
|
|
355
|
+
df = self._fetch_data(
|
|
356
|
+
date_from, count, lower_colnames=lower_colnames, utc=utc)
|
|
359
357
|
if df is None:
|
|
360
358
|
return None
|
|
361
359
|
if filter:
|
|
@@ -365,15 +363,15 @@ class Rates(object):
|
|
|
365
363
|
@property
|
|
366
364
|
def open(self):
|
|
367
365
|
return self.__data['Open']
|
|
368
|
-
|
|
366
|
+
|
|
369
367
|
@property
|
|
370
368
|
def high(self):
|
|
371
369
|
return self.__data['High']
|
|
372
|
-
|
|
370
|
+
|
|
373
371
|
@property
|
|
374
372
|
def low(self):
|
|
375
373
|
return self.__data['Low']
|
|
376
|
-
|
|
374
|
+
|
|
377
375
|
@property
|
|
378
376
|
def close(self):
|
|
379
377
|
return self.__data['Close']
|
|
@@ -381,7 +379,7 @@ class Rates(object):
|
|
|
381
379
|
@property
|
|
382
380
|
def adjclose(self):
|
|
383
381
|
return self.__data['Adj Close']
|
|
384
|
-
|
|
382
|
+
|
|
385
383
|
@property
|
|
386
384
|
def returns(self):
|
|
387
385
|
"""
|
|
@@ -396,10 +394,10 @@ class Rates(object):
|
|
|
396
394
|
and `not percentage change`. If you need the percentage change, multiply these values by 100.
|
|
397
395
|
"""
|
|
398
396
|
data = self.__data.copy()
|
|
399
|
-
data['Returns'] =
|
|
397
|
+
data['Returns'] = data['Adj Close'].pct_change()
|
|
400
398
|
data = data.dropna()
|
|
401
399
|
return data['Returns']
|
|
402
|
-
|
|
400
|
+
|
|
403
401
|
@property
|
|
404
402
|
def volume(self):
|
|
405
403
|
return self.__data['Volume']
|
|
@@ -419,17 +417,17 @@ class Rates(object):
|
|
|
419
417
|
|
|
420
418
|
Args:
|
|
421
419
|
date_from : Starting date for data retrieval.
|
|
422
|
-
|
|
420
|
+
|
|
423
421
|
date_to : Ending date for data retrieval.
|
|
424
422
|
Defaults to the current time.
|
|
425
|
-
|
|
423
|
+
|
|
426
424
|
utc : If True, the data will be in UTC timezone.
|
|
427
425
|
Defaults to False.
|
|
428
|
-
|
|
426
|
+
|
|
429
427
|
filter : If True, the data will be filtered based
|
|
430
428
|
on the trading sessions for the symbol.
|
|
431
429
|
This is use when we want to use the data for backtesting using Zipline.
|
|
432
|
-
|
|
430
|
+
|
|
433
431
|
fill_na : If True, the data will be filled with the nearest value.
|
|
434
432
|
This is use only when `filter` is True and time frame is "1m" or "D1",
|
|
435
433
|
this is because we use ``calendar.minutes_in_range`` or ``calendar.sessions_in_range``
|
|
@@ -438,24 +436,24 @@ class Rates(object):
|
|
|
438
436
|
because the data from MT5 will have approximately the same number of rows as the
|
|
439
437
|
number of trading days or minute in the exchange calendar, so we can fill the missing
|
|
440
438
|
data with the nearest value.
|
|
441
|
-
|
|
439
|
+
|
|
442
440
|
But for other time frames, the data will be reindexed with the exchange calendar
|
|
443
441
|
because the data from MT5 will have more rows than the number of trading days or minute
|
|
444
442
|
in the exchange calendar. So we only take the data that is in the range of the exchange
|
|
445
443
|
calendar sessions or minutes.
|
|
446
|
-
|
|
444
|
+
|
|
447
445
|
lower_colnames : If True, the column names will be converted to lowercase.
|
|
448
|
-
|
|
446
|
+
|
|
449
447
|
save_csv : File path to save the data as a CSV.
|
|
450
448
|
If None, the data won't be saved.
|
|
451
449
|
|
|
452
450
|
Returns:
|
|
453
451
|
Union[pd.DataFrame, None]: A DataFrame containing historical data
|
|
454
452
|
if successful, otherwise None.
|
|
455
|
-
|
|
453
|
+
|
|
456
454
|
Raises:
|
|
457
455
|
ValueError: If the starting date is greater than the ending date.
|
|
458
|
-
|
|
456
|
+
|
|
459
457
|
Notes:
|
|
460
458
|
The `filter` for this method can be use only for Admira Markets Group (AMG) symbols.
|
|
461
459
|
The Datetime for this method is in Local timezone by default.
|
|
@@ -470,13 +468,15 @@ class Rates(object):
|
|
|
470
468
|
if df is None:
|
|
471
469
|
return None
|
|
472
470
|
if filter:
|
|
473
|
-
df = self._filter_data(
|
|
471
|
+
df = self._filter_data(
|
|
472
|
+
df, date_from=date_from, date_to=date_to, fill_na=fill_na)
|
|
474
473
|
if save_csv:
|
|
475
474
|
df.to_csv(f"{self.symbol}.csv")
|
|
476
475
|
return df
|
|
477
476
|
|
|
478
|
-
|
|
479
|
-
|
|
477
|
+
|
|
478
|
+
def download_historical_data(symbol, timeframe, date_from,
|
|
479
|
+
date_to=pd.Timestamp.now(), lower_colnames=True,
|
|
480
480
|
utc=False, filter=False, fill_na=False, save_csv=False, **kwargs):
|
|
481
481
|
"""Download historical data from MetaTrader 5 terminal.
|
|
482
482
|
See `Rates.get_historical_data` for more details.
|
|
@@ -492,23 +492,26 @@ def download_historical_data(symbol, timeframe, date_from,
|
|
|
492
492
|
)
|
|
493
493
|
return data
|
|
494
494
|
|
|
495
|
-
|
|
495
|
+
|
|
496
|
+
def get_data_from_pos(symbol, timeframe, start_pos=0, fill_na=False,
|
|
496
497
|
count=MAX_BARS, lower_colnames=False, utc=False, filter=False,
|
|
497
|
-
|
|
498
|
+
session_duration=23.0, **kwargs):
|
|
498
499
|
"""Get historical data from a specific position.
|
|
499
500
|
See `Rates.get_rates_from_pos` for more details.
|
|
500
501
|
"""
|
|
501
|
-
rates = Rates(symbol, timeframe, start_pos,
|
|
502
|
+
rates = Rates(symbol, timeframe, start_pos,
|
|
503
|
+
count, session_duration, **kwargs)
|
|
502
504
|
data = rates.get_rates_from_pos(filter=filter, fill_na=fill_na,
|
|
503
505
|
lower_colnames=lower_colnames, utc=utc)
|
|
504
506
|
return data
|
|
505
507
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
+
|
|
509
|
+
def get_data_from_date(symbol, timeframe, date_from, count=MAX_BARS, fill_na=False,
|
|
510
|
+
lower_colnames=False, utc=False, filter=False, **kwargs):
|
|
508
511
|
"""Get historical data from a specific date.
|
|
509
512
|
See `Rates.get_rates_from` for more details.
|
|
510
513
|
"""
|
|
511
514
|
rates = Rates(symbol, timeframe, **kwargs)
|
|
512
515
|
data = rates.get_rates_from(date_from, count, filter=filter, fill_na=fill_na,
|
|
513
516
|
lower_colnames=lower_colnames, utc=utc)
|
|
514
|
-
return data
|
|
517
|
+
return data
|
bbstrader/metatrader/risk.py
CHANGED
|
@@ -1,25 +1,13 @@
|
|
|
1
1
|
import random
|
|
2
|
-
import re
|
|
3
|
-
import numpy as np
|
|
4
|
-
from scipy.stats import norm
|
|
5
2
|
from datetime import datetime
|
|
3
|
+
from typing import Any, Dict, Optional, Union
|
|
4
|
+
|
|
6
5
|
import MetaTrader5 as Mt5
|
|
6
|
+
from scipy.stats import norm
|
|
7
|
+
|
|
7
8
|
from bbstrader.metatrader.account import Account
|
|
8
9
|
from bbstrader.metatrader.rates import Rates
|
|
9
|
-
from bbstrader.metatrader.utils import
|
|
10
|
-
TIMEFRAMES,
|
|
11
|
-
raise_mt5_error,
|
|
12
|
-
TimeFrame
|
|
13
|
-
)
|
|
14
|
-
from typing import (
|
|
15
|
-
List,
|
|
16
|
-
Dict,
|
|
17
|
-
Optional,
|
|
18
|
-
Literal,
|
|
19
|
-
Union,
|
|
20
|
-
Any
|
|
21
|
-
)
|
|
22
|
-
|
|
10
|
+
from bbstrader.metatrader.utils import TIMEFRAMES, TimeFrame
|
|
23
11
|
|
|
24
12
|
_COMMD_SUPPORTED_ = [
|
|
25
13
|
"GOLD", "XAUEUR", "SILVER", "BRENT", "CRUDOIL", "WTI", "UKOIL",
|
|
@@ -36,6 +24,7 @@ _ADMIRAL_MARKETS_FUTURES_ = [
|
|
|
36
24
|
|
|
37
25
|
__all__ = ['RiskManagement']
|
|
38
26
|
|
|
27
|
+
|
|
39
28
|
class RiskManagement(Account):
|
|
40
29
|
"""
|
|
41
30
|
The RiskManagement class provides foundational
|
|
@@ -118,7 +107,7 @@ class RiskManagement(Account):
|
|
|
118
107
|
tp (int, optional): Take Profit in points, Must be a positive number.
|
|
119
108
|
be (int, optional): Break Even in points, Must be a positive number.
|
|
120
109
|
rr (float, optional): Risk reward ratio, Must be a positive number. Defaults to 1.5.
|
|
121
|
-
|
|
110
|
+
|
|
122
111
|
See `bbstrader.metatrader.account.check_mt5_connection()` for more details on how to connect to MT5 terminal.
|
|
123
112
|
"""
|
|
124
113
|
super().__init__(**kwargs)
|
|
@@ -137,7 +126,8 @@ class RiskManagement(Account):
|
|
|
137
126
|
if time_frame not in TIMEFRAMES:
|
|
138
127
|
raise ValueError("Unsupported time frame {}".format(time_frame))
|
|
139
128
|
if var_time_frame not in TIMEFRAMES:
|
|
140
|
-
raise ValueError(
|
|
129
|
+
raise ValueError(
|
|
130
|
+
"Unsupported time frame {}".format(var_time_frame))
|
|
141
131
|
|
|
142
132
|
self.kwargs = kwargs
|
|
143
133
|
self.symbol = symbol
|
|
@@ -167,15 +157,15 @@ class RiskManagement(Account):
|
|
|
167
157
|
@dailydd.setter
|
|
168
158
|
def dailydd(self, value: float):
|
|
169
159
|
self.daily_dd = value
|
|
170
|
-
|
|
160
|
+
|
|
171
161
|
@property
|
|
172
162
|
def maxrisk(self) -> float:
|
|
173
163
|
return self.max_risk
|
|
174
|
-
|
|
164
|
+
|
|
175
165
|
@maxrisk.setter
|
|
176
166
|
def maxrisk(self, value: float):
|
|
177
167
|
self.max_risk = value
|
|
178
|
-
|
|
168
|
+
|
|
179
169
|
def _convert_time_frame(self, tf: str) -> int:
|
|
180
170
|
"""Convert time frame to minutes"""
|
|
181
171
|
if tf == 'D1':
|
|
@@ -236,7 +226,7 @@ class RiskManagement(Account):
|
|
|
236
226
|
decimal_index = value_str.index('.')
|
|
237
227
|
num_digits = len(value_str) - decimal_index - 1
|
|
238
228
|
return num_digits
|
|
239
|
-
|
|
229
|
+
|
|
240
230
|
elif value_str == '1.0':
|
|
241
231
|
return 0
|
|
242
232
|
else:
|
|
@@ -280,7 +270,7 @@ class RiskManagement(Account):
|
|
|
280
270
|
"""
|
|
281
271
|
minutes = self.get_minutes()
|
|
282
272
|
tf_int = self._convert_time_frame(self._tf)
|
|
283
|
-
interval = round((minutes / tf_int)
|
|
273
|
+
interval = round((minutes / tf_int) * 252)
|
|
284
274
|
|
|
285
275
|
rate = Rates(self.symbol, self._tf, 0, interval, **self.kwargs)
|
|
286
276
|
returns = rate.returns*100
|
|
@@ -334,7 +324,7 @@ class RiskManagement(Account):
|
|
|
334
324
|
"""
|
|
335
325
|
minutes = self.get_minutes()
|
|
336
326
|
tf_int = self._convert_time_frame(tf)
|
|
337
|
-
interval = round((minutes / tf_int)
|
|
327
|
+
interval = round((minutes / tf_int) * 252)
|
|
338
328
|
|
|
339
329
|
rate = Rates(self.symbol, tf, 0, interval, **self.kwargs)
|
|
340
330
|
returns = rate.returns*100
|
|
@@ -418,15 +408,13 @@ class RiskManagement(Account):
|
|
|
418
408
|
|
|
419
409
|
Returns:
|
|
420
410
|
Dict[str, Union[int, float, Any]]: A dictionary containing the following keys:
|
|
421
|
-
|
|
411
|
+
|
|
422
412
|
- `'currency_risk'`: Dollar amount risk on a single trade.
|
|
423
413
|
- `'trade_loss'`: Loss value per tick in dollars.
|
|
424
414
|
- `'trade_profit'`: Profit value per tick in dollars.
|
|
425
415
|
- `'volume'`: Contract size multiplied by the average price.
|
|
426
416
|
- `'lot'`: Lot size per trade.
|
|
427
417
|
"""
|
|
428
|
-
|
|
429
|
-
account_info = self.get_account_info()
|
|
430
418
|
s_info = self.symbol_info
|
|
431
419
|
|
|
432
420
|
laverage = self.get_leverage(self.account_leverage)
|
|
@@ -435,9 +423,9 @@ class RiskManagement(Account):
|
|
|
435
423
|
av_price = (s_info.bid + s_info.ask)/2
|
|
436
424
|
trade_risk = self.get_trade_risk()
|
|
437
425
|
symbol_type = self.get_symbol_type(self.symbol)
|
|
438
|
-
FX
|
|
439
|
-
COMD
|
|
440
|
-
FUT
|
|
426
|
+
FX = symbol_type == 'FX'
|
|
427
|
+
COMD = symbol_type == 'COMD'
|
|
428
|
+
FUT = symbol_type == 'FUT'
|
|
441
429
|
CRYPTO = symbol_type == 'CRYPTO'
|
|
442
430
|
if COMD:
|
|
443
431
|
supported = _COMMD_SUPPORTED_
|
|
@@ -483,16 +471,15 @@ class RiskManagement(Account):
|
|
|
483
471
|
tick_value_loss = tick_value_loss / contract_size
|
|
484
472
|
tick_value_profit = tick_value_profit / contract_size
|
|
485
473
|
if (tick_value == 0
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
474
|
+
or tick_value_loss == 0
|
|
475
|
+
or tick_value_profit == 0
|
|
476
|
+
):
|
|
489
477
|
raise ValueError(
|
|
490
478
|
f"""The Tick Values for {self.symbol} is 0.0
|
|
491
479
|
We can not procced with currency risk calculation
|
|
492
480
|
Please check your Broker trade conditions
|
|
493
481
|
and symbol specifications for {self.symbol}"""
|
|
494
482
|
)
|
|
495
|
-
point = float(s_info.point)
|
|
496
483
|
|
|
497
484
|
# Case where the stop loss is given
|
|
498
485
|
if self.sl is not None:
|
|
@@ -580,7 +567,6 @@ class RiskManagement(Account):
|
|
|
580
567
|
"""
|
|
581
568
|
trade_loss = currency_risk/sl
|
|
582
569
|
trade_profit = (currency_risk*self.rr)/(sl*self.rr)
|
|
583
|
-
laverage = self.get_account_info().leverage
|
|
584
570
|
av_price = (self.symbol_info.bid + self.symbol_info.ask)/2
|
|
585
571
|
_lot = round(trade_loss/(size*loss), 2)
|
|
586
572
|
lot = self._check_lot(_lot)
|
|
@@ -652,7 +638,7 @@ class RiskManagement(Account):
|
|
|
652
638
|
margin = Mt5.order_calc_margin(
|
|
653
639
|
action, self.symbol, volume_min, av_price
|
|
654
640
|
)
|
|
655
|
-
if margin
|
|
641
|
+
if margin is None:
|
|
656
642
|
return AL
|
|
657
643
|
try:
|
|
658
644
|
leverage = (
|