cryptodatapy 0.2.8__py3-none-any.whl → 0.2.9__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.
@@ -12,7 +12,6 @@ class DataRequest:
12
12
  """
13
13
  Data request class which contains parameters for data retrieval.
14
14
  """
15
-
16
15
  def __init__(
17
16
  self,
18
17
  source: str = "ccxt",
@@ -0,0 +1,2 @@
1
+ from cryptodatapy.extract.exchanges.exchange import Exchange
2
+ from cryptodatapy.extract.exchanges.dydx import Dydx
@@ -0,0 +1,137 @@
1
+ import logging
2
+ from typing import Any, Dict, List, Optional, Union
3
+
4
+ import pandas as pd
5
+ import asyncio
6
+
7
+ from cryptodatapy.extract.datarequest import DataRequest
8
+ from cryptodatapy.extract.exchanges.exchange import Exchange
9
+ from cryptodatapy.transform.convertparams import ConvertParams
10
+ from cryptodatapy.transform.wrangle import WrangleData
11
+ from cryptodatapy.util.datacredentials import DataCredentials
12
+
13
+ # data credentials
14
+ data_cred = DataCredentials()
15
+
16
+
17
+ class Dydx(Exchange):
18
+ """
19
+ Retrieves data from dydx exchange.
20
+ """
21
+ def __init__(
22
+ self,
23
+ name: str = "dydx",
24
+ exch_type: str = "dex",
25
+ is_active: bool = True,
26
+ categories: Union[str, List[str]] = "crypto",
27
+ assets: Optional[Dict[str, List[str]]] = None,
28
+ markets: Optional[Dict[str, List[str]]] = None,
29
+ market_types: List[str] = ["spot", "future", "perpetual_future", "option"],
30
+ fields: Optional[List[str]] = ["open", "high", "low", "close", "volume", "funding_rate", 'oi'],
31
+ frequencies: Optional[Dict[str, Union[str, int]]] = None,
32
+ fees: Optional[Dict[str, float]] = {'spot': {'maker': 0.0, 'taker': 0.0},
33
+ 'perpetual_future': {'maker': 0.0, 'taker': 0.0}
34
+ },
35
+ base_url: Optional[str] = None,
36
+ api_key: Optional[str] = None,
37
+ max_obs_per_call: Optional[int] = None,
38
+ rate_limit: Optional[Any] = None
39
+ ):
40
+ """
41
+ Initializes the Dydx class.
42
+
43
+ Parameters:
44
+ -----------
45
+ name: str
46
+ The name of the exchange.
47
+ exch_type: str
48
+ The type of the exchange.
49
+ is_active: bool
50
+ Whether the exchange is active.
51
+ categories: Union[str, List[str]]
52
+ The categories of the exchange.
53
+ assets: Optional[Dict[str, List[str]]]
54
+ The assets traded on the exchange.
55
+ markets: Optional[Dict[str, List[str]]]
56
+ The markets traded on the exchange.
57
+ market_types: List[str]
58
+ The types of markets traded on the exchange.
59
+ fields: Optional[List[str]]
60
+ The fields to retrieve from the exchange.
61
+ frequencies: Optional[Dict[str, Union[str, int]]]
62
+ The frequencies of the data to retrieve.
63
+ fees: Optional[Dict[str, float]]
64
+ The fees for the exchange.
65
+ base_url: Optional[str]
66
+ The base url of the exchange.
67
+ api_key: Optional[str]
68
+ The api key for the exchange.
69
+ max_obs_per_call: Optional[int]
70
+ The maximum number of observations per call.
71
+ rate_limit: Optional[Any]
72
+ The rate limit for the exchange.
73
+ """
74
+ super().__init__(
75
+ name=name,
76
+ exch_type=exch_type,
77
+ is_active=is_active,
78
+ categories=categories,
79
+ assets=assets,
80
+ markets=markets,
81
+ market_types=market_types,
82
+ fields=fields,
83
+ frequencies=frequencies,
84
+ fees=fees,
85
+ base_url=base_url,
86
+ api_key=api_key,
87
+ max_obs_per_call=max_obs_per_call,
88
+ rate_limit=rate_limit
89
+ )
90
+ self.data_req = None
91
+ self.data = pd.DataFrame()
92
+
93
+ def get_assets_info(self):
94
+ pass
95
+
96
+ def get_markets_info(self):
97
+ pass
98
+
99
+ def get_fields_info(self, data_type: Optional[str]):
100
+ pass
101
+
102
+ def get_frequencies_info(self):
103
+ pass
104
+
105
+ def get_rate_limit_info(self):
106
+ pass
107
+
108
+ def get_metadata(self):
109
+ pass
110
+
111
+ def _fetch_ohlcv(self):
112
+ pass
113
+
114
+ def _fetch_funding_rates(self):
115
+ pass
116
+
117
+ def _fetch_open_interest(self):
118
+ pass
119
+
120
+ def _convert_params(self):
121
+ pass
122
+
123
+ @staticmethod
124
+ def _wrangle_data_resp(data_req: DataRequest, data_resp: Union[Dict[str, Any], pd.DataFrame]) -> pd.DataFrame:
125
+ pass
126
+
127
+ def _fetch_tidy_ohlcv(self):
128
+ pass
129
+
130
+ def _fetch_tidy_funding_rates(self):
131
+ pass
132
+
133
+ def _fetch_tidy_open_interest(self):
134
+ pass
135
+
136
+ def get_data(self, data_req) -> pd.DataFrame:
137
+ pass
@@ -0,0 +1,439 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any, Dict, List, Optional, Union
3
+
4
+ import pandas as pd
5
+
6
+ from cryptodatapy.extract.datarequest import DataRequest
7
+
8
+
9
+ class Exchange(ABC):
10
+ """
11
+ Abstract base class for crypto exchanges (CEX or DEX).
12
+
13
+ This class provides a blueprint for interacting with crypto exchanges,
14
+ including authentication, data retrieval, and trading functionality.
15
+ """
16
+ def __init__(
17
+ self,
18
+ name,
19
+ exch_type,
20
+ is_active,
21
+ categories,
22
+ assets,
23
+ markets,
24
+ market_types,
25
+ fields,
26
+ frequencies,
27
+ fees,
28
+ base_url,
29
+ api_key,
30
+ max_obs_per_call,
31
+ rate_limit
32
+ ):
33
+ self.name = name
34
+ self.exch_type = exch_type
35
+ self.is_active = is_active
36
+ self.categories = categories
37
+ self.assets = assets
38
+ self.markets = markets
39
+ self.market_types = market_types
40
+ self.fields = fields
41
+ self.frequencies = frequencies
42
+ self.fees = fees
43
+ self.base_url = base_url
44
+ self.api_key = api_key
45
+ self.max_obs_per_call = max_obs_per_call
46
+ self.rate_limit = rate_limit
47
+
48
+ @property
49
+ def name(self):
50
+ """
51
+ Returns the type of exchange.
52
+ """
53
+ return self._name
54
+
55
+ @name.setter
56
+ def name(self, name: str):
57
+ """
58
+ Sets the name of the exchange.
59
+ """
60
+ if name is None:
61
+ self._name = name
62
+ elif not isinstance(name, str):
63
+ raise TypeError("Exchange name must be a string.")
64
+ else:
65
+ self._name = name
66
+
67
+ @property
68
+ def exch_type(self):
69
+ """
70
+ Returns the type of exchange.
71
+ """
72
+ return self._exch_type
73
+
74
+ @exch_type.setter
75
+ def exch_type(self, exch_type: str):
76
+ """
77
+ Sets the type of exchange.
78
+ """
79
+ valid_exch_types = ['cex', 'dex']
80
+
81
+ if exch_type is None:
82
+ self._exch_type = exch_type
83
+ elif not isinstance(exch_type, str):
84
+ raise TypeError("Exchange type must be a string.")
85
+ elif exch_type not in valid_exch_types:
86
+ raise ValueError(f"{exch_type} is invalid. Valid exchange types are: {valid_exch_types}")
87
+ else:
88
+ self._exch_type = exch_type
89
+
90
+ @property
91
+ def is_active(self):
92
+ """
93
+ Returns whether the exchange is active.
94
+ """
95
+ return self._is_active
96
+
97
+ @is_active.setter
98
+ def is_active(self, is_active: bool):
99
+ """
100
+ Sets whether the exchange is active.
101
+ """
102
+ if is_active is None:
103
+ self._is_active = is_active
104
+ elif not isinstance(is_active, bool):
105
+ raise TypeError("is_active must be a boolean.")
106
+ else:
107
+ self._is_active = is_active
108
+
109
+ @property
110
+ def categories(self):
111
+ """
112
+ Returns a list of available categories for the data vendor.
113
+ """
114
+ return self._categories
115
+
116
+ @categories.setter
117
+ def categories(self, categories: Union[str, List[str]]):
118
+ """
119
+ Sets a list of available categories for the data vendor.
120
+ """
121
+ valid_categories = [
122
+ "crypto",
123
+ "fx",
124
+ "eqty",
125
+ "cmdty",
126
+ "index",
127
+ "rates",
128
+ "bonds",
129
+ "credit",
130
+ "macro",
131
+ "alt",
132
+ ]
133
+ cat = []
134
+
135
+ if categories is None:
136
+ self._categories = categories
137
+ else:
138
+ if not isinstance(categories, str) and not isinstance(categories, list):
139
+ raise TypeError("Categories must be a string or list of strings.")
140
+ if isinstance(categories, str):
141
+ categories = [categories]
142
+ for category in categories:
143
+ if category in valid_categories:
144
+ cat.append(category)
145
+ else:
146
+ raise ValueError(
147
+ f"{category} is invalid. Valid categories are: {valid_categories}"
148
+ )
149
+ self._categories = cat
150
+
151
+ @property
152
+ def assets(self):
153
+ """
154
+ Returns a list of available assets for the data vendor.
155
+ """
156
+ return self._assets
157
+
158
+ @assets.setter
159
+ def assets(self, assets: Optional[Union[str, List[str], Dict[str, List[str]]]]):
160
+ """
161
+ Sets a list of available assets for the data vendor.
162
+ """
163
+ if (
164
+ assets is None
165
+ or isinstance(assets, list)
166
+ or isinstance(assets, dict)
167
+ or isinstance(assets, pd.DataFrame)
168
+ ):
169
+ self._assets = assets
170
+ elif isinstance(assets, str):
171
+ self._assets = [assets]
172
+ else:
173
+ raise TypeError(
174
+ "Assets must be a string (ticker), list of strings (tickers),"
175
+ " a dict with {cat: List[str]} key-value pairs or dataframe."
176
+ )
177
+
178
+ @abstractmethod
179
+ def get_assets_info(self):
180
+ """
181
+ Gets info for available assets from the data vendor.
182
+ """
183
+ # to be implemented by subclasses
184
+
185
+ @property
186
+ def markets(self):
187
+ """
188
+ Returns a list of available markets for the data vendor.
189
+ """
190
+ return self._markets
191
+
192
+ @markets.setter
193
+ def markets(self, markets: Optional[Union[str, List[str], Dict[str, List[str]]]]):
194
+ """
195
+ Sets a list of available markets for the data vendor.
196
+ """
197
+ if (
198
+ markets is None
199
+ or isinstance(markets, list)
200
+ or isinstance(markets, dict)
201
+ or isinstance(markets, pd.DataFrame)
202
+ ):
203
+ self._markets = markets
204
+ elif isinstance(markets, str):
205
+ self._markets = [markets]
206
+ else:
207
+ raise TypeError(
208
+ "Markets must be a string (ticker), list of strings (tickers),"
209
+ " a dict with {cat: List[str]} key-value pairs or dataframe."
210
+ )
211
+
212
+ @abstractmethod
213
+ def get_markets_info(self):
214
+ """
215
+ Gets info for available markets from the data vendor.
216
+ """
217
+ # to be implemented by subclasses
218
+
219
+ @property
220
+ def market_types(self):
221
+ """
222
+ Returns a list of available market types for the data vendor.
223
+ """
224
+ return self._market_types
225
+
226
+ @market_types.setter
227
+ def market_types(self, market_types: Optional[Union[str, List[str]]]):
228
+ """
229
+ Sets a list of available market types for the data vendor.
230
+ """
231
+ valid_mkt_types, mkt_types_list = [
232
+ None,
233
+ "spot",
234
+ "etf",
235
+ "perpetual_future",
236
+ "future",
237
+ "swap",
238
+ "option",
239
+ ], []
240
+
241
+ if market_types is None:
242
+ self._market_types = market_types
243
+ elif isinstance(market_types, str) and market_types in valid_mkt_types:
244
+ self._market_types = [market_types]
245
+ elif isinstance(market_types, list):
246
+ for mkt in market_types:
247
+ if mkt in valid_mkt_types:
248
+ mkt_types_list.append(mkt)
249
+ else:
250
+ raise ValueError(
251
+ f"{mkt} is invalid. Valid market types are: {valid_mkt_types}"
252
+ )
253
+ self._market_types = mkt_types_list
254
+ else:
255
+ raise TypeError("Market types must be a string or list of strings.")
256
+
257
+ @property
258
+ def fields(self):
259
+ """
260
+ Returns a list of available fields for the data vendor.
261
+ """
262
+ return self._fields
263
+
264
+ @fields.setter
265
+ def fields(self, fields: Optional[Union[str, List[str], Dict[str, List[str]]]]):
266
+ """
267
+ Sets a list of available fields for the data vendor.
268
+ """
269
+ if fields is None or isinstance(fields, list) or isinstance(fields, dict):
270
+ self._fields = fields
271
+ elif isinstance(fields, str):
272
+ self._fields = [fields]
273
+ else:
274
+ raise TypeError(
275
+ "Fields must be a string (field), list of strings (fields) or"
276
+ " a dict with {cat: List[str]} key-value pairs."
277
+ )
278
+
279
+ @abstractmethod
280
+ def get_fields_info(self, data_type: Optional[str]):
281
+ """
282
+ Gets info for available fields from the data vendor.
283
+ """
284
+ # to be implemented by subclasses
285
+
286
+ @property
287
+ def frequencies(self):
288
+ """
289
+ Returns a list of available data frequencies for the data vendor.
290
+ """
291
+ return self._frequencies
292
+
293
+ @frequencies.setter
294
+ def frequencies(
295
+ self, frequencies: Optional[Union[str, List[str], Dict[str, List[str]]]]
296
+ ):
297
+ """
298
+ Sets a list of available data frequencies for the data vendor.
299
+ """
300
+ if (
301
+ frequencies is None
302
+ or isinstance(frequencies, list)
303
+ or isinstance(frequencies, dict)
304
+ ):
305
+ self._frequencies = frequencies
306
+ elif isinstance(frequencies, str):
307
+ self._frequencies = [frequencies]
308
+ else:
309
+ raise TypeError(
310
+ "Frequencies must be a string (frequency), list of strings (frequencies) or"
311
+ " a dict with {cat: List[str]} key-value pairs."
312
+ )
313
+
314
+ @abstractmethod
315
+ def get_frequencies_info(self, data_type: Optional[str]):
316
+ """
317
+ Gets info for available frequencies from the exchange.
318
+ """
319
+ # to be implemented by subclasses
320
+
321
+ @property
322
+ def fees(self):
323
+ """
324
+ Returns a list of fees for the data vendor.
325
+ """
326
+ return self._fees
327
+
328
+ @fees.setter
329
+ def fees(self, fees: Optional[Union[float, Dict[str, float]]]):
330
+ """
331
+ Sets a list of fees for the data vendor.
332
+ """
333
+ if fees is None or isinstance(fees, float) or isinstance(fees, dict):
334
+ self._fees = fees
335
+ else:
336
+ raise TypeError("Fees must be a float or dict with {cat: float} key-value pairs.")
337
+
338
+ @property
339
+ def base_url(self):
340
+ """
341
+ Returns the base url for the data vendor.
342
+ """
343
+ return self._base_url
344
+
345
+ @base_url.setter
346
+ def base_url(self, url: Optional[str]):
347
+ """
348
+ Sets the base url for the data vendor.
349
+ """
350
+ if url is None or isinstance(url, str):
351
+ self._base_url = url
352
+ else:
353
+ raise TypeError(
354
+ "Base url must be a string containing the data vendor's base URL to which endpoint paths"
355
+ " are appended."
356
+ )
357
+
358
+ @property
359
+ def api_key(self):
360
+ """
361
+ Returns the api key for the data vendor.
362
+ """
363
+ return self._api_key
364
+
365
+ @api_key.setter
366
+ def api_key(self, api_key: Optional[str]):
367
+ """
368
+ Sets the api key for the data vendor.
369
+ """
370
+ if api_key is None or isinstance(api_key, str) or isinstance(api_key, dict):
371
+ self._api_key = api_key
372
+ else:
373
+ raise TypeError(
374
+ "Api key must be a string or dict with data source-api key key-value pairs."
375
+ )
376
+
377
+ @property
378
+ def max_obs_per_call(self):
379
+ """
380
+ Returns the maximum observations per API call for the data vendor.
381
+ """
382
+ return self._max_obs_per_call
383
+
384
+ @max_obs_per_call.setter
385
+ def max_obs_per_call(self, limit: Optional[Union[int, str]]):
386
+ """
387
+ Sets the maximum number of observations per API call for the data vendor.
388
+ """
389
+ if limit is None:
390
+ self._max_obs_per_call = limit
391
+ elif isinstance(limit, int) or isinstance(limit, str):
392
+ self._max_obs_per_call = int(limit)
393
+ else:
394
+ raise TypeError(
395
+ "Maximum number of observations per API call must be an integer or string."
396
+ )
397
+
398
+ @property
399
+ def rate_limit(self):
400
+ """
401
+ Returns the number of API calls made and remaining.
402
+ """
403
+ return self._rate_limit
404
+
405
+ @rate_limit.setter
406
+ def rate_limit(self, limit: Optional[Any]):
407
+ """
408
+ Sets the number of API calls made and remaining.
409
+ """
410
+ self._rate_limit = limit
411
+
412
+ @abstractmethod
413
+ def get_rate_limit_info(self):
414
+ """
415
+ Gets the number of API calls made and remaining.
416
+ """
417
+ # to be implemented by subclasses
418
+
419
+ @abstractmethod
420
+ def get_metadata(self) -> None:
421
+ """
422
+ Get exchange metadata.
423
+ """
424
+ # to be implemented by subclasses
425
+
426
+ @abstractmethod
427
+ def get_data(self, data_req) -> pd.DataFrame:
428
+ """
429
+ Submits get data request to API.
430
+ """
431
+ # to be implemented by subclasses
432
+
433
+ @staticmethod
434
+ @abstractmethod
435
+ def _wrangle_data_resp(data_req: DataRequest, data_resp: Union[Dict[str, Any], pd.DataFrame]) -> pd.DataFrame:
436
+ """
437
+ Wrangles data response from data vendor API into tidy format.
438
+ """
439
+ # to be implemented by subclasses