Merge pull request #3030 from freqtrade/coingekko

Coingekko replacing coinmarketcap
This commit is contained in:
hroff-1902 2020-03-07 20:42:08 +03:00 committed by GitHub
commit 77944175e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 81 additions and 81 deletions

View File

@ -45,7 +45,7 @@ dependencies:
- pip: - pip:
# Required for app # Required for app
- cython - cython
- coinmarketcap - pycoingecko
- ccxt - ccxt
- TA-Lib - TA-Lib
- py_find_1st - py_find_1st

View File

@ -43,7 +43,7 @@ SUPPORTED_FIAT = [
"EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY", "EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY",
"KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN", "KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN",
"RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD", "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD",
"BTC", "XBT", "ETH", "XRP", "LTC", "BCH", "USDT" "BTC", "ETH", "XRP", "LTC", "BCH"
] ]
MINIMAL_CONFIG = { MINIMAL_CONFIG = {

View File

@ -7,7 +7,7 @@ import logging
import time import time
from typing import Dict, List from typing import Dict, List
from coinmarketcap import Market from pycoingecko import CoinGeckoAPI
from freqtrade.constants import SUPPORTED_FIAT from freqtrade.constants import SUPPORTED_FIAT
@ -38,8 +38,8 @@ class CryptoFiat:
# Private attributes # Private attributes
self._expiration = 0.0 self._expiration = 0.0
self.crypto_symbol = crypto_symbol.upper() self.crypto_symbol = crypto_symbol.lower()
self.fiat_symbol = fiat_symbol.upper() self.fiat_symbol = fiat_symbol.lower()
self.set_price(price=price) self.set_price(price=price)
def set_price(self, price: float) -> None: def set_price(self, price: float) -> None:
@ -67,17 +67,20 @@ class CryptoToFiatConverter:
This object is also a Singleton This object is also a Singleton
""" """
__instance = None __instance = None
_coinmarketcap: Market = None _coingekko: CoinGeckoAPI = None
_cryptomap: Dict = {} _cryptomap: Dict = {}
def __new__(cls): def __new__(cls):
"""
This class is a singleton - cannot be instantiated twice.
"""
if CryptoToFiatConverter.__instance is None: if CryptoToFiatConverter.__instance is None:
CryptoToFiatConverter.__instance = object.__new__(cls) CryptoToFiatConverter.__instance = object.__new__(cls)
try: try:
CryptoToFiatConverter._coinmarketcap = Market() CryptoToFiatConverter._coingekko = CoinGeckoAPI()
except BaseException: except BaseException:
CryptoToFiatConverter._coinmarketcap = None CryptoToFiatConverter._coingekko = None
return CryptoToFiatConverter.__instance return CryptoToFiatConverter.__instance
def __init__(self) -> None: def __init__(self) -> None:
@ -86,14 +89,12 @@ class CryptoToFiatConverter:
def _load_cryptomap(self) -> None: def _load_cryptomap(self) -> None:
try: try:
coinlistings = self._coinmarketcap.listings() coinlistings = self._coingekko.get_coins_list()
self._cryptomap = dict(map(lambda coin: (coin["symbol"], str(coin["id"])), # Create mapping table from synbol to coingekko_id
coinlistings["data"])) self._cryptomap = {x['symbol']: x['id'] for x in coinlistings}
except (BaseException) as exception: except (Exception) as exception:
logger.error( logger.error(
"Could not load FIAT Cryptocurrency map for the following problem: %s", f"Could not load FIAT Cryptocurrency map for the following problem: {exception}")
type(exception).__name__
)
def convert_amount(self, crypto_amount: float, crypto_symbol: str, fiat_symbol: str) -> float: 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) :param fiat_symbol: FIAT currency you want to convert to (e.g USD)
:return: Price in FIAT :return: Price in FIAT
""" """
crypto_symbol = crypto_symbol.upper() crypto_symbol = crypto_symbol.lower()
fiat_symbol = fiat_symbol.upper() fiat_symbol = fiat_symbol.lower()
# Check if the fiat convertion you want is supported # Check if the fiat convertion you want is supported
if not self._is_supported_fiat(fiat=fiat_symbol): if not self._is_supported_fiat(fiat=fiat_symbol):
@ -170,15 +171,13 @@ class CryptoToFiatConverter:
:return: bool, True supported, False not supported :return: bool, True supported, False not supported
""" """
fiat = fiat.upper() return fiat.upper() in SUPPORTED_FIAT
return fiat in SUPPORTED_FIAT
def _find_price(self, crypto_symbol: str, fiat_symbol: str) -> float: def _find_price(self, crypto_symbol: str, fiat_symbol: str) -> float:
""" """
Call CoinMarketCap API to retrieve the price in the FIAT Call CoinGekko API to retrieve the price in the FIAT
:param crypto_symbol: Crypto-currency you want to convert (e.g BTC) :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 fiat_symbol: FIAT currency you want to convert to (e.g usd)
:return: float, price of the crypto-currency in Fiat :return: float, price of the crypto-currency in Fiat
""" """
# Check if the fiat convertion you want is supported # Check if the fiat convertion you want is supported
@ -195,12 +194,13 @@ class CryptoToFiatConverter:
return 0.0 return 0.0
try: try:
_gekko_id = self._cryptomap[crypto_symbol]
return float( return float(
self._coinmarketcap.ticker( self._coingekko.get_price(
currency=self._cryptomap[crypto_symbol], ids=_gekko_id,
convert=fiat_symbol vs_currencies=fiat_symbol
)['data']['quotes'][fiat_symbol.upper()]['price'] )[_gekko_id][fiat_symbol]
) )
except BaseException as exception: except Exception as exception:
logger.error("Error in _find_price: %s", exception) logger.error("Error in _find_price: %s", exception)
return 0.0 return 0.0

View File

@ -11,7 +11,7 @@ wrapt==1.12.0
jsonschema==3.2.0 jsonschema==3.2.0
TA-Lib==0.4.17 TA-Lib==0.4.17
tabulate==0.8.6 tabulate==0.8.6
coinmarketcap==5.0.3 pycoingecko==1.2.0
jinja2==2.11.1 jinja2==2.11.1
# find first, C search in arrays # find first, C search in arrays

View File

@ -73,7 +73,7 @@ setup(name='freqtrade',
'jsonschema', 'jsonschema',
'TA-Lib', 'TA-Lib',
'tabulate', 'tabulate',
'coinmarketcap', 'pycoingecko',
'py_find_1st', 'py_find_1st',
'python-rapidjson', 'python-rapidjson',
'sdnotify', 'sdnotify',

View File

@ -167,23 +167,23 @@ def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False)) -> None:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def patch_coinmarketcap(mocker) -> None: def patch_coingekko(mocker) -> None:
""" """
Mocker to coinmarketcap to speed up tests Mocker to coingekko to speed up tests
:param mocker: mocker to patch coinmarketcap class :param mocker: mocker to patch coingekko class
:return: None :return: None
""" """
tickermock = MagicMock(return_value={'price_usd': 12345.0}) tickermock = MagicMock(return_value={'bitcoin': {'usd': 12345.0}, 'ethereum': {'usd': 12345.0}})
listmock = MagicMock(return_value={'data': [{'id': 1, 'name': 'Bitcoin', 'symbol': 'BTC', listmock = MagicMock(return_value=[{'id': 'bitcoin', 'name': 'Bitcoin', 'symbol': 'btc',
'website_slug': 'bitcoin'}, 'website_slug': 'bitcoin'},
{'id': 1027, 'name': 'Ethereum', 'symbol': 'ETH', {'id': 'ethereum', 'name': 'Ethereum', 'symbol': 'eth',
'website_slug': 'ethereum'} 'website_slug': 'ethereum'}
]}) ])
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.rpc.fiat_convert.Market', 'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
ticker=tickermock, get_price=tickermock,
listings=listmock, get_coins_list=listmock,
) )

