diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 047a34dd6..f6a111944 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,7 +74,7 @@ jobs: # Fake travis environment to get coveralls working correctly export TRAVIS_PULL_REQUEST="https://github.com/${GITHUB_REPOSITORY}/pull/$(cat $GITHUB_EVENT_PATH | jq -r .number)" export TRAVIS_BRANCH=${GITHUB_REF#"ref/heads"} - export TRAVIS_BRANCH=${HEAD_REF} + export CI_BRANCH=${GITHUB_REF#"ref/heads"} echo "${TRAVIS_BRANCH}" coveralls || true diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 358c63f90..ec341ff0a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -266,7 +266,11 @@ class FreqtradeBot: amount_reserve_percent += self.strategy.stoploss # it should not be more than 50% amount_reserve_percent = max(amount_reserve_percent, 0.5) - return min(min_stake_amounts) / amount_reserve_percent + + # The value returned should satisfy both limits: for amount (base currency) and + # for cost (quote, stake currency), so max() is used here. + # See also #2575 at github. + return max(min_stake_amounts) / amount_reserve_percent def create_trades(self) -> bool: """ diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py index 1ec8cc305..8f4cc4787 100644 --- a/freqtrade/rpc/api_server.py +++ b/freqtrade/rpc/api_server.py @@ -354,7 +354,8 @@ class ApiServer(RPC): Returns the current status of the trades in json format """ - results = self._rpc_balance(self._config.get('fiat_display_currency', '')) + results = self._rpc_balance(self._config['stake_currency'], + self._config.get('fiat_display_currency', '')) return self.rest_dump(results) @require_login diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 8898f3068..4cebe646e 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -297,34 +297,42 @@ class RPC: 'best_rate': round(bp_rate * 100, 2), } - def _rpc_balance(self, fiat_display_currency: str) -> Dict: + def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict: """ Returns current account balance per crypto """ output = [] total = 0.0 - for coin, balance in self._freqtrade.exchange.get_balances().items(): - if not balance['total']: + try: + tickers = self._freqtrade.exchange.get_tickers() + except (TemporaryError, DependencyException): + raise RPCException('Error getting current tickers.') + + for coin, balance in self._freqtrade.wallets.get_all_balances().items(): + if not balance.total: continue - if coin == 'BTC': + est_stake: float = 0 + if coin == stake_currency: rate = 1.0 + est_stake = balance.total else: try: - 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(pair, False) + pair = self._freqtrade.exchange.get_valid_pair_combination(coin, stake_currency) + rate = tickers.get(pair, {}).get('bid', None) + if rate: + if pair.startswith(stake_currency): + rate = 1.0 / rate + est_stake = rate * balance.total except (TemporaryError, DependencyException): logger.warning(f" Could not get rate for pair {coin}.") continue - est_btc: float = rate * balance['total'] - total = total + est_btc + total = total + (est_stake or 0) output.append({ 'currency': coin, - 'free': balance['free'] if balance['free'] is not None else 0, - 'balance': balance['total'] if balance['total'] is not None else 0, - 'used': balance['used'] if balance['used'] is not None else 0, - 'est_btc': est_btc, + 'free': balance.free if balance.free is not None else 0, + 'balance': balance.total if balance.total is not None else 0, + 'used': balance.used if balance.used is not None else 0, + 'est_stake': est_stake or 0, + 'stake': stake_currency, }) if total == 0.0: if self._freqtrade.config.get('dry_run', False): diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 0547af7b0..2ae22f472 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -325,15 +325,16 @@ class Telegram(RPC): def _balance(self, update: Update, context: CallbackContext) -> None: """ Handler for /balance """ try: - result = self._rpc_balance(self._config.get('fiat_display_currency', '')) + result = self._rpc_balance(self._config['stake_currency'], + self._config.get('fiat_display_currency', '')) output = '' for currency in result['currencies']: - if currency['est_btc'] > 0.0001: + if currency['est_stake'] > 0.0001: curr_output = "*{currency}:*\n" \ "\t`Available: {free: .8f}`\n" \ "\t`Balance: {balance: .8f}`\n" \ "\t`Pending: {used: .8f}`\n" \ - "\t`Est. BTC: {est_btc: .8f}`\n".format(**currency) + "\t`Est. {stake}: {est_stake: .8f}`\n".format(**currency) else: curr_output = "*{currency}:* not showing <1$ amount \n".format(**currency) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 90b68c49d..c674b5286 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -2,7 +2,7 @@ """ Wallet """ import logging -from typing import Dict, NamedTuple +from typing import Dict, NamedTuple, Any from freqtrade.exchange import Exchange from freqtrade import constants @@ -72,3 +72,6 @@ class Wallets: ) logger.info('Wallets synced.') + + def get_all_balances(self) -> Dict[str, Any]: + return self._wallets diff --git a/requirements-common.txt b/requirements-common.txt index 2c176e9c3..e5c66590a 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -1,6 +1,6 @@ # requirements without requirements installable via conda # mainly used for Raspberry pi installs -ccxt==1.19.54 +ccxt==1.19.86 SQLAlchemy==1.3.11 python-telegram-bot==12.2.0 arrow==0.15.4 @@ -8,7 +8,7 @@ cachetools==3.1.1 requests==2.22.0 urllib3==1.25.7 wrapt==1.11.2 -jsonschema==3.1.1 +jsonschema==3.2.0 TA-Lib==0.4.17 tabulate==0.8.6 coinmarketcap==5.0.3 diff --git a/requirements-dev.txt b/requirements-dev.txt index 297b95623..a60bbf0eb 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,10 +8,10 @@ flake8==3.7.9 flake8-type-annotations==0.1.0 flake8-tidy-imports==3.1.0 mypy==0.740 -pytest==5.2.4 +pytest==5.3.0 pytest-asyncio==0.10.0 pytest-cov==2.8.1 -pytest-mock==1.11.2 +pytest-mock==1.12.1 pytest-random-order==1.0.4 # Convert jupyter notebooks to markdown documents diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index ff8de9cb2..96a22b42e 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -2,7 +2,7 @@ -r requirements.txt # Required for hyperopt -scipy==1.3.2 +scipy==1.3.3 scikit-learn==0.21.3 scikit-optimize==0.5.2 filelock==3.0.12 diff --git a/tests/conftest.py b/tests/conftest.py index fbd23a0dc..65fdc5b89 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -325,7 +325,7 @@ def get_markets(): }, 'price': 500000, 'cost': { - 'min': 1, + 'min': 0.0001, 'max': 500000, }, }, @@ -351,7 +351,7 @@ def get_markets(): }, 'price': 500000, 'cost': { - 'min': 1, + 'min': 0.0001, 'max': 500000, }, }, @@ -376,7 +376,7 @@ def get_markets(): }, 'price': 500000, 'cost': { - 'min': 1, + 'min': 0.0001, 'max': 500000, }, }, @@ -401,7 +401,7 @@ def get_markets(): }, 'price': 500000, 'cost': { - 'min': 1, + 'min': 0.0001, 'max': 500000, }, }, @@ -426,7 +426,7 @@ def get_markets(): }, 'price': 500000, 'cost': { - 'min': 1, + 'min': 0.0001, 'max': 500000, }, }, @@ -451,7 +451,7 @@ def get_markets(): }, 'price': 500000, 'cost': { - 'min': 1, + 'min': 0.0001, 'max': 500000, }, }, @@ -479,7 +479,7 @@ def get_markets(): 'max': None }, 'cost': { - 'min': 0.001, + 'min': 0.0001, 'max': None } }, @@ -980,6 +980,28 @@ def tickers(): 'quoteVolume': 62.68220262, 'info': {} }, + 'BTC/USDT': { + 'symbol': 'BTC/USDT', + 'timestamp': 1573758371399, + 'datetime': '2019-11-14T19:06:11.399Z', + 'high': 8800.0, + 'low': 8582.6, + 'bid': 8648.16, + 'bidVolume': 0.238771, + 'ask': 8648.72, + 'askVolume': 0.016253, + 'vwap': 8683.13647806, + 'open': 8759.7, + 'close': 8648.72, + 'last': 8648.72, + 'previousClose': 8759.67, + 'change': -110.98, + 'percentage': -1.267, + 'average': None, + 'baseVolume': 35025.943355, + 'quoteVolume': 304135046.4242901, + 'info': {} + }, 'ETH/USDT': { 'symbol': 'ETH/USDT', 'timestamp': 1522014804118, @@ -1067,7 +1089,29 @@ def tickers(): 'baseVolume': 59698.79897, 'quoteVolume': 29132399.743954, 'info': {} - } + }, + 'XRP/BTC': { + 'symbol': 'XRP/BTC', + 'timestamp': 1573758257534, + 'datetime': '2019-11-14T19:04:17.534Z', + 'high': 3.126e-05, + 'low': 3.061e-05, + 'bid': 3.093e-05, + 'bidVolume': 27901.0, + 'ask': 3.095e-05, + 'askVolume': 10551.0, + 'vwap': 3.091e-05, + 'open': 3.119e-05, + 'close': 3.094e-05, + 'last': 3.094e-05, + 'previousClose': 3.117e-05, + 'change': -2.5e-07, + 'percentage': -0.802, + 'average': None, + 'baseVolume': 37334921.0, + 'quoteVolume': 1154.19266394, + 'info': {} + }, }) @@ -1317,8 +1361,8 @@ def rpc_balance(): 'used': 0.0 }, 'XRP': { - 'total': 1.0, - 'free': 1.0, + 'total': 0.1, + 'free': 0.01, 'used': 0.0 }, 'EUR': { diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 32d66d3e8..43285cdb1 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -100,7 +100,7 @@ def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_co markets=PropertyMock(return_value=shitcoinmarkets), ) # argument: use the whitelist dynamically by exchange-volume - whitelist = ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC'] + whitelist = ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC', 'HOT/BTC'] bot.pairlists.refresh_pairlist() assert whitelist == bot.pairlists.whitelist @@ -135,10 +135,10 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): @pytest.mark.parametrize("pairlists,base_currency,whitelist_result", [ ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}], - "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']), + "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC', 'HOT/BTC']), # Different sorting depending on quote or bid volume ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}], - "BTC", ['HOT/BTC', 'FUEL/BTC', 'LTC/BTC', 'TKN/BTC', 'ETH/BTC']), + "BTC", ['HOT/BTC', 'FUEL/BTC', 'XRP/BTC', 'LTC/BTC', 'TKN/BTC']), ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}], "USDT", ['ETH/USDT']), # No pair for ETH ... @@ -146,19 +146,19 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): "ETH", []), # Precisionfilter and quote volume ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, - {"method": "PrecisionFilter"}], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'FUEL/BTC']), + {"method": "PrecisionFilter"}], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']), # Precisionfilter bid ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}, - {"method": "PrecisionFilter"}], "BTC", ['FUEL/BTC', 'LTC/BTC', 'TKN/BTC', 'ETH/BTC']), + {"method": "PrecisionFilter"}], "BTC", ['FUEL/BTC', 'XRP/BTC', 'LTC/BTC', 'TKN/BTC']), # PriceFilter and VolumePairList ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "PriceFilter", "low_price_ratio": 0.03}], - "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'FUEL/BTC']), + "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']), # Hot is removed by precision_filter, Fuel by low_price_filter. - ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, + ([{"method": "VolumePairList", "number_assets": 6, "sort_key": "quoteVolume"}, {"method": "PrecisionFilter"}, {"method": "PriceFilter", "low_price_ratio": 0.02} - ], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC']), + ], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']), # StaticPairlist Only ([{"method": "StaticPairList"}, ], "BTC", ['ETH/BTC', 'TKN/BTC']), diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 0e8588aea..699f2d962 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -355,29 +355,18 @@ def test_rpc_balance_handle_error(default_conf, mocker): mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=mock_balance), - get_ticker=MagicMock(side_effect=TemporaryError('Could not load ticker due to xxx')) + get_tickers=MagicMock(side_effect=TemporaryError('Could not load ticker due to xxx')) ) freqtradebot = get_patched_freqtradebot(mocker, 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) - assert prec_satoshi(result['value'], 180000) - assert 'USD' == result['symbol'] - assert result['currencies'] == [{ - 'currency': 'BTC', - 'free': 10.0, - 'balance': 12.0, - 'used': 2.0, - 'est_btc': 12.0, - }] - assert result['total'] == 12.0 + with pytest.raises(RPCException, match="Error getting current tickers."): + rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency']) -def test_rpc_balance_handle(default_conf, mocker): +def test_rpc_balance_handle(default_conf, mocker, tickers): mock_balance = { 'BTC': { 'free': 10.0, @@ -389,7 +378,7 @@ def test_rpc_balance_handle(default_conf, mocker): 'total': 5.0, 'used': 4.0, }, - 'PAX': { + 'USDT': { 'free': 5.0, 'total': 10.0, 'used': 5.0, @@ -405,10 +394,9 @@ def test_rpc_balance_handle(default_conf, mocker): 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_tickers=tickers, get_valid_pair_combination=MagicMock( - side_effect=lambda a, b: f"{b}/{a}" if a == "PAX" else f"{a}/{b}") + side_effect=lambda a, b: f"{b}/{a}" if a == "USDT" else f"{a}/{b}") ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) @@ -416,30 +404,35 @@ def test_rpc_balance_handle(default_conf, mocker): 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) + result = rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency']) + assert prec_satoshi(result['total'], 12.309096315) + assert prec_satoshi(result['value'], 184636.44472997) assert 'USD' == result['symbol'] assert result['currencies'] == [ {'currency': 'BTC', - 'free': 10.0, - 'balance': 12.0, - 'used': 2.0, - 'est_btc': 12.0, + 'free': 10.0, + 'balance': 12.0, + 'used': 2.0, + 'est_stake': 12.0, + 'stake': 'BTC', }, {'free': 1.0, 'balance': 5.0, 'currency': 'ETH', - 'est_btc': 0.05, - 'used': 4.0 + 'est_stake': 0.30794, + 'used': 4.0, + 'stake': 'BTC', + }, {'free': 5.0, 'balance': 10.0, - 'currency': 'PAX', - 'est_btc': 0.1, - 'used': 5.0} + 'currency': 'USDT', + 'est_stake': 0.0011563153318162476, + 'used': 5.0, + 'stake': 'BTC', + } ] - assert result['total'] == 12.15 + assert result['total'] == 12.309096315331816 def test_rpc_start(mocker, default_conf) -> None: diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index a59fd1942..555fcdc81 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -230,28 +230,10 @@ def test_api_stopbuy(botclient): def test_api_balance(botclient, mocker, rpc_balance): ftbot, client = botclient - def mock_ticker(symbol, refresh): - if symbol == 'BTC/USDT': - return { - 'bid': 10000.00, - 'ask': 10000.00, - 'last': 10000.00, - } - elif symbol == 'XRP/BTC': - return { - 'bid': 0.00001, - 'ask': 0.00001, - 'last': 0.00001, - } - return { - 'bid': 0.1, - 'ask': 0.1, - 'last': 0.1, - } 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}") + ftbot.wallets.update() rc = client_get(client, f"{BASE_URI}/balance") assert_response(rc) @@ -262,7 +244,8 @@ def test_api_balance(botclient, mocker, rpc_balance): 'free': 12.0, 'balance': 12.0, 'used': 0.0, - 'est_btc': 12.0, + 'est_stake': 12.0, + 'stake': 'BTC', } diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index f69c2ac38..367eb8366 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -461,29 +461,10 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, assert '*Best Performing:* `ETH/BTC: 6.20%`' in msg_mock.call_args_list[-1][0][0] -def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance) -> None: - - def mock_ticker(symbol, refresh): - if symbol == 'BTC/USDT': - return { - 'bid': 10000.00, - 'ask': 10000.00, - 'last': 10000.00, - } - elif symbol == 'XRP/BTC': - return { - 'bid': 0.00001, - 'ask': 0.00001, - 'last': 0.00001, - } - return { - 'bid': 0.1, - 'ask': 0.1, - 'last': 0.1, - } +def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tickers) -> None: 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_tickers', tickers) mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination', side_effect=lambda a, b: f"{a}/{b}") @@ -564,7 +545,8 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None 'free': 1.0, 'used': 0.5, 'balance': i, - 'est_btc': 1 + 'est_stake': 1, + 'stake': 'BTC', }) mocker.patch('freqtrade.rpc.rpc.RPC._rpc_balance', return_value={ 'currencies': balances, diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 76a50f0f4..fe58ccf37 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -299,7 +299,7 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker, limit_buy_order, fee) -> None: patch_RPCManager(mocker) patch_exchange(mocker) - default_conf['stake_amount'] = 0.0098751 + default_conf['stake_amount'] = 0.00098751 default_conf['max_open_trades'] = 2 mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -313,7 +313,7 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker, trade = Trade.query.first() assert trade is not None - assert trade.stake_amount == 0.0098751 + assert trade.stake_amount == 0.00098751 assert trade.is_open assert trade.open_date is not None @@ -321,11 +321,11 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker, trade = Trade.query.order_by(Trade.id.desc()).first() assert trade is not None - assert trade.stake_amount == 0.0098751 + assert trade.stake_amount == 0.00098751 assert trade.is_open assert trade.open_date is not None - assert Trade.total_open_trades_stakes() == 1.97502e-02 + assert Trade.total_open_trades_stakes() == 1.97502e-03 def test_get_min_pair_stake_amount(mocker, default_conf) -> None: @@ -334,6 +334,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: freqtrade = FreqtradeBot(default_conf) freqtrade.strategy.stoploss = -0.05 markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}} + # no pair found mocker.patch( 'freqtrade.exchange.Exchange.markets', @@ -425,7 +426,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: PropertyMock(return_value=markets) ) result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 2) - assert result == min(2, 2 * 2) / 0.9 + assert result == max(2, 2 * 2) / 0.9 # min amount and cost are set (amount is minial) markets["ETH/BTC"]["limits"] = { @@ -437,7 +438,27 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: PropertyMock(return_value=markets) ) result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 2) - assert result == min(8, 2 * 2) / 0.9 + assert result == max(8, 2 * 2) / 0.9 + + +def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: + patch_RPCManager(mocker) + patch_exchange(mocker) + freqtrade = FreqtradeBot(default_conf) + freqtrade.strategy.stoploss = -0.05 + markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}} + + # Real Binance data + markets["ETH/BTC"]["limits"] = { + 'cost': {'min': 0.0001}, + 'amount': {'min': 0.001} + } + mocker.patch( + 'freqtrade.exchange.Exchange.markets', + PropertyMock(return_value=markets) + ) + result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 0.020405) + assert round(result, 8) == round(max(0.0001, 0.001 * 0.020405) / 0.9, 8) def test_create_trades(default_conf, ticker, limit_buy_order, fee, mocker) -> None: