bbstrader 2.0.3__cp312-cp312-macosx_11_0_arm64.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.
- bbstrader/__init__.py +27 -0
- bbstrader/__main__.py +92 -0
- bbstrader/api/__init__.py +96 -0
- bbstrader/api/handlers.py +245 -0
- bbstrader/api/metatrader_client.cpython-312-darwin.so +0 -0
- bbstrader/api/metatrader_client.pyi +624 -0
- bbstrader/assets/bbs_.png +0 -0
- bbstrader/assets/bbstrader.ico +0 -0
- bbstrader/assets/bbstrader.png +0 -0
- bbstrader/assets/qs_metrics_1.png +0 -0
- bbstrader/btengine/__init__.py +54 -0
- bbstrader/btengine/backtest.py +358 -0
- bbstrader/btengine/data.py +737 -0
- bbstrader/btengine/event.py +229 -0
- bbstrader/btengine/execution.py +287 -0
- bbstrader/btengine/performance.py +408 -0
- bbstrader/btengine/portfolio.py +393 -0
- bbstrader/btengine/strategy.py +588 -0
- bbstrader/compat.py +28 -0
- bbstrader/config.py +100 -0
- bbstrader/core/__init__.py +27 -0
- bbstrader/core/data.py +628 -0
- bbstrader/core/strategy.py +466 -0
- bbstrader/metatrader/__init__.py +48 -0
- bbstrader/metatrader/_copier.py +720 -0
- bbstrader/metatrader/account.py +865 -0
- bbstrader/metatrader/broker.py +418 -0
- bbstrader/metatrader/copier.py +1487 -0
- bbstrader/metatrader/rates.py +495 -0
- bbstrader/metatrader/risk.py +667 -0
- bbstrader/metatrader/trade.py +1692 -0
- bbstrader/metatrader/utils.py +402 -0
- bbstrader/models/__init__.py +39 -0
- bbstrader/models/nlp.py +932 -0
- bbstrader/models/optimization.py +182 -0
- bbstrader/scripts.py +665 -0
- bbstrader/trading/__init__.py +33 -0
- bbstrader/trading/execution.py +1159 -0
- bbstrader/trading/strategy.py +362 -0
- bbstrader/trading/utils.py +69 -0
- bbstrader-2.0.3.dist-info/METADATA +396 -0
- bbstrader-2.0.3.dist-info/RECORD +45 -0
- bbstrader-2.0.3.dist-info/WHEEL +5 -0
- bbstrader-2.0.3.dist-info/entry_points.txt +3 -0
- bbstrader-2.0.3.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Optional, Union
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from exchange_calendars import get_calendar, get_calendar_names
|
|
6
|
+
|
|
7
|
+
from bbstrader.metatrader.account import Account
|
|
8
|
+
from bbstrader.metatrader.broker import EXCHANGES
|
|
9
|
+
from bbstrader.metatrader.utils import TIMEFRAMES, SymbolType, raise_mt5_error
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
import MetaTrader5 as Mt5
|
|
13
|
+
except ImportError:
|
|
14
|
+
import bbstrader.compat # noqa: F401
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"Rates",
|
|
19
|
+
"download_historical_data",
|
|
20
|
+
"get_data_from_pos",
|
|
21
|
+
"get_data_from_date",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
MAX_BARS = 10_000_000
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
IDX_CALENDARS = {
|
|
28
|
+
"CAD": "XTSE",
|
|
29
|
+
"AUD": "XASX",
|
|
30
|
+
"GBP": "XLON",
|
|
31
|
+
"HKD": "XSHG",
|
|
32
|
+
"ZAR": "XJSE",
|
|
33
|
+
"CHF": "XSWX",
|
|
34
|
+
"NOK": "XOSL",
|
|
35
|
+
"EUR": "XETR",
|
|
36
|
+
"SGD": "XSES",
|
|
37
|
+
"USD": "us_futures",
|
|
38
|
+
"JPY": "us_futures",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
CALENDARS = {
|
|
43
|
+
SymbolType.FOREX: "us_futures",
|
|
44
|
+
SymbolType.STOCKS: EXCHANGES,
|
|
45
|
+
SymbolType.ETFs: EXCHANGES,
|
|
46
|
+
SymbolType.INDICES: IDX_CALENDARS,
|
|
47
|
+
SymbolType.COMMODITIES: "us_futures",
|
|
48
|
+
SymbolType.CRYPTO: "24/7",
|
|
49
|
+
SymbolType.FUTURES: "us_futures",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
SESSION_TIMEFRAMES = [
|
|
53
|
+
Mt5.TIMEFRAME_D1,
|
|
54
|
+
Mt5.TIMEFRAME_W1,
|
|
55
|
+
Mt5.TIMEFRAME_H12,
|
|
56
|
+
Mt5.TIMEFRAME_MN1,
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Rates(object):
|
|
61
|
+
"""
|
|
62
|
+
Provides methods to retrieve historical financial data from MetaTrader 5.
|
|
63
|
+
|
|
64
|
+
This class encapsulates interactions with the MetaTrader 5 (MT5) terminal
|
|
65
|
+
to fetch historical price data for a given symbol and timeframe. It offers
|
|
66
|
+
flexibility in retrieving data either by specifying a starting position
|
|
67
|
+
and count of bars or by providing a specific date range .
|
|
68
|
+
|
|
69
|
+
Notes:
|
|
70
|
+
All data is rerturn as pandas.DataFrame
|
|
71
|
+
|
|
72
|
+
1. Befor using this class, ensure that the `Max bars in chart` in your terminal
|
|
73
|
+
is set to a value that is greater than the number of bars you want to retrieve
|
|
74
|
+
or just set it to Unlimited.
|
|
75
|
+
In your MT5 terminal, go to `Tools` -> `Options` -> `Charts` -> `Max bars in chart`.
|
|
76
|
+
|
|
77
|
+
2. The `open, high, low, close, adjclose, returns,
|
|
78
|
+
volume` properties returns data in Broker's timezone by default.
|
|
79
|
+
|
|
80
|
+
See `bbstrader.metatrader.broker.check_mt5_connection()` for more details on how to connect to MT5 terminal.
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
>>> rates = Rates("EURUSD", "1h")
|
|
84
|
+
>>> df = rates.get_historical_data(
|
|
85
|
+
... date_from=datetime(2023, 1, 1),
|
|
86
|
+
... date_to=datetime(2023, 1, 10),
|
|
87
|
+
... )
|
|
88
|
+
>>> print(df.head())
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
symbol: str,
|
|
94
|
+
timeframe: str = "D1",
|
|
95
|
+
start_pos: int = 0,
|
|
96
|
+
count: Optional[int] = MAX_BARS,
|
|
97
|
+
**kwargs,
|
|
98
|
+
):
|
|
99
|
+
"""
|
|
100
|
+
Initializes a new Rates instance.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
symbol (str): Financial instrument symbol (e.g., "EURUSD").
|
|
104
|
+
timeframe (str): Timeframe string (e.g., "D1", "1h", "5m").
|
|
105
|
+
start_pos (int): Starting index (int) for data retrieval.
|
|
106
|
+
count (int, optional): Number of bars to retrieve default is
|
|
107
|
+
the maximum bars availble in the MT5 terminal.
|
|
108
|
+
Raises:
|
|
109
|
+
ValueError: If the provided timeframe is invalid.
|
|
110
|
+
"""
|
|
111
|
+
self.symbol = symbol
|
|
112
|
+
self.start_pos = start_pos
|
|
113
|
+
self.count = count
|
|
114
|
+
self.time_frame = self._validate_time_frame(timeframe)
|
|
115
|
+
self.__account = Account(**kwargs)
|
|
116
|
+
self.__data = self.get_rates_from_pos
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def open(self):
|
|
120
|
+
return self.__data()["Open"]
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def high(self):
|
|
124
|
+
return self.__data()["High"]
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def low(self):
|
|
128
|
+
return self.__data()["Low"]
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def close(self):
|
|
132
|
+
return self.__data()["Close"]
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def adjclose(self):
|
|
136
|
+
return self.__data()["Adj Close"]
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def returns(self):
|
|
140
|
+
"""
|
|
141
|
+
Fractional change between the current and a prior element.
|
|
142
|
+
|
|
143
|
+
Computes the fractional change from the immediately previous row by default.
|
|
144
|
+
This is useful in comparing the fraction of change in a time series of elements.
|
|
145
|
+
|
|
146
|
+
Note
|
|
147
|
+
----
|
|
148
|
+
It calculates fractional change (also known as `per unit change or relative change`)
|
|
149
|
+
and `not percentage change`. If you need the percentage change, multiply these values by 100.
|
|
150
|
+
"""
|
|
151
|
+
data = self.__data()
|
|
152
|
+
data["Returns"] = data["Adj Close"].pct_change()
|
|
153
|
+
data = data.dropna()
|
|
154
|
+
return data["Returns"]
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def volume(self):
|
|
158
|
+
return self.__data()["Volume"]
|
|
159
|
+
|
|
160
|
+
def _validate_time_frame(self, time_frame: str) -> int:
|
|
161
|
+
"""Validates and returns the MT5 timeframe code."""
|
|
162
|
+
if time_frame not in TIMEFRAMES:
|
|
163
|
+
raise ValueError(
|
|
164
|
+
f"Unsupported time frame '{time_frame}'. "
|
|
165
|
+
f"Possible values are: {list(TIMEFRAMES.keys())}"
|
|
166
|
+
)
|
|
167
|
+
return TIMEFRAMES[time_frame]
|
|
168
|
+
|
|
169
|
+
def _fetch_data(
|
|
170
|
+
self,
|
|
171
|
+
start: Union[int, datetime, pd.Timestamp],
|
|
172
|
+
count: Union[int, datetime, pd.Timestamp],
|
|
173
|
+
lower_colnames=False,
|
|
174
|
+
utc=False,
|
|
175
|
+
) -> Union[pd.DataFrame, None]:
|
|
176
|
+
"""Fetches data from MT5 and returns a DataFrame or None."""
|
|
177
|
+
try:
|
|
178
|
+
rates = None
|
|
179
|
+
if isinstance(start, int) and isinstance(count, int):
|
|
180
|
+
rates = Mt5.copy_rates_from_pos(
|
|
181
|
+
self.symbol, self.time_frame, start, count
|
|
182
|
+
)
|
|
183
|
+
elif isinstance(start, (datetime, pd.Timestamp)) and isinstance(count, int):
|
|
184
|
+
rates = Mt5.copy_rates_from(self.symbol, self.time_frame, start, count)
|
|
185
|
+
elif isinstance(start, (datetime, pd.Timestamp)) and isinstance(
|
|
186
|
+
count, (datetime, pd.Timestamp)
|
|
187
|
+
):
|
|
188
|
+
rates = Mt5.copy_rates_range(self.symbol, self.time_frame, start, count)
|
|
189
|
+
if rates is None:
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
df = pd.DataFrame(rates)
|
|
193
|
+
return self._format_dataframe(df, lower_colnames=lower_colnames, utc=utc)
|
|
194
|
+
except Exception as e:
|
|
195
|
+
raise_mt5_error(e)
|
|
196
|
+
|
|
197
|
+
def _format_dataframe(
|
|
198
|
+
self, df: pd.DataFrame, lower_colnames=False, utc=False
|
|
199
|
+
) -> pd.DataFrame:
|
|
200
|
+
"""Formats the raw MT5 data into a standardized DataFrame."""
|
|
201
|
+
|
|
202
|
+
df = df.copy()
|
|
203
|
+
|
|
204
|
+
df = df[["time", "open", "high", "low", "close", "tick_volume"]]
|
|
205
|
+
df.columns = ["Date", "Open", "High", "Low", "Close", "Volume"]
|
|
206
|
+
|
|
207
|
+
df["Adj Close"] = df["Close"]
|
|
208
|
+
df = df[["Date", "Open", "High", "Low", "Close", "Adj Close", "Volume"]]
|
|
209
|
+
|
|
210
|
+
df["Date"] = pd.to_datetime(df["Date"], unit="s", utc=utc)
|
|
211
|
+
df.set_index("Date", inplace=True)
|
|
212
|
+
|
|
213
|
+
if lower_colnames:
|
|
214
|
+
df.columns = df.columns.str.lower().str.replace(" ", "_")
|
|
215
|
+
df.index.name = df.index.name.lower().replace(" ", "_")
|
|
216
|
+
|
|
217
|
+
return df
|
|
218
|
+
|
|
219
|
+
def _filter_data(
|
|
220
|
+
self, df: pd.DataFrame, date_from=None, date_to=None, fill_na=False
|
|
221
|
+
) -> pd.DataFrame:
|
|
222
|
+
df = df.copy()
|
|
223
|
+
symbol_type = self.__account.get_symbol_type(self.symbol)
|
|
224
|
+
currencies = self.__account.get_currency_rates(self.symbol)
|
|
225
|
+
if symbol_type in CALENDARS:
|
|
226
|
+
if symbol_type == SymbolType.STOCKS or symbol_type == SymbolType.ETFs:
|
|
227
|
+
for exchange in CALENDARS[symbol_type]:
|
|
228
|
+
if exchange in get_calendar_names():
|
|
229
|
+
symbols = self.__account.get_stocks_from_exchange(
|
|
230
|
+
exchange_code=exchange
|
|
231
|
+
)
|
|
232
|
+
if self.symbol in symbols:
|
|
233
|
+
calendar = get_calendar(exchange, side="right")
|
|
234
|
+
break
|
|
235
|
+
elif symbol_type == SymbolType.INDICES:
|
|
236
|
+
calendar = get_calendar(
|
|
237
|
+
CALENDARS[symbol_type][currencies["mc"]], side="right"
|
|
238
|
+
)
|
|
239
|
+
else:
|
|
240
|
+
calendar = get_calendar(CALENDARS[symbol_type], side="right")
|
|
241
|
+
date_from = date_from or df.index[0]
|
|
242
|
+
date_to = date_to or df.index[-1]
|
|
243
|
+
if self.time_frame in SESSION_TIMEFRAMES:
|
|
244
|
+
valid_sessions = calendar.sessions_in_range(date_from, date_to)
|
|
245
|
+
else:
|
|
246
|
+
valid_sessions = calendar.minutes_in_range(date_from, date_to)
|
|
247
|
+
if self.time_frame in [Mt5.TIMEFRAME_M1, Mt5.TIMEFRAME_D1]:
|
|
248
|
+
# save the index name of the dataframe
|
|
249
|
+
index_name = df.index.name
|
|
250
|
+
if fill_na:
|
|
251
|
+
if isinstance(fill_na, bool):
|
|
252
|
+
method = "nearest"
|
|
253
|
+
if isinstance(fill_na, str):
|
|
254
|
+
method = fill_na
|
|
255
|
+
df = df.reindex(valid_sessions, method=method)
|
|
256
|
+
else:
|
|
257
|
+
df.reindex(valid_sessions, method=None)
|
|
258
|
+
df.index = df.index.rename(index_name)
|
|
259
|
+
else:
|
|
260
|
+
df = df[df.index.isin(valid_sessions)]
|
|
261
|
+
return df
|
|
262
|
+
|
|
263
|
+
def _check_filter(self, filter, utc):
|
|
264
|
+
if filter and self.time_frame not in SESSION_TIMEFRAMES and not utc:
|
|
265
|
+
utc = True
|
|
266
|
+
elif filter and self.time_frame in SESSION_TIMEFRAMES and utc:
|
|
267
|
+
utc = False
|
|
268
|
+
return utc
|
|
269
|
+
|
|
270
|
+
def get_rates_from_pos(
|
|
271
|
+
self, filter=False, fill_na=False, lower_colnames=False, utc=False
|
|
272
|
+
) -> Union[pd.DataFrame, None]:
|
|
273
|
+
"""
|
|
274
|
+
Retrieves historical data starting from a specific position.
|
|
275
|
+
|
|
276
|
+
Uses the `start_pos` and `count` attributes specified during
|
|
277
|
+
initialization to fetch data.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
filter : See `Rates.get_historical_data` for more details.
|
|
281
|
+
fill_na : See `Rates.get_historical_data` for more details.
|
|
282
|
+
lower_colnames : If True, the column names will be converted to lowercase.
|
|
283
|
+
utc (bool, optional): If True, the data will be in UTC timezone.
|
|
284
|
+
Defaults to False.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Union[pd.DataFrame, None]: A DataFrame containing historical
|
|
288
|
+
data if successful, otherwise None.
|
|
289
|
+
|
|
290
|
+
Raises:
|
|
291
|
+
ValueError: If `start_pos` or `count` is not provided during
|
|
292
|
+
initialization.
|
|
293
|
+
|
|
294
|
+
Notes:
|
|
295
|
+
The Datetime for this method is in Broker's timezone.
|
|
296
|
+
"""
|
|
297
|
+
if self.start_pos is None or self.count is None:
|
|
298
|
+
raise ValueError(
|
|
299
|
+
"Both 'start_pos' and 'count' must be provided "
|
|
300
|
+
"when calling 'get_rates_from_pos'."
|
|
301
|
+
)
|
|
302
|
+
utc = self._check_filter(filter, utc)
|
|
303
|
+
df = self._fetch_data(
|
|
304
|
+
self.start_pos, self.count, lower_colnames=lower_colnames, utc=utc
|
|
305
|
+
)
|
|
306
|
+
if df is None:
|
|
307
|
+
return None
|
|
308
|
+
if filter:
|
|
309
|
+
return self._filter_data(df, fill_na=fill_na)
|
|
310
|
+
return df
|
|
311
|
+
|
|
312
|
+
def get_rates_from(
|
|
313
|
+
self,
|
|
314
|
+
date_from: datetime | pd.Timestamp,
|
|
315
|
+
count: int = MAX_BARS,
|
|
316
|
+
filter=False,
|
|
317
|
+
fill_na=False,
|
|
318
|
+
lower_colnames=False,
|
|
319
|
+
utc=False,
|
|
320
|
+
) -> Union[pd.DataFrame, None]:
|
|
321
|
+
"""
|
|
322
|
+
Retrieves historical data within a specified date range.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
date_from : Starting date for data retrieval.
|
|
326
|
+
The data will be retrieved from this date going to the past.
|
|
327
|
+
|
|
328
|
+
count : Number of bars to retrieve.
|
|
329
|
+
|
|
330
|
+
filter : See `Rates.get_historical_data` for more details.
|
|
331
|
+
fill_na : See `Rates.get_historical_data` for more details.
|
|
332
|
+
lower_colnames : If True, the column names will be converted to lowercase.
|
|
333
|
+
utc (bool, optional): If True, the data will be in UTC timezone.
|
|
334
|
+
Defaults to False.
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
Union[pd.DataFrame, None]: A DataFrame containing historical
|
|
338
|
+
data if successful, otherwise None.
|
|
339
|
+
"""
|
|
340
|
+
utc = self._check_filter(filter, utc)
|
|
341
|
+
df = self._fetch_data(date_from, count, lower_colnames=lower_colnames, utc=utc)
|
|
342
|
+
if df is None:
|
|
343
|
+
return None
|
|
344
|
+
if filter:
|
|
345
|
+
return self._filter_data(df, fill_na=fill_na)
|
|
346
|
+
return df
|
|
347
|
+
|
|
348
|
+
def get_historical_data(
|
|
349
|
+
self,
|
|
350
|
+
date_from: datetime | pd.Timestamp,
|
|
351
|
+
date_to: datetime | pd.Timestamp = pd.Timestamp.now(),
|
|
352
|
+
utc: bool = False,
|
|
353
|
+
filter: Optional[bool] = False,
|
|
354
|
+
fill_na: Optional[bool | str] = False,
|
|
355
|
+
lower_colnames: Optional[bool] = True,
|
|
356
|
+
save_csv: Optional[bool] = False,
|
|
357
|
+
) -> Union[pd.DataFrame, None]:
|
|
358
|
+
"""
|
|
359
|
+
Retrieves historical data within a specified date range.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
date_from : Starting date for data retrieval.
|
|
363
|
+
|
|
364
|
+
date_to : Ending date for data retrieval.
|
|
365
|
+
Defaults to the current time.
|
|
366
|
+
|
|
367
|
+
utc : If True, the data will be in UTC timezone.
|
|
368
|
+
Defaults to False.
|
|
369
|
+
|
|
370
|
+
filter : If True, the data will be filtered based
|
|
371
|
+
on the trading sessions for the symbol.
|
|
372
|
+
This is use when we want to use the data for backtesting using Zipline.
|
|
373
|
+
|
|
374
|
+
fill_na : If True, the data will be filled with the nearest value.
|
|
375
|
+
This is use only when `filter` is True and time frame is "1m" or "D1",
|
|
376
|
+
this is because we use ``calendar.minutes_in_range`` or ``calendar.sessions_in_range``
|
|
377
|
+
where calendar is the ``ExchangeCalendar`` from `exchange_calendars` package.
|
|
378
|
+
So, for "1m" or "D1" time frame, the data will be filled with the nearest value
|
|
379
|
+
because the data from MT5 will have approximately the same number of rows as the
|
|
380
|
+
number of trading days or minute in the exchange calendar, so we can fill the missing
|
|
381
|
+
data with the nearest value.
|
|
382
|
+
|
|
383
|
+
But for other time frames, the data will be reindexed with the exchange calendar
|
|
384
|
+
because the data from MT5 will have more rows than the number of trading days or minute
|
|
385
|
+
in the exchange calendar. So we only take the data that is in the range of the exchange
|
|
386
|
+
calendar sessions or minutes.
|
|
387
|
+
|
|
388
|
+
lower_colnames : If True, the column names will be converted to lowercase.
|
|
389
|
+
|
|
390
|
+
save_csv : File path to save the data as a CSV.
|
|
391
|
+
If None, the data won't be saved.
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
Union[pd.DataFrame, None]: A DataFrame containing historical data
|
|
395
|
+
if successful, otherwise None.
|
|
396
|
+
|
|
397
|
+
Raises:
|
|
398
|
+
ValueError: If the starting date is greater than the ending date.
|
|
399
|
+
|
|
400
|
+
Notes:
|
|
401
|
+
The `filter` for this method can be use only for Admira Markets Group (AMG) symbols.
|
|
402
|
+
The Datetime for this method is in Local timezone by default.
|
|
403
|
+
All STK symbols are filtered based on the the exchange calendar.
|
|
404
|
+
All FX symbols are filtered based on the ``us_futures`` calendar.
|
|
405
|
+
All IDX symbols are filtered based on the exchange calendar of margin currency.
|
|
406
|
+
All COMD symbols are filtered based on the exchange calendar of the commodity.
|
|
407
|
+
"""
|
|
408
|
+
utc = self._check_filter(filter, utc)
|
|
409
|
+
df = self._fetch_data(
|
|
410
|
+
date_from, date_to, lower_colnames=lower_colnames, utc=utc
|
|
411
|
+
)
|
|
412
|
+
if df is None:
|
|
413
|
+
return None
|
|
414
|
+
if filter:
|
|
415
|
+
df = self._filter_data(
|
|
416
|
+
df, date_from=date_from, date_to=date_to, fill_na=fill_na
|
|
417
|
+
)
|
|
418
|
+
if save_csv:
|
|
419
|
+
df.to_csv(f"{self.symbol}.csv")
|
|
420
|
+
return df
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def download_historical_data(
|
|
424
|
+
symbol,
|
|
425
|
+
timeframe,
|
|
426
|
+
date_from,
|
|
427
|
+
date_to=pd.Timestamp.now(),
|
|
428
|
+
lower_colnames=True,
|
|
429
|
+
utc=False,
|
|
430
|
+
filter=False,
|
|
431
|
+
fill_na=False,
|
|
432
|
+
save_csv=False,
|
|
433
|
+
**kwargs,
|
|
434
|
+
):
|
|
435
|
+
"""Download historical data from MetaTrader 5 terminal.
|
|
436
|
+
See `Rates.get_historical_data` for more details.
|
|
437
|
+
"""
|
|
438
|
+
rates = Rates(symbol, timeframe, **kwargs)
|
|
439
|
+
data = rates.get_historical_data(
|
|
440
|
+
date_from=date_from,
|
|
441
|
+
date_to=date_to,
|
|
442
|
+
save_csv=save_csv,
|
|
443
|
+
utc=utc,
|
|
444
|
+
filter=filter,
|
|
445
|
+
lower_colnames=lower_colnames,
|
|
446
|
+
)
|
|
447
|
+
return data
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def get_data_from_pos(
|
|
451
|
+
symbol,
|
|
452
|
+
timeframe,
|
|
453
|
+
start_pos=0,
|
|
454
|
+
fill_na=False,
|
|
455
|
+
count=MAX_BARS,
|
|
456
|
+
lower_colnames=False,
|
|
457
|
+
utc=False,
|
|
458
|
+
filter=False,
|
|
459
|
+
session_duration=23.0,
|
|
460
|
+
**kwargs,
|
|
461
|
+
):
|
|
462
|
+
"""Get historical data from a specific position.
|
|
463
|
+
See `Rates.get_rates_from_pos` for more details.
|
|
464
|
+
"""
|
|
465
|
+
rates = Rates(symbol, timeframe, start_pos, count, session_duration, **kwargs)
|
|
466
|
+
data = rates.get_rates_from_pos(
|
|
467
|
+
filter=filter, fill_na=fill_na, lower_colnames=lower_colnames, utc=utc
|
|
468
|
+
)
|
|
469
|
+
return data
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def get_data_from_date(
|
|
473
|
+
symbol,
|
|
474
|
+
timeframe,
|
|
475
|
+
date_from,
|
|
476
|
+
count=MAX_BARS,
|
|
477
|
+
fill_na=False,
|
|
478
|
+
lower_colnames=False,
|
|
479
|
+
utc=False,
|
|
480
|
+
filter=False,
|
|
481
|
+
**kwargs,
|
|
482
|
+
):
|
|
483
|
+
"""Get historical data from a specific date.
|
|
484
|
+
See `Rates.get_rates_from` for more details.
|
|
485
|
+
"""
|
|
486
|
+
rates = Rates(symbol, timeframe, **kwargs)
|
|
487
|
+
data = rates.get_rates_from(
|
|
488
|
+
date_from,
|
|
489
|
+
count,
|
|
490
|
+
filter=filter,
|
|
491
|
+
fill_na=fill_na,
|
|
492
|
+
lower_colnames=lower_colnames,
|
|
493
|
+
utc=utc,
|
|
494
|
+
)
|
|
495
|
+
return data
|