Merge pull request #7146 from freqtrade/fix/liquidation

Update liquidation price handling
This commit is contained in:
Matthias
2022-07-31 08:09:54 +02:00
committed by GitHub
9 changed files with 142 additions and 106 deletions

View File

@@ -9,6 +9,7 @@ class ExitType(Enum):
STOP_LOSS = "stop_loss"
STOPLOSS_ON_EXCHANGE = "stoploss_on_exchange"
TRAILING_STOP_LOSS = "trailing_stop_loss"
LIQUIDATION = "liquidation"
EXIT_SIGNAL = "exit_signal"
FORCE_EXIT = "force_exit"
EMERGENCY_EXIT = "emergency_exit"

View File

@@ -1016,7 +1016,7 @@ class FreqtradeBot(LoggingMixin):
trade.stoploss_order_id = None
logger.error(f'Unable to place a stoploss order on exchange. {e}')
logger.warning('Exiting the trade forcefully')
self.execute_trade_exit(trade, trade.stop_loss, exit_check=ExitCheckTuple(
self.execute_trade_exit(trade, stop_price, exit_check=ExitCheckTuple(
exit_type=ExitType.EMERGENCY_EXIT))
except ExchangeError:
@@ -1086,7 +1086,7 @@ class FreqtradeBot(LoggingMixin):
if (trade.is_open
and stoploss_order
and stoploss_order['status'] in ('canceled', 'cancelled')):
if self.create_stoploss_order(trade=trade, stop_price=trade.stop_loss):
if self.create_stoploss_order(trade=trade, stop_price=trade.stoploss_or_liquidation):
return False
else:
trade.stoploss_order_id = None
@@ -1115,7 +1115,7 @@ class FreqtradeBot(LoggingMixin):
:param order: Current on exchange stoploss order
:return: None
"""
stoploss_norm = self.exchange.price_to_precision(trade.pair, trade.stop_loss)
stoploss_norm = self.exchange.price_to_precision(trade.pair, trade.stoploss_or_liquidation)
if self.exchange.stoploss_adjust(stoploss_norm, order, side=trade.exit_side):
# we check if the update is necessary
@@ -1133,7 +1133,7 @@ class FreqtradeBot(LoggingMixin):
f"for pair {trade.pair}")
# Create new stoploss order
if not self.create_stoploss_order(trade=trade, stop_price=trade.stop_loss):
if not self.create_stoploss_order(trade=trade, stop_price=stoploss_norm):
logger.warning(f"Could not create trailing stoploss order "
f"for pair {trade.pair}.")
@@ -1432,14 +1432,15 @@ class FreqtradeBot(LoggingMixin):
)
exit_type = 'exit'
exit_reason = exit_tag or exit_check.exit_reason
if exit_check.exit_type in (ExitType.STOP_LOSS, ExitType.TRAILING_STOP_LOSS):
if exit_check.exit_type in (
ExitType.STOP_LOSS, ExitType.TRAILING_STOP_LOSS, ExitType.LIQUIDATION):
exit_type = 'stoploss'
# if stoploss is on exchange and we are on dry_run mode,
# we consider the sell price stop price
if (self.config['dry_run'] and exit_type == 'stoploss'
and self.strategy.order_types['stoploss_on_exchange']):
limit = trade.stop_loss
limit = trade.stoploss_or_liquidation
# set custom_exit_price if available
proposed_limit_rate = limit
@@ -1464,11 +1465,12 @@ class FreqtradeBot(LoggingMixin):
amount = self._safe_exit_amount(trade.pair, trade.amount)
time_in_force = self.strategy.order_time_in_force['exit']
if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)(
if (exit_check.exit_type != ExitType.LIQUIDATION and not strategy_safe_wrapper(
self.strategy.confirm_trade_exit, default_retval=True)(
pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit,
time_in_force=time_in_force, exit_reason=exit_reason,
sell_reason=exit_reason, # sellreason -> compatibility
current_time=datetime.now(timezone.utc)):
current_time=datetime.now(timezone.utc))):
logger.info(f"User denied exit for {trade.pair}.")
return False
@@ -1661,7 +1663,7 @@ class FreqtradeBot(LoggingMixin):
trade = self.cancel_stoploss_on_exchange(trade)
# TODO: Margin will need to use interest_rate as well.
# interest_rate = self.exchange.get_interest_rate()
trade.set_isolated_liq(self.exchange.get_liquidation_price(
trade.set_liquidation_price(self.exchange.get_liquidation_price(
leverage=trade.leverage,
pair=trade.pair,
amount=trade.amount,

View File

@@ -381,7 +381,8 @@ class Backtesting:
Get close rate for backtesting result
"""
# Special handling if high or low hit STOP_LOSS or ROI
if exit.exit_type in (ExitType.STOP_LOSS, ExitType.TRAILING_STOP_LOSS):
if exit.exit_type in (
ExitType.STOP_LOSS, ExitType.TRAILING_STOP_LOSS, ExitType.LIQUIDATION):
return self._get_close_rate_for_stoploss(row, trade, exit, trade_dur)
elif exit.exit_type == (ExitType.ROI):
return self._get_close_rate_for_roi(row, trade, exit, trade_dur)
@@ -396,11 +397,16 @@ class Backtesting:
is_short = trade.is_short or False
leverage = trade.leverage or 1.0
side_1 = -1 if is_short else 1
if exit.exit_type == ExitType.LIQUIDATION and trade.liquidation_price:
stoploss_value = trade.liquidation_price
else:
stoploss_value = trade.stop_loss
if is_short:
if trade.stop_loss < row[LOW_IDX]:
if stoploss_value < row[LOW_IDX]:
return row[OPEN_IDX]
else:
if trade.stop_loss > row[HIGH_IDX]:
if stoploss_value > row[HIGH_IDX]:
return row[OPEN_IDX]
# Special case: trailing triggers within same candle as trade opened. Assume most
@@ -433,7 +439,7 @@ class Backtesting:
return max(row[LOW_IDX], stop_rate)
# Set close_rate to stoploss
return trade.stop_loss
return stoploss_value
def _get_close_rate_for_roi(self, row: Tuple, trade: LocalTrade, exit: ExitCheckTuple,
trade_dur: int) -> float:
@@ -592,7 +598,8 @@ class Backtesting:
# Confirm trade exit:
time_in_force = self.strategy.order_time_in_force['exit']
if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)(
if (exit_.exit_type != ExitType.LIQUIDATION and not strategy_safe_wrapper(
self.strategy.confirm_trade_exit, default_retval=True)(
pair=trade.pair,
trade=trade, # type: ignore[arg-type]
order_type='limit',
@@ -601,7 +608,7 @@ class Backtesting:
time_in_force=time_in_force,
sell_reason=exit_reason, # deprecated
exit_reason=exit_reason,
current_time=exit_candle_time):
current_time=exit_candle_time)):
return None
trade.exit_reason = exit_reason
@@ -807,7 +814,7 @@ class Backtesting:
trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss, initial=True)
trade.set_isolated_liq(self.exchange.get_liquidation_price(
trade.set_liquidation_price(self.exchange.get_liquidation_price(
pair=pair,
open_rate=propose_rate,
amount=amount,

View File

@@ -302,6 +302,16 @@ class LocalTrade():
# Futures properties
funding_fees: Optional[float] = None
@property
def stoploss_or_liquidation(self) -> float:
if self.liquidation_price:
if self.is_short:
return min(self.stop_loss, self.liquidation_price)
else:
return max(self.stop_loss, self.liquidation_price)
return self.stop_loss
@property
def buy_tag(self) -> Optional[str]:
"""
@@ -497,7 +507,7 @@ class LocalTrade():
self.max_rate = max(current_price, self.max_rate or self.open_rate)
self.min_rate = min(current_price_low, self.min_rate or self.open_rate)
def set_isolated_liq(self, liquidation_price: Optional[float]):
def set_liquidation_price(self, liquidation_price: Optional[float]):
"""
Method you should use to set self.liquidation price.
Assures stop_loss is not passed the liquidation price
@@ -506,22 +516,13 @@ class LocalTrade():
return
self.liquidation_price = liquidation_price
def _set_stop_loss(self, stop_loss: float, percent: float):
def __set_stop_loss(self, stop_loss: float, percent: float):
"""
Method you should use to set self.stop_loss.
Assures stop_loss is not passed the liquidation price
Method used internally to set self.stop_loss.
"""
if self.liquidation_price is not None:
if self.is_short:
sl = min(stop_loss, self.liquidation_price)
else:
sl = max(stop_loss, self.liquidation_price)
else:
sl = stop_loss
if not self.stop_loss:
self.initial_stop_loss = sl
self.stop_loss = sl
self.initial_stop_loss = stop_loss
self.stop_loss = stop_loss
self.stop_loss_pct = -1 * abs(percent)
self.stoploss_last_update = datetime.utcnow()
@@ -543,18 +544,12 @@ class LocalTrade():
leverage = self.leverage or 1.0
if self.is_short:
new_loss = float(current_price * (1 + abs(stoploss / leverage)))
# If trading with leverage, don't set the stoploss below the liquidation price
if self.liquidation_price:
new_loss = min(self.liquidation_price, new_loss)
else:
new_loss = float(current_price * (1 - abs(stoploss / leverage)))
# If trading with leverage, don't set the stoploss below the liquidation price
if self.liquidation_price:
new_loss = max(self.liquidation_price, new_loss)
# no stop loss assigned yet
if self.initial_stop_loss_pct is None or refresh:
self._set_stop_loss(new_loss, stoploss)
self.__set_stop_loss(new_loss, stoploss)
self.initial_stop_loss = new_loss
self.initial_stop_loss_pct = -1 * abs(stoploss)
@@ -569,7 +564,7 @@ class LocalTrade():
# ? decreasing the minimum stoploss
if (higher_stop and not self.is_short) or (lower_stop and self.is_short):
logger.debug(f"{self.pair} - Adjusting stoploss...")
self._set_stop_loss(new_loss, stoploss)
self.__set_stop_loss(new_loss, stoploss)
else:
logger.debug(f"{self.pair} - Keeping current stoploss...")

View File

@@ -49,7 +49,7 @@ class StoplossGuard(IProtection):
trades1 = Trade.get_trades_proxy(pair=pair, is_open=False, close_date=look_back_until)
trades = [trade for trade in trades1 if (str(trade.exit_reason) in (
ExitType.TRAILING_STOP_LOSS.value, ExitType.STOP_LOSS.value,
ExitType.STOPLOSS_ON_EXCHANGE.value)
ExitType.STOPLOSS_ON_EXCHANGE.value, ExitType.LIQUIDATION.value)
and trade.close_profit and trade.close_profit < self._profit_limit)]
if self._only_per_side:

View File

@@ -963,7 +963,7 @@ class IStrategy(ABC, HyperStrategyMixin):
# ROI
# Trailing stoploss
if stoplossflag.exit_type == ExitType.STOP_LOSS:
if stoplossflag.exit_type in (ExitType.STOP_LOSS, ExitType.LIQUIDATION):
logger.debug(f"{trade.pair} - Stoploss hit. exit_type={stoplossflag.exit_type}")
exits.append(stoplossflag)
@@ -1035,6 +1035,17 @@ class IStrategy(ABC, HyperStrategyMixin):
sl_higher_long = (trade.stop_loss >= (low or current_rate) and not trade.is_short)
sl_lower_short = (trade.stop_loss <= (high or current_rate) and trade.is_short)
liq_higher_long = (trade.liquidation_price
and trade.liquidation_price >= (low or current_rate)
and not trade.is_short)
liq_lower_short = (trade.liquidation_price
and trade.liquidation_price <= (high or current_rate)
and trade.is_short)
if (liq_higher_long or liq_lower_short):
logger.debug(f"{trade.pair} - Liquidation price hit. exit_type=ExitType.LIQUIDATION")
return ExitCheckTuple(exit_type=ExitType.LIQUIDATION)
# evaluate if the stoploss was hit if stoploss is not on exchange
# in Dry-Run, this handles stoploss logic as well, as the logic will not be different to
# regular stoploss handling.
@@ -1052,13 +1063,6 @@ class IStrategy(ABC, HyperStrategyMixin):
f"stoploss is {trade.stop_loss:.6f}, "
f"initial stoploss was at {trade.initial_stop_loss:.6f}, "
f"trade opened at {trade.open_rate:.6f}")
new_stoploss = (
trade.stop_loss + trade.initial_stop_loss
if trade.is_short else
trade.stop_loss - trade.initial_stop_loss
)
logger.debug(f"{trade.pair} - Trailing stop saved "
f"{new_stoploss:.6f}")
return ExitCheckTuple(exit_type=exit_type)