exchange.liquidation_price methods combined, dry_run check on exchange for liquidation price

This commit is contained in:
Sam Germain
2022-01-29 18:47:17 -06:00
parent 143c37d36f
commit b8f4cebce7
8 changed files with 140 additions and 124 deletions

View File

@@ -277,17 +277,15 @@ class Binance(Exchange):
# The lowest notional_floor for any pair in loadLeverageBrackets is always 0 because it
# describes the min amount for a bracket, and the lowest bracket will always go down to 0
def liquidation_price(
def dry_run_liquidation_price(
self,
pair: str,
open_rate: float, # Entry price of position
is_short: bool,
mm_ratio: float,
position: float, # Absolute value of position size
wallet_balance: float, # Or margin balance
taker_fee_rate: Optional[float] = None, # (Gateio & Okex)
maintenance_amt: Optional[float] = None, # (Binance)
mm_ex_1: Optional[float] = 0.0, # (Binance) Cross only
upnl_ex_1: Optional[float] = 0.0, # (Binance) Cross only
mm_ex_1: float = 0.0, # (Binance) Cross only
upnl_ex_1: float = 0.0, # (Binance) Cross only
) -> Optional[float]:
"""
MARGIN: https://www.binance.com/en/support/faq/f6b010588e55413aa58b7d63ee0125ed
@@ -296,14 +294,10 @@ class Binance(Exchange):
:param exchange_name:
:param open_rate: (EP1) Entry price of position
:param is_short: True if the trade is a short, false otherwise
:param mm_ratio: (MMR)
# Binance's formula specifies maintenance margin rate which is mm_ratio * 100%
:param position: Absolute value of position size (in base currency)
:param maintenance_amt: (CUM) Maintenance Amount of position
:param wallet_balance: (WB)
Cross-Margin Mode: crossWalletBalance
Isolated-Margin Mode: isolatedWalletBalance
:param taker_fee_rate: # * Not required by Binance
:param maintenance_amt:
# * Only required for Cross
@@ -314,30 +308,20 @@ class Binance(Exchange):
Cross-Margin Mode: Unrealized PNL of all other contracts, excluding Contract 1.
Isolated-Margin Mode: 0
"""
if self.trading_mode == TradingMode.SPOT:
return None
elif (self.collateral is None):
raise OperationalException('Binance.collateral must be set for liquidation_price')
if (maintenance_amt is None):
raise OperationalException(
f"Parameter maintenance_amt is required by Binance.liquidation_price"
f"for {self.collateral.value} {self.trading_mode.value}"
)
if (self.collateral == Collateral.CROSS and (mm_ex_1 is None or upnl_ex_1 is None)):
raise OperationalException(
f"Parameters mm_ex_1 and upnl_ex_1 are required by Binance.liquidation_price"
f"for {self.collateral.value} {self.trading_mode.value}"
)
side_1 = -1 if is_short else 1
position = abs(position)
cross_vars = (
upnl_ex_1 - mm_ex_1 # type: ignore
if self.collateral == Collateral.CROSS else
0.0
)
cross_vars = upnl_ex_1 - mm_ex_1 if self.collateral == Collateral.CROSS else 0.0
# mm_ratio: Binance's formula specifies maintenance margin rate which is mm_ratio * 100%
# maintenance_amt: (CUM) Maintenance Amount of position
mm_ratio, maintenance_amt = self.get_maintenance_ratio_and_amt(pair, position)
if (maintenance_amt is None):
raise OperationalException(
"Parameter maintenance_amt is required by Binance.liquidation_price"
f"for {self.trading_mode.value}"
)
if self.trading_mode == TradingMode.FUTURES:
return (
@@ -348,6 +332,6 @@ class Binance(Exchange):
(position * mm_ratio) - (side_1 * position)
)
)
raise OperationalException(
f"Binance does not support {self.collateral.value} {self.trading_mode.value} trading")
else:
raise OperationalException(
"Freqtrade only supports isolated futures for leverage trading")

View File

