bbstrader 0.1.9__py3-none-any.whl → 0.1.92__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 +4 -2
- bbstrader/btengine/__init__.py +5 -5
- bbstrader/btengine/backtest.py +60 -12
- bbstrader/btengine/data.py +157 -57
- bbstrader/btengine/event.py +14 -5
- bbstrader/btengine/execution.py +128 -26
- bbstrader/btengine/performance.py +4 -7
- bbstrader/btengine/portfolio.py +83 -36
- bbstrader/btengine/strategy.py +525 -6
- bbstrader/config.py +111 -0
- bbstrader/metatrader/__init__.py +4 -4
- bbstrader/metatrader/account.py +348 -53
- bbstrader/metatrader/rates.py +280 -27
- bbstrader/metatrader/risk.py +34 -23
- bbstrader/metatrader/trade.py +328 -169
- bbstrader/metatrader/utils.py +2 -53
- bbstrader/models/factors.py +0 -0
- bbstrader/models/ml.py +0 -0
- bbstrader/models/optimization.py +170 -0
- bbstrader/models/portfolios.py +202 -0
- bbstrader/trading/__init__.py +1 -1
- bbstrader/trading/execution.py +275 -169
- bbstrader/trading/scripts.py +57 -0
- bbstrader/trading/strategies.py +41 -65
- bbstrader/tseries.py +274 -39
- {bbstrader-0.1.9.dist-info → bbstrader-0.1.92.dist-info}/METADATA +12 -3
- bbstrader-0.1.92.dist-info/RECORD +32 -0
- {bbstrader-0.1.9.dist-info → bbstrader-0.1.92.dist-info}/WHEEL +1 -1
- bbstrader-0.1.9.dist-info/RECORD +0 -26
- {bbstrader-0.1.9.dist-info → bbstrader-0.1.92.dist-info}/LICENSE +0 -0
- {bbstrader-0.1.9.dist-info → bbstrader-0.1.92.dist-info}/top_level.txt +0 -0
bbstrader/metatrader/rates.py
CHANGED
|
@@ -3,13 +3,66 @@ import MetaTrader5 as Mt5
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from typing import Union, Optional
|
|
5
5
|
from bbstrader.metatrader.utils import (
|
|
6
|
-
raise_mt5_error,
|
|
6
|
+
raise_mt5_error,
|
|
7
|
+
TimeFrame,
|
|
8
|
+
TIMEFRAMES
|
|
9
|
+
)
|
|
10
|
+
from bbstrader.metatrader.account import Account
|
|
11
|
+
from bbstrader.metatrader.account import AMG_EXCHANGES
|
|
7
12
|
from bbstrader.metatrader.account import check_mt5_connection
|
|
8
13
|
from pandas.tseries.offsets import CustomBusinessDay
|
|
9
14
|
from pandas.tseries.holiday import USFederalHolidayCalendar
|
|
15
|
+
from exchange_calendars import(
|
|
16
|
+
get_calendar,
|
|
17
|
+
get_calendar_names
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
'Rates',
|
|
22
|
+
'download_historical_data',
|
|
23
|
+
'get_data_from_pos'
|
|
24
|
+
]
|
|
10
25
|
|
|
11
26
|
MAX_BARS = 10_000_000
|
|
12
27
|
|
|
28
|
+
IDX_CALENDARS = {
|
|
29
|
+
"CAD": "XTSE",
|
|
30
|
+
"AUD": "XASX",
|
|
31
|
+
"GBP": "XLON",
|
|
32
|
+
"HKD": "XSHG",
|
|
33
|
+
"ZAR": "XJSE",
|
|
34
|
+
"CHF": "XSWX",
|
|
35
|
+
"NOK": "XOSL",
|
|
36
|
+
"EUR": "XETR",
|
|
37
|
+
"SGD": "XSES",
|
|
38
|
+
"USD": "us_futures",
|
|
39
|
+
"JPY": "us_futures",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
COMD_CALENDARS = {
|
|
43
|
+
"Energies" : "us_futures",
|
|
44
|
+
"Metals" : "us_futures",
|
|
45
|
+
"Agricultures" : "CBOT",
|
|
46
|
+
"Bonds": {"USD" : "CBOT", "EUR": "EUREX"},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
CALENDARS = {
|
|
50
|
+
"FX" : "us_futures",
|
|
51
|
+
"STK" : AMG_EXCHANGES,
|
|
52
|
+
"ETF" : AMG_EXCHANGES,
|
|
53
|
+
"IDX" : IDX_CALENDARS,
|
|
54
|
+
"COMD" : COMD_CALENDARS,
|
|
55
|
+
"CRYPTO": "24/7",
|
|
56
|
+
"FUT" : None,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
SESSION_TIMEFRAMES = [
|
|
60
|
+
Mt5.TIMEFRAME_D1,
|
|
61
|
+
Mt5.TIMEFRAME_W1,
|
|
62
|
+
Mt5.TIMEFRAME_H12,
|
|
63
|
+
Mt5.TIMEFRAME_MN1
|
|
64
|
+
]
|
|
65
|
+
|
|
13
66
|
|
|
14
67
|
class Rates(object):
|
|
15
68
|
"""
|
|
@@ -26,8 +79,8 @@ class Rates(object):
|
|
|
26
79
|
or just set it to Unlimited.
|
|
27
80
|
In your MT5 terminal, go to `Tools` -> `Options` -> `Charts` -> `Max bars in chart`.
|
|
28
81
|
|
|
29
|
-
2. The `
|
|
30
|
-
|
|
82
|
+
2. The `open, high, low, close, adjclose, returns,
|
|
83
|
+
volume` properties returns data in Broker's timezone by default.
|
|
31
84
|
|
|
32
85
|
Example:
|
|
33
86
|
>>> rates = Rates("EURUSD", "1h")
|
|
@@ -70,9 +123,9 @@ class Rates(object):
|
|
|
70
123
|
self.start_pos = self._get_start_pos(start_pos, time_frame)
|
|
71
124
|
self.count = count
|
|
72
125
|
self._mt5_initialized()
|
|
126
|
+
self.__account = Account()
|
|
73
127
|
self.__data = self.get_rates_from_pos()
|
|
74
128
|
|
|
75
|
-
|
|
76
129
|
def _mt5_initialized(self):
|
|
77
130
|
check_mt5_connection()
|
|
78
131
|
|
|
@@ -125,8 +178,10 @@ class Rates(object):
|
|
|
125
178
|
return TIMEFRAMES[time_frame]
|
|
126
179
|
|
|
127
180
|
def _fetch_data(
|
|
128
|
-
self,
|
|
129
|
-
|
|
181
|
+
self,
|
|
182
|
+
start: Union[int, datetime, pd.Timestamp],
|
|
183
|
+
count: Union[int, datetime, pd.Timestamp],
|
|
184
|
+
lower_colnames=False, utc=False,
|
|
130
185
|
) -> Union[pd.DataFrame, None]:
|
|
131
186
|
"""Fetches data from MT5 and returns a DataFrame or None."""
|
|
132
187
|
try:
|
|
@@ -134,7 +189,17 @@ class Rates(object):
|
|
|
134
189
|
rates = Mt5.copy_rates_from_pos(
|
|
135
190
|
self.symbol, self.time_frame, start, count
|
|
136
191
|
)
|
|
137
|
-
elif
|
|
192
|
+
elif (
|
|
193
|
+
isinstance(start, (datetime, pd.Timestamp)) and
|
|
194
|
+
isinstance(count, int)
|
|
195
|
+
):
|
|
196
|
+
rates = Mt5.copy_rates_from(
|
|
197
|
+
self.symbol, self.time_frame, start, count
|
|
198
|
+
)
|
|
199
|
+
elif (
|
|
200
|
+
isinstance(start, (datetime, pd.Timestamp)) and
|
|
201
|
+
isinstance(count, (datetime, pd.Timestamp))
|
|
202
|
+
):
|
|
138
203
|
rates = Mt5.copy_rates_range(
|
|
139
204
|
self.symbol, self.time_frame, start, count
|
|
140
205
|
)
|
|
@@ -142,28 +207,103 @@ class Rates(object):
|
|
|
142
207
|
return None
|
|
143
208
|
|
|
144
209
|
df = pd.DataFrame(rates)
|
|
145
|
-
return self._format_dataframe(df)
|
|
210
|
+
return self._format_dataframe(df, lower_colnames=lower_colnames, utc=utc)
|
|
146
211
|
except Exception as e:
|
|
147
212
|
raise_mt5_error(e)
|
|
148
213
|
|
|
149
|
-
def _format_dataframe(self, df: pd.DataFrame
|
|
214
|
+
def _format_dataframe(self, df: pd.DataFrame,
|
|
215
|
+
lower_colnames=False, utc=False) -> pd.DataFrame:
|
|
150
216
|
"""Formats the raw MT5 data into a standardized DataFrame."""
|
|
151
217
|
df = df.copy()
|
|
152
218
|
df = df[['time', 'open', 'high', 'low', 'close', 'tick_volume']]
|
|
153
219
|
df.columns = ['Date', 'Open', 'High', 'Low', 'Close', 'Volume']
|
|
154
220
|
df['Adj Close'] = df['Close']
|
|
155
221
|
df = df[['Date', 'Open', 'High', 'Low', 'Close', 'Adj Close', 'Volume']]
|
|
156
|
-
df
|
|
222
|
+
#df = df.columns.rename(str.lower).str.replace(' ', '_')
|
|
223
|
+
df['Date'] = pd.to_datetime(df['Date'], unit='s', utc=utc)
|
|
157
224
|
df.set_index('Date', inplace=True)
|
|
225
|
+
if lower_colnames:
|
|
226
|
+
df.columns = df.columns.str.lower().str.replace(' ', '_')
|
|
227
|
+
df.index.name = df.index.name.lower().replace(' ', '_')
|
|
158
228
|
return df
|
|
159
229
|
|
|
160
|
-
def
|
|
230
|
+
def _filter_data(self, df: pd.DataFrame, date_from=None, date_to=None, fill_na=False) -> pd.DataFrame:
|
|
231
|
+
df = df.copy()
|
|
232
|
+
symbol_type = self.__account.get_symbol_type(self.symbol)
|
|
233
|
+
currencies = self.__account.get_currency_rates(self.symbol)
|
|
234
|
+
s_info = self.__account.get_symbol_info(self.symbol)
|
|
235
|
+
if symbol_type in CALENDARS:
|
|
236
|
+
if symbol_type == 'STK' or symbol_type == 'ETF':
|
|
237
|
+
for exchange in CALENDARS[symbol_type]:
|
|
238
|
+
if exchange in get_calendar_names():
|
|
239
|
+
symbols = self.__account.get_stocks_from_exchange(
|
|
240
|
+
exchange_code=exchange)
|
|
241
|
+
if self.symbol in symbols:
|
|
242
|
+
calendar = get_calendar(exchange, side='right')
|
|
243
|
+
break
|
|
244
|
+
elif symbol_type == 'IDX':
|
|
245
|
+
calendar = get_calendar(CALENDARS[symbol_type][currencies['mc']], side='right')
|
|
246
|
+
elif symbol_type == 'COMD':
|
|
247
|
+
for commodity in CALENDARS[symbol_type]:
|
|
248
|
+
if commodity in s_info.path:
|
|
249
|
+
calendar = get_calendar(CALENDARS[symbol_type][commodity], side='right')
|
|
250
|
+
elif symbol_type == 'FUT':
|
|
251
|
+
if 'Index' in s_info.path:
|
|
252
|
+
calendar = get_calendar(CALENDARS['IDX'][currencies['mc']], side='right')
|
|
253
|
+
else:
|
|
254
|
+
for commodity, cal in COMD_CALENDARS.items():
|
|
255
|
+
if self.symbol in self.__account.get_future_symbols(category=commodity):
|
|
256
|
+
if commodity == 'Bonds':
|
|
257
|
+
calendar = get_calendar(cal[currencies['mc']], side='right')
|
|
258
|
+
else:
|
|
259
|
+
calendar = get_calendar(cal, side='right')
|
|
260
|
+
else:
|
|
261
|
+
calendar = get_calendar(CALENDARS[symbol_type], side='right')
|
|
262
|
+
date_from = date_from or df.index[0]
|
|
263
|
+
date_to = date_to or df.index[-1]
|
|
264
|
+
if self.time_frame in SESSION_TIMEFRAMES:
|
|
265
|
+
valid_sessions = calendar.sessions_in_range(date_from, date_to)
|
|
266
|
+
else:
|
|
267
|
+
valid_sessions = calendar.minutes_in_range(date_from, date_to)
|
|
268
|
+
if self.time_frame in [Mt5.TIMEFRAME_M1, Mt5.TIMEFRAME_D1]:
|
|
269
|
+
# save the index name of the dataframe
|
|
270
|
+
index_name = df.index.name
|
|
271
|
+
if fill_na:
|
|
272
|
+
if isinstance(fill_na, bool):
|
|
273
|
+
method = 'nearest'
|
|
274
|
+
if isinstance(fill_na, str):
|
|
275
|
+
method = fill_na
|
|
276
|
+
df = df.reindex(valid_sessions, method=method)
|
|
277
|
+
else:
|
|
278
|
+
df.reindex(valid_sessions, method=None)
|
|
279
|
+
df.index = df.index.rename(index_name)
|
|
280
|
+
else:
|
|
281
|
+
df = df[df.index.isin(valid_sessions)]
|
|
282
|
+
return df
|
|
283
|
+
|
|
284
|
+
def _check_filter(self, filter, utc):
|
|
285
|
+
if filter and self.time_frame not in SESSION_TIMEFRAMES and not utc:
|
|
286
|
+
utc = True
|
|
287
|
+
elif filter and self.time_frame in SESSION_TIMEFRAMES and utc:
|
|
288
|
+
utc = False
|
|
289
|
+
return utc
|
|
290
|
+
|
|
291
|
+
def get_rates_from_pos(self, filter=False, fill_na=False,
|
|
292
|
+
lower_colnames=False, utc=False
|
|
293
|
+
) -> Union[pd.DataFrame, None]:
|
|
161
294
|
"""
|
|
162
295
|
Retrieves historical data starting from a specific position.
|
|
163
296
|
|
|
164
297
|
Uses the `start_pos` and `count` attributes specified during
|
|
165
298
|
initialization to fetch data.
|
|
166
299
|
|
|
300
|
+
Args:
|
|
301
|
+
filter : See `Rates.get_historical_data` for more details.
|
|
302
|
+
fill_na : See `Rates.get_historical_data` for more details.
|
|
303
|
+
lower_colnames : If True, the column names will be converted to lowercase.
|
|
304
|
+
utc (bool, optional): If True, the data will be in UTC timezone.
|
|
305
|
+
Defaults to False.
|
|
306
|
+
|
|
167
307
|
Returns:
|
|
168
308
|
Union[pd.DataFrame, None]: A DataFrame containing historical
|
|
169
309
|
data if successful, otherwise None.
|
|
@@ -180,31 +320,66 @@ class Rates(object):
|
|
|
180
320
|
"Both 'start_pos' and 'count' must be provided "
|
|
181
321
|
"when calling 'get_rates_from_pos'."
|
|
182
322
|
)
|
|
183
|
-
|
|
323
|
+
utc = self._check_filter(filter, utc)
|
|
324
|
+
df = self._fetch_data(self.start_pos, self.count,
|
|
325
|
+
lower_colnames=lower_colnames, utc=utc)
|
|
326
|
+
if df is None:
|
|
327
|
+
return None
|
|
328
|
+
if filter:
|
|
329
|
+
return self._filter_data(df, fill_na=fill_na)
|
|
184
330
|
return df
|
|
185
331
|
|
|
332
|
+
def get_rates_from(self, date_from: datetime | pd.Timestamp, count: int=MAX_BARS,
|
|
333
|
+
filter=False, fill_na=False, lower_colnames=False, utc=False) -> Union[pd.DataFrame, None]:
|
|
334
|
+
"""
|
|
335
|
+
Retrieves historical data within a specified date range.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
date_from : Starting date for data retrieval.
|
|
339
|
+
The data will be retrieved from this date going to the past.
|
|
340
|
+
|
|
341
|
+
count : Number of bars to retrieve.
|
|
342
|
+
|
|
343
|
+
filter : See `Rates.get_historical_data` for more details.
|
|
344
|
+
fill_na : See `Rates.get_historical_data` for more details.
|
|
345
|
+
lower_colnames : If True, the column names will be converted to lowercase.
|
|
346
|
+
utc (bool, optional): If True, the data will be in UTC timezone.
|
|
347
|
+
Defaults to False.
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
Union[pd.DataFrame, None]: A DataFrame containing historical
|
|
351
|
+
data if successful, otherwise None.
|
|
352
|
+
"""
|
|
353
|
+
utc = self._check_filter(filter, utc)
|
|
354
|
+
df = self._fetch_data(date_from, count, lower_colnames=lower_colnames, utc=utc)
|
|
355
|
+
if df is None:
|
|
356
|
+
return None
|
|
357
|
+
if filter:
|
|
358
|
+
return self._filter_data(df, fill_na=fill_na)
|
|
359
|
+
return df
|
|
360
|
+
|
|
186
361
|
@property
|
|
187
|
-
def
|
|
362
|
+
def open(self):
|
|
188
363
|
return self.__data['Open']
|
|
189
364
|
|
|
190
365
|
@property
|
|
191
|
-
def
|
|
366
|
+
def high(self):
|
|
192
367
|
return self.__data['High']
|
|
193
368
|
|
|
194
369
|
@property
|
|
195
|
-
def
|
|
370
|
+
def low(self):
|
|
196
371
|
return self.__data['Low']
|
|
197
372
|
|
|
198
373
|
@property
|
|
199
|
-
def
|
|
374
|
+
def close(self):
|
|
200
375
|
return self.__data['Close']
|
|
201
376
|
|
|
202
377
|
@property
|
|
203
|
-
def
|
|
378
|
+
def adjclose(self):
|
|
204
379
|
return self.__data['Adj Close']
|
|
205
380
|
|
|
206
381
|
@property
|
|
207
|
-
def
|
|
382
|
+
def returns(self):
|
|
208
383
|
"""
|
|
209
384
|
Fractional change between the current and a prior element.
|
|
210
385
|
|
|
@@ -222,23 +397,52 @@ class Rates(object):
|
|
|
222
397
|
return data['Returns']
|
|
223
398
|
|
|
224
399
|
@property
|
|
225
|
-
def
|
|
400
|
+
def volume(self):
|
|
226
401
|
return self.__data['Volume']
|
|
227
402
|
|
|
228
403
|
def get_historical_data(
|
|
229
404
|
self,
|
|
230
|
-
date_from: datetime,
|
|
231
|
-
date_to: datetime =
|
|
405
|
+
date_from: datetime | pd.Timestamp,
|
|
406
|
+
date_to: datetime | pd.Timestamp = pd.Timestamp.now(),
|
|
407
|
+
utc: bool = False,
|
|
408
|
+
filter: Optional[bool] = False,
|
|
409
|
+
fill_na: Optional[bool | str] = False,
|
|
410
|
+
lower_colnames: Optional[bool] = True,
|
|
232
411
|
save_csv: Optional[bool] = False,
|
|
233
412
|
) -> Union[pd.DataFrame, None]:
|
|
234
413
|
"""
|
|
235
414
|
Retrieves historical data within a specified date range.
|
|
236
415
|
|
|
237
416
|
Args:
|
|
238
|
-
date_from
|
|
239
|
-
|
|
417
|
+
date_from : Starting date for data retrieval.
|
|
418
|
+
|
|
419
|
+
date_to : Ending date for data retrieval.
|
|
240
420
|
Defaults to the current time.
|
|
241
|
-
|
|
421
|
+
|
|
422
|
+
utc : If True, the data will be in UTC timezone.
|
|
423
|
+
Defaults to False.
|
|
424
|
+
|
|
425
|
+
filter : If True, the data will be filtered based
|
|
426
|
+
on the trading sessions for the symbol.
|
|
427
|
+
This is use when we want to use the data for backtesting using Zipline.
|
|
428
|
+
|
|
429
|
+
fill_na : If True, the data will be filled with the nearest value.
|
|
430
|
+
This is use only when `filter` is True and time frame is "1m" or "D1",
|
|
431
|
+
this is because we use ``calendar.minutes_in_range`` or ``calendar.sessions_in_range``
|
|
432
|
+
where calendar is the ``ExchangeCalendar`` from `exchange_calendars` package.
|
|
433
|
+
So, for "1m" or "D1" time frame, the data will be filled with the nearest value
|
|
434
|
+
because the data from MT5 will have approximately the same number of rows as the
|
|
435
|
+
number of trading days or minute in the exchange calendar, so we can fill the missing
|
|
436
|
+
data with the nearest value.
|
|
437
|
+
|
|
438
|
+
But for other time frames, the data will be reindexed with the exchange calendar
|
|
439
|
+
because the data from MT5 will have more rows than the number of trading days or minute
|
|
440
|
+
in the exchange calendar. So we only take the data that is in the range of the exchange
|
|
441
|
+
calendar sessions or minutes.
|
|
442
|
+
|
|
443
|
+
lower_colnames : If True, the column names will be converted to lowercase.
|
|
444
|
+
|
|
445
|
+
save_csv : File path to save the data as a CSV.
|
|
242
446
|
If None, the data won't be saved.
|
|
243
447
|
|
|
244
448
|
Returns:
|
|
@@ -249,9 +453,58 @@ class Rates(object):
|
|
|
249
453
|
ValueError: If the starting date is greater than the ending date.
|
|
250
454
|
|
|
251
455
|
Notes:
|
|
252
|
-
The
|
|
456
|
+
The `filter` for this method can be use only for Admira Markets Group (AMG) symbols.
|
|
457
|
+
The Datetime for this method is in Local timezone by default.
|
|
458
|
+
All STK symbols are filtered based on the the exchange calendar.
|
|
459
|
+
All FX symbols are filtered based on the ``us_futures`` calendar.
|
|
460
|
+
All IDX symbols are filtered based on the exchange calendar of margin currency.
|
|
461
|
+
All COMD symbols are filtered based on the exchange calendar of the commodity.
|
|
253
462
|
"""
|
|
254
|
-
|
|
255
|
-
|
|
463
|
+
utc = self._check_filter(filter, utc)
|
|
464
|
+
df = self._fetch_data(date_from, date_to,
|
|
465
|
+
lower_colnames=lower_colnames, utc=utc)
|
|
466
|
+
if df is None:
|
|
467
|
+
return None
|
|
468
|
+
if filter:
|
|
469
|
+
df = self._filter_data(df, date_from=date_from, date_to=date_to, fill_na=fill_na)
|
|
470
|
+
if save_csv:
|
|
256
471
|
df.to_csv(f"{self.symbol}.csv")
|
|
257
472
|
return df
|
|
473
|
+
|
|
474
|
+
def download_historical_data(symbol, time_frame, date_from,
|
|
475
|
+
date_to=pd.Timestamp.now(),lower_colnames=True,
|
|
476
|
+
utc=False, filter=False, fill_na=False, save_csv=False):
|
|
477
|
+
"""Download historical data from MetaTrader 5 terminal.
|
|
478
|
+
See `Rates.get_historical_data` for more details.
|
|
479
|
+
"""
|
|
480
|
+
rates = Rates(symbol, time_frame)
|
|
481
|
+
data = rates.get_historical_data(
|
|
482
|
+
date_from=date_from,
|
|
483
|
+
date_to=date_to,
|
|
484
|
+
save_csv=save_csv,
|
|
485
|
+
utc=utc,
|
|
486
|
+
filter=filter,
|
|
487
|
+
lower_colnames=lower_colnames
|
|
488
|
+
)
|
|
489
|
+
return data
|
|
490
|
+
|
|
491
|
+
def get_data_from_pos(symbol, time_frame, start_pos=0, fill_na=False,
|
|
492
|
+
count=MAX_BARS, lower_colnames=False, utc=False, filter=False,
|
|
493
|
+
session_duration=23.0):
|
|
494
|
+
"""Get historical data from a specific position.
|
|
495
|
+
See `Rates.get_rates_from_pos` for more details.
|
|
496
|
+
"""
|
|
497
|
+
rates = Rates(symbol, time_frame, start_pos, count, session_duration)
|
|
498
|
+
data = rates.get_rates_from_pos(filter=filter, fill_na=fill_na,
|
|
499
|
+
lower_colnames=lower_colnames, utc=utc)
|
|
500
|
+
return data
|
|
501
|
+
|
|
502
|
+
def get_data_from_date(symbol, time_frame, date_from, count=MAX_BARS, fill_na=False,
|
|
503
|
+
lower_colnames=False, utc=False, filter=False):
|
|
504
|
+
"""Get historical data from a specific date.
|
|
505
|
+
See `Rates.get_rates_from` for more details.
|
|
506
|
+
"""
|
|
507
|
+
rates = Rates(symbol, time_frame)
|
|
508
|
+
data = rates.get_rates_from(date_from, count, filter=filter, fill_na=fill_na,
|
|
509
|
+
lower_colnames=lower_colnames, utc=utc)
|
|
510
|
+
return data
|
bbstrader/metatrader/risk.py
CHANGED
|
@@ -7,8 +7,18 @@ import MetaTrader5 as Mt5
|
|
|
7
7
|
from bbstrader.metatrader.account import Account
|
|
8
8
|
from bbstrader.metatrader.rates import Rates
|
|
9
9
|
from bbstrader.metatrader.utils import (
|
|
10
|
-
TIMEFRAMES,
|
|
11
|
-
|
|
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
|
+
)
|
|
12
22
|
|
|
13
23
|
|
|
14
24
|
_COMMD_SUPPORTED_ = [
|
|
@@ -16,7 +26,6 @@ _COMMD_SUPPORTED_ = [
|
|
|
16
26
|
'XAGEUR', 'XAGUSD', 'XAUAUD', 'XAUEUR', 'XAUUSD', 'XAUGBP', 'USOIL'
|
|
17
27
|
]
|
|
18
28
|
|
|
19
|
-
|
|
20
29
|
_ADMIRAL_MARKETS_FUTURES_ = [
|
|
21
30
|
'#USTNote_', '#Bund_', '#USDX_', '_AUS200_', '_Canada60_', '_SouthAfrica40_',
|
|
22
31
|
'_STXE600_', '_EURO50_', '_GER40_', '_GermanyTech30_', '_MidCapGER50_',
|
|
@@ -25,6 +34,7 @@ _ADMIRAL_MARKETS_FUTURES_ = [
|
|
|
25
34
|
'_XAU_', '_HK50_', '_HSCEI50_'
|
|
26
35
|
]
|
|
27
36
|
|
|
37
|
+
__all__ = ['RiskManagement']
|
|
28
38
|
|
|
29
39
|
class RiskManagement(Account):
|
|
30
40
|
"""
|
|
@@ -135,7 +145,7 @@ class RiskManagement(Account):
|
|
|
135
145
|
self.pchange = pchange_sl
|
|
136
146
|
self.var_level = var_level
|
|
137
147
|
self.var_tf = var_time_frame
|
|
138
|
-
self.daily_dd = daily_risk
|
|
148
|
+
self.daily_dd = round(daily_risk, 5)
|
|
139
149
|
self.max_risk = max_risk
|
|
140
150
|
self.rr = rr
|
|
141
151
|
self.sl = sl
|
|
@@ -193,7 +203,7 @@ class RiskManagement(Account):
|
|
|
193
203
|
volume_step = s_info.volume_step
|
|
194
204
|
lot = self.currency_risk()['lot']
|
|
195
205
|
steps = self._volume_step(volume_step)
|
|
196
|
-
if steps >=
|
|
206
|
+
if float(steps) >= float(1):
|
|
197
207
|
return round(lot, steps)
|
|
198
208
|
else:
|
|
199
209
|
return round(lot)
|
|
@@ -203,13 +213,13 @@ class RiskManagement(Account):
|
|
|
203
213
|
|
|
204
214
|
value_str = str(value)
|
|
205
215
|
|
|
206
|
-
if '.' in value_str:
|
|
216
|
+
if '.' in value_str and value_str != '1.0':
|
|
207
217
|
decimal_index = value_str.index('.')
|
|
208
218
|
num_digits = len(value_str) - decimal_index - 1
|
|
209
|
-
|
|
210
219
|
return num_digits
|
|
211
|
-
|
|
212
|
-
|
|
220
|
+
|
|
221
|
+
elif value_str == '1.0':
|
|
222
|
+
return 0
|
|
213
223
|
else:
|
|
214
224
|
return 0
|
|
215
225
|
|
|
@@ -254,7 +264,7 @@ class RiskManagement(Account):
|
|
|
254
264
|
interval = round((minutes / tf_int) * 252)
|
|
255
265
|
|
|
256
266
|
rate = Rates(self.symbol, self._tf, 0, interval)
|
|
257
|
-
returns = rate.
|
|
267
|
+
returns = rate.returns*100
|
|
258
268
|
std = returns.std()
|
|
259
269
|
point = self.get_symbol_info(self.symbol).point
|
|
260
270
|
av_price = (self.symbol_info.bid + self.symbol_info.ask)/2
|
|
@@ -308,7 +318,7 @@ class RiskManagement(Account):
|
|
|
308
318
|
interval = round((minutes / tf_int) * 252)
|
|
309
319
|
|
|
310
320
|
rate = Rates(self.symbol, tf, 0, interval)
|
|
311
|
-
returns = rate.
|
|
321
|
+
returns = rate.returns*100
|
|
312
322
|
p = self.get_account_info().margin_free
|
|
313
323
|
mu = returns.mean()
|
|
314
324
|
sigma = returns.std()
|
|
@@ -405,10 +415,11 @@ class RiskManagement(Account):
|
|
|
405
415
|
|
|
406
416
|
av_price = (s_info.bid + s_info.ask)/2
|
|
407
417
|
trade_risk = self.get_trade_risk()
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
418
|
+
symbol_type = self.get_symbol_type(self.symbol)
|
|
419
|
+
FX = symbol_type == 'FX'
|
|
420
|
+
COMD = symbol_type == 'COMD'
|
|
421
|
+
FUT = symbol_type == 'FUT'
|
|
422
|
+
CRYPTO = symbol_type == 'CRYPTO'
|
|
412
423
|
if COMD:
|
|
413
424
|
supported = _COMMD_SUPPORTED_
|
|
414
425
|
if self.symbol.split('.')[0] not in supported:
|
|
@@ -503,14 +514,14 @@ class RiskManagement(Account):
|
|
|
503
514
|
trade_loss = (lot * contract_size) * tick_value_loss
|
|
504
515
|
trade_profit = (lot * contract_size) * tick_value_profit
|
|
505
516
|
|
|
506
|
-
if self.get_symbol_type(self.symbol) == 'IDX':
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
if contract_size == 1:
|
|
513
|
-
|
|
517
|
+
# if self.get_symbol_type(self.symbol) == 'IDX':
|
|
518
|
+
# rates = self.get_currency_rates(self.symbol)
|
|
519
|
+
# if rates['mc'] == rates['pc'] == 'JPY':
|
|
520
|
+
# lot = lot * contract_size
|
|
521
|
+
# lot = self._check_lot(lot)
|
|
522
|
+
# volume = round(lot * av_price * contract_size)
|
|
523
|
+
# if contract_size == 1:
|
|
524
|
+
# volume = round(lot * av_price)
|
|
514
525
|
|
|
515
526
|
return {
|
|
516
527
|
'currency_risk': currency_risk,
|