From fcdbe846e51a9c76f86c97b84dec28680d857a71 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 3 Jul 2019 20:06:50 +0200 Subject: [PATCH 1/4] Fix #1981 - Detect reverted currency pairs --- freqtrade/exchange/exchange.py | 11 ++++++++++- freqtrade/rpc/rpc.py | 7 ++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index c3171b961..536a707cd 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -270,6 +270,15 @@ class Exchange(object): f'Pair {pair} is not available on {self.name}. ' f'Please remove {pair} from your whitelist.') + def get_valid_pair_combination(self, paira, pairb) -> str: + """ + Get valid combination of paira and pairb by trying both combinations. + """ + for pair in [f"{paira}/{pairb}", f"{pairb}/{paira}"]: + if pair in self._api.markets and self._api.markets[pair].get('active'): + return pair + raise DependencyException(f"Could not combine {paira} and {pairb} to get a valid pair.") + def validate_timeframes(self, timeframe: List[str]) -> None: """ Checks if ticker interval from config is a supported timeframe on the exchange @@ -501,7 +510,7 @@ 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: + if pair not in self._api.markets or not self._api.markets[pair].get('active'): raise DependencyException(f"Pair {pair} not available") data = self._api.fetch_ticker(pair) try: diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index f5adffc65..f77e0eddb 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -281,10 +281,11 @@ class RPC(object): rate = 1.0 else: try: - if coin in('USDT', 'USD', 'EUR'): - rate = 1.0 / self._freqtrade.get_sell_rate('BTC/' + coin, False) + pair = self._freqtrade.exchange.get_valid_pair_combination(coin, "BTC") + if pair.startswith("BTC"): + rate = 1.0 / self._freqtrade.get_sell_rate(pair, False) else: - rate = self._freqtrade.get_sell_rate(coin + '/BTC', False) + rate = self._freqtrade.get_sell_rate(pair, False) except (TemporaryError, DependencyException): logger.warning(f" Could not get rate for pair {coin}.") continue From 1bcf2737fe871701516d15ba120037ba51f93b1b Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 3 Jul 2019 20:07:26 +0200 Subject: [PATCH 2/4] Add tests for new behaviour --- freqtrade/tests/exchange/test_exchange.py | 2 +- freqtrade/tests/rpc/test_rpc.py | 68 ++++++++++++++++++++++- freqtrade/tests/rpc/test_rpc_apiserver.py | 2 + freqtrade/tests/rpc/test_rpc_telegram.py | 2 + 4 files changed, 72 insertions(+), 2 deletions(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index b74882ad4..804cf1af5 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -910,7 +910,7 @@ def test_get_ticker(default_conf, mocker, exchange_name): 'last': 0.0001, } api_mock.fetch_ticker = MagicMock(return_value=tick) - api_mock.markets = {'ETH/BTC': {}} + api_mock.markets = {'ETH/BTC': {'active': True}} exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) # retrieve original ticker ticker = exchange.get_ticker(pair='ETH/BTC') diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index 5a4b5d1b2..d273244b0 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -324,7 +324,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, markets, assert prec_satoshi(stats['best_rate'], 6.2) -def test_rpc_balance_handle(default_conf, mocker): +def test_rpc_balance_handle_error(default_conf, mocker): mock_balance = { 'BTC': { 'free': 10.0, @@ -371,6 +371,72 @@ def test_rpc_balance_handle(default_conf, mocker): assert result['total'] == 12.0 +def test_rpc_balance_handle(default_conf, mocker): + mock_balance = { + 'BTC': { + 'free': 10.0, + 'total': 12.0, + 'used': 2.0, + }, + 'ETH': { + 'free': 1.0, + 'total': 5.0, + 'used': 4.0, + }, + 'PAX': { + 'free': 5.0, + 'total': 10.0, + 'used': 5.0, + } + } + + mocker.patch.multiple( + 'freqtrade.rpc.fiat_convert.Market', + ticker=MagicMock(return_value={'price_usd': 15000.0}), + ) + patch_exchange(mocker) + mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_balances=MagicMock(return_value=mock_balance), + get_ticker=MagicMock( + side_effect=lambda p, r: {'bid': 100} if p == "BTC/PAX" else {'bid': 0.01}), + get_valid_pair_combination=MagicMock( + side_effect=lambda a, b: f"{b}/{a}" if a == "PAX" else f"{a}/{b}") + ) + + freqtradebot = FreqtradeBot(default_conf) + patch_get_signal(freqtradebot, (True, False)) + rpc = RPC(freqtradebot) + rpc._fiat_converter = CryptoToFiatConverter() + + result = rpc._rpc_balance(default_conf['fiat_display_currency']) + assert prec_satoshi(result['total'], 12.15) + assert prec_satoshi(result['value'], 182250) + assert 'USD' == result['symbol'] + assert result['currencies'] == [ + {'currency': 'BTC', + 'available': 10.0, + 'balance': 12.0, + 'pending': 2.0, + 'est_btc': 12.0, + }, + {'available': 1.0, + 'balance': 5.0, + 'currency': 'ETH', + 'est_btc': 0.05, + 'pending': 4.0 + }, + {'available': 5.0, + 'balance': 10.0, + 'currency': 'PAX', + 'est_btc': 0.1, + 'pending': 5.0} + ] + assert result['total'] == 12.15 + + def test_rpc_start(mocker, default_conf) -> None: patch_exchange(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) diff --git a/freqtrade/tests/rpc/test_rpc_apiserver.py b/freqtrade/tests/rpc/test_rpc_apiserver.py index b7721fd8e..bd420ada6 100644 --- a/freqtrade/tests/rpc/test_rpc_apiserver.py +++ b/freqtrade/tests/rpc/test_rpc_apiserver.py @@ -244,6 +244,8 @@ def test_api_balance(botclient, mocker, rpc_balance): } mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance) mocker.patch('freqtrade.exchange.Exchange.get_ticker', side_effect=mock_ticker) + mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination', + side_effect=lambda a, b: f"{a}/{b}") rc = client_get(client, f"{BASE_URI}/balance") assert_response(rc) diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 673536993..1bee5bff3 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -518,6 +518,8 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance) -> N mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance) mocker.patch('freqtrade.exchange.Exchange.get_ticker', side_effect=mock_ticker) + mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination', + side_effect=lambda a, b: f"{a}/{b}") msg_mock = MagicMock() mocker.patch.multiple( From 40fe2d2c164bddd3ce6f17f84a2d636498636ff4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 3 Jul 2019 20:20:12 +0200 Subject: [PATCH 3/4] Test get_valid_pair_combination --- freqtrade/exchange/exchange.py | 2 +- freqtrade/tests/exchange/test_exchange.py | 24 +++++++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 536a707cd..a41b51e90 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -275,7 +275,7 @@ class Exchange(object): Get valid combination of paira and pairb by trying both combinations. """ for pair in [f"{paira}/{pairb}", f"{pairb}/{paira}"]: - if pair in self._api.markets and self._api.markets[pair].get('active'): + if pair in self.markets and self.markets[pair].get('active'): return pair raise DependencyException(f"Could not combine {paira} and {pairb} to get a valid pair.") diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 804cf1af5..4b264db08 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -1455,10 +1455,11 @@ def test_stoploss_limit_order_dry_run(default_conf, mocker): def test_merge_ft_has_dict(default_conf, mocker): - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=MagicMock())) - mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) + mocker.patch.multiple('freqtrade.exchange.Exchange', + _init_ccxt=MagicMock(return_value=MagicMock()), + _load_async_markets=MagicMock(), + validate_pairs=MagicMock(), + validate_timeframes=MagicMock()) ex = Exchange(default_conf) assert ex._ft_has == Exchange._ft_has_default @@ -1479,3 +1480,18 @@ def test_merge_ft_has_dict(default_conf, mocker): assert ex._ft_has != Exchange._ft_has_default assert not ex._ft_has['stoploss_on_exchange'] assert ex._ft_has['DeadBeef'] == 20 + + +def test_get_valid_pair_combination(default_conf, mocker, markets): + mocker.patch.multiple('freqtrade.exchange.Exchange', + _init_ccxt=MagicMock(return_value=MagicMock()), + _load_async_markets=MagicMock(), + validate_pairs=MagicMock(), + validate_timeframes=MagicMock(), + markets=PropertyMock(return_value=markets)) + ex = Exchange(default_conf) + + assert ex.get_valid_pair_combination("ETH", "BTC") == "ETH/BTC" + assert ex.get_valid_pair_combination("BTC", "ETH") == "ETH/BTC" + with pytest.raises(DependencyException, match=r"Could not combine.* to get a valid pair."): + ex.get_valid_pair_combination("NOPAIR", "ETH") From 6c2415d32fba4dfb784f3e0c18652b9eb304fa80 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 7 Jul 2019 06:36:35 +0200 Subject: [PATCH 4/4] Rename parameters from pair to curr --- freqtrade/exchange/exchange.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index a41b51e90..8aacc0cee 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -270,14 +270,14 @@ class Exchange(object): f'Pair {pair} is not available on {self.name}. ' f'Please remove {pair} from your whitelist.') - def get_valid_pair_combination(self, paira, pairb) -> str: + def get_valid_pair_combination(self, curr_1, curr_2) -> str: """ - Get valid combination of paira and pairb by trying both combinations. + Get valid pair combination of curr_1 and curr_2 by trying both combinations. """ - for pair in [f"{paira}/{pairb}", f"{pairb}/{paira}"]: + for pair in [f"{curr_1}/{curr_2}", f"{curr_2}/{curr_1}"]: if pair in self.markets and self.markets[pair].get('active'): return pair - raise DependencyException(f"Could not combine {paira} and {pairb} to get a valid pair.") + raise DependencyException(f"Could not combine {curr_1} and {curr_2} to get a valid pair.") def validate_timeframes(self, timeframe: List[str]) -> None: """