Optimize the code. Fix stop_rate judgment error

This commit is contained in:
adriance 2022-03-15 12:04:02 +08:00
parent 7dd57e8c04
commit 7059892304

View File

@ -358,168 +358,122 @@ class Backtesting:
Get close rate for backtesting result Get close rate for backtesting result
""" """
# Special handling if high or low hit STOP_LOSS or ROI # Special handling if high or low hit STOP_LOSS or ROI
is_short = trade.is_short or False
if is_short:
return self._get_short_close_rate(sell_row, trade, sell, trade_dur)
else:
return self._get_long_close_rate(sell_row, trade, sell, trade_dur)
def _get_short_close_rate(self, sell_row: Tuple, trade: LocalTrade, sell: SellCheckTuple,
trade_dur: int) -> float:
leverage = trade.leverage or 1.0
if sell.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): if sell.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
return self._get_close_rate_for_stoploss(sell_row, trade, sell, trade_dur)
elif sell.sell_type == (SellType.ROI):
return self._get_close_rate_for_roi(sell_row, trade, sell, trade_dur)
else:
return sell_row[OPEN_IDX]
def _get_close_rate_for_stoploss(self, sell_row: Tuple, trade: LocalTrade, sell: SellCheckTuple,
trade_dur: int) -> float:
# our stoploss was already lower than candle high,
# possibly due to a cancelled trade exit.
# sell at open price.
is_short = trade.is_short or False
leverage = trade.leverage or 1.0
side_1 = -1 if is_short else 1
if is_short:
if trade.stop_loss < sell_row[LOW_IDX]: if trade.stop_loss < sell_row[LOW_IDX]:
# our stoploss was already lower than candle high, return sell_row[OPEN_IDX]
# possibly due to a cancelled trade exit. else:
# sell at open price. if trade.stop_loss > sell_row[HIGH_IDX]:
return sell_row[OPEN_IDX] return sell_row[OPEN_IDX]
# Special case: trailing triggers within same candle as trade opened. Assume most # Special case: trailing triggers within same candle as trade opened. Assume most
# pessimistic price movement, which is moving just enough to arm stoploss and # pessimistic price movement, which is moving just enough to arm stoploss and
# immediately going down to stop price. # immediately going down to stop price.
if sell.sell_type == SellType.TRAILING_STOP_LOSS and trade_dur == 0: if sell.sell_type == SellType.TRAILING_STOP_LOSS and trade_dur == 0:
if ( if (
not self.strategy.use_custom_stoploss and self.strategy.trailing_stop not self.strategy.use_custom_stoploss and self.strategy.trailing_stop
and self.strategy.trailing_only_offset_is_reached and self.strategy.trailing_only_offset_is_reached
and self.strategy.trailing_stop_positive_offset is not None and self.strategy.trailing_stop_positive_offset is not None
and self.strategy.trailing_stop_positive and self.strategy.trailing_stop_positive
): ):
# Worst case: price reaches stop_positive_offset and dives down. # Worst case: price reaches stop_positive_offset and dives down.
stop_rate = (sell_row[OPEN_IDX] * stop_rate = (sell_row[OPEN_IDX] *
(1 - abs(self.strategy.trailing_stop_positive_offset) + (1 + side_1 * abs(self.strategy.trailing_stop_positive_offset) +
abs(self.strategy.trailing_stop_positive))) abs(self.strategy.trailing_stop_positive / leverage)))
else:
# Worst case: price ticks tiny bit above open and dives down.
stop_rate = sell_row[OPEN_IDX] * (1 -
side_1 * abs(trade.stop_loss_pct / leverage))
if is_short:
assert stop_rate > sell_row[LOW_IDX]
else: else:
# Worst case: price ticks tiny bit above open and dives down. assert stop_rate < sell_row[HIGH_IDX]
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. # Limit lower-end to candle low to avoid sells below the low.
# This still remains "worst case" - but "worst realistic case". # This still remains "worst case" - but "worst realistic case".
if is_short:
return min(sell_row[HIGH_IDX], stop_rate) return min(sell_row[HIGH_IDX], stop_rate)
else:
return max(sell_row[LOW_IDX], stop_rate)
# Set close_rate to stoploss # Set close_rate to stoploss
return trade.stop_loss return trade.stop_loss
elif sell.sell_type == (SellType.ROI):
roi_entry, roi = self.strategy.min_roi_reached_entry(trade_dur)
if roi is not None and roi_entry is not None:
if roi == -1 and roi_entry % self.timeframe_min == 0:
# When forceselling with ROI=-1, the roi time will always be equal to trade_dur.
# If that entry is a multiple of the timeframe (so on candle open)
# - we'll use open instead of close
return sell_row[OPEN_IDX]
# - (Expected abs profit - open_rate - open_fee) / (fee_close -1) def _get_close_rate_for_roi(self, sell_row: Tuple, trade: LocalTrade, sell: SellCheckTuple,
open_fee_rate = trade.open_rate * (1 - trade.fee_open) trade_dur: int) -> float:
roi_rate = trade.open_rate * roi / leverage is_short = trade.is_short or False
close_rate = (roi_rate - open_fee_rate) / (trade.fee_close + 1) leverage = trade.leverage or 1.0
if (trade_dur > 0 and trade_dur == roi_entry side_1 = -1 if is_short else 1
and roi_entry % self.timeframe_min == 0 roi_entry, roi = self.strategy.min_roi_reached_entry(trade_dur)
and sell_row[OPEN_IDX] < close_rate): if roi is not None and roi_entry is not None:
# new ROI entry came into effect. if roi == -1 and roi_entry % self.timeframe_min == 0:
# use Open rate if open_rate > calculated sell rate # When forceselling with ROI=-1, the roi time will always be equal to trade_dur.
return sell_row[OPEN_IDX] # If that entry is a multiple of the timeframe (so on candle open)
# - we'll use open instead of close
return sell_row[OPEN_IDX]
if ( # - (Expected abs profit - open_rate - open_fee) / (fee_close -1)
trade_dur == 0 roi_rate = trade.open_rate * roi / leverage
open_fee_rate = side_1 * trade.open_rate * (1 + side_1 * trade.fee_open)
close_rate = -side_1 * (roi_rate + open_fee_rate) / (trade.fee_close - side_1 * 1)
if is_short:
is_new_roi = sell_row[OPEN_IDX] < close_rate
else:
is_new_roi = sell_row[OPEN_IDX] > close_rate
if (trade_dur > 0 and trade_dur == roi_entry
and roi_entry % self.timeframe_min == 0
and is_new_roi):
# 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 (
(
is_short
# Red candle (for longs), TODO: green candle (for shorts) # Red candle (for longs), TODO: green candle (for shorts)
and sell_row[OPEN_IDX] < sell_row[CLOSE_IDX] # Red candle 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 trade.open_rate > sell_row[OPEN_IDX] # trade-open below open_rate
and close_rate < sell_row[CLOSE_IDX] and close_rate < sell_row[CLOSE_IDX]
): )
# ROI on opening candles with custom pricing can only or
# trigger if the entry was at Open or lower. (
# details: https: // github.com/freqtrade/freqtrade/issues/6261 not is_short
# 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 max(min(close_rate, sell_row[HIGH_IDX]), sell_row[LOW_IDX])
else:
# This should not be reached...
return sell_row[OPEN_IDX]
else:
return sell_row[OPEN_IDX]
def _get_long_close_rate(self, sell_row: Tuple, trade: LocalTrade, sell: SellCheckTuple,
trade_dur: int) -> float:
leverage = trade.leverage or 1.0
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]
# Special case: trailing triggers within same candle as trade opened. Assume most
# pessimistic price movement, which is moving just enough to arm stoploss and
# immediately going down to stop price.
if sell.sell_type == SellType.TRAILING_STOP_LOSS and trade_dur == 0:
if (
not self.strategy.use_custom_stoploss and self.strategy.trailing_stop
and self.strategy.trailing_only_offset_is_reached
and self.strategy.trailing_stop_positive_offset is not None
and self.strategy.trailing_stop_positive
):
# Worst case: price reaches stop_positive_offset and dives down.
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]
# 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)
# Set close_rate to stoploss
return trade.stop_loss
elif sell.sell_type == (SellType.ROI):
roi_entry, roi = self.strategy.min_roi_reached_entry(trade_dur)
if roi is not None and roi_entry is not None:
if roi == -1 and roi_entry % self.timeframe_min == 0:
# When forceselling with ROI=-1, the roi time will always be equal to trade_dur.
# If that entry is a multiple of the timeframe (so on candle open)
# - we'll use open instead of close
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 *
(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
# Red candle (for longs), TODO: green candle (for shorts) # Red candle (for longs), TODO: green candle (for shorts)
and sell_row[OPEN_IDX] > sell_row[CLOSE_IDX] # Red candle 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 trade.open_rate < sell_row[OPEN_IDX] # trade-open below open_rate
and close_rate > sell_row[CLOSE_IDX] 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. # ROI on opening candles with custom pricing can only
# details: https: // github.com/freqtrade/freqtrade/issues/6261 # trigger if the entry was at Open or lower.
# If open_rate is < open, only allow sells below the close on red candles. # details: https: // github.com/freqtrade/freqtrade/issues/6261
raise ValueError("Opening candle ROI on red candles.") # 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 # Use the maximum between close_rate and low as we
# cannot sell outside of a candle. # cannot sell outside of a candle.
# Applies when a new ROI setting comes in place and the whole candle is above that. # Applies when a new ROI setting comes in place and the whole candle is above that.
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]) return min(max(close_rate, sell_row[LOW_IDX]), sell_row[HIGH_IDX])
else:
# This should not be reached...
return sell_row[OPEN_IDX]
else: else:
# This should not be reached...
return sell_row[OPEN_IDX] return sell_row[OPEN_IDX]
def _get_adjust_trade_entry_for_candle(self, trade: LocalTrade, row: Tuple def _get_adjust_trade_entry_for_candle(self, trade: LocalTrade, row: Tuple