From 5ca531f47d63ce42bb107c9a05258b1a9f8e9068 Mon Sep 17 00:00:00 2001 From: Herculino Trotta Date: Sun, 17 Aug 2025 03:54:10 -0300 Subject: [PATCH] refactor(currencies): DEPRECATE SYNTH FINANCE --- app/apps/currencies/exchange_rates/fetcher.py | 2 - .../currencies/exchange_rates/providers.py | 137 +----------------- .../0020_migrate_synth_finance_services.py | 51 +++++++ ..._alter_exchangerateservice_service_type.py | 18 +++ app/apps/currencies/models.py | 2 - 5 files changed, 77 insertions(+), 133 deletions(-) create mode 100644 app/apps/currencies/migrations/0020_migrate_synth_finance_services.py create mode 100644 app/apps/currencies/migrations/0021_alter_exchangerateservice_service_type.py diff --git a/app/apps/currencies/exchange_rates/fetcher.py b/app/apps/currencies/exchange_rates/fetcher.py index 324701d..11d43d4 100644 --- a/app/apps/currencies/exchange_rates/fetcher.py +++ b/app/apps/currencies/exchange_rates/fetcher.py @@ -12,8 +12,6 @@ logger = logging.getLogger(__name__) # Map service types to provider classes PROVIDER_MAPPING = { - "synth_finance": providers.SynthFinanceProvider, - "synth_finance_stock": providers.SynthFinanceStockProvider, "coingecko_free": providers.CoinGeckoFreeProvider, "coingecko_pro": providers.CoinGeckoProProvider, "transitive": providers.TransitiveRateProvider, diff --git a/app/apps/currencies/exchange_rates/providers.py b/app/apps/currencies/exchange_rates/providers.py index 886208d..d54da17 100644 --- a/app/apps/currencies/exchange_rates/providers.py +++ b/app/apps/currencies/exchange_rates/providers.py @@ -13,70 +13,6 @@ 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""" @@ -152,71 +88,6 @@ class CoinGeckoProProvider(CoinGeckoFreeProvider): self.session.headers.update({"x-cg-pro-api-key": api_key}) -class SynthFinanceStockProvider(ExchangeRateProvider): - """Implementation for Synth Finance API Real-Time Prices endpoint (synthfinance.com)""" - - BASE_URL = "https://api.synthfinance.com/tickers" - rates_inverted = True - - 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}", "accept": "application/json"} - ) - - def get_rates( - self, target_currencies: QuerySet, exchange_currencies: set - ) -> List[Tuple[Currency, Currency, Decimal]]: - results = [] - - for currency in target_currencies: - if currency.exchange_currency not in exchange_currencies: - continue - - try: - # Same currency has rate of 1 - if currency.code == currency.exchange_currency.code: - rate = Decimal("1") - results.append((currency.exchange_currency, currency, rate)) - continue - - # Fetch real-time price for this ticker - response = self.session.get( - f"{self.BASE_URL}/{currency.code}/real-time" - ) - response.raise_for_status() - data = response.json() - - # Use fair market value as the rate - rate = Decimal(data["data"]["fair_market_value"]) - results.append((currency.exchange_currency, currency, rate)) - - # Log API usage - credits_used = data["meta"]["credits_used"] - credits_remaining = data["meta"]["credits_remaining"] - logger.info( - f"Synth Finance API call for {currency.code}: {credits_used} credits used, {credits_remaining} remaining" - ) - except requests.RequestException as e: - logger.error( - f"Error fetching rate from Synth Finance API for ticker {currency.code}: {e}", - exc_info=True, - ) - except KeyError as e: - logger.error( - f"Unexpected response structure from Synth Finance API for ticker {currency.code}: {e}", - exc_info=True, - ) - except Exception as e: - logger.error( - f"Unexpected error processing Synth Finance data for ticker {currency.code}: {e}", - exc_info=True, - ) - - return results - - class TransitiveRateProvider(ExchangeRateProvider): """Calculates exchange rates through paths of existing rates""" @@ -463,6 +334,10 @@ class TwelveDataProvider(ExchangeRateProvider): logger.info(f"Successfully fetched rate for {symbol} from Twelve Data.") + time.sleep( + 60 + ) # We sleep every pair as to not step over TwelveData's minute limit + except requests.RequestException as e: logger.error( f"Error fetching rate from Twelve Data API for symbol {symbol}: {e}" @@ -610,6 +485,10 @@ class TwelveDataMarketsProvider(ExchangeRateProvider): f"Successfully processed price for {asset.code} as {final_price} {target_exchange_currency.code}" ) + time.sleep( + 60 + ) # We sleep every pair as to not step over TwelveData's minute limit + except requests.RequestException as e: logger.error( f"TwelveDataMarkets: API request failed for {code_value}: {e}" diff --git a/app/apps/currencies/migrations/0020_migrate_synth_finance_services.py b/app/apps/currencies/migrations/0020_migrate_synth_finance_services.py new file mode 100644 index 0000000..34027c5 --- /dev/null +++ b/app/apps/currencies/migrations/0020_migrate_synth_finance_services.py @@ -0,0 +1,51 @@ +# Generated by Django 5.2.5 on 2025-08-17 06:25 + +from django.db import migrations + +# The new value we are migrating to +NEW_SERVICE_TYPE = "frankfurter" +# The old values we are deprecating +OLD_SERVICE_TYPE_TO_UPDATE = "synth_finance" +OLD_SERVICE_TYPE_TO_DELETE = "synth_finance_stock" + + +def forwards_func(apps, schema_editor): + """ + Forward migration: + - Deletes all ExchangeRateService instances with service_type 'synth_finance_stock'. + - Updates all ExchangeRateService instances with service_type 'synth_finance' to 'frankfurter'. + """ + ExchangeRateService = apps.get_model("currencies", "ExchangeRateService") + db_alias = schema_editor.connection.alias + + # 1. Delete the SYNTH_FINANCE_STOCK entries + ExchangeRateService.objects.using(db_alias).filter( + service_type=OLD_SERVICE_TYPE_TO_DELETE + ).delete() + + # 2. Update the SYNTH_FINANCE entries to FRANKFURTER + ExchangeRateService.objects.using(db_alias).filter( + service_type=OLD_SERVICE_TYPE_TO_UPDATE + ).update(service_type=NEW_SERVICE_TYPE, api_key=None) + + +def backwards_func(apps, schema_editor): + """ + Backward migration: This operation is not safely reversible. + - We cannot know which 'frankfurter' services were originally 'synth_finance'. + - The deleted 'synth_finance_stock' services cannot be recovered. + We will leave this function empty to allow migrating backwards without doing anything. + """ + pass + + +class Migration(migrations.Migration): + + dependencies = [ + # Add the previous migration file here + ("currencies", "0019_alter_exchangerateservice_service_type"), + ] + + operations = [ + migrations.RunPython(forwards_func, reverse_code=backwards_func), + ] diff --git a/app/apps/currencies/migrations/0021_alter_exchangerateservice_service_type.py b/app/apps/currencies/migrations/0021_alter_exchangerateservice_service_type.py new file mode 100644 index 0000000..bc42dab --- /dev/null +++ b/app/apps/currencies/migrations/0021_alter_exchangerateservice_service_type.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.5 on 2025-08-17 06:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('currencies', '0020_migrate_synth_finance_services'), + ] + + operations = [ + migrations.AlterField( + model_name='exchangerateservice', + name='service_type', + field=models.CharField(choices=[('coingecko_free', 'CoinGecko (Demo/Free)'), ('coingecko_pro', 'CoinGecko (Pro)'), ('transitive', 'Transitive (Calculated from Existing Rates)'), ('frankfurter', 'Frankfurter'), ('twelvedata', 'TwelveData'), ('twelvedatamarkets', 'TwelveData Markets')], max_length=255, verbose_name='Service Type'), + ), + ] diff --git a/app/apps/currencies/models.py b/app/apps/currencies/models.py index 9d77828..286a87f 100644 --- a/app/apps/currencies/models.py +++ b/app/apps/currencies/models.py @@ -94,8 +94,6 @@ class ExchangeRateService(models.Model): """Configuration for exchange rate services""" class ServiceType(models.TextChoices): - SYNTH_FINANCE = "synth_finance", "Synth Finance" - SYNTH_FINANCE_STOCK = "synth_finance_stock", "Synth Finance Stock" COINGECKO_FREE = "coingecko_free", "CoinGecko (Demo/Free)" COINGECKO_PRO = "coingecko_pro", "CoinGecko (Pro)" TRANSITIVE = "transitive", "Transitive (Calculated from Existing Rates)"