From 4e15611b055a7afb5dfc00fd687967fa52bca1bf Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 8 Sep 2022 07:18:38 +0200 Subject: [PATCH 1/4] Don't crash in case of funding fee fetch error --- freqtrade/exchange/exchange.py | 11 +++++-- freqtrade/freqtradebot.py | 57 ++++++++++++++++++++-------------- 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 569dcad9b..be5af91db 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -2509,8 +2509,13 @@ class Exchange: cache=False, drop_incomplete=False, ) - funding_rates = candle_histories[funding_comb] - mark_rates = candle_histories[mark_comb] + try: + # we can't assume we always get histories - for example during exchange downtimes + funding_rates = candle_histories[funding_comb] + mark_rates = candle_histories[mark_comb] + except KeyError: + raise ExchangeError("Could not find funding rates") from None + funding_mark_rates = self.combine_funding_and_mark( funding_rates=funding_rates, mark_rates=mark_rates) @@ -2590,6 +2595,8 @@ class Exchange: :param is_short: trade direction :param amount: Trade amount :param open_date: Open date of the trade + :return: funding fee since open_date + :raies: ExchangeError if something goes wrong. """ if self.trading_mode == TradingMode.FUTURES: if self._config['dry_run']: diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ec32cae0e..61c323ed3 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -281,14 +281,17 @@ class FreqtradeBot(LoggingMixin): def update_funding_fees(self): if self.trading_mode == TradingMode.FUTURES: trades = Trade.get_open_trades() - for trade in trades: - funding_fees = self.exchange.get_funding_fees( - pair=trade.pair, - amount=trade.amount, - is_short=trade.is_short, - open_date=trade.date_last_filled_utc - ) - trade.funding_fees = funding_fees + try: + for trade in trades: + funding_fees = self.exchange.get_funding_fees( + pair=trade.pair, + amount=trade.amount, + is_short=trade.is_short, + open_date=trade.date_last_filled_utc + ) + trade.funding_fees = funding_fees + except ExchangeError: + logger.warning("Could not update funding fees for open trades.") else: return 0.0 @@ -671,14 +674,12 @@ class FreqtradeBot(LoggingMixin): if not stake_amount: return False - if pos_adjust: - logger.info(f"Position adjust: about to create a new order for {pair} with stake: " - f"{stake_amount} for {trade}") - else: - logger.info( - f"{name} signal found: about create a new trade for {pair} with stake_amount: " - f"{stake_amount} ...") - + msg = (f"Position adjust: about to create a new order for {pair} with stake: " + f"{stake_amount} for {trade}" if pos_adjust + else + f"{name} signal found: about create a new trade for {pair} with stake_amount: " + f"{stake_amount} ...") + logger.info(msg) amount = (stake_amount / enter_limit_requested) * leverage order_type = ordertype or self.strategy.order_types['entry'] @@ -741,8 +742,12 @@ class FreqtradeBot(LoggingMixin): # This is a new trade if trade is None: - funding_fees = self.exchange.get_funding_fees( - pair=pair, amount=amount, is_short=is_short, open_date=open_date) + try: + funding_fees = self.exchange.get_funding_fees( + pair=pair, amount=amount, is_short=is_short, open_date=open_date) + except ExchangeError: + logger.warning("Could not update funding fee.") + trade = Trade( pair=pair, base_currency=base_currency, @@ -1493,12 +1498,16 @@ class FreqtradeBot(LoggingMixin): :param exit_check: CheckTuple with signal and reason :return: True if it succeeds False """ - trade.funding_fees = self.exchange.get_funding_fees( - pair=trade.pair, - amount=trade.amount, - is_short=trade.is_short, - open_date=trade.date_last_filled_utc, - ) + try: + trade.funding_fees = self.exchange.get_funding_fees( + pair=trade.pair, + amount=trade.amount, + is_short=trade.is_short, + open_date=trade.date_last_filled_utc, + ) + except ExchangeError: + logger.warning("Could not update funding fee.") + exit_type = 'exit' exit_reason = exit_tag or exit_check.exit_reason if exit_check.exit_type in ( From 39b6cadd14e8253541c2836daed435059d20670d Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 8 Sep 2022 07:24:57 +0200 Subject: [PATCH 2/4] Test keyerror case for funding_Fee calculation --- freqtrade/exchange/exchange.py | 2 +- tests/exchange/test_exchange.py | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index be5af91db..33a56c530 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -2514,7 +2514,7 @@ class Exchange: funding_rates = candle_histories[funding_comb] mark_rates = candle_histories[mark_comb] except KeyError: - raise ExchangeError("Could not find funding rates") from None + raise ExchangeError("Could not find funding rates.") from None funding_mark_rates = self.combine_funding_and_mark( funding_rates=funding_rates, mark_rates=mark_rates) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 3b903f8ee..71690ecdf 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -11,8 +11,9 @@ import pytest from pandas import DataFrame from freqtrade.enums import CandleType, MarginMode, TradingMode -from freqtrade.exceptions import (DDosProtection, DependencyException, InvalidOrderException, - OperationalException, PricingError, TemporaryError) +from freqtrade.exceptions import (DDosProtection, DependencyException, ExchangeError, + InvalidOrderException, OperationalException, PricingError, + TemporaryError) from freqtrade.exchange import (Binance, Bittrex, Exchange, Kraken, amount_to_precision, date_minus_candles, market_is_active, price_to_precision, timeframe_to_minutes, timeframe_to_msecs, timeframe_to_next_date, @@ -4179,17 +4180,24 @@ def test__fetch_and_calculate_funding_fees( type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True}) type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True}) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange) + ex = get_patched_exchange(mocker, default_conf, api_mock, id=exchange) mocker.patch('freqtrade.exchange.Exchange.timeframes', PropertyMock( return_value=['1h', '4h', '8h'])) - funding_fees = exchange._fetch_and_calculate_funding_fees( + funding_fees = ex._fetch_and_calculate_funding_fees( pair='ADA/USDT', amount=amount, is_short=True, open_date=d1, close_date=d2) assert pytest.approx(funding_fees) == expected_fees # Fees for Longs are inverted - funding_fees = exchange._fetch_and_calculate_funding_fees( + funding_fees = ex._fetch_and_calculate_funding_fees( pair='ADA/USDT', amount=amount, is_short=False, open_date=d1, close_date=d2) assert pytest.approx(funding_fees) == -expected_fees + # Return empty "refresh_latest" + mocker.patch("freqtrade.exchange.Exchange.refresh_latest_ohlcv", return_value={}) + ex = get_patched_exchange(mocker, default_conf, api_mock, id=exchange) + with pytest.raises(ExchangeError, match="Could not find funding rates."): + ex._fetch_and_calculate_funding_fees( + pair='ADA/USDT', amount=amount, is_short=False, open_date=d1, close_date=d2) + @pytest.mark.parametrize('exchange,expected_fees', [ ('binance', -0.0009140999999999999), From 791f61c0899647f3c04ac072305e73187e266268 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 8 Sep 2022 07:13:15 +0000 Subject: [PATCH 3/4] Add test case for funding fee update failure --- freqtrade/freqtradebot.py | 2 -- tests/test_freqtradebot.py | 10 ++++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 61c323ed3..269c562d7 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -292,8 +292,6 @@ class FreqtradeBot(LoggingMixin): trade.funding_fees = funding_fees except ExchangeError: logger.warning("Could not update funding fees for open trades.") - else: - return 0.0 def startup_backpopulate_precision(self): diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 30b0b75b6..5f943504c 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -5430,6 +5430,16 @@ def test_update_funding_fees( )) +def test_update_funding_fees_error(mocker, default_conf, caplog): + mocker.patch('freqtrade.exchange.Exchange.get_funding_fees', side_effect=ExchangeError()) + default_conf['trading_mode'] = 'futures' + default_conf['margin_mode'] = 'isolated' + freqtrade = get_patched_freqtradebot(mocker, default_conf) + freqtrade.update_funding_fees() + + log_has("Could not update funding fees for open trades.", caplog) + + def test_position_adjust(mocker, default_conf_usdt, fee) -> None: patch_RPCManager(mocker) patch_exchange(mocker) From 9ef0ffe277e40159c207bb19782504d4bff492ff Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 8 Sep 2022 07:19:17 +0000 Subject: [PATCH 4/4] Update tests for funding-Fee exceptions --- freqtrade/freqtradebot.py | 3 ++- tests/test_freqtradebot.py | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 269c562d7..6c001a8d6 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -740,11 +740,12 @@ class FreqtradeBot(LoggingMixin): # This is a new trade if trade is None: + funding_fees = 0.0 try: funding_fees = self.exchange.get_funding_fees( pair=pair, amount=amount, is_short=is_short, open_date=open_date) except ExchangeError: - logger.warning("Could not update funding fee.") + logger.warning("Could not find funding fee.") trade = Trade( pair=pair, diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 5f943504c..565797d81 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -506,7 +506,7 @@ def test_create_trades_multiple_trades( def test_create_trades_preopen(default_conf_usdt, ticker_usdt, fee, mocker, - limit_buy_order_usdt_open) -> None: + limit_buy_order_usdt_open, caplog) -> None: patch_RPCManager(mocker) patch_exchange(mocker) default_conf_usdt['max_open_trades'] = 4 @@ -515,6 +515,7 @@ def test_create_trades_preopen(default_conf_usdt, ticker_usdt, fee, mocker, fetch_ticker=ticker_usdt, create_order=MagicMock(return_value=limit_buy_order_usdt_open), get_fee=fee, + get_funding_fees=MagicMock(side_effect=ExchangeError()), ) freqtrade = FreqtradeBot(default_conf_usdt) patch_get_signal(freqtrade) @@ -522,6 +523,7 @@ def test_create_trades_preopen(default_conf_usdt, ticker_usdt, fee, mocker, # Create 2 existing trades freqtrade.execute_entry('ETH/USDT', default_conf_usdt['stake_amount']) freqtrade.execute_entry('NEO/BTC', default_conf_usdt['stake_amount']) + assert log_has("Could not find funding fee.", caplog) assert len(Trade.get_open_trades()) == 2 # Change order_id for new orders @@ -3666,7 +3668,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit( (True, 29.70297029, 2.2, 2.3, -8.63762376, -0.1443212, 'loss'), ]) def test_execute_trade_exit_market_order( - default_conf_usdt, ticker_usdt, fee, is_short, current_rate, amount, + default_conf_usdt, ticker_usdt, fee, is_short, current_rate, amount, caplog, limit, profit_amount, profit_ratio, profit_or_loss, ticker_usdt_sell_up, mocker ) -> None: """ @@ -3694,6 +3696,7 @@ def test_execute_trade_exit_market_order( fetch_ticker=ticker_usdt, get_fee=fee, _is_dry_limit_order_filled=MagicMock(return_value=True), + get_funding_fees=MagicMock(side_effect=ExchangeError()), ) patch_whitelist(mocker, default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt) @@ -3719,6 +3722,7 @@ def test_execute_trade_exit_market_order( limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'], exit_check=ExitCheckTuple(exit_type=ExitType.ROI) ) + assert log_has("Could not update funding fee.", caplog) assert not trade.is_open assert pytest.approx(trade.close_profit) == profit_ratio