diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index f5a222d2d..fcd027d52 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -112,9 +112,6 @@ class Binance(Exchange): except ccxt.BaseError as e: raise OperationalException(e) from e - def _apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float): - return stake_amount / leverage - @retrier def fill_leverage_brackets(self): """ @@ -154,3 +151,27 @@ class Binance(Exchange): if nominal_value >= min_amount: max_lev = 1/margin_req 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 diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 03ab281c9..2fb63d201 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -145,7 +145,7 @@ class Exchange: self._api_async = self._init_ccxt( exchange_config, ccxt_async, ccxt_kwargs=ccxt_async_config) - trading_mode: TradingMode = ( + self.trading_mode: TradingMode = ( TradingMode(config.get('trading_mode')) if config.get('trading_mode') else TradingMode.SPOT @@ -156,7 +156,7 @@ class Exchange: else None ) - if trading_mode != TradingMode.SPOT: + if self.trading_mode != TradingMode.SPOT: self.fill_leverage_brackets() 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_required_startup_candles(config.get('startup_candle_count', 0), 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 self.markets_refresh_interval: int = exchange_config.get( "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 leverage: The amount of leverage being used on the current trade """ - return stake_amount + return stake_amount / leverage # Dry-run methods @@ -771,12 +771,14 @@ class Exchange: # Order handling 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']: dry_order = self.create_dry_run_order(pair, ordertype, side, amount, rate) return dry_order + if self.trading_mode != TradingMode.SPOT: + self._set_leverage(leverage, pair) params = self._params.copy() if time_in_force != 'gtc' and ordertype != 'market': param = self._ft_has.get('time_in_force_parameter', '') @@ -1600,7 +1602,12 @@ class Exchange: return 1.0 @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 have the same leverage on every trade diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index b72a92070..661000d4d 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -146,6 +146,7 @@ class Kraken(Exchange): leverages = {} for pair, market in self.markets.items(): + leverages[pair] = [1] info = market['info'] leverage_buy = info.get('leverage_buy', []) 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" "for {pair}. Please notify freqtrade because this has never happened before" ) - if max(leverage_buy) < max(leverage_sell): - leverages[pair] = leverage_buy + if max(leverage_buy) <= max(leverage_sell): + leverages[pair] += [int(lev) for lev in leverage_buy] else: - leverages[pair] = leverage_sell + leverages[pair] += [int(lev) for lev in leverage_sell] else: - leverages[pair] = leverage_buy + leverages[pair] += [int(lev) for lev in leverage_buy] self._leverage_brackets = leverages 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])) - 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 - anything in this function + Kraken set's the leverage as an option in the order object, so we need to + add it to params """ - return + if leverage > 1.0: + self._params['leverage'] = leverage + else: + if 'leverage' in self._params: + del self._params['leverage'] diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index 96287da44..dd012f4ab 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -4,6 +4,7 @@ from unittest.mock import MagicMock, PropertyMock import ccxt import pytest +from freqtrade.enums import TradingMode from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException from tests.conftest import get_patched_exchange from tests.exchange.test_exchange import ccxt_exceptionhandlers @@ -232,3 +233,25 @@ def test_fill_leverage_brackets_binance(default_conf, mocker): "fill_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 + ) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 239704bdd..3231d9811 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2969,18 +2969,11 @@ def test_calculate_backoff(retrycount, max_retries, expected): assert calculate_backoff(retrycount, max_retries) == expected -@pytest.mark.parametrize('exchange,stake_amount,leverage,min_stake_with_lev', [ - ('binance', 9.0, 3.0, 3.0), - ('binance', 20.0, 5.0, 4.0), - ('binance', 100.0, 100.0, 1.0), - # Kraken - ('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) +@pytest.mark.parametrize('exchange', ['binance', 'kraken', 'ftx']) +@pytest.mark.parametrize('stake_amount,leverage,min_stake_with_lev', [ + (9.0, 3.0, 3.0), + (20.0, 5.0, 4.0), + (100.0, 100.0, 1.0) ]) def test_apply_leverage_to_stake_amount( 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 -@pytest.mark.parametrize("collateral", [ - (Collateral.CROSS), - (Collateral.ISOLATED) +@pytest.mark.parametrize("exchange_name,trading_mode", [ + ("binance", TradingMode.FUTURES), + ("ftx", TradingMode.MARGIN), + ("ftx", TradingMode.FUTURES) ]) -@pytest.mark.parametrize("exchange_name", [("ftx"), ("binance")]) -def test_set_leverage(mocker, default_conf, exchange_name, collateral): +def test__set_leverage(mocker, default_conf, exchange_name, trading_mode): api_mock = MagicMock() api_mock.set_leverage = MagicMock() @@ -3010,10 +3003,11 @@ def test_set_leverage(mocker, default_conf, exchange_name, collateral): default_conf, api_mock, exchange_name, - "set_leverage", + "_set_leverage", "set_leverage", 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.ISOLATED) ]) -@pytest.mark.parametrize("exchange_name", [("ftx"), ("binance")]) -def test_set_margin_mode(mocker, default_conf, exchange_name, collateral): +def test_set_margin_mode(mocker, default_conf, collateral): api_mock = MagicMock() api_mock.set_margin_mode = MagicMock() @@ -3032,7 +3025,7 @@ def test_set_margin_mode(mocker, default_conf, exchange_name, collateral): mocker, default_conf, api_mock, - exchange_name, + "binance", "set_margin_mode", "set_margin_mode", pair="XRP/USDT", diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index 3fcd35e37..a8d715c9a 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -1,9 +1,10 @@ from random import randint -from unittest.mock import MagicMock +from unittest.mock import MagicMock, PropertyMock import ccxt import pytest +from freqtrade.enums import TradingMode from freqtrade.exceptions import DependencyException, InvalidOrderException from freqtrade.exchange.common import API_FETCH_ORDER_RETRY_COUNT 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.fill_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 + ) diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index 880af7816..0055a0dd1 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -294,7 +294,28 @@ def test_fill_leverage_brackets_kraken(default_conf, mocker): exchange.fill_leverage_brackets() assert exchange._leverage_brackets == { - 'BLK/BTC': ['2', '3'], - 'TKN/BTC': ['2', '3', '4', '5'], - 'ETH/BTC': ['2'] + 'BLK/BTC': [1, 2, 3], + 'TKN/BTC': [1, 2, 3, 4, 5], + '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