cryptofeed 2.4.0__tar.gz → 2.4.1__tar.gz

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 (109) hide show
  1. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/AUTHORS.md +5 -0
  2. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/CHANGES.md +13 -0
  3. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/INSTALL.md +5 -0
  4. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/LICENSE +1 -1
  5. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/PKG-INFO +62 -10
  6. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/README.md +2 -1
  7. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/__init__.py +1 -1
  8. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/backends/_util.py +1 -1
  9. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/backends/aggregate.py +4 -4
  10. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/backends/arctic.py +1 -1
  11. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/backends/backend.py +1 -1
  12. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/backends/gcppubsub.py +1 -1
  13. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/backends/http.py +1 -1
  14. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/backends/influxdb.py +1 -1
  15. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/backends/kafka.py +1 -1
  16. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/backends/mongo.py +1 -1
  17. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/backends/postgres.py +1 -1
  18. cryptofeed-2.4.1/cryptofeed/backends/quasardb.py +175 -0
  19. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/backends/quest.py +7 -4
  20. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/backends/rabbitmq.py +1 -1
  21. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/backends/redis.py +1 -1
  22. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/backends/socket.py +1 -1
  23. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/backends/zmq.py +1 -1
  24. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/callback.py +1 -1
  25. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/config.py +1 -1
  26. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/connection.py +13 -8
  27. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/connection_handler.py +1 -21
  28. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/defines.py +1 -1
  29. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exceptions.py +1 -1
  30. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchange.py +4 -4
  31. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/__init__.py +1 -1
  32. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/ascendex.py +1 -1
  33. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/bequant.py +1 -1
  34. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/binance.py +1 -1
  35. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/binance_delivery.py +1 -1
  36. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/binance_futures.py +1 -1
  37. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/binance_tr.py +1 -1
  38. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/binance_us.py +1 -1
  39. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/bitdotcom.py +3 -3
  40. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/bitfinex.py +2 -2
  41. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/bitflyer.py +1 -1
  42. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/bitget.py +1 -1
  43. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/bithumb.py +1 -1
  44. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/bitmex.py +2 -2
  45. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/bitstamp.py +1 -1
  46. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/blockchain.py +1 -1
  47. cryptofeed-2.4.1/cryptofeed/exchanges/bybit.py +618 -0
  48. cryptofeed-2.4.1/cryptofeed/exchanges/coinbase.py +192 -0
  49. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/cryptodotcom.py +1 -1
  50. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/delta.py +1 -1
  51. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/dydx.py +1 -1
  52. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/fmfw.py +1 -1
  53. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/gateio.py +1 -1
  54. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/gateio_futures.py +1 -1
  55. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/gemini.py +1 -1
  56. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/hitbtc.py +1 -1
  57. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/huobi.py +1 -1
  58. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/huobi_dm.py +1 -1
  59. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/huobi_swap.py +4 -4
  60. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/independent_reserve.py +6 -6
  61. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/kraken.py +1 -1
  62. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/kraken_futures.py +1 -1
  63. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/kucoin.py +2 -2
  64. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/mixins/binance_rest.py +1 -1
  65. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/mixins/bitfinex_rest.py +1 -1
  66. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/mixins/bitmex_rest.py +1 -1
  67. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/mixins/bitstamp_rest.py +1 -1
  68. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/mixins/coinbase_rest.py +1 -1
  69. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/mixins/deribit_rest.py +1 -1
  70. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/mixins/dydx_rest.py +1 -1
  71. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/mixins/gemini_rest.py +1 -1
  72. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/mixins/kraken_rest.py +1 -1
  73. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/mixins/okx_rest.py +1 -1
  74. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/mixins/upbit_rest.py +1 -1
  75. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/okcoin.py +1 -1
  76. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/okx.py +2 -2
  77. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/phemex.py +1 -1
  78. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/poloniex.py +1 -1
  79. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/probit.py +1 -1
  80. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/upbit.py +1 -1
  81. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/feed.py +1 -1
  82. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/feedhandler.py +1 -1
  83. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/log.py +1 -1
  84. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/nbbo.py +1 -1
  85. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/raw_data_collection.py +1 -1
  86. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/symbols.py +1 -1
  87. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/types.c +21224 -9672
  88. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/types.pyx +1 -1
  89. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/util/book.py +1 -1
  90. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/util/perf.py +1 -1
  91. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/util/time.py +1 -1
  92. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed.egg-info/PKG-INFO +62 -10
  93. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed.egg-info/SOURCES.txt +1 -0
  94. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed.egg-info/requires.txt +6 -2
  95. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/setup.py +8 -6
  96. cryptofeed-2.4.0/cryptofeed/exchanges/bybit.py +0 -520
  97. cryptofeed-2.4.0/cryptofeed/exchanges/coinbase.py +0 -391
  98. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/MANIFEST.in +0 -0
  99. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/backends/__init__.py +0 -0
  100. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/ascendex_futures.py +0 -0
  101. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/deribit.py +0 -0
  102. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/exx.py +0 -0
  103. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/exchanges/mixins/__init__.py +0 -0
  104. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/util/__init__.py +0 -0
  105. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed/util/split.py +0 -0
  106. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed.egg-info/dependency_links.txt +0 -0
  107. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/cryptofeed.egg-info/top_level.txt +0 -0
  108. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/pyproject.toml +0 -0
  109. {cryptofeed-2.4.0 → cryptofeed-2.4.1}/setup.cfg +0 -0
