Exchange coingekko for coinmarketcap
This commit is contained in:
		| @@ -7,7 +7,7 @@ import logging | ||||
| import time | ||||
| from typing import Dict, List | ||||
|  | ||||
| from coinmarketcap import Market | ||||
| from pycoingecko import CoinGeckoAPI | ||||
|  | ||||
| from freqtrade.constants import SUPPORTED_FIAT | ||||
|  | ||||
| @@ -38,8 +38,8 @@ class CryptoFiat: | ||||
|         # Private attributes | ||||
|         self._expiration = 0.0 | ||||
|  | ||||
|         self.crypto_symbol = crypto_symbol.upper() | ||||
|         self.fiat_symbol = fiat_symbol.upper() | ||||
|         self.crypto_symbol = crypto_symbol.lower() | ||||
|         self.fiat_symbol = fiat_symbol.lower() | ||||
|         self.set_price(price=price) | ||||
|  | ||||
|     def set_price(self, price: float) -> None: | ||||
| @@ -67,17 +67,20 @@ class CryptoToFiatConverter: | ||||
|     This object is also a Singleton | ||||
|     """ | ||||
|     __instance = None | ||||
|     _coinmarketcap: Market = None | ||||
|     _coingekko: CoinGeckoAPI = None | ||||
|  | ||||
|     _cryptomap: Dict = {} | ||||
|  | ||||
|     def __new__(cls): | ||||
|         """ | ||||
|         This class is a singleton - should not be instanciated twice. | ||||
|         """ | ||||
|         if CryptoToFiatConverter.__instance is None: | ||||
|             CryptoToFiatConverter.__instance = object.__new__(cls) | ||||
|             try: | ||||
|                 CryptoToFiatConverter._coinmarketcap = Market() | ||||
|                 CryptoToFiatConverter._coingekko = CoinGeckoAPI() | ||||
|             except BaseException: | ||||
|                 CryptoToFiatConverter._coinmarketcap = None | ||||
|                 CryptoToFiatConverter._coingekko = None | ||||
|         return CryptoToFiatConverter.__instance | ||||
|  | ||||
|     def __init__(self) -> None: | ||||
| @@ -86,14 +89,12 @@ class CryptoToFiatConverter: | ||||
|  | ||||
|     def _load_cryptomap(self) -> None: | ||||
|         try: | ||||
|             coinlistings = self._coinmarketcap.listings() | ||||
|             self._cryptomap = dict(map(lambda coin: (coin["symbol"], str(coin["id"])), | ||||
|                                        coinlistings["data"])) | ||||
|         except (BaseException) as exception: | ||||
|             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( | ||||
|                 "Could not load FIAT Cryptocurrency map for the following problem: %s", | ||||
|                 type(exception).__name__ | ||||
|             ) | ||||
|                 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: | ||||
|         """ | ||||
| @@ -115,8 +116,8 @@ class CryptoToFiatConverter: | ||||
|         :param fiat_symbol: FIAT currency you want to convert to (e.g USD) | ||||
|         :return: Price in FIAT | ||||
|         """ | ||||
|         crypto_symbol = crypto_symbol.upper() | ||||
|         fiat_symbol = fiat_symbol.upper() | ||||
|         crypto_symbol = crypto_symbol.lower() | ||||
|         fiat_symbol = fiat_symbol.lower() | ||||
|  | ||||
|         # Check if the fiat convertion you want is supported | ||||
|         if not self._is_supported_fiat(fiat=fiat_symbol): | ||||
| @@ -170,15 +171,13 @@ class CryptoToFiatConverter: | ||||
|         :return: bool, True supported, False not supported | ||||
|         """ | ||||
|  | ||||
|         fiat = fiat.upper() | ||||
|  | ||||
|         return fiat in SUPPORTED_FIAT | ||||
|         return fiat.upper() in SUPPORTED_FIAT | ||||
|  | ||||
|     def _find_price(self, crypto_symbol: str, fiat_symbol: str) -> float: | ||||
|         """ | ||||
|         Call CoinMarketCap 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) | ||||
|         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 convertion you want is supported | ||||
| @@ -195,12 +194,13 @@ class CryptoToFiatConverter: | ||||
|             return 0.0 | ||||
|  | ||||
|         try: | ||||
|             _gekko_id = self._cryptomap[crypto_symbol] | ||||
|             return float( | ||||
|                 self._coinmarketcap.ticker( | ||||
|                     currency=self._cryptomap[crypto_symbol], | ||||
|                     convert=fiat_symbol | ||||
|                 )['data']['quotes'][fiat_symbol.upper()]['price'] | ||||
|                 self._coingekko.get_price( | ||||
|                     ids=_gekko_id, | ||||
|                     vs_currencies=fiat_symbol | ||||
|                 )[_gekko_id][fiat_symbol] | ||||
|             ) | ||||
|         except BaseException as exception: | ||||
|         except Exception as exception: | ||||
|             logger.error("Error in _find_price: %s", exception) | ||||
|             return 0.0 | ||||
|   | ||||
| @@ -167,23 +167,23 @@ def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False)) -> None: | ||||
|  | ||||
|  | ||||
| @pytest.fixture(autouse=True) | ||||
| def patch_coinmarketcap(mocker) -> None: | ||||
| def patch_coingekko(mocker) -> None: | ||||
|     """ | ||||
|     Mocker to coinmarketcap to speed up tests | ||||
|     :param mocker: mocker to patch coinmarketcap class | ||||
|     Mocker to coingekko to speed up tests | ||||
|     :param mocker: mocker to patch coingekko class | ||||
|     :return: None | ||||
|     """ | ||||
|  | ||||
|     tickermock = MagicMock(return_value={'price_usd': 12345.0}) | ||||
|     listmock = MagicMock(return_value={'data': [{'id': 1, 'name': 'Bitcoin', 'symbol': 'BTC', | ||||
|                                                  'website_slug': 'bitcoin'}, | ||||
|                                                 {'id': 1027, 'name': 'Ethereum', 'symbol': 'ETH', | ||||
|                                                  'website_slug': 'ethereum'} | ||||
|                                                 ]}) | ||||
|     tickermock = MagicMock(return_value={'bitcoin': {'usd': 12345.0}, 'ethereum': {'usd': 12345.0}}) | ||||
|     listmock = MagicMock(return_value=[{'id': 'bitcoin', 'name': 'Bitcoin', 'symbol': 'btc', | ||||
|                                         'website_slug': 'bitcoin'}, | ||||
|                                        {'id': 'ethereum', 'name': 'Ethereum', 'symbol': 'eth', | ||||
|                                         'website_slug': 'ethereum'} | ||||
|                                        ]) | ||||
|     mocker.patch.multiple( | ||||
|         'freqtrade.rpc.fiat_convert.Market', | ||||
|         ticker=tickermock, | ||||
|         listings=listmock, | ||||
|         'freqtrade.rpc.fiat_convert.CoinGeckoAPI', | ||||
|         get_price=tickermock, | ||||
|         get_coins_list=listmock, | ||||
|  | ||||
|     ) | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,7 @@ import pytest | ||||
| from requests.exceptions import RequestException | ||||
|  | ||||
| from freqtrade.rpc.fiat_convert import CryptoFiat, CryptoToFiatConverter | ||||
| from tests.conftest import log_has | ||||
| from tests.conftest import log_has, log_has_re | ||||
|  | ||||
|  | ||||
| def test_pair_convertion_object(): | ||||
| @@ -22,8 +22,8 @@ def test_pair_convertion_object(): | ||||
|     assert pair_convertion.CACHE_DURATION == 6 * 60 * 60 | ||||
|  | ||||
|     # Check a regular usage | ||||
|     assert pair_convertion.crypto_symbol == 'BTC' | ||||
|     assert pair_convertion.fiat_symbol == 'USD' | ||||
|     assert pair_convertion.crypto_symbol == 'btc' | ||||
|     assert pair_convertion.fiat_symbol == 'usd' | ||||
|     assert pair_convertion.price == 12345.0 | ||||
|     assert pair_convertion.is_expired() is False | ||||
|  | ||||
| @@ -57,15 +57,15 @@ def test_fiat_convert_add_pair(mocker): | ||||
|     fiat_convert._add_pair(crypto_symbol='btc', fiat_symbol='usd', price=12345.0) | ||||
|     pair_len = len(fiat_convert._pairs) | ||||
|     assert pair_len == 1 | ||||
|     assert fiat_convert._pairs[0].crypto_symbol == 'BTC' | ||||
|     assert fiat_convert._pairs[0].fiat_symbol == 'USD' | ||||
|     assert fiat_convert._pairs[0].crypto_symbol == 'btc' | ||||
|     assert fiat_convert._pairs[0].fiat_symbol == 'usd' | ||||
|     assert fiat_convert._pairs[0].price == 12345.0 | ||||
|  | ||||
|     fiat_convert._add_pair(crypto_symbol='btc', fiat_symbol='Eur', price=13000.2) | ||||
|     pair_len = len(fiat_convert._pairs) | ||||
|     assert pair_len == 2 | ||||
|     assert fiat_convert._pairs[1].crypto_symbol == 'BTC' | ||||
|     assert fiat_convert._pairs[1].fiat_symbol == 'EUR' | ||||
|     assert fiat_convert._pairs[1].crypto_symbol == 'btc' | ||||
|     assert fiat_convert._pairs[1].fiat_symbol == 'eur' | ||||
|     assert fiat_convert._pairs[1].price == 13000.2 | ||||
|  | ||||
|  | ||||
| @@ -100,15 +100,15 @@ def test_fiat_convert_get_price(mocker): | ||||
|  | ||||
|     fiat_convert = CryptoToFiatConverter() | ||||
|  | ||||
|     with pytest.raises(ValueError, match=r'The fiat US DOLLAR is not supported.'): | ||||
|         fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='US Dollar') | ||||
|     with pytest.raises(ValueError, match=r'The fiat us dollar is not supported.'): | ||||
|         fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='US Dollar') | ||||
|  | ||||
|     # Check the value return by the method | ||||
|     pair_len = len(fiat_convert._pairs) | ||||
|     assert pair_len == 0 | ||||
|     assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 28000.0 | ||||
|     assert fiat_convert._pairs[0].crypto_symbol == 'BTC' | ||||
|     assert fiat_convert._pairs[0].fiat_symbol == 'USD' | ||||
|     assert fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='usd') == 28000.0 | ||||
|     assert fiat_convert._pairs[0].crypto_symbol == 'btc' | ||||
|     assert fiat_convert._pairs[0].fiat_symbol == 'usd' | ||||
|     assert fiat_convert._pairs[0].price == 28000.0 | ||||
|     assert fiat_convert._pairs[0]._expiration != 0 | ||||
|     assert len(fiat_convert._pairs) == 1 | ||||
| @@ -116,13 +116,13 @@ def test_fiat_convert_get_price(mocker): | ||||
|     # Verify the cached is used | ||||
|     fiat_convert._pairs[0].price = 9867.543 | ||||
|     expiration = fiat_convert._pairs[0]._expiration | ||||
|     assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 9867.543 | ||||
|     assert fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='usd') == 9867.543 | ||||
|     assert fiat_convert._pairs[0]._expiration == expiration | ||||
|  | ||||
|     # Verify the cache expiration | ||||
|     expiration = time.time() - 2 * 60 * 60 | ||||
|     fiat_convert._pairs[0]._expiration = expiration | ||||
|     assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 28000.0 | ||||
|     assert fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='usd') == 28000.0 | ||||
|     assert fiat_convert._pairs[0]._expiration is not expiration | ||||
|  | ||||
|  | ||||
| @@ -143,15 +143,15 @@ def test_loadcryptomap(mocker): | ||||
|     fiat_convert = CryptoToFiatConverter() | ||||
|     assert len(fiat_convert._cryptomap) == 2 | ||||
|  | ||||
|     assert fiat_convert._cryptomap["BTC"] == "1" | ||||
|     assert fiat_convert._cryptomap["btc"] == "bitcoin" | ||||
|  | ||||
|  | ||||
| def test_fiat_init_network_exception(mocker): | ||||
|     # Because CryptoToFiatConverter is a Singleton we reset the listings | ||||
|     listmock = MagicMock(side_effect=RequestException) | ||||
|     mocker.patch.multiple( | ||||
|         'freqtrade.rpc.fiat_convert.Market', | ||||
|         listings=listmock, | ||||
|         'freqtrade.rpc.fiat_convert.CoinGeckoAPI', | ||||
|         get_coins_list=listmock, | ||||
|     ) | ||||
|     # with pytest.raises(RequestEsxception): | ||||
|     fiat_convert = CryptoToFiatConverter() | ||||
| @@ -163,24 +163,24 @@ def test_fiat_init_network_exception(mocker): | ||||
|  | ||||
|  | ||||
| def test_fiat_convert_without_network(mocker): | ||||
|     # Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap | ||||
|     # Because CryptoToFiatConverter is a Singleton we reset the value of _coingekko | ||||
|  | ||||
|     fiat_convert = CryptoToFiatConverter() | ||||
|  | ||||
|     cmc_temp = CryptoToFiatConverter._coinmarketcap | ||||
|     CryptoToFiatConverter._coinmarketcap = None | ||||
|     cmc_temp = CryptoToFiatConverter._coingekko | ||||
|     CryptoToFiatConverter._coingekko = None | ||||
|  | ||||
|     assert fiat_convert._coinmarketcap is None | ||||
|     assert fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='USD') == 0.0 | ||||
|     CryptoToFiatConverter._coinmarketcap = cmc_temp | ||||
|     assert fiat_convert._coingekko is None | ||||
|     assert fiat_convert._find_price(crypto_symbol='btc', fiat_symbol='usd') == 0.0 | ||||
|     CryptoToFiatConverter._coingekko = cmc_temp | ||||
|  | ||||
|  | ||||
| def test_fiat_invalid_response(mocker, caplog): | ||||
|     # Because CryptoToFiatConverter is a Singleton we reset the listings | ||||
|     listmock = MagicMock(return_value="{'novalidjson':DEADBEEFf}") | ||||
|     mocker.patch.multiple( | ||||
|         'freqtrade.rpc.fiat_convert.Market', | ||||
|         listings=listmock, | ||||
|         'freqtrade.rpc.fiat_convert.CoinGeckoAPI', | ||||
|         get_coins_list=listmock, | ||||
|     ) | ||||
|     # with pytest.raises(RequestEsxception): | ||||
|     fiat_convert = CryptoToFiatConverter() | ||||
| @@ -189,8 +189,8 @@ def test_fiat_invalid_response(mocker, caplog): | ||||
|  | ||||
|     length_cryptomap = len(fiat_convert._cryptomap) | ||||
|     assert length_cryptomap == 0 | ||||
|     assert log_has('Could not load FIAT Cryptocurrency map for the following problem: TypeError', | ||||
|                    caplog) | ||||
|     assert log_has_re('Could not load FIAT Cryptocurrency map for the following problem: .*', | ||||
|                       caplog) | ||||
|  | ||||
|  | ||||
| def test_convert_amount(mocker): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user