From 2aa2b5bcfff98e81ad4d505cf712b09c41ab41cf Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 5 Jul 2021 23:53:49 -0600 Subject: [PATCH] Added checks for making sure stop_loss doesn't go below liquidation_price --- freqtrade/persistence/models.py | 27 ++++++++++++++++++- tests/conftest.py | 12 ++++++--- .../persistence/test_persistence_leverage.py | 4 +++ tests/persistence/test_persistence_short.py | 9 ++++++- 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 54a5676d9..415024018 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -294,8 +294,30 @@ class LocalTrade(): def __init__(self, **kwargs): for key in kwargs: setattr(self, key, kwargs[key]) + self.set_liquidation_price(self.liquidation_price) self.recalc_open_trade_value() + def set_stop_loss_helper(self, stop_loss: Optional[float], liquidation_price: Optional[float]): + # Stoploss would be better as a computed variable, but that messes up the database so it might not be possible + # TODO-mg: What should be done about initial_stop_loss + if liquidation_price is not None: + if stop_loss is not None: + if self.is_short: + self.stop_loss = min(stop_loss, liquidation_price) + else: + self.stop_loss = max(stop_loss, liquidation_price) + else: + self.stop_loss = liquidation_price + self.liquidation_price = liquidation_price + else: + self.stop_loss = stop_loss + + def set_stop_loss(self, stop_loss: float): + self.set_stop_loss_helper(stop_loss=stop_loss, liquidation_price=self.liquidation_price) + + def set_liquidation_price(self, liquidation_price: float): + self.set_stop_loss_helper(stop_loss=self.stop_loss, liquidation_price=liquidation_price) + def __repr__(self): open_since = self.open_date.strftime(DATETIME_PRINT_FORMAT) if self.is_open else 'closed' @@ -390,7 +412,7 @@ class LocalTrade(): def _set_new_stoploss(self, new_loss: float, stoploss: float): """Assign new stop value""" - self.stop_loss = new_loss + self.set_stop_loss(new_loss) if self.is_short: self.stop_loss_pct = abs(stoploss) else: @@ -484,6 +506,9 @@ class LocalTrade(): self.amount = float(safe_value_fallback(order, 'filled', 'amount')) if 'leverage' in order: self.leverage = order['leverage'] + if 'liquidation_price' in order: + self.liquidation_price = order['liquidation_price'] + self.set_stop_loss(self.stop_loss) self.recalc_open_trade_value() if self.is_open: payment = "SELL" if self.is_short else "BUY" diff --git a/tests/conftest.py b/tests/conftest.py index f935b7fa2..20fbde61c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2132,7 +2132,8 @@ def limit_short_order_open(): 'cost': 0.00106733393, 'remaining': 90.99181073, 'status': 'open', - 'is_short': True + 'is_short': True, + 'liquidation_price': 0.00001300 } @@ -2185,7 +2186,8 @@ def market_short_order(): 'remaining': 0.0, 'status': 'closed', 'is_short': True, - 'leverage': 3.0 + 'leverage': 3.0, + 'liquidation_price': 0.00004300 } @@ -2223,7 +2225,8 @@ def limit_leveraged_buy_order_open(): 'remaining': 272.97543219, 'leverage': 3.0, 'status': 'open', - 'exchange': 'binance' + 'exchange': 'binance', + 'liquidation_price': 0.00001000 } @@ -2277,7 +2280,8 @@ def market_leveraged_buy_order(): 'filled': 275.97543219, 'remaining': 0.0, 'status': 'closed', - 'exchange': 'kraken' + 'exchange': 'kraken', + 'liquidation_price': 0.00004000 } diff --git a/tests/persistence/test_persistence_leverage.py b/tests/persistence/test_persistence_leverage.py index 44da84f37..74103156d 100644 --- a/tests/persistence/test_persistence_leverage.py +++ b/tests/persistence/test_persistence_leverage.py @@ -428,6 +428,8 @@ def test_update_limit_order_lev(limit_leveraged_buy_order, limit_leveraged_sell_ assert trade.close_profit is None assert trade.close_date is None assert trade.borrowed == 0.0019999999998453998 + assert trade.stop_loss == 0.00001000 + assert trade.liquidation_price == 0.00001000 assert log_has_re(r"LIMIT_BUY has been fulfilled for Trade\(id=2, " r"pair=ETH/BTC, amount=272.97543219, open_rate=0.00001099, open_since=.*\).", caplog) @@ -494,6 +496,8 @@ def test_update_market_order_lev(market_leveraged_buy_order, market_leveraged_se assert trade.close_profit is None assert trade.close_date is None assert trade.interest_rate == 0.0005 + assert trade.stop_loss == 0.00004000 + assert trade.liquidation_price == 0.00004000 # TODO: Uncomment the next assert and make it work. # The logger also has the exact same but there's some spacing in there assert log_has_re(r"MARKET_BUY has been fulfilled for Trade\(id=1, " diff --git a/tests/persistence/test_persistence_short.py b/tests/persistence/test_persistence_short.py index e66914858..67961f415 100644 --- a/tests/persistence/test_persistence_short.py +++ b/tests/persistence/test_persistence_short.py @@ -433,6 +433,8 @@ def test_update_with_binance_short(limit_short_order, limit_exit_short_order, fe assert trade.close_date is None assert trade.borrowed == 90.99181073 assert trade.is_short is True + assert trade.stop_loss == 0.00001300 + assert trade.liquidation_price == 0.00001300 assert log_has_re(r"LIMIT_SELL has been fulfilled for Trade\(id=2, " r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001173, open_since=.*\).", caplog) @@ -506,6 +508,8 @@ def test_update_market_order_short( assert trade.close_profit is None assert trade.close_date is None assert trade.interest_rate == 0.0005 + assert trade.stop_loss == 0.00004300 + assert trade.liquidation_price == 0.00004300 # The logger also has the exact same but there's some spacing in there assert log_has_re(r"MARKET_SELL has been fulfilled for Trade\(id=1, " r"pair=ETH/BTC, amount=275.97543219, open_rate=0.00004173, open_since=.*\).", @@ -670,7 +674,10 @@ def test_adjust_stop_loss_short(fee): assert trade.initial_stop_loss == 1.05 assert trade.initial_stop_loss_pct == 0.05 assert trade.stop_loss_pct == 0.1 - trade.liquidation_price == 1.03 + trade.set_liquidation_price(0.63) + trade.adjust_stop_loss(0.59, -0.1) + assert trade.stop_loss == 0.63 + assert trade.liquidation_price == 0.63 # TODO-mg: Do a test with a trade that has a liquidation price