@@ -14,3 +14,8 @@ Cryptofeed was originally created by Bryant Moscon, but many others have contrib
14
14
  * [O. Janche](https://github.com/toyan) - <toyan@yandex.ru>
15
15
  * [Bastien Enjalbert](https://github.com/bastienjalbert) - <bastienjalbert@gmail.com>
16
16
  * [Jonggyun Kim](https://github.com/gyunt) - <truth0233@gmail.com>
17
+ * [QuasarDB](https://quasar.ai/)
18
+ * [Thomas Bouamoud](https://github.com/thomasbs17) - <thomasbs17@yahoo.fr>
19
+ * [Carlo Eugster](https://github.com/carloe) - <carlo@relaun.ch>
20
+ * [Marten Schlüter](https://github.com/maschlr)
21
+ * [Handel Scholze](https://github.com/HandelSM) - <handel.scholze@gmail.com>
@@ -1,5 +1,18 @@
1
1
  ## Changelog
2
2
 
3
+ ### 2.4.1 (2025-02-08)
4
+ * Update: Added `is_data_json` to `write()` in `HTTPSync` from `connection.py` to support JSON payloads (#1071)
5
+ * Bugfix: Handle empty nextFundingRate in OKX
6
+ * Bugfix: Handle null next_funding_time and estimated_rate in HuobiSwap funding
7
+ * Update: transitioned from Coinbase Pro (retired) to Coinbase Advanced Trade
8
+ * Feature: Bybit spot support
9
+ * Update: Bybit migrate to API V5 for public streams
10
+ * Bugfix: Handle None ids for Kraken trades in QuestDB
11
+ * Bugfix: Handle OrderChanged event in IndependentReserve
12
+ * Bugfix: Remove deprecated `USD` currency from bit.com
13
+ * Bugfix: Make `entry` key optional when retrieving symbols for BitMex
14
+ * Update: Changes to work with latest version of websockets
15
+
3
16
  ### 2.4.0 (2024-01-07)
4
17
  * Update: Fix tests
5
18
  * Update: Okcoin moved to v5 API used by OKX
@@ -47,6 +47,11 @@ for the exhaustive list of these *extra* dependencies.
47
47
 
48
48
  pip install --user --upgrade cryptofeed[postgres]
49
49
 
50
+ * QuasarDB backend
51
+ To install Cryptofeed along with [QuasarDB](https://quasar.ai/) in one bundle:
52
+
53
+ pip install --user --upgrade cryptofeed[quasardb]
54
+
50
55
  * RabbitMQ backend
51
56
 
52
57
  pip install --user --upgrade cryptofeed[rabbit]
@@ -1,4 +1,4 @@
1
- Copyright (C) 2017-2024 Bryant Moscon - bmoscon@gmail.com
1
+ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to
@@ -1,33 +1,68 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cryptofeed
3
- Version: 2.4.0
3
+ Version: 2.4.1
4
4
  Summary: Cryptocurrency Exchange Websocket Data Feed Handler
5
5
  Home-page: https://github.com/bmoscon/cryptofeed
6
6
  Author: Bryant Moscon
7
7
  Author-email: bmoscon@gmail.com
8
8
  License: XFree86
9
9
  Keywords: cryptocurrency,bitcoin,btc,feed handler,market feed,market data,crypto assets,Trades,Tickers,BBO,Funding,Open Interest,Liquidation,Order book,Bid,Ask,fmfw.io,Bitfinex,bitFlyer,AscendEX,Bitstamp,Blockchain.com,Bybit,Binance,Binance Delivery,Binance Futures,Binance US,BitMEX,Coinbase,Deribit,EXX,Gate.io,Gemini,HitBTC,Huobi,Huobi DM,Huobi Swap,Kraken,Kraken Futures,OKCoin,OKX,Poloniex,ProBit,Upbit
10
- Platform: UNKNOWN
11
10
  Classifier: Intended Audience :: Developers
12
11
  Classifier: Development Status :: 4 - Beta
13
12
  Classifier: Programming Language :: Python :: 3 :: Only
14
- Classifier: Programming Language :: Python :: 3.8
15
13
  Classifier: Programming Language :: Python :: 3.9
16
14
  Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Framework :: AsyncIO
18
- Requires-Python: >=3.8
18
+ Requires-Python: >=3.9
19
19
  Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ License-File: AUTHORS.md
22
+ Requires-Dist: requests>=2.18.4
23
+ Requires-Dist: websockets>=14.1
24
+ Requires-Dist: pyyaml
25
+ Requires-Dist: aiohttp>=3.11.6
26
+ Requires-Dist: aiofile>=2.0.0
27
+ Requires-Dist: yapic.json>=1.6.3
28
+ Requires-Dist: uvloop; platform_system != "Windows"
29
+ Requires-Dist: order_book>=0.6.0
30
+ Requires-Dist: aiodns>=1.1
20
31
  Provides-Extra: arctic
21
- Provides-Extra: gcp_pubsub
32
+ Requires-Dist: arctic; extra == "arctic"
33
+ Requires-Dist: pandas; extra == "arctic"
34
+ Provides-Extra: gcp-pubsub
35
+ Requires-Dist: google_cloud_pubsub>=2.4.1; extra == "gcp-pubsub"
36
+ Requires-Dist: gcloud_aio_pubsub; extra == "gcp-pubsub"
22
37
  Provides-Extra: kafka
38
+ Requires-Dist: aiokafka>=0.7.0; extra == "kafka"
23
39
  Provides-Extra: mongo
40
+ Requires-Dist: motor; extra == "mongo"
24
41
  Provides-Extra: postgres
42
+ Requires-Dist: asyncpg; extra == "postgres"
43
+ Provides-Extra: quasardb
44
+ Requires-Dist: quasardb; extra == "quasardb"
45
+ Requires-Dist: numpy; extra == "quasardb"
25
46
  Provides-Extra: rabbit
47
+ Requires-Dist: aio_pika; extra == "rabbit"
48
+ Requires-Dist: pika; extra == "rabbit"
26
49
  Provides-Extra: redis
50
+ Requires-Dist: hiredis; extra == "redis"
51
+ Requires-Dist: redis>=4.5.1; extra == "redis"
27
52
  Provides-Extra: zmq
53
+ Requires-Dist: pyzmq; extra == "zmq"
28
54
  Provides-Extra: all
29
- License-File: LICENSE
30
- License-File: AUTHORS.md
55
+ Requires-Dist: arctic; extra == "all"
56
+ Requires-Dist: google_cloud_pubsub>=2.4.1; extra == "all"
57
+ Requires-Dist: gcloud_aio_pubsub; extra == "all"
58
+ Requires-Dist: aiokafka>=0.7.0; extra == "all"
59
+ Requires-Dist: motor; extra == "all"
60
+ Requires-Dist: asyncpg; extra == "all"
61
+ Requires-Dist: aio_pika; extra == "all"
62
+ Requires-Dist: pika; extra == "all"
63
+ Requires-Dist: hiredis; extra == "all"
64
+ Requires-Dist: redis>=4.5.1; extra == "all"
65
+ Requires-Dist: pyzmq; extra == "all"
31
66
 
32
67
  # Cryptocurrency Exchange Feed Handler
33
68
  [![License](https://img.shields.io/badge/license-XFree86-blue.svg)](LICENSE)
@@ -171,6 +206,7 @@ Supported Backends:
171
206
  * Kafka
172
207
  * RabbitMQ
173
208
  * PostgreSQL
209
+ * [QuasarDB](https://quasar.ai/)
174
210
  * GCP Pub/Sub
175
211
  * [QuestDB](https://questdb.io/)
176
212
 
@@ -206,7 +242,7 @@ Cryptofeed supports some REST interfaces for retrieving real-time and historical
206
242
 
207
243
  ## Future Work
208
244
 
209
- There are a lot of planned features, new exchanges, etc planned! If you'd like to discuss ongoing development, please join the [slack](https://join.slack.com/t/cryptofeed-dev/shared_invite/enQtNjY4ODIwODA1MzQ3LTIzMzY3Y2YxMGVhNmQ4YzFhYTc3ODU1MjQ5MDdmY2QyZjdhMGU5ZDFhZDlmMmYzOTUzOTdkYTZiOGUwNGIzYTk) or open a thread in the [discussions](https://github.com/bmoscon/cryptofeed/discussions) in GitHub.
245
+ There are a lot of planned features, new exchanges, etc planned! If you'd like to discuss ongoing development, please join the [discord](https://discord.gg/zaBYaGAYfR) or open a thread in the [discussions](https://github.com/bmoscon/cryptofeed/discussions) in GitHub.
210
246
 
211
247
  ## Contributing
212
248
 
@@ -273,6 +309,11 @@ for the exhaustive list of these *extra* dependencies.
273
309
 
274
310
  pip install --user --upgrade cryptofeed[postgres]
275
311
 
312
+ * QuasarDB backend
313
+ To install Cryptofeed along with [QuasarDB](https://quasar.ai/) in one bundle:
314
+
315
+ pip install --user --upgrade cryptofeed[quasardb]
316
+
276
317
  * RabbitMQ backend
277
318
 
278
319
  pip install --user --upgrade cryptofeed[rabbit]
@@ -297,6 +338,19 @@ Your Pull Requests are also welcome, even for minor changes.
297
338
 
298
339
  ## Changelog
299
340
 
341
+ ### 2.4.1 (2025-02-08)
342
+ * Update: Added `is_data_json` to `write()` in `HTTPSync` from `connection.py` to support JSON payloads (#1071)
343
+ * Bugfix: Handle empty nextFundingRate in OKX
344
+ * Bugfix: Handle null next_funding_time and estimated_rate in HuobiSwap funding
345
+ * Update: transitioned from Coinbase Pro (retired) to Coinbase Advanced Trade
346
+ * Feature: Bybit spot support
347
+ * Update: Bybit migrate to API V5 for public streams
348
+ * Bugfix: Handle None ids for Kraken trades in QuestDB
349
+ * Bugfix: Handle OrderChanged event in IndependentReserve
350
+ * Bugfix: Remove deprecated `USD` currency from bit.com
351
+ * Bugfix: Make `entry` key optional when retrieving symbols for BitMex
352
+ * Update: Changes to work with latest version of websockets
353
+
300
354
  ### 2.4.0 (2024-01-07)
301
355
  * Update: Fix tests
302
356
  * Update: Okcoin moved to v5 API used by OKX
@@ -1086,5 +1140,3 @@ Your Pull Requests are also welcome, even for minor changes.
1086
1140
  ### 0.5.0 (2018-01-02)
1087
1141
  * Initial release: GDAX, Poloniex, Bitfinex Support
1088
1142
  * Feature: NBBO support
1089
-
1090
-
@@ -140,6 +140,7 @@ Supported Backends:
140
140
  * Kafka
141
141
  * RabbitMQ
142
142
  * PostgreSQL
143
+ * [QuasarDB](https://quasar.ai/)
143
144
  * GCP Pub/Sub
144
145
  * [QuestDB](https://questdb.io/)
145
146
 
@@ -175,7 +176,7 @@ Cryptofeed supports some REST interfaces for retrieving real-time and historical
175
176
 
176
177
  ## Future Work
177
178
 
178
- There are a lot of planned features, new exchanges, etc planned! If you'd like to discuss ongoing development, please join the [slack](https://join.slack.com/t/cryptofeed-dev/shared_invite/enQtNjY4ODIwODA1MzQ3LTIzMzY3Y2YxMGVhNmQ4YzFhYTc3ODU1MjQ5MDdmY2QyZjdhMGU5ZDFhZDlmMmYzOTUzOTdkYTZiOGUwNGIzYTk) or open a thread in the [discussions](https://github.com/bmoscon/cryptofeed/discussions) in GitHub.
179
+ There are a lot of planned features, new exchanges, etc planned! If you'd like to discuss ongoing development, please join the [discord](https://discord.gg/zaBYaGAYfR) or open a thread in the [discussions](https://github.com/bmoscon/cryptofeed/discussions) in GitHub.
179
180
 
180
181
  ## Contributing
181
182
 
@@ -1,5 +1,5 @@
1
1
  '''
2
- Copyright (C) 2017-2024 Bryant Moscon - bmoscon@gmail.com
2
+ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
3
3
 
4
4
  Please see the LICENSE file for the terms and conditions
5
5
  associated with this software.
@@ -1,5 +1,5 @@
1
1
  '''
2
- Copyright (C) 2017-2024 Bryant Moscon - bmoscon@gmail.com
2
+ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
3
3
 
4
4
  Please see the LICENSE file for the terms and conditions
5
5
  associated with this software.
@@ -1,5 +1,5 @@
1
1
  '''
2
- Copyright (C) 2017-2024 Bryant Moscon - bmoscon@gmail.com
2
+ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
3
3
 
4
4
  Please see the LICENSE file for the terms and conditions
5
5
  associated with this software.
@@ -13,9 +13,9 @@ import numpy as np
13
13
  class AggregateCallback:
14
14
  def __init__(self, handler):
15
15
  self.handler = handler
16
- if not callable(self.handler):
17
- setattr(self, 'start', self.handler.start)
18
- setattr(self, 'stop', self.handler.stop)
16
+ if hasattr(self.handler, "__class__") and hasattr(self.handler, "start") and hasattr(self.handler, "stop"):
17
+ setattr(self, "start", self.handler.start)
18
+ setattr(self, "stop", self.handler.stop)
19
19
  self.__name__ = self.handler.__class__
20
20
 
21
21
 
@@ -1,5 +1,5 @@
1
1
  '''
2
- Copyright (C) 2017-2024 Bryant Moscon - bmoscon@gmail.com
2
+ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
3
3
 
4
4
  Please see the LICENSE file for the terms and conditions
5
5
  associated with this software.
@@ -1,5 +1,5 @@
1
1
  '''
2
- Copyright (C) 2017-2024 Bryant Moscon - bmoscon@gmail.com
2
+ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
3
3
 
4
4
  Please see the LICENSE file for the terms and conditions
5
5
  associated with this software.
@@ -1,5 +1,5 @@
1
1
  '''
2
- Copyright (C) 2017-2024 Bryant Moscon - bmoscon@gmail.com
2
+ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
3
3
 
4
4
  Please see the LICENSE file for the terms and conditions
5
5
  associated with this software.
@@ -1,5 +1,5 @@
1
1
  '''
2
- Copyright (C) 2017-2024 Bryant Moscon - bmoscon@gmail.com
2
+ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
3
3
 
4
4
  Please see the LICENSE file for the terms and conditions
5
5
  associated with this software.
@@ -1,5 +1,5 @@
1
1
  '''
2
- Copyright (C) 2017-2024 Bryant Moscon - bmoscon@gmail.com
2
+ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
3
3
 
4
4
  Please see the LICENSE file for the terms and conditions
5
5
  associated with this software.
@@ -1,5 +1,5 @@
1
1
  '''
2
- Copyright (C) 2017-2024 Bryant Moscon - bmoscon@gmail.com
2
+ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
3
3
 
4
4
  Please see the LICENSE file for the terms and conditions
5
5
  associated with this software.
@@ -1,5 +1,5 @@
1
1
  '''
2
- Copyright (C) 2017-2024 Bryant Moscon - bmoscon@gmail.com
2
+ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
3
3
 
4
4
  Please see the LICENSE file for the terms and conditions
5
5
  associated with this software.
@@ -1,5 +1,5 @@
1
1
  '''
2
- Copyright (C) 2017-2024 Bryant Moscon - bmoscon@gmail.com
2
+ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
3
3
 
4
4
  Please see the LICENSE file for the terms and conditions
5
5
  associated with this software.
@@ -0,0 +1,175 @@
1
+ from datetime import datetime, timedelta
2
+ import quasardb.pool as pool
3
+ import quasardb.numpy as qdbnp
4
+ import numpy as np
5
+ from cryptofeed.backends.backend import BackendCallback
6
+
7
+
8
+ class QuasarCallback(BackendCallback):
9
+ def __init__(self, uri="qdb://127.0.0.1:2836", username: str = "", private_key: str = "", public_key: str = "", none_to=None, shard_size: timedelta = timedelta(minutes=15)):
10
+ self.numeric_type = float
11
+ self.table = ""
12
+ self.running = True
13
+ self.none_to = none_to
14
+ self.shard_size = self._get_str_timedelta(shard_size)
15
+
16
+ pool.initialize(uri=uri, user_name=username, user_private_key=private_key, cluster_public_key=public_key)
17
+
18
+ def _get_str_timedelta(self, delta: timedelta):
19
+ # calculate the number of hours, minutes, and remaining seconds from timedelta, return it in correct format for query
20
+ hours, remainder = divmod(delta.total_seconds(), 3600)
21
+ minutes, seconds = divmod(remainder, 60)
22
+ return f"{int(hours)}hour {int(minutes)}min {int(seconds)}s"
23
+
24
+ def format(self, data: dict):
25
+ data['timestamp'] = np.datetime64(datetime.utcfromtimestamp(data['timestamp']), 'ns')
26
+ data['receipt_timestamp'] = np.datetime64(datetime.utcfromtimestamp(data['receipt_timestamp']), 'ns')
27
+ data['timestamp'], data['receipt_timestamp'] = data['receipt_timestamp'], data['timestamp']
28
+ index = data['timestamp']
29
+ data.pop('timestamp')
30
+ return index, data
31
+
32
+ def _set_table_name(self, data: dict):
33
+ # setting table name
34
+ # {channel}/{exchange}/{symbol_1-symbol_2}
35
+ # eg. ticker/coinbase/btc-usd
36
+ self.table = f"{self.table_prefix.lower()}/{data['exchange'].lower()}/{data['symbol'].lower()}"
37
+
38
+ def _create_table(self, conn):
39
+ if not conn.table(self.table).exists():
40
+ conn.query(self.query)
41
+
42
+ def _insert_format(self, date: np.datetime64, data: dict):
43
+ # converts values to np.array
44
+ for key, value in data.items():
45
+ data[key] = np.array([value])
46
+ return np.array([date]), data
47
+
48
+ async def write(self, data: dict):
49
+ self._set_table_name(data)
50
+ self._create_query()
51
+ index, data = self.format(data)
52
+ idx, np_array = self._insert_format(index, data)
53
+ # write to table, if table doesnt exist it will be created with specified shard_size value
54
+ with pool.instance().connect() as conn:
55
+ self._create_table(conn)
56
+ qdbnp.write_arrays(np_array, conn, conn.table(self.table), index=idx, fast=True, _async=True)
57
+
58
+
59
+ class TickerQuasar(QuasarCallback):
60
+ table_prefix = "ticker"
61
+
62
+ def _create_query(self):
63
+ self.query = f'CREATE TABLE "{self.table}" (exchange SYMBOL(exchange), symbol SYMBOL(symbol), bid DOUBLE, ask DOUBLE, receipt_timestamp TIMESTAMP) SHARD_SIZE = {self.shard_size}'
64
+
65
+
66
+ class TradeQuasar(QuasarCallback):
67
+ table_prefix = "trades"
68
+
69
+ def _create_query(self):
70
+ self.query = f'CREATE TABLE "{self.table}" (exchange SYMBOL(exchange), symbol SYMBOL(symbol), side SYMBOL(side), amount DOUBLE, price DOUBLE, id STRING, type SYMBOL(type), receipt_timestamp TIMESTAMP) SHARD_SIZE = {self.shard_size}'
71
+
72
+
73
+ class CandlesQuasar(QuasarCallback):
74
+ table_prefix = "candles"
75
+
76
+ def format(self, data: dict):
77
+ index, data = super().format(data)
78
+ data['start'] = datetime.utcfromtimestamp(data['start'])
79
+ data['stop'] = datetime.utcfromtimestamp(data['stop'])
80
+ data['closed'] = int(data['closed'])
81
+ return index, data
82
+
83
+ def _create_query(self):
84
+ self.query = f'CREATE TABLE "{self.table}" (exchange SYMBOL(exchange), symbol SYMBOL(symbol), start TIMESTAMP, stop TIMESTAMP, interval STRING, trades STRING, open DOUBLE, close DOUBLE, high DOUBLE, low DOUBLE, volume DOUBLE, closed INT64, receipt_timestamp TIMESTAMP) SHARD_SIZE = {self.shard_size}'
85
+
86
+
87
+ class FundingQuasar(QuasarCallback):
88
+ table_prefix = "funding"
89
+
90
+ def format(self, data: dict):
91
+ index, data = super().format(data)
92
+ data['next_funding_time'] = datetime.utcfromtimestamp(data['next_funding_time'])
93
+ return index, data
94
+
95
+ def _create_query(self):
96
+ self.query = f'CREATE TABLE "{self.table}" (exchange SYMBOL(exchange), symbol SYMBOL(symbol), mark_price DOUBLE, rate DOUBLE, next_funding_time TIMESTAMP, predicted_rate DOUBLE, receipt_timestamp TIMESTAMP) SHARD_SIZE = {self.shard_size}'
97
+
98
+
99
+ class BookQuasar(QuasarCallback):
100
+ table_prefix = "book"
101
+
102
+ def format(self, data: dict):
103
+ index, data = super().format(data)
104
+ # store only best bid and best ask
105
+ if not data['book']:
106
+ best_bid = max(data["delta"]["bid"], key=lambda x: x[0])
107
+ best_ask = min(data["delta"]["ask"], key=lambda x: x[0])
108
+
109
+ data['best_bid_price'] = best_bid[0]
110
+ data['best_bid_amount'] = best_bid[1]
111
+ data['best_ask_price'] = best_ask[0]
112
+ data['best_ask_amount'] = best_ask[1]
113
+ data.pop('delta')
114
+ else:
115
+ best_bid = max(data["book"]["bid"].keys())
116
+ best_ask = min(data["book"]["ask"].keys())
117
+
118
+ data['best_bid_price'] = best_bid
119
+ data['best_bid_amount'] = data["book"]["bid"][best_bid]
120
+ data['best_ask_price'] = best_ask
121
+ data['best_ask_amount'] = data["book"]["ask"][best_ask]
122
+ data.pop('book')
123
+ return index, data
124
+
125
+ def _create_query(self):
126
+ self.query = f'CREATE TABLE "{self.table}" (exchange SYMBOL(exchange), symbol SYMBOL(symbol), best_bid_price DOUBLE, best_bid_amount DOUBLE, best_ask_price DOUBLE, best_ask_amount DOUBLE, receipt_timestamp TIMESTAMP) SHARD_SIZE = {self.shard_size}'
127
+
128
+
129
+ class LiquidationsQuasar(QuasarCallback):
130
+ table_prefix = "liquidations"
131
+
132
+ def _create_query(self):
133
+ self.query = f'CREATE TABLE "{self.table}" (exchange SYMBOL(exchange), symbol SYMBOL(symbol), side SYMBOL(side), quantity DOUBLE, price DOUBLE, id STRING, status SYMBOL(type), receipt_timestamp TIMESTAMP) SHARD_SIZE = {self.shard_size}'
134
+
135
+
136
+ class OpenInterestQuasar(QuasarCallback):
137
+ table_prefix = "open_interest"
138
+
139
+ def _create_query(self):
140
+ self.query = f'CREATE TABLE "{self.table}" (exchange SYMBOL(exchange), symbol SYMBOL(symbol), open_interest FLOAT, receipt_timestamp TIMESTAMP) SHARD_SIZE = {self.shard_size}'
141
+
142
+
143
+ class OrderInfoQuasar(QuasarCallback):
144
+ table_prefix = "order_info"
145
+
146
+ def _create_query(self):
147
+ self.query = f'CREATE TABLE "{self.table}" (exchange SYMBOL(exchange), symbol SYMBOL(symbol), id STRING, client_order_id STRING, side SYMBOL(side), status SYMBOL(type), type SYMBOL(type), price DOUBLE, amount DOUBLE, remaining DOUBLE, account STRING, receipt_timestamp TIMESTAMP) SHARD_SIZE = {self.shard_size}'
148
+
149
+
150
+ class TransactionsQuasar(QuasarCallback):
151
+ table_prefix = "transactions"
152
+
153
+ def _create_query(self):
154
+ self.query = f'CREATE TABLE "{self.table}" (exchange SYMBOL(exchange), currency SYMBOL(currency), type SYMBOL(type), status SYMBOL(type), amount DOUBLE, receipt_timestamp TIMESTAMP) SHARD_SIZE = {self.shard_size}'
155
+
156
+
157
+ class BalancesQuasar(QuasarCallback):
158
+ table_prefix = "balances"
159
+
160
+ def _create_query(self):
161
+ self.query = f'CREATE TABLE "{self.table}" (exchange SYMBOL(exchange), currency SYMBOL(currency), balance DOUBLE, reserved DOUBLE, receipt_timestamp TIMESTAMP) SHARD_SIZE = {self.shard_size}'
162
+
163
+
164
+ class FillsQuasar(QuasarCallback):
165
+ table_prefix = "fills"
166
+
167
+ def _create_query(self):
168
+ self.query = f'CREATE TABLE "{self.table}" (exchange SYMBOL(exchange), symbol SYMBOL(symbol), price DOUBLE, amount DOUBLE, side SYMBOL(side), fee DOUBLE, id STRING, order_id STRING, liquidity DOUBLE, type SYMBOL(type), account STRING, receipt_timestamp TIMESTAMP) SHARD_SIZE = {self.shard_size}'
169
+
170
+
171
+ class IndexQuasar(QuasarCallback):
172
+ table_prefix = "index"
173
+
174
+ def _create_query(self):
175
+ self.query = f'CREATE TABLE "{self.table}" (exchange SYMBOL(exchange), symbol SYMBOL(symbol), price DOUBLE, receipt_timestamp TIMESTAMP) SHARD_SIZE = {self.shard_size}'
@@ -1,5 +1,5 @@
1
1
  '''
2
- Copyright (C) 2017-2024 Bryant Moscon - bmoscon@gmail.com
2
+ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
3
3
 
4
4
  Please see the LICENSE file for the terms and conditions
5
5
  associated with this software.
@@ -22,7 +22,7 @@ class QuestCallback(SocketCallback):
22
22
  self.running = True
23
23
 
24
24
  async def writer(self):
25
- while True:
25
+ while self.running:
26
26
  await self.connect()
27
27
  async with self.read_queue() as updates:
28
28
  update = "\n".join(updates) + "\n"
@@ -54,9 +54,12 @@ class TradeQuest(QuestCallback, BackendCallback):
54
54
  async def write(self, data):
55
55
  timestamp = data["timestamp"]
56
56
  received_timestamp_int = int(data["receipt_timestamp"] * 1_000_000)
57
+ id_field = f'id={data["id"]}i,' if data["id"] is not None else ''
57
58
  timestamp_int = int(timestamp * 1_000_000_000) if timestamp is not None else received_timestamp_int * 1000
58
- update = f'{self.key}-{data["exchange"]},symbol={data["symbol"]},side={data["side"]},type={data["type"]} ' \
59
- f'price={data["price"]},amount={data["amount"]},id={data["id"]}i,receipt_timestamp={received_timestamp_int}t {timestamp_int}'
59
+ update = (
60
+ f'{self.key}-{data["exchange"]},symbol={data["symbol"]},side={data["side"]},type={data["type"]} '
61
+ f'price={data["price"]},amount={data["amount"]},{id_field}receipt_timestamp={received_timestamp_int}t {timestamp_int}'
62
+ )
60
63
  await self.queue.put(update)
61
64
 
62
65
 
@@ -1,5 +1,5 @@
1
1
  '''
2
- Copyright (C) 2017-2024 Bryant Moscon - bmoscon@gmail.com
2
+ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
3
3
 
4
4
  Please see the LICENSE file for the terms and conditions
5
5
  associated with this software.
@@ -1,5 +1,5 @@
1
1
  '''
2
- Copyright (C) 2017-2024 Bryant Moscon - bmoscon@gmail.com
2
+ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
3
3
 
4
4
  Please see the LICENSE file for the terms and conditions
5
5
  associated with this software.
@@ -1,5 +1,5 @@
1
1
  '''
2
- Copyright (C) 2017-2024 Bryant Moscon - bmoscon@gmail.com
2
+ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
3
3
 
4
4
  Please see the LICENSE file for the terms and conditions
5
5
  associated with this software.
@@ -1,5 +1,5 @@
1
1
  '''
2
- Copyright (C) 2017-2024 Bryant Moscon - bmoscon@gmail.com
2
+ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
3
3
 
4
4
  Please see the LICENSE file for the terms and conditions
5
5
  associated with this software.
@@ -1,5 +1,5 @@
1
1
  '''
2
- Copyright (C) 2017-2024 Bryant Moscon - bmoscon@gmail.com
2
+ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
3
3
 
4
4
  Please see the LICENSE file for the terms and conditions
5
5
  associated with this software.
@@ -1,5 +1,5 @@
1
1
  '''
2
- Copyright (C) 2017-2024 Bryant Moscon - bmoscon@gmail.com
2
+ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
3
3
 
4
4
  Please see the LICENSE file for the terms and conditions
5
5
  associated with this software.
@@ -1,5 +1,5 @@
1
1
  '''
2
- Copyright (C) 2017-2024 Bryant Moscon - bmoscon@gmail.com
2
+ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
3
3
 
4
4
  Please see the LICENSE file for the terms and conditions
5
5
  associated with this software.
@@ -16,7 +16,8 @@ from dataclasses import dataclass
16
16
 
17
17
  from aiohttp.client_reqrep import ClientResponse
18
18
  import requests
19
- import websockets
19
+ from websockets.asyncio.client import connect, ClientConnection
20
+ from websockets.protocol import State
20
21
  import aiohttp
21
22
  from aiohttp.typedefs import StrOrURL
22
23
  from yapic import json as json_parser
@@ -55,9 +56,13 @@ class HTTPSync(Connection):
55
56
  r = requests.get(address, headers=headers, params=params)
56
57
  return self.process_response(r, address, json=json, text=text, uuid=uuid)
57
58
 
58
- def write(self, address: str, data=None, json=False, text=True, uuid=None):
59
+ def write(self, address: str, data=None, json=False, text=True, uuid=None, is_data_json=False):
59
60
  LOG.debug("HTTPSync: post to %s", address)
60
- r = requests.post(address, data=data)
61
+ if (is_data_json):
62
+ r = requests.post(address, json=data)
63
+ else:
64
+ r = requests.post(address, data=data)
65
+
61
66
  return self.process_response(r, address, json=json, text=text, uuid=uuid)
62
67
 
63
68
 
@@ -81,7 +86,7 @@ class AsyncConnection(Connection):
81
86
  self.last_message = None
82
87
  self.authentication = authentication
83
88
  self.subscription = subscription
84
- self.conn: Union[websockets.WebSocketClientProtocol, aiohttp.ClientSession] = None
89
+ self.conn: Union[ClientConnection, aiohttp.ClientSession] = None
85
90
  atexit.register(self.__del__)
86
91
 
87
92
  def __del__(self):
@@ -303,7 +308,7 @@ class WSAsyncConn(AsyncConnection):
303
308
 
304
309
  @property
305
310
  def is_open(self) -> bool:
306
- return self.conn and not self.conn.closed
311
+ return self.conn and not self.conn.state == State.CLOSED
307
312
 
308
313
  async def _open(self):
309
314
  if self.is_open:
@@ -315,7 +320,7 @@ class WSAsyncConn(AsyncConnection):
315
320
  if self.authentication:
316
321
  self.address, self.ws_kwargs = await self.authentication(self.address, self.ws_kwargs)
317
322
 
318
- self.conn = await websockets.connect(self.address, **self.ws_kwargs)
323
+ self.conn = await connect(self.address, **self.ws_kwargs)
319
324
  self.sent = 0
320
325
  self.received = 0
321
326
  self.last_message = None
@@ -357,7 +362,7 @@ class WebsocketEndpoint:
357
362
  authentication: bool = None
358
363
 
359
364
  def __post_init__(self):
360
- defaults = {'ping_interval': 10, 'ping_timeout': None, 'max_size': 2**23, 'max_queue': None, 'read_limit': 2**18}
365
+ defaults = {'ping_interval': 10, 'ping_timeout': None, 'max_size': None, 'max_queue': None}
361
366
  if self.options:
362
367
  defaults.update(self.options)
363
368
  self.options = defaults