mirror of
https://github.com/freqtrade/freqtrade.git
synced 2025-11-29 08:33:07 +00:00
Merge pull request #9515 from stash86/bt-metrics
In partial exit, do full exit if remaining == 0
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user