@@ -1984,18 +1984,55 @@ class Exchange:
return 0.0
@retrier
def get_liquidation_price(self, pair: str):
def get_liquidation_price(
self,
pair: str,
# Dry-run
open_rate: Optional[float] = None, # Entry price of position
is_short: Optional[bool] = None,
position: Optional[float] = None, # Absolute value of position size
wallet_balance: Optional[float] = None, # Or margin balance
mm_ex_1: float = 0.0, # (Binance) Cross only
upnl_ex_1: float = 0.0, # (Binance) Cross only
):
"""
Set's the margin mode on the exchange to cross or isolated for a specific pair
:param pair: base/quote currency pair (e.g. "ADA/USDT")
"""
if self.trading_mode == TradingMode.SPOT:
return None
elif (self.collateral is None):
raise OperationalException(f'{self.name}.collateral must be set for liquidation_price')
elif (self.trading_mode != TradingMode.FUTURES and self.collateral != Collateral.ISOLATED):
raise OperationalException(
f"{self.name} does not support {self.collateral.value} {self.trading_mode.value}")
if self._config['dry_run'] or not self.exchange_has("fetchPositions"):
return
if (
open_rate is None or
is_short is None or
position is None or
wallet_balance is None
):
raise OperationalException(
f"Parameters open_rate, is_short, position, wallet_balance are"
f"required by {self.name}.liquidation_price for dry_run"
)
return self.dry_run_liquidation_price(
pair=pair,
open_rate=open_rate,
is_short=is_short,
position=position,
wallet_balance=wallet_balance,
mm_ex_1=mm_ex_1,
upnl_ex_1=upnl_ex_1
)
try:
positions = self._api.fetch_positions([pair])
position = positions[0]
return position['liquidationPrice']
pos = positions[0]
return pos['liquidationPrice']
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
@@ -2014,17 +2051,15 @@ class Exchange:
"""
raise OperationalException(self.name + ' does not support leverage futures trading')
def liquidation_price(
def dry_run_liquidation_price(
self,
pair: str,
open_rate: float, # Entry price of position
is_short: bool,
mm_ratio: float,
position: float, # Absolute value of position size
wallet_balance: float, # Or margin balance
taker_fee_rate: Optional[float] = None, # (Gateio & Okex)
maintenance_amt: Optional[float] = None, # (Binance)
mm_ex_1: Optional[float] = 0.0, # (Binance) Cross only
upnl_ex_1: Optional[float] = 0.0, # (Binance) Cross only
mm_ex_1: float = 0.0, # (Binance) Cross only
upnl_ex_1: float = 0.0, # (Binance) Cross only
) -> Optional[float]:
"""
PERPETUAL:
@@ -2036,33 +2071,26 @@ class Exchange:
:param open_rate: Entry price of position
:param is_short: True if the trade is a short, false otherwise
:param position: Absolute value of position size (in base currency)
:param mm_ratio:
:param trading_mode: SPOT, MARGIN, FUTURES, etc.
:param collateral: Either ISOLATED or CROSS
:param wallet_balance: Amount of collateral in the wallet being used to trade
Cross-Margin Mode: crossWalletBalance
Isolated-Margin Mode: isolatedWalletBalance
:param taker_fee_rate:
# * Not required by Gateio or OKX
:param maintenance_amt:
:param mm_ex_1:
:param upnl_ex_1:
"""
if self.trading_mode == TradingMode.SPOT:
return None
elif (self.collateral is None):
raise OperationalException('Binance.collateral must be set for liquidation_price')
if (not taker_fee_rate):
raise OperationalException(
f"Parameter taker_fee_rate is required by {self.name}.liquidation_price"
)
market = self.markets[pair]
taker_fee_rate = market['taker']
mm_ratio, _ = self.get_maintenance_ratio_and_amt(pair, position)
if self.trading_mode == TradingMode.FUTURES and self.collateral == Collateral.ISOLATED:
# if is_inverse:
# raise OperationalException(
# "Freqtrade does not support inverse contracts at the moment")
if market['inverse']:
raise OperationalException(
"Freqtrade does not yet support inverse contracts")
value = wallet_balance / position
@@ -2073,7 +2101,7 @@ class Exchange:
return (open_rate - value) / (1 - mm_ratio_taker)
else:
raise OperationalException(
f"{self.name} does not support {self.collateral.value} {self.trading_mode.value}")
"Freqtrade only supports isolated futures for leverage trading")
def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool:

View File

@@ -1,5 +1,6 @@
import logging
from typing import Dict, List, Tuple
from freqtrade.enums import Collateral, TradingMode
from freqtrade.exchange import Exchange

View File

@@ -19,7 +19,7 @@ from freqtrade.edge import Edge
from freqtrade.enums import (Collateral, RPCMessageType, RunMode, SellType, SignalDirection, State,
TradingMode)
from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
InvalidOrderException, PricingError)
InvalidOrderException, OperationalException, PricingError)
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
from freqtrade.misc import safe_value_fallback, safe_value_fallback2
from freqtrade.mixins import LoggingMixin
@@ -606,38 +606,31 @@ class FreqtradeBot(LoggingMixin):
is_short: bool
) -> Tuple[float, Optional[float]]:
interest_rate = 0.0
isolated_liq = None
# if TradingMode == TradingMode.MARGIN:
# interest_rate = self.exchange.get_interest_rate(
# pair=pair,
# open_rate=open_rate,
# is_short=is_short
# )
if self.collateral_type == Collateral.ISOLATED:
if self.config['dry_run']:
mm_ratio, maintenance_amt = self.exchange.get_maintenance_ratio_and_amt(
pair,
amount
)
taker_fee_rate = self.exchange.markets[pair]['taker']
isolated_liq = self.exchange.liquidation_price(
open_rate=open_rate,
is_short=is_short,
mm_ratio=mm_ratio,
position=amount,
wallet_balance=(amount * open_rate)/leverage, # TODO: Update for cross
taker_fee_rate=taker_fee_rate,
maintenance_amt=maintenance_amt,
mm_ex_1=0.0,
upnl_ex_1=0.0,
)
else:
isolated_liq = self.exchange.get_liquidation_price(pair)
return interest_rate, isolated_liq
if self.trading_mode == TradingMode.SPOT:
return (0.0, None)
elif (
self.collateral_type == Collateral.ISOLATED and
self.trading_mode == TradingMode.FUTURES
):
isolated_liq = self.exchange.get_liquidation_price(
pair=pair,
open_rate=open_rate,
is_short=is_short,
position=amount,
wallet_balance=(amount * open_rate)/leverage, # TODO: Update for cross
mm_ex_1=0.0,
upnl_ex_1=0.0,
)
return (0.0, isolated_liq)
else:
raise OperationalException(
"Freqtrade only supports isolated futures for leverage trading")
def execute_entry(
self,

View File

@@ -362,10 +362,7 @@ class LocalTrade():
self.stop_loss_pct = -1 * abs(percent)
self.stoploss_last_update = datetime.utcnow()
def set_isolated_liq(
self,
isolated_liq: float,
):
def set_isolated_liq(self, isolated_liq: float):
"""
Method you should use to set self.liquidation price.
Assures stop_loss is not passed the liquidation price