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.
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/CHANGES.md +10 -0
- cryptofeed-2.5.0/PKG-INFO +257 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/README.md +6 -6
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/kafka.py +1 -2
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/rabbitmq.py +2 -3
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/redis.py +1 -1
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/socket.py +2 -2
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/callback.py +2 -2
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/binance.py +8 -4
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/bybit.py +1 -1
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/gemini.py +1 -1
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/binance_rest.py +1 -1
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/bitstamp_rest.py +1 -1
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/upbit_rest.py +1 -1
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/feedhandler.py +20 -5
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/raw_data_collection.py +1 -1
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/types.c +17935 -23176
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/types.pyx +1 -0
- cryptofeed-2.5.0/cryptofeed.egg-info/PKG-INFO +257 -0
- cryptofeed-2.5.0/pyproject.toml +121 -0
- cryptofeed-2.5.0/setup.py +27 -0
- cryptofeed-2.4.1/PKG-INFO +0 -1142
- cryptofeed-2.4.1/cryptofeed.egg-info/PKG-INFO +0 -1142
- cryptofeed-2.4.1/pyproject.toml +0 -30
- cryptofeed-2.4.1/setup.py +0 -112
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/AUTHORS.md +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/INSTALL.md +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/LICENSE +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/MANIFEST.in +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/__init__.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/__init__.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/_util.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/aggregate.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/arctic.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/backend.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/gcppubsub.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/http.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/influxdb.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/mongo.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/postgres.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/quasardb.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/quest.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/backends/zmq.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/config.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/connection.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/connection_handler.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/defines.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exceptions.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchange.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/__init__.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/ascendex.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/ascendex_futures.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/bequant.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/binance_delivery.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/binance_futures.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/binance_tr.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/binance_us.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/bitdotcom.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/bitfinex.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/bitflyer.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/bitget.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/bithumb.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/bitmex.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/bitstamp.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/blockchain.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/coinbase.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/cryptodotcom.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/delta.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/deribit.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/dydx.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/exx.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/fmfw.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/gateio.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/gateio_futures.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/hitbtc.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/huobi.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/huobi_dm.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/huobi_swap.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/independent_reserve.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/kraken.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/kraken_futures.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/kucoin.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/__init__.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/bitfinex_rest.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/bitmex_rest.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/coinbase_rest.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/deribit_rest.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/dydx_rest.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/gemini_rest.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/kraken_rest.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/mixins/okx_rest.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/okcoin.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/okx.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/phemex.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/poloniex.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/probit.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/exchanges/upbit.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/feed.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/log.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/nbbo.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/symbols.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/util/__init__.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/util/book.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/util/perf.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/util/split.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed/util/time.py +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed.egg-info/SOURCES.txt +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed.egg-info/dependency_links.txt +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed.egg-info/requires.txt +0 -0
- {cryptofeed-2.4.1 → cryptofeed-2.5.0}/cryptofeed.egg-info/top_level.txt +0 -0
- {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)
|
|
68
|
+

|
|
69
|
+
[](https://pypi.python.org/pypi/cryptofeed)
|
|
70
|
+
[](https://www.codacy.com/app/bmoscon/cryptofeed?utm_source=github.com&utm_medium=referral&utm_content=bmoscon/cryptofeed&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)
|
|
3
|
-

|
|
4
4
|
[](https://pypi.python.org/pypi/cryptofeed)
|
|
5
5
|
[](https://www.codacy.com/app/bmoscon/cryptofeed?utm_source=github.com&utm_medium=referral&utm_content=bmoscon/cryptofeed&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.
|
|
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
|
|
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
|
-
|
|
162
|
+
uv sync
|
|
163
163
|
|
|
164
|
-
Alternatively, you can install in
|
|
164
|
+
Alternatively, you can install from source in editable mode with pip:
|
|
165
165
|
|
|
166
|
-
|
|
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
|
|
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}"
|
|
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}"
|
|
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(
|
|
83
|
-
chunks = wrap(
|
|
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.
|
|
23
|
-
await loop.run_in_executor(None, self.callback,
|
|
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) <
|
|
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,
|
|
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
|
-
|
|
158
|
-
|
|
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['
|
|
199
|
+
options['additional_headers'] = header
|
|
200
200
|
return f'{address}?{symbols}', options
|
|
201
201
|
|
|
202
202
|
async def subscribe(self, conn: AsyncConnection):
|
|
@@ -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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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.
|
|
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'):
|