Move leverage_prep calculations to exchange class

This commit is contained in:
Matthias 2022-02-28 19:27:48 +01:00
parent 1121965c6e
commit 8e2d3445a7
6 changed files with 163 additions and 201 deletions

View File

@ -2055,6 +2055,42 @@ class Exchange:
except ccxt.BaseError as e:
raise OperationalException(e) from e
def leverage_prep(
self,
pair: str,
open_rate: float,
amount: float, # quote currency, includes leverage
leverage: float,
is_short: bool
) -> Tuple[float, Optional[float]]:
# if TradingMode == TradingMode.MARGIN:
# interest_rate = self.get_interest_rate(
# pair=pair,
# open_rate=open_rate,
# is_short=is_short
# )
if self.trading_mode == TradingMode.SPOT:
return (0.0, None)
elif (
self.margin_mode == MarginMode.ISOLATED and
self.trading_mode == TradingMode.FUTURES
):
wallet_balance = (amount * open_rate) / leverage
isolated_liq = self.get_liquidation_price(
pair=pair,
open_rate=open_rate,
is_short=is_short,
position=amount,
wallet_balance=wallet_balance,
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 funding_fee_cutoff(self, open_date: datetime):
"""
:param open_date: The open date for a trade

View File

@ -578,42 +578,6 @@ class FreqtradeBot(LoggingMixin):
logger.info(f"Bids to asks delta for {pair} does not satisfy condition.")
return False
def leverage_prep(
self,
pair: str,
open_rate: float,
amount: float, # quote currency, includes leverage
leverage: float,
is_short: bool
) -> Tuple[float, Optional[float]]:
# if TradingMode == TradingMode.MARGIN:
# interest_rate = self.exchange.get_interest_rate(
# pair=pair,
# open_rate=open_rate,
# is_short=is_short
# )
if self.trading_mode == TradingMode.SPOT:
return (0.0, None)
elif (
self.margin_mode == MarginMode.ISOLATED and
self.trading_mode == TradingMode.FUTURES
):
wallet_balance = (amount * open_rate)/leverage
isolated_liq = self.exchange.get_liquidation_price(
pair=pair,
open_rate=open_rate,
is_short=is_short,
position=amount,
wallet_balance=wallet_balance,
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,
pair: str,
@ -724,7 +688,7 @@ class FreqtradeBot(LoggingMixin):
amount = safe_value_fallback(order, 'filled', 'amount')
enter_limit_filled_price = safe_value_fallback(order, 'average', 'price')
interest_rate, isolated_liq = self.leverage_prep(
interest_rate, isolated_liq = self.exchange.leverage_prep(
leverage=leverage,
pair=pair,
amount=amount,

View File

@ -592,42 +592,6 @@ class Backtesting:
else:
return self._get_sell_trade_entry_for_candle(trade, sell_row)
def _leverage_prep(
self,
pair: str,
open_rate: float,
amount: float, # quote currency, includes leverage
leverage: float,
is_short: bool
) -> Tuple[float, Optional[float]]:
# if TradingMode == TradingMode.MARGIN:
# interest_rate = self.exchange.get_interest_rate(
# pair=pair,
# open_rate=open_rate,
# is_short=is_short
# )
if self.trading_mode == TradingMode.SPOT:
return (0.0, None)
elif (
self.margin_mode == MarginMode.ISOLATED and
self.trading_mode == TradingMode.FUTURES
):
wallet_balance = (amount * open_rate)/leverage
isolated_liq = self.exchange.get_liquidation_price(
pair=pair,
open_rate=open_rate,
is_short=is_short,
position=amount,
wallet_balance=wallet_balance,
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 _enter_trade(self, pair: str, row: Tuple, direction: str,
stake_amount: Optional[float] = None,
trade: Optional[LocalTrade] = None) -> Optional[LocalTrade]:
@ -702,7 +666,7 @@ class Backtesting:
self.order_id_counter += 1
amount = round((stake_amount / propose_rate) * leverage, 8)
is_short = (direction == 'short')
(interest_rate, isolated_liq) = self._leverage_prep(
(interest_rate, isolated_liq) = self.exchange.leverage_prep(
pair=pair,
open_rate=propose_rate,
amount=amount,

View File

@ -4527,3 +4527,127 @@ def test__get_params(mocker, default_conf, exchange_name):
time_in_force='ioc',
leverage=3.0,
) == params2
@pytest.mark.parametrize('liquidation_buffer', [0.0, 0.05])
@pytest.mark.parametrize(
"is_short,trading_mode,exchange_name,margin_mode,leverage,open_rate,amount,expected_liq", [
(False, 'spot', 'binance', '', 5.0, 10.0, 1.0, None),
(True, 'spot', 'binance', '', 5.0, 10.0, 1.0, None),
(False, 'spot', 'gateio', '', 5.0, 10.0, 1.0, None),
(True, 'spot', 'gateio', '', 5.0, 10.0, 1.0, None),
(False, 'spot', 'okx', '', 5.0, 10.0, 1.0, None),
(True, 'spot', 'okx', '', 5.0, 10.0, 1.0, None),
# Binance, short
(True, 'futures', 'binance', 'isolated', 5.0, 10.0, 1.0, 11.89108910891089),
(True, 'futures', 'binance', 'isolated', 3.0, 10.0, 1.0, 13.211221122079207),
(True, 'futures', 'binance', 'isolated', 5.0, 8.0, 1.0, 9.514851485148514),
(True, 'futures', 'binance', 'isolated', 5.0, 10.0, 0.6, 12.557755775577558),
# Binance, long
(False, 'futures', 'binance', 'isolated', 5, 10, 1.0, 8.070707070707071),
(False, 'futures', 'binance', 'isolated', 5, 8, 1.0, 6.454545454545454),
(False, 'futures', 'binance', 'isolated', 3, 10, 1.0, 6.717171717171718),
(False, 'futures', 'binance', 'isolated', 5, 10, 0.6, 7.39057239057239),
# Gateio/okx, short
(True, 'futures', 'gateio', 'isolated', 5, 10, 1.0, 11.87413417771621),
(True, 'futures', 'gateio', 'isolated', 5, 10, 2.0, 11.87413417771621),
(True, 'futures', 'gateio', 'isolated', 3, 10, 1.0, 13.476180850346978),
(True, 'futures', 'gateio', 'isolated', 5, 8, 1.0, 9.499307342172967),
# Gateio/okx, long
(False, 'futures', 'gateio', 'isolated', 5.0, 10.0, 1.0, 8.085708510208207),
(False, 'futures', 'gateio', 'isolated', 3.0, 10.0, 1.0, 6.738090425173506),
# (True, 'futures', 'okx', 'isolated', 11.87413417771621),
# (False, 'futures', 'okx', 'isolated', 8.085708510208207),
]
)
def test_leverage_prep(
mocker,
default_conf_usdt,
is_short,
trading_mode,
exchange_name,
margin_mode,
leverage,
open_rate,
amount,
expected_liq,
liquidation_buffer,
):
"""
position = 0.2 * 5
wb: wallet balance (stake_amount if isolated)
cum_b: maintenance amount
side_1: -1 if is_short else 1
ep1: entry price
mmr_b: maintenance margin ratio
Binance, Short
leverage = 5, open_rate = 10, amount = 1.0
((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position))
((2 + 0.01) - ((-1) * 1 * 10)) / ((1 * 0.01) - ((-1) * 1)) = 11.89108910891089
leverage = 3, open_rate = 10, amount = 1.0
((3.3333333333 + 0.01) - ((-1) * 1.0 * 10)) / ((1.0 * 0.01) - ((-1) * 1.0)) = 13.2112211220
leverage = 5, open_rate = 8, amount = 1.0
((1.6 + 0.01) - ((-1) * 1 * 8)) / ((1 * 0.01) - ((-1) * 1)) = 9.514851485148514
leverage = 5, open_rate = 10, amount = 0.6
((1.6 + 0.01) - ((-1) * 0.6 * 10)) / ((0.6 * 0.01) - ((-1) * 0.6)) = 12.557755775577558
Binance, Long
leverage = 5, open_rate = 10, amount = 1.0
((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position))
((2 + 0.01) - (1 * 1 * 10)) / ((1 * 0.01) - (1 * 1)) = 8.070707070707071
leverage = 5, open_rate = 8, amount = 1.0
((1.6 + 0.01) - (1 * 1 * 8)) / ((1 * 0.01) - (1 * 1)) = 6.454545454545454
leverage = 3, open_rate = 10, amount = 1.0
((2 + 0.01) - (1 * 0.6 * 10)) / ((0.6 * 0.01) - (1 * 0.6)) = 6.717171717171718
leverage = 5, open_rate = 10, amount = 0.6
((1.6 + 0.01) - (1 * 0.6 * 10)) / ((0.6 * 0.01) - (1 * 0.6)) = 7.39057239057239
Gateio/Okx, Short
leverage = 5, open_rate = 10, amount = 1.0
(open_rate + (wallet_balance / position)) / (1 + (mm_ratio + taker_fee_rate))
(10 + (2 / 1.0)) / (1 + (0.01 + 0.0006)) = 11.87413417771621
leverage = 5, open_rate = 10, amount = 2.0
(10 + (4 / 2.0)) / (1 + (0.01 + 0.0006)) = 11.87413417771621
leverage = 3, open_rate = 10, amount = 1.0
(10 + (3.3333333333333 / 1.0)) / (1 - (0.01 + 0.0006)) = 13.476180850346978
leverage = 5, open_rate = 8, amount = 1.0
(8 + (1.6 / 1.0)) / (1 + (0.01 + 0.0006)) = 9.499307342172967
Gateio/Okx, Long
leverage = 5, open_rate = 10, amount = 1.0
(open_rate - (wallet_balance / position)) / (1 - (mm_ratio + taker_fee_rate))
(10 - (2 / 1)) / (1 - (0.01 + 0.0006)) = 8.085708510208207
leverage = 5, open_rate = 10, amount = 2.0
(10 - (4 / 2.0)) / (1 + (0.01 + 0.0006)) = 7.916089451810806
leverage = 3, open_rate = 10, amount = 1.0
(10 - (3.333333333333333333 / 1.0)) / (1 - (0.01 + 0.0006)) = 6.738090425173506
leverage = 5, open_rate = 8, amount = 1.0
(8 - (1.6 / 1.0)) / (1 + (0.01 + 0.0006)) = 6.332871561448645
"""
default_conf_usdt['liquidation_buffer'] = liquidation_buffer
default_conf_usdt['trading_mode'] = trading_mode
default_conf_usdt['exchange']['name'] = exchange_name
default_conf_usdt['margin_mode'] = margin_mode
mocker.patch('freqtrade.exchange.Gateio.validate_ordertypes')
exchange = get_patched_exchange(mocker, default_conf_usdt)
exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(0.01, 0.01))
exchange.name = exchange_name
# default_conf_usdt.update({
# "dry_run": False,
# })
(interest, liq) = exchange.leverage_prep(
pair='ETH/USDT:USDT',
open_rate=open_rate,
amount=amount,
leverage=leverage,
is_short=is_short,
)
assert interest == 0.0
if expected_liq is None:
assert liq is None
else:
buffer_amount = liquidation_buffer * abs(open_rate - expected_liq)
expected_liq = expected_liq - buffer_amount if is_short else expected_liq + buffer_amount
isclose(expected_liq, liq)

View File

@ -3,6 +3,7 @@
import random
from copy import deepcopy
from datetime import datetime, timedelta, timezone
from math import isclose
from pathlib import Path
from unittest.mock import MagicMock, PropertyMock
@ -10,7 +11,6 @@ import numpy as np
import pandas as pd
import pytest
from arrow import Arrow
from math import isclose
from freqtrade import constants
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_backtesting

View File

@ -4839,132 +4839,6 @@ def test_get_valid_price(mocker, default_conf_usdt) -> None:
assert valid_price_at_min_alwd < proposed_price
@pytest.mark.parametrize('liquidation_buffer', [0.0, 0.05])
@pytest.mark.parametrize(
"is_short,trading_mode,exchange_name,margin_mode,leverage,open_rate,amount,expected_liq", [
(False, 'spot', 'binance', '', 5.0, 10.0, 1.0, None),
(True, 'spot', 'binance', '', 5.0, 10.0, 1.0, None),
(False, 'spot', 'gateio', '', 5.0, 10.0, 1.0, None),
(True, 'spot', 'gateio', '', 5.0, 10.0, 1.0, None),
(False, 'spot', 'okx', '', 5.0, 10.0, 1.0, None),
(True, 'spot', 'okx', '', 5.0, 10.0, 1.0, None),
# Binance, short
(True, 'futures', 'binance', 'isolated', 5.0, 10.0, 1.0, 11.89108910891089),
(True, 'futures', 'binance', 'isolated', 3.0, 10.0, 1.0, 13.211221122079207),
(True, 'futures', 'binance', 'isolated', 5.0, 8.0, 1.0, 9.514851485148514),
(True, 'futures', 'binance', 'isolated', 5.0, 10.0, 0.6, 12.557755775577558),
# Binance, long
(False, 'futures', 'binance', 'isolated', 5, 10, 1.0, 8.070707070707071),
(False, 'futures', 'binance', 'isolated', 5, 8, 1.0, 6.454545454545454),
(False, 'futures', 'binance', 'isolated', 3, 10, 1.0, 6.717171717171718),
(False, 'futures', 'binance', 'isolated', 5, 10, 0.6, 7.39057239057239),
# Gateio/okx, short
(True, 'futures', 'gateio', 'isolated', 5, 10, 1.0, 11.87413417771621),
(True, 'futures', 'gateio', 'isolated', 5, 10, 2.0, 11.87413417771621),
(True, 'futures', 'gateio', 'isolated', 3, 10, 1.0, 13.476180850346978),
(True, 'futures', 'gateio', 'isolated', 5, 8, 1.0, 9.499307342172967),
# Gateio/okx, long
(False, 'futures', 'gateio', 'isolated', 5.0, 10.0, 1.0, 8.085708510208207),
(False, 'futures', 'gateio', 'isolated', 3.0, 10.0, 1.0, 6.738090425173506),
# (True, 'futures', 'okx', 'isolated', 11.87413417771621),
# (False, 'futures', 'okx', 'isolated', 8.085708510208207),
]
)
def test_leverage_prep(
mocker,
default_conf_usdt,
is_short,
trading_mode,
exchange_name,
margin_mode,
leverage,
open_rate,
amount,
expected_liq,
liquidation_buffer,
):
"""
position = 0.2 * 5
wb: wallet balance (stake_amount if isolated)
cum_b: maintenance amount
side_1: -1 if is_short else 1
ep1: entry price
mmr_b: maintenance margin ratio
Binance, Short
leverage = 5, open_rate = 10, amount = 1.0
((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position))
((2 + 0.01) - ((-1) * 1 * 10)) / ((1 * 0.01) - ((-1) * 1)) = 11.89108910891089
leverage = 3, open_rate = 10, amount = 1.0
((3.3333333333 + 0.01) - ((-1) * 1.0 * 10)) / ((1.0 * 0.01) - ((-1) * 1.0)) = 13.2112211220
leverage = 5, open_rate = 8, amount = 1.0
((1.6 + 0.01) - ((-1) * 1 * 8)) / ((1 * 0.01) - ((-1) * 1)) = 9.514851485148514
leverage = 5, open_rate = 10, amount = 0.6
((1.6 + 0.01) - ((-1) * 0.6 * 10)) / ((0.6 * 0.01) - ((-1) * 0.6)) = 12.557755775577558
Binance, Long
leverage = 5, open_rate = 10, amount = 1.0
((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position))
((2 + 0.01) - (1 * 1 * 10)) / ((1 * 0.01) - (1 * 1)) = 8.070707070707071
leverage = 5, open_rate = 8, amount = 1.0
((1.6 + 0.01) - (1 * 1 * 8)) / ((1 * 0.01) - (1 * 1)) = 6.454545454545454
leverage = 3, open_rate = 10, amount = 1.0
((2 + 0.01) - (1 * 0.6 * 10)) / ((0.6 * 0.01) - (1 * 0.6)) = 6.717171717171718
leverage = 5, open_rate = 10, amount = 0.6
((1.6 + 0.01) - (1 * 0.6 * 10)) / ((0.6 * 0.01) - (1 * 0.6)) = 7.39057239057239
Gateio/Okx, Short
leverage = 5, open_rate = 10, amount = 1.0
(open_rate + (wallet_balance / position)) / (1 + (mm_ratio + taker_fee_rate))
(10 + (2 / 1.0)) / (1 + (0.01 + 0.0006)) = 11.87413417771621
leverage = 5, open_rate = 10, amount = 2.0
(10 + (4 / 2.0)) / (1 + (0.01 + 0.0006)) = 11.87413417771621
leverage = 3, open_rate = 10, amount = 1.0
(10 + (3.3333333333333 / 1.0)) / (1 - (0.01 + 0.0006)) = 13.476180850346978
leverage = 5, open_rate = 8, amount = 1.0
(8 + (1.6 / 1.0)) / (1 + (0.01 + 0.0006)) = 9.499307342172967
Gateio/Okx, Long
leverage = 5, open_rate = 10, amount = 1.0
(open_rate - (wallet_balance / position)) / (1 - (mm_ratio + taker_fee_rate))
(10 - (2 / 1)) / (1 - (0.01 + 0.0006)) = 8.085708510208207
leverage = 5, open_rate = 10, amount = 2.0
(10 - (4 / 2.0)) / (1 + (0.01 + 0.0006)) = 7.916089451810806
leverage = 3, open_rate = 10, amount = 1.0
(10 - (3.333333333333333333 / 1.0)) / (1 - (0.01 + 0.0006)) = 6.738090425173506
leverage = 5, open_rate = 8, amount = 1.0
(8 - (1.6 / 1.0)) / (1 + (0.01 + 0.0006)) = 6.332871561448645
"""
default_conf_usdt['liquidation_buffer'] = liquidation_buffer
default_conf_usdt['trading_mode'] = trading_mode
default_conf_usdt['exchange']['name'] = exchange_name
default_conf_usdt['margin_mode'] = margin_mode
mocker.patch('freqtrade.exchange.Gateio.validate_ordertypes')
patch_RPCManager(mocker)
patch_exchange(mocker, id=exchange_name)
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(0.01, 0.01))
freqtrade.exchange.name = exchange_name
# default_conf_usdt.update({
# "dry_run": False,
# })
(interest, liq) = freqtrade.leverage_prep(
pair='ETH/USDT:USDT',
open_rate=open_rate,
amount=amount,
leverage=leverage,
is_short=is_short,
)
assert interest == 0.0
if expected_liq is None:
assert liq is None
else:
buffer_amount = liquidation_buffer * abs(open_rate - expected_liq)
expected_liq = expected_liq - buffer_amount if is_short else expected_liq + buffer_amount
isclose(expected_liq, liq)
@pytest.mark.parametrize('trading_mode,calls,t1,t2', [
('spot', 0, "2021-09-01 00:00:00", "2021-09-01 08:00:00"),
('margin', 0, "2021-09-01 00:00:00", "2021-09-01 08:00:00"),