Add short-exit logic to backtesting

This commit is contained in:
Matthias 2021-08-24 19:55:00 +02:00
parent eb71ee847c
commit b40f985b13
4 changed files with 42 additions and 22 deletions

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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