From b4c746d06bcea3702ee9402f5cbebdb328c6ae58 Mon Sep 17 00:00:00 2001 From: akrem Date: Wed, 22 Mar 2023 09:36:48 -0400 Subject: [PATCH 1/2] Add coinpaprika source --- src/telliot_feeds/feeds/reth_btc_feed.py | 6 +- src/telliot_feeds/feeds/steth_btc_feed.py | 6 +- .../sources/price/spot/coinpaprika.py | 78 +++++++++++++++++++ tests/sources/test_spot_price_sources.py | 22 ++++++ 4 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 src/telliot_feeds/sources/price/spot/coinpaprika.py diff --git a/src/telliot_feeds/feeds/reth_btc_feed.py b/src/telliot_feeds/feeds/reth_btc_feed.py index 17ecd86b..83e82b1c 100644 --- a/src/telliot_feeds/feeds/reth_btc_feed.py +++ b/src/telliot_feeds/feeds/reth_btc_feed.py @@ -1,6 +1,7 @@ from telliot_feeds.datafeed import DataFeed from telliot_feeds.queries.price.spot_price import SpotPrice from telliot_feeds.sources.price.spot.coingecko import CoinGeckoSpotPriceSource +from telliot_feeds.sources.price.spot.coinpaprika import CoinpaprikaSpotPriceSource from telliot_feeds.sources.price_aggregator import PriceAggregator reth_btc_median_feed = DataFeed( @@ -9,6 +10,9 @@ asset="reth", currency="btc", algorithm="median", - sources=[CoinGeckoSpotPriceSource(asset="reth", currency="btc")], + sources=[ + CoinGeckoSpotPriceSource(asset="reth", currency="btc"), + CoinpaprikaSpotPriceSource(asset="reth-rocket-pool-eth", currency="btc"), + ], ), ) diff --git a/src/telliot_feeds/feeds/steth_btc_feed.py b/src/telliot_feeds/feeds/steth_btc_feed.py index a354f4ed..d670f204 100644 --- a/src/telliot_feeds/feeds/steth_btc_feed.py +++ b/src/telliot_feeds/feeds/steth_btc_feed.py @@ -1,6 +1,7 @@ from telliot_feeds.datafeed import DataFeed from telliot_feeds.queries.price.spot_price import SpotPrice from telliot_feeds.sources.price.spot.coingecko import CoinGeckoSpotPriceSource +from telliot_feeds.sources.price.spot.coinpaprika import CoinpaprikaSpotPriceSource from telliot_feeds.sources.price_aggregator import PriceAggregator steth_btc_median_feed = DataFeed( @@ -9,6 +10,9 @@ asset="steth", currency="btc", algorithm="median", - sources=[CoinGeckoSpotPriceSource(asset="steth", currency="btc")], + sources=[ + CoinGeckoSpotPriceSource(asset="steth", currency="btc"), + CoinpaprikaSpotPriceSource(asset="steth-lido-staked-ether", currency="btc"), + ], ), ) diff --git a/src/telliot_feeds/sources/price/spot/coinpaprika.py b/src/telliot_feeds/sources/price/spot/coinpaprika.py new file mode 100644 index 00000000..2a0f38ca --- /dev/null +++ b/src/telliot_feeds/sources/price/spot/coinpaprika.py @@ -0,0 +1,78 @@ +from dataclasses import dataclass +from dataclasses import field +from typing import Any +from urllib.parse import urlencode + +from telliot_feeds.dtypes.datapoint import datetime_now_utc +from telliot_feeds.dtypes.datapoint import OptionalDataPoint +from telliot_feeds.pricing.price_service import WebPriceService +from telliot_feeds.pricing.price_source import PriceSource +from telliot_feeds.utils.log import get_logger + + +logger = get_logger(__name__) + + +class CoinpaprikaSpotPriceService(WebPriceService): + """Coinpaprika Price Service""" + + def __init__(self, **kwargs: Any) -> None: + kwargs["name"] = "Coinpaprika Price Service" + kwargs["url"] = "https://api.coinpaprika.com" + super().__init__(**kwargs) + + async def get_price(self, asset: str, currency: str) -> OptionalDataPoint[float]: + """Implement PriceServiceInterface + + This implementation gets the price from the Coinpaprika API.""" + + asset = asset.lower() + currency = currency.upper() + + url_params = urlencode({"quotes": f"{currency}"}) + + request_url = f"/v1/tickers/{asset}?&{url_params}" + + d = self.get_url(request_url) + + if "error" in d: + logger.error(d) + return None, None + elif "response" in d: + response = d["response"] + + quote = response.get("quotes") + if quote is None: + logger.error("No quotes in response") + return None, None + quote_currency = quote.get(currency) + if quote_currency is None: + logger.error(f"No prices in {currency} returned from Coinpaprika API") + return None, None + + price = quote_currency.get("price") + if price is None: + logger.error("Error parsing Coinpaprika API response") + return None, None + return price, datetime_now_utc() + + else: + raise Exception("Invalid response from get_url") + + +@dataclass +class CoinpaprikaSpotPriceSource(PriceSource): + asset: str = "" + currency: str = "" + service: CoinpaprikaSpotPriceService = field(default_factory=CoinpaprikaSpotPriceService, init=False) + + +if __name__ == "__main__": + import asyncio + + async def main() -> None: + source = CoinpaprikaSpotPriceSource(asset="eth-ethereum", currency="btc") + v, _ = await source.fetch_new_datapoint() + print(v) + + asyncio.run(main()) diff --git a/tests/sources/test_spot_price_sources.py b/tests/sources/test_spot_price_sources.py index fa6b7a7c..793c5982 100644 --- a/tests/sources/test_spot_price_sources.py +++ b/tests/sources/test_spot_price_sources.py @@ -16,6 +16,7 @@ from telliot_feeds.sources.price.spot.coinbase import CoinbaseSpotPriceService from telliot_feeds.sources.price.spot.coingecko import CoinGeckoSpotPriceService from telliot_feeds.sources.price.spot.coinmarketcap import CoinMarketCapSpotPriceService +from telliot_feeds.sources.price.spot.coinpaprika import CoinpaprikaSpotPriceService from telliot_feeds.sources.price.spot.gemini import GeminiSpotPriceService from telliot_feeds.sources.price.spot.kraken import KrakenSpotPriceService from telliot_feeds.sources.price.spot.nomics import NomicsSpotPriceService @@ -38,6 +39,7 @@ "kraken": KrakenSpotPriceService(), "coinmarketcap": CoinMarketCapSpotPriceService(), "bitfinex": BitfinexSpotPriceService(), + "coinpaprika": CoinpaprikaSpotPriceService(), } @@ -293,3 +295,23 @@ async def test_failed_price_service_request(): assert v is None assert t is None + + +@pytest.mark.asyncio +async def test_coinpaprika(): + """Test Coinpaprika price service""" + # Example response from Coinpaprika API + # TODO: consider using a mock responses to lower api calls for any test not just this api + # {"id":"steth-lido-staked-ether","name":"Lido Staked Ether","symbol":"STETH","rank":10, + # "circulating_supply":5842138,"total_supply":5842137,"max_supply":0,"beta_value":1.32621, + # "first_data_at":"2021-08-12T00:00:00Z","last_updated":"2023-03-22T13:14:08Z", + # "quotes":{"USD":{"price":1797.56354446107,"volume_24h":19751787.534851808,"volume_24h_change_24h":-72.95, + # "market_cap":10501614291,"market_cap_change_24h":-1.07,"percent_change_15m":0.01, + # "percent_change_30m":-0.02,"percent_change_1h":-0.03,"percent_change_6h":0.05, + # "percent_change_12h":0.24,"percent_change_24h":-1.16,"percent_change_7d":7.59, + # "percent_change_30d":7.27,"percent_change_1y":-39.28,"ath_price":4824.292564201527, + # "ath_date":"2021-11-10T16:05:00Z","percent_from_price_ath":-62.74}}} + v, t = await get_price("steth-lido-staked-ether", "btc", service["coinpaprika"]) + validate_price(v, t) + assert v is not None + assert t is not None From 13d3eb01e6e13d64e6cf696627e1a174d9449b82 Mon Sep 17 00:00:00 2001 From: akrem Date: Wed, 22 Mar 2023 10:04:40 -0400 Subject: [PATCH 2/2] Remove comment --- tests/sources/test_spot_price_sources.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tests/sources/test_spot_price_sources.py b/tests/sources/test_spot_price_sources.py index 793c5982..98b258e1 100644 --- a/tests/sources/test_spot_price_sources.py +++ b/tests/sources/test_spot_price_sources.py @@ -300,17 +300,6 @@ async def test_failed_price_service_request(): @pytest.mark.asyncio async def test_coinpaprika(): """Test Coinpaprika price service""" - # Example response from Coinpaprika API - # TODO: consider using a mock responses to lower api calls for any test not just this api - # {"id":"steth-lido-staked-ether","name":"Lido Staked Ether","symbol":"STETH","rank":10, - # "circulating_supply":5842138,"total_supply":5842137,"max_supply":0,"beta_value":1.32621, - # "first_data_at":"2021-08-12T00:00:00Z","last_updated":"2023-03-22T13:14:08Z", - # "quotes":{"USD":{"price":1797.56354446107,"volume_24h":19751787.534851808,"volume_24h_change_24h":-72.95, - # "market_cap":10501614291,"market_cap_change_24h":-1.07,"percent_change_15m":0.01, - # "percent_change_30m":-0.02,"percent_change_1h":-0.03,"percent_change_6h":0.05, - # "percent_change_12h":0.24,"percent_change_24h":-1.16,"percent_change_7d":7.59, - # "percent_change_30d":7.27,"percent_change_1y":-39.28,"ath_price":4824.292564201527, - # "ath_date":"2021-11-10T16:05:00Z","percent_from_price_ath":-62.74}}} v, t = await get_price("steth-lido-staked-ether", "btc", service["coinpaprika"]) validate_price(v, t) assert v is not None