View File

@ -8,7 +8,7 @@ import pytest
from requests.exceptions import RequestException from requests.exceptions import RequestException
from freqtrade.rpc.fiat_convert import CryptoFiat, CryptoToFiatConverter 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(): def test_pair_convertion_object():
@ -22,8 +22,8 @@ def test_pair_convertion_object():
assert pair_convertion.CACHE_DURATION == 6 * 60 * 60 assert pair_convertion.CACHE_DURATION == 6 * 60 * 60
# Check a regular usage # Check a regular usage
assert pair_convertion.crypto_symbol == 'BTC' assert pair_convertion.crypto_symbol == 'btc'
assert pair_convertion.fiat_symbol == 'USD' assert pair_convertion.fiat_symbol == 'usd'
assert pair_convertion.price == 12345.0 assert pair_convertion.price == 12345.0
assert pair_convertion.is_expired() is False 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) fiat_convert._add_pair(crypto_symbol='btc', fiat_symbol='usd', price=12345.0)
pair_len = len(fiat_convert._pairs) pair_len = len(fiat_convert._pairs)
assert pair_len == 1 assert pair_len == 1
assert fiat_convert._pairs[0].crypto_symbol == 'BTC' assert fiat_convert._pairs[0].crypto_symbol == 'btc'
assert fiat_convert._pairs[0].fiat_symbol == 'USD' assert fiat_convert._pairs[0].fiat_symbol == 'usd'
assert fiat_convert._pairs[0].price == 12345.0 assert fiat_convert._pairs[0].price == 12345.0
fiat_convert._add_pair(crypto_symbol='btc', fiat_symbol='Eur', price=13000.2) fiat_convert._add_pair(crypto_symbol='btc', fiat_symbol='Eur', price=13000.2)
pair_len = len(fiat_convert._pairs) pair_len = len(fiat_convert._pairs)
assert pair_len == 2 assert pair_len == 2
assert fiat_convert._pairs[1].crypto_symbol == 'BTC' assert fiat_convert._pairs[1].crypto_symbol == 'btc'
assert fiat_convert._pairs[1].fiat_symbol == 'EUR' assert fiat_convert._pairs[1].fiat_symbol == 'eur'
assert fiat_convert._pairs[1].price == 13000.2 assert fiat_convert._pairs[1].price == 13000.2
@ -100,15 +100,15 @@ def test_fiat_convert_get_price(mocker):
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter()
with pytest.raises(ValueError, match=r'The fiat US DOLLAR is not supported.'): with pytest.raises(ValueError, match=r'The fiat us dollar is not supported.'):
fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='US Dollar') fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='US Dollar')
# Check the value return by the method # Check the value return by the method
pair_len = len(fiat_convert._pairs) pair_len = len(fiat_convert._pairs)
assert pair_len == 0 assert pair_len == 0
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].crypto_symbol == 'BTC' assert fiat_convert._pairs[0].crypto_symbol == 'btc'
assert fiat_convert._pairs[0].fiat_symbol == 'USD' assert fiat_convert._pairs[0].fiat_symbol == 'usd'
assert fiat_convert._pairs[0].price == 28000.0 assert fiat_convert._pairs[0].price == 28000.0
assert fiat_convert._pairs[0]._expiration != 0 assert fiat_convert._pairs[0]._expiration != 0
assert len(fiat_convert._pairs) == 1 assert len(fiat_convert._pairs) == 1
@ -116,13 +116,13 @@ def test_fiat_convert_get_price(mocker):
# Verify the cached is used # Verify the cached is used
fiat_convert._pairs[0].price = 9867.543 fiat_convert._pairs[0].price = 9867.543
expiration = fiat_convert._pairs[0]._expiration 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 assert fiat_convert._pairs[0]._expiration == expiration
# Verify the cache expiration # Verify the cache expiration
expiration = time.time() - 2 * 60 * 60 expiration = time.time() - 2 * 60 * 60
fiat_convert._pairs[0]._expiration = expiration 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 assert fiat_convert._pairs[0]._expiration is not expiration
@ -143,15 +143,15 @@ def test_loadcryptomap(mocker):
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter()
assert len(fiat_convert._cryptomap) == 2 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): def test_fiat_init_network_exception(mocker):
# Because CryptoToFiatConverter is a Singleton we reset the listings # Because CryptoToFiatConverter is a Singleton we reset the listings
listmock = MagicMock(side_effect=RequestException) listmock = MagicMock(side_effect=RequestException)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.rpc.fiat_convert.Market', 'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
listings=listmock, get_coins_list=listmock,
) )
# with pytest.raises(RequestEsxception): # with pytest.raises(RequestEsxception):
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter()
@ -163,24 +163,24 @@ def test_fiat_init_network_exception(mocker):
def test_fiat_convert_without_network(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() fiat_convert = CryptoToFiatConverter()
cmc_temp = CryptoToFiatConverter._coinmarketcap cmc_temp = CryptoToFiatConverter._coingekko
CryptoToFiatConverter._coinmarketcap = None CryptoToFiatConverter._coingekko = None
assert fiat_convert._coinmarketcap is None assert fiat_convert._coingekko is None
assert fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='USD') == 0.0 assert fiat_convert._find_price(crypto_symbol='btc', fiat_symbol='usd') == 0.0
CryptoToFiatConverter._coinmarketcap = cmc_temp CryptoToFiatConverter._coingekko = cmc_temp
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}")
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.rpc.fiat_convert.Market', 'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
listings=listmock, get_coins_list=listmock,
) )
# with pytest.raises(RequestEsxception): # with pytest.raises(RequestEsxception):
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter()
@ -189,8 +189,8 @@ def test_fiat_invalid_response(mocker, caplog):
length_cryptomap = len(fiat_convert._cryptomap) length_cryptomap = len(fiat_convert._cryptomap)
assert length_cryptomap == 0 assert length_cryptomap == 0
assert log_has('Could not load FIAT Cryptocurrency map for the following problem: TypeError', assert log_has_re('Could not load FIAT Cryptocurrency map for the following problem: .*',
caplog) caplog)
def test_convert_amount(mocker): def test_convert_amount(mocker):

