cryptofeed 2.4.1__tar.gz → 2.5.0__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 (111) hide show
  1. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/CHANGES.md +10 -0
  2. cryptofeed-2.5.0/PKG-INFO +257 -0
  3. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/README.md +6 -6
  4. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/kafka.py +1 -2
  5. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/rabbitmq.py +2 -3
  6. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/redis.py +1 -1
  7. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/socket.py +2 -2
  8. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/callback.py +2 -2
  9. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/binance.py +8 -4
  10. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/bybit.py +1 -1
  11. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/gemini.py +1 -1
  12. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/binance_rest.py +1 -1
  13. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/bitstamp_rest.py +1 -1
  14. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/upbit_rest.py +1 -1
  15. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/feedhandler.py +20 -5
  16. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/raw_data_collection.py +1 -1
  17. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/types.c +17935 -23176
  18. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/types.pyx +1 -0
  19. cryptofeed-2.5.0/cryptofeed.egg-info/PKG-INFO +257 -0
  20. cryptofeed-2.5.0/pyproject.toml +121 -0
  21. cryptofeed-2.5.0/setup.py +27 -0
  22. cryptofeed-2.4.1/PKG-INFO +0 -1142
  23. cryptofeed-2.4.1/cryptofeed.egg-info/PKG-INFO +0 -1142
  24. cryptofeed-2.4.1/pyproject.toml +0 -30
  25. cryptofeed-2.4.1/setup.py +0 -112
  26. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/AUTHORS.md +0 -0
  27. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/INSTALL.md +0 -0
  28. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/LICENSE +0 -0
  29. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/MANIFEST.in +0 -0
  30. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/__init__.py +0 -0
  31. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/__init__.py +0 -0
  32. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/_util.py +0 -0
  33. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/aggregate.py +0 -0
  34. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/arctic.py +0 -0
  35. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/backend.py +0 -0
  36. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/gcppubsub.py +0 -0
  37. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/http.py +0 -0
  38. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/influxdb.py +0 -0
  39. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/mongo.py +0 -0
  40. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/postgres.py +0 -0
  41. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/quasardb.py +0 -0
  42. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/quest.py +0 -0
  43. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/zmq.py +0 -0
  44. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/config.py +0 -0
  45. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/connection.py +0 -0
  46. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/connection_handler.py +0 -0
  47. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/defines.py +0 -0
  48. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exceptions.py +0 -0
  49. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchange.py +0 -0
  50. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/__init__.py +0 -0
  51. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/ascendex.py +0 -0
  52. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/ascendex_futures.py +0 -0
  53. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/bequant.py +0 -0
  54. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/binance_delivery.py +0 -0
  55. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/binance_futures.py +0 -0
  56. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/binance_tr.py +0 -0
  57. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/binance_us.py +0 -0
  58. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/bitdotcom.py +0 -0
  59. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/bitfinex.py +0 -0
  60. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/bitflyer.py +0 -0
  61. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/bitget.py +0 -0
  62. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/bithumb.py +0 -0
  63. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/bitmex.py +0 -0
  64. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/bitstamp.py +0 -0
  65. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/blockchain.py +0 -0
  66. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/coinbase.py +0 -0
  67. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/cryptodotcom.py +0 -0
  68. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/delta.py +0 -0
  69. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/deribit.py +0 -0
  70. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/dydx.py +0 -0
  71. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/exx.py +0 -0
  72. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/fmfw.py +0 -0
  73. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/gateio.py +0 -0
  74. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/gateio_futures.py +0 -0
  75. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/hitbtc.py +0 -0
  76. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/huobi.py +0 -0
  77. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/huobi_dm.py +0 -0
  78. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/huobi_swap.py +0 -0
  79. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/independent_reserve.py +0 -0
  80. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/kraken.py +0 -0
  81. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/kraken_futures.py +0 -0
  82. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/kucoin.py +0 -0
  83. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/__init__.py +0 -0
  84. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/bitfinex_rest.py +0 -0
  85. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/bitmex_rest.py +0 -0
  86. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/coinbase_rest.py +0 -0
  87. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/deribit_rest.py +0 -0
  88. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/dydx_rest.py +0 -0
  89. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/gemini_rest.py +0 -0
  90. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/kraken_rest.py +0 -0
  91. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/okx_rest.py +0 -0
  92. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/okcoin.py +0 -0
  93. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/okx.py +0 -0
  94. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/phemex.py +0 -0
  95. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/poloniex.py +0 -0
  96. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/probit.py +0 -0
  97. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/upbit.py +0 -0
  98. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/feed.py +0 -0
  99. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/log.py +0 -0
  100. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/nbbo.py +0 -0
  101. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/symbols.py +0 -0
  102. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/util/__init__.py +0 -0
  103. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/util/book.py +0 -0
  104. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/util/perf.py +0 -0
  105. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/util/split.py +0 -0
  106. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/util/time.py +0 -0
  107. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed.egg-info/SOURCES.txt +0 -0
  108. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed.egg-info/dependency_links.txt +0 -0
  109. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed.egg-info/requires.txt +0 -0
  110. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed.egg-info/top_level.txt +0 -0
  111. {cryptofeed-2.4.1 → cryptofeed-2.5.0}/setup.cfg +0 -0
@@ -1,5 +1,15 @@
1
1
  ## Changelog
2
2
 
3
+ ### 2.5.0 (2026-08-08)
4
+ * Update: Python 3.12+ required, completed move to uv, `setup.py` removed
5
+ * Update: CI/CD overhaul
6
+ * Bugfix: various bugs in FeedHandler that prevented it from running on 3.14+
7
+ * Bugfix: various backend issues that prevented them from working with later versions of Python
8
+ * Bugfix: Gemini authenticated issue with newer version of websocket library
9
+ * Bugfix: Binance listenKey refresh now uses aiohttp
10
+ * Update: Binance per connection stream limit (issue #1087)
11
+ * Bugfix: Bybit L2_BOOK callback timestamp normalization (issue #1083)
12
+
3
13
  ### 2.4.1 (2025-02-08)
4
14
  * Update: Added `is_data_json` to `write()` in `HTTPSync` from `connection.py` to support JSON payloads (#1071)
5
15
  * Bugfix: Handle empty nextFundingRate in OKX
@@ -0,0 +1,257 @@
1
+ Metadata-Version: 2.4
2
+ Name: cryptofeed
3
+ Version: 2.5.0
4
+ Summary: Cryptocurrency Exchange Websocket Data Feed Handler
5
+ Author-email: Bryant Moscon <bmoscon@gmail.com>
6
+ License-Expression: XFree86-1.1
7
+ Project-URL: Homepage, https://github.com/bmoscon/cryptofeed
8
+ 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
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Framework :: AsyncIO
16
+ Requires-Python: >=3.12
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ License-File: AUTHORS.md
20
+ Requires-Dist: requests>=2.18.4
21
+ Requires-Dist: websockets>=14.1
22
+ Requires-Dist: pyyaml
23
+ Requires-Dist: aiohttp>=3.11.6
24
+ Requires-Dist: aiofile>=2.0.0
25
+ Requires-Dist: yapic.json>=1.6.3
26
+ Requires-Dist: uvloop; platform_system != "Windows"
27
+ Requires-Dist: order_book>=0.6.0
28
+ Requires-Dist: aiodns>=1.1
29
+ Provides-Extra: arctic
30
+ Requires-Dist: arctic; extra == "arctic"
31
+ Requires-Dist: pandas; extra == "arctic"
32
+ Provides-Extra: gcp-pubsub
33
+ Requires-Dist: google_cloud_pubsub>=2.4.1; extra == "gcp-pubsub"
34
+ Requires-Dist: gcloud_aio_pubsub; extra == "gcp-pubsub"
35
+ Provides-Extra: kafka
36
+ Requires-Dist: aiokafka>=0.7.0; extra == "kafka"
37
+ Provides-Extra: mongo
38
+ Requires-Dist: motor; extra == "mongo"
39
+ Provides-Extra: postgres
40
+ Requires-Dist: asyncpg; extra == "postgres"
41
+ Provides-Extra: quasardb
42
+ Requires-Dist: quasardb; extra == "quasardb"
43
+ Requires-Dist: numpy; extra == "quasardb"
44
+ Provides-Extra: rabbit
45
+ Requires-Dist: aio_pika; extra == "rabbit"
46
+ Requires-Dist: pika; extra == "rabbit"
47
+ Provides-Extra: redis
48
+ Requires-Dist: hiredis; extra == "redis"
49
+ Requires-Dist: redis>=4.5.1; extra == "redis"
50
+ Provides-Extra: zmq
51
+ Requires-Dist: pyzmq; extra == "zmq"
52
+ Provides-Extra: all
53
+ Requires-Dist: arctic; extra == "all"
54
+ Requires-Dist: google_cloud_pubsub>=2.4.1; extra == "all"
55
+ Requires-Dist: gcloud_aio_pubsub; extra == "all"
56
+ Requires-Dist: aiokafka>=0.7.0; extra == "all"
57
+ Requires-Dist: motor; extra == "all"
58
+ Requires-Dist: asyncpg; extra == "all"
59
+ Requires-Dist: aio_pika; extra == "all"
60
+ Requires-Dist: pika; extra == "all"
61
+ Requires-Dist: hiredis; extra == "all"
62
+ Requires-Dist: redis>=4.5.1; extra == "all"
63
+ Requires-Dist: pyzmq; extra == "all"
64
+ Dynamic: license-file
65
+
66
+ # Cryptocurrency Exchange Feed Handler
67
+ [![License](https://img.shields.io/badge/license-XFree86-blue.svg)](LICENSE)
68
+ ![Python](https://img.shields.io/badge/Python-3.12+-green.svg)
69
+ [![PyPi](https://img.shields.io/badge/PyPi-cryptofeed-brightgreen.svg)](https://pypi.python.org/pypi/cryptofeed)
70
+ [![Codacy Badge](https://api.codacy.com/project/badge/Grade/efa4e0d6e10b41d0b51454d08f7b33b1)](https://www.codacy.com/app/bmoscon/cryptofeed?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=bmoscon/cryptofeed&amp;utm_campaign=Badge_Grade)
71
+
72
+ Handles multiple cryptocurrency exchange data feeds and returns normalized and standardized results to client registered callbacks for events like trades, book updates, ticker updates, etc. Utilizes websockets when possible, but can also poll data via REST endpoints if a websocket is not provided.
73
+
74
+ ## Supported exchanges
75
+
76
+ * [AscendEX](https://ascendex.com/)
77
+ * [Bequant](https://bequant.io/)
78
+ * [Bitfinex](https://bitfinex.com)
79
+ * [bitFlyer](https://bitflyer.com/)
80
+ * [Bithumb](https://en.bithumb.com/)
81
+ * [Bitstamp](https://www.bitstamp.net/)
82
+ * [Blockchain.com](https://www.blockchain.com/)
83
+ * [Bybit](https://www.bybit.com/)
84
+ * [Binance](https://www.binance.com/en)
85
+ * [Binance Delivery](https://binance-docs.github.io/apidocs/delivery/en/)
86
+ * [Binance Futures](https://www.binance.com/en/futures)
87
+ * [Binance US](https://www.binance.us/en)
88
+ * [Bit.com](https://www.bit.com)
89
+ * [Bitget](https://www.bitget.com/)
90
+ * [BitMEX](https://www.bitmex.com/)
91
+ * [Coinbase](https://www.coinbase.com/)
92
+ * [Crypto.com](https://www.crypto.com)
93
+ * [Delta](https://www.delta.exchange/)
94
+ * [Deribit](https://www.deribit.com/)
95
+ * [dYdX](https://dydx.exchange/)
96
+ * [FMFW.io](https://www.fmfw.io/)
97
+ * [EXX](https://www.exx.com/)
98
+ * [Gate.io](https://www.gate.io/)
99
+ * [Gate.io Futures](https://www.gate.io/futures_center)
100
+ * [Gemini](https://gemini.com/)
101
+ * [HitBTC](https://hitbtc.com/)
102
+ * [Huobi](https://www.hbg.com/)
103
+ * [Huobi DM](https://www.huobi.com/en-us/markets/hb_dm/)
104
+ * Huobi Swap (Coin-M and USDT-M)
105
+ * [Independent Reserve](https://www.independentreserve.com/)
106
+ * [Kraken](https://www.kraken.com/)
107
+ * [Kraken Futures](https://futures.kraken.com/)
108
+ * [KuCoin](https://www.kucoin.com/)
109
+ * [OKCoin](http://okcoin.com/)
110
+ * [OKX](https://www.okx.com/)
111
+ * [Phemex](https://phemex.com/)
112
+ * [Poloniex](https://www.poloniex.com/)
113
+ * [ProBit](https://www.probit.com/)
114
+ * [Upbit](https://sg.upbit.com/home)
115
+
116
+
117
+ ## Basic Usage
118
+
119
+ Create a FeedHandler object and add subscriptions. For the various data channels that an exchange supports, you can supply callbacks for data events, or use provided backends (described below) to handle the data for you. Start the feed handler and you're done!
120
+
121
+ ```python
122
+ from cryptofeed import FeedHandler
123
+ # not all imports shown for clarity
124
+
125
+ fh = FeedHandler()
126
+
127
+ # ticker, trade, and book are user defined functions that
128
+ # will be called when ticker, trade and book updates are received
129
+ ticker_cb = {TICKER: ticker}
130
+ trade_cb = {TRADES: trade}
131
+ gemini_cb = {TRADES: trade, L2_BOOK: book}
132
+
133
+
134
+ fh.add_feed(Coinbase(symbols=['BTC-USD'], channels=[TICKER], callbacks=ticker_cb))
135
+ fh.add_feed(Bitfinex(symbols=['BTC-USD'], channels=[TICKER], callbacks=ticker_cb))
136
+ fh.add_feed(Poloniex(symbols=['BTC-USDT'], channels=[TRADES], callbacks=trade_cb))
137
+ fh.add_feed(Gemini(symbols=['BTC-USD', 'ETH-USD'], channels=[TRADES, L2_BOOK], callbacks=gemini_cb))
138
+
139
+ fh.run()
140
+ ```
141
+
142
+ Please see the [examples](https://github.com/bmoscon/cryptofeed/tree/master/examples) for more code samples and the [documentation](https://github.com/bmoscon/cryptofeed/blob/master/docs/README.md) for more information about the library usage.
143
+
144
+
145
+ For an example of a containerized application using cryptofeed to store data to a backend, please see [Cryptostore](https://github.com/bmoscon/cryptostore).
146
+
147
+
148
+ ## National Best Bid/Offer (NBBO)
149
+
150
+ Cryptofeed also provides a synthetic [NBBO](examples/demo_nbbo.py) (National Best Bid/Offer) feed that aggregates the best bids and asks from the user specified feeds.
151
+
152
+ ```python
153
+ from cryptofeed import FeedHandler
154
+ from cryptofeed.exchanges import Coinbase, Gemini, Kraken
155
+
156
+
157
+ def nbbo_update(symbol, bid, bid_size, ask, ask_size, bid_feed, ask_feed):
158
+ print(f'Pair: {symbol} Bid Price: {bid:.2f} Bid Size: {bid_size:.6f} Bid Feed: {bid_feed} Ask Price: {ask:.2f} Ask Size: {ask_size:.6f} Ask Feed: {ask_feed}')
159
+
160
+
161
+ def main():
162
+ f = FeedHandler()
163
+ f.add_nbbo([Coinbase, Kraken, Gemini], ['BTC-USD'], nbbo_update)
164
+ f.run()
165
+ ```
166
+
167
+ ## Supported Channels
168
+
169
+ Cryptofeed supports the following channels from exchanges:
170
+
171
+ ### Market Data Channels (Public)
172
+
173
+ * L1_BOOK - Top of book
174
+ * L2_BOOK - Price aggregated sizes. Some exchanges provide the entire depth, some provide a subset.
175
+ * L3_BOOK - Price aggregated orders. Like the L2 book, some exchanges may only provide partial depth.
176
+ * TRADES - Note this reports the taker's side, even for exchanges that report the maker side.
177
+ * TICKER
178
+ * FUNDING
179
+ * OPEN_INTEREST - Open interest data.
180
+ * LIQUIDATIONS
181
+ * INDEX
182
+ * CANDLES - Candlestick / K-Line data.
183
+
184
+ ### Authenticated Data Channels
185
+
186
+ * ORDER_INFO - Order status updates
187
+ * TRANSACTIONS - Real-time updates on account deposits and withdrawals
188
+ * BALANCES - Updates on wallet funds
189
+ * FILLS - User's executed trades
190
+
191
+
192
+ ## Backends
193
+
194
+ Cryptofeed supports `backend` callbacks that will write directly to storage or other interfaces.
195
+
196
+ Supported Backends:
197
+ * Redis (Streams and Sorted Sets)
198
+ * [Arctic](https://github.com/manahl/arctic)
199
+ * ZeroMQ
200
+ * UDP Sockets
201
+ * TCP Sockets
202
+ * Unix Domain Sockets
203
+ * [InfluxDB v2](https://github.com/influxdata/influxdb)
204
+ * MongoDB
205
+ * Kafka
206
+ * RabbitMQ
207
+ * PostgreSQL
208
+ * [QuasarDB](https://quasar.ai/)
209
+ * GCP Pub/Sub
210
+ * [QuestDB](https://questdb.io/)
211
+
212
+
213
+ ## Installation
214
+
215
+ **Note:** cryptofeed requires Python 3.12+
216
+
217
+ Cryptofeed can be installed from PyPi. (It's recommended that you install in a virtual environment of your choosing).
218
+
219
+ pip install cryptofeed
220
+
221
+ Cryptofeed has optional dependencies, depending on the backends used. You can install them individually, or all at once. To install Cryptofeed along with all its optional dependencies in one bundle:
222
+
223
+ pip install cryptofeed[all]
224
+
225
+ If you wish to clone the repository and work on it (development uses [uv](https://docs.astral.sh/uv/)), run this from the root of the cloned repository:
226
+
227
+ uv sync
228
+
229
+ Alternatively, you can install from source in editable mode with pip:
230
+
231
+ pip install -e .
232
+
233
+ See more discussion of package installation in [INSTALL.md](https://github.com/bmoscon/cryptofeed/blob/master/INSTALL.md).
234
+
235
+
236
+
237
+ ## Rest API
238
+
239
+ Cryptofeed supports some REST interfaces for retrieving real-time and historical data, as well as order placement and account management. These are integrated into the exchange classes directly. You can view the supported methods by calling the `info()` method on any exchange. The methods for interacting with the exchange RET endpoints exist in two flavors, the synchronous methods (suffixed with `_sync`) as well as the asynchronous which can be utilized with asyncio. For more information see the [documentation](docs/rest.md).
240
+
241
+
242
+ ## Future Work
243
+
244
+ 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.
245
+
246
+ ## Contributing
247
+
248
+ Issues and PRs are welcomed!
249
+
250
+ Cryptofeed wouldn't be possible without the help of many [contributors](AUTHORS.md)! I owe them and all other contributors my thanks!
251
+
252
+ ## Donations / Support
253
+
254
+ Support and donations are appreciated but not required. You can donate via [GitHub Sponsors](https://github.com/sponsors/bmoscon), or via the addresses below:
255
+
256
+ * Bitcoin: bc1qm0kxz8hqacaglku5fjhfe9a5hjnuyfwk02lsyr
257
+ * Ethereum: 0x690709FEe13eEce9E7852089BB2D53Ae5D073154
@@ -1,6 +1,6 @@
1
1
  # Cryptocurrency Exchange Feed Handler
2
2
  [![License](https://img.shields.io/badge/license-XFree86-blue.svg)](LICENSE)
3
- ![Python](https://img.shields.io/badge/Python-3.8+-green.svg)
3
+ ![Python](https://img.shields.io/badge/Python-3.12+-green.svg)
4
4
  [![PyPi](https://img.shields.io/badge/PyPi-cryptofeed-brightgreen.svg)](https://pypi.python.org/pypi/cryptofeed)
5
5
  [![Codacy Badge](https://api.codacy.com/project/badge/Grade/efa4e0d6e10b41d0b51454d08f7b33b1)](https://www.codacy.com/app/bmoscon/cryptofeed?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=bmoscon/cryptofeed&amp;utm_campaign=Badge_Grade)
6
6
 
@@ -147,7 +147,7 @@ Supported Backends:
147
147
 
148
148
  ## Installation
149
149
 
150
- **Note:** cryptofeed requires Python 3.8+
150
+ **Note:** cryptofeed requires Python 3.12+
151
151
 
152
152
  Cryptofeed can be installed from PyPi. (It's recommended that you install in a virtual environment of your choosing).
153
153
 
@@ -157,13 +157,13 @@ Cryptofeed has optional dependencies, depending on the backends used. You can in
157
157
 
158
158
  pip install cryptofeed[all]
159
159
 
160
- If you wish to clone the repository and install from source, run this command from the root of the cloned repository.
160
+ If you wish to clone the repository and work on it (development uses [uv](https://docs.astral.sh/uv/)), run this from the root of the cloned repository:
161
161
 
162
- python setup.py install
162
+ uv sync
163
163
 
164
- Alternatively, you can install in 'edit' mode (also called development mode):
164
+ Alternatively, you can install from source in editable mode with pip:
165
165
 
166
- python setup.py develop
166
+ pip install -e .
167
167
 
168
168
  See more discussion of package installation in [INSTALL.md](https://github.com/bmoscon/cryptofeed/blob/master/INSTALL.md).
169
169
 
@@ -56,11 +56,10 @@ class KafkaCallback(BackendQueue):
56
56
 
57
57
  async def _connect(self):
58
58
  if not self.producer:
59
- loop = asyncio.get_event_loop()
60
59
  try:
61
60
  config_keys = ', '.join([k for k in self.producer_config.keys()])
62
61
  LOG.info(f'{self.__class__.__name__}: Configuring AIOKafka with the following parameters: {config_keys}')
63
- self.producer = AIOKafkaProducer(**self.producer_config, loop=loop)
62
+ self.producer = AIOKafkaProducer(**self.producer_config)
64
63
  # Quit if invalid config option passed to AIOKafka
65
64
  except (TypeError, ValueError) as e:
66
65
  LOG.error(f'{self.__class__.__name__}: Invalid AIOKafka configuration: {e.args}{chr(10)}See https://aiokafka.readthedocs.io/en/stable/api.html#aiokafka.AIOKafkaProducer for list of configuration options')
@@ -4,7 +4,6 @@ Copyright (C) 2017-2025 Bryant Moscon - bmoscon@gmail.com
4
4
  Please see the LICENSE file for the terms and conditions
5
5
  associated with this software.
6
6
  '''
7
- import asyncio
8
7
  from collections import defaultdict
9
8
 
10
9
  import aio_pika
@@ -44,11 +43,11 @@ class RabbitCallback:
44
43
  async def connect(self):
45
44
  if not self.conn:
46
45
  if self.exchange_mode:
47
- connection = await aio_pika.connect_robust(f"amqp://{self.host}", loop=asyncio.get_running_loop())
46
+ connection = await aio_pika.connect_robust(f"amqp://{self.host}")
48
47
  self.conn = await connection.channel()
49
48
  self.conn = await self.conn.declare_exchange(self.exchange_name, self.exchange_type, durable=True, auto_delete=False)
50
49
  else:
51
- connection = await aio_pika.connect_robust(f"amqp://{self.host}", loop=asyncio.get_running_loop())
50
+ connection = await aio_pika.connect_robust(f"amqp://{self.host}")
52
51
  self.conn = await connection.channel()
53
52
  await self.conn.declare_queue(self.queue_name, auto_delete=False, durable=True)
54
53
 
@@ -24,7 +24,7 @@ class RedisCallback(BackendQueue):
24
24
  prefix = 'unix://'
25
25
  port = None
26
26
 
27
- self.redis = f"{prefix}{host}" + f":{port}" if port else ""
27
+ self.redis = f"{prefix}{host}" + (f":{port}" if port else "")
28
28
  self.key = key if key else self.default_key
29
29
  self.numeric_type = numeric_type
30
30
  self.none_to = none_to
@@ -79,8 +79,8 @@ class SocketCallback(BackendQueue):
79
79
  data = {'type': self.key, 'data': update}
80
80
  data = json.dumps(data)
81
81
  if self.conn_type == 'udp://':
82
- if len(update) > self.mtu:
83
- chunks = wrap(update, self.mtu)
82
+ if len(data) > self.mtu:
83
+ chunks = wrap(data, self.mtu)
84
84
  for chunk in chunks:
85
85
  msg = json.dumps({'type': 'chunked', 'chunks': len(chunks), 'data': chunk}).encode()
86
86
  self.conn.sendto(msg)
@@ -19,8 +19,8 @@ class Callback:
19
19
  elif self.is_async:
20
20
  await self.callback(obj, receipt_timestamp)
21
21
  else:
22
- loop = asyncio.get_event_loop()
23
- await loop.run_in_executor(None, self.callback, (obj, receipt_timestamp))
22
+ loop = asyncio.get_running_loop()
23
+ await loop.run_in_executor(None, self.callback, obj, receipt_timestamp)
24
24
 
25
25
 
26
26
  class TradeCallback(Callback):
@@ -8,6 +8,7 @@ import logging
8
8
  from asyncio import create_task, sleep
9
9
  from collections import defaultdict
10
10
  from decimal import Decimal
11
+ import aiohttp
11
12
  import requests
12
13
  import time
13
14
  from typing import Dict, Union, Tuple
@@ -45,6 +46,7 @@ class Binance(Feed, BinanceRestMixin):
45
46
  ORDER_INFO: ORDER_INFO
46
47
  }
47
48
  request_limit = 20
49
+ per_connection_limit = 1024
48
50
 
49
51
  @classmethod
50
52
  def timestamp_normalize(cls, ts: float) -> float:
@@ -135,14 +137,14 @@ class Binance(Feed, BinanceRestMixin):
135
137
  pair = pair.lower()
136
138
  subs.append(f"{pair}@{stream}")
137
139
 
138
- if 0 < len(subs) < 200:
140
+ if 0 < len(subs) < self.per_connection_limit:
139
141
  return address + '/'.join(subs)
140
142
  else:
141
143
  def split_list(_list: list, n: int):
142
144
  for i in range(0, len(_list), n):
143
145
  yield _list[i:i + n]
144
146
 
145
- return [address + '/'.join(chunk) for chunk in split_list(subs, 200)]
147
+ return [address + '/'.join(chunk) for chunk in split_list(subs, self.per_connection_limit)]
146
148
 
147
149
  def _reset(self):
148
150
  self._l2_book = {}
@@ -154,8 +156,10 @@ class Binance(Feed, BinanceRestMixin):
154
156
  if self._auth_token is None:
155
157
  raise ValueError('There is no token to refresh')
156
158
  payload = {'listenKey': self._auth_token}
157
- r = requests.put(f'{self.rest_endpoints[0].route("authentication", sandbox=self.sandbox)}?{urlencode(payload)}', headers={'X-MBX-APIKEY': self.key_id})
158
- r.raise_for_status()
159
+ url = f'{self.rest_endpoints[0].route("authentication", sandbox=self.sandbox)}?{urlencode(payload)}'
160
+ async with aiohttp.ClientSession() as session:
161
+ async with session.put(url, headers={'X-MBX-APIKEY': self.key_id}) as r:
162
+ r.raise_for_status()
159
163
 
160
164
  def _generate_token(self) -> str:
161
165
  url = self.rest_endpoints[0].route('authentication', sandbox=self.sandbox)
@@ -402,7 +402,7 @@ class Bybit(Feed):
402
402
  if update_type == 'delta':
403
403
  delta = {BID: data['b'], ASK: data['a']}
404
404
 
405
- await self.book_callback(L2_BOOK, self._l2_book[pair], timestamp, timestamp=int(msg['ts']), raw=msg, delta=delta)
405
+ await self.book_callback(L2_BOOK, self._l2_book[pair], timestamp, timestamp=self.timestamp_normalize(int(msg['ts'])), raw=msg, delta=delta)
406
406
 
407
407
  async def _ticker_open_interest_funding_index(self, msg: dict, timestamp: float, conn: AsyncConnection):
408
408
  '''
@@ -196,7 +196,7 @@ class Gemini(Feed, GeminiRestMixin):
196
196
  if self.is_authenticated_channel(channel):
197
197
  symbols.extend(self.subscription.get(channel))
198
198
  symbols = '&'.join([f"symbolFilter={s.lower()}" for s in symbols]) # needs to match REST format (lower case)
199
- options['extra_headers'] = header
199
+ options['additional_headers'] = header
200
200
  return f'{address}?{symbols}', options
201
201
 
202
202
  async def subscribe(self, conn: AsyncConnection):
@@ -218,7 +218,7 @@ class BinanceDeliveryRestMixin(BinanceRestMixin):
218
218
  class BinanceUSRestMixin(BinanceRestMixin):
219
219
  api = 'https://api.binance.us/api/v3/'
220
220
  rest_channels = (
221
- TRADES
221
+ TRADES,
222
222
  )
223
223
 
224
224
 
@@ -21,7 +21,7 @@ LOG = logging.getLogger('feedhandler')
21
21
 
22
22
  class BitstampRestMixin(RestExchange):
23
23
  api = "https://www.bitstamp.net/api/v2/"
24
- rest_channels = (CANDLES)
24
+ rest_channels = (CANDLES,)
25
25
  valid_candle_intervals = {'1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h', '6h', '12h', '1d', '3d'}
26
26
 
27
27
  async def candles(self, symbol: str, start=None, end=None, interval='1m', retry_count=1, retry_delay=60):
@@ -22,7 +22,7 @@ LOG = logging.getLogger('feedhandler')
22
22
 
23
23
  class UpbitRestMixin(RestExchange):
24
24
  api = "https://api.upbit.com/v1/"
25
- rest_channels = (CANDLES)
25
+ rest_channels = (CANDLES,)
26
26
  valid_candle_intervals = {'1m', '3m', '5m', '10m', '15m', '30m', '1h', '4h', '1d', '1w', '1M'}
27
27
 
28
28
  async def candles(self, symbol: str, start=None, end=None, interval='1m', retry_count=1, retry_delay=60):
@@ -10,6 +10,7 @@ import logging
10
10
  import signal
11
11
  from signal import SIGABRT, SIGINT, SIGTERM
12
12
  import sys
13
+ import warnings
13
14
  from typing import List
14
15
 
15
16
  try:
@@ -32,6 +33,16 @@ from cryptofeed.exchanges import EXCHANGE_MAP
32
33
  LOG = logging.getLogger('feedhandler')
33
34
 
34
35
 
36
+ def _get_event_loop():
37
+ # handle python versions where asyncio.get_event_loop() was removed
38
+ try:
39
+ return asyncio.get_event_loop()
40
+ except RuntimeError:
41
+ loop = asyncio.new_event_loop()
42
+ asyncio.set_event_loop(loop)
43
+ return loop
44
+
45
+
35
46
  def setup_signal_handlers(loop):
36
47
  """
37
48
  This must be run from the loop in the main thread
@@ -73,7 +84,9 @@ class FeedHandler:
73
84
  if self.config.uvloop:
74
85
  try:
75
86
  import uvloop
76
- asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
87
+ with warnings.catch_warnings():
88
+ warnings.simplefilter('ignore', DeprecationWarning)
89
+ asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
77
90
  LOG.info('FH: uvloop initalized')
78
91
  except ImportError:
79
92
  LOG.info("FH: uvloop not initialized")
@@ -100,7 +113,7 @@ class FeedHandler:
100
113
 
101
114
  if self.running:
102
115
  if loop is None:
103
- loop = asyncio.get_event_loop()
116
+ loop = _get_event_loop()
104
117
 
105
118
  self.feeds[-1].start(loop)
106
119
 
@@ -132,7 +145,7 @@ class FeedHandler:
132
145
  a custom exception handler for asyncio
133
146
  """
134
147
  self.running = True
135
- loop = asyncio.get_event_loop()
148
+ loop = _get_event_loop()
136
149
  # Good to enable when debugging or without code change: export PYTHONASYNCIODEBUG=1)
137
150
  # loop.set_debug(True)
138
151
 
@@ -162,7 +175,7 @@ class FeedHandler:
162
175
  def _stop(self, loop=None):
163
176
  self.running = False
164
177
  if not loop:
165
- loop = asyncio.get_event_loop()
178
+ loop = _get_event_loop()
166
179
 
167
180
  LOG.info('FH: shutdown connections handlers in feeds')
168
181
  for feed in self.feeds:
@@ -191,13 +204,15 @@ class FeedHandler:
191
204
  await asyncio.gather(*shutdown_tasks)
192
205
 
193
206
  def stop(self, loop=None):
207
+ if not loop:
208
+ loop = _get_event_loop()
194
209
  shutdown_tasks = self._stop(loop=loop)
195
210
  loop.run_until_complete(asyncio.gather(*shutdown_tasks))
196
211
 
197
212
  def close(self, loop=None):
198
213
  """Stop the asynchronous generators and close the event loop."""
199
214
  if not loop:
200
- loop = asyncio.get_event_loop()
215
+ loop = _get_event_loop()
201
216
 
202
217
  LOG.info('FH: stop the AsyncIO loop')
203
218
  loop.stop()
@@ -19,7 +19,7 @@ from cryptofeed.exchanges import EXCHANGE_MAP
19
19
 
20
20
  def bytes_string_to_bytes(string):
21
21
  tree = ast.parse(string)
22
- return tree.body[0].value.s
22
+ return tree.body[0].value.value
23
23
 
24
24
 
25
25
  def playback(feed: str, filenames: list, callbacks: dict = None, config: str = 'config.yaml'):