Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add coinpaprika API source #636

Merged
merged 2 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/telliot_feeds/feeds/reth_btc_feed.py
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -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"),
],
),
)
6 changes: 5 additions & 1 deletion src/telliot_feeds/feeds/steth_btc_feed.py
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -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"),
],
),
)
78 changes: 78 additions & 0 deletions src/telliot_feeds/sources/price/spot/coinpaprika.py
Original file line number Diff line number Diff line change
@@ -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")
akremstudy marked this conversation as resolved.
Show resolved Hide resolved
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())
22 changes: 22 additions & 0 deletions tests/sources/test_spot_price_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -38,6 +39,7 @@
"kraken": KrakenSpotPriceService(),
"coinmarketcap": CoinMarketCapSpotPriceService(),
"bitfinex": BitfinexSpotPriceService(),
"coinpaprika": CoinpaprikaSpotPriceService(),
}


Expand Down Expand Up @@ -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
akremstudy marked this conversation as resolved.
Show resolved Hide resolved
# {"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