View File

@ -95,8 +95,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.rpc.fiat_convert.Market', 'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
ticker=MagicMock(return_value={'price_usd': 15000.0}), get_price=MagicMock(return_value={'bitcoin': {'usd': 15000.0}}),
) )
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
@ -178,7 +178,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
day[1] == '0.00006217 BTC') day[1] == '0.00006217 BTC')
assert (day[2] == '0.000 USD' or assert (day[2] == '0.000 USD' or
day[2] == '0.933 USD') day[2] == '0.767 USD')
# ensure first day is current date # ensure first day is current date
assert str(days[0][0]) == str(datetime.utcnow().date()) assert str(days[0][0]) == str(datetime.utcnow().date())
@ -190,8 +190,8 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
limit_buy_order, limit_sell_order, mocker) -> None: limit_buy_order, limit_sell_order, mocker) -> None:
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.rpc.fiat_convert.Market', 'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
ticker=MagicMock(return_value={'price_usd': 15000.0}), get_price=MagicMock(return_value={'bitcoin': {'usd': 15000.0}}),
) )
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
@ -273,8 +273,8 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee,
ticker_sell_up, limit_buy_order, limit_sell_order): ticker_sell_up, limit_buy_order, limit_sell_order):
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.rpc.fiat_convert.Market', 'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
ticker=MagicMock(return_value={'price_usd': 15000.0}), get_price=MagicMock(return_value={'bitcoin': {'usd': 15000.0}}),
) )
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
return_value=15000.0) return_value=15000.0)
@ -341,8 +341,8 @@ def test_rpc_balance_handle_error(default_conf, mocker):
# ETH will be skipped due to mocked Error below # ETH will be skipped due to mocked Error below
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.rpc.fiat_convert.Market', 'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
ticker=MagicMock(return_value={'price_usd': 15000.0}), get_price=MagicMock(return_value={'bitcoin': {'usd': 15000.0}}),
) )
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
@ -380,8 +380,8 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
} }
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.rpc.fiat_convert.Market', 'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
ticker=MagicMock(return_value={'price_usd': 15000.0}), get_price=MagicMock(return_value={'bitcoin': {'usd': 15000.0}}),
) )
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())

View File

@ -1210,7 +1210,7 @@ def test_send_msg_buy_notification(default_conf, mocker) -> None:
'*Amount:* `1333.33333333`\n' \ '*Amount:* `1333.33333333`\n' \
'*Open Rate:* `0.00001099`\n' \ '*Open Rate:* `0.00001099`\n' \
'*Current Rate:* `0.00001099`\n' \ '*Current Rate:* `0.00001099`\n' \
'*Total:* `(0.001000 BTC, 0.000 USD)`' '*Total:* `(0.001000 BTC, 12.345 USD)`'
def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None: def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None: