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
|
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,
|
trade, sell_rate, datetime.now(timezone.utc), buy, sell,
|
||||||
force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0
|
force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0
|
||||||
)
|
)
|
||||||
|
|
||||||
if should_sell.sell_flag:
|
if should_exit.sell_flag:
|
||||||
logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}')
|
logger.info(f'Executing Sell for {trade.pair}. Reason: {should_exit.sell_type}')
|
||||||
self.execute_sell(trade, sell_rate, should_sell)
|
self.execute_sell(trade, sell_rate, should_exit)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -332,12 +332,13 @@ class Backtesting:
|
|||||||
return sell_row[OPEN_IDX]
|
return sell_row[OPEN_IDX]
|
||||||
|
|
||||||
def _get_sell_trade_entry(self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]:
|
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_candle_time = sell_row[DATE_IDX].to_pydatetime()
|
||||||
sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], # type: ignore
|
sell = self.strategy.should_exit(
|
||||||
sell_candle_time, buy=sell_row[LONG_IDX],
|
trade, sell_row[OPEN_IDX], sell_candle_time, # type: ignore
|
||||||
sell=sell_row[ELONG_IDX],
|
enter_long=sell_row[LONG_IDX], enter_short=sell_row[SHORT_IDX],
|
||||||
low=sell_row[LOW_IDX], high=sell_row[HIGH_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:
|
if sell.sell_flag:
|
||||||
trade.close_date = sell_candle_time
|
trade.close_date = sell_candle_time
|
||||||
|
@ -614,8 +614,10 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool,
|
def should_exit(self, trade: Trade, rate: float, date: datetime, *,
|
||||||
sell: bool, low: float = None, high: float = None,
|
enter_long: bool, enter_short: bool,
|
||||||
|
exit_long: bool, exit_short: bool,
|
||||||
|
low: float = None, high: float = None,
|
||||||
force_stoploss: float = 0) -> SellCheckTuple:
|
force_stoploss: float = 0) -> SellCheckTuple:
|
||||||
"""
|
"""
|
||||||
This function evaluates if one of the conditions required to trigger a sell/exit_short
|
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
|
:param force_stoploss: Externally provided stoploss
|
||||||
:return: True if trade should be exited, False otherwise
|
: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_rate = rate
|
||||||
current_profit = trade.calc_profit_ratio(current_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)
|
current_profit = trade.calc_profit_ratio(current_rate)
|
||||||
|
|
||||||
# if enter signal and ignore_roi is set, we don't need to evaluate min_roi.
|
# 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,
|
and self.min_roi_reached(trade=trade, current_profit=current_profit,
|
||||||
current_time=date))
|
current_time=date))
|
||||||
|
|
||||||
@ -652,8 +658,8 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
if (self.sell_profit_only and current_profit <= self.sell_profit_offset):
|
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
|
# sell_profit_only and profit doesn't reach the offset - ignore sell signal
|
||||||
pass
|
pass
|
||||||
elif self.use_sell_signal and not buy:
|
elif self.use_sell_signal and not enter:
|
||||||
if sell:
|
if exit_:
|
||||||
sell_signal = SellType.SELL_SIGNAL
|
sell_signal = SellType.SELL_SIGNAL
|
||||||
else:
|
else:
|
||||||
trade_type = "exit_short" if trade.is_short else "sell"
|
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.
|
# Initiate stoploss with open_rate. Does nothing if stoploss is already set.
|
||||||
trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True)
|
trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True)
|
||||||
|
|
||||||
dir_correct = (
|
dir_correct = (trade.stop_loss < (low or current_rate)
|
||||||
trade.stop_loss < (low or current_rate) and not trade.is_short or
|
if not trade.is_short else
|
||||||
trade.stop_loss > (low or current_rate) and trade.is_short
|
trade.stop_loss > (high or current_rate)
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.use_custom_stoploss and dir_correct:
|
if self.use_custom_stoploss and dir_correct:
|
||||||
stop_loss_value = strategy_safe_wrapper(self.custom_stoploss, default_retval=None
|
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
|
sl_offset = self.trailing_stop_positive_offset
|
||||||
|
|
||||||
# Make sure current_profit is calculated using high for backtesting.
|
# 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)
|
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.
|
# 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
|
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_flag is False
|
||||||
assert res.sell_type == SellType.NONE
|
assert res.sell_type == SellType.NONE
|
||||||
|
|
||||||
strategy.custom_sell = MagicMock(return_value=True)
|
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_flag is True
|
||||||
assert res.sell_type == SellType.CUSTOM_SELL
|
assert res.sell_type == SellType.CUSTOM_SELL
|
||||||
assert res.sell_reason == 'custom_sell'
|
assert res.sell_reason == 'custom_sell'
|
||||||
|
|
||||||
strategy.custom_sell = MagicMock(return_value='hello world')
|
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_type == SellType.CUSTOM_SELL
|
||||||
assert res.sell_flag is True
|
assert res.sell_flag is True
|
||||||
assert res.sell_reason == 'hello world'
|
assert res.sell_reason == 'hello world'
|
||||||
|
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
strategy.custom_sell = MagicMock(return_value='h' * 100)
|
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_type == SellType.CUSTOM_SELL
|
||||||
assert res.sell_flag is True
|
assert res.sell_flag is True
|
||||||
assert res.sell_reason == 'h' * 64
|
assert res.sell_reason == 'h' * 64
|
||||||
|
Loading…
Reference in New Issue
Block a user