Merge branch 'lev-exchange' into lev-freqtradebot

This commit is contained in:
Sam Germain 2021-09-12 03:15:32 -06:00
commit 1d7a8f667a
7 changed files with 142 additions and 43 deletions

View File

@ -112,9 +112,6 @@ class Binance(Exchange):
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) from e raise OperationalException(e) from e
def _apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float):
return stake_amount / leverage
@retrier @retrier
def fill_leverage_brackets(self): def fill_leverage_brackets(self):
""" """
@ -154,3 +151,27 @@ class Binance(Exchange):
if nominal_value >= min_amount: if nominal_value >= min_amount:
max_lev = 1/margin_req max_lev = 1/margin_req
return max_lev return max_lev
@retrier
def _set_leverage(
self,
leverage: float,
pair: Optional[str] = None,
trading_mode: Optional[TradingMode] = None
):
"""
Set's the leverage before making a trade, in order to not
have the same leverage on every trade
"""
trading_mode = trading_mode or self.trading_mode
try:
if trading_mode == TradingMode.FUTURES:
self._api.set_leverage(symbol=pair, leverage=leverage)
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not set leverage due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e

View File

@ -145,7 +145,7 @@ class Exchange:
self._api_async = self._init_ccxt( self._api_async = self._init_ccxt(
exchange_config, ccxt_async, ccxt_kwargs=ccxt_async_config) exchange_config, ccxt_async, ccxt_kwargs=ccxt_async_config)
trading_mode: TradingMode = ( self.trading_mode: TradingMode = (
TradingMode(config.get('trading_mode')) TradingMode(config.get('trading_mode'))
if config.get('trading_mode') if config.get('trading_mode')
else TradingMode.SPOT else TradingMode.SPOT
@ -156,7 +156,7 @@ class Exchange:
else None else None
) )
if trading_mode != TradingMode.SPOT: if self.trading_mode != TradingMode.SPOT:
self.fill_leverage_brackets() self.fill_leverage_brackets()
logger.info('Using Exchange "%s"', self.name) logger.info('Using Exchange "%s"', self.name)
@ -176,7 +176,7 @@ class Exchange:
self.validate_order_time_in_force(config.get('order_time_in_force', {})) self.validate_order_time_in_force(config.get('order_time_in_force', {}))
self.validate_required_startup_candles(config.get('startup_candle_count', 0), self.validate_required_startup_candles(config.get('startup_candle_count', 0),
config.get('timeframe', '')) config.get('timeframe', ''))
self.validate_trading_mode_and_collateral(trading_mode, collateral) self.validate_trading_mode_and_collateral(self.trading_mode, collateral)
# Converts the interval provided in minutes in config to seconds # Converts the interval provided in minutes in config to seconds
self.markets_refresh_interval: int = exchange_config.get( self.markets_refresh_interval: int = exchange_config.get(
"markets_refresh_interval", 60) * 60 "markets_refresh_interval", 60) * 60
@ -630,7 +630,7 @@ class Exchange:
:param stake_amount: The stake amount for a pair before leverage is considered :param stake_amount: The stake amount for a pair before leverage is considered
:param leverage: The amount of leverage being used on the current trade :param leverage: The amount of leverage being used on the current trade
""" """
return stake_amount return stake_amount / leverage
# Dry-run methods # Dry-run methods
@ -771,12 +771,14 @@ class Exchange:
# Order handling # Order handling
def create_order(self, pair: str, ordertype: str, side: str, amount: float, def create_order(self, pair: str, ordertype: str, side: str, amount: float,
rate: float, time_in_force: str = 'gtc') -> Dict: rate: float, time_in_force: str = 'gtc', leverage=1.0) -> Dict:
if self._config['dry_run']: if self._config['dry_run']:
dry_order = self.create_dry_run_order(pair, ordertype, side, amount, rate) dry_order = self.create_dry_run_order(pair, ordertype, side, amount, rate)
return dry_order return dry_order
if self.trading_mode != TradingMode.SPOT:
self._set_leverage(leverage, pair)
params = self._params.copy() params = self._params.copy()
if time_in_force != 'gtc' and ordertype != 'market': if time_in_force != 'gtc' and ordertype != 'market':
param = self._ft_has.get('time_in_force_parameter', '') param = self._ft_has.get('time_in_force_parameter', '')
@ -1600,7 +1602,12 @@ class Exchange:
return 1.0 return 1.0
@retrier @retrier
def set_leverage(self, leverage: float, pair: Optional[str]): def _set_leverage(
self,
leverage: float,
pair: Optional[str] = None,
trading_mode: Optional[TradingMode] = None
):
""" """
Set's the leverage before making a trade, in order to not Set's the leverage before making a trade, in order to not
have the same leverage on every trade have the same leverage on every trade

View File

