From 54dd9ce7ad385083aaf374e4a976108f3514923a Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 24 Jul 2021 01:32:42 -0600 Subject: [PATCH 01/53] Add prep functions to exchange --- freqtrade/exchange/binance.py | 103 ++++++++++++++++++++++++++++++++- freqtrade/exchange/bittrex.py | 23 +++++++- freqtrade/exchange/exchange.py | 54 ++++++++++++++++- freqtrade/exchange/kraken.py | 22 ++++++- 4 files changed, 196 insertions(+), 6 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 189f5f481..33f22f970 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -1,6 +1,6 @@ """ Binance exchange subclass """ import logging -from typing import Dict +from typing import Dict, Optional import ccxt @@ -90,3 +90,104 @@ class Binance(Exchange): f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e except ccxt.BaseError as e: raise OperationalException(e) from e + + def transfer(self, asset: str, amount: float, frm: str, to: str, pair: Optional[str]): + res = self._api.sapi_post_margin_isolated_transfer({ + "asset": asset, + "amount": amount, + "transFrom": frm, + "transTo": to, + "symbol": pair + }) + logger.info(f"Transfer response: {res}") + + def borrow(self, asset: str, amount: float, pair: str): + res = self._api.sapi_post_margin_loan({ + "asset": asset, + "isIsolated": True, + "symbol": pair, + "amount": amount + }) # borrow from binance + logger.info(f"Borrow response: {res}") + + def repay(self, asset: str, amount: float, pair: str): + res = self._api.sapi_post_margin_repay({ + "asset": asset, + "isIsolated": True, + "symbol": pair, + "amount": amount + }) # borrow from binance + logger.info(f"Borrow response: {res}") + + def setup_leveraged_enter( + self, + pair: str, + leverage: float, + amount: float, + quote_currency: Optional[str], + is_short: Optional[bool] + ): + if not quote_currency or not is_short: + raise OperationalException( + "quote_currency and is_short are required arguments to setup_leveraged_enter" + " when trading with leverage on binance" + ) + open_rate = 2 # TODO-mg: get the real open_rate, or real stake_amount + stake_amount = amount * open_rate + if is_short: + borrowed = stake_amount * ((leverage-1)/leverage) + else: + borrowed = amount + + self.transfer( # Transfer to isolated margin + asset=quote_currency, + amount=stake_amount, + frm='SPOT', + to='ISOLATED_MARGIN', + pair=pair + ) + + self.borrow( + asset=quote_currency, + amount=borrowed, + pair=pair + ) # borrow from binance + + def complete_leveraged_exit( + self, + pair: str, + leverage: float, + amount: float, + quote_currency: Optional[str], + is_short: Optional[bool] + ): + + if not quote_currency or not is_short: + raise OperationalException( + "quote_currency and is_short are required arguments to setup_leveraged_enter" + " when trading with leverage on binance" + ) + + open_rate = 2 # TODO-mg: get the real open_rate, or real stake_amount + stake_amount = amount * open_rate + if is_short: + borrowed = stake_amount * ((leverage-1)/leverage) + else: + borrowed = amount + + self.repay( + asset=quote_currency, + amount=borrowed, + pair=pair + ) # repay binance + + self.transfer( # Transfer to isolated margin + asset=quote_currency, + amount=stake_amount, + frm='ISOLATED_MARGIN', + to='SPOT', + pair=pair + ) + + def apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float): + return stake_amount / leverage diff --git a/freqtrade/exchange/bittrex.py b/freqtrade/exchange/bittrex.py index 69e2f2b8d..e4d344d27 100644 --- a/freqtrade/exchange/bittrex.py +++ b/freqtrade/exchange/bittrex.py @@ -1,8 +1,9 @@ """ Bittrex exchange subclass """ import logging -from typing import Dict +from typing import Dict, Optional from freqtrade.exchange import Exchange +from freqtrade.exceptions import OperationalException logger = logging.getLogger(__name__) @@ -23,3 +24,23 @@ class Bittrex(Exchange): }, "l2_limit_range": [1, 25, 500], } + + def setup_leveraged_enter( + self, + pair: str, + leverage: float, + amount: float, + quote_currency: Optional[str], + is_short: Optional[bool] + ): + raise OperationalException("Bittrex does not support leveraged trading") + + def complete_leveraged_exit( + self, + pair: str, + leverage: float, + amount: float, + quote_currency: Optional[str], + is_short: Optional[bool] + ): + raise OperationalException("Bittrex does not support leveraged trading") diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 80f20b17e..e976a266f 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -189,6 +189,7 @@ class Exchange: 'secret': exchange_config.get('secret'), 'password': exchange_config.get('password'), 'uid': exchange_config.get('uid', ''), + 'options': exchange_config.get('options', {}) } if ccxt_kwargs: logger.info('Applying additional ccxt config: %s', ccxt_kwargs) @@ -540,8 +541,9 @@ class Exchange: else: return 1 / pow(10, precision) - def get_min_pair_stake_amount(self, pair: str, price: float, - stoploss: float) -> Optional[float]: + def get_min_pair_stake_amount(self, pair: str, price: float, stoploss: float, + leverage: Optional[float] = 1.0) -> Optional[float]: + # TODO-mg: Using leverage makes the min stake amount lower (on binance at least) try: market = self.markets[pair] except KeyError: @@ -575,7 +577,20 @@ class Exchange: # The value returned should satisfy both limits: for amount (base currency) and # for cost (quote, stake currency), so max() is used here. # See also #2575 at github. - return max(min_stake_amounts) * amount_reserve_percent + return self.apply_leverage_to_stake_amount( + max(min_stake_amounts) * amount_reserve_percent, + leverage or 1.0 + ) + + def apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float): + """ + #* Should be implemented by child classes if leverage affects the stake_amount + Takes the minimum stake amount for a pair with no leverage and returns the minimum + stake amount when 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 + """ + return stake_amount # Dry-run methods @@ -713,6 +728,15 @@ class Exchange: raise InvalidOrderException( f'Tried to get an invalid dry-run-order (id: {order_id}). Message: {e}') from e + def get_max_leverage(self, pair: str, stake_amount: float, price: float) -> float: + """ + Gets the maximum leverage available on this pair that is below the config leverage + but higher than the config min_leverage + """ + + raise OperationalException(f"Leverage is not available on {self.name} using freqtrade") + return 1.0 + # Order handling def create_order(self, pair: str, ordertype: str, side: str, amount: float, @@ -737,6 +761,7 @@ class Exchange: order = self._api.create_order(pair, ordertype, side, amount, rate_for_order, params) self._log_exchange_response('create_order', order) + return order except ccxt.InsufficientFunds as e: @@ -757,6 +782,26 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e + def setup_leveraged_enter( + self, + pair: str, + leverage: float, + amount: float, + quote_currency: Optional[str], + is_short: Optional[bool] + ): + raise OperationalException(f"Leverage is not available on {self.name} using freqtrade") + + def complete_leveraged_exit( + self, + pair: str, + leverage: float, + amount: float, + quote_currency: Optional[str], + is_short: Optional[bool] + ): + raise OperationalException(f"Leverage is not available on {self.name} using freqtrade") + def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) @@ -1525,6 +1570,9 @@ class Exchange: self._async_get_trade_history(pair=pair, since=since, until=until, from_id=from_id)) + def transfer(self, asset: str, amount: float, frm: str, to: str, pair: Optional[str]): + self._api.transfer(asset, amount, frm, to) + def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool: return exchange_name in ccxt_exchanges(ccxt_module) diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 1b069aa6c..d7dfd3f3b 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -1,6 +1,6 @@ """ Kraken exchange subclass """ import logging -from typing import Any, Dict +from typing import Any, Dict, Optional import ccxt @@ -124,3 +124,23 @@ class Kraken(Exchange): f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e except ccxt.BaseError as e: raise OperationalException(e) from e + + def setup_leveraged_enter( + self, + pair: str, + leverage: float, + amount: float, + quote_currency: Optional[str], + is_short: Optional[bool] + ): + return + + def complete_leveraged_exit( + self, + pair: str, + leverage: float, + amount: float, + quote_currency: Optional[str], + is_short: Optional[bool] + ): + return From ebf531081755592e58da0ee89ecadbe31b9cc717 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 25 Jul 2021 23:40:38 -0600 Subject: [PATCH 02/53] Added get_interest template method in exchange --- freqtrade/exchange/exchange.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index e976a266f..fba673c63 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -584,7 +584,7 @@ class Exchange: def apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float): """ - #* Should be implemented by child classes if leverage affects the stake_amount + # * Should be implemented by child classes if leverage affects the stake_amount Takes the minimum stake amount for a pair with no leverage and returns the minimum stake amount when leverage is considered :param stake_amount: The stake amount for a pair before leverage is considered @@ -1573,6 +1573,16 @@ class Exchange: def transfer(self, asset: str, amount: float, frm: str, to: str, pair: Optional[str]): self._api.transfer(asset, amount, frm, to) + def get_isolated_liq(self, pair: str, open_rate: float, + amount: float, leverage: float, is_short: bool) -> float: + raise OperationalException( + f"Isolated margin is not available on {self.name} using freqtrade" + ) + + def get_interest_rate(self, pair: str, open_rate: float, is_short: bool) -> float: + # TODO-mg: implement + return 0.0005 + def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool: return exchange_name in ccxt_exchanges(ccxt_module) From f4e26a616f9127ef7f15679b8b8649b64d6a9c65 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 26 Jul 2021 00:01:57 -0600 Subject: [PATCH 03/53] Exchange stoploss function takes side --- freqtrade/exchange/binance.py | 9 ++++++-- freqtrade/exchange/exchange.py | 5 +++-- freqtrade/exchange/ftx.py | 8 +++++-- freqtrade/exchange/kraken.py | 7 +++++-- freqtrade/freqtradebot.py | 16 ++++++++------ tests/exchange/test_binance.py | 21 ++++++++++--------- tests/exchange/test_exchange.py | 4 ++-- tests/exchange/test_ftx.py | 20 +++++++++--------- tests/exchange/test_kraken.py | 16 +++++++------- tests/test_freqtradebot.py | 37 ++++++++++++++++++++------------- 10 files changed, 85 insertions(+), 58 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 33f22f970..c285cec21 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -25,20 +25,25 @@ class Binance(Exchange): "l2_limit_range": [5, 10, 20, 50, 100, 500, 1000], } - def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. + :param side: "buy" or "sell" """ + # TODO-mg: Short support return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice']) @retrier(retries=0) - def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: + def stoploss(self, pair: str, amount: float, + stop_price: float, order_types: Dict, side: str) -> Dict: """ creates a stoploss limit order. this stoploss-limit is binance-specific. It may work with a limited number of other exchanges, but this has not been tested yet. + :param side: "buy" or "sell" """ + # TODO-mg: Short support # Limit price threshold: As limit price should always be below stop-price limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) rate = stop_price * limit_price_pct diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index fba673c63..0471c0149 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -802,14 +802,15 @@ class Exchange: ): raise OperationalException(f"Leverage is not available on {self.name} using freqtrade") - def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. """ raise OperationalException(f"stoploss is not implemented for {self.name}.") - def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: + def stoploss(self, pair: str, amount: float, + stop_price: float, order_types: Dict, side: str) -> Dict: """ creates a stoploss order. The precise ordertype is determined by the order_types dict or exchange default. diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 6cd549d60..4a078bbb7 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -31,21 +31,25 @@ class Ftx(Exchange): return (parent_check and market.get('spot', False) is True) - def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. """ + # TODO-mg: Short support return order['type'] == 'stop' and stop_loss > float(order['price']) @retrier(retries=0) - def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: + def stoploss(self, pair: str, amount: float, + stop_price: float, order_types: Dict, side: str) -> Dict: """ Creates a stoploss order. depending on order_types.stoploss configuration, uses 'market' or limit order. Limit orders are defined by having orderPrice set, otherwise a market order is used. """ + # TODO-mg: Short support + limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) limit_rate = stop_price * limit_price_pct diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index d7dfd3f3b..36c1608bd 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -67,20 +67,23 @@ class Kraken(Exchange): except ccxt.BaseError as e: raise OperationalException(e) from e - def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. """ + # TODO-mg: Short support return (order['type'] in ('stop-loss', 'stop-loss-limit') and stop_loss > float(order['price'])) @retrier(retries=0) - def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: + def stoploss(self, pair: str, amount: float, + stop_price: float, order_types: Dict, side: str) -> Dict: """ Creates a stoploss market order. Stoploss market orders is the only stoploss type supported by kraken. """ + # TODO-mg: Short support params = self._params.copy() if order_types.get('stoploss', 'market') == 'limit': diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 78f6da9ec..e2586ed28 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -728,9 +728,13 @@ class FreqtradeBot(LoggingMixin): :return: True if the order succeeded, and False in case of problems. """ try: - stoploss_order = self.exchange.stoploss(pair=trade.pair, amount=trade.amount, - stop_price=stop_price, - order_types=self.strategy.order_types) + stoploss_order = self.exchange.stoploss( + pair=trade.pair, + amount=trade.amount, + stop_price=stop_price, + order_types=self.strategy.order_types, + side=trade.exit_side + ) order_obj = Order.parse_from_ccxt_object(stoploss_order, trade.pair, 'stoploss') trade.orders.append(order_obj) @@ -819,11 +823,11 @@ class FreqtradeBot(LoggingMixin): # if trailing stoploss is enabled we check if stoploss value has changed # in which case we cancel stoploss order and put another one with new # value immediately - self.handle_trailing_stoploss_on_exchange(trade, stoploss_order) + self.handle_trailing_stoploss_on_exchange(trade, stoploss_order, side=trade.exit_side) return False - def handle_trailing_stoploss_on_exchange(self, trade: Trade, order: dict) -> None: + def handle_trailing_stoploss_on_exchange(self, trade: Trade, order: dict, side: str) -> None: """ Check to see if stoploss on exchange should be updated in case of trailing stoploss on exchange @@ -831,7 +835,7 @@ class FreqtradeBot(LoggingMixin): :param order: Current on exchange stoploss order :return: None """ - if self.exchange.stoploss_adjust(trade.stop_loss, order): + if self.exchange.stoploss_adjust(trade.stop_loss, order, side): # we check if the update is necessary update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60) if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() >= update_beat: diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index f2b508761..7b324efa2 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -32,12 +32,13 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') with pytest.raises(OperationalException): - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side="sell", order_types={'stoploss_on_exchange_limit_ratio': 1.05}) api_mock.create_order.reset_mock() order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio} - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types) + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order_types=order_types, side="sell") assert 'id' in order assert 'info' in order @@ -54,17 +55,17 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected): with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("binance Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance", "stoploss", "create_order", retries=1, - pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") def test_stoploss_order_dry_run_binance(default_conf, mocker): @@ -77,12 +78,12 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') with pytest.raises(OperationalException): - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side="sell", order_types={'stoploss_on_exchange_limit_ratio': 1.05}) api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") assert 'id' in order assert 'info' in order @@ -100,8 +101,8 @@ def test_stoploss_adjust_binance(mocker, default_conf): 'price': 1500, 'info': {'stopPrice': 1500}, } - assert exchange.stoploss_adjust(1501, order) - assert not exchange.stoploss_adjust(1499, order) + assert exchange.stoploss_adjust(1501, order, side="sell") + assert not exchange.stoploss_adjust(1499, order, side="sell") # Test with invalid order case order['type'] = 'stop_loss' - assert not exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1501, order, side="sell") diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 144063c07..d03316ea5 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2581,10 +2581,10 @@ def test_get_fee(default_conf, mocker, exchange_name): def test_stoploss_order_unsupported_exchange(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, id='bittrex') with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): - exchange.stoploss_adjust(1, {}) + exchange.stoploss_adjust(1, {}, side="sell") def test_merge_ft_has_dict(default_conf, mocker): diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index 3794bb79c..3887e2b08 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -32,7 +32,7 @@ def test_stoploss_order_ftx(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') # stoploss_on_exchange_limit_ratio is irrelevant for ftx market orders - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side="sell", order_types={'stoploss_on_exchange_limit_ratio': 1.05}) assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' @@ -47,7 +47,7 @@ def test_stoploss_order_ftx(default_conf, mocker): api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") assert 'id' in order assert 'info' in order @@ -61,7 +61,7 @@ def test_stoploss_order_ftx(default_conf, mocker): api_mock.create_order.reset_mock() order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, - order_types={'stoploss': 'limit'}) + order_types={'stoploss': 'limit'}, side="sell") assert 'id' in order assert 'info' in order @@ -78,17 +78,17 @@ def test_stoploss_order_ftx(default_conf, mocker): with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("ftx Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") ccxt_exceptionhandlers(mocker, default_conf, api_mock, "ftx", "stoploss", "create_order", retries=1, - pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") def test_stoploss_order_dry_run_ftx(default_conf, mocker): @@ -101,7 +101,7 @@ def test_stoploss_order_dry_run_ftx(default_conf, mocker): api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") assert 'id' in order assert 'info' in order @@ -118,11 +118,11 @@ def test_stoploss_adjust_ftx(mocker, default_conf): 'type': STOPLOSS_ORDERTYPE, 'price': 1500, } - assert exchange.stoploss_adjust(1501, order) - assert not exchange.stoploss_adjust(1499, order) + assert exchange.stoploss_adjust(1501, order, side="sell") + assert not exchange.stoploss_adjust(1499, order, side="sell") # Test with invalid order case ... order['type'] = 'stop_loss_limit' - assert not exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1501, order, side="sell") def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order): diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index eb79dfc10..c2b96cf17 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -183,7 +183,7 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, side="sell", order_types={'stoploss': ordertype, 'stoploss_on_exchange_limit_ratio': 0.99 }) @@ -208,17 +208,17 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype): with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("kraken Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken", "stoploss", "create_order", retries=1, - pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") def test_stoploss_order_dry_run_kraken(default_conf, mocker): @@ -231,7 +231,7 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker): api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") assert 'id' in order assert 'info' in order @@ -248,8 +248,8 @@ def test_stoploss_adjust_kraken(mocker, default_conf): 'type': STOPLOSS_ORDERTYPE, 'price': 1500, } - assert exchange.stoploss_adjust(1501, order) - assert not exchange.stoploss_adjust(1499, order) + assert exchange.stoploss_adjust(1501, order, side="sell") + assert not exchange.stoploss_adjust(1499, order, side="sell") # Test with invalid order case ... order['type'] = 'stop_loss_limit' - assert not exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1501, order, side="sell") diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index e0880db8f..3106c3e00 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1343,10 +1343,13 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, assert freqtrade.handle_stoploss_on_exchange(trade) is False cancel_order_mock.assert_called_once_with(100, 'ETH/BTC') - stoploss_order_mock.assert_called_once_with(amount=85.32423208, - pair='ETH/BTC', - order_types=freqtrade.strategy.order_types, - stop_price=0.00002346 * 0.95) + stoploss_order_mock.assert_called_once_with( + amount=85.32423208, + pair='ETH/BTC', + order_types=freqtrade.strategy.order_types, + stop_price=0.00002346 * 0.95, + side="sell" + ) # price fell below stoploss, so dry-run sells trade. mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={ @@ -1417,7 +1420,7 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c side_effect=InvalidOrderException()) mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', return_value=stoploss_order_hanging) - freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) + freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging, side="buy") assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/BTC.*", caplog) # Still try to create order @@ -1427,7 +1430,7 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c caplog.clear() cancel_mock = mocker.patch("freqtrade.exchange.Binance.cancel_stoploss_order", MagicMock()) mocker.patch("freqtrade.exchange.Binance.stoploss", side_effect=ExchangeError()) - freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) + freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging, side="buy") assert cancel_mock.call_count == 1 assert log_has_re(r"Could not create trailing stoploss order for pair ETH/BTC\..*", caplog) @@ -1526,10 +1529,13 @@ def test_handle_stoploss_on_exchange_custom_stop(mocker, default_conf, fee, assert freqtrade.handle_stoploss_on_exchange(trade) is False cancel_order_mock.assert_called_once_with(100, 'ETH/BTC') - stoploss_order_mock.assert_called_once_with(amount=85.32423208, - pair='ETH/BTC', - order_types=freqtrade.strategy.order_types, - stop_price=0.00002346 * 0.96) + stoploss_order_mock.assert_called_once_with( + amount=85.32423208, + pair='ETH/BTC', + order_types=freqtrade.strategy.order_types, + stop_price=0.00002346 * 0.96, + side="sell" + ) # price fell below stoploss, so dry-run sells trade. mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={ @@ -1647,10 +1653,13 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, # stoploss should be set to 1% as trailing is on assert trade.stop_loss == 0.00002346 * 0.99 cancel_order_mock.assert_called_once_with(100, 'NEO/BTC') - stoploss_order_mock.assert_called_once_with(amount=2132892.49146757, - pair='NEO/BTC', - order_types=freqtrade.strategy.order_types, - stop_price=0.00002346 * 0.99) + stoploss_order_mock.assert_called_once_with( + amount=2132892.49146757, + pair='NEO/BTC', + order_types=freqtrade.strategy.order_types, + stop_price=0.00002346 * 0.99, + side="sell" + ) def test_enter_positions(mocker, default_conf, caplog) -> None: From d262af35cafd007f23f436c8474274f647eab2e8 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 2 Aug 2021 06:14:30 -0600 Subject: [PATCH 04/53] Removed setup leverage and transfer functions from exchange --- freqtrade/exchange/binance.py | 100 +-------------------------------- freqtrade/exchange/bittrex.py | 23 +------- freqtrade/exchange/exchange.py | 32 +---------- freqtrade/exchange/kraken.py | 22 +------- 4 files changed, 4 insertions(+), 173 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index c285cec21..675f85e62 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -1,6 +1,6 @@ """ Binance exchange subclass """ import logging -from typing import Dict, Optional +from typing import Dict import ccxt @@ -96,103 +96,5 @@ class Binance(Exchange): except ccxt.BaseError as e: raise OperationalException(e) from e - def transfer(self, asset: str, amount: float, frm: str, to: str, pair: Optional[str]): - res = self._api.sapi_post_margin_isolated_transfer({ - "asset": asset, - "amount": amount, - "transFrom": frm, - "transTo": to, - "symbol": pair - }) - logger.info(f"Transfer response: {res}") - - def borrow(self, asset: str, amount: float, pair: str): - res = self._api.sapi_post_margin_loan({ - "asset": asset, - "isIsolated": True, - "symbol": pair, - "amount": amount - }) # borrow from binance - logger.info(f"Borrow response: {res}") - - def repay(self, asset: str, amount: float, pair: str): - res = self._api.sapi_post_margin_repay({ - "asset": asset, - "isIsolated": True, - "symbol": pair, - "amount": amount - }) # borrow from binance - logger.info(f"Borrow response: {res}") - - def setup_leveraged_enter( - self, - pair: str, - leverage: float, - amount: float, - quote_currency: Optional[str], - is_short: Optional[bool] - ): - if not quote_currency or not is_short: - raise OperationalException( - "quote_currency and is_short are required arguments to setup_leveraged_enter" - " when trading with leverage on binance" - ) - open_rate = 2 # TODO-mg: get the real open_rate, or real stake_amount - stake_amount = amount * open_rate - if is_short: - borrowed = stake_amount * ((leverage-1)/leverage) - else: - borrowed = amount - - self.transfer( # Transfer to isolated margin - asset=quote_currency, - amount=stake_amount, - frm='SPOT', - to='ISOLATED_MARGIN', - pair=pair - ) - - self.borrow( - asset=quote_currency, - amount=borrowed, - pair=pair - ) # borrow from binance - - def complete_leveraged_exit( - self, - pair: str, - leverage: float, - amount: float, - quote_currency: Optional[str], - is_short: Optional[bool] - ): - - if not quote_currency or not is_short: - raise OperationalException( - "quote_currency and is_short are required arguments to setup_leveraged_enter" - " when trading with leverage on binance" - ) - - open_rate = 2 # TODO-mg: get the real open_rate, or real stake_amount - stake_amount = amount * open_rate - if is_short: - borrowed = stake_amount * ((leverage-1)/leverage) - else: - borrowed = amount - - self.repay( - asset=quote_currency, - amount=borrowed, - pair=pair - ) # repay binance - - self.transfer( # Transfer to isolated margin - asset=quote_currency, - amount=stake_amount, - frm='ISOLATED_MARGIN', - to='SPOT', - pair=pair - ) - def apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float): return stake_amount / leverage diff --git a/freqtrade/exchange/bittrex.py b/freqtrade/exchange/bittrex.py index e4d344d27..69e2f2b8d 100644 --- a/freqtrade/exchange/bittrex.py +++ b/freqtrade/exchange/bittrex.py @@ -1,9 +1,8 @@ """ Bittrex exchange subclass """ import logging -from typing import Dict, Optional +from typing import Dict from freqtrade.exchange import Exchange -from freqtrade.exceptions import OperationalException logger = logging.getLogger(__name__) @@ -24,23 +23,3 @@ class Bittrex(Exchange): }, "l2_limit_range": [1, 25, 500], } - - def setup_leveraged_enter( - self, - pair: str, - leverage: float, - amount: float, - quote_currency: Optional[str], - is_short: Optional[bool] - ): - raise OperationalException("Bittrex does not support leveraged trading") - - def complete_leveraged_exit( - self, - pair: str, - leverage: float, - amount: float, - quote_currency: Optional[str], - is_short: Optional[bool] - ): - raise OperationalException("Bittrex does not support leveraged trading") diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 0471c0149..87d920fba 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -730,8 +730,7 @@ class Exchange: def get_max_leverage(self, pair: str, stake_amount: float, price: float) -> float: """ - Gets the maximum leverage available on this pair that is below the config leverage - but higher than the config min_leverage + Gets the maximum leverage available on this pair """ raise OperationalException(f"Leverage is not available on {self.name} using freqtrade") @@ -782,26 +781,6 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e - def setup_leveraged_enter( - self, - pair: str, - leverage: float, - amount: float, - quote_currency: Optional[str], - is_short: Optional[bool] - ): - raise OperationalException(f"Leverage is not available on {self.name} using freqtrade") - - def complete_leveraged_exit( - self, - pair: str, - leverage: float, - amount: float, - quote_currency: Optional[str], - is_short: Optional[bool] - ): - raise OperationalException(f"Leverage is not available on {self.name} using freqtrade") - def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) @@ -1571,15 +1550,6 @@ class Exchange: self._async_get_trade_history(pair=pair, since=since, until=until, from_id=from_id)) - def transfer(self, asset: str, amount: float, frm: str, to: str, pair: Optional[str]): - self._api.transfer(asset, amount, frm, to) - - def get_isolated_liq(self, pair: str, open_rate: float, - amount: float, leverage: float, is_short: bool) -> float: - raise OperationalException( - f"Isolated margin is not available on {self.name} using freqtrade" - ) - def get_interest_rate(self, pair: str, open_rate: float, is_short: bool) -> float: # TODO-mg: implement return 0.0005 diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 36c1608bd..010b574d6 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -1,6 +1,6 @@ """ Kraken exchange subclass """ import logging -from typing import Any, Dict, Optional +from typing import Any, Dict import ccxt @@ -127,23 +127,3 @@ class Kraken(Exchange): f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e except ccxt.BaseError as e: raise OperationalException(e) from e - - def setup_leveraged_enter( - self, - pair: str, - leverage: float, - amount: float, - quote_currency: Optional[str], - is_short: Optional[bool] - ): - return - - def complete_leveraged_exit( - self, - pair: str, - leverage: float, - amount: float, - quote_currency: Optional[str], - is_short: Optional[bool] - ): - return From add7e74632f260cf375afa7bef1372e59a6f95ba Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 8 Aug 2021 19:34:33 -0600 Subject: [PATCH 05/53] Added set_leverage function to exchange --- freqtrade/exchange/exchange.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 87d920fba..bf5fc4de3 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -760,7 +760,6 @@ class Exchange: order = self._api.create_order(pair, ordertype, side, amount, rate_for_order, params) self._log_exchange_response('create_order', order) - return order except ccxt.InsufficientFunds as e: @@ -1554,6 +1553,15 @@ class Exchange: # TODO-mg: implement return 0.0005 + def set_leverage(self, pair, leverage): + """ + Binance Futures must set the leverage before making a futures trade, in order to not + have the same leverage on every trade + # TODO-lev: This may be the case for any futures exchange, or even margin trading on + # TODO-lev: some exchanges, so check this + """ + self._api.set_leverage(symbol=pair, leverage=leverage) + def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool: return exchange_name in ccxt_exchanges(ccxt_module) From 455bcf5389e1756983e5e9cc45b378c01ee0a4ae Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 8 Aug 2021 23:13:35 -0600 Subject: [PATCH 06/53] Added TODOs to test files --- freqtrade/exchange/binance.py | 4 ++-- freqtrade/exchange/exchange.py | 4 ++-- freqtrade/exchange/ftx.py | 4 ++-- freqtrade/exchange/kraken.py | 4 ++-- tests/exchange/test_exchange.py | 24 ++++++++++++++++++++++++ tests/exchange/test_ftx.py | 2 ++ tests/exchange/test_kraken.py | 2 ++ 7 files changed, 36 insertions(+), 8 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 675f85e62..8c70fdb1f 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -31,7 +31,7 @@ class Binance(Exchange): Returns True if adjustment is necessary. :param side: "buy" or "sell" """ - # TODO-mg: Short support + # TODO-lev: Short support return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice']) @retrier(retries=0) @@ -43,7 +43,7 @@ class Binance(Exchange): It may work with a limited number of other exchanges, but this has not been tested yet. :param side: "buy" or "sell" """ - # TODO-mg: Short support + # TODO-lev: Short support # Limit price threshold: As limit price should always be below stop-price limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) rate = stop_price * limit_price_pct diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index bf5fc4de3..b7b7151c2 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -543,7 +543,7 @@ class Exchange: def get_min_pair_stake_amount(self, pair: str, price: float, stoploss: float, leverage: Optional[float] = 1.0) -> Optional[float]: - # TODO-mg: Using leverage makes the min stake amount lower (on binance at least) + # TODO-lev: Using leverage makes the min stake amount lower (on binance at least) try: market = self.markets[pair] except KeyError: @@ -1550,7 +1550,7 @@ class Exchange: until=until, from_id=from_id)) def get_interest_rate(self, pair: str, open_rate: float, is_short: bool) -> float: - # TODO-mg: implement + # TODO-lev: implement return 0.0005 def set_leverage(self, pair, leverage): diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 4a078bbb7..aca060d2b 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -36,7 +36,7 @@ class Ftx(Exchange): Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. """ - # TODO-mg: Short support + # TODO-lev: Short support return order['type'] == 'stop' and stop_loss > float(order['price']) @retrier(retries=0) @@ -48,7 +48,7 @@ class Ftx(Exchange): Limit orders are defined by having orderPrice set, otherwise a market order is used. """ - # TODO-mg: Short support + # TODO-lev: Short support limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) limit_rate = stop_price * limit_price_pct diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 010b574d6..303c4d885 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -72,7 +72,7 @@ class Kraken(Exchange): Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. """ - # TODO-mg: Short support + # TODO-lev: Short support return (order['type'] in ('stop-loss', 'stop-loss-limit') and stop_loss > float(order['price'])) @@ -83,7 +83,7 @@ class Kraken(Exchange): Creates a stoploss market order. Stoploss market orders is the only stoploss type supported by kraken. """ - # TODO-mg: Short support + # TODO-lev: Short support params = self._params.copy() if order_types.get('stoploss', 'market') == 'limit': diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index d03316ea5..0c1e027b7 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -116,6 +116,8 @@ def test_init_ccxt_kwargs(default_conf, mocker, caplog): assert ex._api.headers == {'hello': 'world'} Exchange._headers = {} + # TODO-lev: Test with options + def test_destroy(default_conf, mocker, caplog): caplog.set_level(logging.DEBUG) @@ -307,6 +309,7 @@ def test_price_get_one_pip(default_conf, mocker, price, precision_mode, precisio def test_get_min_pair_stake_amount(mocker, default_conf) -> None: + # TODO-lev: Test with leverage exchange = get_patched_exchange(mocker, default_conf, id="binance") stoploss = -0.05 markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}} @@ -425,6 +428,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: + # TODO-lev: Test with leverage exchange = get_patched_exchange(mocker, default_conf, id="binance") stoploss = -0.05 markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}} @@ -445,6 +449,11 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: ) +def apply_leverage_to_stake_amount(): + # TODO-lev + return + + def test_set_sandbox(default_conf, mocker): """ Test working scenario @@ -2933,3 +2942,18 @@ def test_calculate_fee_rate(mocker, default_conf, order, expected) -> None: ]) def test_calculate_backoff(retrycount, max_retries, expected): assert calculate_backoff(retrycount, max_retries) == expected + + +def test_get_max_leverage(): + # TODO-lev + return + + +def test_get_interest_rate(): + # TODO-lev + return + + +def test_set_leverage(): + # TODO-lev + return diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index 3887e2b08..76b01dd35 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -13,6 +13,8 @@ from .test_exchange import ccxt_exceptionhandlers STOPLOSS_ORDERTYPE = 'stop' +# TODO-lev: All these stoploss tests with shorts + def test_stoploss_order_ftx(default_conf, mocker): api_mock = MagicMock() diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index c2b96cf17..60250fc71 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -164,6 +164,8 @@ def test_get_balances_prod(default_conf, mocker): ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken", "get_balances", "fetch_balance") +# TODO-lev: All these stoploss tests with shorts + @pytest.mark.parametrize('ordertype', ['market', 'limit']) def test_stoploss_order_kraken(default_conf, mocker, ordertype): From 134a7ec59b0e3f42f0fdad87b883dcc88fa01a6c Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 20 Aug 2021 02:40:22 -0600 Subject: [PATCH 07/53] Implemented fill_leverage_brackets get_max_leverage and set_leverage for binance, kraken and ftx. Wrote tests test_apply_leverage_to_stake_amount and test_get_max_leverage --- freqtrade/exchange/binance.py | 42 +++++++++++++++++++++-- freqtrade/exchange/exchange.py | 50 ++++++++++++++++++++------- freqtrade/exchange/ftx.py | 29 +++++++++++++++- freqtrade/exchange/kraken.py | 41 +++++++++++++++++++++- tests/exchange/test_binance.py | 45 ++++++++++++++++++++++++ tests/exchange/test_exchange.py | 61 +++++++++++++++++++++++++++++---- 6 files changed, 245 insertions(+), 23 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 8c70fdb1f..1339677d2 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -1,6 +1,6 @@ """ Binance exchange subclass """ import logging -from typing import Dict +from typing import Dict, Optional import ccxt @@ -96,5 +96,43 @@ 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): + def _apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float): return stake_amount / leverage + + def fill_leverage_brackets(self): + """ + Assigns property _leverage_brackets to a dictionary of information about the leverage + allowed on each pair + """ + leverage_brackets = self._api.load_leverage_brackets() + for pair, brackets in leverage_brackets.items: + self.leverage_brackets[pair] = [ + [ + min_amount, + float(margin_req) + ] for [ + min_amount, + margin_req + ] in brackets + ] + + def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float: + """ + Returns the maximum leverage that a pair can be traded at + :param pair: The base/quote currency pair being traded + :nominal_value: The total value of the trade in quote currency (collateral + debt) + """ + pair_brackets = self._leverage_brackets[pair] + max_lev = 1.0 + for [min_amount, margin_req] in pair_brackets: + print(nominal_value, min_amount) + if nominal_value >= min_amount: + max_lev = 1/margin_req + return max_lev + + def set_leverage(self, pair, leverage): + """ + Binance Futures must set the leverage before making a futures trade, in order to not + have the same leverage on every trade + """ + self._api.set_leverage(symbol=pair, leverage=leverage) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index b7b7151c2..340a63ab5 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -74,6 +74,8 @@ class Exchange: } _ft_has: Dict = {} + _leverage_brackets: Dict + def __init__(self, config: Dict[str, Any], validate: bool = True) -> None: """ Initializes this module with the given config, @@ -161,6 +163,16 @@ class Exchange: self.markets_refresh_interval: int = exchange_config.get( "markets_refresh_interval", 60) * 60 + leverage = config.get('leverage_mode') + if leverage is not False: + try: + # TODO-lev: This shouldn't need to happen, but for some reason I get that the + # TODO-lev: method isn't implemented + self.fill_leverage_brackets() + except Exception as error: + logger.debug(error) + logger.debug("Could not load leverage_brackets") + def __del__(self): """ Destructor - clean up async stuff @@ -355,6 +367,7 @@ class Exchange: # Also reload async markets to avoid issues with newly listed pairs self._load_async_markets(reload=True) self._last_markets_refresh = arrow.utcnow().int_timestamp + self.fill_leverage_brackets() except ccxt.BaseError: logger.exception("Could not reload markets.") @@ -577,12 +590,12 @@ class Exchange: # The value returned should satisfy both limits: for amount (base currency) and # for cost (quote, stake currency), so max() is used here. # See also #2575 at github. - return self.apply_leverage_to_stake_amount( + return self._apply_leverage_to_stake_amount( max(min_stake_amounts) * amount_reserve_percent, leverage or 1.0 ) - def apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float): + def _apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float): """ # * Should be implemented by child classes if leverage affects the stake_amount Takes the minimum stake amount for a pair with no leverage and returns the minimum @@ -728,14 +741,6 @@ class Exchange: raise InvalidOrderException( f'Tried to get an invalid dry-run-order (id: {order_id}). Message: {e}') from e - def get_max_leverage(self, pair: str, stake_amount: float, price: float) -> float: - """ - Gets the maximum leverage available on this pair - """ - - raise OperationalException(f"Leverage is not available on {self.name} using freqtrade") - return 1.0 - # Order handling def create_order(self, pair: str, ordertype: str, side: str, amount: float, @@ -1553,13 +1558,32 @@ class Exchange: # TODO-lev: implement return 0.0005 + def fill_leverage_brackets(self): + """ + #TODO-lev: Should maybe be renamed, leverage_brackets might not be accurate for kraken + Assigns property _leverage_brackets to a dictionary of information about the leverage + allowed on each pair + """ + raise OperationalException( + f"{self.name.capitalize()}.fill_leverage_brackets has not been implemented.") + + def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float: + """ + Returns the maximum leverage that a pair can be traded at + :param pair: The base/quote currency pair being traded + :nominal_value: The total value of the trade in quote currency (collateral + debt) + """ + raise OperationalException( + f"{self.name.capitalize()}.get_max_leverage has not been implemented.") + def set_leverage(self, pair, leverage): """ - Binance Futures must set the leverage before making a futures trade, in order to not + Set's the leverage before making a trade, in order to not have the same leverage on every trade - # TODO-lev: This may be the case for any futures exchange, or even margin trading on - # TODO-lev: some exchanges, so check this """ + raise OperationalException( + f"{self.name.capitalize()}.set_leverage has not been implemented.") + self._api.set_leverage(symbol=pair, leverage=leverage) diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index aca060d2b..64e728761 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -1,6 +1,6 @@ """ FTX exchange subclass """ import logging -from typing import Any, Dict +from typing import Any, Dict, Optional import ccxt @@ -156,3 +156,30 @@ class Ftx(Exchange): if order['type'] == 'stop': return safe_value_fallback2(order, order, 'id_stop', 'id') return order['id'] + + def _apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float): + # TODO-lev: implement + return stake_amount + + def fill_leverage_brackets(self): + """ + FTX leverage is static across the account, and doesn't change from pair to pair, + so _leverage_brackets doesn't need to be set + """ + return + + def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float: + """ + Returns the maximum leverage that a pair can be traded at, which is always 20 on ftx + :param pair: Here for super method, not used on FTX + :nominal_value: Here for super method, not used on FTX + """ + return 20.0 + + def set_leverage(self, pair, leverage): + """ + Sets the leverage used for the user's account + :param pair: Here for super method, not used on FTX + :param leverage: + """ + self._api.private_post_account_leverage({'leverage': leverage}) diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 303c4d885..358a1991c 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -1,6 +1,6 @@ """ Kraken exchange subclass """ import logging -from typing import Any, Dict +from typing import Any, Dict, Optional import ccxt @@ -127,3 +127,42 @@ class Kraken(Exchange): f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e except ccxt.BaseError as e: raise OperationalException(e) from e + + def fill_leverage_brackets(self): + """ + Assigns property _leverage_brackets to a dictionary of information about the leverage + allowed on each pair + """ + # TODO-lev: Not sure if this works correctly for futures + leverages = {} + for pair, market in self._api.load_markets().items(): + info = market['info'] + leverage_buy = info['leverage_buy'] + leverage_sell = info['leverage_sell'] + if len(info['leverage_buy']) > 0 or len(info['leverage_sell']) > 0: + if leverage_buy != leverage_sell: + print(f"\033[91m The buy leverage != the sell leverage for {pair}." + "please let freqtrade know because this has never happened before" + ) + if max(leverage_buy) < max(leverage_sell): + leverages[pair] = leverage_buy + else: + leverages[pair] = leverage_sell + else: + leverages[pair] = leverage_buy + self._leverage_brackets = leverages + + def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float: + """ + Returns the maximum leverage that a pair can be traded at + :param pair: The base/quote currency pair being traded + :nominal_value: Here for super class, not needed on Kraken + """ + return float(max(self._leverage_brackets[pair])) + + def set_leverage(self, pair, leverage): + """ + Kraken set's the leverage as an option it the order object, so it doesn't do + anything in this function + """ + return diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index 7b324efa2..aba185134 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -106,3 +106,48 @@ def test_stoploss_adjust_binance(mocker, default_conf): # Test with invalid order case order['type'] = 'stop_loss' assert not exchange.stoploss_adjust(1501, order, side="sell") + + +@pytest.mark.parametrize('pair,nominal_value,max_lev', [ + ("BNB/BUSD", 0.0, 40.0), + ("BNB/USDT", 100.0, 153.84615384615384), + ("BTC/USDT", 170.30, 250.0), + ("BNB/BUSD", 999999.9, 10.0), + ("BNB/USDT", 5000000.0, 6.666666666666667), + ("BTC/USDT", 300000000.1, 2.0), +]) +def test_get_max_leverage_binance( + default_conf, + mocker, + pair, + nominal_value, + max_lev +): + exchange = get_patched_exchange(mocker, default_conf, id="binance") + exchange._leverage_brackets = { + 'BNB/BUSD': [[0.0, 0.025], + [100000.0, 0.05], + [500000.0, 0.1], + [1000000.0, 0.15], + [2000000.0, 0.25], + [5000000.0, 0.5]], + 'BNB/USDT': [[0.0, 0.0065], + [10000.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.15], + [10000000.0, 0.25]], + 'BTC/USDT': [[0.0, 0.004], + [50000.0, 0.005], + [250000.0, 0.01], + [1000000.0, 0.025], + [5000000.0, 0.05], + [20000000.0, 0.1], + [50000000.0, 0.125], + [100000000.0, 0.15], + [200000000.0, 0.25], + [300000000.0, 0.5]], + } + assert exchange.get_max_leverage(pair, nominal_value) == max_lev diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 0c1e027b7..518629531 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -449,11 +449,6 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: ) -def apply_leverage_to_stake_amount(): - # TODO-lev - return - - def test_set_sandbox(default_conf, mocker): """ Test working scenario @@ -2944,7 +2939,61 @@ def test_calculate_backoff(retrycount, max_retries, expected): assert calculate_backoff(retrycount, max_retries) == expected -def test_get_max_leverage(): +@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 + # TODO-lev: - implement FTX tests + # ('ftx', 9.0, 3.0, 10.0), + # ('ftx', 20.0, 5.0, 20.0), + # ('ftx', 100.0, 100.0, 100.0), +]) +def test_apply_leverage_to_stake_amount( + exchange, + stake_amount, + leverage, + min_stake_with_lev, + mocker, + default_conf +): + exchange = get_patched_exchange(mocker, default_conf, id=exchange) + assert exchange._apply_leverage_to_stake_amount(stake_amount, leverage) == min_stake_with_lev + + +@pytest.mark.parametrize('exchange_name,pair,nominal_value,max_lev', [ + # Kraken + ("kraken", "ADA/BTC", 0.0, 3.0), + ("kraken", "BTC/EUR", 100.0, 5.0), + ("kraken", "ZEC/USD", 173.31, 2.0), + # FTX + ("ftx", "ADA/BTC", 0.0, 20.0), + ("ftx", "BTC/EUR", 100.0, 20.0), + ("ftx", "ZEC/USD", 173.31, 20.0), + # Binance tests this method inside it's own test file +]) +def test_get_max_leverage( + default_conf, + mocker, + exchange_name, + pair, + nominal_value, + max_lev +): + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange._leverage_brackets = { + 'ADA/BTC': ['2', '3'], + 'BTC/EUR': ['2', '3', '4', '5'], + 'ZEC/USD': ['2'] + } + assert exchange.get_max_leverage(pair, nominal_value) == max_lev + + +def test_fill_leverage_brackets(): # TODO-lev return From c256dc3745127f1959fc7d7155ea57ee68e6f9c6 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 20 Aug 2021 18:50:02 -0600 Subject: [PATCH 08/53] Removed some outdated TODOs and whitespace --- freqtrade/exchange/exchange.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 340a63ab5..0f7bf6b39 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -556,7 +556,6 @@ class Exchange: def get_min_pair_stake_amount(self, pair: str, price: float, stoploss: float, leverage: Optional[float] = 1.0) -> Optional[float]: - # TODO-lev: Using leverage makes the min stake amount lower (on binance at least) try: market = self.markets[pair] except KeyError: @@ -1584,8 +1583,6 @@ class Exchange: raise OperationalException( f"{self.name.capitalize()}.set_leverage has not been implemented.") - self._api.set_leverage(symbol=pair, leverage=leverage) - def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool: return exchange_name in ccxt_exchanges(ccxt_module) From 16db8d70a5b680d2c295daa9763a3041bfc62748 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 21 Aug 2021 01:13:51 -0600 Subject: [PATCH 09/53] Added error handlers to api functions and made a logger warning in fill_leverage_brackets --- freqtrade/exchange/binance.py | 41 +++++++++++++++++++++++++---------- freqtrade/exchange/ftx.py | 10 ++++++++- freqtrade/exchange/kraken.py | 38 +++++++++++++++++++------------- 3 files changed, 61 insertions(+), 28 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 1339677d2..15599eff9 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -104,17 +104,26 @@ class Binance(Exchange): Assigns property _leverage_brackets to a dictionary of information about the leverage allowed on each pair """ - leverage_brackets = self._api.load_leverage_brackets() - for pair, brackets in leverage_brackets.items: - self.leverage_brackets[pair] = [ - [ - min_amount, - float(margin_req) - ] for [ - min_amount, - margin_req - ] in brackets - ] + try: + leverage_brackets = self._api.load_leverage_brackets() + for pair, brackets in leverage_brackets.items: + self.leverage_brackets[pair] = [ + [ + min_amount, + float(margin_req) + ] for [ + min_amount, + margin_req + ] in brackets + ] + + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError(f'Could not fetch leverage amounts due to' + f'{e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float: """ @@ -135,4 +144,12 @@ class Binance(Exchange): Binance Futures must set the leverage before making a futures trade, in order to not have the same leverage on every trade """ - self._api.set_leverage(symbol=pair, leverage=leverage) + try: + 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/ftx.py b/freqtrade/exchange/ftx.py index 64e728761..8ffba92c7 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -182,4 +182,12 @@ class Ftx(Exchange): :param pair: Here for super method, not used on FTX :param leverage: """ - self._api.private_post_account_leverage({'leverage': leverage}) + try: + self._api.private_post_account_leverage({'leverage': leverage}) + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError(f'Could not fetch leverage amounts due to' + f'{e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 358a1991c..e020f7fd8 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -135,22 +135,30 @@ class Kraken(Exchange): """ # TODO-lev: Not sure if this works correctly for futures leverages = {} - for pair, market in self._api.load_markets().items(): - info = market['info'] - leverage_buy = info['leverage_buy'] - leverage_sell = info['leverage_sell'] - if len(info['leverage_buy']) > 0 or len(info['leverage_sell']) > 0: - if leverage_buy != leverage_sell: - print(f"\033[91m The buy leverage != the sell leverage for {pair}." - "please let freqtrade know because this has never happened before" - ) - if max(leverage_buy) < max(leverage_sell): - leverages[pair] = leverage_buy + try: + for pair, market in self._api.load_markets().items(): + info = market['info'] + leverage_buy = info['leverage_buy'] + leverage_sell = info['leverage_sell'] + if len(info['leverage_buy']) > 0 or len(info['leverage_sell']) > 0: + if leverage_buy != leverage_sell: + logger.warning(f"The buy leverage != the sell leverage for {pair}. Please" + "let freqtrade know because this has never happened before" + ) + if max(leverage_buy) < max(leverage_sell): + leverages[pair] = leverage_buy + else: + leverages[pair] = leverage_sell else: - leverages[pair] = leverage_sell - else: - leverages[pair] = leverage_buy - self._leverage_brackets = leverages + leverages[pair] = leverage_buy + self._leverage_brackets = leverages + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError(f'Could not fetch leverage amounts due to' + f'{e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float: """ From 4ef1f0a977b3ac1ef7aef13a4de3e991423b6edc Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 21 Aug 2021 16:26:04 -0600 Subject: [PATCH 10/53] Changed ftx set_leverage implementation --- freqtrade/exchange/binance.py | 16 ---------------- freqtrade/exchange/exchange.py | 13 ++++++++++--- freqtrade/exchange/ftx.py | 16 ---------------- 3 files changed, 10 insertions(+), 35 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 15599eff9..1177a4409 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -134,22 +134,6 @@ class Binance(Exchange): pair_brackets = self._leverage_brackets[pair] max_lev = 1.0 for [min_amount, margin_req] in pair_brackets: - print(nominal_value, min_amount) if nominal_value >= min_amount: max_lev = 1/margin_req return max_lev - - def set_leverage(self, pair, leverage): - """ - Binance Futures must set the leverage before making a futures trade, in order to not - have the same leverage on every trade - """ - try: - 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 0f7bf6b39..ffcf1d401 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1575,13 +1575,20 @@ class Exchange: raise OperationalException( f"{self.name.capitalize()}.get_max_leverage has not been implemented.") - def set_leverage(self, pair, leverage): + def set_leverage(self, leverage: float, pair: Optional[str]): """ Set's the leverage before making a trade, in order to not have the same leverage on every trade """ - raise OperationalException( - f"{self.name.capitalize()}.set_leverage has not been implemented.") + try: + 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 def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool: diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 8ffba92c7..9ed220806 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -175,19 +175,3 @@ class Ftx(Exchange): :nominal_value: Here for super method, not used on FTX """ return 20.0 - - def set_leverage(self, pair, leverage): - """ - Sets the leverage used for the user's account - :param pair: Here for super method, not used on FTX - :param leverage: - """ - try: - self._api.private_post_account_leverage({'leverage': leverage}) - except ccxt.DDoSProtection as e: - raise DDosProtection(e) from e - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError(f'Could not fetch leverage amounts due to' - f'{e.__class__.__name__}. Message: {e}') from e - except ccxt.BaseError as e: - raise OperationalException(e) from e From 5748c9bc13915903d1128ace05882b937afbefe6 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 21 Aug 2021 21:10:03 -0600 Subject: [PATCH 11/53] Added short functionality to exchange stoplss methods --- freqtrade/exchange/binance.py | 28 ++++++++++++++++------------ freqtrade/exchange/ftx.py | 17 +++++++++-------- freqtrade/exchange/kraken.py | 21 ++++++++++----------- freqtrade/persistence/models.py | 1 - 4 files changed, 35 insertions(+), 32 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 1177a4409..3117f5ee1 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -31,8 +31,11 @@ class Binance(Exchange): Returns True if adjustment is necessary. :param side: "buy" or "sell" """ - # TODO-lev: Short support - return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice']) + + return order['type'] == 'stop_loss_limit' and ( + side == "sell" and stop_loss > float(order['info']['stopPrice']) or + side == "buy" and stop_loss < float(order['info']['stopPrice']) + ) @retrier(retries=0) def stoploss(self, pair: str, amount: float, @@ -43,7 +46,6 @@ class Binance(Exchange): It may work with a limited number of other exchanges, but this has not been tested yet. :param side: "buy" or "sell" """ - # TODO-lev: Short support # Limit price threshold: As limit price should always be below stop-price limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) rate = stop_price * limit_price_pct @@ -52,14 +54,16 @@ class Binance(Exchange): stop_price = self.price_to_precision(pair, stop_price) + bad_stop_price = (stop_price <= rate) if side == "sell" else (stop_price >= rate) + # Ensure rate is less than stop price - if stop_price <= rate: + if bad_stop_price: raise OperationalException( - 'In stoploss limit order, stop price should be more than limit price') + 'In stoploss limit order, stop price should be better than limit price') if self._config['dry_run']: dry_order = self.create_dry_run_order( - pair, ordertype, "sell", amount, stop_price) + pair, ordertype, side, amount, stop_price) return dry_order try: @@ -70,7 +74,7 @@ class Binance(Exchange): rate = self.price_to_precision(pair, rate) - order = self._api.create_order(symbol=pair, type=ordertype, side='sell', + order = self._api.create_order(symbol=pair, type=ordertype, side=side, amount=amount, price=rate, params=params) logger.info('stoploss limit order added for %s. ' 'stop price: %s. limit: %s', pair, stop_price, rate) @@ -78,21 +82,21 @@ class Binance(Exchange): return order except ccxt.InsufficientFunds as e: raise InsufficientFundsError( - f'Insufficient funds to create {ordertype} sell order on market {pair}. ' - f'Tried to sell amount {amount} at rate {rate}. ' + f'Insufficient funds to create {ordertype} {side} order on market {pair}. ' + f'Tried to {side} amount {amount} at rate {rate}. ' f'Message: {e}') from e except ccxt.InvalidOrder as e: # Errors: # `binance Order would trigger immediately.` raise InvalidOrderException( - f'Could not create {ordertype} sell order on market {pair}. ' - f'Tried to sell amount {amount} at rate {rate}. ' + f'Could not create {ordertype} {side} order on market {pair}. ' + f'Tried to {side} amount {amount} at rate {rate}. ' f'Message: {e}') from e except ccxt.DDoSProtection as e: raise DDosProtection(e) from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( - f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e + f'Could not place {side} order due to {e.__class__.__name__}. Message: {e}') from e except ccxt.BaseError as e: raise OperationalException(e) from e diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 9ed220806..bd8350853 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -36,8 +36,10 @@ class Ftx(Exchange): Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. """ - # TODO-lev: Short support - return order['type'] == 'stop' and stop_loss > float(order['price']) + return order['type'] == 'stop' and ( + side == "sell" and stop_loss > float(order['price']) or + side == "buy" and stop_loss < float(order['price']) + ) @retrier(retries=0) def stoploss(self, pair: str, amount: float, @@ -48,7 +50,6 @@ class Ftx(Exchange): Limit orders are defined by having orderPrice set, otherwise a market order is used. """ - # TODO-lev: Short support limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) limit_rate = stop_price * limit_price_pct @@ -59,7 +60,7 @@ class Ftx(Exchange): if self._config['dry_run']: dry_order = self.create_dry_run_order( - pair, ordertype, "sell", amount, stop_price) + pair, ordertype, side, amount, stop_price) return dry_order try: @@ -71,7 +72,7 @@ class Ftx(Exchange): params['stopPrice'] = stop_price amount = self.amount_to_precision(pair, amount) - order = self._api.create_order(symbol=pair, type=ordertype, side='sell', + order = self._api.create_order(symbol=pair, type=ordertype, side=side, amount=amount, params=params) self._log_exchange_response('create_stoploss_order', order) logger.info('stoploss order added for %s. ' @@ -79,19 +80,19 @@ class Ftx(Exchange): return order except ccxt.InsufficientFunds as e: raise InsufficientFundsError( - f'Insufficient funds to create {ordertype} sell order on market {pair}. ' + f'Insufficient funds to create {ordertype} {side} order on market {pair}. ' f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. ' f'Message: {e}') from e except ccxt.InvalidOrder as e: raise InvalidOrderException( - f'Could not create {ordertype} sell order on market {pair}. ' + f'Could not create {ordertype} {side} order on market {pair}. ' f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. ' f'Message: {e}') from e except ccxt.DDoSProtection as e: raise DDosProtection(e) from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( - f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e + f'Could not place {side} order due to {e.__class__.__name__}. Message: {e}') from e except ccxt.BaseError as e: raise OperationalException(e) from e diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index e020f7fd8..f12ac0c20 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -72,18 +72,18 @@ class Kraken(Exchange): Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. """ - # TODO-lev: Short support - return (order['type'] in ('stop-loss', 'stop-loss-limit') - and stop_loss > float(order['price'])) + return (order['type'] in ('stop-loss', 'stop-loss-limit') and ( + (side == "sell" and stop_loss > float(order['price'])) or + (side == "buy" and stop_loss < float(order['price'])) + )) - @retrier(retries=0) + @ retrier(retries=0) def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict, side: str) -> Dict: """ Creates a stoploss market order. Stoploss market orders is the only stoploss type supported by kraken. """ - # TODO-lev: Short support params = self._params.copy() if order_types.get('stoploss', 'market') == 'limit': @@ -98,13 +98,13 @@ class Kraken(Exchange): if self._config['dry_run']: dry_order = self.create_dry_run_order( - pair, ordertype, "sell", amount, stop_price) + pair, ordertype, side, amount, stop_price) return dry_order try: amount = self.amount_to_precision(pair, amount) - order = self._api.create_order(symbol=pair, type=ordertype, side='sell', + order = self._api.create_order(symbol=pair, type=ordertype, side=side, amount=amount, price=stop_price, params=params) self._log_exchange_response('create_stoploss_order', order) logger.info('stoploss order added for %s. ' @@ -112,19 +112,19 @@ class Kraken(Exchange): return order except ccxt.InsufficientFunds as e: raise InsufficientFundsError( - f'Insufficient funds to create {ordertype} sell order on market {pair}. ' + f'Insufficient funds to create {ordertype} {side} order on market {pair}. ' f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. ' f'Message: {e}') from e except ccxt.InvalidOrder as e: raise InvalidOrderException( - f'Could not create {ordertype} sell order on market {pair}. ' + f'Could not create {ordertype} {side} order on market {pair}. ' f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. ' f'Message: {e}') from e except ccxt.DDoSProtection as e: raise DDosProtection(e) from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( - f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e + f'Could not place {side} order due to {e.__class__.__name__}. Message: {e}') from e except ccxt.BaseError as e: raise OperationalException(e) from e @@ -133,7 +133,6 @@ class Kraken(Exchange): Assigns property _leverage_brackets to a dictionary of information about the leverage allowed on each pair """ - # TODO-lev: Not sure if this works correctly for futures leverages = {} try: for pair, market in self._api.load_markets().items(): diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index b73611c1b..630078ab3 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -499,7 +499,6 @@ class LocalTrade(): lower_stop = new_loss < self.stop_loss # stop losses only walk up, never down!, - # TODO-lev # ? But adding more to a leveraged trade would create a lower liquidation price, # ? decreasing the minimum stoploss if (higher_stop and not self.is_short) or (lower_stop and self.is_short): From 8a5bad7c3ed3efb61023b6d2efbf3a13f96dc6e7 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 22 Aug 2021 20:58:22 -0600 Subject: [PATCH 12/53] exchange - kraken - minor changes --- freqtrade/exchange/kraken.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index f12ac0c20..567bd6735 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -77,7 +77,7 @@ class Kraken(Exchange): (side == "buy" and stop_loss < float(order['price'])) )) - @ retrier(retries=0) + @retrier(retries=0) def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict, side: str) -> Dict: """ @@ -135,7 +135,7 @@ class Kraken(Exchange): """ leverages = {} try: - for pair, market in self._api.load_markets().items(): + for pair, market in self.markets.items(): info = market['info'] leverage_buy = info['leverage_buy'] leverage_sell = info['leverage_sell'] From f950f039a8a20bf62488709a086723364db3ec45 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 22 Aug 2021 23:28:03 -0600 Subject: [PATCH 13/53] added tests for min stake amount with leverage --- tests/exchange/test_exchange.py | 53 +++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 518629531..1bfdd376b 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -309,7 +309,6 @@ def test_price_get_one_pip(default_conf, mocker, price, precision_mode, precisio def test_get_min_pair_stake_amount(mocker, default_conf) -> None: - # TODO-lev: Test with leverage exchange = get_patched_exchange(mocker, default_conf, id="binance") stoploss = -0.05 markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}} @@ -381,7 +380,12 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: PropertyMock(return_value=markets) ) result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss) - assert isclose(result, 2 * (1+0.05) / (1-abs(stoploss))) + expected_result = 2 * (1+0.05) / (1-abs(stoploss)) + assert isclose(result, expected_result) + # With Leverage + result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss, 3.0) + assert isclose(result, expected_result/3) + # TODO-lev: Min stake for base, kraken and ftx # min amount is set markets["ETH/BTC"]["limits"] = { @@ -393,7 +397,12 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: PropertyMock(return_value=markets) ) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) - assert isclose(result, 2 * 2 * (1+0.05) / (1-abs(stoploss))) + expected_result = 2 * 2 * (1+0.05) / (1-abs(stoploss)) + assert isclose(result, expected_result) + # With Leverage + result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 5.0) + assert isclose(result, expected_result/5) + # TODO-lev: Min stake for base, kraken and ftx # min amount and cost are set (cost is minimal) markets["ETH/BTC"]["limits"] = { @@ -405,7 +414,12 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: PropertyMock(return_value=markets) ) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) - assert isclose(result, max(2, 2 * 2) * (1+0.05) / (1-abs(stoploss))) + expected_result = max(2, 2 * 2) * (1+0.05) / (1-abs(stoploss)) + assert isclose(result, expected_result) + # With Leverage + result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 10) + assert isclose(result, expected_result/10) + # TODO-lev: Min stake for base, kraken and ftx # min amount and cost are set (amount is minial) markets["ETH/BTC"]["limits"] = { @@ -417,18 +431,32 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: PropertyMock(return_value=markets) ) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) - assert isclose(result, max(8, 2 * 2) * (1+0.05) / (1-abs(stoploss))) + expected_result = max(8, 2 * 2) * (1+0.05) / (1-abs(stoploss)) + assert isclose(result, expected_result) + # With Leverage + result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 7.0) + assert isclose(result, expected_result/7.0) + # TODO-lev: Min stake for base, kraken and ftx result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4) - assert isclose(result, max(8, 2 * 2) * 1.5) + expected_result = max(8, 2 * 2) * 1.5 + assert isclose(result, expected_result) + # With Leverage + result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4, 8.0) + assert isclose(result, expected_result/8.0) + # TODO-lev: Min stake for base, kraken and ftx # Really big stoploss result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1) - assert isclose(result, max(8, 2 * 2) * 1.5) + expected_result = max(8, 2 * 2) * 1.5 + assert isclose(result, expected_result) + # With Leverage + result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0) + assert isclose(result, expected_result/12) + # TODO-lev: Min stake for base, kraken and ftx def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: - # TODO-lev: Test with leverage exchange = get_patched_exchange(mocker, default_conf, id="binance") stoploss = -0.05 markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}} @@ -443,10 +471,11 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: PropertyMock(return_value=markets) ) result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss) - assert round(result, 8) == round( - max(0.0001, 0.001 * 0.020405) * (1+0.05) / (1-abs(stoploss)), - 8 - ) + expected_result = max(0.0001, 0.001 * 0.020405) * (1+0.05) / (1-abs(stoploss)) + assert round(result, 8) == round(expected_result, 8) + result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss, 3.0) + assert round(result, 8) == round(expected_result/3, 8) + # TODO-lev: Min stake for base, kraken and ftx def test_set_sandbox(default_conf, mocker): From 3a4d247b64b47fb22c942318d7a7aefe5ee40f61 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 22 Aug 2021 23:36:36 -0600 Subject: [PATCH 14/53] Changed stoploss side on some tests --- freqtrade/exchange/ftx.py | 1 - tests/test_freqtradebot.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index bd8350853..1dc30002e 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -50,7 +50,6 @@ class Ftx(Exchange): Limit orders are defined by having orderPrice set, otherwise a market order is used. """ - limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) limit_rate = stop_price * limit_price_pct diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 3106c3e00..a841744b7 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1420,7 +1420,7 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c side_effect=InvalidOrderException()) mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', return_value=stoploss_order_hanging) - freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging, side="buy") + freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging, side="sell") assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/BTC.*", caplog) # Still try to create order @@ -1430,7 +1430,7 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c caplog.clear() cancel_mock = mocker.patch("freqtrade.exchange.Binance.cancel_stoploss_order", MagicMock()) mocker.patch("freqtrade.exchange.Binance.stoploss", side_effect=ExchangeError()) - freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging, side="buy") + freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging, side="sell") assert cancel_mock.call_count == 1 assert log_has_re(r"Could not create trailing stoploss order for pair ETH/BTC\..*", caplog) From 39fe3814735ed4009c93ef51e2e47f6872446ffe Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Wed, 1 Sep 2021 23:40:32 -0600 Subject: [PATCH 15/53] set margin mode exchange function --- freqtrade/exchange/exchange.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index ffcf1d401..558417332 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1590,6 +1590,9 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e + def set_margin_mode(self, symbol, marginType, params={}): + self._api.set_margin_mode(symbol, marginType, params) + def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool: return exchange_name in ccxt_exchanges(ccxt_module) From e6c9b8ffe5c9c47ac9abb81be09b759a69535fba Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 3 Sep 2021 18:11:39 -0600 Subject: [PATCH 16/53] completed set_margin_mode --- freqtrade/exchange/exchange.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 558417332..b9c2db152 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -22,6 +22,7 @@ from pandas import DataFrame from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, ListPairsWithTimeframes) from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list +from freqtrade.enums import Collateral from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError, InvalidOrderException, OperationalException, PricingError, RetryableOrderError, TemporaryError) @@ -1590,8 +1591,24 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e - def set_margin_mode(self, symbol, marginType, params={}): - self._api.set_margin_mode(symbol, marginType, params) + def set_margin_mode(self, symbol: str, collateral: Collateral, params: dict = {}): + ''' + Set's the margin mode on the exchange to cross or isolated for a specific pair + :param symbol: base/quote currency pair (e.g. "ADA/USDT") + ''' + if not self.exchange_has("setMarginMode"): + # Some exchanges only support one collateral type + return + + try: + self._api.set_margin_mode(symbol, collateral.value, params) + 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 def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool: From 5708fee0e69d7fcfdac2b9cc66d8259fd2403528 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 3 Sep 2021 19:00:04 -0600 Subject: [PATCH 17/53] Wrote failing tests for exchange.set_leverage and exchange.set_margin_mode --- freqtrade/exchange/exchange.py | 2 +- tests/exchange/test_exchange.py | 113 +++++++++++++++++++++++++++++++- 2 files changed, 111 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index b9c2db152..4aaa5bc0b 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1606,7 +1606,7 @@ class Exchange: 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 + f'Could not set margin mode due to {e.__class__.__name__}. Message: {e}') from e except ccxt.BaseError as e: raise OperationalException(e) from e diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 1bfdd376b..d76ac1bfe 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -10,6 +10,7 @@ import ccxt import pytest from pandas import DataFrame +from freqtrade.enums import Collateral from freqtrade.exceptions import (DDosProtection, DependencyException, InvalidOrderException, OperationalException, PricingError, TemporaryError) from freqtrade.exchange import Binance, Bittrex, Exchange, Kraken @@ -3023,6 +3024,71 @@ def test_get_max_leverage( def test_fill_leverage_brackets(): + api_mock = MagicMock() + api_mock.set_leverage = MagicMock(return_value=[ + { + 'amount': 0.14542341, + 'code': 'USDT', + 'datetime': '2021-09-01T08:00:01.000Z', + 'id': '485478', + 'info': {'asset': 'USDT', + 'income': '0.14542341', + 'incomeType': 'FUNDING_FEE', + 'info': 'FUNDING_FEE', + 'symbol': 'XRPUSDT', + 'time': '1630512001000', + 'tradeId': '', + 'tranId': '4854789484855218760'}, + 'symbol': 'XRP/USDT', + 'timestamp': 1630512001000 + }, + { + 'amount': -0.14642341, + 'code': 'USDT', + 'datetime': '2021-09-01T16:00:01.000Z', + 'id': '485479', + 'info': {'asset': 'USDT', + 'income': '-0.14642341', + 'incomeType': 'FUNDING_FEE', + 'info': 'FUNDING_FEE', + 'symbol': 'XRPUSDT', + 'time': '1630512001000', + 'tradeId': '', + 'tranId': '4854789484855218760'}, + 'symbol': 'XRP/USDT', + 'timestamp': 1630512001000 + } + ]) + type(api_mock).has = PropertyMock(return_value={'fetchFundingHistory': True}) + + # mocker.patch('freqtrade.exchange.Exchange.get_funding_fees', lambda pair, since: y) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + date_time = datetime.strptime("2021-09-01T00:00:01.000Z", '%Y-%m-%dT%H:%M:%S.%fZ') + unix_time = int(date_time.strftime('%s')) + expected_fees = -0.001 # 0.14542341 + -0.14642341 + fees_from_datetime = exchange.get_funding_fees( + pair='XRP/USDT', + since=date_time + ) + fees_from_unix_time = exchange.get_funding_fees( + pair='XRP/USDT', + since=unix_time + ) + + assert(isclose(expected_fees, fees_from_datetime)) + assert(isclose(expected_fees, fees_from_unix_time)) + + ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + exchange_name, + "get_funding_fees", + "fetch_funding_history", + pair="XRP/USDT", + since=unix_time + ) + # TODO-lev return @@ -3032,6 +3098,47 @@ def test_get_interest_rate(): return -def test_set_leverage(): - # TODO-lev - return +@pytest.mark.parametrize("collateral", [ + (Collateral.CROSS), + (Collateral.ISOLATED) +]) +@pytest.mark.parametrize("exchange_name", [("ftx"), ("binance")]) +def test_set_leverage(mocker, default_conf, exchange_name, collateral): + + api_mock = MagicMock() + api_mock.set_leverage = MagicMock() + type(api_mock).has = PropertyMock(return_value={'setLeverage': True}) + + ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + exchange_name, + "set_leverage", + "set_leverage", + symbol="XRP/USDT", + collateral=collateral + ) + + +@pytest.mark.parametrize("collateral", [ + (Collateral.CROSS), + (Collateral.ISOLATED) +]) +@pytest.mark.parametrize("exchange_name", [("ftx"), ("binance")]) +def test_set_margin_mode(mocker, default_conf, exchange_name, collateral): + + api_mock = MagicMock() + api_mock.set_leverage = MagicMock() + type(api_mock).has = PropertyMock(return_value={'setMarginMode': True}) + + ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + exchange_name, + "set_margin_mode", + "set_margin_mode", + symbol="XRP/USDT", + collateral=collateral + ) From 607e403eb2325ef3c29c027eecae39cbce2801d0 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 3 Sep 2021 19:25:16 -0600 Subject: [PATCH 18/53] split test_get_max_leverage into separate exchange files --- tests/exchange/test_binance.py | 8 +-- tests/exchange/test_exchange.py | 101 +------------------------------- tests/exchange/test_ftx.py | 10 ++++ tests/exchange/test_kraken.py | 15 +++++ 4 files changed, 29 insertions(+), 105 deletions(-) diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index aba185134..4cf8485a7 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -116,13 +116,7 @@ def test_stoploss_adjust_binance(mocker, default_conf): ("BNB/USDT", 5000000.0, 6.666666666666667), ("BTC/USDT", 300000000.1, 2.0), ]) -def test_get_max_leverage_binance( - default_conf, - mocker, - pair, - nominal_value, - max_lev -): +def test_get_max_leverage_binance(default_conf, mocker, pair, nominal_value, max_lev): exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange._leverage_brackets = { 'BNB/BUSD': [[0.0, 0.025], diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index d76ac1bfe..9c580ea51 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2978,10 +2978,9 @@ def test_calculate_backoff(retrycount, max_retries, expected): ('kraken', 20.0, 5.0, 20.0), ('kraken', 100.0, 100.0, 100.0), # FTX - # TODO-lev: - implement FTX tests - # ('ftx', 9.0, 3.0, 10.0), - # ('ftx', 20.0, 5.0, 20.0), - # ('ftx', 100.0, 100.0, 100.0), + ('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( exchange, @@ -2995,101 +2994,7 @@ def test_apply_leverage_to_stake_amount( assert exchange._apply_leverage_to_stake_amount(stake_amount, leverage) == min_stake_with_lev -@pytest.mark.parametrize('exchange_name,pair,nominal_value,max_lev', [ - # Kraken - ("kraken", "ADA/BTC", 0.0, 3.0), - ("kraken", "BTC/EUR", 100.0, 5.0), - ("kraken", "ZEC/USD", 173.31, 2.0), - # FTX - ("ftx", "ADA/BTC", 0.0, 20.0), - ("ftx", "BTC/EUR", 100.0, 20.0), - ("ftx", "ZEC/USD", 173.31, 20.0), - # Binance tests this method inside it's own test file -]) -def test_get_max_leverage( - default_conf, - mocker, - exchange_name, - pair, - nominal_value, - max_lev -): - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - exchange._leverage_brackets = { - 'ADA/BTC': ['2', '3'], - 'BTC/EUR': ['2', '3', '4', '5'], - 'ZEC/USD': ['2'] - } - assert exchange.get_max_leverage(pair, nominal_value) == max_lev - - def test_fill_leverage_brackets(): - api_mock = MagicMock() - api_mock.set_leverage = MagicMock(return_value=[ - { - 'amount': 0.14542341, - 'code': 'USDT', - 'datetime': '2021-09-01T08:00:01.000Z', - 'id': '485478', - 'info': {'asset': 'USDT', - 'income': '0.14542341', - 'incomeType': 'FUNDING_FEE', - 'info': 'FUNDING_FEE', - 'symbol': 'XRPUSDT', - 'time': '1630512001000', - 'tradeId': '', - 'tranId': '4854789484855218760'}, - 'symbol': 'XRP/USDT', - 'timestamp': 1630512001000 - }, - { - 'amount': -0.14642341, - 'code': 'USDT', - 'datetime': '2021-09-01T16:00:01.000Z', - 'id': '485479', - 'info': {'asset': 'USDT', - 'income': '-0.14642341', - 'incomeType': 'FUNDING_FEE', - 'info': 'FUNDING_FEE', - 'symbol': 'XRPUSDT', - 'time': '1630512001000', - 'tradeId': '', - 'tranId': '4854789484855218760'}, - 'symbol': 'XRP/USDT', - 'timestamp': 1630512001000 - } - ]) - type(api_mock).has = PropertyMock(return_value={'fetchFundingHistory': True}) - - # mocker.patch('freqtrade.exchange.Exchange.get_funding_fees', lambda pair, since: y) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - date_time = datetime.strptime("2021-09-01T00:00:01.000Z", '%Y-%m-%dT%H:%M:%S.%fZ') - unix_time = int(date_time.strftime('%s')) - expected_fees = -0.001 # 0.14542341 + -0.14642341 - fees_from_datetime = exchange.get_funding_fees( - pair='XRP/USDT', - since=date_time - ) - fees_from_unix_time = exchange.get_funding_fees( - pair='XRP/USDT', - since=unix_time - ) - - assert(isclose(expected_fees, fees_from_datetime)) - assert(isclose(expected_fees, fees_from_unix_time)) - - ccxt_exceptionhandlers( - mocker, - default_conf, - api_mock, - exchange_name, - "get_funding_fees", - "fetch_funding_history", - pair="XRP/USDT", - since=unix_time - ) - - # TODO-lev return diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index 76b01dd35..8b44b6069 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -193,3 +193,13 @@ def test_get_order_id(mocker, default_conf): } } assert exchange.get_order_id_conditional(order) == '1111' + + +@pytest.mark.parametrize('pair,nominal_value,max_lev', [ + ("ADA/BTC", 0.0, 20.0), + ("BTC/EUR", 100.0, 20.0), + ("ZEC/USD", 173.31, 20.0), +]) +def test_get_max_leverage_ftx(default_conf, mocker, pair, nominal_value, max_lev): + exchange = get_patched_exchange(mocker, default_conf, id="ftx") + assert exchange.get_max_leverage(pair, nominal_value) == max_lev diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index 60250fc71..db53ffc48 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -255,3 +255,18 @@ def test_stoploss_adjust_kraken(mocker, default_conf): # Test with invalid order case ... order['type'] = 'stop_loss_limit' assert not exchange.stoploss_adjust(1501, order, side="sell") + + +@pytest.mark.parametrize('pair,nominal_value,max_lev', [ + ("ADA/BTC", 0.0, 3.0), + ("BTC/EUR", 100.0, 5.0), + ("ZEC/USD", 173.31, 2.0), +]) +def test_get_max_leverage_kraken(default_conf, mocker, pair, nominal_value, max_lev): + exchange = get_patched_exchange(mocker, default_conf, id="kraken") + exchange._leverage_brackets = { + 'ADA/BTC': ['2', '3'], + 'BTC/EUR': ['2', '3', '4', '5'], + 'ZEC/USD': ['2'] + } + assert exchange.get_max_leverage(pair, nominal_value) == max_lev From 8264cc546d4b14ef3971eaef37c7920304d9f767 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 3 Sep 2021 19:56:13 -0600 Subject: [PATCH 19/53] Wrote dummy tests for exchange.get_interest_rate --- freqtrade/exchange/exchange.py | 4 ++-- tests/exchange/test_exchange.py | 34 ++++++++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 4aaa5bc0b..7c983e58e 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1554,9 +1554,9 @@ class Exchange: self._async_get_trade_history(pair=pair, since=since, until=until, from_id=from_id)) - def get_interest_rate(self, pair: str, open_rate: float, is_short: bool) -> float: + def get_interest_rate(self, pair: str, maker_or_taker: str, is_short: bool) -> float: # TODO-lev: implement - return 0.0005 + return (0.0005, 0.0005) def fill_leverage_brackets(self): """ diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 9c580ea51..bf89f745f 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2998,9 +2998,37 @@ def test_fill_leverage_brackets(): return -def test_get_interest_rate(): - # TODO-lev - return +# TODO-lev: These tests don't test anything real, they need to be replaced with real values once +# get_interest_rates is written +@pytest.mark.parametrize('exchange_name,pair,maker_or_taker,is_short,borrow_rate,interest_rate', [ + ('binance', "ADA/USDT", "maker", True, 0.0005, 0.0005), + ('binance', "ADA/USDT", "maker", False, 0.0005, 0.0005), + ('binance', "ADA/USDT", "taker", True, 0.0005, 0.0005), + ('binance', "ADA/USDT", "taker", False, 0.0005, 0.0005), + # Kraken + ('kraken', "ADA/USDT", "maker", True, 0.0005, 0.0005), + ('kraken', "ADA/USDT", "maker", False, 0.0005, 0.0005), + ('kraken', "ADA/USDT", "taker", True, 0.0005, 0.0005), + ('kraken', "ADA/USDT", "taker", False, 0.0005, 0.0005), + # FTX + ('ftx', "ADA/USDT", "maker", True, 0.0005, 0.0005), + ('ftx', "ADA/USDT", "maker", False, 0.0005, 0.0005), + ('ftx', "ADA/USDT", "taker", True, 0.0005, 0.0005), + ('ftx', "ADA/USDT", "taker", False, 0.0005, 0.0005), +]) +def test_get_interest_rate( + default_conf, + mocker, + exchange_name, + pair, + maker_or_taker, + is_short, + borrow_rate, + interest_rate +): + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + assert exchange.get_interest_rate( + pair, maker_or_taker, is_short) == (borrow_rate, interest_rate) @pytest.mark.parametrize("collateral", [ From 8d74233aa51d53bf48d5d3561d372b4c29700776 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 3 Sep 2021 19:56:53 -0600 Subject: [PATCH 20/53] ftx.fill_leverage_brackets test --- freqtrade/exchange/exchange.py | 2 +- tests/exchange/test_ftx.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 7c983e58e..6689731d8 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -75,7 +75,7 @@ class Exchange: } _ft_has: Dict = {} - _leverage_brackets: Dict + _leverage_brackets: Dict = {} def __init__(self, config: Dict[str, Any], validate: bool = True) -> None: """ diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index 8b44b6069..0f3870a7f 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -195,6 +195,12 @@ def test_get_order_id(mocker, default_conf): assert exchange.get_order_id_conditional(order) == '1111' +def test_fill_leverage_brackets(default_conf, mocker): + exchange = get_patched_exchange(mocker, default_conf, id="ftx") + exchange.fill_leverage_brackets() + assert bool(exchange._leverage_brackets) is False + + @pytest.mark.parametrize('pair,nominal_value,max_lev', [ ("ADA/BTC", 0.0, 20.0), ("BTC/EUR", 100.0, 20.0), From 0232f0fa18d92616fa67aed1fddbebc29db3248a Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 3 Sep 2021 20:20:42 -0600 Subject: [PATCH 21/53] Added failing fill_leverage_brackets test to test_kraken --- freqtrade/exchange/kraken.py | 2 +- tests/exchange/test_ftx.py | 1 + tests/exchange/test_kraken.py | 230 ++++++++++++++++++++++++++++++++++ 3 files changed, 232 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 567bd6735..052e7cac5 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -135,7 +135,7 @@ class Kraken(Exchange): """ leverages = {} try: - for pair, market in self.markets.items(): + for pair, market in self._api.markets.items(): info = market['info'] leverage_buy = info['leverage_buy'] leverage_sell = info['leverage_sell'] diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index 0f3870a7f..b3deae3de 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -196,6 +196,7 @@ def test_get_order_id(mocker, default_conf): def test_fill_leverage_brackets(default_conf, mocker): + # FTX only has one account wide leverage, so there's no leverage brackets exchange = get_patched_exchange(mocker, default_conf, id="ftx") exchange.fill_leverage_brackets() assert bool(exchange._leverage_brackets) is False diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index db53ffc48..eddef08b8 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -270,3 +270,233 @@ def test_get_max_leverage_kraken(default_conf, mocker, pair, nominal_value, max_ 'ZEC/USD': ['2'] } assert exchange.get_max_leverage(pair, nominal_value) == max_lev + + +def test_fill_leverage_brackets_kraken(default_conf, mocker): + api_mock = MagicMock() + api_mock.load_markets = MagicMock(return_value={{ + "ADA/BTC": {'active': True, + 'altname': 'ADAXBT', + 'base': 'ADA', + 'baseId': 'ADA', + 'darkpool': False, + 'id': 'ADAXBT', + 'info': {'aclass_base': 'currency', + 'aclass_quote': 'currency', + 'altname': 'ADAXBT', + 'base': 'ADA', + 'fee_volume_currency': 'ZUSD', + 'fees': [['0', '0.26'], + ['50000', '0.24'], + ['100000', '0.22'], + ['250000', '0.2'], + ['500000', '0.18'], + ['1000000', '0.16'], + ['2500000', '0.14'], + ['5000000', '0.12'], + ['10000000', '0.1']], + 'fees_maker': [['0', '0.16'], + ['50000', '0.14'], + ['100000', '0.12'], + ['250000', '0.1'], + ['500000', '0.08'], + ['1000000', '0.06'], + ['2500000', '0.04'], + ['5000000', '0.02'], + ['10000000', '0']], + 'leverage_buy': ['2', '3'], + 'leverage_sell': ['2', '3'], + 'lot': 'unit', + 'lot_decimals': '8', + 'lot_multiplier': '1', + 'margin_call': '80', + 'margin_stop': '40', + 'ordermin': '5', + 'pair_decimals': '8', + 'quote': 'XXBT', + 'wsname': 'ADA/XBT'}, + 'limits': {'amount': {'max': 100000000.0, 'min': 5.0}, + 'cost': {'max': None, 'min': 0}, + 'price': {'max': None, 'min': 1e-08}}, + 'maker': 0.0016, + 'percentage': True, + 'precision': {'amount': 8, 'price': 8}, + 'quote': 'BTC', + 'quoteId': 'XXBT', + 'symbol': 'ADA/BTC', + 'taker': 0.0026, + 'tierBased': True, + 'tiers': {'maker': [[0, 0.0016], + [50000, 0.0014], + [100000, 0.0012], + [250000, 0.001], + [500000, 0.0008], + [1000000, 0.0006], + [2500000, 0.0004], + [5000000, 0.0002], + [10000000, 0.0]], + 'taker': [[0, 0.0026], + [50000, 0.0024], + [100000, 0.0022], + [250000, 0.002], + [500000, 0.0018], + [1000000, 0.0016], + [2500000, 0.0014], + [5000000, 0.0012], + [10000000, 0.0001]]}}, + "BTC/EUR": {'active': True, + 'altname': 'XBTEUR', + 'base': 'BTC', + 'baseId': 'XXBT', + 'darkpool': False, + 'id': 'XXBTZEUR', + 'info': {'aclass_base': 'currency', + 'aclass_quote': 'currency', + 'altname': 'XBTEUR', + 'base': 'XXBT', + 'fee_volume_currency': 'ZUSD', + 'fees': [['0', '0.26'], + ['50000', '0.24'], + ['100000', '0.22'], + ['250000', '0.2'], + ['500000', '0.18'], + ['1000000', '0.16'], + ['2500000', '0.14'], + ['5000000', '0.12'], + ['10000000', '0.1']], + 'fees_maker': [['0', '0.16'], + ['50000', '0.14'], + ['100000', '0.12'], + ['250000', '0.1'], + ['500000', '0.08'], + ['1000000', '0.06'], + ['2500000', '0.04'], + ['5000000', '0.02'], + ['10000000', '0']], + 'leverage_buy': ['2', '3', '4', '5'], + 'leverage_sell': ['2', '3', '4', '5'], + 'lot': 'unit', + 'lot_decimals': '8', + 'lot_multiplier': '1', + 'margin_call': '80', + 'margin_stop': '40', + 'ordermin': '0.0001', + 'pair_decimals': '1', + 'quote': 'ZEUR', + 'wsname': 'XBT/EUR'}, + 'limits': {'amount': {'max': 100000000.0, 'min': 0.0001}, + 'cost': {'max': None, 'min': 0}, + 'price': {'max': None, 'min': 0.1}}, + 'maker': 0.0016, + 'percentage': True, + 'precision': {'amount': 8, 'price': 1}, + 'quote': 'EUR', + 'quoteId': 'ZEUR', + 'symbol': 'BTC/EUR', + 'taker': 0.0026, + 'tierBased': True, + 'tiers': {'maker': [[0, 0.0016], + [50000, 0.0014], + [100000, 0.0012], + [250000, 0.001], + [500000, 0.0008], + [1000000, 0.0006], + [2500000, 0.0004], + [5000000, 0.0002], + [10000000, 0.0]], + 'taker': [[0, 0.0026], + [50000, 0.0024], + [100000, 0.0022], + [250000, 0.002], + [500000, 0.0018], + [1000000, 0.0016], + [2500000, 0.0014], + [5000000, 0.0012], + [10000000, 0.0001]]}}, + "ZEC/USD": {'active': True, + 'altname': 'ZECUSD', + 'base': 'ZEC', + 'baseId': 'XZEC', + 'darkpool': False, + 'id': 'XZECZUSD', + 'info': {'aclass_base': 'currency', + 'aclass_quote': 'currency', + 'altname': 'ZECUSD', + 'base': 'XZEC', + 'fee_volume_currency': 'ZUSD', + 'fees': [['0', '0.26'], + ['50000', '0.24'], + ['100000', '0.22'], + ['250000', '0.2'], + ['500000', '0.18'], + ['1000000', '0.16'], + ['2500000', '0.14'], + ['5000000', '0.12'], + ['10000000', '0.1']], + 'fees_maker': [['0', '0.16'], + ['50000', '0.14'], + ['100000', '0.12'], + ['250000', '0.1'], + ['500000', '0.08'], + ['1000000', '0.06'], + ['2500000', '0.04'], + ['5000000', '0.02'], + ['10000000', '0']], + 'leverage_buy': ['2'], + 'leverage_sell': ['2'], + 'lot': 'unit', + 'lot_decimals': '8', + 'lot_multiplier': '1', + 'margin_call': '80', + 'margin_stop': '40', + 'ordermin': '0.035', + 'pair_decimals': '2', + 'quote': 'ZUSD', + 'wsname': 'ZEC/USD'}, + 'limits': {'amount': {'max': 100000000.0, 'min': 0.035}, + 'cost': {'max': None, 'min': 0}, + 'price': {'max': None, 'min': 0.01}}, + 'maker': 0.0016, + 'percentage': True, + 'precision': {'amount': 8, 'price': 2}, + 'quote': 'USD', + 'quoteId': 'ZUSD', + 'symbol': 'ZEC/USD', + 'taker': 0.0026, + 'tierBased': True, + 'tiers': {'maker': [[0, 0.0016], + [50000, 0.0014], + [100000, 0.0012], + [250000, 0.001], + [500000, 0.0008], + [1000000, 0.0006], + [2500000, 0.0004], + [5000000, 0.0002], + [10000000, 0.0]], + 'taker': [[0, 0.0026], + [50000, 0.0024], + [100000, 0.0022], + [250000, 0.002], + [500000, 0.0018], + [1000000, 0.0016], + [2500000, 0.0014], + [5000000, 0.0012], + [10000000, 0.0001]]}} + + }}) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") + + assert exchange._leverage_brackets == { + 'ADA/BTC': ['2', '3'], + 'BTC/EUR': ['2', '3', '4', '5'], + 'ZEC/USD': ['2'] + } + + ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + "kraken", + "fill_leverage_brackets", + "fill_leverage_brackets" + ) From 2b7d94a8551fbe64dd9225eea7ea6fcec09fb3fc Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 3 Sep 2021 20:30:19 -0600 Subject: [PATCH 22/53] Rearranged tests at end of ftx to match other exchanges --- tests/exchange/test_ftx.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index b3deae3de..771065cdd 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -195,13 +195,6 @@ def test_get_order_id(mocker, default_conf): assert exchange.get_order_id_conditional(order) == '1111' -def test_fill_leverage_brackets(default_conf, mocker): - # FTX only has one account wide leverage, so there's no leverage brackets - exchange = get_patched_exchange(mocker, default_conf, id="ftx") - exchange.fill_leverage_brackets() - assert bool(exchange._leverage_brackets) is False - - @pytest.mark.parametrize('pair,nominal_value,max_lev', [ ("ADA/BTC", 0.0, 20.0), ("BTC/EUR", 100.0, 20.0), @@ -210,3 +203,10 @@ def test_fill_leverage_brackets(default_conf, mocker): def test_get_max_leverage_ftx(default_conf, mocker, pair, nominal_value, max_lev): exchange = get_patched_exchange(mocker, default_conf, id="ftx") assert exchange.get_max_leverage(pair, nominal_value) == max_lev + + +def test_fill_leverage_brackets_ftx(default_conf, mocker): + # FTX only has one account wide leverage, so there's no leverage brackets + exchange = get_patched_exchange(mocker, default_conf, id="ftx") + exchange.fill_leverage_brackets() + assert bool(exchange._leverage_brackets) is False From cd33f69c7e6030ed79a0e7d9a4b4a87c67c0f08a Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 3 Sep 2021 20:30:52 -0600 Subject: [PATCH 23/53] Wrote failing test_fill_leverage_brackets_binance --- tests/exchange/test_binance.py | 64 ++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index 4cf8485a7..bc4cfaa36 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -145,3 +145,67 @@ def test_get_max_leverage_binance(default_conf, mocker, pair, nominal_value, max [300000000.0, 0.5]], } assert exchange.get_max_leverage(pair, nominal_value) == max_lev + + +def test_fill_leverage_brackets_binance(default_conf, mocker): + api_mock = MagicMock() + api_mock.load_leverage_brackets = MagicMock(return_value={{ + 'ADA/BUSD': [[0.0, '0.025'], + [100000.0, '0.05'], + [500000.0, '0.1'], + [1000000.0, '0.15'], + [2000000.0, '0.25'], + [5000000.0, '0.5']], + 'BTC/USDT': [[0.0, '0.004'], + [50000.0, '0.005'], + [250000.0, '0.01'], + [1000000.0, '0.025'], + [5000000.0, '0.05'], + [20000000.0, '0.1'], + [50000000.0, '0.125'], + [100000000.0, '0.15'], + [200000000.0, '0.25'], + [300000000.0, '0.5']], + "ZEC/USDT": [[0.0, '0.01'], + [5000.0, '0.025'], + [25000.0, '0.05'], + [100000.0, '0.1'], + [250000.0, '0.125'], + [1000000.0, '0.5']], + + }}) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") + + assert exchange._leverage_brackets == { + 'ADA/BUSD': [[0.0, '0.025'], + [100000.0, '0.05'], + [500000.0, '0.1'], + [1000000.0, '0.15'], + [2000000.0, '0.25'], + [5000000.0, '0.5']], + 'BTC/USDT': [[0.0, '0.004'], + [50000.0, '0.005'], + [250000.0, '0.01'], + [1000000.0, '0.025'], + [5000000.0, '0.05'], + [20000000.0, '0.1'], + [50000000.0, '0.125'], + [100000000.0, '0.15'], + [200000000.0, '0.25'], + [300000000.0, '0.5']], + "ZEC/USDT": [[0.0, '0.01'], + [5000.0, '0.025'], + [25000.0, '0.05'], + [100000.0, '0.1'], + [250000.0, '0.125'], + [1000000.0, '0.5']], + } + + ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + "binance", + "fill_leverage_brackets", + "fill_leverage_brackets" + ) From 97d1306e34285c2a2a69791ff130a23cbcd60795 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 4 Sep 2021 19:16:17 -0600 Subject: [PATCH 24/53] Added retrier to exchange functions and reduced failing tests down to 2 --- freqtrade/exchange/exchange.py | 21 ++++++++++++++++++--- tests/exchange/test_binance.py | 4 ++-- tests/exchange/test_exchange.py | 6 +++--- tests/exchange/test_kraken.py | 4 ++-- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 6689731d8..e96a9e324 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1554,10 +1554,23 @@ class Exchange: self._async_get_trade_history(pair=pair, since=since, until=until, from_id=from_id)) - def get_interest_rate(self, pair: str, maker_or_taker: str, is_short: bool) -> float: + @retrier + def get_interest_rate( + self, + pair: str, + maker_or_taker: str, + is_short: bool + ) -> Tuple[float, float]: + """ + :param pair: base/quote currency pair + :param maker_or_taker: "maker" if limit order, "taker" if market order + :param is_short: True if requesting base interest, False if requesting quote interest + :return: (open_interest, rollover_interest) + """ # TODO-lev: implement return (0.0005, 0.0005) + @retrier def fill_leverage_brackets(self): """ #TODO-lev: Should maybe be renamed, leverage_brackets might not be accurate for kraken @@ -1576,6 +1589,7 @@ class Exchange: raise OperationalException( f"{self.name.capitalize()}.get_max_leverage has not been implemented.") + @retrier def set_leverage(self, leverage: float, pair: Optional[str]): """ Set's the leverage before making a trade, in order to not @@ -1591,7 +1605,8 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e - def set_margin_mode(self, symbol: str, collateral: Collateral, params: dict = {}): + @retrier + def set_margin_mode(self, pair: str, collateral: Collateral, params: dict = {}): ''' Set's the margin mode on the exchange to cross or isolated for a specific pair :param symbol: base/quote currency pair (e.g. "ADA/USDT") @@ -1601,7 +1616,7 @@ class Exchange: return try: - self._api.set_margin_mode(symbol, collateral.value, params) + self._api.set_margin_mode(pair, collateral.value, params) except ccxt.DDoSProtection as e: raise DDosProtection(e) from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index bc4cfaa36..aa4c4c62e 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -149,7 +149,7 @@ def test_get_max_leverage_binance(default_conf, mocker, pair, nominal_value, max def test_fill_leverage_brackets_binance(default_conf, mocker): api_mock = MagicMock() - api_mock.load_leverage_brackets = MagicMock(return_value={{ + api_mock.load_leverage_brackets = MagicMock(return_value={ 'ADA/BUSD': [[0.0, '0.025'], [100000.0, '0.05'], [500000.0, '0.1'], @@ -173,7 +173,7 @@ def test_fill_leverage_brackets_binance(default_conf, mocker): [250000.0, '0.125'], [1000000.0, '0.5']], - }}) + }) exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") assert exchange._leverage_brackets == { diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index bf89f745f..bcb27c8ed 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -3049,8 +3049,8 @@ def test_set_leverage(mocker, default_conf, exchange_name, collateral): exchange_name, "set_leverage", "set_leverage", - symbol="XRP/USDT", - collateral=collateral + pair="XRP/USDT", + leverage=5.0 ) @@ -3072,6 +3072,6 @@ def test_set_margin_mode(mocker, default_conf, exchange_name, collateral): exchange_name, "set_margin_mode", "set_margin_mode", - symbol="XRP/USDT", + pair="XRP/USDT", collateral=collateral ) diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index eddef08b8..90c032679 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -274,7 +274,7 @@ def test_get_max_leverage_kraken(default_conf, mocker, pair, nominal_value, max_ def test_fill_leverage_brackets_kraken(default_conf, mocker): api_mock = MagicMock() - api_mock.load_markets = MagicMock(return_value={{ + api_mock.load_markets = MagicMock(return_value={ "ADA/BTC": {'active': True, 'altname': 'ADAXBT', 'base': 'ADA', @@ -483,7 +483,7 @@ def test_fill_leverage_brackets_kraken(default_conf, mocker): [5000000, 0.0012], [10000000, 0.0001]]}} - }}) + }) exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") assert exchange._leverage_brackets == { From 619ecc9728f42c8d6c7f027659182f7904116e20 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 4 Sep 2021 19:47:04 -0600 Subject: [PATCH 25/53] Added exceptions to exchange.interest_rate --- freqtrade/exchange/exchange.py | 13 +++++++++++-- tests/exchange/test_exchange.py | 24 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index e96a9e324..4c11937b2 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1562,13 +1562,22 @@ class Exchange: is_short: bool ) -> Tuple[float, float]: """ + Gets the rate of interest for borrowed currency when margin trading :param pair: base/quote currency pair :param maker_or_taker: "maker" if limit order, "taker" if market order :param is_short: True if requesting base interest, False if requesting quote interest :return: (open_interest, rollover_interest) """ - # TODO-lev: implement - return (0.0005, 0.0005) + try: + # TODO-lev: implement, currently there is no ccxt method for this + return (0.0005, 0.0005) + 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 @retrier def fill_leverage_brackets(self): diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index bcb27c8ed..f7c0b0f38 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -3031,6 +3031,30 @@ def test_get_interest_rate( pair, maker_or_taker, is_short) == (borrow_rate, interest_rate) +@pytest.mark.parametrize("exchange_name", [("binance"), ("ftx"), ("kraken")]) +@pytest.mark.parametrize("maker_or_taker", [("maker"), ("taker")]) +@pytest.mark.parametrize("is_short", [(True), (False)]) +def test_get_interest_rate_exceptions(mocker, default_conf, exchange_name, maker_or_taker, is_short): + + # api_mock = MagicMock() + # # TODO-lev: get_interest_rate currently not implemented on CCXT, so this may need to be renamed + # api_mock.get_interest_rate = MagicMock() + # type(api_mock).has = PropertyMock(return_value={'getInterestRate': True}) + + # ccxt_exceptionhandlers( + # mocker, + # default_conf, + # api_mock, + # exchange_name, + # "get_interest_rate", + # "get_interest_rate", + # pair="XRP/USDT", + # is_short=is_short, + # maker_or_taker="maker_or_taker" + # ) + return + + @pytest.mark.parametrize("collateral", [ (Collateral.CROSS), (Collateral.ISOLATED) From d1c4030b88b13f3e645d95a4f0ed7876bb746b1b Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 4 Sep 2021 19:58:42 -0600 Subject: [PATCH 26/53] fill_leverage_brackets usinge self.markets.items instead of self._api.markets.items --- freqtrade/exchange/kraken.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 052e7cac5..567bd6735 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -135,7 +135,7 @@ class Kraken(Exchange): """ leverages = {} try: - for pair, market in self._api.markets.items(): + for pair, market in self.markets.items(): info = market['info'] leverage_buy = info['leverage_buy'] leverage_sell = info['leverage_sell'] From 9e73d026637acc1893a37e0dde7b4322f3a6cbe0 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 4 Sep 2021 21:55:55 -0600 Subject: [PATCH 27/53] Added validating checks for trading_mode and collateral on each exchange --- freqtrade/exchange/binance.py | 10 +++++- freqtrade/exchange/exchange.py | 59 ++++++++++++++++++++++++------- freqtrade/exchange/ftx.py | 9 ++++- freqtrade/exchange/kraken.py | 17 ++++++--- tests/exchange/test_exchange.py | 61 +++++++++++++++++++++++++++++++-- 5 files changed, 133 insertions(+), 23 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 3117f5ee1..3d491c867 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -1,9 +1,10 @@ """ Binance exchange subclass """ import logging -from typing import Dict, Optional +from typing import Dict, List, Optional, Tuple import ccxt +from freqtrade.enums import Collateral, TradingMode from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException, OperationalException, TemporaryError) from freqtrade.exchange import Exchange @@ -25,6 +26,13 @@ class Binance(Exchange): "l2_limit_range": [5, 10, 20, 50, 100, 500, 1000], } + _supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [ + # TradingMode.SPOT always supported and not required in this list + # (TradingMode.MARGIN, Collateral.CROSS), # TODO-lev: Uncomment once supported + # (TradingMode.FUTURES, Collateral.CROSS), # TODO-lev: Uncomment once supported + # (TradingMode.FUTURES, Collateral.ISOLATED) # TODO-lev: Uncomment once supported + ] + def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 4c11937b2..d6004760a 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -22,7 +22,7 @@ from pandas import DataFrame from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, ListPairsWithTimeframes) from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list -from freqtrade.enums import Collateral +from freqtrade.enums import Collateral, TradingMode from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError, InvalidOrderException, OperationalException, PricingError, RetryableOrderError, TemporaryError) @@ -77,6 +77,10 @@ class Exchange: _leverage_brackets: Dict = {} + _supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [ + # TradingMode.SPOT always supported and not required in this list + ] + def __init__(self, config: Dict[str, Any], validate: bool = True) -> None: """ Initializes this module with the given config, @@ -142,6 +146,26 @@ class Exchange: self._api_async = self._init_ccxt( exchange_config, ccxt_async, ccxt_kwargs=ccxt_async_config) + trading_mode: TradingMode = ( + TradingMode(config.get('trading_mode')) + if config.get('trading_mode') + else TradingMode.SPOT + ) + collateral: Optional[Collateral] = ( + Collateral(config.get('collateral')) + if config.get('collateral') + else None + ) + + if trading_mode != TradingMode.SPOT: + try: + # TODO-lev: This shouldn't need to happen, but for some reason I get that the + # TODO-lev: method isn't implemented + self.fill_leverage_brackets() + except Exception as error: + logger.debug(error) + logger.debug("Could not load leverage_brackets") + logger.info('Using Exchange "%s"', self.name) if validate: @@ -159,21 +183,11 @@ 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) # Converts the interval provided in minutes in config to seconds self.markets_refresh_interval: int = exchange_config.get( "markets_refresh_interval", 60) * 60 - leverage = config.get('leverage_mode') - if leverage is not False: - try: - # TODO-lev: This shouldn't need to happen, but for some reason I get that the - # TODO-lev: method isn't implemented - self.fill_leverage_brackets() - except Exception as error: - logger.debug(error) - logger.debug("Could not load leverage_brackets") - def __del__(self): """ Destructor - clean up async stuff @@ -384,7 +398,7 @@ class Exchange: raise OperationalException( 'Could not load markets, therefore cannot start. ' 'Please investigate the above error for more details.' - ) + ) quote_currencies = self.get_quote_currencies() if stake_currency not in quote_currencies: raise OperationalException( @@ -496,6 +510,25 @@ class Exchange: f"This strategy requires {startup_candles} candles to start. " f"{self.name} only provides {candle_limit} for {timeframe}.") + def validate_trading_mode_and_collateral( + self, + trading_mode: TradingMode, + collateral: Optional[Collateral] # Only None when trading_mode = TradingMode.SPOT + ): + """ + Checks if freqtrade can perform trades using the configured + trading mode(Margin, Futures) and Collateral(Cross, Isolated) + Throws OperationalException: + If the trading_mode/collateral type are not supported by freqtrade on this exchange + """ + if trading_mode != TradingMode.SPOT and ( + (trading_mode, collateral) not in self._supported_trading_mode_collateral_pairs + ): + collateral_value = collateral and collateral.value + raise OperationalException( + f"Freqtrade does not support {collateral_value} {trading_mode.value} on {self.name}" + ) + def exchange_has(self, endpoint: str) -> bool: """ Checks if exchange implements a specific API endpoint. diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 1dc30002e..3e6ff01a3 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -1,9 +1,10 @@ """ FTX exchange subclass """ import logging -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional, Tuple import ccxt +from freqtrade.enums import Collateral, TradingMode from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException, OperationalException, TemporaryError) from freqtrade.exchange import Exchange @@ -21,6 +22,12 @@ class Ftx(Exchange): "ohlcv_candle_limit": 1500, } + _supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [ + # TradingMode.SPOT always supported and not required in this list + # (TradingMode.MARGIN, Collateral.CROSS), # TODO-lev: Uncomment once supported + # (TradingMode.FUTURES, Collateral.CROSS) # TODO-lev: Uncomment once supported + ] + def market_is_tradable(self, market: Dict[str, Any]) -> bool: """ Check if the market symbol is tradable by Freqtrade. diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 567bd6735..7c36c421b 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -1,9 +1,10 @@ """ Kraken exchange subclass """ import logging -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional, Tuple import ccxt +from freqtrade.enums import Collateral, TradingMode from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException, OperationalException, TemporaryError) from freqtrade.exchange import Exchange @@ -23,6 +24,12 @@ class Kraken(Exchange): "trades_pagination_arg": "since", } + _supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [ + # TradingMode.SPOT always supported and not required in this list + # (TradingMode.MARGIN, Collateral.CROSS), # TODO-lev: Uncomment once supported + # (TradingMode.FUTURES, Collateral.CROSS) # TODO-lev: No CCXT support + ] + def market_is_tradable(self, market: Dict[str, Any]) -> bool: """ Check if the market symbol is tradable by Freqtrade. @@ -33,7 +40,7 @@ class Kraken(Exchange): return (parent_check and market.get('darkpool', False) is False) - @retrier + @ retrier def get_balances(self) -> dict: if self._config['dry_run']: return {} @@ -48,8 +55,8 @@ class Kraken(Exchange): orders = self._api.fetch_open_orders() order_list = [(x["symbol"].split("/")[0 if x["side"] == "sell" else 1], - x["remaining"] if x["side"] == "sell" else x["remaining"] * x["price"], - # Don't remove the below comment, this can be important for debugging + x["remaining"] if x["side"] == "sell" else x["remaining"] * x["price"], + # Don't remove the below comment, this can be important for debugging # x["side"], x["amount"], ) for x in orders] for bal in balances: @@ -77,7 +84,7 @@ class Kraken(Exchange): (side == "buy" and stop_loss < float(order['price'])) )) - @retrier(retries=0) + @ retrier(retries=0) def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict, side: str) -> Dict: """ diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index f7c0b0f38..1915b3f34 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -10,7 +10,7 @@ import ccxt import pytest from pandas import DataFrame -from freqtrade.enums import Collateral +from freqtrade.enums import Collateral, TradingMode from freqtrade.exceptions import (DDosProtection, DependencyException, InvalidOrderException, OperationalException, PricingError, TemporaryError) from freqtrade.exchange import Binance, Bittrex, Exchange, Kraken @@ -3034,10 +3034,16 @@ def test_get_interest_rate( @pytest.mark.parametrize("exchange_name", [("binance"), ("ftx"), ("kraken")]) @pytest.mark.parametrize("maker_or_taker", [("maker"), ("taker")]) @pytest.mark.parametrize("is_short", [(True), (False)]) -def test_get_interest_rate_exceptions(mocker, default_conf, exchange_name, maker_or_taker, is_short): +def test_get_interest_rate_exceptions( + mocker, + default_conf, + exchange_name, + maker_or_taker, + is_short +): # api_mock = MagicMock() - # # TODO-lev: get_interest_rate currently not implemented on CCXT, so this may need to be renamed + # # TODO-lev: get_interest_rate currently not implemented on CCXT, so this may be renamed # api_mock.get_interest_rate = MagicMock() # type(api_mock).has = PropertyMock(return_value={'getInterestRate': True}) @@ -3099,3 +3105,52 @@ def test_set_margin_mode(mocker, default_conf, exchange_name, collateral): pair="XRP/USDT", collateral=collateral ) + + +@pytest.mark.parametrize("exchange_name, trading_mode, collateral, exception_thrown", [ + ("binance", TradingMode.SPOT, None, False), + ("binance", TradingMode.MARGIN, Collateral.ISOLATED, True), + ("kraken", TradingMode.SPOT, None, False), + ("kraken", TradingMode.MARGIN, Collateral.ISOLATED, True), + ("kraken", TradingMode.FUTURES, Collateral.ISOLATED, True), + ("ftx", TradingMode.SPOT, None, False), + ("ftx", TradingMode.MARGIN, Collateral.ISOLATED, True), + ("ftx", TradingMode.FUTURES, Collateral.ISOLATED, True), + ("bittrex", TradingMode.SPOT, None, False), + ("bittrex", TradingMode.MARGIN, Collateral.CROSS, True), + ("bittrex", TradingMode.MARGIN, Collateral.ISOLATED, True), + ("bittrex", TradingMode.FUTURES, Collateral.CROSS, True), + ("bittrex", TradingMode.FUTURES, Collateral.ISOLATED, True), + + # TODO-lev: Remove once implemented + ("binance", TradingMode.MARGIN, Collateral.CROSS, True), + ("binance", TradingMode.FUTURES, Collateral.CROSS, True), + ("binance", TradingMode.FUTURES, Collateral.ISOLATED, True), + ("kraken", TradingMode.MARGIN, Collateral.CROSS, True), + ("kraken", TradingMode.FUTURES, Collateral.CROSS, True), + ("ftx", TradingMode.MARGIN, Collateral.CROSS, True), + ("ftx", TradingMode.FUTURES, Collateral.CROSS, True), + + # TODO-lev: Uncomment once implemented + # ("binance", TradingMode.MARGIN, Collateral.CROSS, False), + # ("binance", TradingMode.FUTURES, Collateral.CROSS, False), + # ("binance", TradingMode.FUTURES, Collateral.ISOLATED, False), + # ("kraken", TradingMode.MARGIN, Collateral.CROSS, False), + # ("kraken", TradingMode.FUTURES, Collateral.CROSS, False), + # ("ftx", TradingMode.MARGIN, Collateral.ISOLATED, False), + # ("ftx", TradingMode.FUTURES, Collateral.ISOLATED, False) +]) +def test_validate_trading_mode_and_collateral( + default_conf, + mocker, + exchange_name, + trading_mode, + collateral, + exception_thrown +): + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + if (exception_thrown): + with pytest.raises(OperationalException): + exchange.validate_trading_mode_and_collateral(trading_mode, collateral) + else: + exchange.validate_trading_mode_and_collateral(trading_mode, collateral) From 93da13212c6cb46534a2725739b9c47dd225b3ec Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 5 Sep 2021 22:27:14 -0600 Subject: [PATCH 28/53] test_fill_leverage_brackets_kraken and test_get_max_leverage_binance now pass but test_fill_leverage_brackets_ftx does not if called after test_get_max_leverage_binance --- freqtrade/exchange/binance.py | 5 +- freqtrade/exchange/exchange.py | 8 +- freqtrade/exchange/kraken.py | 42 +++--- tests/conftest.py | 27 +++- tests/exchange/test_binance.py | 97 +++++++------- tests/exchange/test_exchange.py | 6 +- tests/exchange/test_ftx.py | 2 +- tests/exchange/test_kraken.py | 226 +------------------------------- 8 files changed, 103 insertions(+), 310 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 3d491c867..9a96e1f19 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -111,6 +111,7 @@ class Binance(Exchange): def _apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float): return stake_amount / leverage + @retrier def fill_leverage_brackets(self): """ Assigns property _leverage_brackets to a dictionary of information about the leverage @@ -118,8 +119,8 @@ class Binance(Exchange): """ try: leverage_brackets = self._api.load_leverage_brackets() - for pair, brackets in leverage_brackets.items: - self.leverage_brackets[pair] = [ + for pair, brackets in leverage_brackets.items(): + self._leverage_brackets[pair] = [ [ min_amount, float(margin_req) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index d6004760a..6e25689f3 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -158,13 +158,7 @@ class Exchange: ) if trading_mode != TradingMode.SPOT: - try: - # TODO-lev: This shouldn't need to happen, but for some reason I get that the - # TODO-lev: method isn't implemented - self.fill_leverage_brackets() - except Exception as error: - logger.debug(error) - logger.debug("Could not load leverage_brackets") + self.fill_leverage_brackets() logger.info('Using Exchange "%s"', self.name) diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 7c36c421b..5207018ad 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -141,30 +141,24 @@ class Kraken(Exchange): allowed on each pair """ leverages = {} - try: - for pair, market in self.markets.items(): - info = market['info'] - leverage_buy = info['leverage_buy'] - leverage_sell = info['leverage_sell'] - if len(info['leverage_buy']) > 0 or len(info['leverage_sell']) > 0: - if leverage_buy != leverage_sell: - logger.warning(f"The buy leverage != the sell leverage for {pair}. Please" - "let freqtrade know because this has never happened before" - ) - if max(leverage_buy) < max(leverage_sell): - leverages[pair] = leverage_buy - else: - leverages[pair] = leverage_sell - else: + + for pair, market in self.markets.items(): + info = market['info'] + leverage_buy = info['leverage_buy'] if 'leverage_buy' in info else [] + leverage_sell = info['leverage_sell'] if 'leverage_sell' in info else [] + if len(leverage_buy) > 0 or len(leverage_sell) > 0: + if leverage_buy != leverage_sell: + logger.warning( + f"The buy({leverage_buy}) and sell({leverage_sell}) leverage are not equal" + "{pair}. Please let freqtrade know because this has never happened before" + ) + if max(leverage_buy) < max(leverage_sell): leverages[pair] = leverage_buy - self._leverage_brackets = leverages - except ccxt.DDoSProtection as e: - raise DDosProtection(e) from e - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError(f'Could not fetch leverage amounts due to' - f'{e.__class__.__name__}. Message: {e}') from e - except ccxt.BaseError as e: - raise OperationalException(e) from e + else: + leverages[pair] = leverage_sell + else: + leverages[pair] = leverage_buy + self._leverage_brackets = leverages def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float: """ @@ -176,7 +170,7 @@ class Kraken(Exchange): def set_leverage(self, pair, leverage): """ - Kraken set's the leverage as an option it the order object, so it doesn't do + Kraken set's the leverage as an option in the order object, so it doesn't do anything in this function """ return diff --git a/tests/conftest.py b/tests/conftest.py index 188236f40..f4cbef686 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -437,7 +437,10 @@ def get_markets(): 'max': 500000, }, }, - 'info': {}, + 'info': { + 'leverage_buy': ['2'], + 'leverage_sell': ['2'], + }, }, 'TKN/BTC': { 'id': 'tknbtc', @@ -463,7 +466,10 @@ def get_markets(): 'max': 500000, }, }, - 'info': {}, + 'info': { + 'leverage_buy': ['2', '3', '4', '5'], + 'leverage_sell': ['2', '3', '4', '5'], + }, }, 'BLK/BTC': { 'id': 'blkbtc', @@ -488,7 +494,10 @@ def get_markets(): 'max': 500000, }, }, - 'info': {}, + 'info': { + 'leverage_buy': ['2', '3'], + 'leverage_sell': ['2', '3'], + }, }, 'LTC/BTC': { 'id': 'ltcbtc', @@ -513,7 +522,10 @@ def get_markets(): 'max': 500000, }, }, - 'info': {}, + 'info': { + 'leverage_buy': [], + 'leverage_sell': [], + }, }, 'XRP/BTC': { 'id': 'xrpbtc', @@ -591,7 +603,10 @@ def get_markets(): 'max': None } }, - 'info': {}, + 'info': { + 'leverage_buy': [], + 'leverage_sell': [], + }, }, 'ETH/USDT': { 'id': 'USDT-ETH', @@ -707,6 +722,8 @@ def get_markets(): 'max': None } }, + 'info': { + } }, } diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index aa4c4c62e..f2bd68154 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -1,5 +1,5 @@ from random import randint -from unittest.mock import MagicMock +from unittest.mock import MagicMock, PropertyMock import ccxt import pytest @@ -150,62 +150,67 @@ def test_get_max_leverage_binance(default_conf, mocker, pair, nominal_value, max def test_fill_leverage_brackets_binance(default_conf, mocker): api_mock = MagicMock() api_mock.load_leverage_brackets = MagicMock(return_value={ - 'ADA/BUSD': [[0.0, '0.025'], - [100000.0, '0.05'], - [500000.0, '0.1'], - [1000000.0, '0.15'], - [2000000.0, '0.25'], - [5000000.0, '0.5']], - 'BTC/USDT': [[0.0, '0.004'], - [50000.0, '0.005'], - [250000.0, '0.01'], - [1000000.0, '0.025'], - [5000000.0, '0.05'], - [20000000.0, '0.1'], - [50000000.0, '0.125'], - [100000000.0, '0.15'], - [200000000.0, '0.25'], - [300000000.0, '0.5']], - "ZEC/USDT": [[0.0, '0.01'], - [5000.0, '0.025'], - [25000.0, '0.05'], - [100000.0, '0.1'], - [250000.0, '0.125'], - [1000000.0, '0.5']], + 'ADA/BUSD': [[0.0, 0.025], + [100000.0, 0.05], + [500000.0, 0.1], + [1000000.0, 0.15], + [2000000.0, 0.25], + [5000000.0, 0.5]], + 'BTC/USDT': [[0.0, 0.004], + [50000.0, 0.005], + [250000.0, 0.01], + [1000000.0, 0.025], + [5000000.0, 0.05], + [20000000.0, 0.1], + [50000000.0, 0.125], + [100000000.0, 0.15], + [200000000.0, 0.25], + [300000000.0, 0.5]], + "ZEC/USDT": [[0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5]], }) exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") + exchange.fill_leverage_brackets() assert exchange._leverage_brackets == { - 'ADA/BUSD': [[0.0, '0.025'], - [100000.0, '0.05'], - [500000.0, '0.1'], - [1000000.0, '0.15'], - [2000000.0, '0.25'], - [5000000.0, '0.5']], - 'BTC/USDT': [[0.0, '0.004'], - [50000.0, '0.005'], - [250000.0, '0.01'], - [1000000.0, '0.025'], - [5000000.0, '0.05'], - [20000000.0, '0.1'], - [50000000.0, '0.125'], - [100000000.0, '0.15'], - [200000000.0, '0.25'], - [300000000.0, '0.5']], - "ZEC/USDT": [[0.0, '0.01'], - [5000.0, '0.025'], - [25000.0, '0.05'], - [100000.0, '0.1'], - [250000.0, '0.125'], - [1000000.0, '0.5']], + 'ADA/BUSD': [[0.0, 0.025], + [100000.0, 0.05], + [500000.0, 0.1], + [1000000.0, 0.15], + [2000000.0, 0.25], + [5000000.0, 0.5]], + 'BTC/USDT': [[0.0, 0.004], + [50000.0, 0.005], + [250000.0, 0.01], + [1000000.0, 0.025], + [5000000.0, 0.05], + [20000000.0, 0.1], + [50000000.0, 0.125], + [100000000.0, 0.15], + [200000000.0, 0.25], + [300000000.0, 0.5]], + "ZEC/USDT": [[0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5]], } + api_mock = MagicMock() + api_mock.load_leverage_brackets = MagicMock() + type(api_mock).has = PropertyMock(return_value={'loadLeverageBrackets': True}) + ccxt_exceptionhandlers( mocker, default_conf, api_mock, "binance", "fill_leverage_brackets", - "fill_leverage_brackets" + "load_leverage_brackets" ) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 1915b3f34..348aa3290 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -3092,7 +3092,7 @@ def test_set_leverage(mocker, default_conf, exchange_name, collateral): def test_set_margin_mode(mocker, default_conf, exchange_name, collateral): api_mock = MagicMock() - api_mock.set_leverage = MagicMock() + api_mock.set_margin_mode = MagicMock() type(api_mock).has = PropertyMock(return_value={'setMarginMode': True}) ccxt_exceptionhandlers( @@ -3137,8 +3137,8 @@ def test_set_margin_mode(mocker, default_conf, exchange_name, collateral): # ("binance", TradingMode.FUTURES, Collateral.ISOLATED, False), # ("kraken", TradingMode.MARGIN, Collateral.CROSS, False), # ("kraken", TradingMode.FUTURES, Collateral.CROSS, False), - # ("ftx", TradingMode.MARGIN, Collateral.ISOLATED, False), - # ("ftx", TradingMode.FUTURES, Collateral.ISOLATED, False) + # ("ftx", TradingMode.MARGIN, Collateral.CROSS, False), + # ("ftx", TradingMode.FUTURES, Collateral.CROSS, False) ]) def test_validate_trading_mode_and_collateral( default_conf, diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index 771065cdd..1ed528dd9 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -209,4 +209,4 @@ def test_fill_leverage_brackets_ftx(default_conf, mocker): # FTX only has one account wide leverage, so there's no leverage brackets exchange = get_patched_exchange(mocker, default_conf, id="ftx") exchange.fill_leverage_brackets() - assert bool(exchange._leverage_brackets) is False + assert exchange._leverage_brackets == {} diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index 90c032679..8222f5ce8 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -274,229 +274,11 @@ def test_get_max_leverage_kraken(default_conf, mocker, pair, nominal_value, max_ def test_fill_leverage_brackets_kraken(default_conf, mocker): api_mock = MagicMock() - api_mock.load_markets = MagicMock(return_value={ - "ADA/BTC": {'active': True, - 'altname': 'ADAXBT', - 'base': 'ADA', - 'baseId': 'ADA', - 'darkpool': False, - 'id': 'ADAXBT', - 'info': {'aclass_base': 'currency', - 'aclass_quote': 'currency', - 'altname': 'ADAXBT', - 'base': 'ADA', - 'fee_volume_currency': 'ZUSD', - 'fees': [['0', '0.26'], - ['50000', '0.24'], - ['100000', '0.22'], - ['250000', '0.2'], - ['500000', '0.18'], - ['1000000', '0.16'], - ['2500000', '0.14'], - ['5000000', '0.12'], - ['10000000', '0.1']], - 'fees_maker': [['0', '0.16'], - ['50000', '0.14'], - ['100000', '0.12'], - ['250000', '0.1'], - ['500000', '0.08'], - ['1000000', '0.06'], - ['2500000', '0.04'], - ['5000000', '0.02'], - ['10000000', '0']], - 'leverage_buy': ['2', '3'], - 'leverage_sell': ['2', '3'], - 'lot': 'unit', - 'lot_decimals': '8', - 'lot_multiplier': '1', - 'margin_call': '80', - 'margin_stop': '40', - 'ordermin': '5', - 'pair_decimals': '8', - 'quote': 'XXBT', - 'wsname': 'ADA/XBT'}, - 'limits': {'amount': {'max': 100000000.0, 'min': 5.0}, - 'cost': {'max': None, 'min': 0}, - 'price': {'max': None, 'min': 1e-08}}, - 'maker': 0.0016, - 'percentage': True, - 'precision': {'amount': 8, 'price': 8}, - 'quote': 'BTC', - 'quoteId': 'XXBT', - 'symbol': 'ADA/BTC', - 'taker': 0.0026, - 'tierBased': True, - 'tiers': {'maker': [[0, 0.0016], - [50000, 0.0014], - [100000, 0.0012], - [250000, 0.001], - [500000, 0.0008], - [1000000, 0.0006], - [2500000, 0.0004], - [5000000, 0.0002], - [10000000, 0.0]], - 'taker': [[0, 0.0026], - [50000, 0.0024], - [100000, 0.0022], - [250000, 0.002], - [500000, 0.0018], - [1000000, 0.0016], - [2500000, 0.0014], - [5000000, 0.0012], - [10000000, 0.0001]]}}, - "BTC/EUR": {'active': True, - 'altname': 'XBTEUR', - 'base': 'BTC', - 'baseId': 'XXBT', - 'darkpool': False, - 'id': 'XXBTZEUR', - 'info': {'aclass_base': 'currency', - 'aclass_quote': 'currency', - 'altname': 'XBTEUR', - 'base': 'XXBT', - 'fee_volume_currency': 'ZUSD', - 'fees': [['0', '0.26'], - ['50000', '0.24'], - ['100000', '0.22'], - ['250000', '0.2'], - ['500000', '0.18'], - ['1000000', '0.16'], - ['2500000', '0.14'], - ['5000000', '0.12'], - ['10000000', '0.1']], - 'fees_maker': [['0', '0.16'], - ['50000', '0.14'], - ['100000', '0.12'], - ['250000', '0.1'], - ['500000', '0.08'], - ['1000000', '0.06'], - ['2500000', '0.04'], - ['5000000', '0.02'], - ['10000000', '0']], - 'leverage_buy': ['2', '3', '4', '5'], - 'leverage_sell': ['2', '3', '4', '5'], - 'lot': 'unit', - 'lot_decimals': '8', - 'lot_multiplier': '1', - 'margin_call': '80', - 'margin_stop': '40', - 'ordermin': '0.0001', - 'pair_decimals': '1', - 'quote': 'ZEUR', - 'wsname': 'XBT/EUR'}, - 'limits': {'amount': {'max': 100000000.0, 'min': 0.0001}, - 'cost': {'max': None, 'min': 0}, - 'price': {'max': None, 'min': 0.1}}, - 'maker': 0.0016, - 'percentage': True, - 'precision': {'amount': 8, 'price': 1}, - 'quote': 'EUR', - 'quoteId': 'ZEUR', - 'symbol': 'BTC/EUR', - 'taker': 0.0026, - 'tierBased': True, - 'tiers': {'maker': [[0, 0.0016], - [50000, 0.0014], - [100000, 0.0012], - [250000, 0.001], - [500000, 0.0008], - [1000000, 0.0006], - [2500000, 0.0004], - [5000000, 0.0002], - [10000000, 0.0]], - 'taker': [[0, 0.0026], - [50000, 0.0024], - [100000, 0.0022], - [250000, 0.002], - [500000, 0.0018], - [1000000, 0.0016], - [2500000, 0.0014], - [5000000, 0.0012], - [10000000, 0.0001]]}}, - "ZEC/USD": {'active': True, - 'altname': 'ZECUSD', - 'base': 'ZEC', - 'baseId': 'XZEC', - 'darkpool': False, - 'id': 'XZECZUSD', - 'info': {'aclass_base': 'currency', - 'aclass_quote': 'currency', - 'altname': 'ZECUSD', - 'base': 'XZEC', - 'fee_volume_currency': 'ZUSD', - 'fees': [['0', '0.26'], - ['50000', '0.24'], - ['100000', '0.22'], - ['250000', '0.2'], - ['500000', '0.18'], - ['1000000', '0.16'], - ['2500000', '0.14'], - ['5000000', '0.12'], - ['10000000', '0.1']], - 'fees_maker': [['0', '0.16'], - ['50000', '0.14'], - ['100000', '0.12'], - ['250000', '0.1'], - ['500000', '0.08'], - ['1000000', '0.06'], - ['2500000', '0.04'], - ['5000000', '0.02'], - ['10000000', '0']], - 'leverage_buy': ['2'], - 'leverage_sell': ['2'], - 'lot': 'unit', - 'lot_decimals': '8', - 'lot_multiplier': '1', - 'margin_call': '80', - 'margin_stop': '40', - 'ordermin': '0.035', - 'pair_decimals': '2', - 'quote': 'ZUSD', - 'wsname': 'ZEC/USD'}, - 'limits': {'amount': {'max': 100000000.0, 'min': 0.035}, - 'cost': {'max': None, 'min': 0}, - 'price': {'max': None, 'min': 0.01}}, - 'maker': 0.0016, - 'percentage': True, - 'precision': {'amount': 8, 'price': 2}, - 'quote': 'USD', - 'quoteId': 'ZUSD', - 'symbol': 'ZEC/USD', - 'taker': 0.0026, - 'tierBased': True, - 'tiers': {'maker': [[0, 0.0016], - [50000, 0.0014], - [100000, 0.0012], - [250000, 0.001], - [500000, 0.0008], - [1000000, 0.0006], - [2500000, 0.0004], - [5000000, 0.0002], - [10000000, 0.0]], - 'taker': [[0, 0.0026], - [50000, 0.0024], - [100000, 0.0022], - [250000, 0.002], - [500000, 0.0018], - [1000000, 0.0016], - [2500000, 0.0014], - [5000000, 0.0012], - [10000000, 0.0001]]}} - - }) exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") + exchange.fill_leverage_brackets() assert exchange._leverage_brackets == { - 'ADA/BTC': ['2', '3'], - 'BTC/EUR': ['2', '3', '4', '5'], - 'ZEC/USD': ['2'] + 'BLK/BTC': ['2', '3'], + 'TKN/BTC': ['2', '3', '4', '5'], + 'ETH/BTC': ['2'] } - - ccxt_exceptionhandlers( - mocker, - default_conf, - api_mock, - "kraken", - "fill_leverage_brackets", - "fill_leverage_brackets" - ) From 9f96b977f6a1ea08d036dc53ebacd2aed2cea976 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Thu, 9 Sep 2021 13:42:35 -0600 Subject: [PATCH 29/53] removed interest method from exchange, will create a separate interest PR --- freqtrade/exchange/exchange.py | 25 ------------- tests/exchange/test_exchange.py | 63 --------------------------------- 2 files changed, 88 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 6e25689f3..dcc6e513a 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1581,31 +1581,6 @@ class Exchange: self._async_get_trade_history(pair=pair, since=since, until=until, from_id=from_id)) - @retrier - def get_interest_rate( - self, - pair: str, - maker_or_taker: str, - is_short: bool - ) -> Tuple[float, float]: - """ - Gets the rate of interest for borrowed currency when margin trading - :param pair: base/quote currency pair - :param maker_or_taker: "maker" if limit order, "taker" if market order - :param is_short: True if requesting base interest, False if requesting quote interest - :return: (open_interest, rollover_interest) - """ - try: - # TODO-lev: implement, currently there is no ccxt method for this - return (0.0005, 0.0005) - 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 - @retrier def fill_leverage_brackets(self): """ diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 348aa3290..5d7901420 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2998,69 +2998,6 @@ def test_fill_leverage_brackets(): return -# TODO-lev: These tests don't test anything real, they need to be replaced with real values once -# get_interest_rates is written -@pytest.mark.parametrize('exchange_name,pair,maker_or_taker,is_short,borrow_rate,interest_rate', [ - ('binance', "ADA/USDT", "maker", True, 0.0005, 0.0005), - ('binance', "ADA/USDT", "maker", False, 0.0005, 0.0005), - ('binance', "ADA/USDT", "taker", True, 0.0005, 0.0005), - ('binance', "ADA/USDT", "taker", False, 0.0005, 0.0005), - # Kraken - ('kraken', "ADA/USDT", "maker", True, 0.0005, 0.0005), - ('kraken', "ADA/USDT", "maker", False, 0.0005, 0.0005), - ('kraken', "ADA/USDT", "taker", True, 0.0005, 0.0005), - ('kraken', "ADA/USDT", "taker", False, 0.0005, 0.0005), - # FTX - ('ftx', "ADA/USDT", "maker", True, 0.0005, 0.0005), - ('ftx', "ADA/USDT", "maker", False, 0.0005, 0.0005), - ('ftx', "ADA/USDT", "taker", True, 0.0005, 0.0005), - ('ftx', "ADA/USDT", "taker", False, 0.0005, 0.0005), -]) -def test_get_interest_rate( - default_conf, - mocker, - exchange_name, - pair, - maker_or_taker, - is_short, - borrow_rate, - interest_rate -): - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - assert exchange.get_interest_rate( - pair, maker_or_taker, is_short) == (borrow_rate, interest_rate) - - -@pytest.mark.parametrize("exchange_name", [("binance"), ("ftx"), ("kraken")]) -@pytest.mark.parametrize("maker_or_taker", [("maker"), ("taker")]) -@pytest.mark.parametrize("is_short", [(True), (False)]) -def test_get_interest_rate_exceptions( - mocker, - default_conf, - exchange_name, - maker_or_taker, - is_short -): - - # api_mock = MagicMock() - # # TODO-lev: get_interest_rate currently not implemented on CCXT, so this may be renamed - # api_mock.get_interest_rate = MagicMock() - # type(api_mock).has = PropertyMock(return_value={'getInterestRate': True}) - - # ccxt_exceptionhandlers( - # mocker, - # default_conf, - # api_mock, - # exchange_name, - # "get_interest_rate", - # "get_interest_rate", - # pair="XRP/USDT", - # is_short=is_short, - # maker_or_taker="maker_or_taker" - # ) - return - - @pytest.mark.parametrize("collateral", [ (Collateral.CROSS), (Collateral.ISOLATED) From 785b71aec1ca6a5468380db2801dc0fea24117fd Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Thu, 9 Sep 2021 13:42:43 -0600 Subject: [PATCH 30/53] formatting --- freqtrade/exchange/kraken.py | 4 ++-- freqtrade/persistence/models.py | 1 + tests/exchange/test_exchange.py | 4 ---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 5207018ad..861063b3f 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -40,7 +40,7 @@ class Kraken(Exchange): return (parent_check and market.get('darkpool', False) is False) - @ retrier + @retrier def get_balances(self) -> dict: if self._config['dry_run']: return {} @@ -84,7 +84,7 @@ class Kraken(Exchange): (side == "buy" and stop_loss < float(order['price'])) )) - @ retrier(retries=0) + @retrier(retries=0) def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict, side: str) -> Dict: """ diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 630078ab3..b73611c1b 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -499,6 +499,7 @@ class LocalTrade(): lower_stop = new_loss < self.stop_loss # stop losses only walk up, never down!, + # TODO-lev # ? But adding more to a leveraged trade would create a lower liquidation price, # ? decreasing the minimum stoploss if (higher_stop and not self.is_short) or (lower_stop and self.is_short): diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 5d7901420..239704bdd 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2994,10 +2994,6 @@ def test_apply_leverage_to_stake_amount( assert exchange._apply_leverage_to_stake_amount(stake_amount, leverage) == min_stake_with_lev -def test_fill_leverage_brackets(): - return - - @pytest.mark.parametrize("collateral", [ (Collateral.CROSS), (Collateral.ISOLATED) From 2c7cf794f58b67fd290d32442e4b786d130fc79a Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Thu, 9 Sep 2021 14:24:07 -0600 Subject: [PATCH 31/53] Test for short exchange.stoploss exchange.stoploss_adjust --- freqtrade/exchange/binance.py | 5 ++- freqtrade/exchange/exchange.py | 1 + freqtrade/exchange/ftx.py | 9 +++--- tests/exchange/test_binance.py | 51 ++++++++++++++++++++---------- tests/exchange/test_ftx.py | 57 ++++++++++++++++++++++------------ tests/exchange/test_kraken.py | 39 +++++++++++++---------- 6 files changed, 105 insertions(+), 57 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 9a96e1f19..5680a7b47 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -55,7 +55,10 @@ class Binance(Exchange): :param side: "buy" or "sell" """ # Limit price threshold: As limit price should always be below stop-price - limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) + limit_price_pct = order_types.get( + 'stoploss_on_exchange_limit_ratio', + 0.99 if side == 'sell' else 1.01 + ) rate = stop_price * limit_price_pct ordertype = "stop_loss_limit" diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index dcc6e513a..b07ee95ce 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -624,6 +624,7 @@ class Exchange: def _apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float): """ + #TODO-lev: Find out how this works on Kraken and FTX # * Should be implemented by child classes if leverage affects the stake_amount Takes the minimum stake amount for a pair with no leverage and returns the minimum stake amount when leverage is considered diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 3e6ff01a3..870791cf5 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -57,7 +57,10 @@ class Ftx(Exchange): Limit orders are defined by having orderPrice set, otherwise a market order is used. """ - limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) + limit_price_pct = order_types.get( + 'stoploss_on_exchange_limit_ratio', + 0.99 if side == "sell" else 1.01 + ) limit_rate = stop_price * limit_price_pct ordertype = "stop" @@ -164,10 +167,6 @@ class Ftx(Exchange): return safe_value_fallback2(order, order, 'id_stop', 'id') return order['id'] - def _apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float): - # TODO-lev: implement - return stake_amount - def fill_leverage_brackets(self): """ FTX leverage is static across the account, and doesn't change from pair to pair, diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index f2bd68154..ad55ede9b 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -9,12 +9,22 @@ from tests.conftest import get_patched_exchange from tests.exchange.test_exchange import ccxt_exceptionhandlers -@pytest.mark.parametrize('limitratio,expected', [ - (None, 220 * 0.99), - (0.99, 220 * 0.99), - (0.98, 220 * 0.98), +@pytest.mark.parametrize('limitratio,exchangelimitratio,expected,side', [ + (None, 1.05, 220 * 0.99, "sell"), + (0.99, 1.05, 220 * 0.99, "sell"), + (0.98, 1.05, 220 * 0.98, "sell"), + (None, 0.95, 220 * 1.01, "buy"), + (1.01, 0.95, 220 * 1.01, "buy"), + (1.02, 0.95, 220 * 1.02, "buy"), ]) -def test_stoploss_order_binance(default_conf, mocker, limitratio, expected): +def test_stoploss_order_binance( + default_conf, + mocker, + limitratio, + exchangelimitratio, + expected, + side +): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) order_type = 'stop_loss_limit' @@ -32,20 +42,25 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') with pytest.raises(OperationalException): - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side="sell", - order_types={'stoploss_on_exchange_limit_ratio': 1.05}) + order = exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=190, + side=side, + order_types={'stoploss_on_exchange_limit_ratio': exchangelimitratio} + ) api_mock.create_order.reset_mock() order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio} order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, - order_types=order_types, side="sell") + order_types=order_types, side=side) assert 'id' in order assert 'info' in order assert order['id'] == order_id assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' assert api_mock.create_order.call_args_list[0][1]['type'] == order_type - assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell' + assert api_mock.create_order.call_args_list[0][1]['side'] == side assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 # Price should be 1% below stopprice assert api_mock.create_order.call_args_list[0][1]['price'] == expected @@ -55,17 +70,17 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected): with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("binance Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance", "stoploss", "create_order", retries=1, - pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") + pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) def test_stoploss_order_dry_run_binance(default_conf, mocker): @@ -94,18 +109,22 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker): assert order['amount'] == 1 -def test_stoploss_adjust_binance(mocker, default_conf): +@pytest.mark.parametrize('sl1,sl2,sl3,side', [ + (1501, 1499, 1501, "sell"), + (1499, 1501, 1499, "buy") +]) +def test_stoploss_adjust_binance(mocker, default_conf, sl1, sl2, sl3, side): exchange = get_patched_exchange(mocker, default_conf, id='binance') order = { 'type': 'stop_loss_limit', 'price': 1500, 'info': {'stopPrice': 1500}, } - assert exchange.stoploss_adjust(1501, order, side="sell") - assert not exchange.stoploss_adjust(1499, order, side="sell") + assert exchange.stoploss_adjust(sl1, order, side=side) + assert not exchange.stoploss_adjust(sl2, order, side=side) # Test with invalid order case order['type'] = 'stop_loss' - assert not exchange.stoploss_adjust(1501, order, side="sell") + assert not exchange.stoploss_adjust(sl3, order, side=side) @pytest.mark.parametrize('pair,nominal_value,max_lev', [ diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index 1ed528dd9..33eb0e3c4 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -16,7 +16,11 @@ STOPLOSS_ORDERTYPE = 'stop' # TODO-lev: All these stoploss tests with shorts -def test_stoploss_order_ftx(default_conf, mocker): +@pytest.mark.parametrize('order_price,exchangelimitratio,side', [ + (217.8, 1.05, "sell"), + (222.2, 0.95, "buy"), +]) +def test_stoploss_order_ftx(default_conf, mocker, order_price, exchangelimitratio, side): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) @@ -34,12 +38,12 @@ def test_stoploss_order_ftx(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') # stoploss_on_exchange_limit_ratio is irrelevant for ftx market orders - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side="sell", - order_types={'stoploss_on_exchange_limit_ratio': 1.05}) + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side=side, + order_types={'stoploss_on_exchange_limit_ratio': exchangelimitratio}) assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE - assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell' + assert api_mock.create_order.call_args_list[0][1]['side'] == side assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params'] assert 'stopPrice' in api_mock.create_order.call_args_list[0][1]['params'] @@ -49,51 +53,52 @@ def test_stoploss_order_ftx(default_conf, mocker): api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) assert 'id' in order assert 'info' in order assert order['id'] == order_id assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE - assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell' + assert api_mock.create_order.call_args_list[0][1]['side'] == side assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params'] assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 220 api_mock.create_order.reset_mock() order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, - order_types={'stoploss': 'limit'}, side="sell") + order_types={'stoploss': 'limit'}, side=side) assert 'id' in order assert 'info' in order assert order['id'] == order_id assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE - assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell' + assert api_mock.create_order.call_args_list[0][1]['side'] == side assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 assert 'orderPrice' in api_mock.create_order.call_args_list[0][1]['params'] - assert api_mock.create_order.call_args_list[0][1]['params']['orderPrice'] == 217.8 + assert api_mock.create_order.call_args_list[0][1]['params']['orderPrice'] == order_price assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 220 # test exception handling with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("ftx Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) ccxt_exceptionhandlers(mocker, default_conf, api_mock, "ftx", "stoploss", "create_order", retries=1, - pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") + pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) -def test_stoploss_order_dry_run_ftx(default_conf, mocker): +@pytest.mark.parametrize('side', [("sell"), ("buy")]) +def test_stoploss_order_dry_run_ftx(default_conf, mocker, side): api_mock = MagicMock() default_conf['dry_run'] = True mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) @@ -103,7 +108,7 @@ def test_stoploss_order_dry_run_ftx(default_conf, mocker): api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) assert 'id' in order assert 'info' in order @@ -114,20 +119,24 @@ def test_stoploss_order_dry_run_ftx(default_conf, mocker): assert order['amount'] == 1 -def test_stoploss_adjust_ftx(mocker, default_conf): +@pytest.mark.parametrize('sl1,sl2,sl3,side', [ + (1501, 1499, 1501, "sell"), + (1499, 1501, 1499, "buy") +]) +def test_stoploss_adjust_ftx(mocker, default_conf, sl1, sl2, sl3, side): exchange = get_patched_exchange(mocker, default_conf, id='ftx') order = { 'type': STOPLOSS_ORDERTYPE, 'price': 1500, } - assert exchange.stoploss_adjust(1501, order, side="sell") - assert not exchange.stoploss_adjust(1499, order, side="sell") + assert exchange.stoploss_adjust(sl1, order, side=side) + assert not exchange.stoploss_adjust(sl2, order, side=side) # Test with invalid order case ... order['type'] = 'stop_loss_limit' - assert not exchange.stoploss_adjust(1501, order, side="sell") + assert not exchange.stoploss_adjust(sl3, order, side=side) -def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order): +def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order, limit_buy_order): default_conf['dry_run'] = True order = MagicMock() order.myid = 123 @@ -160,6 +169,16 @@ def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order): assert resp['type'] == 'stop' assert resp['status_stop'] == 'triggered' + api_mock.fetch_order = MagicMock(return_value=limit_buy_order) + + resp = exchange.fetch_stoploss_order('X', 'TKN/BTC') + assert resp + assert api_mock.fetch_order.call_count == 1 + assert resp['id_stop'] == 'mocked_limit_buy' + assert resp['id'] == 'X' + assert resp['type'] == 'stop' + assert resp['status_stop'] == 'triggered' + with pytest.raises(InvalidOrderException): api_mock.fetch_orders = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx') diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index 8222f5ce8..01f27997c 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -164,11 +164,13 @@ def test_get_balances_prod(default_conf, mocker): ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken", "get_balances", "fetch_balance") -# TODO-lev: All these stoploss tests with shorts - @pytest.mark.parametrize('ordertype', ['market', 'limit']) -def test_stoploss_order_kraken(default_conf, mocker, ordertype): +@pytest.mark.parametrize('side,limitratio,adjustedprice', [ + ("buy", 0.99, 217.8), + ("sell", 1.01, 222.2), +]) +def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, limitratio, adjustedprice): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) @@ -185,9 +187,9 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, side="sell", + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, side=side, order_types={'stoploss': ordertype, - 'stoploss_on_exchange_limit_ratio': 0.99 + 'stoploss_on_exchange_limit_ratio': limitratio }) assert 'id' in order @@ -197,12 +199,12 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype): if ordertype == 'limit': assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_LIMIT_ORDERTYPE assert api_mock.create_order.call_args_list[0][1]['params'] == { - 'trading_agreement': 'agree', 'price2': 217.8} + 'trading_agreement': 'agree', 'price2': adjustedprice} else: assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE assert api_mock.create_order.call_args_list[0][1]['params'] == { 'trading_agreement': 'agree'} - assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell' + assert api_mock.create_order.call_args_list[0][1]['side'] == side assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 assert api_mock.create_order.call_args_list[0][1]['price'] == 220 @@ -210,20 +212,21 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype): with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("kraken Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken", "stoploss", "create_order", retries=1, - pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") + pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) -def test_stoploss_order_dry_run_kraken(default_conf, mocker): +@pytest.mark.parametrize('side', ['buy', 'sell']) +def test_stoploss_order_dry_run_kraken(default_conf, mocker, side): api_mock = MagicMock() default_conf['dry_run'] = True mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) @@ -233,7 +236,7 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker): api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) assert 'id' in order assert 'info' in order @@ -244,17 +247,21 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker): assert order['amount'] == 1 -def test_stoploss_adjust_kraken(mocker, default_conf): +@pytest.mark.parametrize('sl1,sl2,sl3,side', [ + (1501, 1499, 1501, "sell"), + (1499, 1501, 1499, "buy") +]) +def test_stoploss_adjust_kraken(mocker, default_conf, sl1, sl2, sl3, side): exchange = get_patched_exchange(mocker, default_conf, id='kraken') order = { 'type': STOPLOSS_ORDERTYPE, 'price': 1500, } - assert exchange.stoploss_adjust(1501, order, side="sell") - assert not exchange.stoploss_adjust(1499, order, side="sell") + assert exchange.stoploss_adjust(sl1, order, side=side) + assert not exchange.stoploss_adjust(sl2, order, side=side) # Test with invalid order case ... order['type'] = 'stop_loss_limit' - assert not exchange.stoploss_adjust(1501, order, side="sell") + assert not exchange.stoploss_adjust(sl3, order, side=side) @pytest.mark.parametrize('pair,nominal_value,max_lev', [ From 063861ada35d7f91711921d6769877635efe3263 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Thu, 9 Sep 2021 15:38:32 -0600 Subject: [PATCH 32/53] Added todos for short stoploss --- tests/test_freqtradebot.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index a841744b7..c4be69cd1 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1252,6 +1252,7 @@ def test_create_stoploss_order_insufficient_funds(mocker, default_conf, caplog, @pytest.mark.usefixtures("init_persistence") def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, limit_buy_order, limit_sell_order) -> None: + # TODO-lev: test for short # When trailing stoploss is set stoploss = MagicMock(return_value={'id': 13434334}) patch_RPCManager(mocker) @@ -1362,6 +1363,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, caplog, limit_buy_order, limit_sell_order) -> None: + # TODO-lev: test for short # When trailing stoploss is set stoploss = MagicMock(return_value={'id': 13434334}) patch_exchange(mocker) @@ -1439,6 +1441,7 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c def test_handle_stoploss_on_exchange_custom_stop(mocker, default_conf, fee, limit_buy_order, limit_sell_order) -> None: # When trailing stoploss is set + # TODO-lev: test for short stoploss = MagicMock(return_value={'id': 13434334}) patch_RPCManager(mocker) mocker.patch.multiple( @@ -1548,7 +1551,7 @@ def test_handle_stoploss_on_exchange_custom_stop(mocker, default_conf, fee, def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, limit_buy_order, limit_sell_order) -> None: - + # TODO-lev: test for short # When trailing stoploss is set stoploss = MagicMock(return_value={'id': 13434334}) patch_RPCManager(mocker) From 77fc21a16b0b56587ba5bf5b755d8ed9e11b5f95 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Thu, 9 Sep 2021 23:58:10 -0600 Subject: [PATCH 33/53] Patched test_fill_leverage_brackets_ftx so that exchange._leverage_brackets doesn't retain the values from binance --- tests/exchange/test_ftx.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index 33eb0e3c4..a98e46b27 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -227,5 +227,8 @@ def test_get_max_leverage_ftx(default_conf, mocker, pair, nominal_value, max_lev def test_fill_leverage_brackets_ftx(default_conf, mocker): # FTX only has one account wide leverage, so there's no leverage brackets exchange = get_patched_exchange(mocker, default_conf, id="ftx") + # TODO: This is a patch, develop a real solution + # TODO: _leverage_brackets retains it's value from the binance tests, but shouldn't + exchange._leverage_brackets = {} exchange.fill_leverage_brackets() assert exchange._leverage_brackets == {} From 77aa372909899ec0f823fd322acf4e71d933baa2 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 10 Sep 2021 02:09:27 -0600 Subject: [PATCH 34/53] Fixed test_ftx patch --- freqtrade/exchange/exchange.py | 3 +-- tests/exchange/test_ftx.py | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index b07ee95ce..b9da0cf7c 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -75,8 +75,6 @@ class Exchange: } _ft_has: Dict = {} - _leverage_brackets: Dict = {} - _supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [ # TradingMode.SPOT always supported and not required in this list ] @@ -90,6 +88,7 @@ class Exchange: self._api: ccxt.Exchange = None self._api_async: ccxt_async.Exchange = None self._markets: Dict = {} + self._leverage_brackets: Dict = {} self._config.update(config) diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index a98e46b27..33eb0e3c4 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -227,8 +227,5 @@ def test_get_max_leverage_ftx(default_conf, mocker, pair, nominal_value, max_lev def test_fill_leverage_brackets_ftx(default_conf, mocker): # FTX only has one account wide leverage, so there's no leverage brackets exchange = get_patched_exchange(mocker, default_conf, id="ftx") - # TODO: This is a patch, develop a real solution - # TODO: _leverage_brackets retains it's value from the binance tests, but shouldn't - exchange._leverage_brackets = {} exchange.fill_leverage_brackets() assert exchange._leverage_brackets == {} From b0e05b92d3aa16404798edc2c657d904c2242c04 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 10 Sep 2021 13:39:42 -0600 Subject: [PATCH 35/53] Added minor changes from lev-exchange review --- freqtrade/exchange/binance.py | 15 ++++++++------- freqtrade/exchange/exchange.py | 7 +++++-- freqtrade/exchange/ftx.py | 10 +++++----- freqtrade/exchange/kraken.py | 15 +++++++++------ tests/exchange/test_binance.py | 17 ++++++++--------- tests/exchange/test_kraken.py | 25 ++++++++++++++++--------- 6 files changed, 51 insertions(+), 38 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 5680a7b47..f5a222d2d 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -41,8 +41,8 @@ class Binance(Exchange): """ return order['type'] == 'stop_loss_limit' and ( - side == "sell" and stop_loss > float(order['info']['stopPrice']) or - side == "buy" and stop_loss < float(order['info']['stopPrice']) + (side == "sell" and stop_loss > float(order['info']['stopPrice'])) or + (side == "buy" and stop_loss < float(order['info']['stopPrice'])) ) @retrier(retries=0) @@ -55,11 +55,12 @@ class Binance(Exchange): :param side: "buy" or "sell" """ # Limit price threshold: As limit price should always be below stop-price - limit_price_pct = order_types.get( - 'stoploss_on_exchange_limit_ratio', - 0.99 if side == 'sell' else 1.01 - ) - rate = stop_price * limit_price_pct + limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) + if side == "sell": + # TODO: Name limit_rate in other exchange subclasses + rate = stop_price * limit_price_pct + else: + rate = stop_price * (2 - limit_price_pct) ordertype = "stop_loss_limit" diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index b9da0cf7c..03ab281c9 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1597,8 +1597,7 @@ class Exchange: :param pair: The base/quote currency pair being traded :nominal_value: The total value of the trade in quote currency (collateral + debt) """ - raise OperationalException( - f"{self.name.capitalize()}.get_max_leverage has not been implemented.") + return 1.0 @retrier def set_leverage(self, leverage: float, pair: Optional[str]): @@ -1606,6 +1605,10 @@ class Exchange: Set's the leverage before making a trade, in order to not have the same leverage on every trade """ + if not self.exchange_has("setLeverage"): + # Some exchanges only support one collateral type + return + try: self._api.set_leverage(symbol=pair, leverage=leverage) except ccxt.DDoSProtection as e: diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 870791cf5..095d8eaa1 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -57,11 +57,11 @@ class Ftx(Exchange): Limit orders are defined by having orderPrice set, otherwise a market order is used. """ - limit_price_pct = order_types.get( - 'stoploss_on_exchange_limit_ratio', - 0.99 if side == "sell" else 1.01 - ) - limit_rate = stop_price * limit_price_pct + limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) + if side == "sell": + limit_rate = stop_price * limit_price_pct + else: + limit_rate = stop_price * (2 - limit_price_pct) ordertype = "stop" diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 861063b3f..b72a92070 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -55,8 +55,8 @@ class Kraken(Exchange): orders = self._api.fetch_open_orders() order_list = [(x["symbol"].split("/")[0 if x["side"] == "sell" else 1], - x["remaining"] if x["side"] == "sell" else x["remaining"] * x["price"], - # Don't remove the below comment, this can be important for debugging + x["remaining"] if x["side"] == "sell" else x["remaining"] * x["price"], + # Don't remove the below comment, this can be important for debugging # x["side"], x["amount"], ) for x in orders] for bal in balances: @@ -96,7 +96,10 @@ class Kraken(Exchange): if order_types.get('stoploss', 'market') == 'limit': ordertype = "stop-loss-limit" limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) - limit_rate = stop_price * limit_price_pct + if side == "sell": + limit_rate = stop_price * limit_price_pct + else: + limit_rate = stop_price * (2 - limit_price_pct) params['price2'] = self.price_to_precision(pair, limit_rate) else: ordertype = "stop-loss" @@ -144,13 +147,13 @@ class Kraken(Exchange): for pair, market in self.markets.items(): info = market['info'] - leverage_buy = info['leverage_buy'] if 'leverage_buy' in info else [] - leverage_sell = info['leverage_sell'] if 'leverage_sell' in info else [] + leverage_buy = info.get('leverage_buy', []) + leverage_sell = info.get('leverage_sell', []) if len(leverage_buy) > 0 or len(leverage_sell) > 0: if leverage_buy != leverage_sell: logger.warning( f"The buy({leverage_buy}) and sell({leverage_sell}) leverage are not equal" - "{pair}. Please let freqtrade know because this has never happened before" + "for {pair}. Please notify freqtrade because this has never happened before" ) if max(leverage_buy) < max(leverage_sell): leverages[pair] = leverage_buy diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index ad55ede9b..96287da44 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -9,19 +9,18 @@ from tests.conftest import get_patched_exchange from tests.exchange.test_exchange import ccxt_exceptionhandlers -@pytest.mark.parametrize('limitratio,exchangelimitratio,expected,side', [ - (None, 1.05, 220 * 0.99, "sell"), - (0.99, 1.05, 220 * 0.99, "sell"), - (0.98, 1.05, 220 * 0.98, "sell"), - (None, 0.95, 220 * 1.01, "buy"), - (1.01, 0.95, 220 * 1.01, "buy"), - (1.02, 0.95, 220 * 1.02, "buy"), +@pytest.mark.parametrize('limitratio,expected,side', [ + (None, 220 * 0.99, "sell"), + (0.99, 220 * 0.99, "sell"), + (0.98, 220 * 0.98, "sell"), + (None, 220 * 1.01, "buy"), + (0.99, 220 * 1.01, "buy"), + (0.98, 220 * 1.02, "buy"), ]) def test_stoploss_order_binance( default_conf, mocker, limitratio, - exchangelimitratio, expected, side ): @@ -47,7 +46,7 @@ def test_stoploss_order_binance( amount=1, stop_price=190, side=side, - order_types={'stoploss_on_exchange_limit_ratio': exchangelimitratio} + order_types={'stoploss_on_exchange_limit_ratio': 1.05} ) api_mock.create_order.reset_mock() diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index 01f27997c..66e7f4f0b 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -166,11 +166,11 @@ def test_get_balances_prod(default_conf, mocker): @pytest.mark.parametrize('ordertype', ['market', 'limit']) -@pytest.mark.parametrize('side,limitratio,adjustedprice', [ - ("buy", 0.99, 217.8), - ("sell", 1.01, 222.2), +@pytest.mark.parametrize('side,adjustedprice', [ + ("sell", 217.8), + ("buy", 222.2), ]) -def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, limitratio, adjustedprice): +def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedprice): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) @@ -187,10 +187,15 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, limitratio exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, side=side, - order_types={'stoploss': ordertype, - 'stoploss_on_exchange_limit_ratio': limitratio - }) + order = exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + side=side, + order_types={ + 'stoploss': ordertype, + 'stoploss_on_exchange_limit_ratio': 0.99 + }) assert 'id' in order assert 'info' in order @@ -199,7 +204,9 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, limitratio if ordertype == 'limit': assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_LIMIT_ORDERTYPE assert api_mock.create_order.call_args_list[0][1]['params'] == { - 'trading_agreement': 'agree', 'price2': adjustedprice} + 'trading_agreement': 'agree', + 'price2': adjustedprice + } else: assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE assert api_mock.create_order.call_args_list[0][1]['params'] == { From 5b84298e0305c84d1ff2dee56e150a05c030f320 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 12 Sep 2021 00:03:02 -0600 Subject: [PATCH 36/53] kraken._apply_leverage_to_stake_amount --- freqtrade/exchange/kraken.py | 9 +++++++++ tests/exchange/test_exchange.py | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index b72a92070..14ebedef0 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -163,6 +163,15 @@ class Kraken(Exchange): leverages[pair] = leverage_buy self._leverage_brackets = leverages + def _apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float): + """ + Takes the minimum stake amount for a pair with no leverage and returns the minimum + stake amount when 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 + """ + return stake_amount / leverage + def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float: """ Returns the maximum leverage that a pair can be traded at diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 239704bdd..330793822 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2974,9 +2974,9 @@ def test_calculate_backoff(retrycount, max_retries, expected): ('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), + ('kraken', 9.0, 3.0, 3.0), + ('kraken', 20.0, 5.0, 4.0), + ('kraken', 100.0, 100.0, 1.0), # FTX ('ftx', 9.0, 3.0, 9.0), ('ftx', 20.0, 5.0, 20.0), From 1344c9f7fc3ae103baf207e1d67f5fe3ac11d57b Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 12 Sep 2021 01:30:26 -0600 Subject: [PATCH 37/53] _apply_leverage_to_min_stake_amount --- freqtrade/exchange/binance.py | 3 --- freqtrade/exchange/exchange.py | 2 +- freqtrade/exchange/kraken.py | 9 --------- tests/exchange/test_exchange.py | 15 ++++----------- 4 files changed, 5 insertions(+), 24 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index f5a222d2d..1fcdc0ab4 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): """ diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 03ab281c9..dfee82d7b 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -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 diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 14ebedef0..b72a92070 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -163,15 +163,6 @@ class Kraken(Exchange): leverages[pair] = leverage_buy self._leverage_brackets = leverages - def _apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float): - """ - Takes the minimum stake amount for a pair with no leverage and returns the minimum - stake amount when 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 - """ - return stake_amount / leverage - def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float: """ Returns the maximum leverage that a pair can be traded at diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 330793822..5c0323915 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', ['binance', 'kraken', 'ftx']) @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, 3.0), - ('kraken', 20.0, 5.0, 4.0), - ('kraken', 100.0, 100.0, 1.0), - # FTX - ('ftx', 9.0, 3.0, 9.0), - ('ftx', 20.0, 5.0, 20.0), - ('ftx', 100.0, 100.0, 100.0) + (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, From 09418938fe920ae9f9dc335d54705f2347f102f9 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 12 Sep 2021 01:51:09 -0600 Subject: [PATCH 38/53] Updated kraken fill leverage brackets and set_leverage --- freqtrade/exchange/kraken.py | 15 ++++++++------- tests/exchange/test_kraken.py | 15 ++++++++++++--- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index b72a92070..97125f7ec 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: @@ -173,7 +174,7 @@ class Kraken(Exchange): def set_leverage(self, pair, leverage): """ - 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 + self._params['leverage'] = leverage diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index 66e7f4f0b..74a06c96c 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -292,7 +292,16 @@ 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] } From 0c1e5afc91384c88e4a3bf6d7aba9894780ef6e3 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 12 Sep 2021 02:02:10 -0600 Subject: [PATCH 39/53] Added set leverage to create_order --- freqtrade/exchange/exchange.py | 5 +++-- freqtrade/exchange/kraken.py | 2 +- tests/exchange/test_exchange.py | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index dfee82d7b..a5778432a 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -771,12 +771,13 @@ 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 + self._set_leverage(pair, leverage) params = self._params.copy() if time_in_force != 'gtc' and ordertype != 'market': param = self._ft_has.get('time_in_force_parameter', '') @@ -1600,7 +1601,7 @@ class Exchange: return 1.0 @retrier - def set_leverage(self, leverage: float, pair: Optional[str]): + def _set_leverage(self, leverage: float, pair: Optional[str]): """ 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 97125f7ec..6981204a4 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -172,7 +172,7 @@ class Kraken(Exchange): """ return float(max(self._leverage_brackets[pair])) - def set_leverage(self, pair, leverage): + def _set_leverage(self, pair, leverage): """ Kraken set's the leverage as an option in the order object, so we need to add it to params diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 5c0323915..939e45d63 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2970,7 +2970,7 @@ def test_calculate_backoff(retrycount, max_retries, expected): @pytest.mark.parametrize('exchange', ['binance', 'kraken', 'ftx']) -@pytest.mark.parametrize('exchange,stake_amount,leverage,min_stake_with_lev', [ +@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) @@ -2992,7 +2992,7 @@ def test_apply_leverage_to_stake_amount( (Collateral.ISOLATED) ]) @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, collateral): api_mock = MagicMock() api_mock.set_leverage = MagicMock() @@ -3003,7 +3003,7 @@ 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 From bc102d57c91ff225c9ca3cd1745d7b8460efcce0 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 12 Sep 2021 02:06:18 -0600 Subject: [PATCH 40/53] Updated set leverage to check trading mode --- freqtrade/exchange/binance.py | 21 +++++++++++++++++++++ freqtrade/exchange/exchange.py | 9 +++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 1fcdc0ab4..178fa49da 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -151,3 +151,24 @@ 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]): + """ + Set's the leverage before making a trade, in order to not + have the same leverage on every trade + """ + if not self.exchange_has("setLeverage"): + # Some exchanges only support one collateral type + return + + try: + if self.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 a5778432a..bef8f5e57 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 @@ -777,7 +777,8 @@ class Exchange: dry_order = self.create_dry_run_order(pair, ordertype, side, amount, rate) return dry_order - self._set_leverage(pair, leverage) + if self.trading_mode != TradingMode.SPOT: + self._set_leverage(pair, leverage) params = self._params.copy() if time_in_force != 'gtc' and ordertype != 'market': param = self._ft_has.get('time_in_force_parameter', '') From ad44048e29adc83518bdf2f5a8b9aaa2bc721897 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 12 Sep 2021 02:42:13 -0600 Subject: [PATCH 41/53] customized set_leverage for different exchanges --- freqtrade/exchange/binance.py | 13 ++++++++----- freqtrade/exchange/exchange.py | 9 +++++++-- freqtrade/exchange/kraken.py | 12 ++++++++++-- tests/exchange/test_exchange.py | 18 +++++++++--------- tests/exchange/test_kraken.py | 6 ++++++ 5 files changed, 40 insertions(+), 18 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 178fa49da..4315585b6 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -153,17 +153,20 @@ class Binance(Exchange): return max_lev @retrier - def _set_leverage(self, leverage: float, pair: Optional[str]): + def _set_leverage( + self, + leverage: float, + pair: Optional[str], + trading_mode: Optional[TradingMode] + ): """ Set's the leverage before making a trade, in order to not have the same leverage on every trade """ - if not self.exchange_has("setLeverage"): - # Some exchanges only support one collateral type - return + trading_mode = trading_mode or self.trading_mode try: - if self.trading_mode == TradingMode.FUTURES: + if trading_mode == TradingMode.FUTURES: self._api.set_leverage(symbol=pair, leverage=leverage) except ccxt.DDoSProtection as e: raise DDosProtection(e) from e diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index bef8f5e57..dd3304921 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -778,7 +778,7 @@ class Exchange: return dry_order if self.trading_mode != TradingMode.SPOT: - self._set_leverage(pair, leverage) + 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', '') @@ -1602,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], + trading_mode: Optional[TradingMode] + ): """ 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 6981204a4..46f1ab934 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -172,9 +172,17 @@ 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], + trading_mode: Optional[TradingMode] + ): """ Kraken set's the leverage as an option in the order object, so we need to add it to params """ - self._params['leverage'] = leverage + if leverage > 1.0: + self._params['leverage'] = leverage + else: + del self._params['leverage'] diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 939e45d63..3231d9811 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2987,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() @@ -3006,7 +3006,8 @@ def test__set_leverage(mocker, default_conf, exchange_name, collateral): "_set_leverage", "set_leverage", pair="XRP/USDT", - leverage=5.0 + leverage=5.0, + trading_mode=trading_mode ) @@ -3014,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() @@ -3025,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_kraken.py b/tests/exchange/test_kraken.py index 74a06c96c..1a712fd3f 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -305,3 +305,9 @@ def test_fill_leverage_brackets_kraken(default_conf, mocker): 'XLTCUSDT': [1], 'LTC/ETH': [1] } + + +def test_kraken__set_leverage(default_conf, mocker): + exchange = get_patched_exchange(mocker, default_conf, id="kraken") + exchange._set_leverage(3) + assert exchange.params['leverage'] == 3 From e070bdd161a3b433c1afe0f0552f02632a03f47b Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 12 Sep 2021 03:09:51 -0600 Subject: [PATCH 42/53] set leverage more thorough tests --- freqtrade/exchange/binance.py | 4 ++-- freqtrade/exchange/exchange.py | 4 ++-- freqtrade/exchange/kraken.py | 7 ++++--- tests/exchange/test_binance.py | 23 +++++++++++++++++++++++ tests/exchange/test_ftx.py | 26 +++++++++++++++++++++++++- tests/exchange/test_kraken.py | 10 ++++++++-- 6 files changed, 64 insertions(+), 10 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 4315585b6..fcd027d52 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -156,8 +156,8 @@ class Binance(Exchange): def _set_leverage( self, leverage: float, - pair: Optional[str], - trading_mode: Optional[TradingMode] + pair: Optional[str] = None, + trading_mode: Optional[TradingMode] = None ): """ Set's the leverage before making a trade, in order to not diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index dd3304921..2fb63d201 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1605,8 +1605,8 @@ class Exchange: def _set_leverage( self, leverage: float, - pair: Optional[str], - trading_mode: Optional[TradingMode] + pair: Optional[str] = None, + trading_mode: Optional[TradingMode] = None ): """ Set's the leverage before making a trade, in order to not diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 46f1ab934..661000d4d 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -175,8 +175,8 @@ class Kraken(Exchange): def _set_leverage( self, leverage: float, - pair: Optional[str], - trading_mode: Optional[TradingMode] + pair: Optional[str] = None, + trading_mode: Optional[TradingMode] = None ): """ Kraken set's the leverage as an option in the order object, so we need to @@ -185,4 +185,5 @@ class Kraken(Exchange): if leverage > 1.0: self._params['leverage'] = leverage else: - del self._params['leverage'] + 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_ftx.py b/tests/exchange/test_ftx.py index 33eb0e3c4..88c4c069b 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 @@ -229,3 +230,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 1a712fd3f..374b054a6 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -307,7 +307,13 @@ def test_fill_leverage_brackets_kraken(default_conf, mocker): } -def test_kraken__set_leverage(default_conf, mocker): +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 + 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 From 17a5cc96feb7058f7831cd0cbc5663654adfca13 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 13 Sep 2021 00:09:55 -0600 Subject: [PATCH 43/53] Added set_margin_mode to create_order --- freqtrade/exchange/binance.py | 4 ++++ freqtrade/exchange/exchange.py | 10 +++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index fcd027d52..d079d4ad6 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -152,6 +152,10 @@ class Binance(Exchange): max_lev = 1/margin_req return max_lev + def lev_prep(self, pair: str, leverage: float): + self.set_margin_mode(pair, self.collateral) + self._set_leverage(leverage, pair, self.trading_mode) + @retrier def _set_leverage( self, diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 2fb63d201..07a817006 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -150,7 +150,7 @@ class Exchange: if config.get('trading_mode') else TradingMode.SPOT ) - collateral: Optional[Collateral] = ( + self.collateral: Optional[Collateral] = ( Collateral(config.get('collateral')) if config.get('collateral') else None @@ -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(self.trading_mode, collateral) + self.validate_trading_mode_and_collateral(self.trading_mode, self.collateral) # Converts the interval provided in minutes in config to seconds self.markets_refresh_interval: int = exchange_config.get( "markets_refresh_interval", 60) * 60 @@ -770,6 +770,10 @@ class Exchange: # Order handling + def lev_prep(self, pair: str, leverage: float): + self.set_margin_mode(pair, self.collateral) + self._set_leverage(leverage, pair) + def create_order(self, pair: str, ordertype: str, side: str, amount: float, rate: float, time_in_force: str = 'gtc', leverage=1.0) -> Dict: @@ -778,7 +782,7 @@ class Exchange: return dry_order if self.trading_mode != TradingMode.SPOT: - self._set_leverage(leverage, pair) + self.lev_prep(pair, leverage) params = self._params.copy() if time_in_force != 'gtc' and ordertype != 'market': param = self._ft_has.get('time_in_force_parameter', '') From cbaf477bec00071877eb3946b8b2c89ec15c7ac4 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Wed, 15 Sep 2021 21:55:19 -0600 Subject: [PATCH 44/53] changed kraken set lev implementation --- freqtrade/exchange/exchange.py | 13 +++++++++---- freqtrade/exchange/kraken.py | 10 ++++++---- tests/exchange/test_exchange.py | 8 +++++++- tests/exchange/test_kraken.py | 12 ------------ 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 0c3b29e1a..554873100 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -775,6 +775,13 @@ class Exchange: self.set_margin_mode(pair, self.collateral) self._set_leverage(leverage, pair) + def _get_params(self, time_in_force: str, ordertype: str, leverage: float) -> Dict: + params = self._params.copy() + if time_in_force != 'gtc' and ordertype != 'market': + param = self._ft_has.get('time_in_force_parameter', '') + params.update({param: time_in_force}) + return params + def create_order(self, pair: str, ordertype: str, side: str, amount: float, rate: float, time_in_force: str = 'gtc', leverage=1.0) -> Dict: @@ -784,10 +791,8 @@ class Exchange: if self.trading_mode != TradingMode.SPOT: self.lev_prep(pair, leverage) - params = self._params.copy() - if time_in_force != 'gtc' and ordertype != 'market': - param = self._ft_has.get('time_in_force_parameter', '') - params.update({param: time_in_force}) + + params = self._get_params(time_in_force, ordertype, leverage) try: # Set the precision for amount and price(rate) as accepted by the exchange diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 661000d4d..60af42c69 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -182,8 +182,10 @@ class Kraken(Exchange): Kraken set's the leverage as an option in the order object, so we need to add it to params """ + return + + def _get_params(self, time_in_force: str, ordertype: str, leverage: float) -> Dict: + params = super()._get_params(time_in_force, ordertype, leverage) if leverage > 1.0: - self._params['leverage'] = leverage - else: - if 'leverage' in self._params: - del self._params['leverage'] + params['leverage'] = leverage + return params diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 535726b4b..8c7f908b2 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1110,7 +1110,13 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order = exchange.create_order( - pair='ETH/BTC', ordertype=ordertype, side=side, amount=1, rate=200) + pair='ETH/BTC', + ordertype=ordertype, + side=side, + amount=1, + rate=200, + leverage=3.0 + ) assert 'id' in order assert 'info' in order diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index 374b054a6..74a06c96c 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -305,15 +305,3 @@ def test_fill_leverage_brackets_kraken(default_conf, mocker): '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 From 57c7926515b9973a3a6f767963f5e0c52e2b44c2 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Thu, 16 Sep 2021 23:05:13 -0600 Subject: [PATCH 45/53] leverage updates on exchange classes --- freqtrade/data/leverage_brackets.json | 1214 +++++++++++++++++++++++++ freqtrade/exchange/binance.py | 74 +- freqtrade/exchange/exchange.py | 44 +- freqtrade/exchange/ftx.py | 16 +- freqtrade/exchange/kraken.py | 17 +- freqtrade/freqtradebot.py | 3 +- tests/exchange/test_binance.py | 52 +- tests/exchange/test_exchange.py | 44 +- tests/exchange/test_ftx.py | 86 +- tests/exchange/test_kraken.py | 34 +- tests/test_freqtradebot.py | 9 +- 11 files changed, 1467 insertions(+), 126 deletions(-) create mode 100644 freqtrade/data/leverage_brackets.json diff --git a/freqtrade/data/leverage_brackets.json b/freqtrade/data/leverage_brackets.json new file mode 100644 index 000000000..4450b015e --- /dev/null +++ b/freqtrade/data/leverage_brackets.json @@ -0,0 +1,1214 @@ +{ + "1000SHIB/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "1INCH/USDT": [ + [0.0, "0.012"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "AAVE/USDT": [ + [0.0, "0.01"], + [50000.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.1665"], + [10000000.0, "0.25"] + ], + "ADA/BUSD": [ + [0.0, "0.025"], + [100000.0, "0.05"], + [500000.0, "0.1"], + [1000000.0, "0.15"], + [2000000.0, "0.25"], + [5000000.0, "0.5"] + ], + "ADA/USDT": [ + [0.0, "0.0065"], + [10000.0, "0.01"], + [50000.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.15"], + [10000000.0, "0.25"] + ], + "AKRO/USDT": [ + [0.0, "0.012"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "ALGO/USDT": [ + [0.0, "0.01"], + [50000.0, "0.025"], + [150000.0, "0.05"], + [250000.0, "0.1"], + [500000.0, "0.125"], + [1000000.0, "0.25"], + [2000000.0, "0.5"] + ], + "ALICE/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "ALPHA/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "ANKR/USDT": [ + [0.0, "0.012"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "ATA/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "ATOM/USDT": [ + [0.0, "0.01"], + [50000.0, "0.025"], + [150000.0, "0.05"], + [250000.0, "0.1"], + [500000.0, "0.125"], + [1000000.0, "0.25"], + [2000000.0, "0.5"] + ], + "AUDIO/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "AVAX/USDT": [ + [0.0, "0.01"], + [50000.0, "0.025"], + [150000.0, "0.05"], + [250000.0, "0.1"], + [500000.0, "0.125"], + [750000.0, "0.25"], + [1000000.0, "0.5"] + ], + "AXS/USDT": [ + [0.0, "0.01"], + [50000.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.1665"], + [10000000.0, "0.25"], + [15000000.0, "0.5"] + ], + "BAKE/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "BAL/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "BAND/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "BAT/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "BCH/USDT": [ + [0.0, "0.0065"], + [10000.0, "0.01"], + [50000.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.15"], + [10000000.0, "0.25"] + ], + "BEL/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "BLZ/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "BNB/BUSD": [ + [0.0, "0.025"], + [100000.0, "0.05"], + [500000.0, "0.1"], + [1000000.0, "0.15"], + [2000000.0, "0.25"], + [5000000.0, "0.5"] + ], + "BNB/USDT": [ + [0.0, "0.0065"], + [10000.0, "0.01"], + [50000.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.15"], + [10000000.0, "0.25"] + ], + "BTC/BUSD": [ + [0.0, "0.004"], + [25000.0, "0.005"], + [100000.0, "0.01"], + [500000.0, "0.025"], + [1000000.0, "0.05"], + [2000000.0, "0.1"], + [5000000.0, "0.125"], + [10000000.0, "0.15"], + [20000000.0, "0.25"], + [30000000.0, "0.5"] + ], + "BTC/USDT": [ + [0.0, "0.004"], + [50000.0, "0.005"], + [250000.0, "0.01"], + [1000000.0, "0.025"], + [5000000.0, "0.05"], + [20000000.0, "0.1"], + [50000000.0, "0.125"], + [100000000.0, "0.15"], + [200000000.0, "0.25"], + [300000000.0, "0.5"] + ], + "BTCBUSD_210129": [ + [0.0, "0.004"], + [5000.0, "0.005"], + [25000.0, "0.01"], + [100000.0, "0.025"], + [500000.0, "0.05"], + [2000000.0, "0.1"], + [5000000.0, "0.125"], + [10000000.0, "0.15"], + [20000000.0, "0.25"] + ], + "BTCBUSD_210226": [ + [0.0, "0.004"], + [5000.0, "0.005"], + [25000.0, "0.01"], + [100000.0, "0.025"], + [500000.0, "0.05"], + [2000000.0, "0.1"], + [5000000.0, "0.125"], + [10000000.0, "0.15"], + [20000000.0, "0.25"] + ], + "BTCDOM/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "BTCSTUSDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "BTCUSDT_210326": [ + [0.0, "0.0065"], + [10000.0, "0.01"], + [50000.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.15"], + [10000000.0, "0.25"] + ], + "BTCUSDT_210625": [ + [0.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.15"], + [10000000.0, "0.25"] + ], + "BTCUSDT_210924": [ + [0.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.15"], + [10000000.0, "0.25"], + [20000000.0, "0.5"] + ], + "BTS/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "BTT/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "BZRX/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "C98/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "CELR/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "CHR/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "CHZ/USDT": [ + [0.0, "0.012"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "COMP/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "COTI/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "CRV/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "CTK/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "CVC/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "DASH/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "DEFI/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "DENT/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "DGB/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "DODO/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "DOGE/BUSD": [ + [0.0, "0.025"], + [100000.0, "0.05"], + [500000.0, "0.1"], + [1000000.0, "0.15"], + [2000000.0, "0.25"], + [5000000.0, "0.5"] + ], + "DOGE/USDT": [ + [0.0, "0.01"], + [50000.0, "0.025"], + [150000.0, "0.05"], + [250000.0, "0.1"], + [500000.0, "0.125"], + [750000.0, "0.25"], + [1000000.0, "0.5"] + ], + "DOT/USDT": [ + [0.0, "0.0065"], + [10000.0, "0.01"], + [50000.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.15"], + [10000000.0, "0.25"] + ], + "DOTECOUSDT": [ + [0.0, "0.012"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "DYDX/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "EGLD/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "ENJ/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "EOS/USDT": [ + [0.0, "0.0065"], + [10000.0, "0.01"], + [50000.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.15"], + [10000000.0, "0.25"] + ], + "ETC/USDT": [ + [0.0, "0.0065"], + [10000.0, "0.01"], + [50000.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.15"], + [10000000.0, "0.25"] + ], + "ETH/BUSD": [ + [0.0, "0.004"], + [25000.0, "0.005"], + [100000.0, "0.01"], + [500000.0, "0.025"], + [1000000.0, "0.05"], + [2000000.0, "0.1"], + [5000000.0, "0.125"], + [10000000.0, "0.15"], + [20000000.0, "0.25"], + [30000000.0, "0.5"] + ], + "ETH/USDT": [ + [0.0, "0.005"], + [10000.0, "0.0065"], + [100000.0, "0.01"], + [500000.0, "0.02"], + [1000000.0, "0.05"], + [2000000.0, "0.1"], + [5000000.0, "0.125"], + [10000000.0, "0.15"], + [20000000.0, "0.25"] + ], + "ETHUSDT_210326": [ + [0.0, "0.0065"], + [10000.0, "0.01"], + [50000.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.15"], + [10000000.0, "0.25"] + ], + "ETHUSDT_210625": [ + [0.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.15"], + [10000000.0, "0.25"] + ], + "ETHUSDT_210924": [ + [0.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.15"], + [10000000.0, "0.25"], + [20000000.0, "0.5"] + ], + "FIL/USDT": [ + [0.0, "0.01"], + [50000.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.1665"], + [10000000.0, "0.25"] + ], + "FLM/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "FTM/USDT": [ + [0.0, "0.01"], + [50000.0, "0.025"], + [150000.0, "0.05"], + [250000.0, "0.1"], + [500000.0, "0.125"], + [750000.0, "0.25"], + [1000000.0, "0.5"] + ], + "FTT/BUSD": [ + [0.0, "0.025"], + [100000.0, "0.05"], + [500000.0, "0.1"], + [1000000.0, "0.15"], + [2000000.0, "0.25"], + [5000000.0, "0.5"] + ], + "GRT/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "GTC/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "HBAR/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "HNT/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "HOT/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "ICP/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "ICX/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "IOST/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "IOTA/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "IOTX/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "KAVA/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "KEEP/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "KNC/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "KSM/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "LENDUSDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "LINA/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "LINK/USDT": [ + [0.0, "0.0065"], + [10000.0, "0.01"], + [50000.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.15"], + [10000000.0, "0.25"] + ], + "LIT/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "LRC/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "LTC/USDT": [ + [0.0, "0.0065"], + [10000.0, "0.01"], + [50000.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.15"], + [10000000.0, "0.25"] + ], + "LUNA/USDT": [ + [0.0, "0.01"], + [50000.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.1665"], + [10000000.0, "0.25"], + [15000000.0, "0.5"] + ], + "MANA/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "MASK/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "MATIC/USDT": [ + [0.0, "0.01"], + [50000.0, "0.025"], + [150000.0, "0.05"], + [250000.0, "0.1"], + [500000.0, "0.125"], + [750000.0, "0.25"], + [1000000.0, "0.5"] + ], + "MKR/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "MTL/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "NEAR/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "NEO/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "NKN/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "OCEAN/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "OGN/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "OMG/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "ONE/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "ONT/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "QTUM/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "RAY/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "REEF/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "REN/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "RLC/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "RSR/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "RUNE/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "RVN/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "SAND/USDT": [ + [0.0, "0.012"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "SC/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "SFP/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "SKL/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "SNX/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "SOL/BUSD": [ + [0.0, "0.025"], + [100000.0, "0.05"], + [500000.0, "0.1"], + [1000000.0, "0.15"], + [2000000.0, "0.25"], + [5000000.0, "0.5"] + ], + "SOL/USDT": [ + [0.0, "0.01"], + [50000.0, "0.025"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.25"], + [10000000.0, "0.5"] + ], + "SRM/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "STMX/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "STORJ/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "SUSHI/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "SXP/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "THETA/USDT": [ + [0.0, "0.01"], + [50000.0, "0.025"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.1665"], + [10000000.0, "0.25"] + ], + "TLM/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "TOMO/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "TRB/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "TRX/USDT": [ + [0.0, "0.0065"], + [10000.0, "0.01"], + [50000.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.15"], + [10000000.0, "0.25"] + ], + "UNFI/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "UNI/USDT": [ + [0.0, "0.01"], + [50000.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.1665"], + [10000000.0, "0.25"] + ], + "VET/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "WAVES/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "XEM/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "XLM/USDT": [ + [0.0, "0.0065"], + [10000.0, "0.01"], + [50000.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.15"], + [10000000.0, "0.25"] + ], + "XMR/USDT": [ + [0.0, "0.0065"], + [10000.0, "0.01"], + [50000.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.15"], + [10000000.0, "0.25"] + ], + "XRP/BUSD": [ + [0.0, "0.025"], + [100000.0, "0.05"], + [500000.0, "0.1"], + [1000000.0, "0.15"], + [2000000.0, "0.25"], + [5000000.0, "0.5"] + ], + "XRP/USDT": [ + [0.0, "0.0065"], + [10000.0, "0.01"], + [50000.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.15"], + [10000000.0, "0.25"] + ], + "XTZ/USDT": [ + [0.0, "0.0065"], + [10000.0, "0.01"], + [50000.0, "0.02"], + [250000.0, "0.05"], + [1000000.0, "0.1"], + [2000000.0, "0.125"], + [5000000.0, "0.15"], + [10000000.0, "0.25"] + ], + "YFI/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "YFII/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "ZEC/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "ZEN/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "ZIL/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ], + "ZRX/USDT": [ + [0.0, "0.01"], + [5000.0, "0.025"], + [25000.0, "0.05"], + [100000.0, "0.1"], + [250000.0, "0.125"], + [1000000.0, "0.5"] + ] +} diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index f4998d9a7..17e865d64 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -1,5 +1,7 @@ """ Binance exchange subclass """ +import json import logging +from pathlib import Path from typing import Dict, List, Optional, Tuple import arrow @@ -47,8 +49,8 @@ class Binance(Exchange): ) @retrier(retries=0) - def stoploss(self, pair: str, amount: float, - stop_price: float, order_types: Dict, side: str) -> Dict: + def stoploss(self, pair: str, amount: float, stop_price: float, + order_types: Dict, side: str, leverage: float) -> Dict: """ creates a stoploss limit order. this stoploss-limit is binance-specific. @@ -76,7 +78,7 @@ class Binance(Exchange): if self._config['dry_run']: dry_order = self.create_dry_run_order( - pair, ordertype, side, amount, stop_price) + pair, ordertype, side, amount, stop_price, leverage) return dry_order try: @@ -87,8 +89,15 @@ class Binance(Exchange): rate = self.price_to_precision(pair, rate) - order = self._api.create_order(symbol=pair, type=ordertype, side=side, - amount=amount, price=rate, params=params) + order = self._api.create_order( + symbol=pair, + type=ordertype, + side=side, + amount=amount, + price=rate, + params=params, + leverage=leverage + ) logger.info('stoploss limit order added for %s. ' 'stop price: %s. limit: %s', pair, stop_price, rate) self._log_exchange_response('create_stoploss_order', order) @@ -119,26 +128,33 @@ class Binance(Exchange): Assigns property _leverage_brackets to a dictionary of information about the leverage allowed on each pair """ - try: - leverage_brackets = self._api.load_leverage_brackets() - for pair, brackets in leverage_brackets.items(): - self._leverage_brackets[pair] = [ - [ - min_amount, - float(margin_req) - ] for [ - min_amount, - margin_req - ] in brackets - ] + if self.trading_mode == TradingMode.FUTURES: + try: + if self._config['dry_run']: + leverage_brackets_path = Path('data') / 'leverage_brackets.json' + with open(leverage_brackets_path) as json_file: + leverage_brackets = json.load(json_file) + else: + leverage_brackets = self._api.load_leverage_brackets() - except ccxt.DDoSProtection as e: - raise DDosProtection(e) from e - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError(f'Could not fetch leverage amounts due to' - f'{e.__class__.__name__}. Message: {e}') from e - except ccxt.BaseError as e: - raise OperationalException(e) from e + for pair, brackets in leverage_brackets.items(): + self._leverage_brackets[pair] = [ + [ + min_amount, + float(margin_req) + ] for [ + min_amount, + margin_req + ] in brackets + ] + + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError(f'Could not fetch leverage amounts due to' + f'{e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float: """ @@ -153,10 +169,6 @@ class Binance(Exchange): max_lev = 1/margin_req return max_lev - def lev_prep(self, pair: str, leverage: float): - self.set_margin_mode(pair, self.collateral) - self._set_leverage(leverage, pair, self.trading_mode) - @retrier def _set_leverage( self, @@ -170,9 +182,11 @@ class Binance(Exchange): """ trading_mode = trading_mode or self.trading_mode + if self._config['dry_run'] or trading_mode != TradingMode.FUTURES: + return + try: - if trading_mode == TradingMode.FUTURES: - self._api.set_leverage(symbol=pair, leverage=leverage) + 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: diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 554873100..8bbc88235 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -258,6 +258,13 @@ class Exchange: """exchange ccxt precisionMode""" return self._api.precisionMode + @property + def running_live_mode(self) -> bool: + return ( + self._config['runmode'].value not in ('backtest', 'hyperopt') and + not self._config['dry_run'] + ) + def _log_exchange_response(self, endpoint, response) -> None: """ Log exchange responses """ if self.log_responses: @@ -617,15 +624,13 @@ class Exchange: # The value returned should satisfy both limits: for amount (base currency) and # for cost (quote, stake currency), so max() is used here. # See also #2575 at github. - return self._apply_leverage_to_stake_amount( + return self._divide_stake_amount_by_leverage( max(min_stake_amounts) * amount_reserve_percent, leverage or 1.0 ) - def _apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float): + def _divide_stake_amount_by_leverage(self, stake_amount: float, leverage: float): """ - #TODO-lev: Find out how this works on Kraken and FTX - # * Should be implemented by child classes if leverage affects the stake_amount Takes the minimum stake amount for a pair with no leverage and returns the minimum stake amount when leverage is considered :param stake_amount: The stake amount for a pair before leverage is considered @@ -636,7 +641,7 @@ class Exchange: # Dry-run methods def create_dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, - rate: float, params: Dict = {}) -> Dict[str, Any]: + rate: float, leverage: float, params: Dict = {}) -> Dict[str, Any]: order_id = f'dry_run_{side}_{datetime.now().timestamp()}' _amount = self.amount_to_precision(pair, amount) dry_order: Dict[str, Any] = { @@ -653,7 +658,8 @@ class Exchange: 'timestamp': arrow.utcnow().int_timestamp * 1000, 'status': "closed" if ordertype == "market" else "open", 'fee': None, - 'info': {} + 'info': {}, + 'leverage': leverage } if dry_order["type"] in ["stop_loss_limit", "stop-loss-limit"]: dry_order["info"] = {"stopPrice": dry_order["price"]} @@ -663,7 +669,7 @@ class Exchange: average = self.get_dry_market_fill_price(pair, side, amount, rate) dry_order.update({ 'average': average, - 'cost': dry_order['amount'] * average, + 'cost': (dry_order['amount'] * average) / leverage }) dry_order = self.add_dry_order_fee(pair, dry_order) @@ -771,7 +777,7 @@ class Exchange: # Order handling - def lev_prep(self, pair: str, leverage: float): + def _lev_prep(self, pair: str, leverage: float): self.set_margin_mode(pair, self.collateral) self._set_leverage(leverage, pair) @@ -783,14 +789,14 @@ class Exchange: return params def create_order(self, pair: str, ordertype: str, side: str, amount: float, - rate: float, time_in_force: str = 'gtc', leverage=1.0) -> Dict: - + rate: float, leverage: float = 1.0, time_in_force: str = 'gtc') -> Dict: + # TODO-lev: remove default for leverage 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, leverage) return dry_order if self.trading_mode != TradingMode.SPOT: - self.lev_prep(pair, leverage) + self._lev_prep(pair, leverage) params = self._get_params(time_in_force, ordertype, leverage) @@ -831,8 +837,8 @@ class Exchange: """ raise OperationalException(f"stoploss is not implemented for {self.name}.") - def stoploss(self, pair: str, amount: float, - stop_price: float, order_types: Dict, side: str) -> Dict: + def stoploss(self, pair: str, amount: float, stop_price: float, + order_types: Dict, side: str, leverage: float) -> Dict: """ creates a stoploss order. The precise ordertype is determined by the order_types dict or exchange default. @@ -1595,15 +1601,13 @@ class Exchange: self._async_get_trade_history(pair=pair, since=since, until=until, from_id=from_id)) - @retrier def fill_leverage_brackets(self): """ #TODO-lev: Should maybe be renamed, leverage_brackets might not be accurate for kraken Assigns property _leverage_brackets to a dictionary of information about the leverage allowed on each pair """ - raise OperationalException( - f"{self.name.capitalize()}.fill_leverage_brackets has not been implemented.") + return def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float: """ @@ -1624,7 +1628,9 @@ class Exchange: Set's the leverage before making a trade, in order to not have the same leverage on every trade """ - if not self.exchange_has("setLeverage"): + # TODO-lev: Make a documentation page that says you can't run 2 bots + # TODO-lev: on the same account with leverage + if self._config['dry_run'] or not self.exchange_has("setLeverage"): # Some exchanges only support one collateral type return @@ -1644,7 +1650,7 @@ class Exchange: Set's the margin mode on the exchange to cross or isolated for a specific pair :param symbol: base/quote currency pair (e.g. "ADA/USDT") ''' - if not self.exchange_has("setMarginMode"): + if self._config['dry_run'] or not self.exchange_has("setMarginMode"): # Some exchanges only support one collateral type return diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 095d8eaa1..eaf9a0477 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -49,8 +49,8 @@ class Ftx(Exchange): ) @retrier(retries=0) - def stoploss(self, pair: str, amount: float, - stop_price: float, order_types: Dict, side: str) -> Dict: + def stoploss(self, pair: str, amount: float, stop_price: float, + order_types: Dict, side: str, leverage: float) -> Dict: """ Creates a stoploss order. depending on order_types.stoploss configuration, uses 'market' or limit order. @@ -69,7 +69,7 @@ class Ftx(Exchange): if self._config['dry_run']: dry_order = self.create_dry_run_order( - pair, ordertype, side, amount, stop_price) + pair, ordertype, side, amount, stop_price, leverage) return dry_order try: @@ -81,8 +81,14 @@ class Ftx(Exchange): params['stopPrice'] = stop_price amount = self.amount_to_precision(pair, amount) - order = self._api.create_order(symbol=pair, type=ordertype, side=side, - amount=amount, params=params) + order = self._api.create_order( + symbol=pair, + type=ordertype, + side=side, + amount=amount, + leverage=leverage, + params=params + ) self._log_exchange_response('create_stoploss_order', order) logger.info('stoploss order added for %s. ' 'stop price: %s.', pair, stop_price) diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 60af42c69..d6a816c9e 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -85,8 +85,8 @@ class Kraken(Exchange): )) @retrier(retries=0) - def stoploss(self, pair: str, amount: float, - stop_price: float, order_types: Dict, side: str) -> Dict: + def stoploss(self, pair: str, amount: float, stop_price: float, + order_types: Dict, side: str, leverage: float) -> Dict: """ Creates a stoploss market order. Stoploss market orders is the only stoploss type supported by kraken. @@ -108,14 +108,21 @@ class Kraken(Exchange): if self._config['dry_run']: dry_order = self.create_dry_run_order( - pair, ordertype, side, amount, stop_price) + pair, ordertype, side, amount, stop_price, leverage) return dry_order try: amount = self.amount_to_precision(pair, amount) - order = self._api.create_order(symbol=pair, type=ordertype, side=side, - amount=amount, price=stop_price, params=params) + order = self._api.create_order( + symbol=pair, + type=ordertype, + side=side, + amount=amount, + price=stop_price, + leverage=leverage, + params=params + ) self._log_exchange_response('create_stoploss_order', order) logger.info('stoploss order added for %s. ' 'stop price: %s.', pair, stop_price) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ca1e9f9b0..2738ec634 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -737,7 +737,8 @@ class FreqtradeBot(LoggingMixin): amount=trade.amount, stop_price=stop_price, order_types=self.strategy.order_types, - side=trade.exit_side + side=trade.exit_side, + leverage=trade.leverage ) order_obj = Order.parse_from_ccxt_object(stoploss_order, trade.pair, 'stoploss') diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index 5a1087534..f0642fda9 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -48,13 +48,20 @@ def test_stoploss_order_binance( amount=1, stop_price=190, side=side, - order_types={'stoploss_on_exchange_limit_ratio': 1.05} + order_types={'stoploss_on_exchange_limit_ratio': 1.05}, + leverage=1.0 ) api_mock.create_order.reset_mock() order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio} - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, - order_types=order_types, side=side) + order = exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types=order_types, + side=side, + leverage=1.0 + ) assert 'id' in order assert 'info' in order @@ -71,17 +78,31 @@ def test_stoploss_order_binance( with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) + exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side=side, + leverage=1.0) with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("binance Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) + exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side=side, + leverage=1.0 + ) ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance", "stoploss", "create_order", retries=1, - pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) + pair='ETH/BTC', amount=1, stop_price=220, order_types={}, + side=side, leverage=1.0) def test_stoploss_order_dry_run_binance(default_conf, mocker): @@ -94,12 +115,25 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') with pytest.raises(OperationalException): - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side="sell", - order_types={'stoploss_on_exchange_limit_ratio': 1.05}) + order = exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=190, + side="sell", + order_types={'stoploss_on_exchange_limit_ratio': 1.05}, + leverage=1.0 + ) api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") + order = exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side="sell", + leverage=1.0 + ) assert 'id' in order assert 'info' in order diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 8c7f908b2..d641b0a63 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -403,7 +403,6 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: # With Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss, 3.0) assert isclose(result, expected_result/3) - # TODO-lev: Min stake for base, kraken and ftx # min amount is set markets["ETH/BTC"]["limits"] = { @@ -420,7 +419,6 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: # With Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 5.0) assert isclose(result, expected_result/5) - # TODO-lev: Min stake for base, kraken and ftx # min amount and cost are set (cost is minimal) markets["ETH/BTC"]["limits"] = { @@ -437,7 +435,6 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: # With Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 10) assert isclose(result, expected_result/10) - # TODO-lev: Min stake for base, kraken and ftx # min amount and cost are set (amount is minial) markets["ETH/BTC"]["limits"] = { @@ -454,7 +451,6 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: # With Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 7.0) assert isclose(result, expected_result/7.0) - # TODO-lev: Min stake for base, kraken and ftx result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4) expected_result = max(8, 2 * 2) * 1.5 @@ -462,7 +458,6 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: # With Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4, 8.0) assert isclose(result, expected_result/8.0) - # TODO-lev: Min stake for base, kraken and ftx # Really big stoploss result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1) @@ -471,7 +466,6 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: # With Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0) assert isclose(result, expected_result/12) - # TODO-lev: Min stake for base, kraken and ftx def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: @@ -493,7 +487,6 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: assert round(result, 8) == round(expected_result, 8) result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss, 3.0) assert round(result, 8) == round(expected_result/3, 8) - # TODO-lev: Min stake for base, kraken and ftx def test_set_sandbox(default_conf, mocker): @@ -1004,7 +997,13 @@ def test_create_dry_run_order(default_conf, mocker, side, exchange_name): exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) order = exchange.create_dry_run_order( - pair='ETH/BTC', ordertype='limit', side=side, amount=1, rate=200) + pair='ETH/BTC', + ordertype='limit', + side=side, + amount=1, + rate=200, + leverage=1.0 + ) assert 'id' in order assert f'dry_run_{side}_' in order["id"] assert order["side"] == side @@ -1027,7 +1026,13 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, startprice, ) order = exchange.create_dry_run_order( - pair='LTC/USDT', ordertype='limit', side=side, amount=1, rate=startprice) + pair='LTC/USDT', + ordertype='limit', + side=side, + amount=1, + rate=startprice, + leverage=1.0 + ) assert order_book_l2_usd.call_count == 1 assert 'id' in order assert f'dry_run_{side}_' in order["id"] @@ -1073,7 +1078,13 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amou ) order = exchange.create_dry_run_order( - pair='LTC/USDT', ordertype='market', side=side, amount=amount, rate=rate) + pair='LTC/USDT', + ordertype='market', + side=side, + amount=amount, + rate=rate, + leverage=1.0 + ) assert 'id' in order assert f'dry_run_{side}_' in order["id"] assert order["side"] == side @@ -2664,7 +2675,14 @@ def test_get_fee(default_conf, mocker, exchange_name): def test_stoploss_order_unsupported_exchange(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, id='bittrex') with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") + exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side="sell", + leverage=1.0 + ) with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): exchange.stoploss_adjust(1, {}, side="sell") @@ -3024,7 +3042,7 @@ def test_calculate_backoff(retrycount, max_retries, expected): (20.0, 5.0, 4.0), (100.0, 100.0, 1.0) ]) -def test_apply_leverage_to_stake_amount( +def test_divide_stake_amount_by_leverage( exchange, stake_amount, leverage, @@ -3033,7 +3051,7 @@ def test_apply_leverage_to_stake_amount( default_conf ): exchange = get_patched_exchange(mocker, default_conf, id=exchange) - assert exchange._apply_leverage_to_stake_amount(stake_amount, leverage) == min_stake_with_lev + assert exchange._divide_stake_amount_by_leverage(stake_amount, leverage) == min_stake_with_lev @pytest.mark.parametrize("exchange_name,trading_mode", [ diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index 88c4c069b..ca6b24d64 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -1,10 +1,9 @@ from random import randint -from unittest.mock import MagicMock, PropertyMock +from unittest.mock import MagicMock 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 @@ -14,8 +13,6 @@ from .test_exchange import ccxt_exceptionhandlers STOPLOSS_ORDERTYPE = 'stop' -# TODO-lev: All these stoploss tests with shorts - @pytest.mark.parametrize('order_price,exchangelimitratio,side', [ (217.8, 1.05, "sell"), @@ -39,8 +36,14 @@ def test_stoploss_order_ftx(default_conf, mocker, order_price, exchangelimitrati exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') # stoploss_on_exchange_limit_ratio is irrelevant for ftx market orders - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side=side, - order_types={'stoploss_on_exchange_limit_ratio': exchangelimitratio}) + order = exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=190, + side=side, + order_types={'stoploss_on_exchange_limit_ratio': exchangelimitratio}, + leverage=1.0 + ) assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE @@ -54,7 +57,14 @@ def test_stoploss_order_ftx(default_conf, mocker, order_price, exchangelimitrati api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) + order = exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side=side, + leverage=1.0 + ) assert 'id' in order assert 'info' in order @@ -67,8 +77,13 @@ def test_stoploss_order_ftx(default_conf, mocker, order_price, exchangelimitrati assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 220 api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, - order_types={'stoploss': 'limit'}, side=side) + order = exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={'stoploss': 'limit'}, side=side, + leverage=1.0 + ) assert 'id' in order assert 'info' in order @@ -85,17 +100,32 @@ def test_stoploss_order_ftx(default_conf, mocker, order_price, exchangelimitrati with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) + exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side=side, + leverage=1.0 + ) with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("ftx Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) + exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side=side, + leverage=1.0 + ) ccxt_exceptionhandlers(mocker, default_conf, api_mock, "ftx", "stoploss", "create_order", retries=1, - pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) + pair='ETH/BTC', amount=1, stop_price=220, order_types={}, + side=side, leverage=1.0) @pytest.mark.parametrize('side', [("sell"), ("buy")]) @@ -109,7 +139,14 @@ def test_stoploss_order_dry_run_ftx(default_conf, mocker, side): api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) + order = exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side=side, + leverage=1.0 + ) assert 'id' in order assert 'info' in order @@ -230,26 +267,3 @@ 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 74a06c96c..a8cd8d8ef 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -195,7 +195,9 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedpr order_types={ 'stoploss': ordertype, 'stoploss_on_exchange_limit_ratio': 0.99 - }) + }, + leverage=1.0 + ) assert 'id' in order assert 'info' in order @@ -219,17 +221,32 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedpr with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) + exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side=side, + leverage=1.0 + ) with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("kraken Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) + exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side=side, + leverage=1.0 + ) ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken", "stoploss", "create_order", retries=1, - pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) + pair='ETH/BTC', amount=1, stop_price=220, order_types={}, + side=side, leverage=1.0) @pytest.mark.parametrize('side', ['buy', 'sell']) @@ -243,7 +260,14 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker, side): api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) + order = exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side=side, + leverage=1.0 + ) assert 'id' in order assert 'info' in order diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index f87841fe8..28ca0ee49 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1349,7 +1349,8 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, pair='ETH/BTC', order_types=freqtrade.strategy.order_types, stop_price=0.00002346 * 0.95, - side="sell" + side="sell", + leverage=1.0 ) # price fell below stoploss, so dry-run sells trade. @@ -1537,7 +1538,8 @@ def test_handle_stoploss_on_exchange_custom_stop(mocker, default_conf, fee, pair='ETH/BTC', order_types=freqtrade.strategy.order_types, stop_price=0.00002346 * 0.96, - side="sell" + side="sell", + leverage=1.0 ) # price fell below stoploss, so dry-run sells trade. @@ -1661,7 +1663,8 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, pair='NEO/BTC', order_types=freqtrade.strategy.order_types, stop_price=0.00002346 * 0.99, - side="sell" + side="sell", + leverage=1.0 ) From dced167ea2d93a7c0d46960b0ace6f4625809500 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Thu, 16 Sep 2021 23:23:36 -0600 Subject: [PATCH 46/53] fixed some stuff in the leverage brackets binance test --- freqtrade/exchange/binance.py | 2 +- tests/exchange/test_binance.py | 117 +++++++++++++++++++-------------- 2 files changed, 70 insertions(+), 49 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 17e865d64..769073052 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -131,7 +131,7 @@ class Binance(Exchange): if self.trading_mode == TradingMode.FUTURES: try: if self._config['dry_run']: - leverage_brackets_path = Path('data') / 'leverage_brackets.json' + leverage_brackets_path = Path('freqtrade/data') / 'leverage_brackets.json' with open(leverage_brackets_path) as json_file: leverage_brackets = json.load(json_file) else: diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index f0642fda9..03b1d5044 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -2,6 +2,9 @@ from datetime import datetime, timezone from random import randint from unittest.mock import MagicMock, PropertyMock +import json +from pathlib import Path + import ccxt import pytest @@ -203,58 +206,76 @@ def test_get_max_leverage_binance(default_conf, mocker, pair, nominal_value, max def test_fill_leverage_brackets_binance(default_conf, mocker): api_mock = MagicMock() - api_mock.load_leverage_brackets = MagicMock(return_value={ - 'ADA/BUSD': [[0.0, 0.025], - [100000.0, 0.05], - [500000.0, 0.1], - [1000000.0, 0.15], - [2000000.0, 0.25], - [5000000.0, 0.5]], - 'BTC/USDT': [[0.0, 0.004], - [50000.0, 0.005], - [250000.0, 0.01], - [1000000.0, 0.025], - [5000000.0, 0.05], - [20000000.0, 0.1], - [50000000.0, 0.125], - [100000000.0, 0.15], - [200000000.0, 0.25], - [300000000.0, 0.5]], - "ZEC/USDT": [[0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5]], + # api_mock.load_leverage_brackets = MagicMock(return_value={ + # 'ADA/BUSD': [[0.0, 0.025], + # [100000.0, 0.05], + # [500000.0, 0.1], + # [1000000.0, 0.15], + # [2000000.0, 0.25], + # [5000000.0, 0.5]], + # 'BTC/USDT': [[0.0, 0.004], + # [50000.0, 0.005], + # [250000.0, 0.01], + # [1000000.0, 0.025], + # [5000000.0, 0.05], + # [20000000.0, 0.1], + # [50000000.0, 0.125], + # [100000000.0, 0.15], + # [200000000.0, 0.25], + # [300000000.0, 0.5]], + # "ZEC/USDT": [[0.0, 0.01], + # [5000.0, 0.025], + # [25000.0, 0.05], + # [100000.0, 0.1], + # [250000.0, 0.125], + # [1000000.0, 0.5]], - }) + # }) exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") + exchange.trading_mode = TradingMode.FUTURES exchange.fill_leverage_brackets() - assert exchange._leverage_brackets == { - 'ADA/BUSD': [[0.0, 0.025], - [100000.0, 0.05], - [500000.0, 0.1], - [1000000.0, 0.15], - [2000000.0, 0.25], - [5000000.0, 0.5]], - 'BTC/USDT': [[0.0, 0.004], - [50000.0, 0.005], - [250000.0, 0.01], - [1000000.0, 0.025], - [5000000.0, 0.05], - [20000000.0, 0.1], - [50000000.0, 0.125], - [100000000.0, 0.15], - [200000000.0, 0.25], - [300000000.0, 0.5]], - "ZEC/USDT": [[0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5]], - } + leverage_brackets_path = Path('freqtrade/data') / 'leverage_brackets.json' + with open(leverage_brackets_path) as json_file: + leverage_brackets = json.load(json_file) + + for pair, brackets in leverage_brackets.items(): + leverage_brackets[pair] = [ + [ + min_amount, + float(margin_req) + ] for [ + min_amount, + margin_req + ] in brackets + ] + + assert exchange._leverage_brackets == leverage_brackets + + # assert exchange._leverage_brackets == { + # 'ADA/BUSD': [[0.0, 0.025], + # [100000.0, 0.05], + # [500000.0, 0.1], + # [1000000.0, 0.15], + # [2000000.0, 0.25], + # [5000000.0, 0.5]], + # 'BTC/USDT': [[0.0, 0.004], + # [50000.0, 0.005], + # [250000.0, 0.01], + # [1000000.0, 0.025], + # [5000000.0, 0.05], + # [20000000.0, 0.1], + # [50000000.0, 0.125], + # [100000000.0, 0.15], + # [200000000.0, 0.25], + # [300000000.0, 0.5]], + # "ZEC/USDT": [[0.0, 0.01], + # [5000.0, 0.025], + # [25000.0, 0.05], + # [100000.0, 0.1], + # [250000.0, 0.125], + # [1000000.0, 0.5]], + # } api_mock = MagicMock() api_mock.load_leverage_brackets = MagicMock() From 798a0c9827a72e7b8abd6ecef6fe0a6531c78a60 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 17 Sep 2021 00:10:53 -0600 Subject: [PATCH 47/53] Tried to add call count to test_create_order --- tests/exchange/test_exchange.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index d641b0a63..8448819aa 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1094,10 +1094,7 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amou assert round(order["average"], 4) == round(endprice, 4) -@pytest.mark.parametrize("side", [ - ("buy"), - ("sell") -]) +@pytest.mark.parametrize("side", ["buy", "sell"]) @pytest.mark.parametrize("ordertype,rate,marketprice", [ ("market", None, None), ("market", 200, True), @@ -1126,7 +1123,7 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, side=side, amount=1, rate=200, - leverage=3.0 + leverage=1.0 ) assert 'id' in order @@ -1138,6 +1135,21 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, assert api_mock.create_order.call_args[0][3] == 1 assert api_mock.create_order.call_args[0][4] is rate + assert api_mock._set_leverage.call_count == 0 if side == "buy" else 1 + assert api_mock.set_margin_mode.call_count == 0 if side == "buy" else 1 + + order = exchange.create_order( + pair='ETH/BTC', + ordertype=ordertype, + side=side, + amount=1, + rate=200, + leverage=3.0 + ) + + assert api_mock._set_leverage.call_count == 1 + assert api_mock.set_margin_mode.call_count == 1 + def test_buy_dry_run(default_conf, mocker): default_conf['dry_run'] = True From 32e52cd4606ce3a687a5e3640ddd4fcc64c46aaf Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 17 Sep 2021 00:41:00 -0600 Subject: [PATCH 48/53] Added leverage brackets dry run test --- freqtrade/exchange/binance.py | 2 +- tests/exchange/test_binance.py | 135 ++-- tests/exchange/test_exchange.py | 5 +- tests/leverage_brackets.py | 1215 +++++++++++++++++++++++++++++++ 4 files changed, 1283 insertions(+), 74 deletions(-) create mode 100644 tests/leverage_brackets.py diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 769073052..572fa2141 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -33,7 +33,7 @@ class Binance(Exchange): # TradingMode.SPOT always supported and not required in this list # (TradingMode.MARGIN, Collateral.CROSS), # TODO-lev: Uncomment once supported # (TradingMode.FUTURES, Collateral.CROSS), # TODO-lev: Uncomment once supported - # (TradingMode.FUTURES, Collateral.ISOLATED) # TODO-lev: Uncomment once supported + (TradingMode.FUTURES, Collateral.ISOLATED) ] def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index 03b1d5044..4999e94af 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -2,16 +2,14 @@ from datetime import datetime, timezone from random import randint from unittest.mock import MagicMock, PropertyMock -import json -from pathlib import Path - import ccxt import pytest -from freqtrade.enums import TradingMode +from freqtrade.enums import Collateral, TradingMode from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException from tests.conftest import get_mock_coro, get_patched_exchange, log_has_re from tests.exchange.test_exchange import ccxt_exceptionhandlers +from tests.leverage_brackets import leverage_brackets @pytest.mark.parametrize('limitratio,expected,side', [ @@ -206,76 +204,61 @@ def test_get_max_leverage_binance(default_conf, mocker, pair, nominal_value, max def test_fill_leverage_brackets_binance(default_conf, mocker): api_mock = MagicMock() - # api_mock.load_leverage_brackets = MagicMock(return_value={ - # 'ADA/BUSD': [[0.0, 0.025], - # [100000.0, 0.05], - # [500000.0, 0.1], - # [1000000.0, 0.15], - # [2000000.0, 0.25], - # [5000000.0, 0.5]], - # 'BTC/USDT': [[0.0, 0.004], - # [50000.0, 0.005], - # [250000.0, 0.01], - # [1000000.0, 0.025], - # [5000000.0, 0.05], - # [20000000.0, 0.1], - # [50000000.0, 0.125], - # [100000000.0, 0.15], - # [200000000.0, 0.25], - # [300000000.0, 0.5]], - # "ZEC/USDT": [[0.0, 0.01], - # [5000.0, 0.025], - # [25000.0, 0.05], - # [100000.0, 0.1], - # [250000.0, 0.125], - # [1000000.0, 0.5]], + api_mock.load_leverage_brackets = MagicMock(return_value={ + 'ADA/BUSD': [[0.0, 0.025], + [100000.0, 0.05], + [500000.0, 0.1], + [1000000.0, 0.15], + [2000000.0, 0.25], + [5000000.0, 0.5]], + 'BTC/USDT': [[0.0, 0.004], + [50000.0, 0.005], + [250000.0, 0.01], + [1000000.0, 0.025], + [5000000.0, 0.05], + [20000000.0, 0.1], + [50000000.0, 0.125], + [100000000.0, 0.15], + [200000000.0, 0.25], + [300000000.0, 0.5]], + "ZEC/USDT": [[0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5]], - # }) + }) + default_conf['dry_run'] = False + default_conf['trading_mode'] = TradingMode.FUTURES + default_conf['collateral'] = Collateral.ISOLATED exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") - exchange.trading_mode = TradingMode.FUTURES exchange.fill_leverage_brackets() - leverage_brackets_path = Path('freqtrade/data') / 'leverage_brackets.json' - with open(leverage_brackets_path) as json_file: - leverage_brackets = json.load(json_file) - - for pair, brackets in leverage_brackets.items(): - leverage_brackets[pair] = [ - [ - min_amount, - float(margin_req) - ] for [ - min_amount, - margin_req - ] in brackets - ] - - assert exchange._leverage_brackets == leverage_brackets - - # assert exchange._leverage_brackets == { - # 'ADA/BUSD': [[0.0, 0.025], - # [100000.0, 0.05], - # [500000.0, 0.1], - # [1000000.0, 0.15], - # [2000000.0, 0.25], - # [5000000.0, 0.5]], - # 'BTC/USDT': [[0.0, 0.004], - # [50000.0, 0.005], - # [250000.0, 0.01], - # [1000000.0, 0.025], - # [5000000.0, 0.05], - # [20000000.0, 0.1], - # [50000000.0, 0.125], - # [100000000.0, 0.15], - # [200000000.0, 0.25], - # [300000000.0, 0.5]], - # "ZEC/USDT": [[0.0, 0.01], - # [5000.0, 0.025], - # [25000.0, 0.05], - # [100000.0, 0.1], - # [250000.0, 0.125], - # [1000000.0, 0.5]], - # } + assert exchange._leverage_brackets == { + 'ADA/BUSD': [[0.0, 0.025], + [100000.0, 0.05], + [500000.0, 0.1], + [1000000.0, 0.15], + [2000000.0, 0.25], + [5000000.0, 0.5]], + 'BTC/USDT': [[0.0, 0.004], + [50000.0, 0.005], + [250000.0, 0.01], + [1000000.0, 0.025], + [5000000.0, 0.05], + [20000000.0, 0.1], + [50000000.0, 0.125], + [100000000.0, 0.15], + [200000000.0, 0.25], + [300000000.0, 0.5]], + "ZEC/USDT": [[0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5]], + } api_mock = MagicMock() api_mock.load_leverage_brackets = MagicMock() @@ -291,12 +274,22 @@ def test_fill_leverage_brackets_binance(default_conf, mocker): ) +def test_fill_leverage_brackets_binance_dryrun(default_conf, mocker): + api_mock = MagicMock() + default_conf['trading_mode'] = TradingMode.FUTURES + default_conf['collateral'] = Collateral.ISOLATED + exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") + exchange.fill_leverage_brackets() + + assert exchange._leverage_brackets == 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}) - + default_conf['dry_run'] = False exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange._set_leverage(3.0, trading_mode=TradingMode.MARGIN) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 8448819aa..ce09e31e7 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -3076,6 +3076,7 @@ def test__set_leverage(mocker, default_conf, exchange_name, trading_mode): api_mock = MagicMock() api_mock.set_leverage = MagicMock() type(api_mock).has = PropertyMock(return_value={'setLeverage': True}) + default_conf['dry_run'] = False ccxt_exceptionhandlers( mocker, @@ -3099,6 +3100,7 @@ def test_set_margin_mode(mocker, default_conf, collateral): api_mock = MagicMock() api_mock.set_margin_mode = MagicMock() type(api_mock).has = PropertyMock(return_value={'setMarginMode': True}) + default_conf['dry_run'] = False ccxt_exceptionhandlers( mocker, @@ -3130,7 +3132,6 @@ def test_set_margin_mode(mocker, default_conf, collateral): # TODO-lev: Remove once implemented ("binance", TradingMode.MARGIN, Collateral.CROSS, True), ("binance", TradingMode.FUTURES, Collateral.CROSS, True), - ("binance", TradingMode.FUTURES, Collateral.ISOLATED, True), ("kraken", TradingMode.MARGIN, Collateral.CROSS, True), ("kraken", TradingMode.FUTURES, Collateral.CROSS, True), ("ftx", TradingMode.MARGIN, Collateral.CROSS, True), @@ -3139,7 +3140,7 @@ def test_set_margin_mode(mocker, default_conf, collateral): # TODO-lev: Uncomment once implemented # ("binance", TradingMode.MARGIN, Collateral.CROSS, False), # ("binance", TradingMode.FUTURES, Collateral.CROSS, False), - # ("binance", TradingMode.FUTURES, Collateral.ISOLATED, False), + ("binance", TradingMode.FUTURES, Collateral.ISOLATED, False), # ("kraken", TradingMode.MARGIN, Collateral.CROSS, False), # ("kraken", TradingMode.FUTURES, Collateral.CROSS, False), # ("ftx", TradingMode.MARGIN, Collateral.CROSS, False), diff --git a/tests/leverage_brackets.py b/tests/leverage_brackets.py new file mode 100644 index 000000000..aa60a7af2 --- /dev/null +++ b/tests/leverage_brackets.py @@ -0,0 +1,1215 @@ +def leverage_brackets(): + return { + "1000SHIB/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "1INCH/USDT": [ + [0.0, 0.012], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "AAVE/USDT": [ + [0.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.1665], + [10000000.0, 0.25] + ], + "ADA/BUSD": [ + [0.0, 0.025], + [100000.0, 0.05], + [500000.0, 0.1], + [1000000.0, 0.15], + [2000000.0, 0.25], + [5000000.0, 0.5] + ], + "ADA/USDT": [ + [0.0, 0.0065], + [10000.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.15], + [10000000.0, 0.25] + ], + "AKRO/USDT": [ + [0.0, 0.012], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "ALGO/USDT": [ + [0.0, 0.01], + [50000.0, 0.025], + [150000.0, 0.05], + [250000.0, 0.1], + [500000.0, 0.125], + [1000000.0, 0.25], + [2000000.0, 0.5] + ], + "ALICE/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "ALPHA/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "ANKR/USDT": [ + [0.0, 0.012], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "ATA/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "ATOM/USDT": [ + [0.0, 0.01], + [50000.0, 0.025], + [150000.0, 0.05], + [250000.0, 0.1], + [500000.0, 0.125], + [1000000.0, 0.25], + [2000000.0, 0.5] + ], + "AUDIO/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "AVAX/USDT": [ + [0.0, 0.01], + [50000.0, 0.025], + [150000.0, 0.05], + [250000.0, 0.1], + [500000.0, 0.125], + [750000.0, 0.25], + [1000000.0, 0.5] + ], + "AXS/USDT": [ + [0.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.1665], + [10000000.0, 0.25], + [15000000.0, 0.5] + ], + "BAKE/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "BAL/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "BAND/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "BAT/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "BCH/USDT": [ + [0.0, 0.0065], + [10000.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.15], + [10000000.0, 0.25] + ], + "BEL/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "BLZ/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "BNB/BUSD": [ + [0.0, 0.025], + [100000.0, 0.05], + [500000.0, 0.1], + [1000000.0, 0.15], + [2000000.0, 0.25], + [5000000.0, 0.5] + ], + "BNB/USDT": [ + [0.0, 0.0065], + [10000.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.15], + [10000000.0, 0.25] + ], + "BTC/BUSD": [ + [0.0, 0.004], + [25000.0, 0.005], + [100000.0, 0.01], + [500000.0, 0.025], + [1000000.0, 0.05], + [2000000.0, 0.1], + [5000000.0, 0.125], + [10000000.0, 0.15], + [20000000.0, 0.25], + [30000000.0, 0.5] + ], + "BTC/USDT": [ + [0.0, 0.004], + [50000.0, 0.005], + [250000.0, 0.01], + [1000000.0, 0.025], + [5000000.0, 0.05], + [20000000.0, 0.1], + [50000000.0, 0.125], + [100000000.0, 0.15], + [200000000.0, 0.25], + [300000000.0, 0.5] + ], + "BTCBUSD_210129": [ + [0.0, 0.004], + [5000.0, 0.005], + [25000.0, 0.01], + [100000.0, 0.025], + [500000.0, 0.05], + [2000000.0, 0.1], + [5000000.0, 0.125], + [10000000.0, 0.15], + [20000000.0, 0.25] + ], + "BTCBUSD_210226": [ + [0.0, 0.004], + [5000.0, 0.005], + [25000.0, 0.01], + [100000.0, 0.025], + [500000.0, 0.05], + [2000000.0, 0.1], + [5000000.0, 0.125], + [10000000.0, 0.15], + [20000000.0, 0.25] + ], + "BTCDOM/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "BTCSTUSDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "BTCUSDT_210326": [ + [0.0, 0.0065], + [10000.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.15], + [10000000.0, 0.25] + ], + "BTCUSDT_210625": [ + [0.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.15], + [10000000.0, 0.25] + ], + "BTCUSDT_210924": [ + [0.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.15], + [10000000.0, 0.25], + [20000000.0, 0.5] + ], + "BTS/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "BTT/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "BZRX/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "C98/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "CELR/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "CHR/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "CHZ/USDT": [ + [0.0, 0.012], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "COMP/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "COTI/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "CRV/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "CTK/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "CVC/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "DASH/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "DEFI/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "DENT/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "DGB/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "DODO/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "DOGE/BUSD": [ + [0.0, 0.025], + [100000.0, 0.05], + [500000.0, 0.1], + [1000000.0, 0.15], + [2000000.0, 0.25], + [5000000.0, 0.5] + ], + "DOGE/USDT": [ + [0.0, 0.01], + [50000.0, 0.025], + [150000.0, 0.05], + [250000.0, 0.1], + [500000.0, 0.125], + [750000.0, 0.25], + [1000000.0, 0.5] + ], + "DOT/USDT": [ + [0.0, 0.0065], + [10000.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.15], + [10000000.0, 0.25] + ], + "DOTECOUSDT": [ + [0.0, 0.012], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "DYDX/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "EGLD/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "ENJ/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "EOS/USDT": [ + [0.0, 0.0065], + [10000.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.15], + [10000000.0, 0.25] + ], + "ETC/USDT": [ + [0.0, 0.0065], + [10000.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.15], + [10000000.0, 0.25] + ], + "ETH/BUSD": [ + [0.0, 0.004], + [25000.0, 0.005], + [100000.0, 0.01], + [500000.0, 0.025], + [1000000.0, 0.05], + [2000000.0, 0.1], + [5000000.0, 0.125], + [10000000.0, 0.15], + [20000000.0, 0.25], + [30000000.0, 0.5] + ], + "ETH/USDT": [ + [0.0, 0.005], + [10000.0, 0.0065], + [100000.0, 0.01], + [500000.0, 0.02], + [1000000.0, 0.05], + [2000000.0, 0.1], + [5000000.0, 0.125], + [10000000.0, 0.15], + [20000000.0, 0.25] + ], + "ETHUSDT_210326": [ + [0.0, 0.0065], + [10000.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.15], + [10000000.0, 0.25] + ], + "ETHUSDT_210625": [ + [0.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.15], + [10000000.0, 0.25] + ], + "ETHUSDT_210924": [ + [0.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.15], + [10000000.0, 0.25], + [20000000.0, 0.5] + ], + "FIL/USDT": [ + [0.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.1665], + [10000000.0, 0.25] + ], + "FLM/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "FTM/USDT": [ + [0.0, 0.01], + [50000.0, 0.025], + [150000.0, 0.05], + [250000.0, 0.1], + [500000.0, 0.125], + [750000.0, 0.25], + [1000000.0, 0.5] + ], + "FTT/BUSD": [ + [0.0, 0.025], + [100000.0, 0.05], + [500000.0, 0.1], + [1000000.0, 0.15], + [2000000.0, 0.25], + [5000000.0, 0.5] + ], + "GRT/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "GTC/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "HBAR/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "HNT/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "HOT/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "ICP/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "ICX/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "IOST/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "IOTA/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "IOTX/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "KAVA/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "KEEP/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "KNC/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "KSM/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "LENDUSDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "LINA/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "LINK/USDT": [ + [0.0, 0.0065], + [10000.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.15], + [10000000.0, 0.25] + ], + "LIT/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "LRC/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "LTC/USDT": [ + [0.0, 0.0065], + [10000.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.15], + [10000000.0, 0.25] + ], + "LUNA/USDT": [ + [0.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.1665], + [10000000.0, 0.25], + [15000000.0, 0.5] + ], + "MANA/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "MASK/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "MATIC/USDT": [ + [0.0, 0.01], + [50000.0, 0.025], + [150000.0, 0.05], + [250000.0, 0.1], + [500000.0, 0.125], + [750000.0, 0.25], + [1000000.0, 0.5] + ], + "MKR/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "MTL/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "NEAR/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "NEO/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "NKN/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "OCEAN/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "OGN/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "OMG/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "ONE/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "ONT/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "QTUM/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "RAY/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "REEF/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "REN/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "RLC/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "RSR/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "RUNE/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "RVN/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "SAND/USDT": [ + [0.0, 0.012], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "SC/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "SFP/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "SKL/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "SNX/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "SOL/BUSD": [ + [0.0, 0.025], + [100000.0, 0.05], + [500000.0, 0.1], + [1000000.0, 0.15], + [2000000.0, 0.25], + [5000000.0, 0.5] + ], + "SOL/USDT": [ + [0.0, 0.01], + [50000.0, 0.025], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.25], + [10000000.0, 0.5] + ], + "SRM/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "STMX/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "STORJ/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "SUSHI/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "SXP/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "THETA/USDT": [ + [0.0, 0.01], + [50000.0, 0.025], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.1665], + [10000000.0, 0.25] + ], + "TLM/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "TOMO/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "TRB/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "TRX/USDT": [ + [0.0, 0.0065], + [10000.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.15], + [10000000.0, 0.25] + ], + "UNFI/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "UNI/USDT": [ + [0.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.1665], + [10000000.0, 0.25] + ], + "VET/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "WAVES/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "XEM/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "XLM/USDT": [ + [0.0, 0.0065], + [10000.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.15], + [10000000.0, 0.25] + ], + "XMR/USDT": [ + [0.0, 0.0065], + [10000.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.15], + [10000000.0, 0.25] + ], + "XRP/BUSD": [ + [0.0, 0.025], + [100000.0, 0.05], + [500000.0, 0.1], + [1000000.0, 0.15], + [2000000.0, 0.25], + [5000000.0, 0.5] + ], + "XRP/USDT": [ + [0.0, 0.0065], + [10000.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.15], + [10000000.0, 0.25] + ], + "XTZ/USDT": [ + [0.0, 0.0065], + [10000.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.15], + [10000000.0, 0.25] + ], + "YFI/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "YFII/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "ZEC/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "ZEN/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "ZIL/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "ZRX/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ] + } From 2c21bbfa0c6c914ec77610be1b118eb9defbdc45 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 17 Sep 2021 14:16:52 -0600 Subject: [PATCH 49/53] Fixed create order margin call count tests and made _ccxt_config a computed property --- freqtrade/exchange/bibox.py | 5 +++- freqtrade/exchange/binance.py | 20 ++++++++++++- freqtrade/exchange/exchange.py | 53 +++++++++++++++------------------ tests/conftest.py | 26 +++++++++++++--- tests/exchange/test_binance.py | 12 ++++++++ tests/exchange/test_exchange.py | 27 +++++++++-------- 6 files changed, 96 insertions(+), 47 deletions(-) diff --git a/freqtrade/exchange/bibox.py b/freqtrade/exchange/bibox.py index f0c2dd00b..074dd2b10 100644 --- a/freqtrade/exchange/bibox.py +++ b/freqtrade/exchange/bibox.py @@ -20,4 +20,7 @@ class Bibox(Exchange): # fetchCurrencies API point requires authentication for Bibox, # so switch it off for Freqtrade load_markets() - _ccxt_config: Dict = {"has": {"fetchCurrencies": False}} + @property + def _ccxt_config(self) -> Dict: + # Parameters to add directly to ccxt sync/async initialization. + return {"has": {"fetchCurrencies": False}} diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 572fa2141..60a1b8019 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -33,9 +33,27 @@ class Binance(Exchange): # TradingMode.SPOT always supported and not required in this list # (TradingMode.MARGIN, Collateral.CROSS), # TODO-lev: Uncomment once supported # (TradingMode.FUTURES, Collateral.CROSS), # TODO-lev: Uncomment once supported - (TradingMode.FUTURES, Collateral.ISOLATED) + # (TradingMode.FUTURES, Collateral.ISOLATED) # TODO-lev: Uncomment once supported ] + @property + def _ccxt_config(self) -> Dict: + # Parameters to add directly to ccxt sync/async initialization. + if self.trading_mode == TradingMode.MARGIN: + return { + "options": { + "defaultType": "margin" + } + } + elif self.trading_mode == TradingMode.FUTURES: + return { + "options": { + "defaultType": "future" + } + } + else: + return {} + def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 8bbc88235..4021e7d02 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -49,9 +49,6 @@ class Exchange: _config: Dict = {} - # Parameters to add directly to ccxt sync/async initialization. - _ccxt_config: Dict = {} - # Parameters to add directly to buy/sell calls (like agreeing to trading agreement) _params: Dict = {} @@ -131,21 +128,6 @@ class Exchange: self._trades_pagination = self._ft_has['trades_pagination'] self._trades_pagination_arg = self._ft_has['trades_pagination_arg'] - # Initialize ccxt objects - ccxt_config = self._ccxt_config.copy() - ccxt_config = deep_merge_dicts(exchange_config.get('ccxt_config', {}), ccxt_config) - ccxt_config = deep_merge_dicts(exchange_config.get('ccxt_sync_config', {}), ccxt_config) - - self._api = self._init_ccxt(exchange_config, ccxt_kwargs=ccxt_config) - - ccxt_async_config = self._ccxt_config.copy() - ccxt_async_config = deep_merge_dicts(exchange_config.get('ccxt_config', {}), - ccxt_async_config) - ccxt_async_config = deep_merge_dicts(exchange_config.get('ccxt_async_config', {}), - ccxt_async_config) - self._api_async = self._init_ccxt( - exchange_config, ccxt_async, ccxt_kwargs=ccxt_async_config) - self.trading_mode: TradingMode = ( TradingMode(config.get('trading_mode')) if config.get('trading_mode') @@ -157,6 +139,21 @@ class Exchange: else None ) + # Initialize ccxt objects + ccxt_config = self._ccxt_config + ccxt_config = deep_merge_dicts(exchange_config.get('ccxt_config', {}), ccxt_config) + ccxt_config = deep_merge_dicts(exchange_config.get('ccxt_sync_config', {}), ccxt_config) + + self._api = self._init_ccxt(exchange_config, ccxt_kwargs=ccxt_config) + + ccxt_async_config = self._ccxt_config + ccxt_async_config = deep_merge_dicts(exchange_config.get('ccxt_config', {}), + ccxt_async_config) + ccxt_async_config = deep_merge_dicts(exchange_config.get('ccxt_async_config', {}), + ccxt_async_config) + self._api_async = self._init_ccxt( + exchange_config, ccxt_async, ccxt_kwargs=ccxt_async_config) + if self.trading_mode != TradingMode.SPOT: self.fill_leverage_brackets() @@ -210,7 +207,7 @@ class Exchange: 'secret': exchange_config.get('secret'), 'password': exchange_config.get('password'), 'uid': exchange_config.get('uid', ''), - 'options': exchange_config.get('options', {}) + # 'options': exchange_config.get('options', {}) } if ccxt_kwargs: logger.info('Applying additional ccxt config: %s', ccxt_kwargs) @@ -231,6 +228,11 @@ class Exchange: return api + @property + def _ccxt_config(self) -> Dict: + # Parameters to add directly to ccxt sync/async initialization. + return {} + @property def name(self) -> str: """exchange Name (from ccxt)""" @@ -258,13 +260,6 @@ class Exchange: """exchange ccxt precisionMode""" return self._api.precisionMode - @property - def running_live_mode(self) -> bool: - return ( - self._config['runmode'].value not in ('backtest', 'hyperopt') and - not self._config['dry_run'] - ) - def _log_exchange_response(self, endpoint, response) -> None: """ Log exchange responses """ if self.log_responses: @@ -624,12 +619,12 @@ class Exchange: # The value returned should satisfy both limits: for amount (base currency) and # for cost (quote, stake currency), so max() is used here. # See also #2575 at github. - return self._divide_stake_amount_by_leverage( + return self._get_stake_amount_considering_leverage( max(min_stake_amounts) * amount_reserve_percent, leverage or 1.0 ) - def _divide_stake_amount_by_leverage(self, stake_amount: float, leverage: float): + def _get_stake_amount_considering_leverage(self, stake_amount: float, leverage: float): """ Takes the minimum stake amount for a pair with no leverage and returns the minimum stake amount when leverage is considered @@ -1603,7 +1598,7 @@ class Exchange: def fill_leverage_brackets(self): """ - #TODO-lev: Should maybe be renamed, leverage_brackets might not be accurate for kraken + # TODO-lev: Should maybe be renamed, leverage_brackets might not be accurate for kraken Assigns property _leverage_brackets to a dictionary of information about the leverage allowed on each pair """ diff --git a/tests/conftest.py b/tests/conftest.py index 3de299752..d2f24fa69 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,7 +18,7 @@ from freqtrade import constants from freqtrade.commands import Arguments from freqtrade.data.converter import ohlcv_to_dataframe from freqtrade.edge import Edge, PairInfo -from freqtrade.enums import RunMode +from freqtrade.enums import Collateral, RunMode, TradingMode from freqtrade.exchange import Exchange from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import LocalTrade, Trade, init_db @@ -81,7 +81,13 @@ def patched_configuration_load_config_file(mocker, config) -> None: ) -def patch_exchange(mocker, api_mock=None, id='binance', mock_markets=True) -> None: +def patch_exchange( + mocker, + api_mock=None, + id='binance', + mock_markets=True, + mock_supported_modes=True +) -> None: mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock(return_value={})) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) @@ -90,10 +96,22 @@ def patch_exchange(mocker, api_mock=None, id='binance', mock_markets=True) -> No mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value=id)) mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value=id.title())) mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=2)) + if mock_markets: mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=get_markets())) + if mock_supported_modes: + mocker.patch( + f'freqtrade.exchange.{id.capitalize()}._supported_trading_mode_collateral_pairs', + PropertyMock(return_value=[ + (TradingMode.MARGIN, Collateral.CROSS), + (TradingMode.MARGIN, Collateral.ISOLATED), + (TradingMode.FUTURES, Collateral.CROSS), + (TradingMode.FUTURES, Collateral.ISOLATED) + ]) + ) + if api_mock: mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) else: @@ -101,8 +119,8 @@ def patch_exchange(mocker, api_mock=None, id='binance', mock_markets=True) -> No def get_patched_exchange(mocker, config, api_mock=None, id='binance', - mock_markets=True) -> Exchange: - patch_exchange(mocker, api_mock, id, mock_markets) + mock_markets=True, mock_supported_modes=True) -> Exchange: + patch_exchange(mocker, api_mock, id, mock_markets, mock_supported_modes) config['exchange']['name'] = id try: exchange = ExchangeResolver.load_exchange(id, config) diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index 4999e94af..cbbace1db 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -336,3 +336,15 @@ async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog): assert exchange._api_async.fetch_ohlcv.call_count == 2 assert res == ohlcv assert log_has_re(r"Candle-data for ETH/BTC available starting with .*", caplog) + + +@pytest.mark.parametrize("trading_mode,collateral,config", [ + ("", "", {}), + ("margin", "cross", {"options": {"defaultType": "margin"}}), + ("futures", "isolated", {"options": {"defaultType": "future"}}), +]) +def test__ccxt_config(default_conf, mocker, trading_mode, collateral, config): + default_conf['trading_mode'] = trading_mode + default_conf['collateral'] = collateral + exchange = get_patched_exchange(mocker, default_conf, id="binance") + assert exchange._ccxt_config == config diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index ce09e31e7..8b16a9f12 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -132,10 +132,9 @@ def test_init_ccxt_kwargs(default_conf, mocker, caplog): assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog) assert ex._api.headers == {'hello': 'world'} + assert ex._ccxt_config == {} Exchange._headers = {} - # TODO-lev: Test with options - def test_destroy(default_conf, mocker, caplog): caplog.set_level(logging.DEBUG) @@ -1116,6 +1115,8 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange._set_leverage = MagicMock() + exchange.set_margin_mode = MagicMock() order = exchange.create_order( pair='ETH/BTC', @@ -1134,10 +1135,10 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, assert api_mock.create_order.call_args[0][2] == side assert api_mock.create_order.call_args[0][3] == 1 assert api_mock.create_order.call_args[0][4] is rate + assert exchange._set_leverage.call_count == 0 + assert exchange.set_margin_mode.call_count == 0 - assert api_mock._set_leverage.call_count == 0 if side == "buy" else 1 - assert api_mock.set_margin_mode.call_count == 0 if side == "buy" else 1 - + exchange.trading_mode = TradingMode.FUTURES order = exchange.create_order( pair='ETH/BTC', ordertype=ordertype, @@ -1147,8 +1148,8 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, leverage=3.0 ) - assert api_mock._set_leverage.call_count == 1 - assert api_mock.set_margin_mode.call_count == 1 + assert exchange._set_leverage.call_count == 1 + assert exchange.set_margin_mode.call_count == 1 def test_buy_dry_run(default_conf, mocker): @@ -3042,7 +3043,6 @@ def test_calculate_fee_rate(mocker, default_conf, order, expected) -> None: (3, 5, 5), (4, 5, 2), (5, 5, 1), - ]) def test_calculate_backoff(retrycount, max_retries, expected): assert calculate_backoff(retrycount, max_retries) == expected @@ -3054,7 +3054,7 @@ def test_calculate_backoff(retrycount, max_retries, expected): (20.0, 5.0, 4.0), (100.0, 100.0, 1.0) ]) -def test_divide_stake_amount_by_leverage( +def test_get_stake_amount_considering_leverage( exchange, stake_amount, leverage, @@ -3063,7 +3063,8 @@ def test_divide_stake_amount_by_leverage( default_conf ): exchange = get_patched_exchange(mocker, default_conf, id=exchange) - assert exchange._divide_stake_amount_by_leverage(stake_amount, leverage) == min_stake_with_lev + assert exchange._get_stake_amount_considering_leverage( + stake_amount, leverage) == min_stake_with_lev @pytest.mark.parametrize("exchange_name,trading_mode", [ @@ -3132,6 +3133,7 @@ def test_set_margin_mode(mocker, default_conf, collateral): # TODO-lev: Remove once implemented ("binance", TradingMode.MARGIN, Collateral.CROSS, True), ("binance", TradingMode.FUTURES, Collateral.CROSS, True), + ("binance", TradingMode.FUTURES, Collateral.ISOLATED, True), ("kraken", TradingMode.MARGIN, Collateral.CROSS, True), ("kraken", TradingMode.FUTURES, Collateral.CROSS, True), ("ftx", TradingMode.MARGIN, Collateral.CROSS, True), @@ -3140,7 +3142,7 @@ def test_set_margin_mode(mocker, default_conf, collateral): # TODO-lev: Uncomment once implemented # ("binance", TradingMode.MARGIN, Collateral.CROSS, False), # ("binance", TradingMode.FUTURES, Collateral.CROSS, False), - ("binance", TradingMode.FUTURES, Collateral.ISOLATED, False), + # ("binance", TradingMode.FUTURES, Collateral.ISOLATED, False), # ("kraken", TradingMode.MARGIN, Collateral.CROSS, False), # ("kraken", TradingMode.FUTURES, Collateral.CROSS, False), # ("ftx", TradingMode.MARGIN, Collateral.CROSS, False), @@ -3154,7 +3156,8 @@ def test_validate_trading_mode_and_collateral( collateral, exception_thrown ): - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange = get_patched_exchange( + mocker, default_conf, id=exchange_name, mock_supported_modes=False) if (exception_thrown): with pytest.raises(OperationalException): exchange.validate_trading_mode_and_collateral(trading_mode, collateral) From 979c6f2f263404ae78f4002262e7a2b3cc8497b3 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 18 Sep 2021 03:49:15 -0600 Subject: [PATCH 50/53] moved leverage_brackets.json to exchange/binance_leverage_brackets.json --- freqtrade/exchange/binance.py | 6 ++++-- .../binance_leverage_brackets.json} | 0 2 files changed, 4 insertions(+), 2 deletions(-) rename freqtrade/{data/leverage_brackets.json => exchange/binance_leverage_brackets.json} (100%) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 60a1b8019..69d781395 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -149,7 +149,9 @@ class Binance(Exchange): if self.trading_mode == TradingMode.FUTURES: try: if self._config['dry_run']: - leverage_brackets_path = Path('freqtrade/data') / 'leverage_brackets.json' + leverage_brackets_path = ( + Path(__file__).parent / 'binance_leverage_brackets.json' + ) with open(leverage_brackets_path) as json_file: leverage_brackets = json.load(json_file) else: @@ -187,7 +189,7 @@ class Binance(Exchange): max_lev = 1/margin_req return max_lev - @retrier + @ retrier def _set_leverage( self, leverage: float, diff --git a/freqtrade/data/leverage_brackets.json b/freqtrade/exchange/binance_leverage_brackets.json similarity index 100% rename from freqtrade/data/leverage_brackets.json rename to freqtrade/exchange/binance_leverage_brackets.json From 835e0e69fcabadc658327950baac6fcb3c4dcfc5 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 19 Sep 2021 02:23:05 -0600 Subject: [PATCH 51/53] removed leverage from create order api call --- docs/leverage.md | 4 ++++ freqtrade/exchange/binance.py | 11 ++--------- freqtrade/exchange/ftx.py | 10 ++-------- freqtrade/exchange/kraken.py | 11 ++--------- 4 files changed, 10 insertions(+), 26 deletions(-) diff --git a/docs/leverage.md b/docs/leverage.md index c4b975a0b..9448c64c3 100644 --- a/docs/leverage.md +++ b/docs/leverage.md @@ -15,3 +15,7 @@ For longs, the currency which pays the interest fee for the `borrowed` will alre Rollover fee = P (borrowed money) * R (quat_hourly_interest) * ceiling(T/4) (in hours) I (interest) = Opening fee + Rollover fee [source](https://support.kraken.com/hc/en-us/articles/206161568-What-are-the-fees-for-margin-trading-) + +# TODO-lev: Mention that says you can't run 2 bots on the same account with leverage, + +#TODO-lev: Create a huge risk disclaimer \ No newline at end of file diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 69d781395..7d83e971b 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -107,15 +107,8 @@ class Binance(Exchange): rate = self.price_to_precision(pair, rate) - order = self._api.create_order( - symbol=pair, - type=ordertype, - side=side, - amount=amount, - price=rate, - params=params, - leverage=leverage - ) + order = self._api.create_order(symbol=pair, type=ordertype, side=side, + amount=amount, price=rate, params=params) logger.info('stoploss limit order added for %s. ' 'stop price: %s. limit: %s', pair, stop_price, rate) self._log_exchange_response('create_stoploss_order', order) diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index eaf9a0477..0f572dee9 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -81,14 +81,8 @@ class Ftx(Exchange): params['stopPrice'] = stop_price amount = self.amount_to_precision(pair, amount) - order = self._api.create_order( - symbol=pair, - type=ordertype, - side=side, - amount=amount, - leverage=leverage, - params=params - ) + order = self._api.create_order(symbol=pair, type=ordertype, side=side, + amount=amount, params=params) self._log_exchange_response('create_stoploss_order', order) logger.info('stoploss order added for %s. ' 'stop price: %s.', pair, stop_price) diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index d6a816c9e..ec49c963f 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -114,15 +114,8 @@ class Kraken(Exchange): try: amount = self.amount_to_precision(pair, amount) - order = self._api.create_order( - symbol=pair, - type=ordertype, - side=side, - amount=amount, - price=stop_price, - leverage=leverage, - params=params - ) + order = self._api.create_order(symbol=pair, type=ordertype, side=side, + amount=amount, price=stop_price, params=params) self._log_exchange_response('create_stoploss_order', order) logger.info('stoploss order added for %s. ' 'stop price: %s.', pair, stop_price) From fa74b95a013e44ce1289b2c3cc231a75c54601d4 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 19 Sep 2021 02:33:28 -0600 Subject: [PATCH 52/53] reduced amount of code for leverage_brackets test --- tests/exchange/test_binance.py | 40 +- tests/leverage_brackets.py | 1215 -------------------------------- 2 files changed, 38 insertions(+), 1217 deletions(-) delete mode 100644 tests/leverage_brackets.py diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index cbbace1db..0c3e86fdd 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -9,7 +9,6 @@ from freqtrade.enums import Collateral, TradingMode from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException from tests.conftest import get_mock_coro, get_patched_exchange, log_has_re from tests.exchange.test_exchange import ccxt_exceptionhandlers -from tests.leverage_brackets import leverage_brackets @pytest.mark.parametrize('limitratio,expected,side', [ @@ -281,7 +280,44 @@ def test_fill_leverage_brackets_binance_dryrun(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") exchange.fill_leverage_brackets() - assert exchange._leverage_brackets == leverage_brackets() + leverage_brackets = { + "1000SHIB/USDT": [ + [0.0, 0.01], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "1INCH/USDT": [ + [0.0, 0.012], + [5000.0, 0.025], + [25000.0, 0.05], + [100000.0, 0.1], + [250000.0, 0.125], + [1000000.0, 0.5] + ], + "AAVE/USDT": [ + [0.0, 0.01], + [50000.0, 0.02], + [250000.0, 0.05], + [1000000.0, 0.1], + [2000000.0, 0.125], + [5000000.0, 0.1665], + [10000000.0, 0.25] + ], + "ADA/BUSD": [ + [0.0, 0.025], + [100000.0, 0.05], + [500000.0, 0.1], + [1000000.0, 0.15], + [2000000.0, 0.25], + [5000000.0, 0.5] + ] + } + + for key, value in leverage_brackets.items(): + assert exchange._leverage_brackets[key] == value def test__set_leverage_binance(mocker, default_conf): diff --git a/tests/leverage_brackets.py b/tests/leverage_brackets.py deleted file mode 100644 index aa60a7af2..000000000 --- a/tests/leverage_brackets.py +++ /dev/null @@ -1,1215 +0,0 @@ -def leverage_brackets(): - return { - "1000SHIB/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "1INCH/USDT": [ - [0.0, 0.012], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "AAVE/USDT": [ - [0.0, 0.01], - [50000.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.1665], - [10000000.0, 0.25] - ], - "ADA/BUSD": [ - [0.0, 0.025], - [100000.0, 0.05], - [500000.0, 0.1], - [1000000.0, 0.15], - [2000000.0, 0.25], - [5000000.0, 0.5] - ], - "ADA/USDT": [ - [0.0, 0.0065], - [10000.0, 0.01], - [50000.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.15], - [10000000.0, 0.25] - ], - "AKRO/USDT": [ - [0.0, 0.012], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "ALGO/USDT": [ - [0.0, 0.01], - [50000.0, 0.025], - [150000.0, 0.05], - [250000.0, 0.1], - [500000.0, 0.125], - [1000000.0, 0.25], - [2000000.0, 0.5] - ], - "ALICE/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "ALPHA/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "ANKR/USDT": [ - [0.0, 0.012], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "ATA/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "ATOM/USDT": [ - [0.0, 0.01], - [50000.0, 0.025], - [150000.0, 0.05], - [250000.0, 0.1], - [500000.0, 0.125], - [1000000.0, 0.25], - [2000000.0, 0.5] - ], - "AUDIO/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "AVAX/USDT": [ - [0.0, 0.01], - [50000.0, 0.025], - [150000.0, 0.05], - [250000.0, 0.1], - [500000.0, 0.125], - [750000.0, 0.25], - [1000000.0, 0.5] - ], - "AXS/USDT": [ - [0.0, 0.01], - [50000.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.1665], - [10000000.0, 0.25], - [15000000.0, 0.5] - ], - "BAKE/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "BAL/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "BAND/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "BAT/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "BCH/USDT": [ - [0.0, 0.0065], - [10000.0, 0.01], - [50000.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.15], - [10000000.0, 0.25] - ], - "BEL/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "BLZ/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "BNB/BUSD": [ - [0.0, 0.025], - [100000.0, 0.05], - [500000.0, 0.1], - [1000000.0, 0.15], - [2000000.0, 0.25], - [5000000.0, 0.5] - ], - "BNB/USDT": [ - [0.0, 0.0065], - [10000.0, 0.01], - [50000.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.15], - [10000000.0, 0.25] - ], - "BTC/BUSD": [ - [0.0, 0.004], - [25000.0, 0.005], - [100000.0, 0.01], - [500000.0, 0.025], - [1000000.0, 0.05], - [2000000.0, 0.1], - [5000000.0, 0.125], - [10000000.0, 0.15], - [20000000.0, 0.25], - [30000000.0, 0.5] - ], - "BTC/USDT": [ - [0.0, 0.004], - [50000.0, 0.005], - [250000.0, 0.01], - [1000000.0, 0.025], - [5000000.0, 0.05], - [20000000.0, 0.1], - [50000000.0, 0.125], - [100000000.0, 0.15], - [200000000.0, 0.25], - [300000000.0, 0.5] - ], - "BTCBUSD_210129": [ - [0.0, 0.004], - [5000.0, 0.005], - [25000.0, 0.01], - [100000.0, 0.025], - [500000.0, 0.05], - [2000000.0, 0.1], - [5000000.0, 0.125], - [10000000.0, 0.15], - [20000000.0, 0.25] - ], - "BTCBUSD_210226": [ - [0.0, 0.004], - [5000.0, 0.005], - [25000.0, 0.01], - [100000.0, 0.025], - [500000.0, 0.05], - [2000000.0, 0.1], - [5000000.0, 0.125], - [10000000.0, 0.15], - [20000000.0, 0.25] - ], - "BTCDOM/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "BTCSTUSDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "BTCUSDT_210326": [ - [0.0, 0.0065], - [10000.0, 0.01], - [50000.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.15], - [10000000.0, 0.25] - ], - "BTCUSDT_210625": [ - [0.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.15], - [10000000.0, 0.25] - ], - "BTCUSDT_210924": [ - [0.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.15], - [10000000.0, 0.25], - [20000000.0, 0.5] - ], - "BTS/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "BTT/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "BZRX/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "C98/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "CELR/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "CHR/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "CHZ/USDT": [ - [0.0, 0.012], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "COMP/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "COTI/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "CRV/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "CTK/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "CVC/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "DASH/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "DEFI/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "DENT/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "DGB/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "DODO/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "DOGE/BUSD": [ - [0.0, 0.025], - [100000.0, 0.05], - [500000.0, 0.1], - [1000000.0, 0.15], - [2000000.0, 0.25], - [5000000.0, 0.5] - ], - "DOGE/USDT": [ - [0.0, 0.01], - [50000.0, 0.025], - [150000.0, 0.05], - [250000.0, 0.1], - [500000.0, 0.125], - [750000.0, 0.25], - [1000000.0, 0.5] - ], - "DOT/USDT": [ - [0.0, 0.0065], - [10000.0, 0.01], - [50000.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.15], - [10000000.0, 0.25] - ], - "DOTECOUSDT": [ - [0.0, 0.012], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "DYDX/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "EGLD/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "ENJ/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "EOS/USDT": [ - [0.0, 0.0065], - [10000.0, 0.01], - [50000.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.15], - [10000000.0, 0.25] - ], - "ETC/USDT": [ - [0.0, 0.0065], - [10000.0, 0.01], - [50000.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.15], - [10000000.0, 0.25] - ], - "ETH/BUSD": [ - [0.0, 0.004], - [25000.0, 0.005], - [100000.0, 0.01], - [500000.0, 0.025], - [1000000.0, 0.05], - [2000000.0, 0.1], - [5000000.0, 0.125], - [10000000.0, 0.15], - [20000000.0, 0.25], - [30000000.0, 0.5] - ], - "ETH/USDT": [ - [0.0, 0.005], - [10000.0, 0.0065], - [100000.0, 0.01], - [500000.0, 0.02], - [1000000.0, 0.05], - [2000000.0, 0.1], - [5000000.0, 0.125], - [10000000.0, 0.15], - [20000000.0, 0.25] - ], - "ETHUSDT_210326": [ - [0.0, 0.0065], - [10000.0, 0.01], - [50000.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.15], - [10000000.0, 0.25] - ], - "ETHUSDT_210625": [ - [0.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.15], - [10000000.0, 0.25] - ], - "ETHUSDT_210924": [ - [0.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.15], - [10000000.0, 0.25], - [20000000.0, 0.5] - ], - "FIL/USDT": [ - [0.0, 0.01], - [50000.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.1665], - [10000000.0, 0.25] - ], - "FLM/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "FTM/USDT": [ - [0.0, 0.01], - [50000.0, 0.025], - [150000.0, 0.05], - [250000.0, 0.1], - [500000.0, 0.125], - [750000.0, 0.25], - [1000000.0, 0.5] - ], - "FTT/BUSD": [ - [0.0, 0.025], - [100000.0, 0.05], - [500000.0, 0.1], - [1000000.0, 0.15], - [2000000.0, 0.25], - [5000000.0, 0.5] - ], - "GRT/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "GTC/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "HBAR/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "HNT/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "HOT/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "ICP/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "ICX/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "IOST/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "IOTA/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "IOTX/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "KAVA/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "KEEP/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "KNC/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "KSM/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "LENDUSDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "LINA/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "LINK/USDT": [ - [0.0, 0.0065], - [10000.0, 0.01], - [50000.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.15], - [10000000.0, 0.25] - ], - "LIT/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "LRC/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "LTC/USDT": [ - [0.0, 0.0065], - [10000.0, 0.01], - [50000.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.15], - [10000000.0, 0.25] - ], - "LUNA/USDT": [ - [0.0, 0.01], - [50000.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.1665], - [10000000.0, 0.25], - [15000000.0, 0.5] - ], - "MANA/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "MASK/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "MATIC/USDT": [ - [0.0, 0.01], - [50000.0, 0.025], - [150000.0, 0.05], - [250000.0, 0.1], - [500000.0, 0.125], - [750000.0, 0.25], - [1000000.0, 0.5] - ], - "MKR/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "MTL/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "NEAR/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "NEO/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "NKN/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "OCEAN/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "OGN/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "OMG/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "ONE/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "ONT/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "QTUM/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "RAY/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "REEF/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "REN/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "RLC/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "RSR/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "RUNE/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "RVN/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "SAND/USDT": [ - [0.0, 0.012], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "SC/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "SFP/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "SKL/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "SNX/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "SOL/BUSD": [ - [0.0, 0.025], - [100000.0, 0.05], - [500000.0, 0.1], - [1000000.0, 0.15], - [2000000.0, 0.25], - [5000000.0, 0.5] - ], - "SOL/USDT": [ - [0.0, 0.01], - [50000.0, 0.025], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.25], - [10000000.0, 0.5] - ], - "SRM/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "STMX/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "STORJ/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "SUSHI/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "SXP/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "THETA/USDT": [ - [0.0, 0.01], - [50000.0, 0.025], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.1665], - [10000000.0, 0.25] - ], - "TLM/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "TOMO/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "TRB/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "TRX/USDT": [ - [0.0, 0.0065], - [10000.0, 0.01], - [50000.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.15], - [10000000.0, 0.25] - ], - "UNFI/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "UNI/USDT": [ - [0.0, 0.01], - [50000.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.1665], - [10000000.0, 0.25] - ], - "VET/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "WAVES/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "XEM/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "XLM/USDT": [ - [0.0, 0.0065], - [10000.0, 0.01], - [50000.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.15], - [10000000.0, 0.25] - ], - "XMR/USDT": [ - [0.0, 0.0065], - [10000.0, 0.01], - [50000.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.15], - [10000000.0, 0.25] - ], - "XRP/BUSD": [ - [0.0, 0.025], - [100000.0, 0.05], - [500000.0, 0.1], - [1000000.0, 0.15], - [2000000.0, 0.25], - [5000000.0, 0.5] - ], - "XRP/USDT": [ - [0.0, 0.0065], - [10000.0, 0.01], - [50000.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.15], - [10000000.0, 0.25] - ], - "XTZ/USDT": [ - [0.0, 0.0065], - [10000.0, 0.01], - [50000.0, 0.02], - [250000.0, 0.05], - [1000000.0, 0.1], - [2000000.0, 0.125], - [5000000.0, 0.15], - [10000000.0, 0.25] - ], - "YFI/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "YFII/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "ZEC/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "ZEN/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "ZIL/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ], - "ZRX/USDT": [ - [0.0, 0.01], - [5000.0, 0.025], - [25000.0, 0.05], - [100000.0, 0.1], - [250000.0, 0.125], - [1000000.0, 0.5] - ] - } From 2d679177e506067753c3a5475c80a8f69ce70f2f Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 19 Sep 2021 03:05:58 -0600 Subject: [PATCH 53/53] Added in lev prep before creating api order --- freqtrade/exchange/binance.py | 1 + freqtrade/exchange/exchange.py | 13 ++++++------- freqtrade/exchange/ftx.py | 1 + freqtrade/exchange/kraken.py | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 7d83e971b..35f427c34 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -107,6 +107,7 @@ class Binance(Exchange): rate = self.price_to_precision(pair, rate) + self._lev_prep(pair, leverage) order = self._api.create_order(symbol=pair, type=ordertype, side=side, amount=amount, price=rate, params=params) logger.info('stoploss limit order added for %s. ' diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 4021e7d02..4617fd4c2 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -773,10 +773,11 @@ class Exchange: # Order handling def _lev_prep(self, pair: str, leverage: float): - self.set_margin_mode(pair, self.collateral) - self._set_leverage(leverage, pair) + if self.trading_mode != TradingMode.SPOT: + self.set_margin_mode(pair, self.collateral) + self._set_leverage(leverage, pair) - def _get_params(self, time_in_force: str, ordertype: str, leverage: float) -> Dict: + def _get_params(self, ordertype: str, leverage: float, time_in_force: str = 'gtc') -> Dict: params = self._params.copy() if time_in_force != 'gtc' and ordertype != 'market': param = self._ft_has.get('time_in_force_parameter', '') @@ -790,10 +791,7 @@ class Exchange: dry_order = self.create_dry_run_order(pair, ordertype, side, amount, rate, leverage) return dry_order - if self.trading_mode != TradingMode.SPOT: - self._lev_prep(pair, leverage) - - params = self._get_params(time_in_force, ordertype, leverage) + params = self._get_params(ordertype, leverage, time_in_force) try: # Set the precision for amount and price(rate) as accepted by the exchange @@ -802,6 +800,7 @@ class Exchange: or self._api.options.get("createMarketBuyOrderRequiresPrice", False)) rate_for_order = self.price_to_precision(pair, rate) if needs_price else None + self._lev_prep(pair, leverage) order = self._api.create_order(pair, ordertype, side, amount, rate_for_order, params) self._log_exchange_response('create_order', order) diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 0f572dee9..62adea04c 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -81,6 +81,7 @@ class Ftx(Exchange): params['stopPrice'] = stop_price amount = self.amount_to_precision(pair, amount) + self._lev_prep(pair, leverage) order = self._api.create_order(symbol=pair, type=ordertype, side=side, amount=amount, params=params) self._log_exchange_response('create_stoploss_order', order) diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index ec49c963f..19d0a4967 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -184,8 +184,8 @@ class Kraken(Exchange): """ return - def _get_params(self, time_in_force: str, ordertype: str, leverage: float) -> Dict: - params = super()._get_params(time_in_force, ordertype, leverage) + def _get_params(self, ordertype: str, leverage: float, time_in_force: str = 'gtc') -> Dict: + params = super()._get_params(ordertype, leverage, time_in_force) if leverage > 1.0: params['leverage'] = leverage return params