Merge pull request #4964 from thraizz/develop
Add backoff timer for coingecko API
This commit is contained in:
commit
74d75599a9
@ -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)
|
||||||
|
@ -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}")
|
||||||
|
Loading…
Reference in New Issue
Block a user