Merge pull request #4964 from thraizz/develop

Add backoff timer for coingecko API
This commit is contained in:
Matthias 2021-05-22 16:54:29 +01:00 committed by GitHub
commit 74d75599a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 55 additions and 1 deletions

View File

@ -3,11 +3,13 @@ Module that define classes to convert Crypto-currency to FIAT
e.g BTC to USD e.g BTC to USD
""" """
import datetime
import logging import logging
from typing import Dict from typing import Dict
from cachetools.ttl import TTLCache from cachetools.ttl import TTLCache
from pycoingecko import CoinGeckoAPI from pycoingecko import CoinGeckoAPI
from requests.exceptions import RequestException
from freqtrade.constants import SUPPORTED_FIAT from freqtrade.constants import SUPPORTED_FIAT
@ -25,6 +27,7 @@ class CryptoToFiatConverter:
_coingekko: CoinGeckoAPI = None _coingekko: CoinGeckoAPI = None
_cryptomap: Dict = {} _cryptomap: Dict = {}
_backoff: float = 0.0
def __new__(cls): def __new__(cls):
""" """
@ -47,8 +50,21 @@ class CryptoToFiatConverter:
def _load_cryptomap(self) -> None: def _load_cryptomap(self) -> None:
try: try:
coinlistings = self._coingekko.get_coins_list() coinlistings = self._coingekko.get_coins_list()
# Create mapping table from synbol to coingekko_id # Create mapping table from symbol to coingekko_id
self._cryptomap = {x['symbol']: x['id'] for x in coinlistings} 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: except (Exception) as exception:
logger.error( logger.error(
f"Could not load FIAT Cryptocurrency map for the following problem: {exception}") f"Could not load FIAT Cryptocurrency map for the following problem: {exception}")
@ -127,6 +143,15 @@ class CryptoToFiatConverter:
if crypto_symbol == fiat_symbol: if crypto_symbol == fiat_symbol:
return 1.0 return 1.0
if self._cryptomap == {}:
if self._backoff <= datetime.datetime.now().timestamp():
self._load_cryptomap()
# return 0.0 if we still dont 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: if crypto_symbol not in self._cryptomap:
# return 0 for unsupported stake currencies (fiat-convert should not break the bot) # return 0 for unsupported stake currencies (fiat-convert should not break the bot)
logger.warning("unsupported crypto-symbol %s - returning 0.0", crypto_symbol) logger.warning("unsupported crypto-symbol %s - returning 0.0", crypto_symbol)

View File

@ -1,6 +1,7 @@
# pragma pylint: disable=missing-docstring, too-many-arguments, too-many-ancestors, # pragma pylint: disable=missing-docstring, too-many-arguments, too-many-ancestors,
# pragma pylint: disable=protected-access, C0103 # pragma pylint: disable=protected-access, C0103
import datetime
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest import pytest
@ -21,6 +22,12 @@ def test_fiat_convert_is_supported(mocker):
def test_fiat_convert_find_price(mocker): def test_fiat_convert_find_price(mocker):
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter()
fiat_convert._cryptomap = {}
fiat_convert._backoff = 0
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._load_cryptomap',
return_value=None)
assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='EUR') == 0.0
with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'): with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'):
fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='ABC') fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='ABC')
@ -115,6 +122,28 @@ def test_fiat_convert_without_network(mocker):
CryptoToFiatConverter._coingekko = cmc_temp CryptoToFiatConverter._coingekko = cmc_temp
def test_fiat_too_many_requests_response(mocker, caplog):
# Because CryptoToFiatConverter is a Singleton we reset the listings
req_exception = "429 Too Many Requests"
listmock = MagicMock(return_value="{}", side_effect=RequestException(req_exception))
mocker.patch.multiple(
'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
get_coins_list=listmock,
)
# with pytest.raises(RequestEsxception):
fiat_convert = CryptoToFiatConverter()
fiat_convert._cryptomap = {}
fiat_convert._load_cryptomap()
length_cryptomap = len(fiat_convert._cryptomap)
assert length_cryptomap == 0
assert fiat_convert._backoff > datetime.datetime.now().timestamp()
assert log_has(
'Too many requests for Coingecko API, backing off and trying again later.',
caplog
)
def test_fiat_invalid_response(mocker, caplog): def test_fiat_invalid_response(mocker, caplog):
# Because CryptoToFiatConverter is a Singleton we reset the listings # Because CryptoToFiatConverter is a Singleton we reset the listings
listmock = MagicMock(return_value="{'novalidjson':DEADBEEFf}") listmock = MagicMock(return_value="{'novalidjson':DEADBEEFf}")