diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 9523ca92f..a76138bbf 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -362,11 +362,18 @@ class Backtesting: """ # Special handling if high or low hit STOP_LOSS or ROI if sell.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): - if trade.stop_loss > sell_row[HIGH_IDX]: - # our stoploss was already higher than candle high, - # possibly due to a cancelled trade exit. - # sell at open price. - return sell_row[OPEN_IDX] + if is_short: + if trade.stop_loss < sell_row[LOW_IDX]: + # our stoploss was already lower than candle high, + # possibly due to a cancelled trade exit. + # sell at open price. + return sell_row[OPEN_IDX] + else: + if trade.stop_loss > sell_row[HIGH_IDX]: + # our stoploss was already higher than candle high, + # possibly due to a cancelled trade exit. + # sell at open price. + return sell_row[OPEN_IDX] # Special case: trailing triggers within same candle as trade opened. Assume most # pessimistic price movement, which is moving just enough to arm stoploss and @@ -379,16 +386,29 @@ class Backtesting: and self.strategy.trailing_stop_positive ): # Worst case: price reaches stop_positive_offset and dives down. - stop_rate = (sell_row[OPEN_IDX] * + if is_short: + stop_rate = (sell_row[OPEN_IDX] * + (1 - abs(self.strategy.trailing_stop_positive_offset) + + abs(self.strategy.trailing_stop_positive))) + else: + stop_rate = (sell_row[OPEN_IDX] * (1 + abs(self.strategy.trailing_stop_positive_offset) - abs(self.strategy.trailing_stop_positive))) else: # Worst case: price ticks tiny bit above open and dives down. - stop_rate = sell_row[OPEN_IDX] * (1 - abs(trade.stop_loss_pct / leverage)) - assert stop_rate < sell_row[HIGH_IDX] + if is_short: + stop_rate = sell_row[OPEN_IDX] * (1 + abs(trade.stop_loss_pct / leverage)) + assert stop_rate > sell_row[HIGH_IDX] + else: + stop_rate = sell_row[OPEN_IDX] * (1 - abs(trade.stop_loss_pct / leverage)) + assert stop_rate < sell_row[HIGH_IDX] + # Limit lower-end to candle low to avoid sells below the low. # This still remains "worst case" - but "worst realistic case". - return max(sell_row[LOW_IDX], stop_rate) + if is_short: + return min(sell_row[HIGH_IDX], stop_rate) + else: + return max(sell_row[LOW_IDX], stop_rate) # Set close_rate to stoploss return trade.stop_loss @@ -402,32 +422,60 @@ class Backtesting: return sell_row[OPEN_IDX] # - (Expected abs profit + open_rate + open_fee) / (fee_close -1) - close_rate = - (trade.open_rate * roi / leverage + trade.open_rate * + if is_short: + close_rate = (trade.open_rate * + (1 - trade.fee_open) - trade.open_rate * roi / leverage) / (trade.fee_close + 1) + if (trade_dur > 0 and trade_dur == roi_entry + and roi_entry % self.timeframe_min == 0 + and sell_row[OPEN_IDX] < close_rate): + # new ROI entry came into effect. + # use Open rate if open_rate > calculated sell rate + return sell_row[OPEN_IDX] + else: + close_rate = - (trade.open_rate * roi / leverage + trade.open_rate * (1 + trade.fee_open)) / (trade.fee_close - 1) - if (trade_dur > 0 and trade_dur == roi_entry - and roi_entry % self.timeframe_min == 0 - and sell_row[OPEN_IDX] > close_rate): - # new ROI entry came into effect. - # use Open rate if open_rate > calculated sell rate - return sell_row[OPEN_IDX] + if (trade_dur > 0 and trade_dur == roi_entry + and roi_entry % self.timeframe_min == 0 + and sell_row[OPEN_IDX] > close_rate): + # new ROI entry came into effect. + # use Open rate if open_rate > calculated sell rate + return sell_row[OPEN_IDX] + + if is_short: + if ( + trade_dur == 0 + # Red candle (for longs), TODO: green candle (for shorts) + and sell_row[OPEN_IDX] < sell_row[CLOSE_IDX] # Red candle + and trade.open_rate > sell_row[OPEN_IDX] # trade-open below open_rate + and close_rate < sell_row[CLOSE_IDX] + ): + # ROI on opening candles with custom pricing can only + # trigger if the entry was at Open or lower. + # details: https: // github.com/freqtrade/freqtrade/issues/6261 + # If open_rate is < open, only allow sells below the close on red candles. + raise ValueError("Opening candle ROI on red candles.") + else: + if ( + trade_dur == 0 + # Red candle (for longs), TODO: green candle (for shorts) + and sell_row[OPEN_IDX] > sell_row[CLOSE_IDX] # Red candle + and trade.open_rate < sell_row[OPEN_IDX] # trade-open below open_rate + and close_rate > sell_row[CLOSE_IDX] + ): + # ROI on opening candles with custom pricing can only + # trigger if the entry was at Open or lower. + # details: https: // github.com/freqtrade/freqtrade/issues/6261 + # If open_rate is < open, only allow sells below the close on red candles. + raise ValueError("Opening candle ROI on red candles.") - if ( - trade_dur == 0 - # Red candle (for longs), TODO: green candle (for shorts) - and sell_row[OPEN_IDX] > sell_row[CLOSE_IDX] # Red candle - and trade.open_rate < sell_row[OPEN_IDX] # trade-open below open_rate - and close_rate > sell_row[CLOSE_IDX] - ): - # ROI on opening candles with custom pricing can only - # trigger if the entry was at Open or lower. - # details: https: // github.com/freqtrade/freqtrade/issues/6261 - # If open_rate is < open, only allow sells below the close on red candles. - raise ValueError("Opening candle ROI on red candles.") # Use the maximum between close_rate and low as we # cannot sell outside of a candle. # Applies when a new ROI setting comes in place and the whole candle is above that. - return min(max(close_rate, sell_row[LOW_IDX]), sell_row[HIGH_IDX]) + if is_short: + return max(min(close_rate, sell_row[HIGH_IDX]), sell_row[LOW_IDX]) + else: + return min(max(close_rate, sell_row[LOW_IDX]), sell_row[HIGH_IDX]) else: # This should not be reached... @@ -610,7 +658,10 @@ class Backtesting: proposed_rate=propose_rate, entry_tag=entry_tag) # default value is the open rate # We can't place orders higher than current high (otherwise it'd be a stop limit buy) # which freqtrade does not support in live. - propose_rate = min(propose_rate, row[HIGH_IDX]) + if direction == "short": + propose_rate = max(propose_rate, row[LOW_IDX]) + else: + propose_rate = min(propose_rate, row[HIGH_IDX]) min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, propose_rate, -0.05) or 0 max_stake_amount = self.exchange.get_max_pair_stake_amount(pair, propose_rate) @@ -700,13 +751,13 @@ class Backtesting: trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss, initial=True) - trade.set_isolated_liq(self.exchange.get_liquidation_price( - pair=pair, - open_rate=propose_rate, - amount=amount, - leverage=leverage, - is_short=is_short, - )) + # trade.set_isolated_liq(self.exchange.get_liquidation_price( + # pair=pair, + # open_rate=propose_rate, + # amount=amount, + # leverage=leverage, + # is_short=is_short, + # )) order = Order( id=self.order_id_counter,