diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 7fd0e5f43..4af9db6db 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -375,6 +375,8 @@ class Exchange(object): def get_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict: if refresh or pair not in self._cached_ticker.keys(): try: + if pair not in self._api.markets: + raise DependencyException(f"Pair {pair} not available") data = self._api.fetch_ticker(pair) try: self._cached_ticker[pair] = { diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index d653ea176..900ad1998 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -10,10 +10,10 @@ from typing import Dict, Any, List, Optional import arrow import sqlalchemy as sql -from numpy import mean, nan_to_num +from numpy import mean, nan_to_num, NAN from pandas import DataFrame -from freqtrade import TemporaryError +from freqtrade import TemporaryError, DependencyException from freqtrade.fiat_convert import CryptoToFiatConverter from freqtrade.misc import shorten_date from freqtrade.persistence import Trade @@ -93,7 +93,10 @@ class RPC(object): if trade.open_order_id: order = self._freqtrade.exchange.get_order(trade.open_order_id, trade.pair) # calculate profit and send message to user - current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid'] + try: + current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid'] + except DependencyException: + current_rate = NAN current_profit = trade.calc_profit_percent(current_rate) fmt_close_profit = (f'{round(trade.close_profit * 100, 2):.2f}%' if trade.close_profit else None) @@ -122,7 +125,10 @@ class RPC(object): trades_list = [] for trade in trades: # calculate profit and send message to user - current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid'] + try: + current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid'] + except DependencyException: + current_rate = NAN trade_perc = (100 * trade.calc_profit_percent(current_rate)) trades_list.append([ trade.id, @@ -207,7 +213,10 @@ class RPC(object): profit_closed_percent.append(profit_percent) else: # Get current rate - current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid'] + try: + current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid'] + except DependencyException: + current_rate = NAN profit_percent = trade.calc_profit_percent(rate=current_rate) profit_all_coin.append( @@ -275,7 +284,7 @@ class RPC(object): rate = 1.0 / self._freqtrade.exchange.get_ticker('BTC/USDT', False)['bid'] else: rate = self._freqtrade.exchange.get_ticker(coin + '/BTC', False)['bid'] - except TemporaryError: + except (TemporaryError, DependencyException): continue est_btc: float = rate * balance['total'] total = total + est_btc diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 93cd1e546..788ef4518 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -572,6 +572,7 @@ def test_get_ticker(default_conf, mocker): 'last': 0.0001, } api_mock.fetch_ticker = MagicMock(return_value=tick) + api_mock.markets = {'ETH/BTC': {}} exchange = get_patched_exchange(mocker, default_conf, api_mock) # retrieve original ticker ticker = exchange.get_ticker(pair='ETH/BTC') @@ -614,6 +615,9 @@ def test_get_ticker(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange.get_ticker(pair='ETH/BTC', refresh=True) + with pytest.raises(DependencyException, match=r'Pair XRP/ETH not available'): + exchange.get_ticker(pair='XRP/ETH', refresh=True) + def test_get_history(default_conf, mocker, caplog): exchange = get_patched_exchange(mocker, default_conf) diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index 88bf5e9ad..b181231c8 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -5,8 +5,9 @@ from datetime import datetime from unittest.mock import MagicMock, ANY import pytest +from numpy import isnan -from freqtrade import TemporaryError +from freqtrade import TemporaryError, DependencyException from freqtrade.fiat_convert import CryptoToFiatConverter from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade @@ -61,6 +62,27 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: 'open_order': '(limit buy rem=0.00000000)' } == results[0] + mocker.patch('freqtrade.exchange.Exchange.get_ticker', + MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available"))) + # invalidate ticker cache + rpc._freqtrade.exchange._cached_ticker = {} + results = rpc._rpc_trade_status() + assert isnan(results[0]['current_profit']) + assert isnan(results[0]['current_rate']) + assert { + 'trade_id': 1, + 'pair': 'ETH/BTC', + 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', + 'date': ANY, + 'open_rate': 1.099e-05, + 'close_rate': None, + 'current_rate': ANY, + 'amount': 90.99181074, + 'close_profit': None, + 'current_profit': ANY, + 'open_order': '(limit buy rem=0.00000000)' + } == results[0] + def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None: patch_coinmarketcap(mocker) @@ -87,6 +109,15 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None: assert 'ETH/BTC' in result['Pair'].all() assert '-0.59%' in result['Profit'].all() + mocker.patch('freqtrade.exchange.Exchange.get_ticker', + MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available"))) + # invalidate ticker cache + rpc._freqtrade.exchange._cached_ticker = {} + result = rpc._rpc_status_table() + assert 'just now' in result['Since'].all() + assert 'ETH/BTC' in result['Pair'].all() + assert 'nan%' in result['Profit'].all() + def test_rpc_daily_profit(default_conf, update, ticker, fee, limit_buy_order, limit_sell_order, markets, mocker) -> None: @@ -208,6 +239,20 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, assert stats['best_pair'] == 'ETH/BTC' assert prec_satoshi(stats['best_rate'], 6.2) + # Test non-available pair + mocker.patch('freqtrade.exchange.Exchange.get_ticker', + MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available"))) + # invalidate ticker cache + rpc._freqtrade.exchange._cached_ticker = {} + stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) + assert stats['trade_count'] == 2 + assert stats['first_trade_date'] == 'just now' + assert stats['latest_trade_date'] == 'just now' + assert stats['avg_duration'] == '0:00:00' + assert stats['best_pair'] == 'ETH/BTC' + assert prec_satoshi(stats['best_rate'], 6.2) + assert isnan(stats['profit_all_coin']) + # Test that rpc_trade_statistics can handle trades that lacks # trade.open_rate (it is set to None)