cryptodatapy 0.2.5__py3-none-any.whl → 0.2.6__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.
Files changed (36) hide show
  1. cryptodatapy/conf/tickers.csv +0 -1
  2. cryptodatapy/extract/data_vendors/CoinMetrics.ipynb +747 -0
  3. cryptodatapy/extract/data_vendors/coinmetrics_api.py +279 -209
  4. cryptodatapy/extract/data_vendors/cryptocompare_api.py +3 -5
  5. cryptodatapy/extract/data_vendors/datavendor.py +32 -12
  6. cryptodatapy/extract/data_vendors/glassnode_api.py +3 -2
  7. cryptodatapy/extract/data_vendors/tiingo_api.py +3 -2
  8. cryptodatapy/extract/datarequest.py +55 -9
  9. cryptodatapy/extract/libraries/ccxt_api.py +13 -2
  10. cryptodatapy/transform/cc_onchain_data.csv +118423 -0
  11. cryptodatapy/transform/clean.py +17 -15
  12. cryptodatapy/transform/clean_onchain_data.ipynb +4750 -0
  13. cryptodatapy/transform/clean_perp_futures_ohlcv.ipynb +1597 -1178
  14. cryptodatapy/transform/convertparams.py +28 -18
  15. cryptodatapy/transform/credit_data.ipynb +291 -0
  16. cryptodatapy/transform/eqty_data.ipynb +809 -0
  17. cryptodatapy/transform/filter.py +13 -10
  18. cryptodatapy/transform/global_credit_data_daily.parquet +0 -0
  19. cryptodatapy/transform/od.py +1 -0
  20. cryptodatapy/transform/rates_data.ipynb +465 -0
  21. cryptodatapy/transform/us_rates_daily.csv +227752 -0
  22. cryptodatapy/util/datacredentials.py +28 -7
  23. {cryptodatapy-0.2.5.dist-info → cryptodatapy-0.2.6.dist-info}/METADATA +2 -2
  24. {cryptodatapy-0.2.5.dist-info → cryptodatapy-0.2.6.dist-info}/RECORD +26 -28
  25. cryptodatapy/.DS_Store +0 -0
  26. cryptodatapy/.idea/.gitignore +0 -3
  27. cryptodatapy/.idea/cryptodatapy.iml +0 -12
  28. cryptodatapy/.idea/csv-plugin.xml +0 -16
  29. cryptodatapy/.idea/inspectionProfiles/Project_Default.xml +0 -6
  30. cryptodatapy/.idea/inspectionProfiles/profiles_settings.xml +0 -6
  31. cryptodatapy/.idea/misc.xml +0 -4
  32. cryptodatapy/.idea/modules.xml +0 -8
  33. cryptodatapy/.idea/vcs.xml +0 -6
  34. cryptodatapy/extract/libraries/ccxt.ipynb +0 -873
  35. {cryptodatapy-0.2.5.dist-info → cryptodatapy-0.2.6.dist-info}/LICENSE +0 -0
  36. {cryptodatapy-0.2.5.dist-info → cryptodatapy-0.2.6.dist-info}/WHEEL +0 -0
@@ -84,8 +84,9 @@ class CryptoCompare(DataVendor):
84
84
  if categories is None:
85
85
  self.categories = ['crypto']
86
86
  if api_key is None:
87
- raise TypeError("Set your api key. We recommend setting your api key in environment variables as"
88
- "'CRYPTOCOMPARE_API_KEY', will allow DataCredentials to automatically load it.")
87
+ raise TypeError("Set your CryptoCompare api key in environment variables as 'CRYPTOCOMPARE_API_KEY' or "
88
+ "add it as an argument when instantiating the class. To get an api key, visit: "
89
+ "https://min-api.cryptocompare.com/")
89
90
  if exchanges is None:
90
91
  self.exchanges = self.get_exchanges_info(as_list=True)
91
92
  if indexes is None:
@@ -467,9 +468,6 @@ class CryptoCompare(DataVendor):
467
468
  urls_params = self.set_urls_params(data_req, data_type, ticker)
468
469
  url, params = urls_params['url'], urls_params['params']
469
470
 
470
- # # data req
471
- # data_resp = DataRequest().get_req(url=url, params=params)
472
-
473
471
  # data req
474
472
  data_resp = DataRequest().get_req(url=url, params=params)
475
473
 
@@ -92,7 +92,7 @@ class DataVendor(ABC):
92
92
 
93
93
  @exchanges.setter
94
94
  def exchanges(
95
- self, exchanges: Optional[Union[str, List[str], Dict[str, List[str]]]]
95
+ self, exchanges: Optional[Union[str, List[str], Dict[str, List[str]], pd.DataFrame]]
96
96
  ):
97
97
  """
98
98
  Sets a list of available exchanges for the data vendor.
@@ -101,6 +101,7 @@ class DataVendor(ABC):
101
101
  exchanges is None
102
102
  or isinstance(exchanges, list)
103
103
  or isinstance(exchanges, dict)
104
+ or isinstance(exchanges, pd.DataFrame)
104
105
  ):
105
106
  self._exchanges = exchanges
106
107
  elif isinstance(exchanges, str):
@@ -126,11 +127,16 @@ class DataVendor(ABC):
126
127
  return self._indexes
127
128
 
128
129
  @indexes.setter
129
- def indexes(self, indexes: Optional[Union[str, List[str], Dict[str, List[str]]]]):
130
+ def indexes(self, indexes: Optional[Union[str, List[str], Dict[str, List[str]], pd.DataFrame]]):
130
131
  """
131
132
  Sets a list of available indexes for the data vendor.
132
133
  """
133
- if indexes is None or isinstance(indexes, list) or isinstance(indexes, dict):
134
+ if (
135
+ indexes is None
136
+ or isinstance(indexes, list)
137
+ or isinstance(indexes, dict)
138
+ or isinstance(indexes, pd.DataFrame)
139
+ ):
134
140
  self._indexes = indexes
135
141
  elif isinstance(indexes, str):
136
142
  self._indexes = [indexes]
@@ -155,11 +161,16 @@ class DataVendor(ABC):
155
161
  return self._assets
156
162
 
157
163
  @assets.setter
158
- def assets(self, assets: Optional[Union[str, List[str], Dict[str, List[str]]]]):
164
+ def assets(self, assets: Optional[Union[str, List[str], Dict[str, List[str]], pd.DataFrame]]):
159
165
  """
160
166
  Sets a list of available assets for the data vendor.
161
167
  """
162
- if assets is None or isinstance(assets, list) or isinstance(assets, dict):
168
+ if (
169
+ assets is None
170
+ or isinstance(assets, list)
171
+ or isinstance(assets, dict)
172
+ or isinstance(assets, pd.DataFrame)
173
+ ):
163
174
  self._assets = assets
164
175
  elif isinstance(assets, str):
165
176
  self._assets = [assets]
@@ -184,11 +195,16 @@ class DataVendor(ABC):
184
195
  return self._markets
185
196
 
186
197
  @markets.setter
187
- def markets(self, markets: Optional[Union[str, List[str], Dict[str, List[str]]]]):
198
+ def markets(self, markets: Optional[Union[str, List[str], Dict[str, List[str]], pd.DataFrame]]):
188
199
  """
189
200
  Sets a list of available markets for the data vendor.
190
201
  """
191
- if markets is None or isinstance(markets, list) or isinstance(markets, dict):
202
+ if (
203
+ markets is None
204
+ or isinstance(markets, list)
205
+ or isinstance(markets, dict)
206
+ or isinstance(markets, pd.DataFrame)
207
+ ):
192
208
  self._markets = markets
193
209
  elif isinstance(markets, str):
194
210
  self._markets = [markets]
@@ -251,11 +267,16 @@ class DataVendor(ABC):
251
267
  return self._fields
252
268
 
253
269
  @fields.setter
254
- def fields(self, fields: Optional[Union[str, List[str], Dict[str, List[str]]]]):
270
+ def fields(self, fields: Optional[Union[str, List[str], Dict[str, List[str]], pd.DataFrame]]):
255
271
  """
256
272
  Sets a list of available fields for the data vendor.
257
273
  """
258
- if fields is None or isinstance(fields, list) or isinstance(fields, dict):
274
+ if (
275
+ fields is None
276
+ or isinstance(fields, list)
277
+ or isinstance(fields, dict)
278
+ or isinstance(fields, pd.DataFrame)
279
+ ):
259
280
  self._fields = fields
260
281
  elif isinstance(fields, str):
261
282
  self._fields = [fields]
@@ -280,9 +301,7 @@ class DataVendor(ABC):
280
301
  return self._frequencies
281
302
 
282
303
  @frequencies.setter
283
- def frequencies(
284
- self, frequencies: Optional[Union[str, List[str], Dict[str, List[str]]]]
285
- ):
304
+ def frequencies(self, frequencies: Optional[Union[str, List[str], Dict[str, List[str]], pd.DataFrame]]):
286
305
  """
287
306
  Sets a list of available data frequencies for the data vendor.
288
307
  """
@@ -290,6 +309,7 @@ class DataVendor(ABC):
290
309
  frequencies is None
291
310
  or isinstance(frequencies, list)
292
311
  or isinstance(frequencies, dict)
312
+ or isinstance(frequencies, pd.DataFrame)
293
313
  ):
294
314
  self._frequencies = frequencies
295
315
  elif isinstance(frequencies, str):
@@ -79,8 +79,9 @@ class Glassnode(DataVendor):
79
79
  if categories is None:
80
80
  self.categories = ['crypto']
81
81
  if api_key is None:
82
- raise TypeError("Set your api key. We recommend setting your api key in environment variables as"
83
- "'GLASSNODE_API_KEY', will allow DataCredentials to automatically load it.")
82
+ raise TypeError("Set your Glassnode api key in environment variables as 'GLASSNODE_API_KEY' or "
83
+ "add it as an argument when instantiating the class. To get an api key, visit: "
84
+ "https://docs.glassnode.com/basic-api/api-key")
84
85
  if assets is None:
85
86
  self.assets = self.get_assets_info(as_list=True)
86
87
  if fields is None:
@@ -109,8 +109,9 @@ class Tiingo(DataVendor):
109
109
  if categories is None:
110
110
  self.categories = ["crypto", "fx", "eqty"]
111
111
  if api_key is None:
112
- raise TypeError("Set your api key. We recommend setting your api key in environment variables as"
113
- "'TIINGO_API_KEY', will allow DataCredentials to automatically load it.")
112
+ raise TypeError("Set your Tiingo api key in environment variables as 'TIINGO_API_KEY' or "
113
+ "add it as an argument when instantiating the class. To get an api key, visit: "
114
+ "https://www.tiingo.com/")
114
115
  if exchanges is None:
115
116
  self.exchanges = self.get_exchanges_info()
116
117
  if assets is None:
@@ -197,7 +197,9 @@ class DataRequest:
197
197
  "y": "yearly",
198
198
  }
199
199
 
200
- if frequency not in list(freq_dict.keys()):
200
+ if frequency is None:
201
+ self._frequency = frequency
202
+ elif frequency not in list(freq_dict.keys()):
201
203
  raise ValueError(
202
204
  f"{frequency} is an invalid data frequency. Valid frequencies are: {freq_dict}"
203
205
  )
@@ -555,15 +557,59 @@ class DataRequest:
555
557
  # get request
556
558
  try:
557
559
  resp = requests.get(url, params=params, headers=headers)
558
- assert resp.status_code == 200
559
- # exception
560
- except AssertionError as e:
561
- logging.warning(e)
560
+ # check for status code
561
+ resp.raise_for_status()
562
+
563
+ return resp.json()
564
+
565
+ # handle HTTP errors
566
+ except requests.exceptions.HTTPError as http_err:
567
+ status_code = resp.status_code
568
+
569
+ # Tailored handling for different status codes
570
+ if status_code == 400:
571
+ logging.warning(f"Bad Request (400): {resp.text}")
572
+ elif status_code == 401:
573
+ logging.warning("Unauthorized (401): Check the authentication credentials.")
574
+ elif status_code == 403:
575
+ logging.warning("Forbidden (403): You do not have permission to access this resource.")
576
+ elif status_code == 404:
577
+ logging.warning("Not Found (404): The requested resource could not be found.")
578
+ elif status_code == 500:
579
+ logging.error("Internal Server Error (500): The server encountered an error.")
580
+ elif status_code == 503:
581
+ logging.error("Service Unavailable (503): The server is temporarily unavailable.")
582
+ else:
583
+ logging.error(f"HTTP error occurred: {http_err} (Status Code: {status_code})")
584
+ logging.error(f"Response Content: {resp.text}")
585
+
586
+ # Increment attempts and log warning
587
+ attempts += 1
588
+ logging.warning(f"Attempt #{attempts}: Failed to get data due to: {http_err}")
589
+ sleep(self.pause) # Pause before retrying
590
+ if attempts == self.trials:
591
+ logging.error("Max attempts reached. Unable to fetch data.")
592
+ break
593
+
594
+ # handle non-HTTP exceptions (e.g., network issues)
595
+ except requests.exceptions.RequestException as req_err:
562
596
  attempts += 1
563
- logging.warning(f"Failed to get data on attempt #{attempts}.")
597
+ logging.warning(f"Request error on attempt #{attempts}: {req_err}. "
598
+ f"Retrying after {self.pause} seconds...")
564
599
  sleep(self.pause)
565
- if attempts == 3:
600
+ if attempts == self.trials:
601
+ logging.error("Max attempts reached. Unable to fetch data due to request errors.")
566
602
  break
567
603
 
568
- else:
569
- return resp.json()
604
+ # handle other exceptions
605
+ except Exception as e:
606
+ attempts += 1
607
+ logging.warning(f"An unexpected error occurred: {e}. "
608
+ f"Retrying after {self.pause} seconds...")
609
+ sleep(self.pause)
610
+ if attempts == self.trials:
611
+ logging.error("Max attempts reached. Unable to fetch data due to request errors.")
612
+ break
613
+
614
+ # return None if the API call fails
615
+ return None
@@ -273,6 +273,7 @@ class CCXT(Library):
273
273
  data_type: str,
274
274
  ticker: str,
275
275
  start_date: str = None,
276
+ end_date: str = None,
276
277
  ) -> pd.DataFrame:
277
278
  """
