Added liquidation_price function

This commit is contained in:
Sam Germain 2021-08-06 01:15:18 -06:00
parent f10ef7f2da
commit a087d03db9
5 changed files with 246 additions and 8 deletions

View File

@ -1,2 +1,3 @@
# flake8: noqa: F401 # flake8: noqa: F401
from freqtrade.leverage.interest import interest from freqtrade.leverage.interest import interest
from freqtrade.leverage.liquidation_price import liquidation_price

View File

@ -0,0 +1,133 @@
from typing import Optional
from freqtrade.enums import Collateral, TradingMode
from freqtrade.exceptions import OperationalException
def liquidation_price(
exchange_name: str,
open_rate: float,
is_short: bool,
leverage: float,
trading_mode: TradingMode,
collateral: Optional[Collateral]
) -> Optional[float]:
leverage_exchanges = [
'binance',
'kraken',
'ftx'
]
if trading_mode == TradingMode.SPOT or exchange_name.lower() not in leverage_exchanges:
return None
if not collateral:
raise OperationalException(
"Parameter collateral is required by liquidation_price when trading_mode is "
f"{trading_mode}"
)
if exchange_name.lower() == "binance":
return binance(open_rate, is_short, leverage, trading_mode, collateral)
elif exchange_name.lower() == "kraken":
return kraken(open_rate, is_short, leverage, trading_mode, collateral)
elif exchange_name.lower() == "ftx":
return ftx(open_rate, is_short, leverage, trading_mode, collateral)
raise OperationalException(
f"liquidation_price is not yet implemented for {exchange_name}"
)
def exception(
exchange: str,
trading_mode: TradingMode,
collateral: Collateral
):
"""
Raises an exception if exchange used doesn't support desired leverage mode
:param name: Name of the exchange
:param trading_mode: spot, margin, futures
:param collateral: cross, isolated
"""
raise OperationalException(
f"{exchange} does not support {collateral.value} {trading_mode.value} trading")
def binance(
open_rate: float,
is_short: bool,
leverage: float,
trading_mode: TradingMode,
collateral: Collateral
):
"""
Calculates the liquidation price on Binance
:param name: Name of the exchange
:param trading_mode: spot, margin, futures
:param collateral: cross, isolated
"""
# TODO-lev: Additional arguments, fill in formulas
if trading_mode == TradingMode.MARGIN and collateral == Collateral.CROSS:
# TODO-lev: perform a calculation based on this formula
# https://www.binance.com/en/support/faq/f6b010588e55413aa58b7d63ee0125ed
exception("binance", trading_mode, collateral)
elif trading_mode == TradingMode.FUTURES and collateral == Collateral.CROSS:
# TODO-lev: perform a calculation based on this formula
# https://www.binance.com/en/support/faq/b3c689c1f50a44cabb3a84e663b81d93
exception("binance", trading_mode, collateral)
elif trading_mode == TradingMode.FUTURES and collateral == Collateral.ISOLATED:
# TODO-lev: perform a calculation based on this formula
# https://www.binance.com/en/support/faq/b3c689c1f50a44cabb3a84e663b81d93
exception("binance", trading_mode, collateral)
# If nothing was returned
exception("binance", trading_mode, collateral)
def kraken(
open_rate: float,
is_short: bool,
leverage: float,
trading_mode: TradingMode,
collateral: Collateral
):
"""
Calculates the liquidation price on Kraken
:param name: Name of the exchange
:param trading_mode: spot, margin, futures
:param collateral: cross, isolated
"""
# TODO-lev: Additional arguments, fill in formulas
if collateral == Collateral.CROSS:
if trading_mode == TradingMode.MARGIN:
exception("kraken", trading_mode, collateral)
# TODO-lev: perform a calculation based on this formula
# https://support.kraken.com/hc/en-us/articles/203325763-Margin-Call-Level-and-Margin-Liquidation-Level
elif trading_mode == TradingMode.FUTURES:
exception("kraken", trading_mode, collateral)
# If nothing was returned
exception("kraken", trading_mode, collateral)
def ftx(
open_rate: float,
is_short: bool,
leverage: float,
trading_mode: TradingMode,
collateral: Collateral
):
"""
Calculates the liquidation price on FTX
:param name: Name of the exchange
:param trading_mode: spot, margin, futures
:param collateral: cross, isolated
"""
if collateral == Collateral.CROSS:
# TODO-lev: Additional arguments, fill in formulas
exception("ftx", trading_mode, collateral)
# If nothing was returned
exception("ftx", trading_mode, collateral)

