freqtrade_origin/freqtrade/rpc/fiat_convert.py

207 lines
7.2 KiB
Python
Raw Normal View History

2018-01-28 01:33:04 +00:00
"""
Module that define classes to convert Crypto-currency to FIAT
e.g BTC to USD
"""
2017-12-25 07:51:41 +00:00
import logging
import time
2018-05-30 20:09:03 +00:00
from typing import Dict, List
2018-03-17 21:44:47 +00:00
2020-03-07 10:52:26 +00:00
from pycoingecko import CoinGeckoAPI
2018-07-04 07:31:35 +00:00
2018-06-03 11:47:36 +00:00
from freqtrade.constants import SUPPORTED_FIAT
2017-12-25 07:51:41 +00:00
2018-07-04 07:31:35 +00:00
2017-12-25 07:51:41 +00:00
logger = logging.getLogger(__name__)
2019-09-12 01:39:52 +00:00
class CryptoFiat:
2018-01-28 01:33:04 +00:00
"""
Object to describe what is the price of Crypto-currency in a FIAT
"""
2017-12-25 07:51:41 +00:00
# Constants
CACHE_DURATION = 6 * 60 * 60 # 6 hours
def __init__(self, crypto_symbol: str, fiat_symbol: str, price: float) -> None:
"""
Create an object that will contains the price for a 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)
:param price: Price in FIAT
"""
# Public attributes
self.crypto_symbol = None
self.fiat_symbol = None
self.price = 0.0
# Private attributes
2018-05-30 20:01:29 +00:00
self._expiration = 0.0
2017-12-25 07:51:41 +00:00
2020-03-07 10:52:26 +00:00
self.crypto_symbol = crypto_symbol.lower()
self.fiat_symbol = fiat_symbol.lower()
2017-12-25 07:51:41 +00:00
self.set_price(price=price)
def set_price(self, price: float) -> None:
"""
Set the price of the Crypto-currency in FIAT and set the expiration time
:param price: Price of the current Crypto currency in the fiat
:return: None
"""
self.price = price
self._expiration = time.time() + self.CACHE_DURATION
def is_expired(self) -> bool:
"""
Return if the current price is still valid or needs to be refreshed
:return: bool, true the price is expired and needs to be refreshed, false the price is
still valid
"""
return self._expiration - time.time() <= 0
2019-09-12 01:39:52 +00:00
class CryptoToFiatConverter:
2018-01-28 01:33:04 +00:00
"""
Main class to initiate Crypto to FIAT.
This object contains a list of pair Crypto, FIAT
This object is also a Singleton
"""
__instance = None
2020-03-07 10:52:26 +00:00
_coingekko: CoinGeckoAPI = None
2018-05-13 17:46:08 +00:00
_cryptomap: Dict = {}
def __new__(cls):
2020-03-07 10:52:26 +00:00
"""
2020-03-07 12:05:46 +00:00
This class is a singleton - cannot be instantiated twice.
2020-03-07 10:52:26 +00:00
"""
if CryptoToFiatConverter.__instance is None:
CryptoToFiatConverter.__instance = object.__new__(cls)
try:
2020-03-07 10:52:26 +00:00
CryptoToFiatConverter._coingekko = CoinGeckoAPI()
except BaseException:
2020-03-07 10:52:26 +00:00
CryptoToFiatConverter._coingekko = None
return CryptoToFiatConverter.__instance
def __init__(self) -> None:
2018-05-30 20:09:03 +00:00
self._pairs: List[CryptoFiat] = []
2018-05-13 17:46:08 +00:00
self._load_cryptomap()
def _load_cryptomap(self) -> None:
try:
2020-03-07 10:52:26 +00:00
coinlistings = self._coingekko.get_coins_list()
# Create mapping table from synbol to coingekko_id
self._cryptomap = {x['symbol']: x['id'] for x in coinlistings}
except (Exception) as exception:
logger.error(
2020-03-07 10:52:26 +00:00
f"Could not load FIAT Cryptocurrency map for the following problem: {exception}")
2017-12-25 07:51:41 +00:00
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)
2017-12-25 07:51:41 +00:00
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
"""
2020-03-07 10:52:26 +00:00
crypto_symbol = crypto_symbol.lower()
fiat_symbol = fiat_symbol.lower()
2017-12-25 07:51:41 +00:00
# Check if the fiat convertion you want is supported
if not self._is_supported_fiat(fiat=fiat_symbol):
2018-06-05 05:33:08 +00:00
raise ValueError(f'The fiat {fiat_symbol} is not supported.')
2017-12-25 07:51:41 +00:00
# Get the pair that interest us and return the price in fiat
for pair in self._pairs:
if pair.crypto_symbol == crypto_symbol and pair.fiat_symbol == fiat_symbol:
# If the price is expired we refresh it, avoid to call the API all the time
if pair.is_expired():
pair.set_price(
price=self._find_price(
crypto_symbol=pair.crypto_symbol,
fiat_symbol=pair.fiat_symbol
)
)
# return the last price we have for this pair
return pair.price
# The pair does not exist, so we create it and return the price
return self._add_pair(
crypto_symbol=crypto_symbol,
fiat_symbol=fiat_symbol,
price=self._find_price(
crypto_symbol=crypto_symbol,
fiat_symbol=fiat_symbol
)
)
def _add_pair(self, crypto_symbol: str, fiat_symbol: str, price: float) -> float:
"""
: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
"""
self._pairs.append(
CryptoFiat(
crypto_symbol=crypto_symbol,
fiat_symbol=fiat_symbol,
price=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
"""
2020-03-07 10:52:26 +00:00
return fiat.upper() in SUPPORTED_FIAT
2017-12-25 07:51:41 +00:00
def _find_price(self, crypto_symbol: str, fiat_symbol: str) -> float:
"""
2020-03-07 10:52:26 +00:00
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)
2017-12-25 07:51:41 +00:00
:return: float, price of the crypto-currency in Fiat
"""
# Check if the fiat convertion you want is supported
if not self._is_supported_fiat(fiat=fiat_symbol):
2018-06-05 05:33:08 +00:00
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
2018-05-13 17:46:08 +00:00
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:
2020-03-07 10:52:26 +00:00
_gekko_id = self._cryptomap[crypto_symbol]
return float(
2020-03-07 10:52:26 +00:00
self._coingekko.get_price(
ids=_gekko_id,
vs_currencies=fiat_symbol
)[_gekko_id][fiat_symbol]
)
2020-03-07 10:52:26 +00:00
except Exception as exception:
logger.error("Error in _find_price: %s", exception)
return 0.0