mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-14 04:03:55 +00:00
171 lines
6.2 KiB
Python
171 lines
6.2 KiB
Python
"""
|
|
Module that define classes to convert Crypto-currency to FIAT
|
|
e.g BTC to USD
|
|
"""
|
|
|
|
import datetime
|
|
import logging
|
|
from typing import Dict
|
|
|
|
from cachetools.ttl import TTLCache
|
|
from pycoingecko import CoinGeckoAPI
|
|
from requests.exceptions import RequestException
|
|
|
|
from freqtrade.constants import SUPPORTED_FIAT
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class CryptoToFiatConverter:
|
|
"""
|
|
Main class to initiate Crypto to FIAT.
|
|
This object contains a list of pair Crypto, FIAT
|
|
This object is also a Singleton
|
|
"""
|
|
__instance = None
|
|
_coingekko: CoinGeckoAPI = None
|
|
|
|
_cryptomap: Dict = {}
|
|
_backoff: float = 0.0
|
|
|
|
def __new__(cls):
|
|
"""
|
|
This class is a singleton - cannot be instantiated twice.
|
|
"""
|
|
if CryptoToFiatConverter.__instance is None:
|
|
CryptoToFiatConverter.__instance = object.__new__(cls)
|
|
try:
|
|
CryptoToFiatConverter._coingekko = CoinGeckoAPI()
|
|
except BaseException:
|
|
CryptoToFiatConverter._coingekko = None
|
|
return CryptoToFiatConverter.__instance
|
|
|
|
def __init__(self) -> None:
|
|
# Timeout: 6h
|
|
self._pair_price: TTLCache = TTLCache(maxsize=500, ttl=6 * 60 * 60)
|
|
|
|
self._load_cryptomap()
|
|
|
|
def _load_cryptomap(self) -> None:
|
|
try:
|
|
coinlistings = self._coingekko.get_coins_list()
|
|
# Create mapping table from symbol to coingekko_id
|
|
self._cryptomap = {x['symbol']: x['id'] for x in coinlistings}
|
|
except RequestException as request_exception:
|
|
if "429" in str(request_exception):
|
|
logger.warning(
|
|
"Too many requests for Coingecko API, backing off and trying again later.")
|
|
# Set backoff timestamp to 60 seconds in the future
|
|
self._backoff = datetime.datetime.now().timestamp() + 60
|
|
return
|
|
# If the request is not a 429 error we want to raise the normal error
|
|
logger.error(
|
|
"Could not load FIAT Cryptocurrency map for the following problem: {}".format(
|
|
request_exception
|
|
)
|
|
)
|
|
except (Exception) as exception:
|
|
logger.error(
|
|
f"Could not load FIAT Cryptocurrency map for the following problem: {exception}")
|
|
|
|
def convert_amount(self, crypto_amount: float, crypto_symbol: str, fiat_symbol: str) -> float:
|
|
"""
|
|
Convert an amount of crypto-currency to fiat
|
|
:param crypto_amount: amount of crypto-currency to convert
|
|
:param crypto_symbol: crypto-currency used
|
|
:param fiat_symbol: fiat to convert to
|
|
:return: float, value in fiat of the crypto-currency amount
|
|
"""
|
|
if crypto_symbol == fiat_symbol:
|
|
return float(crypto_amount)
|
|
price = self.get_price(crypto_symbol=crypto_symbol, fiat_symbol=fiat_symbol)
|
|
return float(crypto_amount) * float(price)
|
|
|
|
def get_price(self, crypto_symbol: str, fiat_symbol: str) -> float:
|
|
"""
|
|
Return the price of the Crypto-currency in Fiat
|
|
:param crypto_symbol: Crypto-currency you want to convert (e.g BTC)
|
|
:param fiat_symbol: FIAT currency you want to convert to (e.g USD)
|
|
:return: Price in FIAT
|
|
"""
|
|
crypto_symbol = crypto_symbol.lower()
|
|
fiat_symbol = fiat_symbol.lower()
|
|
inverse = False
|
|
|
|
if crypto_symbol == 'usd':
|
|
# usd corresponds to "uniswap-state-dollar" for coingecko.
|
|
# We'll therefore need to "swap" the currencies
|
|
logger.info(f"reversing Rates {crypto_symbol}, {fiat_symbol}")
|
|
crypto_symbol = fiat_symbol
|
|
fiat_symbol = 'usd'
|
|
inverse = True
|
|
|
|
symbol = f"{crypto_symbol}/{fiat_symbol}"
|
|
# Check if the fiat conversion you want is supported
|
|
if not self._is_supported_fiat(fiat=fiat_symbol):
|
|
raise ValueError(f'The fiat {fiat_symbol} is not supported.')
|
|
|
|
price = self._pair_price.get(symbol, None)
|
|
|
|
if not price:
|
|
price = self._find_price(
|
|
crypto_symbol=crypto_symbol,
|
|
fiat_symbol=fiat_symbol
|
|
)
|
|
if inverse and price != 0.0:
|
|
price = 1 / price
|
|
self._pair_price[symbol] = price
|
|
|
|
return price
|
|
|
|
def _is_supported_fiat(self, fiat: str) -> bool:
|
|
"""
|
|
Check if the FIAT your want to convert to is supported
|
|
:param fiat: FIAT to check (e.g USD)
|
|
:return: bool, True supported, False not supported
|
|
"""
|
|
|
|
return fiat.upper() in SUPPORTED_FIAT
|
|
|
|
def _find_price(self, crypto_symbol: str, fiat_symbol: str) -> float:
|
|
"""
|
|
Call CoinGekko API to retrieve the price in the FIAT
|
|
:param crypto_symbol: Crypto-currency you want to convert (e.g btc)
|
|
:param fiat_symbol: FIAT currency you want to convert to (e.g usd)
|
|
:return: float, price of the crypto-currency in Fiat
|
|
"""
|
|
# Check if the fiat conversion you want is supported
|
|
if not self._is_supported_fiat(fiat=fiat_symbol):
|
|
raise ValueError(f'The fiat {fiat_symbol} is not supported.')
|
|
|
|
# No need to convert if both crypto and fiat are the same
|
|
if crypto_symbol == fiat_symbol:
|
|
return 1.0
|
|
|
|
if self._cryptomap == {}:
|
|
if self._backoff <= datetime.datetime.now().timestamp():
|
|
self._load_cryptomap()
|
|
# return 0.0 if we still don't have data to check, no reason to proceed
|
|
if self._cryptomap == {}:
|
|
return 0.0
|
|
else:
|
|
return 0.0
|
|
|
|
if crypto_symbol not in self._cryptomap:
|
|
# return 0 for unsupported stake currencies (fiat-convert should not break the bot)
|
|
logger.warning("unsupported crypto-symbol %s - returning 0.0", crypto_symbol)
|
|
return 0.0
|
|
|
|
try:
|
|
_gekko_id = self._cryptomap[crypto_symbol]
|
|
return float(
|
|
self._coingekko.get_price(
|
|
ids=_gekko_id,
|
|
vs_currencies=fiat_symbol
|
|
)[_gekko_id][fiat_symbol]
|
|
)
|
|
except Exception as exception:
|
|
logger.error("Error in _find_price: %s", exception)
|
|
return 0.0
|