View File

@ -14,9 +14,10 @@ from sqlalchemy.pool import StaticPool
from sqlalchemy.sql.schema import UniqueConstraint from sqlalchemy.sql.schema import UniqueConstraint
from freqtrade.constants import DATETIME_PRINT_FORMAT, NON_OPEN_EXCHANGE_STATES from freqtrade.constants import DATETIME_PRINT_FORMAT, NON_OPEN_EXCHANGE_STATES
from freqtrade.enums import SellType, TradingMode from freqtrade.enums import Collateral, SellType, TradingMode
from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.leverage import interest from freqtrade.leverage import interest
from freqtrade.leverage import liquidation_price
from freqtrade.misc import safe_value_fallback from freqtrade.misc import safe_value_fallback
from freqtrade.persistence.migrations import check_migrate from freqtrade.persistence.migrations import check_migrate
@ -333,7 +334,7 @@ class LocalTrade():
for key in kwargs: for key in kwargs:
setattr(self, key, kwargs[key]) setattr(self, key, kwargs[key])
if self.isolated_liq: if self.isolated_liq:
self.set_isolated_liq(self.isolated_liq) self.set_isolated_liq(isolated_liq=self.isolated_liq)
self.recalc_open_trade_value() self.recalc_open_trade_value()
if self.trading_mode == TradingMode.MARGIN and self.interest_rate is None: if self.trading_mode == TradingMode.MARGIN and self.interest_rate is None:
raise OperationalException( raise OperationalException(
@ -362,11 +363,25 @@ class LocalTrade():
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()
def set_isolated_liq(self, isolated_liq: float): def set_isolated_liq(self, isolated_liq: 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
""" """
if not isolated_liq:
isolated_liq = liquidation_price(
exchange_name=self.exchange,
open_rate=self.open_rate,
is_short=self.is_short,
leverage=self.leverage,
trading_mode=self.trading_mode,
collateral=Collateral.ISOLATED
)
if isolated_liq is None:
raise OperationalException(
"leverage/isolated_liq returned None. This exception should never happen"
)
if self.stop_loss is not None: if self.stop_loss is not None:
if self.is_short: if self.is_short:
self.stop_loss = min(self.stop_loss, isolated_liq) self.stop_loss = min(self.stop_loss, isolated_liq)

View File

@ -0,0 +1,89 @@
# from decimal import Decimal
from freqtrade.enums import Collateral, TradingMode
from freqtrade.leverage import liquidation_price
# from freqtrade.exceptions import OperationalException
binance = "binance"
kraken = "kraken"
ftx = "ftx"
other = "bittrex"
def test_liquidation_price():
spot = TradingMode.SPOT
margin = TradingMode.MARGIN
futures = TradingMode.FUTURES
cross = Collateral.CROSS
isolated = Collateral.ISOLATED
# NONE
assert liquidation_price(exchange_name=other, trading_mode=spot) is None
assert liquidation_price(exchange_name=other, trading_mode=margin,
collateral=cross) is None
assert liquidation_price(exchange_name=other, trading_mode=margin,
collateral=isolated) is None
assert liquidation_price(
exchange_name=other, trading_mode=futures, collateral=cross) is None
assert liquidation_price(exchange_name=other, trading_mode=futures,
collateral=isolated) is None
# Binance
assert liquidation_price(exchange_name=binance, trading_mode=spot) is None
assert liquidation_price(exchange_name=binance, trading_mode=spot,
collateral=cross) is None
assert liquidation_price(exchange_name=binance, trading_mode=spot,
collateral=isolated) is None
# TODO-lev: Uncomment these assertions and make them real calculation tests
# TODO-lev: Replace 1.0 with real value
# assert liquidation_price(
# exchange_name=binance,
# trading_mode=margin,
# collateral=cross
# ) == 1.0
# assert liquidation_price(
# exchange_name=binance,
# trading_mode=margin,
# collateral=isolated
# ) == 1.0
# assert liquidation_price(
# exchange_name=binance,
# trading_mode=futures,
# collateral=cross
# ) == 1.0
# Binance supports isolated margin, but freqtrade likely won't for a while on Binance
# liquidation_price(exchange_name=binance, trading_mode=margin, collateral=isolated)
# assert exception thrown #TODO-lev: Check that exception is thrown
# Kraken
assert liquidation_price(exchange_name=kraken, trading_mode=spot) is None
assert liquidation_price(exchange_name=kraken, trading_mode=spot, collateral=cross) is None
assert liquidation_price(exchange_name=kraken, trading_mode=spot,
collateral=isolated) is None
# TODO-lev: Uncomment these assertions and make them real calculation tests
# assert liquidation_price(kraken, trading_mode=margin, collateral=cross) == 1.0
# assert liquidation_price(kraken, trading_mode=margin, collateral=isolated) == 1.0
# liquidation_price(kraken, trading_mode=futures, collateral=cross)
# assert exception thrown #TODO-lev: Check that exception is thrown
# liquidation_price(kraken, trading_mode=futures, collateral=isolated)
# assert exception thrown #TODO-lev: Check that exception is thrown
# FTX
assert liquidation_price(ftx, trading_mode=spot) is None
assert liquidation_price(ftx, trading_mode=spot, collateral=cross) is None
assert liquidation_price(ftx, trading_mode=spot, collateral=isolated) is None
# TODO-lev: Uncomment these assertions and make them real calculation tests
# assert liquidation_price(ftx, trading_mode=margin, collateral=cross) == 1.0
# assert liquidation_price(ftx, trading_mode=margin, collateral=isolated) == 1.0
# liquidation_price(ftx, trading_mode=futures, collateral=cross)
# assert exception thrown #TODO-lev: Check that exception is thrown
# liquidation_price(ftx, trading_mode=futures, collateral=isolated)
# assert exception thrown #TODO-lev: Check that exception is thrown

View File

@ -109,7 +109,7 @@ 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_isolated_liq(isolated_liq=0.09)
assert trade.isolated_liq == 0.09 assert trade.isolated_liq == 0.09
assert trade.stop_loss == 0.09 assert trade.stop_loss == 0.09
assert trade.initial_stop_loss == 0.09 assert trade.initial_stop_loss == 0.09
@ -119,12 +119,12 @@ def test_set_stop_loss_isolated_liq(fee):
assert trade.stop_loss == 0.1 assert trade.stop_loss == 0.1
assert trade.initial_stop_loss == 0.09 assert trade.initial_stop_loss == 0.09
trade.set_isolated_liq(0.08) trade.set_isolated_liq(isolated_liq=0.08)
assert trade.isolated_liq == 0.08 assert trade.isolated_liq == 0.08
assert trade.stop_loss == 0.1 assert trade.stop_loss == 0.1
assert trade.initial_stop_loss == 0.09 assert trade.initial_stop_loss == 0.09
trade.set_isolated_liq(0.11) trade.set_isolated_liq(isolated_liq=0.11)
assert trade.isolated_liq == 0.11 assert trade.isolated_liq == 0.11
assert trade.stop_loss == 0.11 assert trade.stop_loss == 0.11
assert trade.initial_stop_loss == 0.09 assert trade.initial_stop_loss == 0.09
@ -1472,7 +1472,7 @@ 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) trade.set_isolated_liq(isolated_liq=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.63
assert trade.isolated_liq == 0.63 assert trade.isolated_liq == 0.63
@ -1803,7 +1803,7 @@ def test_stoploss_reinitialization_short(default_conf, fee):
assert trade_adj.initial_stop_loss == 1.04 assert trade_adj.initial_stop_loss == 1.04
assert trade_adj.initial_stop_loss_pct == 0.04 assert trade_adj.initial_stop_loss_pct == 0.04
# Stoploss can't go above liquidation price # Stoploss can't go above liquidation price
trade_adj.set_isolated_liq(1.0) trade_adj.set_isolated_liq(isolated_liq=1.0)
trade.adjust_stop_loss(0.97, -0.04) trade.adjust_stop_loss(0.97, -0.04)
assert trade_adj.stop_loss == 1.0 assert trade_adj.stop_loss == 1.0
assert trade_adj.stop_loss == 1.0 assert trade_adj.stop_loss == 1.0