@ -146,6 +146,7 @@ class Kraken(Exchange):
leverages = {} leverages = {}
for pair, market in self.markets.items(): for pair, market in self.markets.items():
leverages[pair] = [1]
info = market['info'] info = market['info']
leverage_buy = info.get('leverage_buy', []) leverage_buy = info.get('leverage_buy', [])
leverage_sell = info.get('leverage_sell', []) leverage_sell = info.get('leverage_sell', [])
@ -155,12 +156,12 @@ class Kraken(Exchange):
f"The buy({leverage_buy}) and sell({leverage_sell}) leverage are not equal" f"The buy({leverage_buy}) and sell({leverage_sell}) leverage are not equal"
"for {pair}. Please notify freqtrade because this has never happened before" "for {pair}. Please notify freqtrade because this has never happened before"
) )
if max(leverage_buy) < max(leverage_sell): if max(leverage_buy) <= max(leverage_sell):
leverages[pair] = leverage_buy leverages[pair] += [int(lev) for lev in leverage_buy]
else: else:
leverages[pair] = leverage_sell leverages[pair] += [int(lev) for lev in leverage_sell]
else: else:
leverages[pair] = leverage_buy leverages[pair] += [int(lev) for lev in leverage_buy]
self._leverage_brackets = leverages self._leverage_brackets = leverages
def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float: def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float:
@ -171,9 +172,18 @@ class Kraken(Exchange):
""" """
return float(max(self._leverage_brackets[pair])) return float(max(self._leverage_brackets[pair]))
def set_leverage(self, pair, leverage): def _set_leverage(
self,
leverage: float,
pair: Optional[str] = None,
trading_mode: Optional[TradingMode] = None
):
""" """
Kraken set's the leverage as an option in the order object, so it doesn't do Kraken set's the leverage as an option in the order object, so we need to
anything in this function add it to params
""" """
return if leverage > 1.0:
self._params['leverage'] = leverage
else:
if 'leverage' in self._params:
del self._params['leverage']

View File

@ -4,6 +4,7 @@ from unittest.mock import MagicMock, PropertyMock
import ccxt import ccxt
import pytest import pytest
from freqtrade.enums import TradingMode
from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException
from tests.conftest import get_patched_exchange from tests.conftest import get_patched_exchange
from tests.exchange.test_exchange import ccxt_exceptionhandlers from tests.exchange.test_exchange import ccxt_exceptionhandlers
@ -232,3 +233,25 @@ def test_fill_leverage_brackets_binance(default_conf, mocker):
"fill_leverage_brackets", "fill_leverage_brackets",
"load_leverage_brackets" "load_leverage_brackets"
) )
def test__set_leverage_binance(mocker, default_conf):
api_mock = MagicMock()
api_mock.set_leverage = MagicMock()
type(api_mock).has = PropertyMock(return_value={'setLeverage': True})
exchange = get_patched_exchange(mocker, default_conf, id="binance")
exchange._set_leverage(3.0, trading_mode=TradingMode.MARGIN)
ccxt_exceptionhandlers(
mocker,
default_conf,
api_mock,
"binance",
"_set_leverage",
"set_leverage",
pair="XRP/USDT",
leverage=5.0,
trading_mode=TradingMode.FUTURES
)

View File

@ -2969,18 +2969,11 @@ def test_calculate_backoff(retrycount, max_retries, expected):
assert calculate_backoff(retrycount, max_retries) == expected assert calculate_backoff(retrycount, max_retries) == expected
@pytest.mark.parametrize('exchange,stake_amount,leverage,min_stake_with_lev', [ @pytest.mark.parametrize('exchange', ['binance', 'kraken', 'ftx'])
('binance', 9.0, 3.0, 3.0), @pytest.mark.parametrize('stake_amount,leverage,min_stake_with_lev', [
('binance', 20.0, 5.0, 4.0), (9.0, 3.0, 3.0),
('binance', 100.0, 100.0, 1.0), (20.0, 5.0, 4.0),
# Kraken (100.0, 100.0, 1.0)
('kraken', 9.0, 3.0, 9.0),
('kraken', 20.0, 5.0, 20.0),
('kraken', 100.0, 100.0, 100.0),
# FTX
('ftx', 9.0, 3.0, 9.0),
('ftx', 20.0, 5.0, 20.0),
('ftx', 100.0, 100.0, 100.0)
]) ])
def test_apply_leverage_to_stake_amount( def test_apply_leverage_to_stake_amount(
exchange, exchange,
@ -2994,12 +2987,12 @@ def test_apply_leverage_to_stake_amount(
assert exchange._apply_leverage_to_stake_amount(stake_amount, leverage) == min_stake_with_lev assert exchange._apply_leverage_to_stake_amount(stake_amount, leverage) == min_stake_with_lev
@pytest.mark.parametrize("collateral", [ @pytest.mark.parametrize("exchange_name,trading_mode", [
(Collateral.CROSS), ("binance", TradingMode.FUTURES),
(Collateral.ISOLATED) ("ftx", TradingMode.MARGIN),
("ftx", TradingMode.FUTURES)
]) ])
@pytest.mark.parametrize("exchange_name", [("ftx"), ("binance")]) def test__set_leverage(mocker, default_conf, exchange_name, trading_mode):
def test_set_leverage(mocker, default_conf, exchange_name, collateral):
api_mock = MagicMock() api_mock = MagicMock()
api_mock.set_leverage = MagicMock() api_mock.set_leverage = MagicMock()
@ -3010,10 +3003,11 @@ def test_set_leverage(mocker, default_conf, exchange_name, collateral):
default_conf, default_conf,
api_mock, api_mock,
exchange_name, exchange_name,
"set_leverage", "_set_leverage",
"set_leverage", "set_leverage",
pair="XRP/USDT", pair="XRP/USDT",
leverage=5.0 leverage=5.0,
trading_mode=trading_mode
) )
@ -3021,8 +3015,7 @@ def test_set_leverage(mocker, default_conf, exchange_name, collateral):
(Collateral.CROSS), (Collateral.CROSS),
(Collateral.ISOLATED) (Collateral.ISOLATED)
]) ])
@pytest.mark.parametrize("exchange_name", [("ftx"), ("binance")]) def test_set_margin_mode(mocker, default_conf, collateral):
def test_set_margin_mode(mocker, default_conf, exchange_name, collateral):
api_mock = MagicMock() api_mock = MagicMock()
api_mock.set_margin_mode = MagicMock() api_mock.set_margin_mode = MagicMock()
@ -3032,7 +3025,7 @@ def test_set_margin_mode(mocker, default_conf, exchange_name, collateral):
mocker, mocker,
default_conf, default_conf,
api_mock, api_mock,
exchange_name, "binance",
"set_margin_mode", "set_margin_mode",
"set_margin_mode", "set_margin_mode",
pair="XRP/USDT", pair="XRP/USDT",

View File

@ -1,9 +1,10 @@
from random import randint from random import randint
from unittest.mock import MagicMock from unittest.mock import MagicMock, PropertyMock
import ccxt import ccxt
import pytest import pytest
from freqtrade.enums import TradingMode
from freqtrade.exceptions import DependencyException, InvalidOrderException from freqtrade.exceptions import DependencyException, InvalidOrderException
from freqtrade.exchange.common import API_FETCH_ORDER_RETRY_COUNT from freqtrade.exchange.common import API_FETCH_ORDER_RETRY_COUNT
from tests.conftest import get_patched_exchange from tests.conftest import get_patched_exchange
@ -227,3 +228,26 @@ def test_fill_leverage_brackets_ftx(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, id="ftx") exchange = get_patched_exchange(mocker, default_conf, id="ftx")
exchange.fill_leverage_brackets() exchange.fill_leverage_brackets()
assert exchange._leverage_brackets == {} assert exchange._leverage_brackets == {}
@pytest.mark.parametrize("trading_mode", [
(TradingMode.MARGIN),
(TradingMode.FUTURES)
])
def test__set_leverage(mocker, default_conf, trading_mode):
api_mock = MagicMock()
api_mock.set_leverage = MagicMock()
type(api_mock).has = PropertyMock(return_value={'setLeverage': True})
ccxt_exceptionhandlers(
mocker,
default_conf,
api_mock,
"ftx",
"_set_leverage",
"set_leverage",
pair="XRP/USDT",
leverage=5.0,
trading_mode=trading_mode
)

View File

@ -294,7 +294,28 @@ def test_fill_leverage_brackets_kraken(default_conf, mocker):
exchange.fill_leverage_brackets() exchange.fill_leverage_brackets()
assert exchange._leverage_brackets == { assert exchange._leverage_brackets == {
'BLK/BTC': ['2', '3'], 'BLK/BTC': [1, 2, 3],
'TKN/BTC': ['2', '3', '4', '5'], 'TKN/BTC': [1, 2, 3, 4, 5],
'ETH/BTC': ['2'] 'ETH/BTC': [1, 2],
'LTC/BTC': [1],
'XRP/BTC': [1],
'NEO/BTC': [1],
'BTT/BTC': [1],
'ETH/USDT': [1],
'LTC/USDT': [1],
'LTC/USD': [1],
'XLTCUSDT': [1],
'LTC/ETH': [1]
} }
def test__set_leverage_kraken(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, id="kraken")
exchange._set_leverage(1)
assert 'leverage' not in exchange._params
exchange._set_leverage(3)
assert exchange._params['leverage'] == 3
exchange._set_leverage(1.0)
assert 'leverage' not in exchange._params
exchange._set_leverage(3.0)
assert exchange._params['leverage'] == 3