278
279
  Sends data request to Python client.
@@ -287,6 +288,8 @@ class CCXT(Library):
287
288
  Ticker symbol to request data for.
288
289
  start_date: str
289
290
  Start date in 'YYYY-MM-DD' format.
291
+ end_date: str
292
+ End date in 'YYYY-MM-DD' format.
290
293
 
291
294
 
292
295
  Returns
@@ -298,6 +301,8 @@ class CCXT(Library):
298
301
  cx_data_req = ConvertParams(data_req).to_ccxt()
299
302
  if start_date is None:
300
303
  start_date = cx_data_req['start_date']
304
+ if end_date is None:
305
+ end_date = cx_data_req['end_date']
301
306
 
302
307
  # data types
303
308
  data_types = {'ohlcv': 'fetchOHLCV', 'funding_rates': 'fetchFundingRateHistory'}
@@ -313,12 +318,14 @@ class CCXT(Library):
313
318
  cx_data_req["freq"],
314
319
  since=start_date,
315
320
  limit=self.max_obs_per_call,
321
+ params={'until': end_date}
316
322
  )
317
323
  elif data_type == 'funding_rates':
318
324
  data_resp = getattr(exch, data_types[data_type])(
319
325
  ticker,
320
326
  since=start_date,
321
327
  limit=1000,
328
+ params={'until': end_date}
322
329
  )
323
330
 
324
331
  return data_resp
@@ -349,6 +356,7 @@ class CCXT(Library):
349
356
  # convert data request parameters to CCXT format and set start date
350
357
  cx_data_req = ConvertParams(data_req).to_ccxt()
351
358
  start_date = cx_data_req['start_date']
359
+ end_date = cx_data_req['end_date']
352
360
 
353
361
  # create empty df
354
362
  df = pd.DataFrame()
@@ -362,7 +370,8 @@ class CCXT(Library):
362
370
  data_resp = self.req_data(data_req=data_req,
363
371
  data_type='ohlcv',
364
372
  ticker=ticker,
365
- start_date=start_date)
373
+ start_date=start_date,
374
+ end_date=end_date)
366
375
 
367
376
  if data_resp is None:
368
377
  attempts += 1
@@ -420,6 +429,7 @@ class CCXT(Library):
420
429
  # convert data request parameters to CCXT format and set start date
421
430
  cx_data_req = ConvertParams(data_req).to_ccxt()
422
431
  start_date = cx_data_req['start_date']
432
+ end_date = cx_data_req['end_date']
423
433
 
424
434
  # create empty df
425
435
  df = pd.DataFrame()
@@ -433,7 +443,8 @@ class CCXT(Library):
433
443
  data_resp = self.req_data(data_req=data_req,
434
444
  data_type='funding_rates',
435
445
  ticker=ticker,
436
- start_date=start_date)
446
+ start_date=start_date,
447
+ end_date=end_date)
437
448
 
438
449
  if data_resp is None:
439
450
  attempts += 1