From 5db883906af22eeb086174d1fcfae149f6978ed4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 13 Dec 2019 06:52:33 +0100 Subject: [PATCH 1/4] Try to verify available amount on the exchange --- freqtrade/freqtradebot.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 0595e0d35..ac73f6d65 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -889,6 +889,27 @@ class FreqtradeBot: # TODO: figure out how to handle partially complete sell orders return False + def _safe_sell_amount(self, pair: str, amount: float) -> float: + """ + Get sellable amount. + Should be trade.amount - but will fall back to the available amount if necessary. + This should cover cases where get_real_amount() was not able to update the amount + for whatever reason. + :param pair: pair - used for logging + :param amount: amount we expect to be available + :return: amount to sell + :raise: DependencyException: if available balance is not within 2% of the available amount. + """ + wallet_amount = self.wallets.get_free(pair) + logger.info(f"Amounts: {wallet_amount} - {amount}") + if wallet_amount > amount: + return amount + elif wallet_amount > amount * 0.98: + logger.info(f"{pair} - Falling back to wallet-amount.") + return wallet_amount + else: + raise DependencyException("Not enough amount to sell.") + def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType) -> None: """ Executes a limit sell for the given trade and limit @@ -919,10 +940,12 @@ class FreqtradeBot: # Emergencysells (default to market!) ordertype = self.strategy.order_types.get("emergencysell", "market") + amount = self._safe_sell_amount(trade.pair, trade.amount) + # Execute sell and update trade record order = self.exchange.sell(pair=str(trade.pair), ordertype=ordertype, - amount=trade.amount, rate=limit, + amount=amount, rate=limit, time_in_force=self.strategy.order_time_in_force['sell'] ) From 04257d8ecc357dea23face544f8e979834a20e19 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 13 Dec 2019 07:06:54 +0100 Subject: [PATCH 2/4] Add tests for safe_sell_amount --- freqtrade/freqtradebot.py | 2 +- tests/test_freqtradebot.py | 76 +++++++++++++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ac73f6d65..3ebe89a71 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -901,7 +901,7 @@ class FreqtradeBot: :raise: DependencyException: if available balance is not within 2% of the available amount. """ wallet_amount = self.wallets.get_free(pair) - logger.info(f"Amounts: {wallet_amount} - {amount}") + logger.debug(f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}") if wallet_amount > amount: return amount elif wallet_amount > amount * 0.98: diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index efab64a6a..9c2fd9ddc 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -883,7 +883,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order) -> None: 'freqtrade.freqtradebot.FreqtradeBot', get_target_bid=get_bid, _get_min_pair_stake_amount=MagicMock(return_value=1) - ) + ) buy_mm = MagicMock(return_value={'id': limit_buy_order['id']}) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -2314,6 +2314,7 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, caplog) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) mocker.patch('freqtrade.exchange.Exchange.cancel_order', side_effect=InvalidOrderException()) + mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=300)) sellmock = MagicMock() patch_exchange(mocker) mocker.patch.multiple( @@ -2591,7 +2592,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, mocker freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple( - sell_flag=False, sell_type=SellType.NONE)) + sell_flag=False, sell_type=SellType.NONE)) freqtrade.create_trades() trade = Trade.query.first() @@ -2631,6 +2632,77 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, mocke assert trade.sell_reason == SellType.SELL_SIGNAL.value +def test_sell_not_enough_balance(default_conf, limit_buy_order, + fee, mocker, caplog) -> None: + patch_RPCManager(mocker) + patch_exchange(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_ticker=MagicMock(return_value={ + 'bid': 0.00002172, + 'ask': 0.00002173, + 'last': 0.00002172 + }), + buy=MagicMock(return_value={'id': limit_buy_order['id']}), + get_fee=fee, + ) + + freqtrade = FreqtradeBot(default_conf) + patch_get_signal(freqtrade) + freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) + + freqtrade.create_trades() + + trade = Trade.query.first() + amnt = trade.amount + trade.update(limit_buy_order) + patch_get_signal(freqtrade, value=(False, True)) + mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=trade.amount * 0.985)) + + assert freqtrade.handle_trade(trade) is True + assert log_has_re(r'.*Falling back to wallet-amount.', caplog) + assert trade.amount != amnt + + +def test__safe_sell_amount(default_conf, caplog, mocker): + patch_RPCManager(mocker) + patch_exchange(mocker) + amount = 95.33 + amount_wallet = 95.29 + mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=amount_wallet)) + trade = Trade( + pair='LTC/ETH', + amount=amount, + exchange='binance', + open_rate=0.245441, + open_order_id="123456" + ) + freqtrade = FreqtradeBot(default_conf) + patch_get_signal(freqtrade) + + assert freqtrade._safe_sell_amount(trade.pair, trade.amount) == amount_wallet + assert log_has_re(r'.*Falling back to wallet-amount.', caplog) + + +def test__safe_sell_amount_error(default_conf, caplog, mocker): + patch_RPCManager(mocker) + patch_exchange(mocker) + amount = 95.33 + amount_wallet = 91.29 + mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=amount_wallet)) + trade = Trade( + pair='LTC/ETH', + amount=amount, + exchange='binance', + open_rate=0.245441, + open_order_id="123456" + ) + freqtrade = FreqtradeBot(default_conf) + patch_get_signal(freqtrade) + with pytest.raises(DependencyException, match=r"Not enough amount to sell."): + assert freqtrade._safe_sell_amount(trade.pair, trade.amount) + + def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplog) -> None: patch_RPCManager(mocker) patch_exchange(mocker) From e72c6a0d94d810b2976c4a6729291fe51602db9d Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 18 Dec 2019 20:02:15 +0100 Subject: [PATCH 3/4] use only first part of the currency to get wallet-amount (!!) --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 4a48bba04..5656268c8 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -903,7 +903,7 @@ class FreqtradeBot: :return: amount to sell :raise: DependencyException: if available balance is not within 2% of the available amount. """ - wallet_amount = self.wallets.get_free(pair) + wallet_amount = self.wallets.get_free(pair.split('/')[0]) logger.debug(f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}") if wallet_amount > amount: return amount From 6507a26cc160fdfe99a58fbd92a8e0a197f4edef Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 18 Dec 2019 20:16:53 +0100 Subject: [PATCH 4/4] Fix some tests after merge --- freqtrade/freqtradebot.py | 2 +- tests/test_freqtradebot.py | 18 ++++++++++++++---- tests/test_integration.py | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5656268c8..a51ab0dbf 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -898,7 +898,7 @@ class FreqtradeBot: Should be trade.amount - but will fall back to the available amount if necessary. This should cover cases where get_real_amount() was not able to update the amount for whatever reason. - :param pair: pair - used for logging + :param pair: Pair we're trying to sell :param amount: amount we expect to be available :return: amount to sell :raise: DependencyException: if available balance is not within 2% of the available amount. diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 300d0ad32..cd5c92199 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1682,6 +1682,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, fee, mock time.sleep(0.01) # Race condition fix trade.update(limit_buy_order) assert trade.is_open is True + freqtrade.wallets.update() patch_get_signal(freqtrade, value=(False, True)) assert freqtrade.handle_trade(trade) is True @@ -2549,6 +2550,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, trade = Trade.query.first() trade.update(limit_buy_order) + freqtrade.wallets.update() patch_get_signal(freqtrade, value=(False, True)) assert freqtrade.handle_trade(trade) is True assert trade.sell_reason == SellType.SELL_SIGNAL.value @@ -2579,6 +2581,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, trade = Trade.query.first() trade.update(limit_buy_order) + freqtrade.wallets.update() patch_get_signal(freqtrade, value=(False, True)) assert freqtrade.handle_trade(trade) is True assert trade.sell_reason == SellType.SELL_SIGNAL.value @@ -2639,6 +2642,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, mocke trade = Trade.query.first() trade.update(limit_buy_order) + freqtrade.wallets.update() patch_get_signal(freqtrade, value=(False, True)) assert freqtrade.handle_trade(trade) is True assert trade.sell_reason == SellType.SELL_SIGNAL.value @@ -2676,7 +2680,7 @@ def test_sell_not_enough_balance(default_conf, limit_buy_order, assert trade.amount != amnt -def test__safe_sell_amount(default_conf, caplog, mocker): +def test__safe_sell_amount(default_conf, fee, caplog, mocker): patch_RPCManager(mocker) patch_exchange(mocker) amount = 95.33 @@ -2687,7 +2691,9 @@ def test__safe_sell_amount(default_conf, caplog, mocker): amount=amount, exchange='binance', open_rate=0.245441, - open_order_id="123456" + open_order_id="123456", + fee_open=fee.return_value, + fee_close=fee.return_value, ) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) @@ -2696,7 +2702,7 @@ def test__safe_sell_amount(default_conf, caplog, mocker): assert log_has_re(r'.*Falling back to wallet-amount.', caplog) -def test__safe_sell_amount_error(default_conf, caplog, mocker): +def test__safe_sell_amount_error(default_conf, fee, caplog, mocker): patch_RPCManager(mocker) patch_exchange(mocker) amount = 95.33 @@ -2707,7 +2713,9 @@ def test__safe_sell_amount_error(default_conf, caplog, mocker): amount=amount, exchange='binance', open_rate=0.245441, - open_order_id="123456" + open_order_id="123456", + fee_open=fee.return_value, + fee_close=fee.return_value, ) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) @@ -2775,6 +2783,7 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, mocker) -> trade = Trade.query.first() trade.update(limit_buy_order) + freqtrade.wallets.update() patch_get_signal(freqtrade, value=(True, True)) assert freqtrade.handle_trade(trade) is False @@ -3512,6 +3521,7 @@ def test_order_book_ask_strategy(default_conf, limit_buy_order, limit_sell_order time.sleep(0.01) # Race condition fix trade.update(limit_buy_order) + freqtrade.wallets.update() assert trade.is_open is True patch_get_signal(freqtrade, value=(False, True)) diff --git a/tests/test_integration.py b/tests/test_integration.py index 728e96d55..2daf13db6 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -71,7 +71,7 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee, ) mocker.patch("freqtrade.strategy.interface.IStrategy.should_sell", should_sell_mock) wallets_mock = mocker.patch("freqtrade.wallets.Wallets.update", MagicMock()) - mocker.patch("freqtrade.wallets.Wallets.get_free", MagicMock(return_value=1)) + mocker.patch("freqtrade.wallets.Wallets.get_free", MagicMock(return_value=1000)) freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade.strategy.order_types['stoploss_on_exchange'] = True