cryptodatapy 0.2.5__py3-none-any.whl → 0.2.7__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.
- cryptodatapy/conf/fields.csv +1 -1
- cryptodatapy/conf/tickers.csv +0 -1
- cryptodatapy/extract/data_vendors/CoinMetrics.ipynb +747 -0
- cryptodatapy/extract/data_vendors/coinmetrics_api.py +279 -209
- cryptodatapy/extract/data_vendors/cryptocompare_api.py +3 -5
- cryptodatapy/extract/data_vendors/datavendor.py +32 -12
- cryptodatapy/extract/data_vendors/glassnode_api.py +3 -2
- cryptodatapy/extract/data_vendors/tiingo_api.py +3 -2
- cryptodatapy/extract/datarequest.py +197 -36
- cryptodatapy/extract/libraries/Untitled.ipynb +33 -0
- cryptodatapy/extract/libraries/ccxt.ipynb +628 -754
- cryptodatapy/extract/libraries/ccxt_api.py +630 -346
- cryptodatapy/extract/libraries/pandasdr_api.py +13 -12
- cryptodatapy/extract/libraries/yfinance_api.py +511 -0
- cryptodatapy/transform/cc_onchain_data.csv +118423 -0
- cryptodatapy/transform/clean.py +17 -15
- cryptodatapy/transform/clean_onchain_data.ipynb +4750 -0
- cryptodatapy/transform/clean_perp_futures_ohlcv.ipynb +1712 -1097
- cryptodatapy/transform/cmdty_data.ipynb +402 -0
- cryptodatapy/transform/convertparams.py +139 -181
- cryptodatapy/transform/credit_data.ipynb +291 -0
- cryptodatapy/transform/eqty_data.ipynb +836 -0
- cryptodatapy/transform/filter.py +13 -10
- cryptodatapy/transform/global_credit_data_daily.parquet +0 -0
- cryptodatapy/transform/od.py +1 -0
- cryptodatapy/transform/rates_data.ipynb +465 -0
- cryptodatapy/transform/us_rates_daily.csv +227752 -0
- cryptodatapy/transform/wrangle.py +109 -20
- cryptodatapy/util/datacredentials.py +28 -7
- {cryptodatapy-0.2.5.dist-info → cryptodatapy-0.2.7.dist-info}/METADATA +10 -7
- {cryptodatapy-0.2.5.dist-info → cryptodatapy-0.2.7.dist-info}/RECORD +33 -31
- {cryptodatapy-0.2.5.dist-info → cryptodatapy-0.2.7.dist-info}/WHEEL +1 -1
- cryptodatapy/.DS_Store +0 -0
- cryptodatapy/.idea/.gitignore +0 -3
- cryptodatapy/.idea/cryptodatapy.iml +0 -12
- cryptodatapy/.idea/csv-plugin.xml +0 -16
- cryptodatapy/.idea/inspectionProfiles/Project_Default.xml +0 -6
- cryptodatapy/.idea/inspectionProfiles/profiles_settings.xml +0 -6
- cryptodatapy/.idea/misc.xml +0 -4
- cryptodatapy/.idea/modules.xml +0 -8
- cryptodatapy/.idea/vcs.xml +0 -6
- {cryptodatapy-0.2.5.dist-info → cryptodatapy-0.2.7.dist-info}/LICENSE +0 -0
@@ -1,9 +1,11 @@
|
|
1
1
|
import logging
|
2
|
-
from time import sleep
|
3
2
|
from typing import Any, Dict, List, Optional, Union
|
4
3
|
|
5
|
-
import ccxt
|
6
4
|
import pandas as pd
|
5
|
+
import asyncio
|
6
|
+
import ccxt
|
7
|
+
import ccxt.async_support as ccxt_async
|
8
|
+
from tqdm.asyncio import tqdm # Progress bar for async
|
7
9
|
|
8
10
|
from cryptodatapy.extract.datarequest import DataRequest
|
9
11
|
from cryptodatapy.extract.libraries.library import Library
|
@@ -27,11 +29,11 @@ class CCXT(Library):
|
|
27
29
|
assets: Optional[Dict[str, List[str]]] = None,
|
28
30
|
markets: Optional[Dict[str, List[str]]] = None,
|
29
31
|
market_types: List[str] = ["spot", "future", "perpetual_future", "option"],
|
30
|
-
fields: Optional[List[str]] =
|
31
|
-
frequencies: Optional[Dict[str,
|
32
|
+
fields: Optional[List[str]] = ["open", "high", "low", "close", "volume", "funding_rate", 'oi'],
|
33
|
+
frequencies: Optional[Dict[str, Union[str, int]]] = None,
|
32
34
|
base_url: Optional[str] = None,
|
33
35
|
api_key: Optional[str] = None,
|
34
|
-
max_obs_per_call: Optional[int] =
|
36
|
+
max_obs_per_call: Optional[int] = 1000,
|
35
37
|
rate_limit: Optional[Any] = None,
|
36
38
|
):
|
37
39
|
"""
|
@@ -84,6 +86,11 @@ class CCXT(Library):
|
|
84
86
|
rate_limit,
|
85
87
|
)
|
86
88
|
|
89
|
+
self.exchange = None
|
90
|
+
self.exchange_async = None
|
91
|
+
self.data_req = None
|
92
|
+
self.data = pd.DataFrame()
|
93
|
+
|
87
94
|
def get_exchanges_info(self) -> List[str]:
|
88
95
|
"""
|
89
96
|
Get exchanges info.
|
@@ -93,7 +100,8 @@ class CCXT(Library):
|
|
93
100
|
exch: list or pd.DataFrame
|
94
101
|
List or dataframe with info on supported exchanges.
|
95
102
|
"""
|
96
|
-
self.exchanges
|
103
|
+
if self.exchanges is None:
|
104
|
+
self.exchanges = ccxt.exchanges
|
97
105
|
|
98
106
|
return self.exchanges
|
99
107
|
|
@@ -103,17 +111,13 @@ class CCXT(Library):
|
|
103
111
|
"""
|
104
112
|
return None
|
105
113
|
|
106
|
-
def get_assets_info(
|
107
|
-
self,
|
108
|
-
exch: str = "binance",
|
109
|
-
as_list: bool = False
|
110
|
-
) -> Union[pd.DataFrame, List[str]]:
|
114
|
+
def get_assets_info(self, exch: str, as_list: bool = False) -> Union[pd.DataFrame, List[str]]:
|
111
115
|
"""
|
112
116
|
Get assets info.
|
113
117
|
|
114
118
|
Parameters
|
115
119
|
----------
|
116
|
-
exch: str
|
120
|
+
exch: str
|
117
121
|
Name of exchange.
|
118
122
|
as_list: bool, default False
|
119
123
|
Returns assets info for selected exchanges as list.
|
@@ -123,23 +127,31 @@ class CCXT(Library):
|
|
123
127
|
assets: list or pd.DataFrame
|
124
128
|
Dataframe with info on available assets or list of assets.
|
125
129
|
"""
|
126
|
-
|
127
|
-
|
130
|
+
if self.assets is None:
|
131
|
+
|
132
|
+
# inst exchange
|
133
|
+
if exch not in ccxt.exchanges:
|
134
|
+
raise ValueError(
|
135
|
+
f"{exch} is not a supported exchange. "
|
136
|
+
f"Use get_exchanges_info() to get a list of supported exchanges.")
|
137
|
+
else:
|
138
|
+
if self.exchange is None:
|
139
|
+
self.exchange = getattr(ccxt, exch)()
|
128
140
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
141
|
+
# get assets on exchange and create df
|
142
|
+
self.exchange.load_markets()
|
143
|
+
self.assets = pd.DataFrame(self.exchange.currencies).T
|
144
|
+
self.assets.index.name = "ticker"
|
133
145
|
|
134
|
-
|
135
|
-
|
136
|
-
|
146
|
+
# as list of assets
|
147
|
+
if as_list:
|
148
|
+
self.assets = self.assets.index.to_list()
|
137
149
|
|
138
150
|
return self.assets
|
139
151
|
|
140
152
|
def get_markets_info(
|
141
153
|
self,
|
142
|
-
exch: str
|
154
|
+
exch: str,
|
143
155
|
quote_ccy: Optional[str] = None,
|
144
156
|
mkt_type: Optional[str] = None,
|
145
157
|
as_list: bool = False,
|
@@ -149,7 +161,7 @@ class CCXT(Library):
|
|
149
161
|
|
150
162
|
Parameters
|
151
163
|
----------
|
152
|
-
exch: str
|
164
|
+
exch: str
|
153
165
|
Name of exchange.
|
154
166
|
quote_ccy: str, optional, default None
|
155
167
|
Quote currency.
|
@@ -163,29 +175,37 @@ class CCXT(Library):
|
|
163
175
|
markets: list or pd.DataFrame
|
164
176
|
List or dataframe with info on available markets, by exchange.
|
165
177
|
"""
|
166
|
-
|
167
|
-
|
178
|
+
if self.markets is None:
|
179
|
+
|
180
|
+
# inst exchange
|
181
|
+
if exch not in ccxt.exchanges:
|
182
|
+
raise ValueError(
|
183
|
+
f"{exch} is not a supported exchange. "
|
184
|
+
f"Use get_exchanges_info() to get a list of supported exchanges.")
|
185
|
+
else:
|
186
|
+
if self.exchange is None:
|
187
|
+
self.exchange = getattr(ccxt, exch)()
|
168
188
|
|
169
|
-
|
170
|
-
|
171
|
-
|
189
|
+
# get assets on exchange
|
190
|
+
self.markets = pd.DataFrame(self.exchange.load_markets()).T
|
191
|
+
self.markets.index.name = "ticker"
|
172
192
|
|
173
|
-
|
174
|
-
|
175
|
-
|
193
|
+
# quote ccy
|
194
|
+
if quote_ccy is not None:
|
195
|
+
self.markets = self.markets[self.markets.quote == quote_ccy.upper()]
|
176
196
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
197
|
+
# mkt type
|
198
|
+
if mkt_type == "perpetual_future":
|
199
|
+
if self.markets[self.markets.type == "swap"].empty:
|
200
|
+
self.markets = self.markets[self.markets.type == "future"]
|
201
|
+
else:
|
202
|
+
self.markets = self.markets[self.markets.type == "swap"]
|
203
|
+
elif mkt_type == "spot" or mkt_type == "future" or mkt_type == "option":
|
204
|
+
self.markets = self.markets[self.markets.type == mkt_type]
|
185
205
|
|
186
|
-
|
187
|
-
|
188
|
-
|
206
|
+
# dict of assets
|
207
|
+
if as_list:
|
208
|
+
self.markets = self.markets.index.to_list()
|
189
209
|
|
190
210
|
return self.markets
|
191
211
|
|
@@ -198,18 +218,18 @@ class CCXT(Library):
|
|
198
218
|
fields: list
|
199
219
|
List of available fields.
|
200
220
|
"""
|
201
|
-
|
202
|
-
|
221
|
+
if self.fields is None:
|
222
|
+
self.fields = ["open", "high", "low", "close", "volume", "funding_rate", 'oi']
|
203
223
|
|
204
224
|
return self.fields
|
205
225
|
|
206
|
-
def get_frequencies_info(self, exch: str
|
226
|
+
def get_frequencies_info(self, exch: str) -> Dict[str, Union[str, int]]:
|
207
227
|
"""
|
208
228
|
Get frequencies info.
|
209
229
|
|
210
230
|
Parameters
|
211
231
|
----------
|
212
|
-
exch: str
|
232
|
+
exch: str
|
213
233
|
Name of exchange for which to get available assets.
|
214
234
|
|
215
235
|
Returns
|
@@ -217,426 +237,661 @@ class CCXT(Library):
|
|
217
237
|
freq: dictionary
|
218
238
|
Dictionary with info on available frequencies.
|
219
239
|
"""
|
220
|
-
|
221
|
-
exchange = getattr(ccxt, exch)()
|
222
|
-
exchange.load_markets()
|
240
|
+
if self.frequencies is None:
|
223
241
|
|
224
|
-
|
225
|
-
|
242
|
+
# inst exchange
|
243
|
+
if exch not in ccxt.exchanges:
|
244
|
+
raise ValueError(
|
245
|
+
f"{exch} is not a supported exchange. "
|
246
|
+
f"Use get_exchanges_info() to get a list of supported exchanges.")
|
247
|
+
else:
|
248
|
+
if self.exchange is None:
|
249
|
+
self.exchange = getattr(ccxt, exch)()
|
250
|
+
|
251
|
+
# freq dict
|
252
|
+
self.frequencies = self.exchange.timeframes
|
226
253
|
|
227
254
|
return self.frequencies
|
228
255
|
|
229
|
-
def get_rate_limit_info(self, exch: str
|
256
|
+
def get_rate_limit_info(self, exch: str) -> Dict[str, Union[str, int]]:
|
230
257
|
"""
|
231
258
|
Get rate limit info.
|
232
259
|
|
233
260
|
Parameters
|
234
261
|
----------
|
235
|
-
exch: str
|
262
|
+
exch: str
|
236
263
|
Name of exchange.
|
237
264
|
|
238
265
|
Returns
|
239
266
|
-------
|
240
267
|
rate_limit: dictionary
|
241
268
|
Dictionary with exchange and required minimal delay between HTTP requests that exchange in milliseconds.
|
269
|
+
|
242
270
|
"""
|
243
|
-
|
244
|
-
|
271
|
+
if self.rate_limit is None:
|
272
|
+
|
273
|
+
# inst exchange
|
274
|
+
if exch not in ccxt.exchanges:
|
275
|
+
raise ValueError(
|
276
|
+
f"{exch} is not a supported exchange. "
|
277
|
+
f"Use get_exchanges_info() to get a list of supported exchanges.")
|
278
|
+
else:
|
279
|
+
if self.exchange is None:
|
280
|
+
self.exchange = getattr(ccxt, exch)()
|
281
|
+
|
282
|
+
self.rate_limit = {
|
283
|
+
"exchange rate limit":
|
284
|
+
"delay in milliseconds between two consequent HTTP requests to the same exchange",
|
285
|
+
exch: self.exchange.rateLimit
|
286
|
+
}
|
245
287
|
|
246
|
-
self.rate_limit = {
|
247
|
-
"exchange rate limit": "delay in milliseconds between two consequent HTTP requests to the same exchange",
|
248
|
-
exch: exchange.rateLimit
|
249
|
-
}
|
250
288
|
return self.rate_limit
|
251
289
|
|
252
|
-
def get_metadata(self) -> None:
|
290
|
+
def get_metadata(self, exch: str) -> None:
|
253
291
|
"""
|
254
292
|
Get CCXT metadata.
|
293
|
+
|
294
|
+
Parameters
|
295
|
+
----------
|
296
|
+
exch: str
|
297
|
+
Name of exchange.
|
255
298
|
"""
|
299
|
+
# inst exchange
|
300
|
+
if exch not in ccxt.exchanges:
|
301
|
+
raise ValueError(
|
302
|
+
f"{exch} is not a supported exchange. Use get_exchanges_info() to get a list of supported exchanges.")
|
303
|
+
else:
|
304
|
+
if self.exchange is None:
|
305
|
+
self.exchange = getattr(ccxt, exch)()
|
306
|
+
|
307
|
+
# load markets
|
308
|
+
self.exchange.load_markets()
|
309
|
+
|
256
310
|
if self.exchanges is None:
|
257
311
|
self.exchanges = self.get_exchanges_info()
|
258
312
|
if self.market_types is None:
|
259
313
|
self.market_types = ["spot", "future", "perpetual_future", "option"]
|
260
314
|
if self.assets is None:
|
261
|
-
self.assets = self.
|
315
|
+
self.assets = list(self.exchange.currencies.keys())
|
262
316
|
if self.markets is None:
|
263
|
-
self.markets = self.
|
317
|
+
self.markets = list(self.exchange.markets.keys())
|
264
318
|
if self.fields is None:
|
265
|
-
self.fields =
|
319
|
+
self.fields = ["open", "high", "low", "close", "volume", "funding_rate", 'oi']
|
266
320
|
if self.frequencies is None:
|
267
|
-
self.frequencies = self.
|
321
|
+
self.frequencies = list(self.exchange.timeframes.keys())
|
268
322
|
if self.rate_limit is None:
|
269
|
-
self.rate_limit = self.
|
323
|
+
self.rate_limit = self.exchange.rateLimit
|
270
324
|
|
271
|
-
def
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
325
|
+
async def _fetch_ohlcv(self,
|
326
|
+
ticker: str,
|
327
|
+
freq: str,
|
328
|
+
start_date: str,
|
329
|
+
end_date: str,
|
330
|
+
exch: str,
|
331
|
+
trials: int = 3
|
332
|
+
) -> List:
|
277
333
|
"""
|
278
|
-
|
334
|
+
Fetches OHLCV data for a specific ticker.
|
279
335
|
|
280
336
|
Parameters
|
281
337
|
----------
|
282
|
-
data_req: DataRequest
|
283
|
-
Parameters of data request in CryptoDataPy format.
|
284
|
-
data_type: str, {'ohlcv', 'funding_rates'},
|
285
|
-
Data type to retrieve.
|
286
338
|
ticker: str
|
287
|
-
Ticker symbol
|
339
|
+
Ticker symbol.
|
340
|
+
freq: str
|
341
|
+
Frequency of data, e.g. '1m', '5m', '1h', '1d'.
|
288
342
|
start_date: str
|
289
|
-
Start date in
|
290
|
-
|
343
|
+
Start date in integers in milliseconds since Unix epoch.
|
344
|
+
end_date: str
|
345
|
+
End date in integers in milliseconds since Unix epoch.
|
346
|
+
exch: str
|
347
|
+
Name of exchange.
|
348
|
+
trials: int, default 3
|
349
|
+
Number of attempts to fetch data.
|
291
350
|
|
292
351
|
Returns
|
293
352
|
-------
|
294
|
-
|
295
|
-
|
353
|
+
data: list
|
354
|
+
List of timestamps with OHLCV data.
|
296
355
|
"""
|
297
|
-
|
298
|
-
|
299
|
-
if start_date is None:
|
300
|
-
start_date = cx_data_req['start_date']
|
301
|
-
|
302
|
-
# data types
|
303
|
-
data_types = {'ohlcv': 'fetchOHLCV', 'funding_rates': 'fetchFundingRateHistory'}
|
356
|
+
attempts = 0
|
357
|
+
data = []
|
304
358
|
|
305
359
|
# inst exch
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
360
|
+
if self.exchange_async is None:
|
361
|
+
self.exchange_async = getattr(ccxt_async, exch)()
|
362
|
+
|
363
|
+
# fetch data
|
364
|
+
if self.exchange_async.has['fetchOHLCV']:
|
365
|
+
|
366
|
+
# while loop to fetch all data
|
367
|
+
while start_date < end_date and attempts < trials:
|
368
|
+
|
369
|
+
try:
|
370
|
+
data_resp = await getattr(self.exchange_async, 'fetchOHLCV')(
|
371
|
+
ticker,
|
372
|
+
freq,
|
373
|
+
since=start_date,
|
374
|
+
limit=self.max_obs_per_call,
|
375
|
+
params={'until': end_date}
|
376
|
+
)
|
323
377
|
|
324
|
-
|
378
|
+
except Exception as e:
|
379
|
+
logging.warning(
|
380
|
+
f"Failed to get OHLCV data from {self.exchange_async.id} for {ticker} on attempt #{attempts+1}."
|
381
|
+
)
|
382
|
+
logging.warning(e)
|
383
|
+
attempts += 1
|
384
|
+
if attempts == trials:
|
385
|
+
logging.warning(
|
386
|
+
f"Failed to get OHLCV data from {self.exchange_async.id} "
|
387
|
+
f"for {ticker} after {trials} attempts."
|
388
|
+
)
|
389
|
+
return data
|
390
|
+
|
391
|
+
await asyncio.sleep(self.exchange_async.rateLimit / 1000)
|
392
|
+
continue
|
393
|
+
|
394
|
+
else:
|
395
|
+
# check if data resp is empty
|
396
|
+
if len(data_resp):
|
397
|
+
# next start date
|
398
|
+
start_date = data_resp[-1][0] + 1
|
399
|
+
data.extend(data_resp)
|
400
|
+
await asyncio.sleep(self.exchange_async.rateLimit / 1000)
|
401
|
+
|
402
|
+
else:
|
403
|
+
break
|
325
404
|
|
326
|
-
|
327
|
-
logging.warning(f"Failed to get {data_type} data for {ticker}.")
|
328
|
-
logging.warning(e)
|
405
|
+
return data
|
329
406
|
|
407
|
+
else:
|
408
|
+
logging.warning(f"OHLCV data is not available for {self.exchange_async.id}.")
|
330
409
|
return None
|
331
410
|
|
332
|
-
def
|
411
|
+
async def fetch_all_ohlcv(self,
|
412
|
+
tickers,
|
413
|
+
freq: str,
|
414
|
+
start_date: str,
|
415
|
+
end_date: str,
|
416
|
+
exch: str,
|
417
|
+
trials: int = 3,
|
418
|
+
pause: int = 0.5
|
419
|
+
):
|
333
420
|
"""
|
334
|
-
|
335
|
-
|
421
|
+
Fetches OHLCV data for a list of tickers.
|
422
|
+
|
423
|
+
Parameters
|
424
|
+
----------
|
425
|
+
tickers: list
|
426
|
+
List of ticker symbols.
|
427
|
+
freq: str
|
428
|
+
Frequency of data, e.g. '1m', '5m', '1h', '1d'.
|
429
|
+
start_date: str
|
430
|
+
Start date in integers in milliseconds since Unix epoch.
|
431
|
+
end_date: str
|
432
|
+
End date in integers in milliseconds since Unix epoch.
|
433
|
+
exch: str
|
434
|
+
Name of exchange.
|
435
|
+
trials: int, default 3
|
436
|
+
Number of attempts to fetch data.
|
437
|
+
pause: int, default 0.5
|
438
|
+
Pause in seconds to respect the rate limit.
|
439
|
+
|
440
|
+
Returns
|
441
|
+
-------
|
442
|
+
data: list
|
443
|
+
List of lists of timestamps and OHLCV data for each ticker.
|
444
|
+
"""
|
445
|
+
# inst exch
|
446
|
+
if self.exchange_async is None:
|
447
|
+
self.exchange_async = getattr(ccxt_async, exch)()
|
448
|
+
|
449
|
+
data = []
|
450
|
+
|
451
|
+
# create progress bar
|
452
|
+
pbar = tqdm(total=len(tickers), desc="Fetching OHLCV data", unit="ticker")
|
453
|
+
|
454
|
+
# loop through tickers
|
455
|
+
for ticker in tickers:
|
456
|
+
data_resp = await self._fetch_ohlcv(ticker, freq, start_date, end_date, trials=trials, exch=exch)
|
457
|
+
data.append(data_resp)
|
458
|
+
pbar.update(1)
|
459
|
+
await asyncio.sleep(pause) # pause between ticker requests to respect the rate limit
|
460
|
+
|
461
|
+
await self.exchange_async.close()
|
462
|
+
|
463
|
+
return data
|
464
|
+
|
465
|
+
async def _fetch_funding_rates(self,
|
466
|
+
ticker: str,
|
467
|
+
start_date: str,
|
468
|
+
end_date: str,
|
469
|
+
exch: str,
|
470
|
+
trials: int = 3
|
471
|
+
) -> List:
|
472
|
+
"""
|
473
|
+
Fetches funding rates data for a specific ticker.
|
336
474
|
|
337
475
|
Parameters
|
338
476
|
----------
|
339
|
-
data_req: DataRequest
|
340
|
-
Parameters of data request in CryptoDataPy format.
|
341
477
|
ticker: str
|
342
478
|
Ticker symbol.
|
479
|
+
start_date: str
|
480
|
+
Start date in integers in milliseconds since Unix epoch.
|
481
|
+
end_date: str
|
482
|
+
End date in integers in milliseconds since Unix epoch.
|
483
|
+
trials: int, default 3
|
484
|
+
Number of attempts to fetch data.
|
343
485
|
|
344
486
|
Returns
|
345
487
|
-------
|
346
|
-
|
347
|
-
|
488
|
+
data: list
|
489
|
+
List of dictionaries with timestamps and funding rates data.
|
348
490
|
"""
|
349
|
-
|
350
|
-
|
351
|
-
start_date = cx_data_req['start_date']
|
491
|
+
attempts = 0
|
492
|
+
data = []
|
352
493
|
|
353
|
-
#
|
354
|
-
|
494
|
+
# inst exch
|
495
|
+
if self.exchange_async is None:
|
496
|
+
self.exchange_async = getattr(ccxt_async, exch)()
|
355
497
|
|
356
|
-
#
|
357
|
-
|
498
|
+
# fetch data
|
499
|
+
if self.exchange_async.has['fetchFundingRateHistory']:
|
358
500
|
|
359
|
-
|
360
|
-
|
501
|
+
# while loop to get all data
|
502
|
+
while start_date < end_date and attempts < trials:
|
361
503
|
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
504
|
+
try:
|
505
|
+
data_resp = await getattr(self.exchange_async, 'fetchFundingRateHistory')(
|
506
|
+
ticker,
|
507
|
+
since=start_date,
|
508
|
+
limit=self.max_obs_per_call,
|
509
|
+
params={'until': end_date}
|
510
|
+
)
|
366
511
|
|
367
|
-
|
368
|
-
attempts += 1
|
369
|
-
sleep(self.get_rate_limit_info(exch=cx_data_req['exch'])[cx_data_req['exch']] / 1000)
|
370
|
-
logging.warning(
|
371
|
-
f"Failed to pull data on attempt #{attempts}."
|
372
|
-
)
|
373
|
-
if attempts == cx_data_req["trials"]:
|
512
|
+
except Exception as e:
|
374
513
|
logging.warning(
|
375
|
-
f"Failed to get
|
514
|
+
f"Failed to get funding rates from {self.exchange_async.id} "
|
515
|
+
f"for {ticker} on attempt #{attempts+1}."
|
376
516
|
)
|
377
|
-
|
517
|
+
logging.warning(e)
|
518
|
+
attempts += 1
|
519
|
+
if attempts == trials:
|
520
|
+
logging.warning(
|
521
|
+
f"Failed to get funding rates from {self.exchange_async.id} "
|
522
|
+
f"for {ticker} after {trials} attempts."
|
523
|
+
)
|
524
|
+
return data
|
525
|
+
|
526
|
+
await asyncio.sleep(self.exchange_async.rateLimit / 1000)
|
527
|
+
continue
|
378
528
|
|
379
|
-
else:
|
380
|
-
# name cols and create df
|
381
|
-
header = ["datetime", "open", "high", "low", "close", "volume"]
|
382
|
-
data = pd.DataFrame(data_resp, columns=header)
|
383
|
-
df = pd.concat([df, data])
|
384
|
-
|
385
|
-
# check if all data has been extracted
|
386
|
-
time_diff = cx_data_req["end_date"] - df.datetime.iloc[-1]
|
387
|
-
if pd.Timedelta(milliseconds=time_diff) < pd.Timedelta(cx_data_req["freq"]):
|
388
|
-
missing_vals = False
|
389
|
-
# missing data, infinite loop
|
390
|
-
elif df.datetime.iloc[-1] == df.datetime.iloc[-2]:
|
391
|
-
missing_vals = False
|
392
|
-
logging.warning(f"Missing recent OHLCV data for {ticker}.")
|
393
|
-
# reset end date and pause before calling API
|
394
529
|
else:
|
395
|
-
#
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
530
|
+
# check if data resp is empty
|
531
|
+
if len(data_resp):
|
532
|
+
# next start date
|
533
|
+
start_date = data_resp[-1]['timestamp'] + 1
|
534
|
+
data.extend(data_resp)
|
535
|
+
await asyncio.sleep(self.exchange_async.rateLimit / 1000)
|
536
|
+
else:
|
537
|
+
break
|
538
|
+
|
539
|
+
return data
|
540
|
+
|
541
|
+
else:
|
542
|
+
logging.warning(f"Funding rates are not available for {self.exchange_async.id}.")
|
543
|
+
return None
|
402
544
|
|
403
|
-
def
|
545
|
+
async def fetch_all_funding_rates(self,
|
546
|
+
tickers,
|
547
|
+
start_date: str,
|
548
|
+
end_date: str,
|
549
|
+
exch: str,
|
550
|
+
trials: int = 3,
|
551
|
+
pause: int = 0.5
|
552
|
+
):
|
404
553
|
"""
|
405
|
-
|
406
|
-
number of observations is larger than the maximum number of observations per call.
|
554
|
+
Fetches funding rates data for a list of tickers.
|
407
555
|
|
408
556
|
Parameters
|
409
557
|
----------
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
558
|
+
tickers: list
|
559
|
+
List of ticker symbols.
|
560
|
+
start_date: str
|
561
|
+
Start date in integers in milliseconds since Unix epoch.
|
562
|
+
end_date: str
|
563
|
+
End date in integers in milliseconds since Unix epoch.
|
564
|
+
exch: str
|
565
|
+
Name of exchange.
|
566
|
+
trials: int, default 3
|
567
|
+
Number of attempts to fetch data.
|
568
|
+
pause: int, default 0.5
|
569
|
+
Pause in seconds to respect the rate limit.
|
414
570
|
|
415
571
|
Returns
|
416
572
|
-------
|
417
|
-
|
418
|
-
|
419
|
-
"""
|
420
|
-
#
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
# create empty df
|
425
|
-
df = pd.DataFrame()
|
426
|
-
# while loop condition
|
427
|
-
missing_vals, attempts = True, 0
|
428
|
-
|
429
|
-
# run a while loop until all data collected
|
430
|
-
while missing_vals and attempts < cx_data_req['trials']:
|
431
|
-
|
432
|
-
# data req
|
433
|
-
data_resp = self.req_data(data_req=data_req,
|
434
|
-
data_type='funding_rates',
|
435
|
-
ticker=ticker,
|
436
|
-
start_date=start_date)
|
437
|
-
|
438
|
-
if data_resp is None:
|
439
|
-
attempts += 1
|
440
|
-
sleep(self.get_rate_limit_info(exch=cx_data_req['exch'])[cx_data_req['exch']] / 1000)
|
441
|
-
logging.warning(
|
442
|
-
f"Failed to pull data on attempt #{attempts}."
|
443
|
-
)
|
444
|
-
if attempts == cx_data_req["trials"]:
|
445
|
-
logging.warning(
|
446
|
-
f"Failed to get funding_rates from {cx_data_req['exch']} for {ticker} after many attempts."
|
447
|
-
)
|
448
|
-
return None
|
573
|
+
data: list
|
574
|
+
List of lists of dictionaries with timestamps and funding rates data for each ticker.
|
575
|
+
"""
|
576
|
+
# inst exch
|
577
|
+
if self.exchange_async is None:
|
578
|
+
self.exchange_async = getattr(ccxt_async, exch)()
|
449
579
|
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
# check if all data has been extracted
|
455
|
-
time_diff = pd.to_datetime(
|
456
|
-
cx_data_req["end_date"], unit="ms"
|
457
|
-
) - pd.to_datetime(data.datetime.iloc[-1]).tz_localize(None)
|
458
|
-
if time_diff < pd.Timedelta("8h"):
|
459
|
-
missing_vals = False
|
460
|
-
# missing data, infinite loop
|
461
|
-
elif df.datetime.iloc[-1] == df.datetime.iloc[-2]:
|
462
|
-
missing_vals = False
|
463
|
-
logging.warning(f"Missing recent funding rate data for {ticker}.")
|
464
|
-
# reset end date and pause before calling API
|
465
|
-
else:
|
466
|
-
# change end date
|
467
|
-
start_date = data.timestamp.iloc[-1]
|
580
|
+
data = []
|
581
|
+
|
582
|
+
# create progress bar
|
583
|
+
pbar = tqdm(total=len(tickers), desc="Fetching funding rates", unit="ticker")
|
468
584
|
|
469
|
-
|
470
|
-
|
585
|
+
# loop through tickers
|
586
|
+
for ticker in tickers:
|
587
|
+
data_resp = await self._fetch_funding_rates(ticker, start_date, end_date, trials=trials, exch=exch)
|
588
|
+
data.append(data_resp)
|
589
|
+
pbar.update(1)
|
590
|
+
await asyncio.sleep(pause) # pause between ticker requests to respect the rate limit
|
471
591
|
|
472
|
-
|
592
|
+
await self.exchange_async.close()
|
473
593
|
|
474
|
-
|
475
|
-
|
594
|
+
return data
|
595
|
+
|
596
|
+
async def _fetch_open_interest(self,
|
597
|
+
ticker: str,
|
598
|
+
freq: str,
|
599
|
+
start_date: str,
|
600
|
+
end_date: str,
|
601
|
+
exch: str,
|
602
|
+
trials: int = 3
|
603
|
+
) -> List:
|
476
604
|
"""
|
477
|
-
|
605
|
+
Fetches open interest data for a specific ticker.
|
478
606
|
|
479
607
|
Parameters
|
480
608
|
----------
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
609
|
+
ticker: str
|
610
|
+
Ticker symbol.
|
611
|
+
freq: str
|
612
|
+
Frequency of data, e.g. '1m', '5m', '1h', '1d'.
|
613
|
+
start_date: str
|
614
|
+
Start date in integers in milliseconds since Unix epoch.
|
615
|
+
end_date: str
|
616
|
+
End date in integers in milliseconds since Unix epoch.
|
617
|
+
exch: str
|
618
|
+
Name of exchange.
|
619
|
+
trials: int, default 3
|
620
|
+
Number of attempts to fetch data.
|
485
621
|
|
486
622
|
Returns
|
487
623
|
-------
|
488
|
-
|
489
|
-
|
624
|
+
data: list
|
625
|
+
List of dictionaries with timestamps and open interest data.
|
490
626
|
"""
|
627
|
+
# number of attempts
|
628
|
+
attempts = 0
|
629
|
+
data = []
|
630
|
+
|
631
|
+
# inst exch
|
632
|
+
if self.exchange_async is None:
|
633
|
+
self.exchange_async = getattr(ccxt_async, exch)()
|
634
|
+
|
635
|
+
# fetch data
|
636
|
+
if self.exchange_async.has['fetchOpenInterestHistory']:
|
637
|
+
|
638
|
+
# while loop to get all data
|
639
|
+
while start_date < end_date and attempts < trials:
|
640
|
+
|
641
|
+
try:
|
642
|
+
data_resp = await getattr(self.exchange_async, 'fetchOpenInterestHistory')(
|
643
|
+
ticker,
|
644
|
+
freq,
|
645
|
+
since=start_date,
|
646
|
+
limit=500,
|
647
|
+
params={'until': end_date}
|
648
|
+
)
|
649
|
+
|
650
|
+
except Exception as e:
|
651
|
+
logging.warning(
|
652
|
+
f"Failed to get open interest from {self.exchange_async.id} "
|
653
|
+
f"for {ticker} on attempt #{attempts + 1}."
|
654
|
+
)
|
655
|
+
logging.warning(e)
|
656
|
+
attempts += 1
|
657
|
+
if attempts == trials:
|
658
|
+
logging.warning(
|
659
|
+
f"Failed to get open interest from {self.exchange_async.id} "
|
660
|
+
f"for {ticker} after {trials} attempts."
|
661
|
+
)
|
662
|
+
return data
|
663
|
+
|
664
|
+
await asyncio.sleep(self.exchange_async.rateLimit / 1000)
|
665
|
+
continue
|
666
|
+
|
667
|
+
else:
|
668
|
+
# check if data resp is empty
|
669
|
+
if len(data_resp):
|
670
|
+
# next start date
|
671
|
+
start_date = data_resp[-1]['timestamp'] + 1
|
672
|
+
data.extend(data_resp)
|
673
|
+
await asyncio.sleep(self.exchange_async.rateLimit / 1000)
|
674
|
+
else:
|
675
|
+
break
|
676
|
+
|
677
|
+
return data
|
678
|
+
|
679
|
+
else:
|
680
|
+
logging.warning(f"Open interest is not available for {self.exchange_async.id}.")
|
681
|
+
return None
|
491
682
|
|
492
|
-
|
683
|
+
async def fetch_all_open_interest(self,
|
684
|
+
tickers,
|
685
|
+
freq: str,
|
686
|
+
start_date: str,
|
687
|
+
end_date: str,
|
688
|
+
exch: str,
|
689
|
+
trials: int = 3,
|
690
|
+
pause: int = 0.5
|
691
|
+
):
|
493
692
|
|
494
|
-
def fetch_tidy_ohlcv(self, data_req: DataRequest, ticker: str) -> pd.DataFrame:
|
495
693
|
"""
|
496
|
-
|
694
|
+
Fetches open interest data for a list of tickers.
|
497
695
|
|
498
696
|
Parameters
|
499
697
|
----------
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
698
|
+
tickers: list
|
699
|
+
List of ticker symbols.
|
700
|
+
freq: str
|
701
|
+
Frequency of data, e.g. '1m', '5m', '1h', '1d'.
|
702
|
+
start_date: str
|
703
|
+
Start date in integers in milliseconds since Unix epoch.
|
704
|
+
end_date: str
|
705
|
+
End date in integers in milliseconds since Unix epoch.
|
706
|
+
exch: str
|
707
|
+
Name of exchange.
|
708
|
+
trials: int, default 3
|
709
|
+
Number of attempts to fetch data.
|
710
|
+
pause: int, default 0.5
|
711
|
+
Pause in seconds to respect the rate limit.
|
504
712
|
|
505
713
|
Returns
|
506
714
|
-------
|
507
|
-
|
508
|
-
|
715
|
+
data: list
|
716
|
+
List of lists of dictionaries with timestamps and open interest data for each ticker.
|
509
717
|
"""
|
510
|
-
#
|
511
|
-
|
718
|
+
# inst exch
|
719
|
+
if self.exchange_async is None:
|
720
|
+
self.exchange_async = getattr(ccxt_async, exch)()
|
512
721
|
|
513
|
-
|
514
|
-
if df is not None:
|
515
|
-
df = self.wrangle_data_resp(data_req, df)
|
722
|
+
data = []
|
516
723
|
|
517
|
-
|
724
|
+
# create progress bar
|
725
|
+
pbar = tqdm(total=len(tickers), desc="Fetching open interest", unit="ticker")
|
518
726
|
|
519
|
-
|
727
|
+
# loop through tickers
|
728
|
+
for ticker in tickers:
|
729
|
+
data_resp = await self._fetch_open_interest(ticker, freq, start_date, end_date, trials=trials, exch=exch)
|
730
|
+
data.append(data_resp)
|
731
|
+
pbar.update(1)
|
732
|
+
await asyncio.sleep(pause) # pause between ticker requests to respect the rate limit
|
733
|
+
|
734
|
+
await self.exchange_async.close()
|
735
|
+
|
736
|
+
return data
|
737
|
+
|
738
|
+
def convert_params(self, data_req: DataRequest) -> Dict[str, Any]:
|
520
739
|
"""
|
521
|
-
|
740
|
+
Converts data request parameters to CCXT format.
|
522
741
|
|
523
742
|
Parameters
|
524
743
|
----------
|
525
744
|
data_req: DataRequest
|
526
745
|
Parameters of data request in CryptoDataPy format.
|
527
|
-
ticker: str
|
528
|
-
Ticker symbol.
|
529
746
|
|
530
747
|
Returns
|
531
748
|
-------
|
532
|
-
|
533
|
-
|
749
|
+
cx_data_req: dict
|
750
|
+
Data request parameters in CCXT format.
|
534
751
|
"""
|
535
|
-
|
536
|
-
df = self.fetch_all_funding_hist(data_req, ticker)
|
752
|
+
self.data_req = ConvertParams(data_req).to_ccxt()
|
537
753
|
|
538
|
-
#
|
539
|
-
|
540
|
-
df = self.wrangle_data_resp(data_req, df)
|
754
|
+
# get metadata
|
755
|
+
self.get_metadata(self.data_req.exch)
|
541
756
|
|
542
|
-
|
757
|
+
# check markets
|
758
|
+
if not any([market in self.markets for market in self.data_req.source_markets]):
|
759
|
+
raise ValueError(
|
760
|
+
f"Selected markets are not available. Use the '.markets' attribute to check supported markets."
|
761
|
+
)
|
543
762
|
|
544
|
-
|
545
|
-
|
546
|
-
|
763
|
+
# check freq
|
764
|
+
if self.data_req.source_freq not in self.frequencies:
|
765
|
+
raise ValueError(
|
766
|
+
f"{self.data_req.source_freq} frequency is not available. "
|
767
|
+
f"Use the '.frequencies' attribute to check available frequencies."
|
768
|
+
)
|
547
769
|
|
548
|
-
|
549
|
-
|
550
|
-
|
770
|
+
# check quote ccy
|
771
|
+
if self.data_req.quote_ccy is not None:
|
772
|
+
if self.data_req.quote_ccy not in self.assets:
|
773
|
+
raise ValueError(
|
774
|
+
f"{self.data_req.quote_ccy} is not supported. "
|
775
|
+
f"Use the '.assets' attribute to check supported currencies."
|
776
|
+
)
|
551
777
|
|
552
|
-
#
|
553
|
-
|
778
|
+
# mkt type
|
779
|
+
if self.data_req.mkt_type not in self.market_types:
|
780
|
+
raise ValueError(
|
781
|
+
f"{self.data_req.mkt_type} is not available for {self.data_req.exch}."
|
782
|
+
)
|
783
|
+
|
784
|
+
# start date
|
785
|
+
if not isinstance(self.data_req.source_start_date, int):
|
786
|
+
raise ValueError(
|
787
|
+
f"Start date must be in integers in milliseconds since Unix epoch."
|
788
|
+
)
|
554
789
|
|
555
|
-
#
|
556
|
-
|
557
|
-
if not any([ticker.upper() in tickers for ticker in cx_data_req["tickers"]]):
|
790
|
+
# end date
|
791
|
+
if not isinstance(self.data_req.source_end_date, int):
|
558
792
|
raise ValueError(
|
559
|
-
f"
|
793
|
+
f"End date must be in integers in milliseconds since Unix epoch."
|
794
|
+
)
|
560
795
|
|
561
|
-
# check
|
562
|
-
fields
|
563
|
-
if not any([field in fields for field in data_req.fields]):
|
796
|
+
# check fields
|
797
|
+
if not any([field in self.fields for field in self.data_req.fields]):
|
564
798
|
raise ValueError(
|
565
|
-
f"
|
799
|
+
f"Selected fields are not available for {self.data_req.exch}. "
|
800
|
+
f"Use fields attribute to check available fields."
|
566
801
|
)
|
567
802
|
|
568
|
-
# check
|
569
|
-
if
|
803
|
+
# check ohlcv
|
804
|
+
if any([field in ['open', 'high', 'low', 'close', 'volume'] for field in self.data_req.fields]) and \
|
805
|
+
not self.exchange.has["fetchOHLCV"]:
|
570
806
|
raise ValueError(
|
571
|
-
f"
|
807
|
+
f"OHLCV data is not available for {self.data_req.exch}."
|
808
|
+
f" Try another exchange or data request."
|
572
809
|
)
|
573
810
|
|
574
|
-
# check
|
575
|
-
if any([field
|
576
|
-
not
|
811
|
+
# check funding rates
|
812
|
+
if any([field == 'funding_rate' for field in self.data_req.fields]) and \
|
813
|
+
not self.exchange.has["fetchFundingRateHistory"]:
|
577
814
|
raise ValueError(
|
578
|
-
f"
|
815
|
+
f"Funding rates are not available for {self.data_req.exch}."
|
579
816
|
f" Try another exchange or data request."
|
580
817
|
)
|
581
818
|
|
582
|
-
# check
|
583
|
-
if any([field == '
|
584
|
-
not
|
819
|
+
# check open interest
|
820
|
+
if any([field == 'oi' for field in self.data_req.fields]) and \
|
821
|
+
not self.exchange.has["fetchOpenInterestHistory"]:
|
585
822
|
raise ValueError(
|
586
|
-
f"
|
823
|
+
f"Open interest is not available for {self.data_req.exch}."
|
587
824
|
f" Try another exchange or data request."
|
588
825
|
)
|
589
826
|
|
590
|
-
# check
|
591
|
-
if any([field == 'funding_rate' for field in data_req.fields]) and \
|
592
|
-
data_req.mkt_type
|
827
|
+
# check perp future
|
828
|
+
if any([(field == 'funding_rate' or field == 'open_interest') for field in self.data_req.fields]) and \
|
829
|
+
self.data_req.mkt_type not in ['perpetual_future', 'future']:
|
593
830
|
raise ValueError(
|
594
|
-
f"
|
595
|
-
f"
|
831
|
+
f"You have requested fields only available for futures markets."
|
832
|
+
f" Change mkt_type to 'perpetual_future' or 'future'."
|
596
833
|
)
|
597
834
|
|
598
|
-
|
835
|
+
return self.data_req
|
836
|
+
|
837
|
+
@staticmethod
|
838
|
+
def wrangle_data_resp(data_req: DataRequest, data_resp: pd.DataFrame, data_type: str) -> pd.DataFrame:
|
599
839
|
"""
|
600
|
-
|
601
|
-
multiindex dataframe.
|
840
|
+
Wrangle data response.
|
602
841
|
|
603
842
|
Parameters
|
604
843
|
----------
|
605
844
|
data_req: DataRequest
|
606
845
|
Parameters of data request in CryptoDataPy format.
|
846
|
+
data_resp: pd.DataFrame
|
847
|
+
Data response from GET request.
|
848
|
+
data_type: str
|
849
|
+
Type of data, e.g. 'ohlcv', 'funding_rate', 'open_interest'.
|
607
850
|
|
608
851
|
Returns
|
609
852
|
-------
|
610
|
-
df: pd.DataFrame
|
611
|
-
|
853
|
+
df: pd.DataFrame
|
854
|
+
Wrangled dataframe with DatetimeIndex and values in tidy format.
|
612
855
|
"""
|
613
|
-
# convert data request parameters to CCXT format
|
614
|
-
cx_data_req = ConvertParams(data_req).to_ccxt()
|
615
856
|
|
616
|
-
|
617
|
-
self.check_params(data_req)
|
857
|
+
return WrangleData(data_req, data_resp).ccxt(data_type=data_type)
|
618
858
|
|
619
|
-
|
620
|
-
|
859
|
+
async def fetch_tidy_ohlcv(self, data_req: DataRequest) -> pd.DataFrame:
|
860
|
+
"""
|
861
|
+
Gets entire OHLCV history and wrangles the data response into tidy data format.
|
621
862
|
|
622
|
-
|
623
|
-
|
863
|
+
Parameters
|
864
|
+
----------
|
865
|
+
data_req: DataRequest
|
866
|
+
Parameters of data request in CryptoDataPy format.
|
624
867
|
|
625
|
-
|
868
|
+
Returns
|
869
|
+
-------
|
870
|
+
df: pd.DataFrame
|
871
|
+
Dataframe with entire OHLCV data history retrieved and wrangled into tidy data format.
|
872
|
+
"""
|
873
|
+
# convert data request parameters to CCXT format
|
874
|
+
self.convert_params(data_req)
|
626
875
|
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
876
|
+
# get entire data history
|
877
|
+
data_resp = await self.fetch_all_ohlcv(self.data_req.source_markets,
|
878
|
+
self.data_req.source_freq,
|
879
|
+
self.data_req.source_start_date,
|
880
|
+
self.data_req.source_end_date,
|
881
|
+
self.data_req.exch,
|
882
|
+
trials=self.data_req.trials,
|
883
|
+
pause=self.data_req.pause)
|
633
884
|
|
634
|
-
|
885
|
+
# wrangle df
|
886
|
+
if any(data_resp):
|
887
|
+
df = self.wrangle_data_resp(data_req, data_resp, data_type='ohlcv')
|
888
|
+
return df
|
889
|
+
else:
|
890
|
+
logging.warning("Failed to get requested OHLCV data.")
|
635
891
|
|
636
|
-
def
|
892
|
+
async def fetch_tidy_funding_rates(self, data_req: DataRequest) -> pd.DataFrame:
|
637
893
|
"""
|
638
|
-
|
639
|
-
multiindex dataframe.
|
894
|
+
Gets entire funding rates history and wrangles the data response into tidy data format.
|
640
895
|
|
641
896
|
Parameters
|
642
897
|
----------
|
@@ -645,33 +900,61 @@ class CCXT(Library):
|
|
645
900
|
|
646
901
|
Returns
|
647
902
|
-------
|
648
|
-
df: pd.DataFrame
|
649
|
-
Dataframe with
|
903
|
+
df: pd.DataFrame
|
904
|
+
Dataframe with entire data history retrieved and wrangled into tidy data format.
|
650
905
|
"""
|
651
906
|
# convert data request parameters to CCXT format
|
652
|
-
|
907
|
+
self.convert_params(data_req)
|
653
908
|
|
654
|
-
#
|
655
|
-
self.
|
909
|
+
# get entire data history
|
910
|
+
data_resp = await self.fetch_all_funding_rates(self.data_req.source_markets,
|
911
|
+
self.data_req.source_start_date,
|
912
|
+
self.data_req.source_end_date,
|
913
|
+
self.data_req.exch,
|
914
|
+
trials=self.data_req.trials,
|
915
|
+
pause=self.data_req.pause)
|
656
916
|
|
657
|
-
#
|
658
|
-
|
917
|
+
# wrangle df
|
918
|
+
if any(data_resp):
|
919
|
+
df = self.wrangle_data_resp(data_req, data_resp, data_type='funding_rates')
|
920
|
+
return df
|
921
|
+
else:
|
922
|
+
logging.warning("Failed to get requested funding rates.")
|
659
923
|
|
660
|
-
|
661
|
-
|
924
|
+
async def fetch_tidy_open_interest(self, data_req: DataRequest) -> pd.DataFrame:
|
925
|
+
"""
|
926
|
+
Gets entire open interest history and wrangles the data response into tidy data format.
|
662
927
|
|
663
|
-
|
928
|
+
Parameters
|
929
|
+
----------
|
930
|
+
data_req: DataRequest
|
931
|
+
Parameters of data request in CryptoDataPy format.
|
932
|
+
|
933
|
+
Returns
|
934
|
+
-------
|
935
|
+
df: pd.DataFrame
|
936
|
+
Dataframe with entire data history retrieved and wrangled into tidy data format.
|
937
|
+
"""
|
938
|
+
# convert data request parameters to CCXT format
|
939
|
+
self.convert_params(data_req)
|
664
940
|
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
941
|
+
# get entire data history
|
942
|
+
data_resp = await self.fetch_all_open_interest(self.data_req.source_markets,
|
943
|
+
self.data_req.source_freq,
|
944
|
+
self.data_req.source_start_date,
|
945
|
+
self.data_req.source_end_date,
|
946
|
+
self.data_req.exch,
|
947
|
+
trials=self.data_req.trials,
|
948
|
+
pause=self.data_req.pause)
|
671
949
|
|
672
|
-
|
950
|
+
# wrangle df
|
951
|
+
if any(data_resp):
|
952
|
+
df = self.wrangle_data_resp(data_req, data_resp, data_type='open_interest')
|
953
|
+
return df
|
954
|
+
else:
|
955
|
+
logging.warning("Failed to get requested open interest.")
|
673
956
|
|
674
|
-
def get_data(self, data_req: DataRequest) -> pd.DataFrame:
|
957
|
+
async def get_data(self, data_req: DataRequest) -> pd.DataFrame:
|
675
958
|
"""
|
676
959
|
Get data specified by data request.
|
677
960
|
|
@@ -684,28 +967,29 @@ class CCXT(Library):
|
|
684
967
|
df: pd.DataFrame - MultiIndex
|
685
968
|
DataFrame with DatetimeIndex (level 0), ticker (level 1), and values for selected fields (cols).
|
686
969
|
"""
|
687
|
-
#
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
ohlcv_list = ["open", "high", "low", "close", "volume"]
|
692
|
-
if any([field in ohlcv_list for field in data_req.fields]):
|
693
|
-
df0 = self.fetch_ohlcv(data_req)
|
694
|
-
df = pd.concat([df, df0])
|
970
|
+
# get OHLCV
|
971
|
+
if any([field in ["open", "high", "low", "close", "volume"] for field in data_req.fields]):
|
972
|
+
df = await self.fetch_tidy_ohlcv(data_req)
|
973
|
+
self.data = pd.concat([self.data, df])
|
695
974
|
|
696
|
-
# get funding
|
975
|
+
# get funding rates
|
697
976
|
if any([field == "funding_rate" for field in data_req.fields]):
|
698
|
-
|
699
|
-
|
977
|
+
df = await self.fetch_tidy_funding_rates(data_req)
|
978
|
+
self.data = pd.concat([self.data, df], axis=1)
|
979
|
+
|
980
|
+
# get open interest
|
981
|
+
if any([field == "oi" for field in data_req.fields]):
|
982
|
+
df = await self.fetch_tidy_open_interest(data_req)
|
983
|
+
self.data = pd.concat([self.data, df], axis=1)
|
700
984
|
|
701
|
-
# check
|
702
|
-
if
|
985
|
+
# check df
|
986
|
+
if self.data.empty:
|
703
987
|
raise Exception(
|
704
988
|
"No data returned. Check data request parameters and try again."
|
705
989
|
)
|
706
990
|
|
707
991
|
# filter df for desired fields and typecast
|
708
|
-
fields = [field for field in data_req.fields if field in
|
709
|
-
|
992
|
+
fields = [field for field in data_req.fields if field in self.data.columns]
|
993
|
+
self.data = self.data.loc[:, fields]
|
710
994
|
|
711
|
-
return
|
995
|
+
return self.data.sort_index()
|