From 6581ba56cab28f025c7444bd57e2e09beefb7ded Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 24 Feb 2020 20:41:45 +0100 Subject: [PATCH 01/10] Use markets.quote to validate --- freqtrade/pairlist/IPairList.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 1ad4da523..7d489ece7 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -99,7 +99,8 @@ class IPairList(ABC): logger.warning(f"Pair {pair} is not compatible with exchange " f"{self._exchange.name}. Removing it from whitelist..") continue - if not pair.endswith(self._config['stake_currency']): + + if markets[pair]['quote'] != self._config['stake_currency']: logger.warning(f"Pair {pair} is not compatible with your stake currency " f"{self._config['stake_currency']}. Removing it from whitelist..") continue From 3e4f663418236f84123eeb186fa0f642f84de89e Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 24 Feb 2020 21:22:58 +0100 Subject: [PATCH 02/10] Move pairlist validation to exchange (we need to use .quote) from markets --- freqtrade/configuration/config_validation.py | 12 ------ freqtrade/exchange/exchange.py | 8 +++- tests/exchange/test_exchange.py | 42 ++++++++++++++++++-- tests/test_configuration.py | 7 ---- 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index 5183ad0b4..5ba7ff294 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -150,15 +150,3 @@ def _validate_whitelist(conf: Dict[str, Any]) -> None: if (pl.get('method') == 'StaticPairList' and not conf.get('exchange', {}).get('pair_whitelist')): raise OperationalException("StaticPairList requires pair_whitelist to be set.") - - if pl.get('method') == 'StaticPairList': - stake = conf['stake_currency'] - invalid_pairs = [] - for pair in conf['exchange'].get('pair_whitelist'): - if not pair.endswith(f'/{stake}'): - invalid_pairs.append(pair) - - if invalid_pairs: - raise OperationalException( - f"Stake-currency '{stake}' not compatible with pair-whitelist. " - f"Please remove the following pairs: {invalid_pairs}") diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index b3b347016..8023417a3 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -300,7 +300,7 @@ class Exchange: if not self.markets: logger.warning('Unable to validate pairs (assuming they are correct).') return - + invalid_pairs = [] for pair in pairs: # Note: ccxt has BaseCurrency/QuoteCurrency format for pairs # TODO: add a support for having coins in BTC/USDT format @@ -322,6 +322,12 @@ class Exchange: logger.warning(f"Pair {pair} is restricted for some users on this exchange." f"Please check if you are impacted by this restriction " f"on the exchange and eventually remove {pair} from your whitelist.") + if not self.markets[pair].get('quote') == self._config['stake_currency']: + invalid_pairs.append(pair) + if invalid_pairs: + raise OperationalException( + f"Stake-currency '{self._config['stake_currency']}' not compatible with " + f"pair-whitelist. Please remove the following pairs: {invalid_pairs}") def get_valid_pair_combination(self, curr_1: str, curr_2: str) -> str: """ diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 8b2e439c3..d1c105591 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -406,7 +406,10 @@ def test_get_quote_currencies(default_conf, mocker): def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly api_mock = MagicMock() type(api_mock).markets = PropertyMock(return_value={ - 'ETH/BTC': {}, 'LTC/BTC': {}, 'XRP/BTC': {}, 'NEO/BTC': {} + 'ETH/BTC': {'quote': 'BTC'}, + 'LTC/BTC': {'quote': 'BTC'}, + 'XRP/BTC': {'quote': 'BTC'}, + 'NEO/BTC': {'quote': 'BTC'}, }) id_mock = PropertyMock(return_value='test_exchange') type(api_mock).id = id_mock @@ -454,9 +457,9 @@ def test_validate_pairs_exception(default_conf, mocker, caplog): def test_validate_pairs_restricted(default_conf, mocker, caplog): api_mock = MagicMock() type(api_mock).markets = PropertyMock(return_value={ - 'ETH/BTC': {}, 'LTC/BTC': {}, - 'XRP/BTC': {'info': {'IsRestricted': True}}, - 'NEO/BTC': {'info': 'TestString'}, # info can also be a string ... + 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, + 'XRP/BTC': {'quote': 'BTC', 'info': {'IsRestricted': True}}, + 'NEO/BTC': {'quote': 'BTC', 'info': 'TestString'}, # info can also be a string ... }) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') @@ -469,6 +472,37 @@ def test_validate_pairs_restricted(default_conf, mocker, caplog): f"on the exchange and eventually remove XRP/BTC from your whitelist.", caplog) +def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog): + api_mock = MagicMock() + type(api_mock).markets = PropertyMock(return_value={ + 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, + 'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'}, + 'HELLO-WORLD': {'quote': 'BTC'}, + }) + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') + mocker.patch('freqtrade.exchange.Exchange._load_async_markets') + mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + + Exchange(default_conf) + + +def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog): + default_conf['exchange']['pair_whitelist'].append('HELLO-WORLD') + api_mock = MagicMock() + type(api_mock).markets = PropertyMock(return_value={ + 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, + 'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'}, + 'HELLO-WORLD': {'quote': 'USDT'}, + }) + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') + mocker.patch('freqtrade.exchange.Exchange._load_async_markets') + mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + + with pytest.raises(OperationalException, match=r"Stake-currency 'BTC' not compatible with.*"): + Exchange(default_conf) + @pytest.mark.parametrize("timeframe", [ ('5m'), ("1m"), ("15m"), ("1h") ]) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index d810305db..828db4d83 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -810,13 +810,6 @@ def test_validate_whitelist(default_conf): validate_config_consistency(conf) - conf = deepcopy(default_conf) - conf['stake_currency'] = 'USDT' - with pytest.raises(OperationalException, - match=r"Stake-currency 'USDT' not compatible with pair-whitelist.*"): - validate_config_consistency(conf) - - def test_load_config_test_comments() -> None: """ Load config with comments From 61037ab7b8f3f2467555c1a2a767580ea25f43a6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 24 Feb 2020 21:50:27 +0100 Subject: [PATCH 03/10] Implement get_pair_base_curr and get_pair_quote_curr --- freqtrade/exchange/exchange.py | 12 ++++++++++++ freqtrade/freqtradebot.py | 6 ++++-- tests/commands/test_commands.py | 31 ++++++++++++++++--------------- tests/conftest.py | 29 ++++++++++++++++++++++++++++- tests/exchange/test_exchange.py | 15 ++++++++------- tests/test_freqtradebot.py | 2 ++ 6 files changed, 70 insertions(+), 25 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 8023417a3..0e0d2dabe 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -228,6 +228,18 @@ class Exchange: markets = self.markets return sorted(set([x['quote'] for _, x in markets.items()])) + def get_pair_quote_currency(self, pair: str) -> str: + """ + Return a pair's quote currency + """ + return self.markets[pair].get('quote') + + def get_pair_base_currency(self, pair: str) -> str: + """ + Return a pair's quote currency + """ + return self.markets[pair].get('base') + def klines(self, pair_interval: Tuple[str, str], copy: bool = True) -> DataFrame: if pair_interval in self._klines: return self._klines[pair_interval].copy() if copy else self._klines[pair_interval] diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 00d5c369a..38583b5ad 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1125,12 +1125,13 @@ class FreqtradeBot: if trade.fee_open == 0 or order['status'] == 'open': return order_amount + trade_base_currency = self.exchange.get_pair_base_currency(trade.pair) # use fee from order-dict if possible if ('fee' in order and order['fee'] is not None and (order['fee'].keys() >= {'currency', 'cost'})): if (order['fee']['currency'] is not None and order['fee']['cost'] is not None and - trade.pair.startswith(order['fee']['currency'])): + trade_base_currency == order['fee']['currency']): new_amount = order_amount - order['fee']['cost'] logger.info("Applying fee on amount for %s (from %s to %s) from Order", trade, order['amount'], new_amount) @@ -1145,6 +1146,7 @@ class FreqtradeBot: return order_amount amount = 0 fee_abs = 0 + trade_base_currency = self.exchange.get_pair_base_currency(trade.pair) for exectrade in trades: amount += exectrade['amount'] if ("fee" in exectrade and exectrade['fee'] is not None and @@ -1152,7 +1154,7 @@ class FreqtradeBot: # only applies if fee is in quote currency! if (exectrade['fee']['currency'] is not None and exectrade['fee']['cost'] is not None and - trade.pair.startswith(exectrade['fee']['currency'])): + trade_base_currency == exectrade['fee']['currency']): fee_abs += exectrade['fee']['cost'] if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC): diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index a9fe0f637..fd8df4b56 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -217,8 +217,9 @@ def test_list_markets(mocker, markets, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 9 active markets: " - "BLK/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/USD, NEO/BTC, TKN/BTC, XLTCUSDT, XRP/BTC.\n" + assert ("Exchange Bittrex has 10 active markets: " + "BLK/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, NEO/BTC, " + "TKN/BTC, XLTCUSDT, XRP/BTC.\n" in captured.out) patch_exchange(mocker, api_mock=api_mock, id="binance") @@ -231,7 +232,7 @@ def test_list_markets(mocker, markets, capsys): pargs['config'] = None start_list_markets(pargs, False) captured = capsys.readouterr() - assert re.match("\nExchange Binance has 9 active markets:\n", + assert re.match("\nExchange Binance has 10 active markets:\n", captured.out) patch_exchange(mocker, api_mock=api_mock, id="bittrex") @@ -243,8 +244,8 @@ def test_list_markets(mocker, markets, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 11 markets: " - "BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/USD, LTC/USDT, NEO/BTC, " + assert ("Exchange Bittrex has 12 markets: " + "BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, LTC/USDT, NEO/BTC, " "TKN/BTC, XLTCUSDT, XRP/BTC.\n" in captured.out) @@ -256,8 +257,8 @@ def test_list_markets(mocker, markets, capsys): ] start_list_markets(get_args(args), True) captured = capsys.readouterr() - assert ("Exchange Bittrex has 8 active pairs: " - "BLK/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/USD, NEO/BTC, TKN/BTC, XRP/BTC.\n" + assert ("Exchange Bittrex has 9 active pairs: " + "BLK/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, NEO/BTC, TKN/BTC, XRP/BTC.\n" in captured.out) # Test list-pairs subcommand with --all: all pairs @@ -268,8 +269,8 @@ def test_list_markets(mocker, markets, capsys): ] start_list_markets(get_args(args), True) captured = capsys.readouterr() - assert ("Exchange Bittrex has 10 pairs: " - "BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/USD, LTC/USDT, NEO/BTC, " + assert ("Exchange Bittrex has 11 pairs: " + "BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, LTC/USDT, NEO/BTC, " "TKN/BTC, XRP/BTC.\n" in captured.out) @@ -282,8 +283,8 @@ def test_list_markets(mocker, markets, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 5 active markets with ETH, LTC as base currencies: " - "ETH/BTC, ETH/USDT, LTC/BTC, LTC/USD, XLTCUSDT.\n" + assert ("Exchange Bittrex has 6 active markets with ETH, LTC as base currencies: " + "ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, XLTCUSDT.\n" in captured.out) # active markets, base=LTC @@ -295,8 +296,8 @@ def test_list_markets(mocker, markets, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 3 active markets with LTC as base currency: " - "LTC/BTC, LTC/USD, XLTCUSDT.\n" + assert ("Exchange Bittrex has 4 active markets with LTC as base currency: " + "LTC/BTC, LTC/ETH, LTC/USD, XLTCUSDT.\n" in captured.out) # active markets, quote=USDT, USD @@ -384,7 +385,7 @@ def test_list_markets(mocker, markets, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 9 active markets:\n" + assert ("Exchange Bittrex has 10 active markets:\n" in captured.out) # Test tabular output, no markets found @@ -407,7 +408,7 @@ def test_list_markets(mocker, markets, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ('["BLK/BTC","ETH/BTC","ETH/USDT","LTC/BTC","LTC/USD","NEO/BTC",' + assert ('["BLK/BTC","ETH/BTC","ETH/USDT","LTC/BTC","LTC/ETH","LTC/USD","NEO/BTC",' '"TKN/BTC","XLTCUSDT","XRP/BTC"]' in captured.out) diff --git a/tests/conftest.py b/tests/conftest.py index acb730330..000f62868 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -575,7 +575,34 @@ def get_markets(): } }, 'info': {}, - } + }, + 'LTC/ETH': { + 'id': 'LTCETH', + 'symbol': 'LTC/ETH', + 'base': 'LTC', + 'quote': 'ETH', + 'active': True, + 'precision': { + 'base': 8, + 'quote': 8, + 'amount': 3, + 'price': 5 + }, + 'limits': { + 'amount': { + 'min': 0.001, + 'max': 10000000.0 + }, + 'price': { + 'min': 1e-05, + 'max': 1000.0 + }, + 'cost': { + 'min': 0.01, + 'max': None + } + }, + }, } diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index d1c105591..98edd0dbb 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -400,7 +400,7 @@ def test_validate_stake_currency_error(default_conf, mocker, caplog): def test_get_quote_currencies(default_conf, mocker): ex = get_patched_exchange(mocker, default_conf) - assert set(ex.get_quote_currencies()) == set(['USD', 'BTC', 'USDT']) + assert set(ex.get_quote_currencies()) == set(['USD', 'ETH', 'BTC', 'USDT']) def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly @@ -1862,6 +1862,7 @@ def test_get_valid_pair_combination(default_conf, mocker, markets): # 'ETH/BTC': 'active': True # 'ETH/USDT': 'active': True # 'LTC/BTC': 'active': False + # 'LTC/ETH': 'active': True # 'LTC/USD': 'active': True # 'LTC/USDT': 'active': True # 'NEO/BTC': 'active': False @@ -1870,26 +1871,26 @@ def test_get_valid_pair_combination(default_conf, mocker, markets): # 'XRP/BTC': 'active': False # all markets ([], [], False, False, - ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/USD', + ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC']), # active markets ([], [], False, True, - ['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/USD', 'NEO/BTC', + ['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'NEO/BTC', 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC']), # all pairs ([], [], True, False, - ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/USD', + ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC']), # active pairs ([], [], True, True, - ['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/USD', 'NEO/BTC', + ['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC']), # all markets, base=ETH, LTC (['ETH', 'LTC'], [], False, False, - ['ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']), + ['ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']), # all markets, base=LTC (['LTC'], [], False, False, - ['LTC/BTC', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']), + ['LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']), # all markets, quote=USDT ([], ['USDT'], False, False, ['ETH/USDT', 'LTC/USDT', 'XLTCUSDT']), diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 20db46fac..9a7816d35 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2192,6 +2192,7 @@ def test_handle_timedout_limit_buy(mocker, default_conf, limit_buy_order) -> Non Trade.session = MagicMock() trade = MagicMock() + trade.pair = 'LTC/ETH' limit_buy_order['remaining'] = limit_buy_order['amount'] assert freqtrade.handle_timedout_limit_buy(trade, limit_buy_order) assert cancel_order_mock.call_count == 1 @@ -2215,6 +2216,7 @@ def test_handle_timedout_limit_buy_corder_empty(mocker, default_conf, limit_buy_ Trade.session = MagicMock() trade = MagicMock() + trade.pair = 'LTC/ETH' limit_buy_order['remaining'] = limit_buy_order['amount'] assert freqtrade.handle_timedout_limit_buy(trade, limit_buy_order) assert cancel_order_mock.call_count == 1 From e9448dc5e248f702b80ddd9be62faefd6e1526d1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Feb 2020 07:01:23 +0100 Subject: [PATCH 04/10] Add tsts for quote and base currency --- tests/exchange/test_exchange.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 98edd0dbb..def4e6ab6 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -403,6 +403,28 @@ def test_get_quote_currencies(default_conf, mocker): assert set(ex.get_quote_currencies()) == set(['USD', 'ETH', 'BTC', 'USDT']) +@pytest.mark.parametrize('pair,expected', [ + ('XRP/BTC', 'BTC'), + ('LTC/USD', 'USD'), + ('ETH/USDT', 'USDT'), + ('XLTCUSDT', 'USDT'), +]) +def test_get_pair_quote_currency(default_conf, mocker, pair, expected): + ex = get_patched_exchange(mocker, default_conf) + assert ex.get_pair_quote_currency(pair) == expected + + +@pytest.mark.parametrize('pair,expected', [ + ('XRP/BTC', 'XRP'), + ('LTC/USD', 'LTC'), + ('ETH/USDT', 'ETH'), + ('XLTCUSDT', 'LTC'), +]) +def test_get_pair_base_currency(default_conf, mocker, pair, expected): + ex = get_patched_exchange(mocker, default_conf) + assert ex.get_pair_base_currency(pair) == expected + + def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly api_mock = MagicMock() type(api_mock).markets = PropertyMock(return_value={ From e8eaa8920ea397a9403ece95d27c3a647ed428d8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Feb 2020 07:01:31 +0100 Subject: [PATCH 05/10] Use get_base_currency instead of splitting by / --- freqtrade/freqtradebot.py | 3 +-- freqtrade/wallets.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 38583b5ad..424a6a220 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -938,8 +938,7 @@ class FreqtradeBot: """ # Update wallets to ensure amounts tied up in a stoploss is now free! self.wallets.update() - - wallet_amount = self.wallets.get_free(pair.split('/')[0]) + wallet_amount = self.wallets.get_free(self.exchange.get_pair_base_currency(pair)) logger.debug(f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}") if wallet_amount >= amount: return amount diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index dd5e34fe6..b913155bc 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -74,7 +74,7 @@ class Wallets: ) for trade in open_trades: - curr = trade.pair.split('/')[0] + curr = self._exchange.get_pair_base_currency(trade.pair) _wallets[curr] = Wallet( curr, trade.amount, From d34515a5de5b08e21cfc2e6854d4037758fc1f11 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Feb 2020 07:03:11 +0100 Subject: [PATCH 06/10] Remove constraint to have pairs in base/quote format --- freqtrade/constants.py | 2 -- tests/exchange/test_exchange.py | 1 + tests/test_configuration.py | 8 +------- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 105cd6b53..1504d1f1c 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -251,7 +251,6 @@ CONF_SCHEMA = { 'type': 'array', 'items': { 'type': 'string', - 'pattern': '^[0-9A-Z]+/[0-9A-Z]+$' }, 'uniqueItems': True }, @@ -259,7 +258,6 @@ CONF_SCHEMA = { 'type': 'array', 'items': { 'type': 'string', - 'pattern': '^[0-9A-Z]+/[0-9A-Z]+$' }, 'uniqueItems': True }, diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index def4e6ab6..ca2bedb01 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -525,6 +525,7 @@ def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog): with pytest.raises(OperationalException, match=r"Stake-currency 'BTC' not compatible with.*"): Exchange(default_conf) + @pytest.mark.parametrize("timeframe", [ ('5m'), ("1m"), ("15m"), ("1h") ]) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 828db4d83..a58e88ea0 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -34,13 +34,6 @@ def all_conf(): return conf -def test_load_config_invalid_pair(default_conf) -> None: - default_conf['exchange']['pair_whitelist'].append('ETH-BTC') - - with pytest.raises(ValidationError, match=r'.*does not match.*'): - validate_config_schema(default_conf) - - def test_load_config_missing_attributes(default_conf) -> None: conf = deepcopy(default_conf) conf.pop('exchange') @@ -810,6 +803,7 @@ def test_validate_whitelist(default_conf): validate_config_consistency(conf) + def test_load_config_test_comments() -> None: """ Load config with comments From 31ac4598ba966b60be897ef22884442fa63eeb6f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Feb 2020 07:16:37 +0100 Subject: [PATCH 07/10] Fix last occurances of pair splitting --- freqtrade/exchange/exchange.py | 4 ++-- freqtrade/pairlist/VolumePairList.py | 4 ++-- freqtrade/rpc/rpc.py | 4 ++-- tests/rpc/test_rpc.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 0e0d2dabe..6964986b0 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -232,13 +232,13 @@ class Exchange: """ Return a pair's quote currency """ - return self.markets[pair].get('quote') + return self.markets.get(pair, {}).get('quote') def get_pair_base_currency(self, pair: str) -> str: """ Return a pair's quote currency """ - return self.markets[pair].get('base') + return self.markets.get(pair, {}).get('base') def klines(self, pair_interval: Tuple[str, str], copy: bool = True) -> DataFrame: if pair_interval in self._klines: diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index e50dafb63..e1cdb4a43 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -91,9 +91,9 @@ class VolumePairList(IPairList): if self._pairlist_pos == 0: # If VolumePairList is the first in the list, use fresh pairlist - # check length so that we make sure that '/' is actually in the string + # check base currency equals to stake currency. filtered_tickers = [v for k, v in tickers.items() - if (len(k.split('/')) == 2 and k.split('/')[1] == base_currency + if (self._exchange.get_pair_quote_currency(k) == base_currency and v[key] is not None)] else: # If other pairlist is in front, use the incomming pairlist. diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 3411318bb..d680d36b1 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -462,7 +462,7 @@ class RPC: # Check pair is in stake currency stake_currency = self._freqtrade.config.get('stake_currency') - if not pair.endswith(stake_currency): + if not self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency: raise RPCException( f'Wrong pair selected. Please pairs with stake {stake_currency} pairs only') # check if valid pair @@ -517,7 +517,7 @@ class RPC: if add: stake_currency = self._freqtrade.config.get('stake_currency') for pair in add: - if (pair.endswith(stake_currency) + if (self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency and pair not in self._freqtrade.pairlists.blacklist): self._freqtrade.pairlists.blacklist.append(pair) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index a35bfa0d6..3c99e6b92 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -687,7 +687,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order) -> None # Test buy pair not with stakes with pytest.raises(RPCException, match=r'Wrong pair selected. Please pairs with stake.*'): - rpc._rpc_forcebuy('XRP/ETH', 0.0001) + rpc._rpc_forcebuy('LTC/ETH', 0.0001) pair = 'XRP/BTC' # Test not buying From 1021ffa1c3051d17482a00e79eef5a2b211cd103 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 26 Feb 2020 07:00:08 +0100 Subject: [PATCH 08/10] Apply suggestions from code review Add suggested changes to comments Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> --- freqtrade/pairlist/VolumePairList.py | 2 +- freqtrade/rpc/rpc.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index e1cdb4a43..d067d5e8a 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -91,7 +91,7 @@ class VolumePairList(IPairList): if self._pairlist_pos == 0: # If VolumePairList is the first in the list, use fresh pairlist - # check base currency equals to stake currency. + # Check if pair quote currency equals to the stake currency. filtered_tickers = [v for k, v in tickers.items() if (self._exchange.get_pair_quote_currency(k) == base_currency and v[key] is not None)] diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index d680d36b1..1e4eaa3e0 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -460,7 +460,7 @@ class RPC: if self._freqtrade.state != State.RUNNING: raise RPCException('trader is not running') - # Check pair is in stake currency + # Check if pair quote currency equals to the stake currency. stake_currency = self._freqtrade.config.get('stake_currency') if not self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency: raise RPCException( From 4e218be51db55e16bccc5513be244b98467ccb9f Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 26 Feb 2020 07:08:09 +0100 Subject: [PATCH 09/10] Don't use markets[pair]['quote'] --- freqtrade/exchange/exchange.py | 2 +- freqtrade/freqtradebot.py | 4 ++-- freqtrade/pairlist/IPairList.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 6964986b0..cc0ecc6cd 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -334,7 +334,7 @@ class Exchange: logger.warning(f"Pair {pair} is restricted for some users on this exchange." f"Please check if you are impacted by this restriction " f"on the exchange and eventually remove {pair} from your whitelist.") - if not self.markets[pair].get('quote') == self._config['stake_currency']: + if not self.get_pair_quote_currency(pair) == self._config['stake_currency']: invalid_pairs.append(pair) if invalid_pairs: raise OperationalException( diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 424a6a220..c3b642095 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -938,7 +938,8 @@ class FreqtradeBot: """ # Update wallets to ensure amounts tied up in a stoploss is now free! self.wallets.update() - wallet_amount = self.wallets.get_free(self.exchange.get_pair_base_currency(pair)) + trade_base_currency = self.exchange.get_pair_base_currency(pair) + wallet_amount = self.wallets.get_free(trade_base_currency) logger.debug(f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}") if wallet_amount >= amount: return amount @@ -1145,7 +1146,6 @@ class FreqtradeBot: return order_amount amount = 0 fee_abs = 0 - trade_base_currency = self.exchange.get_pair_base_currency(trade.pair) for exectrade in trades: amount += exectrade['amount'] if ("fee" in exectrade and exectrade['fee'] is not None and diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 7d489ece7..d45a329dd 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -100,7 +100,7 @@ class IPairList(ABC): f"{self._exchange.name}. Removing it from whitelist..") continue - if markets[pair]['quote'] != self._config['stake_currency']: + if self._exchange.get_pair_quote_currency(pair) != self._config['stake_currency']: logger.warning(f"Pair {pair} is not compatible with your stake currency " f"{self._config['stake_currency']}. Removing it from whitelist..") continue From f38accb77b05accfbf328e62c5b417ed7613d527 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 26 Feb 2020 07:09:54 +0100 Subject: [PATCH 10/10] Return empty string if no quote / base currency can be found --- freqtrade/exchange/exchange.py | 4 ++-- tests/exchange/test_exchange.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index cc0ecc6cd..21627679f 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -232,13 +232,13 @@ class Exchange: """ Return a pair's quote currency """ - return self.markets.get(pair, {}).get('quote') + return self.markets.get(pair, {}).get('quote', '') def get_pair_base_currency(self, pair: str) -> str: """ Return a pair's quote currency """ - return self.markets.get(pair, {}).get('base') + return self.markets.get(pair, {}).get('base', '') def klines(self, pair_interval: Tuple[str, str], copy: bool = True) -> DataFrame: if pair_interval in self._klines: diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index ca2bedb01..3a653edb6 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -408,6 +408,7 @@ def test_get_quote_currencies(default_conf, mocker): ('LTC/USD', 'USD'), ('ETH/USDT', 'USDT'), ('XLTCUSDT', 'USDT'), + ('XRP/NOCURRENCY', ''), ]) def test_get_pair_quote_currency(default_conf, mocker, pair, expected): ex = get_patched_exchange(mocker, default_conf) @@ -419,6 +420,7 @@ def test_get_pair_quote_currency(default_conf, mocker, pair, expected): ('LTC/USD', 'LTC'), ('ETH/USDT', 'ETH'), ('XLTCUSDT', 'LTC'), + ('XRP/NOCURRENCY', ''), ]) def test_get_pair_base_currency(default_conf, mocker, pair, expected): ex = get_patched_exchange(mocker, default_conf)