mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-04-25 10:08:36 +02:00
153 lines
5.7 KiB
Python
153 lines
5.7 KiB
Python
import logging
|
|
import time
|
|
|
|
import requests
|
|
from decimal import Decimal
|
|
from typing import Tuple, List
|
|
|
|
from django.db.models import QuerySet
|
|
|
|
from apps.currencies.models import Currency
|
|
from apps.currencies.exchange_rates.base import ExchangeRateProvider
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class SynthFinanceProvider(ExchangeRateProvider):
|
|
"""Implementation for Synth Finance API (synthfinance.com)"""
|
|
|
|
BASE_URL = "https://api.synthfinance.com/rates/live"
|
|
rates_inverted = False # SynthFinance returns non-inverted rates
|
|
|
|
def __init__(self, api_key: str = None):
|
|
super().__init__(api_key)
|
|
self.session = requests.Session()
|
|
self.session.headers.update({"Authorization": f"Bearer {self.api_key}"})
|
|
|
|
def get_rates(
|
|
self, target_currencies: QuerySet, exchange_currencies: set
|
|
) -> List[Tuple[Currency, Currency, Decimal]]:
|
|
results = []
|
|
currency_groups = {}
|
|
for currency in target_currencies:
|
|
if currency.exchange_currency in exchange_currencies:
|
|
group = currency_groups.setdefault(currency.exchange_currency.code, [])
|
|
group.append(currency)
|
|
|
|
for base_currency, currencies in currency_groups.items():
|
|
try:
|
|
to_currencies = ",".join(
|
|
currency.code
|
|
for currency in currencies
|
|
if currency.code != base_currency
|
|
)
|
|
response = self.session.get(
|
|
f"{self.BASE_URL}",
|
|
params={"from": base_currency, "to": to_currencies},
|
|
)
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
rates = data["data"]["rates"]
|
|
|
|
for currency in currencies:
|
|
if currency.code == base_currency:
|
|
rate = Decimal("1")
|
|
else:
|
|
rate = Decimal(str(rates[currency.code]))
|
|
# Return the rate as is, without inversion
|
|
results.append((currency.exchange_currency, currency, rate))
|
|
|
|
credits_used = data["meta"]["credits_used"]
|
|
credits_remaining = data["meta"]["credits_remaining"]
|
|
logger.info(
|
|
f"Synth Finance API call: {credits_used} credits used, {credits_remaining} remaining"
|
|
)
|
|
except requests.RequestException as e:
|
|
logger.error(
|
|
f"Error fetching rates from Synth Finance API for base {base_currency}: {e}"
|
|
)
|
|
except KeyError as e:
|
|
logger.error(
|
|
f"Unexpected response structure from Synth Finance API for base {base_currency}: {e}"
|
|
)
|
|
except Exception as e:
|
|
logger.error(
|
|
f"Unexpected error processing Synth Finance data for base {base_currency}: {e}"
|
|
)
|
|
return results
|
|
|
|
|
|
class CoinGeckoFreeProvider(ExchangeRateProvider):
|
|
"""Implementation for CoinGecko Free API"""
|
|
|
|
BASE_URL = "https://api.coingecko.com/api/v3"
|
|
rates_inverted = True
|
|
|
|
def __init__(self, api_key: str):
|
|
super().__init__(api_key)
|
|
self.session = requests.Session()
|
|
self.session.headers.update({"x-cg-demo-api-key": api_key})
|
|
|
|
@classmethod
|
|
def requires_api_key(cls) -> bool:
|
|
return True
|
|
|
|
def get_rates(
|
|
self, target_currencies: QuerySet, exchange_currencies: set
|
|
) -> List[Tuple[Currency, Currency, Decimal]]:
|
|
results = []
|
|
all_currencies = set(currency.code.lower() for currency in target_currencies)
|
|
all_currencies.update(currency.code.lower() for currency in exchange_currencies)
|
|
|
|
try:
|
|
response = self.session.get(
|
|
f"{self.BASE_URL}/simple/price",
|
|
params={
|
|
"ids": ",".join(all_currencies),
|
|
"vs_currencies": ",".join(all_currencies),
|
|
},
|
|
)
|
|
response.raise_for_status()
|
|
rates_data = response.json()
|
|
|
|
for target_currency in target_currencies:
|
|
if target_currency.exchange_currency in exchange_currencies:
|
|
try:
|
|
rate = Decimal(
|
|
str(
|
|
rates_data[target_currency.code.lower()][
|
|
target_currency.exchange_currency.code.lower()
|
|
]
|
|
)
|
|
)
|
|
# The rate is already inverted, so we don't need to invert it again
|
|
results.append(
|
|
(target_currency.exchange_currency, target_currency, rate)
|
|
)
|
|
except KeyError:
|
|
logger.error(
|
|
f"Rate not found for {target_currency.code} or {target_currency.exchange_currency.code}"
|
|
)
|
|
except Exception as e:
|
|
logger.error(
|
|
f"Error calculating rate for {target_currency.code}: {e}"
|
|
)
|
|
|
|
time.sleep(1) # CoinGecko allows 10-30 calls/minute for free tier
|
|
except requests.RequestException as e:
|
|
logger.error(f"Error fetching rates from CoinGecko API: {e}")
|
|
|
|
return results
|
|
|
|
|
|
class CoinGeckoProProvider(CoinGeckoFreeProvider):
|
|
"""Implementation for CoinGecko Pro API"""
|
|
|
|
BASE_URL = "https://pro-api.coingecko.com/api/v3/simple/price"
|
|
rates_inverted = True
|
|
|
|
def __init__(self, api_key: str):
|
|
super().__init__(api_key)
|
|
self.session = requests.Session()
|
|
self.session.headers.update({"x-cg-pro-api-key": api_key})
|