Add short-exit logic to backtesting
This commit is contained in:
parent
eb71ee847c
commit
b40f985b13
@ -856,14 +856,14 @@ class FreqtradeBot(LoggingMixin):
|
||||
"""
|
||||
Check and execute sell
|
||||
"""
|
||||
should_sell = self.strategy.should_sell(
|
||||
should_exit: SellCheckTuple = self.strategy.should_exit(
|
||||
trade, sell_rate, datetime.now(timezone.utc), buy, sell,
|
||||
force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0
|
||||
)
|
||||
|
||||
if should_sell.sell_flag:
|
||||
logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}')
|
||||
self.execute_sell(trade, sell_rate, should_sell)
|
||||
if should_exit.sell_flag:
|
||||
logger.info(f'Executing Sell for {trade.pair}. Reason: {should_exit.sell_type}')
|
||||
self.execute_sell(trade, sell_rate, should_exit)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -332,12 +332,13 @@ class Backtesting:
|
||||
return sell_row[OPEN_IDX]
|
||||
|
||||
def _get_sell_trade_entry(self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]:
|
||||
# TODO: short exits
|
||||
sell_candle_time = sell_row[DATE_IDX].to_pydatetime()
|
||||
sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], # type: ignore
|
||||
sell_candle_time, buy=sell_row[LONG_IDX],
|
||||
sell=sell_row[ELONG_IDX],
|
||||
low=sell_row[LOW_IDX], high=sell_row[HIGH_IDX])
|
||||
sell = self.strategy.should_exit(
|
||||
trade, sell_row[OPEN_IDX], sell_candle_time, # type: ignore
|
||||
enter_long=sell_row[LONG_IDX], enter_short=sell_row[SHORT_IDX],
|
||||
exit_long=sell_row[ELONG_IDX], exit_short=sell_row[ESHORT_IDX],
|
||||
low=sell_row[LOW_IDX], high=sell_row[HIGH_IDX]
|
||||
)
|
||||
|
||||
if sell.sell_flag:
|
||||
trade.close_date = sell_candle_time
|
||||
|
@ -614,8 +614,10 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
else:
|
||||
return False
|
||||
|
||||
def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool,
|
||||
sell: bool, low: float = None, high: float = None,
|
||||
def should_exit(self, trade: Trade, rate: float, date: datetime, *,
|
||||
enter_long: bool, enter_short: bool,
|
||||
exit_long: bool, exit_short: bool,
|
||||
low: float = None, high: float = None,
|
||||
force_stoploss: float = 0) -> SellCheckTuple:
|
||||
"""
|
||||
This function evaluates if one of the conditions required to trigger a sell/exit_short
|
||||
@ -625,6 +627,10 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
:param force_stoploss: Externally provided stoploss
|
||||
:return: True if trade should be exited, False otherwise
|
||||
"""
|
||||
|
||||
enter = enter_short if trade.is_short else enter_long
|
||||
exit_ = exit_short if trade.is_short else exit_long
|
||||
|
||||
current_rate = rate
|
||||
current_profit = trade.calc_profit_ratio(current_rate)
|
||||
|
||||
@ -639,7 +645,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
current_profit = trade.calc_profit_ratio(current_rate)
|
||||
|
||||
# if enter signal and ignore_roi is set, we don't need to evaluate min_roi.
|
||||
roi_reached = (not (buy and self.ignore_roi_if_buy_signal)
|
||||
roi_reached = (not (enter and self.ignore_roi_if_buy_signal)
|
||||
and self.min_roi_reached(trade=trade, current_profit=current_profit,
|
||||
current_time=date))
|
||||
|
||||
@ -652,8 +658,8 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
if (self.sell_profit_only and current_profit <= self.sell_profit_offset):
|
||||
# sell_profit_only and profit doesn't reach the offset - ignore sell signal
|
||||
pass
|
||||
elif self.use_sell_signal and not buy:
|
||||
if sell:
|
||||
elif self.use_sell_signal and not enter:
|
||||
if exit_:
|
||||
sell_signal = SellType.SELL_SIGNAL
|
||||
else:
|
||||
trade_type = "exit_short" if trade.is_short else "sell"
|
||||
@ -712,10 +718,10 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
# Initiate stoploss with open_rate. Does nothing if stoploss is already set.
|
||||
trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True)
|
||||
|
||||
dir_correct = (
|
||||
trade.stop_loss < (low or current_rate) and not trade.is_short or
|
||||
trade.stop_loss > (low or current_rate) and trade.is_short
|
||||
)
|
||||
dir_correct = (trade.stop_loss < (low or current_rate)
|
||||
if not trade.is_short else
|
||||
trade.stop_loss > (high or current_rate)
|
||||
)
|
||||
|
||||
if self.use_custom_stoploss and dir_correct:
|
||||
stop_loss_value = strategy_safe_wrapper(self.custom_stoploss, default_retval=None
|
||||
@ -735,6 +741,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
sl_offset = self.trailing_stop_positive_offset
|
||||
|
||||
# Make sure current_profit is calculated using high for backtesting.
|
||||
# TODO-lev: Check this function - high / low usage must be inversed for short trades!
|
||||
high_profit = current_profit if not high else trade.calc_profit_ratio(high)
|
||||
|
||||
# Don't update stoploss if trailing_only_offset_is_reached is true.
|
||||
|
@ -452,27 +452,39 @@ def test_custom_sell(default_conf, fee, caplog) -> None:
|
||||
)
|
||||
|
||||
now = arrow.utcnow().datetime
|
||||
res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
|
||||
res = strategy.should_exit(trade, 1, now,
|
||||
enter_long=False, enter_short=False,
|
||||
exit_long=False, exit_short=False,
|
||||
low=None, high=None)
|
||||
|
||||
assert res.sell_flag is False
|
||||
assert res.sell_type == SellType.NONE
|
||||
|
||||
strategy.custom_sell = MagicMock(return_value=True)
|
||||
res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
|
||||
res = strategy.should_exit(trade, 1, now,
|
||||
enter_long=False, enter_short=False,
|
||||
exit_long=False, exit_short=False,
|
||||
low=None, high=None)
|
||||
assert res.sell_flag is True
|
||||
assert res.sell_type == SellType.CUSTOM_SELL
|
||||
assert res.sell_reason == 'custom_sell'
|
||||
|
||||
strategy.custom_sell = MagicMock(return_value='hello world')
|
||||
|
||||
res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
|
||||
res = strategy.should_exit(trade, 1, now,
|
||||
enter_long=False, enter_short=False,
|
||||
exit_long=False, exit_short=False,
|
||||
low=None, high=None)
|
||||
assert res.sell_type == SellType.CUSTOM_SELL
|
||||
assert res.sell_flag is True
|
||||
assert res.sell_reason == 'hello world'
|
||||
|
||||
caplog.clear()
|
||||
strategy.custom_sell = MagicMock(return_value='h' * 100)
|
||||
res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
|
||||
res = strategy.should_exit(trade, 1, now,
|
||||
enter_long=False, enter_short=False,
|
||||
exit_long=False, exit_short=False,
|
||||
low=None, high=None)
|
||||
assert res.sell_type == SellType.CUSTOM_SELL
|
||||
assert res.sell_flag is True
|
||||
assert res.sell_reason == 'h' * 64
|
||||
|
Loading…
Reference in New Issue
Block a user