diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index ed5eb9812..7242e9c90 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -760,22 +760,31 @@ The `position_adjustment_enable` strategy property enables the usage of `adjust_ For performance reasons, it's disabled by default and freqtrade will show a warning message on startup if enabled. `adjust_trade_position()` can be used to perform additional orders, for example to manage risk with DCA (Dollar Cost Averaging) or to increase or decrease positions. -`max_entry_position_adjustment` property is used to limit the number of additional entries per trade (on top of the first entry order) that the bot can execute. By default, the value is -1 which means the bot have no limit on number of adjustment entries. - -The strategy is expected to return a stake_amount (in stake currency) between `min_stake` and `max_stake` if and when an additional entry order should be made (position is increased -> buy order for long trades, sell order for short trades). -If there are not enough funds in the wallet (the return value is above `max_stake`) then the signal will be ignored. Additional orders also result in additional fees and those orders don't count towards `max_open_trades`. This callback is **not** called when there is an open order (either buy or sell) waiting for execution. `adjust_trade_position()` is called very frequently for the duration of a trade, so you must keep your implementation as performant as possible. -Additional entries are ignored once you have reached the maximum amount of extra entries that you have set on `max_entry_position_adjustment`, but the callback is called anyway looking for partial exits. - Position adjustments will always be applied in the direction of the trade, so a positive value will always increase your position (negative values will decrease your position), no matter if it's a long or short trade. Modifications to leverage are not possible, and the stake-amount returned is assumed to be before applying leverage. +### Increase position + +The strategy is expected to return a positive **stake_amount** (in stake currency) between `min_stake` and `max_stake` if and when an additional entry order should be made (position is increased -> buy order for long trades, sell order for short trades). + +If there are not enough funds in the wallet (the return value is above `max_stake`) then the signal will be ignored. +`max_entry_position_adjustment` property is used to limit the number of additional entries per trade (on top of the first entry order) that the bot can execute. By default, the value is -1 which means the bot have no limit on number of adjustment entries. + +Additional entries are ignored once you have reached the maximum amount of extra entries that you have set on `max_entry_position_adjustment`, but the callback is called anyway looking for partial exits. + +### Decrease position + +The strategy is expected to return a negative stake_amount (in stake currency) for a partial exit. +Returning the full owned stake at that point (based on the current price) (`-(trade.amount / trade.leverage) * current_exit_rate`) results in a full exit. +Returning a value more than the above (so remaining stake_amount would become negative) will result in the bot ignoring the signal. + !!! Note "About stake size" Using fixed stake size means it will be the amount used for the first order, just like without position adjustment. If you wish to buy additional orders with DCA, then make sure to leave enough funds in the wallet for that. diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index e09cf9bff..cfcafdc91 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -673,20 +673,13 @@ class FreqtradeBot(LoggingMixin): amount = self.exchange.amount_to_contract_precision( trade.pair, abs(float(FtPrecise(stake_amount * trade.leverage) / FtPrecise(current_exit_rate)))) - if amount > trade.amount: - # This is currently ineffective as remaining would become < min tradable - # Fixing this would require checking for 0.0 there - - # if we decide that this callback is allowed to "fully exit" - logger.info( - f"Adjusting amount to trade.amount as it is higher. {amount} > {trade.amount}") - amount = trade.amount if amount == 0.0: logger.info("Amount to exit is 0.0 due to exchange limits - not exiting.") return remaining = (trade.amount - amount) * current_exit_rate - if min_exit_stake and remaining < min_exit_stake: + if min_exit_stake and remaining != 0 and remaining < min_exit_stake: logger.info(f"Remaining amount of {remaining} would be smaller " f"than the minimum of {min_exit_stake}.") return diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 242323bec..e9d4cdd8a 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -531,7 +531,7 @@ class Backtesting: def _get_adjust_trade_entry_for_candle( self, trade: LocalTrade, row: Tuple, current_time: datetime ) -> LocalTrade: - current_rate = row[OPEN_IDX] + current_rate: float = row[OPEN_IDX] current_profit = trade.calc_profit_ratio(current_rate) min_stake = self.exchange.get_min_pair_stake_amount(trade.pair, current_rate, -0.1) max_stake = self.exchange.get_max_pair_stake_amount(trade.pair, current_rate) @@ -564,11 +564,8 @@ class Backtesting: self.precision_mode, trade.contract_size) if amount == 0.0: return trade - if amount > trade.amount: - # This is currently ineffective as remaining would become < min tradable - amount = trade.amount remaining = (trade.amount - amount) * current_rate - if remaining < min_stake: + if min_stake and remaining != 0 and remaining < min_stake: # Remaining stake is too low to be sold. return trade exit_ = ExitCheckTuple(ExitType.PARTIAL_EXIT) diff --git a/tests/test_integration.py b/tests/test_integration.py index 31b742273..3dcd91af0 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -650,28 +650,42 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera caplog.clear() # Sell more than what we got (we got ~20 coins left) - # First adjusts the amount to 20 - then rejects. + # Doesn't exit, as the amount is too high. freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-50) freqtrade.process() - assert log_has_re("Adjusting amount to trade.amount as it is higher.*", caplog) - assert log_has_re("Remaining amount of 0.0 would be smaller than the minimum of 10.", caplog) trade = Trade.get_trades().first() assert len(trade.orders) == 2 + + # Amount too low... + freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-(trade.stake_amount * 0.99)) + freqtrade.process() + + trade = Trade.get_trades().first() + assert len(trade.orders) == 2 + + # Amount exactly comes out as exactly 0 + freqtrade.strategy.adjust_trade_position = MagicMock( + return_value=-(trade.amount / trade.leverage * 2.02)) + freqtrade.process() + + trade = Trade.get_trades().first() + assert len(trade.orders) == 3 + assert trade.orders[-1].ft_order_side == 'sell' assert pytest.approx(trade.stake_amount) == 40.198 - assert trade.is_open + assert trade.is_open is False # use amount that would trunc to 0.0 once selling mocker.patch(f"{EXMS}.amount_to_contract_precision", lambda s, p, v: round(v, 1)) freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-0.01) freqtrade.process() trade = Trade.get_trades().first() - assert len(trade.orders) == 2 + assert len(trade.orders) == 3 assert trade.orders[-1].ft_order_side == 'sell' assert pytest.approx(trade.stake_amount) == 40.198 - assert trade.is_open + assert trade.is_open is False assert log_has_re('Amount to exit is 0.0 due to exchange limits - not exiting.', caplog) - expected_profit = starting_amount - 40.1980 + trade.realized_profit + expected_profit = starting_amount - 60 + trade.realized_profit assert pytest.approx(freqtrade.wallets.get_free('USDT')) == expected_profit if spot: assert pytest.approx(freqtrade.wallets.get_total('USDT')) == expected_profit