Merge pull request #7146 from freqtrade/fix/liquidation
Update liquidation price handling
This commit is contained in:
commit
369c6da5d8
@ -623,6 +623,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
|
|
||||||
!!! Warning
|
!!! Warning
|
||||||
`confirm_trade_exit()` can prevent stoploss exits, causing significant losses as this would ignore stoploss exits.
|
`confirm_trade_exit()` can prevent stoploss exits, causing significant losses as this would ignore stoploss exits.
|
||||||
|
`confirm_trade_exit()` will not be called for Liquidations - as liquidations are forced by the exchange, and therefore cannot be rejected.
|
||||||
|
|
||||||
## Adjust trade position
|
## Adjust trade position
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ class ExitType(Enum):
|
|||||||
STOP_LOSS = "stop_loss"
|
STOP_LOSS = "stop_loss"
|
||||||
STOPLOSS_ON_EXCHANGE = "stoploss_on_exchange"
|
STOPLOSS_ON_EXCHANGE = "stoploss_on_exchange"
|
||||||
TRAILING_STOP_LOSS = "trailing_stop_loss"
|
TRAILING_STOP_LOSS = "trailing_stop_loss"
|
||||||
|
LIQUIDATION = "liquidation"
|
||||||
EXIT_SIGNAL = "exit_signal"
|
EXIT_SIGNAL = "exit_signal"
|
||||||
FORCE_EXIT = "force_exit"
|
FORCE_EXIT = "force_exit"
|
||||||
EMERGENCY_EXIT = "emergency_exit"
|
EMERGENCY_EXIT = "emergency_exit"
|
||||||
|
@ -1016,7 +1016,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
trade.stoploss_order_id = None
|
trade.stoploss_order_id = None
|
||||||
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
||||||
logger.warning('Exiting the trade forcefully')
|
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))
|
exit_type=ExitType.EMERGENCY_EXIT))
|
||||||
|
|
||||||
except ExchangeError:
|
except ExchangeError:
|
||||||
@ -1086,7 +1086,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
if (trade.is_open
|
if (trade.is_open
|
||||||
and stoploss_order
|
and stoploss_order
|
||||||
and stoploss_order['status'] in ('canceled', 'cancelled')):
|
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
|
return False
|
||||||
else:
|
else:
|
||||||
trade.stoploss_order_id = None
|
trade.stoploss_order_id = None
|
||||||
@ -1115,7 +1115,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
:param order: Current on exchange stoploss order
|
:param order: Current on exchange stoploss order
|
||||||
:return: None
|
: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):
|
if self.exchange.stoploss_adjust(stoploss_norm, order, side=trade.exit_side):
|
||||||
# we check if the update is necessary
|
# we check if the update is necessary
|
||||||
@ -1133,7 +1133,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
f"for pair {trade.pair}")
|
f"for pair {trade.pair}")
|
||||||
|
|
||||||
# Create new stoploss order
|
# 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 "
|
logger.warning(f"Could not create trailing stoploss order "
|
||||||
f"for pair {trade.pair}.")
|
f"for pair {trade.pair}.")
|
||||||
|
|
||||||
@ -1432,14 +1432,15 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
)
|
)
|
||||||
exit_type = 'exit'
|
exit_type = 'exit'
|
||||||
exit_reason = exit_tag or exit_check.exit_reason
|
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'
|
exit_type = 'stoploss'
|
||||||
|
|
||||||
# if stoploss is on exchange and we are on dry_run mode,
|
# if stoploss is on exchange and we are on dry_run mode,
|
||||||
# we consider the sell price stop price
|
# we consider the sell price stop price
|
||||||
if (self.config['dry_run'] and exit_type == 'stoploss'
|
if (self.config['dry_run'] and exit_type == 'stoploss'
|
||||||
and self.strategy.order_types['stoploss_on_exchange']):
|
and self.strategy.order_types['stoploss_on_exchange']):
|
||||||
limit = trade.stop_loss
|
limit = trade.stoploss_or_liquidation
|
||||||
|
|
||||||
# set custom_exit_price if available
|
# set custom_exit_price if available
|
||||||
proposed_limit_rate = limit
|
proposed_limit_rate = limit
|
||||||
@ -1464,11 +1465,12 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
amount = self._safe_exit_amount(trade.pair, trade.amount)
|
amount = self._safe_exit_amount(trade.pair, trade.amount)
|
||||||
time_in_force = self.strategy.order_time_in_force['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_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,
|
pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit,
|
||||||
time_in_force=time_in_force, exit_reason=exit_reason,
|
time_in_force=time_in_force, exit_reason=exit_reason,
|
||||||
sell_reason=exit_reason, # sellreason -> compatibility
|
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}.")
|
logger.info(f"User denied exit for {trade.pair}.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -1661,7 +1663,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
trade = self.cancel_stoploss_on_exchange(trade)
|
trade = self.cancel_stoploss_on_exchange(trade)
|
||||||
# TODO: Margin will need to use interest_rate as well.
|
# TODO: Margin will need to use interest_rate as well.
|
||||||
# interest_rate = self.exchange.get_interest_rate()
|
# 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,
|
leverage=trade.leverage,
|
||||||
pair=trade.pair,
|
pair=trade.pair,
|
||||||
amount=trade.amount,
|
amount=trade.amount,
|
||||||
|
@ -381,7 +381,8 @@ 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
|
||||||
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)
|
return self._get_close_rate_for_stoploss(row, trade, exit, trade_dur)
|
||||||
elif exit.exit_type == (ExitType.ROI):
|
elif exit.exit_type == (ExitType.ROI):
|
||||||
return self._get_close_rate_for_roi(row, trade, exit, trade_dur)
|
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
|
is_short = trade.is_short or False
|
||||||
leverage = trade.leverage or 1.0
|
leverage = trade.leverage or 1.0
|
||||||
side_1 = -1 if is_short else 1
|
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 is_short:
|
||||||
if trade.stop_loss < row[LOW_IDX]:
|
if stoploss_value < row[LOW_IDX]:
|
||||||
return row[OPEN_IDX]
|
return row[OPEN_IDX]
|
||||||
else:
|
else:
|
||||||
if trade.stop_loss > row[HIGH_IDX]:
|
if stoploss_value > row[HIGH_IDX]:
|
||||||
return row[OPEN_IDX]
|
return 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
|
||||||
@ -433,7 +439,7 @@ class Backtesting:
|
|||||||
return max(row[LOW_IDX], stop_rate)
|
return max(row[LOW_IDX], stop_rate)
|
||||||
|
|
||||||
# Set close_rate to stoploss
|
# 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,
|
def _get_close_rate_for_roi(self, row: Tuple, trade: LocalTrade, exit: ExitCheckTuple,
|
||||||
trade_dur: int) -> float:
|
trade_dur: int) -> float:
|
||||||
@ -592,7 +598,8 @@ class Backtesting:
|
|||||||
# Confirm trade exit:
|
# Confirm trade exit:
|
||||||
time_in_force = self.strategy.order_time_in_force['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,
|
pair=trade.pair,
|
||||||
trade=trade, # type: ignore[arg-type]
|
trade=trade, # type: ignore[arg-type]
|
||||||
order_type='limit',
|
order_type='limit',
|
||||||
@ -601,7 +608,7 @@ class Backtesting:
|
|||||||
time_in_force=time_in_force,
|
time_in_force=time_in_force,
|
||||||
sell_reason=exit_reason, # deprecated
|
sell_reason=exit_reason, # deprecated
|
||||||
exit_reason=exit_reason,
|
exit_reason=exit_reason,
|
||||||
current_time=exit_candle_time):
|
current_time=exit_candle_time)):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
trade.exit_reason = exit_reason
|
trade.exit_reason = exit_reason
|
||||||
@ -807,7 +814,7 @@ class Backtesting:
|
|||||||
|
|
||||||
trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss, initial=True)
|
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,
|
pair=pair,
|
||||||
open_rate=propose_rate,
|
open_rate=propose_rate,
|
||||||
amount=amount,
|
amount=amount,
|
||||||
|
@ -302,6 +302,16 @@ class LocalTrade():
|
|||||||
# Futures properties
|
# Futures properties
|
||||||
funding_fees: Optional[float] = None
|
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
|
@property
|
||||||
def buy_tag(self) -> Optional[str]:
|
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.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)
|
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.
|
Method you should use to set self.liquidation price.
|
||||||
Assures stop_loss is not passed the liquidation price
|
Assures stop_loss is not passed the liquidation price
|
||||||
@ -506,22 +516,13 @@ class LocalTrade():
|
|||||||
return
|
return
|
||||||
self.liquidation_price = liquidation_price
|
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.
|
Method used internally to set self.stop_loss.
|
||||||
Assures stop_loss is not passed the liquidation price
|
|
||||||
"""
|
"""
|
||||||
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:
|
if not self.stop_loss:
|
||||||
self.initial_stop_loss = sl
|
self.initial_stop_loss = stop_loss
|
||||||
self.stop_loss = sl
|
self.stop_loss = stop_loss
|
||||||
|
|
||||||
self.stop_loss_pct = -1 * abs(percent)
|
self.stop_loss_pct = -1 * abs(percent)
|
||||||
self.stoploss_last_update = datetime.utcnow()
|
self.stoploss_last_update = datetime.utcnow()
|
||||||
@ -543,18 +544,12 @@ class LocalTrade():
|
|||||||
leverage = self.leverage or 1.0
|
leverage = self.leverage or 1.0
|
||||||
if self.is_short:
|
if self.is_short:
|
||||||
new_loss = float(current_price * (1 + abs(stoploss / leverage)))
|
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:
|
else:
|
||||||
new_loss = float(current_price * (1 - abs(stoploss / leverage)))
|
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
|
# no stop loss assigned yet
|
||||||
if self.initial_stop_loss_pct is None or refresh:
|
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 = new_loss
|
||||||
self.initial_stop_loss_pct = -1 * abs(stoploss)
|
self.initial_stop_loss_pct = -1 * abs(stoploss)
|
||||||
|
|
||||||
@ -569,7 +564,7 @@ class LocalTrade():
|
|||||||
# ? decreasing the minimum stoploss
|
# ? decreasing the minimum stoploss
|
||||||
if (higher_stop and not self.is_short) or (lower_stop and self.is_short):
|
if (higher_stop and not self.is_short) or (lower_stop and self.is_short):
|
||||||
logger.debug(f"{self.pair} - Adjusting stoploss...")
|
logger.debug(f"{self.pair} - Adjusting stoploss...")
|
||||||
self._set_stop_loss(new_loss, stoploss)
|
self.__set_stop_loss(new_loss, stoploss)
|
||||||
else:
|
else:
|
||||||
logger.debug(f"{self.pair} - Keeping current stoploss...")
|
logger.debug(f"{self.pair} - Keeping current stoploss...")
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ class StoplossGuard(IProtection):
|
|||||||
trades1 = Trade.get_trades_proxy(pair=pair, is_open=False, close_date=look_back_until)
|
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 (
|
trades = [trade for trade in trades1 if (str(trade.exit_reason) in (
|
||||||
ExitType.TRAILING_STOP_LOSS.value, ExitType.STOP_LOSS.value,
|
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)]
|
and trade.close_profit and trade.close_profit < self._profit_limit)]
|
||||||
|
|
||||||
if self._only_per_side:
|
if self._only_per_side:
|
||||||
|
@ -963,7 +963,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
# ROI
|
# ROI
|
||||||
# Trailing stoploss
|
# 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}")
|
logger.debug(f"{trade.pair} - Stoploss hit. exit_type={stoplossflag.exit_type}")
|
||||||
exits.append(stoplossflag)
|
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_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)
|
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
|
# 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
|
# in Dry-Run, this handles stoploss logic as well, as the logic will not be different to
|
||||||
# regular stoploss handling.
|
# regular stoploss handling.
|
||||||
@ -1052,13 +1063,6 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
f"stoploss is {trade.stop_loss:.6f}, "
|
f"stoploss is {trade.stop_loss:.6f}, "
|
||||||
f"initial stoploss was at {trade.initial_stop_loss:.6f}, "
|
f"initial stoploss was at {trade.initial_stop_loss:.6f}, "
|
||||||
f"trade opened at {trade.open_rate:.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)
|
return ExitCheckTuple(exit_type=exit_type)
|
||||||
|
|
||||||
|
@ -408,28 +408,31 @@ def test_min_roi_reached3(default_conf, fee) -> None:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'profit,adjusted,expected,trailing,custom,profit2,adjusted2,expected2,custom_stop', [
|
'profit,adjusted,expected,liq,trailing,custom,profit2,adjusted2,expected2,custom_stop', [
|
||||||
# Profit, adjusted stoploss(absolute), profit for 2nd call, enable trailing,
|
# Profit, adjusted stoploss(absolute), profit for 2nd call, enable trailing,
|
||||||
# enable custom stoploss, expected after 1st call, expected after 2nd call
|
# enable custom stoploss, expected after 1st call, expected after 2nd call
|
||||||
(0.2, 0.9, ExitType.NONE, False, False, 0.3, 0.9, ExitType.NONE, None),
|
(0.2, 0.9, ExitType.NONE, None, False, False, 0.3, 0.9, ExitType.NONE, None),
|
||||||
(0.2, 0.9, ExitType.NONE, False, False, -0.2, 0.9, ExitType.STOP_LOSS, None),
|
(0.2, 0.9, ExitType.NONE, None, False, False, -0.2, 0.9, ExitType.STOP_LOSS, None),
|
||||||
(0.2, 1.14, ExitType.NONE, True, False, 0.05, 1.14, ExitType.TRAILING_STOP_LOSS, None),
|
(0.2, 0.9, ExitType.NONE, 0.8, False, False, -0.2, 0.9, ExitType.LIQUIDATION, None),
|
||||||
(0.01, 0.96, ExitType.NONE, True, False, 0.05, 1, ExitType.NONE, None),
|
(0.2, 1.14, ExitType.NONE, None, True, False, 0.05, 1.14, ExitType.TRAILING_STOP_LOSS,
|
||||||
(0.05, 1, ExitType.NONE, True, False, -0.01, 1, ExitType.TRAILING_STOP_LOSS, None),
|
None),
|
||||||
|
(0.01, 0.96, ExitType.NONE, None, True, False, 0.05, 1, ExitType.NONE, None),
|
||||||
|
(0.05, 1, ExitType.NONE, None, True, False, -0.01, 1, ExitType.TRAILING_STOP_LOSS, None),
|
||||||
# Default custom case - trails with 10%
|
# Default custom case - trails with 10%
|
||||||
(0.05, 0.95, ExitType.NONE, False, True, -0.02, 0.95, ExitType.NONE, None),
|
(0.05, 0.95, ExitType.NONE, None, False, True, -0.02, 0.95, ExitType.NONE, None),
|
||||||
(0.05, 0.95, ExitType.NONE, False, True, -0.06, 0.95, ExitType.TRAILING_STOP_LOSS, None),
|
(0.05, 0.95, ExitType.NONE, None, False, True, -0.06, 0.95, ExitType.TRAILING_STOP_LOSS,
|
||||||
(0.05, 1, ExitType.NONE, False, True, -0.06, 1, ExitType.TRAILING_STOP_LOSS,
|
None),
|
||||||
|
(0.05, 1, ExitType.NONE, None, False, True, -0.06, 1, ExitType.TRAILING_STOP_LOSS,
|
||||||
lambda **kwargs: -0.05),
|
lambda **kwargs: -0.05),
|
||||||
(0.05, 1, ExitType.NONE, False, True, 0.09, 1.04, ExitType.NONE,
|
(0.05, 1, ExitType.NONE, None, False, True, 0.09, 1.04, ExitType.NONE,
|
||||||
lambda **kwargs: -0.05),
|
lambda **kwargs: -0.05),
|
||||||
(0.05, 0.95, ExitType.NONE, False, True, 0.09, 0.98, ExitType.NONE,
|
(0.05, 0.95, ExitType.NONE, None, False, True, 0.09, 0.98, ExitType.NONE,
|
||||||
lambda current_profit, **kwargs: -0.1 if current_profit < 0.6 else -(current_profit * 2)),
|
lambda current_profit, **kwargs: -0.1 if current_profit < 0.6 else -(current_profit * 2)),
|
||||||
# Error case - static stoploss in place
|
# Error case - static stoploss in place
|
||||||
(0.05, 0.9, ExitType.NONE, False, True, 0.09, 0.9, ExitType.NONE,
|
(0.05, 0.9, ExitType.NONE, None, False, True, 0.09, 0.9, ExitType.NONE,
|
||||||
lambda **kwargs: None),
|
lambda **kwargs: None),
|
||||||
])
|
])
|
||||||
def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, trailing, custom,
|
def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, liq, trailing, custom,
|
||||||
profit2, adjusted2, expected2, custom_stop) -> None:
|
profit2, adjusted2, expected2, custom_stop) -> None:
|
||||||
|
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
@ -442,6 +445,7 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili
|
|||||||
fee_close=fee.return_value,
|
fee_close=fee.return_value,
|
||||||
exchange='binance',
|
exchange='binance',
|
||||||
open_rate=1,
|
open_rate=1,
|
||||||
|
liquidation_price=liq,
|
||||||
)
|
)
|
||||||
trade.adjust_min_max_rates(trade.open_rate, trade.open_rate)
|
trade.adjust_min_max_rates(trade.open_rate, trade.open_rate)
|
||||||
strategy.trailing_stop = trailing
|
strategy.trailing_stop = trailing
|
||||||
|
@ -99,7 +99,7 @@ def test_enter_exit_side(fee, is_short):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
def test_set_stop_loss_isolated_liq(fee):
|
def test_set_stop_loss_liquidation(fee):
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
id=2,
|
id=2,
|
||||||
pair='ADA/USDT',
|
pair='ADA/USDT',
|
||||||
@ -115,73 +115,94 @@ def test_set_stop_loss_isolated_liq(fee):
|
|||||||
leverage=2.0,
|
leverage=2.0,
|
||||||
trading_mode=margin
|
trading_mode=margin
|
||||||
)
|
)
|
||||||
trade.set_isolated_liq(0.09)
|
trade.set_liquidation_price(0.09)
|
||||||
assert trade.liquidation_price == 0.09
|
assert trade.liquidation_price == 0.09
|
||||||
assert trade.stop_loss is None
|
assert trade.stop_loss is None
|
||||||
assert trade.initial_stop_loss is None
|
assert trade.initial_stop_loss is None
|
||||||
|
|
||||||
trade._set_stop_loss(0.1, (1.0 / 9.0))
|
trade.adjust_stop_loss(2.0, 0.2, True)
|
||||||
assert trade.liquidation_price == 0.09
|
assert trade.liquidation_price == 0.09
|
||||||
assert trade.stop_loss == 0.1
|
assert trade.stop_loss == 1.8
|
||||||
assert trade.initial_stop_loss == 0.1
|
assert trade.initial_stop_loss == 1.8
|
||||||
|
|
||||||
trade.set_isolated_liq(0.08)
|
trade.set_liquidation_price(0.08)
|
||||||
assert trade.liquidation_price == 0.08
|
assert trade.liquidation_price == 0.08
|
||||||
assert trade.stop_loss == 0.1
|
assert trade.stop_loss == 1.8
|
||||||
assert trade.initial_stop_loss == 0.1
|
assert trade.initial_stop_loss == 1.8
|
||||||
|
|
||||||
trade.set_isolated_liq(0.11)
|
trade.set_liquidation_price(0.11)
|
||||||
trade._set_stop_loss(0.1, 0)
|
trade.adjust_stop_loss(2.0, 0.2)
|
||||||
assert trade.liquidation_price == 0.11
|
assert trade.liquidation_price == 0.11
|
||||||
assert trade.stop_loss == 0.11
|
# Stoploss does not change from liquidation price
|
||||||
assert trade.initial_stop_loss == 0.1
|
assert trade.stop_loss == 1.8
|
||||||
|
assert trade.initial_stop_loss == 1.8
|
||||||
|
|
||||||
# lower stop doesn't move stoploss
|
# lower stop doesn't move stoploss
|
||||||
trade._set_stop_loss(0.1, 0)
|
trade.adjust_stop_loss(1.8, 0.2)
|
||||||
assert trade.liquidation_price == 0.11
|
assert trade.liquidation_price == 0.11
|
||||||
assert trade.stop_loss == 0.11
|
assert trade.stop_loss == 1.8
|
||||||
assert trade.initial_stop_loss == 0.1
|
assert trade.initial_stop_loss == 1.8
|
||||||
|
|
||||||
|
# higher stop does move stoploss
|
||||||
|
trade.adjust_stop_loss(2.1, 0.1)
|
||||||
|
assert trade.liquidation_price == 0.11
|
||||||
|
assert pytest.approx(trade.stop_loss) == 1.994999
|
||||||
|
assert trade.initial_stop_loss == 1.8
|
||||||
|
assert trade.stoploss_or_liquidation == trade.stop_loss
|
||||||
|
|
||||||
trade.stop_loss = None
|
trade.stop_loss = None
|
||||||
trade.liquidation_price = None
|
trade.liquidation_price = None
|
||||||
trade.initial_stop_loss = None
|
trade.initial_stop_loss = None
|
||||||
|
trade.initial_stop_loss_pct = None
|
||||||
|
|
||||||
trade._set_stop_loss(0.07, 0)
|
trade.adjust_stop_loss(2.0, 0.1, True)
|
||||||
assert trade.liquidation_price is None
|
assert trade.liquidation_price is None
|
||||||
assert trade.stop_loss == 0.07
|
assert trade.stop_loss == 1.9
|
||||||
assert trade.initial_stop_loss == 0.07
|
assert trade.initial_stop_loss == 1.9
|
||||||
|
assert trade.stoploss_or_liquidation == 1.9
|
||||||
|
|
||||||
trade.is_short = True
|
trade.is_short = True
|
||||||
trade.recalc_open_trade_value()
|
trade.recalc_open_trade_value()
|
||||||
trade.stop_loss = None
|
trade.stop_loss = None
|
||||||
trade.initial_stop_loss = None
|
trade.initial_stop_loss = None
|
||||||
|
trade.initial_stop_loss_pct = None
|
||||||
|
|
||||||
trade.set_isolated_liq(0.09)
|
trade.set_liquidation_price(3.09)
|
||||||
assert trade.liquidation_price == 0.09
|
assert trade.liquidation_price == 3.09
|
||||||
assert trade.stop_loss is None
|
assert trade.stop_loss is None
|
||||||
assert trade.initial_stop_loss is None
|
assert trade.initial_stop_loss is None
|
||||||
|
|
||||||
trade._set_stop_loss(0.08, (1.0 / 9.0))
|
trade.adjust_stop_loss(2.0, 0.2)
|
||||||
assert trade.liquidation_price == 0.09
|
assert trade.liquidation_price == 3.09
|
||||||
assert trade.stop_loss == 0.08
|
assert trade.stop_loss == 2.2
|
||||||
assert trade.initial_stop_loss == 0.08
|
assert trade.initial_stop_loss == 2.2
|
||||||
|
assert trade.stoploss_or_liquidation == 2.2
|
||||||
|
|
||||||
trade.set_isolated_liq(0.1)
|
trade.set_liquidation_price(3.1)
|
||||||
assert trade.liquidation_price == 0.1
|
assert trade.liquidation_price == 3.1
|
||||||
assert trade.stop_loss == 0.08
|
assert trade.stop_loss == 2.2
|
||||||
assert trade.initial_stop_loss == 0.08
|
assert trade.initial_stop_loss == 2.2
|
||||||
|
assert trade.stoploss_or_liquidation == 2.2
|
||||||
|
|
||||||
trade.set_isolated_liq(0.07)
|
trade.set_liquidation_price(3.8)
|
||||||
trade._set_stop_loss(0.1, (1.0 / 8.0))
|
assert trade.liquidation_price == 3.8
|
||||||
assert trade.liquidation_price == 0.07
|
# Stoploss does not change from liquidation price
|
||||||
assert trade.stop_loss == 0.07
|
assert trade.stop_loss == 2.2
|
||||||
assert trade.initial_stop_loss == 0.08
|
assert trade.initial_stop_loss == 2.2
|
||||||
|
|
||||||
# Stop doesn't move stop higher
|
# Stop doesn't move stop higher
|
||||||
trade._set_stop_loss(0.1, (1.0 / 9.0))
|
trade.adjust_stop_loss(2.0, 0.3)
|
||||||
assert trade.liquidation_price == 0.07
|
assert trade.liquidation_price == 3.8
|
||||||
assert trade.stop_loss == 0.07
|
assert trade.stop_loss == 2.2
|
||||||
assert trade.initial_stop_loss == 0.08
|
assert trade.initial_stop_loss == 2.2
|
||||||
|
|
||||||
|
# Stoploss does move lower
|
||||||
|
trade.set_liquidation_price(1.5)
|
||||||
|
trade.adjust_stop_loss(1.8, 0.1)
|
||||||
|
assert trade.liquidation_price == 1.5
|
||||||
|
assert pytest.approx(trade.stop_loss) == 1.89
|
||||||
|
assert trade.initial_stop_loss == 2.2
|
||||||
|
assert trade.stoploss_or_liquidation == 1.5
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('exchange,is_short,lev,minutes,rate,interest,trading_mode', [
|
@pytest.mark.parametrize('exchange,is_short,lev,minutes,rate,interest,trading_mode', [
|
||||||
@ -1537,26 +1558,26 @@ def test_adjust_stop_loss(fee):
|
|||||||
|
|
||||||
# Get percent of profit with a custom rate (Higher than open rate)
|
# Get percent of profit with a custom rate (Higher than open rate)
|
||||||
trade.adjust_stop_loss(1.3, -0.1)
|
trade.adjust_stop_loss(1.3, -0.1)
|
||||||
assert round(trade.stop_loss, 8) == 1.17
|
assert pytest.approx(trade.stop_loss) == 1.17
|
||||||
assert trade.stop_loss_pct == -0.1
|
assert trade.stop_loss_pct == -0.1
|
||||||
assert trade.initial_stop_loss == 0.95
|
assert trade.initial_stop_loss == 0.95
|
||||||
assert trade.initial_stop_loss_pct == -0.05
|
assert trade.initial_stop_loss_pct == -0.05
|
||||||
|
|
||||||
# current rate lower again ... should not change
|
# current rate lower again ... should not change
|
||||||
trade.adjust_stop_loss(1.2, 0.1)
|
trade.adjust_stop_loss(1.2, 0.1)
|
||||||
assert round(trade.stop_loss, 8) == 1.17
|
assert pytest.approx(trade.stop_loss) == 1.17
|
||||||
assert trade.initial_stop_loss == 0.95
|
assert trade.initial_stop_loss == 0.95
|
||||||
assert trade.initial_stop_loss_pct == -0.05
|
assert trade.initial_stop_loss_pct == -0.05
|
||||||
|
|
||||||
# current rate higher... should raise stoploss
|
# current rate higher... should raise stoploss
|
||||||
trade.adjust_stop_loss(1.4, 0.1)
|
trade.adjust_stop_loss(1.4, 0.1)
|
||||||
assert round(trade.stop_loss, 8) == 1.26
|
assert pytest.approx(trade.stop_loss) == 1.26
|
||||||
assert trade.initial_stop_loss == 0.95
|
assert trade.initial_stop_loss == 0.95
|
||||||
assert trade.initial_stop_loss_pct == -0.05
|
assert trade.initial_stop_loss_pct == -0.05
|
||||||
|
|
||||||
# Initial is true but stop_loss set - so doesn't do anything
|
# Initial is true but stop_loss set - so doesn't do anything
|
||||||
trade.adjust_stop_loss(1.7, 0.1, True)
|
trade.adjust_stop_loss(1.7, 0.1, True)
|
||||||
assert round(trade.stop_loss, 8) == 1.26
|
assert pytest.approx(trade.stop_loss) == 1.26
|
||||||
assert trade.initial_stop_loss == 0.95
|
assert trade.initial_stop_loss == 0.95
|
||||||
assert trade.initial_stop_loss_pct == -0.05
|
assert trade.initial_stop_loss_pct == -0.05
|
||||||
assert trade.stop_loss_pct == -0.1
|
assert trade.stop_loss_pct == -0.1
|
||||||
@ -1609,9 +1630,10 @@ def test_adjust_stop_loss_short(fee):
|
|||||||
assert trade.initial_stop_loss == 1.05
|
assert trade.initial_stop_loss == 1.05
|
||||||
assert trade.initial_stop_loss_pct == -0.05
|
assert trade.initial_stop_loss_pct == -0.05
|
||||||
assert trade.stop_loss_pct == -0.1
|
assert trade.stop_loss_pct == -0.1
|
||||||
trade.set_isolated_liq(0.63)
|
# Liquidation price is lower than stoploss - so liquidation would trigger first.
|
||||||
|
trade.set_liquidation_price(0.63)
|
||||||
trade.adjust_stop_loss(0.59, -0.1)
|
trade.adjust_stop_loss(0.59, -0.1)
|
||||||
assert trade.stop_loss == 0.63
|
assert trade.stop_loss == 0.649
|
||||||
assert trade.liquidation_price == 0.63
|
assert trade.liquidation_price == 0.63
|
||||||
|
|
||||||
|
|
||||||
@ -2009,10 +2031,10 @@ def test_stoploss_reinitialization_short(default_conf, fee):
|
|||||||
assert trade_adj.initial_stop_loss == 1.01
|
assert trade_adj.initial_stop_loss == 1.01
|
||||||
assert trade_adj.initial_stop_loss_pct == -0.05
|
assert trade_adj.initial_stop_loss_pct == -0.05
|
||||||
# Stoploss can't go above liquidation price
|
# Stoploss can't go above liquidation price
|
||||||
trade_adj.set_isolated_liq(0.985)
|
trade_adj.set_liquidation_price(0.985)
|
||||||
trade.adjust_stop_loss(0.9799, -0.05)
|
trade.adjust_stop_loss(0.9799, -0.05)
|
||||||
assert trade_adj.stop_loss == 0.985
|
assert trade_adj.stop_loss == 0.989699
|
||||||
assert trade_adj.stop_loss == 0.985
|
assert trade_adj.liquidation_price == 0.985
|
||||||
|
|
||||||
|
|
||||||
def test_update_fee(fee):
|
def test_update_fee(fee):
|
||||||
|
Loading…
Reference in New Issue
Block a user