Skip to content

Commit

Permalink
Simplify fiat_convert and handle multi-mappings
Browse files Browse the repository at this point in the history
  • Loading branch information
xmatthias committed Aug 17, 2021
1 parent 81715d0 commit 4164f93
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 30 deletions.
40 changes: 24 additions & 16 deletions freqtrade/rpc/fiat_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import datetime
import logging
from typing import Dict
from typing import Dict, List

from cachetools.ttl import TTLCache
from pycoingecko import CoinGeckoAPI
Expand All @@ -25,8 +25,7 @@ class CryptoToFiatConverter:
"""
__instance = None
_coingekko: CoinGeckoAPI = None

_cryptomap: Dict = {}
_coinlistings: List[Dict] = []
_backoff: float = 0.0

def __new__(cls):
Expand All @@ -49,9 +48,8 @@ def __init__(self) -> None:

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}
# Use list-comprehension to ensure we get a list.
self._coinlistings = [x for x in self._coingekko.get_coins_list()]
except RequestException as request_exception:
if "429" in str(request_exception):
logger.warning(
Expand All @@ -69,6 +67,24 @@ def _load_cryptomap(self) -> None:
logger.error(
f"Could not load FIAT Cryptocurrency map for the following problem: {exception}")

def _get_gekko_id(self, crypto_symbol):
if not self._coinlistings:
if self._backoff <= datetime.datetime.now().timestamp():
self._load_cryptomap()
# Still not loaded.
if not self._coinlistings:
return None
else:
return None
found = [x for x in self._coinlistings if x['symbol'] == crypto_symbol]
if len(found) == 1:
return found[0]['id']

if len(found) > 0:
# Wrong!
logger.warning(f"Found multiple mappings in goingekko for {crypto_symbol}.")
return None

def convert_amount(self, crypto_amount: float, crypto_symbol: str, fiat_symbol: str) -> float:
"""
Convert an amount of crypto-currency to fiat
Expand Down Expand Up @@ -143,22 +159,14 @@ def _find_price(self, crypto_symbol: str, fiat_symbol: str) -> float:
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
_gekko_id = self._get_gekko_id(crypto_symbol)

if crypto_symbol not in self._cryptomap:
if not _gekko_id:
# 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,
Expand Down
39 changes: 25 additions & 14 deletions tests/rpc/test_fiat_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def test_fiat_convert_is_supported(mocker):
def test_fiat_convert_find_price(mocker):
fiat_convert = CryptoToFiatConverter()

fiat_convert._cryptomap = {}
fiat_convert._coinlistings = {}
fiat_convert._backoff = 0
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._load_cryptomap',
return_value=None)
Expand All @@ -44,7 +44,7 @@ def test_fiat_convert_find_price(mocker):


def test_fiat_convert_unsupported_crypto(mocker, caplog):
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[])
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._coinlistings', return_value=[])
fiat_convert = CryptoToFiatConverter()
assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0
assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog)
Expand Down Expand Up @@ -88,9 +88,9 @@ def test_fiat_convert_two_FIAT(mocker):
def test_loadcryptomap(mocker):

fiat_convert = CryptoToFiatConverter()
assert len(fiat_convert._cryptomap) == 2
assert len(fiat_convert._coinlistings) == 2

assert fiat_convert._cryptomap["btc"] == "bitcoin"
assert fiat_convert._get_gekko_id("btc") == "bitcoin"


def test_fiat_init_network_exception(mocker):
Expand All @@ -102,11 +102,10 @@ def test_fiat_init_network_exception(mocker):
)
# with pytest.raises(RequestEsxception):
fiat_convert = CryptoToFiatConverter()
fiat_convert._cryptomap = {}
fiat_convert._coinlistings = {}
fiat_convert._load_cryptomap()

length_cryptomap = len(fiat_convert._cryptomap)
assert length_cryptomap == 0
assert len(fiat_convert._coinlistings) == 0


def test_fiat_convert_without_network(mocker):
Expand All @@ -132,32 +131,44 @@ def test_fiat_too_many_requests_response(mocker, caplog):
)
# with pytest.raises(RequestEsxception):
fiat_convert = CryptoToFiatConverter()
fiat_convert._cryptomap = {}
fiat_convert._coinlistings = {}
fiat_convert._load_cryptomap()

length_cryptomap = len(fiat_convert._cryptomap)
assert length_cryptomap == 0
assert len(fiat_convert._coinlistings) == 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_multiple_coins(mocker, caplog):
fiat_convert = CryptoToFiatConverter()
fiat_convert._coinlistings = [
{'id': 'helium', 'symbol': 'hnt', 'name': 'Helium'},
{'id': 'hymnode', 'symbol': 'hnt', 'name': 'Hymnode'},
{'id': 'bitcoin', 'symbol': 'btc', 'name': 'Bitcoin'},
]

assert fiat_convert._get_gekko_id('btc') == 'bitcoin'
assert fiat_convert._get_gekko_id('hnt') is None

assert log_has('Found multiple mappings in goingekko for hnt.', caplog)


def test_fiat_invalid_response(mocker, caplog):
# Because CryptoToFiatConverter is a Singleton we reset the listings
listmock = MagicMock(return_value="{'novalidjson':DEADBEEFf}")
listmock = MagicMock(return_value=None)
mocker.patch.multiple(
'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
get_coins_list=listmock,
)
# with pytest.raises(RequestEsxception):
fiat_convert = CryptoToFiatConverter()
fiat_convert._cryptomap = {}
fiat_convert._coinlistings = []
fiat_convert._load_cryptomap()

length_cryptomap = len(fiat_convert._cryptomap)
assert length_cryptomap == 0
assert len(fiat_convert._coinlistings) == 0
assert log_has_re('Could not load FIAT Cryptocurrency map for the following problem: .*',
caplog)

Expand Down

0 comments on commit 4164f93

Please sign in to comment.