diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 5cc7b9776..24dad1f56 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -593,7 +593,7 @@ Additional orders also result in additional fees and those orders don't count to This callback is **not** called when there is an open order (either buy or sell) waiting for execution, or when you have reached the maximum amount of extra buys that you have set on `max_entry_position_adjustment`. `adjust_trade_position()` is called very frequently for the duration of a trade, so you must keep your implementation as performant as possible. -Position adjustments will always be applied in the direction of the trade, so a positive value will always increase your position, no matter if it's a long or short trade. +Position adjustments will always be applied in the direction of the trade, so a positive value will always increase your position, no matter if it's a long or short trade. !!! Note "About stake size" Using fixed stake size means it will be the amount used for the first order, just like without position adjustment. diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 20565120c..554837860 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -533,7 +533,7 @@ class FreqtradeBot(LoggingMixin): if stake_amount is not None and stake_amount > 0.0: # We should increase our position - self.execute_entry(trade.pair, stake_amount, trade=trade) + self.execute_entry(trade.pair, stake_amount, trade=trade, is_short=trade.is_short) if stake_amount is not None and stake_amount < 0.0: # We should decrease our position diff --git a/tests/strategy/strats/strategy_test_v3.py b/tests/strategy/strats/strategy_test_v3.py index a056b316c..0b73c1271 100644 --- a/tests/strategy/strats/strategy_test_v3.py +++ b/tests/strategy/strats/strategy_test_v3.py @@ -183,7 +183,7 @@ class StrategyTestV3(IStrategy): current_profit: float, min_stake: float, max_stake: float, **kwargs): if current_profit < -0.0075: - orders = trade.select_filled_orders('buy') + orders = trade.select_filled_orders(trade.enter_side) return round(orders[0].cost, 0) return None diff --git a/tests/test_integration.py b/tests/test_integration.py index bb14aa03d..846d7a5db 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -214,6 +214,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: + # TODO-lev: this should also check with different leverages per entry order! default_conf_usdt['position_adjustment_enable'] = True freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) @@ -231,13 +232,13 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: assert len(Trade.get_trades().all()) == 1 trade = Trade.get_trades().first() assert len(trade.orders) == 1 - assert trade.stake_amount == 60 + assert pytest.approx(trade.stake_amount) == 60 assert trade.open_rate == 2.0 # No adjustment freqtrade.process() trade = Trade.get_trades().first() assert len(trade.orders) == 1 - assert trade.stake_amount == 60 + assert pytest.approx(trade.stake_amount) == 60 # Reduce bid amount ticker_usdt_modif = ticker_usdt.return_value @@ -266,6 +267,7 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: assert trade.amount == trade.orders[0].amount + trade.orders[1].amount assert trade.nr_of_successful_buys == 2 + assert trade.nr_of_successful_entries == 2 # Sell patch_get_signal(freqtrade, enter_long=False, exit_long=True) @@ -280,3 +282,76 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: assert trade.orders[2].amount == trade.amount assert trade.nr_of_successful_buys == 2 + assert trade.nr_of_successful_entries == 2 + + +def test_dca_short(default_conf_usdt, ticker_usdt, fee, mocker) -> None: + # TODO-lev: this should also check with different leverages per entry order! + default_conf_usdt['position_adjustment_enable'] = True + + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + fetch_ticker=ticker_usdt, + get_fee=fee, + amount_to_precision=lambda s, x, y: y, + price_to_precision=lambda s, x, y: y, + ) + + patch_get_signal(freqtrade, enter_long=False, enter_short=True) + freqtrade.enter_positions() + + assert len(Trade.get_trades().all()) == 1 + trade = Trade.get_trades().first() + assert len(trade.orders) == 1 + assert pytest.approx(trade.stake_amount) == 60 + assert trade.open_rate == 2.02 + # No adjustment + freqtrade.process() + trade = Trade.get_trades().first() + assert len(trade.orders) == 1 + assert pytest.approx(trade.stake_amount) == 60 + + # Reduce bid amount + ticker_usdt_modif = ticker_usdt.return_value + ticker_usdt_modif['ask'] = ticker_usdt_modif['ask'] * 1.015 + ticker_usdt_modif['bid'] = ticker_usdt_modif['bid'] * 1.0125 + mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value=ticker_usdt_modif) + + # additional buy order + freqtrade.process() + trade = Trade.get_trades().first() + assert len(trade.orders) == 2 + for o in trade.orders: + assert o.status == "closed" + assert pytest.approx(trade.stake_amount) == 120 + + # Open-rate averaged between 2.0 and 2.0 * 1.015 + assert trade.open_rate >= 2.02 + assert trade.open_rate < 2.02 * 1.015 + + # No action - profit raised above 1% (the bar set in the strategy). + freqtrade.process() + trade = Trade.get_trades().first() + assert len(trade.orders) == 2 + assert pytest.approx(trade.stake_amount) == 120 + # assert trade.orders[0].amount == 30 + assert trade.orders[1].amount == 60 / ticker_usdt_modif['ask'] + + assert trade.amount == trade.orders[0].amount + trade.orders[1].amount + assert trade.nr_of_successful_entries == 2 + + # Buy + patch_get_signal(freqtrade, enter_long=False, exit_short=True) + freqtrade.process() + trade = Trade.get_trades().first() + assert trade.is_open is False + # assert trade.orders[0].amount == 30 + assert trade.orders[0].side == 'sell' + assert trade.orders[1].amount == 60 / ticker_usdt_modif['ask'] + # Sold everything + assert trade.orders[-1].side == 'buy' + assert trade.orders[2].amount == trade.amount + + assert trade.nr_of_successful_entries == 2 + assert trade.nr_of_successful_exits == 1