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