From f3b42b0ef330db41a3d83ff053ff484d1f3b9ba2 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 29 Jan 2022 03:22:08 -0600 Subject: [PATCH 01/19] wrote exchange.get_max_amount_tradable --- freqtrade/exchange/exchange.py | 14 +++ tests/exchange/test_exchange.py | 147 ++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 2b35417d3..99a903f26 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -2119,6 +2119,20 @@ class Exchange: raise OperationalException( "Freqtrade only supports isolated futures for leverage trading") + def get_max_amount_tradable(self, pair: str) -> float: + ''' + Gets the maximum amount of a currency that the exchange will let you trade + ''' + market = self.markets[pair] + contractSize = market['contractSize'] + if not contractSize or market['spot']: + contractSize = 1 + maxAmount = market['limits']['amount']['max'] + if maxAmount: + return maxAmount * contractSize + else: + return float('inf') + def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool: return exchange_name in ccxt_exchanges(ccxt_module) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index ae94ae102..14a6661d1 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -4042,3 +4042,150 @@ def test_liquidation_price( upnl_ex_1=upnl_ex_1, position=position, ), 2), expected) + + +def test_get_max_amount_tradable( + mocker, + default_conf, +): + api_mock = MagicMock() + exchange = get_patched_exchange(mocker, default_conf, api_mock) + markets = { + 'XRP/USDT': { + 'limits': { + 'leverage': { + 'min': None, + 'max': None, + }, + 'amount': { + 'min': 0.001, + 'max': 10000 + }, + 'price': { + 'min': 39.86, + 'max': 306177 + }, + 'cost': { + 'min': 5, + 'max': None + }, + 'market': { + 'min': 0.001, + 'max': 2000 + }, + }, + 'precision': { + 'price': 2, + 'amount': 3, + 'base': 8, + 'quote': 8 + }, + 'contractSize': None, + 'spot': False, + 'swap': True + }, + 'LTC/USDT': { + 'limits': { + 'leverage': { + 'min': None, + 'max': None, + }, + 'amount': { + 'min': 0.001, + 'max': None + }, + 'price': { + 'min': 39.86, + 'max': 306177 + }, + 'cost': { + 'min': 5, + 'max': None + }, + 'market': { + 'min': 0.001, + 'max': 2000 + }, + }, + 'precision': { + 'price': 2, + 'amount': 3, + 'base': 8, + 'quote': 8 + }, + 'contractSize': 0.01, + 'spot': False, + 'swap': True + }, + 'ETH/USDT': { + 'limits': { + 'leverage': { + 'min': None, + 'max': None, + }, + 'amount': { + 'min': 0.001, + 'max': 10000 + }, + 'price': { + 'min': 39.86, + 'max': 306177 + }, + 'cost': { + 'min': 5, + 'max': None + }, + 'market': { + 'min': 0.001, + 'max': 2000 + }, + }, + 'precision': { + 'price': 2, + 'amount': 3, + 'base': 8, + 'quote': 8 + }, + 'contractSize': 0.01, + 'spot': False, + 'swap': True + }, + 'BTC/USDT': { + 'limits': { + 'leverage': { + 'min': None, + 'max': None, + }, + 'amount': { + 'min': 0.001, + 'max': 10000 + }, + 'price': { + 'min': 39.86, + 'max': 306177 + }, + 'cost': { + 'min': 5, + 'max': None + }, + 'market': { + 'min': 0.001, + 'max': 2000 + }, + }, + 'precision': { + 'price': 2, + 'amount': 3, + 'base': 8, + 'quote': 8 + }, + 'contractSize': 0.01, + 'spot': True, + 'swap': False + } + } + mocker.patch('freqtrade.exchange.Exchange.markets', markets) + assert exchange.get_max_amount_tradable('XRP/USDT') == 10000 + assert exchange.get_max_amount_tradable('LTC/USDT') == float('inf') + assert exchange.get_max_amount_tradable('ETH/USDT') == 100 + assert exchange.get_max_amount_tradable('BTC/USDT') == 10000 From 73319a74d31456714768073bfdd1b4ecb5432650 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 29 Jan 2022 21:26:03 -0600 Subject: [PATCH 02/19] moved get_max_leverage to get_min_pair_stake_amount --- freqtrade/exchange/exchange.py | 30 +++++++----- tests/exchange/test_exchange.py | 83 +++------------------------------ 2 files changed, 25 insertions(+), 88 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 99a903f26..ecd1d9b70 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -685,21 +685,26 @@ class Exchange: raise ValueError(f"Can't get market information for symbol {pair}") min_stake_amounts = [] + max_stake_amounts = [float('inf')] limits = market['limits'] if (limits['cost']['min'] is not None): min_stake_amounts.append( - self._contracts_to_amount( - pair, - limits['cost']['min'] - ) + self._contracts_to_amount(pair, limits['cost']['min']) ) if (limits['amount']['min'] is not None): min_stake_amounts.append( - self._contracts_to_amount( - pair, - limits['amount']['min'] * price - ) + self._contracts_to_amount(pair, limits['amount']['min'] * price) + ) + + if (limits['cost']['max'] is not None): + max_stake_amounts.append( + self._contracts_to_amount(pair, limits['cost']['max']) + ) + + if (limits['amount']['max'] is not None): + max_stake_amounts.append( + self._contracts_to_amount(pair, limits['amount']['max'] * price) ) if not min_stake_amounts: @@ -717,9 +722,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._get_stake_amount_considering_leverage( - max(min_stake_amounts) * amount_reserve_percent, - leverage or 1.0 + return min( + self._get_stake_amount_considering_leverage( + max(min_stake_amounts) * amount_reserve_percent, + leverage or 1.0 + ), + min(max_stake_amounts) ) def _get_stake_amount_considering_leverage(self, stake_amount: float, leverage: float): diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 14a6661d1..eaad11de5 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -4050,35 +4050,18 @@ def test_get_max_amount_tradable( ): api_mock = MagicMock() exchange = get_patched_exchange(mocker, default_conf, api_mock) + # TODO-lev: Move this to test_get_min_pair_stake_amount markets = { 'XRP/USDT': { 'limits': { - 'leverage': { - 'min': None, - 'max': None, - }, 'amount': { 'min': 0.001, 'max': 10000 }, - 'price': { - 'min': 39.86, - 'max': 306177 - }, 'cost': { 'min': 5, 'max': None }, - 'market': { - 'min': 0.001, - 'max': 2000 - }, - }, - 'precision': { - 'price': 2, - 'amount': 3, - 'base': 8, - 'quote': 8 }, 'contractSize': None, 'spot': False, @@ -4086,32 +4069,14 @@ def test_get_max_amount_tradable( }, 'LTC/USDT': { 'limits': { - 'leverage': { - 'min': None, - 'max': None, - }, 'amount': { 'min': 0.001, 'max': None }, - 'price': { - 'min': 39.86, - 'max': 306177 - }, 'cost': { 'min': 5, 'max': None }, - 'market': { - 'min': 0.001, - 'max': 2000 - }, - }, - 'precision': { - 'price': 2, - 'amount': 3, - 'base': 8, - 'quote': 8 }, 'contractSize': 0.01, 'spot': False, @@ -4119,32 +4084,14 @@ def test_get_max_amount_tradable( }, 'ETH/USDT': { 'limits': { - 'leverage': { - 'min': None, - 'max': None, - }, 'amount': { 'min': 0.001, 'max': 10000 }, - 'price': { - 'min': 39.86, - 'max': 306177 - }, 'cost': { 'min': 5, 'max': None }, - 'market': { - 'min': 0.001, - 'max': 2000 - }, - }, - 'precision': { - 'price': 2, - 'amount': 3, - 'base': 8, - 'quote': 8 }, 'contractSize': 0.01, 'spot': False, @@ -4152,40 +4099,22 @@ def test_get_max_amount_tradable( }, 'BTC/USDT': { 'limits': { - 'leverage': { - 'min': None, - 'max': None, - }, 'amount': { 'min': 0.001, 'max': 10000 }, - 'price': { - 'min': 39.86, - 'max': 306177 - }, 'cost': { 'min': 5, 'max': None }, - 'market': { - 'min': 0.001, - 'max': 2000 - }, - }, - 'precision': { - 'price': 2, - 'amount': 3, - 'base': 8, - 'quote': 8 }, 'contractSize': 0.01, 'spot': True, 'swap': False } } - mocker.patch('freqtrade.exchange.Exchange.markets', markets) - assert exchange.get_max_amount_tradable('XRP/USDT') == 10000 - assert exchange.get_max_amount_tradable('LTC/USDT') == float('inf') - assert exchange.get_max_amount_tradable('ETH/USDT') == 100 - assert exchange.get_max_amount_tradable('BTC/USDT') == 10000 + # mocker.patch('freqtrade.exchange.Exchange.markets', markets) + # assert exchange.get_max_amount_tradable('XRP/USDT') == 10000 + # assert exchange.get_max_amount_tradable('LTC/USDT') == float('inf') + # assert exchange.get_max_amount_tradable('ETH/USDT') == 100 + # assert exchange.get_max_amount_tradable('BTC/USDT') == 10000 From 64ad810445b0d2ea6494b4fcfc6c74d9535e0205 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 31 Jan 2022 02:31:44 -0600 Subject: [PATCH 03/19] Revert "moved get_max_leverage to get_min_pair_stake_amount" This reverts commit 90e48d5b98bcfb1452aa818a3274745eac395712. --- freqtrade/exchange/exchange.py | 39 +++++++--------- tests/exchange/test_exchange.py | 83 ++++++++++++++++++++++++++++++--- 2 files changed, 95 insertions(+), 27 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index ecd1d9b70..56442b842 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -684,27 +684,27 @@ class Exchange: except KeyError: raise ValueError(f"Can't get market information for symbol {pair}") + if 'limits' not in market: + return None + min_stake_amounts = [] - max_stake_amounts = [float('inf')] limits = market['limits'] - if (limits['cost']['min'] is not None): + if ('cost' in limits and 'min' in limits['cost'] + and limits['cost']['min'] is not None): min_stake_amounts.append( - self._contracts_to_amount(pair, limits['cost']['min']) + self._contracts_to_amount( + pair, + limits['cost']['min'] + ) ) - if (limits['amount']['min'] is not None): + if ('amount' in limits and 'min' in limits['amount'] + and limits['amount']['min'] is not None): min_stake_amounts.append( - self._contracts_to_amount(pair, limits['amount']['min'] * price) - ) - - if (limits['cost']['max'] is not None): - max_stake_amounts.append( - self._contracts_to_amount(pair, limits['cost']['max']) - ) - - if (limits['amount']['max'] is not None): - max_stake_amounts.append( - self._contracts_to_amount(pair, limits['amount']['max'] * price) + self._contracts_to_amount( + pair, + limits['amount']['min'] * price + ) ) if not min_stake_amounts: @@ -722,12 +722,9 @@ 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 min( - self._get_stake_amount_considering_leverage( - max(min_stake_amounts) * amount_reserve_percent, - leverage or 1.0 - ), - min(max_stake_amounts) + return self._get_stake_amount_considering_leverage( + max(min_stake_amounts) * amount_reserve_percent, + leverage or 1.0 ) def _get_stake_amount_considering_leverage(self, stake_amount: float, leverage: float): diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index eaad11de5..14a6661d1 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -4050,18 +4050,35 @@ def test_get_max_amount_tradable( ): api_mock = MagicMock() exchange = get_patched_exchange(mocker, default_conf, api_mock) - # TODO-lev: Move this to test_get_min_pair_stake_amount markets = { 'XRP/USDT': { 'limits': { + 'leverage': { + 'min': None, + 'max': None, + }, 'amount': { 'min': 0.001, 'max': 10000 }, + 'price': { + 'min': 39.86, + 'max': 306177 + }, 'cost': { 'min': 5, 'max': None }, + 'market': { + 'min': 0.001, + 'max': 2000 + }, + }, + 'precision': { + 'price': 2, + 'amount': 3, + 'base': 8, + 'quote': 8 }, 'contractSize': None, 'spot': False, @@ -4069,14 +4086,32 @@ def test_get_max_amount_tradable( }, 'LTC/USDT': { 'limits': { + 'leverage': { + 'min': None, + 'max': None, + }, 'amount': { 'min': 0.001, 'max': None }, + 'price': { + 'min': 39.86, + 'max': 306177 + }, 'cost': { 'min': 5, 'max': None }, + 'market': { + 'min': 0.001, + 'max': 2000 + }, + }, + 'precision': { + 'price': 2, + 'amount': 3, + 'base': 8, + 'quote': 8 }, 'contractSize': 0.01, 'spot': False, @@ -4084,14 +4119,32 @@ def test_get_max_amount_tradable( }, 'ETH/USDT': { 'limits': { + 'leverage': { + 'min': None, + 'max': None, + }, 'amount': { 'min': 0.001, 'max': 10000 }, + 'price': { + 'min': 39.86, + 'max': 306177 + }, 'cost': { 'min': 5, 'max': None }, + 'market': { + 'min': 0.001, + 'max': 2000 + }, + }, + 'precision': { + 'price': 2, + 'amount': 3, + 'base': 8, + 'quote': 8 }, 'contractSize': 0.01, 'spot': False, @@ -4099,22 +4152,40 @@ def test_get_max_amount_tradable( }, 'BTC/USDT': { 'limits': { + 'leverage': { + 'min': None, + 'max': None, + }, 'amount': { 'min': 0.001, 'max': 10000 }, + 'price': { + 'min': 39.86, + 'max': 306177 + }, 'cost': { 'min': 5, 'max': None }, + 'market': { + 'min': 0.001, + 'max': 2000 + }, + }, + 'precision': { + 'price': 2, + 'amount': 3, + 'base': 8, + 'quote': 8 }, 'contractSize': 0.01, 'spot': True, 'swap': False } } - # mocker.patch('freqtrade.exchange.Exchange.markets', markets) - # assert exchange.get_max_amount_tradable('XRP/USDT') == 10000 - # assert exchange.get_max_amount_tradable('LTC/USDT') == float('inf') - # assert exchange.get_max_amount_tradable('ETH/USDT') == 100 - # assert exchange.get_max_amount_tradable('BTC/USDT') == 10000 + mocker.patch('freqtrade.exchange.Exchange.markets', markets) + assert exchange.get_max_amount_tradable('XRP/USDT') == 10000 + assert exchange.get_max_amount_tradable('LTC/USDT') == float('inf') + assert exchange.get_max_amount_tradable('ETH/USDT') == 100 + assert exchange.get_max_amount_tradable('BTC/USDT') == 10000 From 6e8420914e2a0edcdfc77054043c6ee87a70a86b Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 31 Jan 2022 03:07:42 -0600 Subject: [PATCH 04/19] removed unnecessary CCXT checks in exchange.get_min_pair_stake_amount --- freqtrade/exchange/exchange.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 56442b842..99a903f26 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -684,13 +684,9 @@ class Exchange: except KeyError: raise ValueError(f"Can't get market information for symbol {pair}") - if 'limits' not in market: - return None - min_stake_amounts = [] limits = market['limits'] - if ('cost' in limits and 'min' in limits['cost'] - and limits['cost']['min'] is not None): + if (limits['cost']['min'] is not None): min_stake_amounts.append( self._contracts_to_amount( pair, @@ -698,8 +694,7 @@ class Exchange: ) ) - if ('amount' in limits and 'min' in limits['amount'] - and limits['amount']['min'] is not None): + if (limits['amount']['min'] is not None): min_stake_amounts.append( self._contracts_to_amount( pair, From ff5fffefb461c9ad757262c5c94e52d4e4c5bc9b Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 31 Jan 2022 03:57:47 -0600 Subject: [PATCH 05/19] exchange.get_max_amount_tradable looks at cost also --- freqtrade/exchange/exchange.py | 36 ++++++-- tests/exchange/test_exchange.py | 146 +++++++++++++------------------- 2 files changed, 89 insertions(+), 93 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 99a903f26..557c4c3f3 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -2119,19 +2119,39 @@ class Exchange: raise OperationalException( "Freqtrade only supports isolated futures for leverage trading") - def get_max_amount_tradable(self, pair: str) -> float: + def get_max_amount_tradable(self, pair: str, price: float) -> float: ''' Gets the maximum amount of a currency that the exchange will let you trade ''' + try: + market = self.markets[pair] + except KeyError: + raise ValueError(f"Can't get market information for symbol {pair}") + market = self.markets[pair] - contractSize = market['contractSize'] - if not contractSize or market['spot']: - contractSize = 1 - maxAmount = market['limits']['amount']['max'] - if maxAmount: - return maxAmount * contractSize - else: + limits = market['limits'] + max_amounts = [] + + if (limits['cost']['max'] is not None): + max_amounts.append( + self._contracts_to_amount( + pair, + limits['cost']['max'] / price + ) + ) + + if (limits['amount']['max'] is not None): + max_amounts.append( + self._contracts_to_amount( + pair, + limits['amount']['max'] + ) + ) + + if not max_amounts: return float('inf') + else: + return min(max_amounts) def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool: diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 14a6661d1..b81e2a206 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -4049,143 +4049,119 @@ def test_get_max_amount_tradable( default_conf, ): api_mock = MagicMock() + default_conf['collateral'] = 'isolated' + default_conf['trading_mode'] = 'futures' exchange = get_patched_exchange(mocker, default_conf, api_mock) markets = { - 'XRP/USDT': { + 'XRP/USDT:USDT': { 'limits': { - 'leverage': { - 'min': None, - 'max': None, - }, 'amount': { 'min': 0.001, 'max': 10000 }, - 'price': { - 'min': 39.86, - 'max': 306177 - }, 'cost': { 'min': 5, 'max': None }, - 'market': { - 'min': 0.001, - 'max': 2000 - }, - }, - 'precision': { - 'price': 2, - 'amount': 3, - 'base': 8, - 'quote': 8 }, 'contractSize': None, 'spot': False, - 'swap': True }, - 'LTC/USDT': { + 'LTC/USDT:USDT': { 'limits': { - 'leverage': { - 'min': None, - 'max': None, - }, 'amount': { 'min': 0.001, 'max': None }, - 'price': { - 'min': 39.86, - 'max': 306177 - }, 'cost': { 'min': 5, 'max': None }, - 'market': { - 'min': 0.001, - 'max': 2000 - }, - }, - 'precision': { - 'price': 2, - 'amount': 3, - 'base': 8, - 'quote': 8 }, 'contractSize': 0.01, 'spot': False, - 'swap': True }, - 'ETH/USDT': { + 'ETH/USDT:USDT': { 'limits': { - 'leverage': { - 'min': None, - 'max': None, - }, 'amount': { 'min': 0.001, 'max': 10000 }, - 'price': { - 'min': 39.86, - 'max': 306177 - }, 'cost': { 'min': 5, - 'max': None + 'max': 30000, }, - 'market': { - 'min': 0.001, - 'max': 2000 - }, - }, - 'precision': { - 'price': 2, - 'amount': 3, - 'base': 8, - 'quote': 8 }, 'contractSize': 0.01, 'spot': False, - 'swap': True }, 'BTC/USDT': { 'limits': { - 'leverage': { - 'min': None, - 'max': None, - }, 'amount': { 'min': 0.001, 'max': 10000 }, - 'price': { - 'min': 39.86, - 'max': 306177 - }, 'cost': { 'min': 5, 'max': None }, - 'market': { - 'min': 0.001, - 'max': 2000 - }, - }, - 'precision': { - 'price': 2, - 'amount': 3, - 'base': 8, - 'quote': 8 }, 'contractSize': 0.01, 'spot': True, - 'swap': False - } + }, + 'ADA/USDT': { + 'limits': { + 'amount': { + 'min': 0.001, + 'max': 10000 + }, + 'cost': { + 'min': 5, + 'max': 500, + }, + }, + 'contractSize': 0.01, + 'spot': True, + }, + 'DOGE/USDT:USDT': { + 'limits': { + 'amount': { + 'min': 0.001, + 'max': 10000 + }, + 'cost': { + 'min': 5, + 'max': 500 + }, + }, + 'contractSize': None, + 'spot': False, + }, + 'LUNA/USDT:USDT': { + 'limits': { + 'amount': { + 'min': 0.001, + 'max': 10000 + }, + 'cost': { + 'min': 5, + 'max': 500 + }, + }, + 'contractSize': 0.01, + 'spot': False, + }, } + mocker.patch('freqtrade.exchange.Exchange.markets', markets) - assert exchange.get_max_amount_tradable('XRP/USDT') == 10000 - assert exchange.get_max_amount_tradable('LTC/USDT') == float('inf') - assert exchange.get_max_amount_tradable('ETH/USDT') == 100 - assert exchange.get_max_amount_tradable('BTC/USDT') == 10000 + assert exchange.get_max_amount_tradable('XRP/USDT:USDT', 2.0) == 10000 + assert exchange.get_max_amount_tradable('LTC/USDT:USDT', 2.0) == float('inf') + assert exchange.get_max_amount_tradable('ETH/USDT:USDT', 2.0) == 100 + assert exchange.get_max_amount_tradable('DOGE/USDT:USDT', 2.0) == 250 + assert exchange.get_max_amount_tradable('LUNA/USDT:USDT', 2.0) == 2.5 + + default_conf['trading_mode'] = 'spot' + exchange = get_patched_exchange(mocker, default_conf, api_mock) + mocker.patch('freqtrade.exchange.Exchange.markets', markets) + assert exchange.get_max_amount_tradable('BTC/USDT', 2.0) == 10000 + assert exchange.get_max_amount_tradable('ADA/USDT', 2.0) == 250 From 55d91f018f9f2785d782ac1c47d48b3c96f097fc Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Tue, 1 Feb 2022 20:24:06 -0600 Subject: [PATCH 06/19] exchange._get_stake_amount_limit (merged min_pair_stake_amount and get_max_tradeable amount) --- freqtrade/exchange/exchange.py | 88 +++++++++++++++------------------ tests/exchange/test_exchange.py | 16 +++--- 2 files changed, 48 insertions(+), 56 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 557c4c3f3..3adb174b1 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -9,7 +9,7 @@ import logging from copy import deepcopy from datetime import datetime, timedelta, timezone from math import ceil -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Dict, List, Literal, Optional, Tuple, Union import arrow import ccxt @@ -677,33 +677,59 @@ class Exchange: else: return 1 / pow(10, precision) - def get_min_pair_stake_amount(self, pair: str, price: float, stoploss: float, - leverage: Optional[float] = 1.0) -> Optional[float]: + def get_min_pair_stake_amount( + self, + pair: str, + price: float, + stoploss: float, + leverage: Optional[float] = 1.0 + ) -> Optional[float]: + return self._get_stake_amount_limit(pair, price, stoploss, 'min', leverage) + + def get_max_pair_stake_amount( + self, + pair: str, + price: float, + stoploss: float + ) -> Optional[float]: + return self._get_stake_amount_limit(pair, price, stoploss, 'max') + + def _get_stake_amount_limit( + self, + pair: str, + price: float, + stoploss: float, + limit: Literal['min', 'max'], + leverage: Optional[float] = 1.0 + ) -> Optional[float]: + + isMin = limit == 'min' + try: market = self.markets[pair] except KeyError: raise ValueError(f"Can't get market information for symbol {pair}") - min_stake_amounts = [] + stake_limits = [] limits = market['limits'] - if (limits['cost']['min'] is not None): - min_stake_amounts.append( + if (limits['cost'][limit] is not None): + stake_limits.append( self._contracts_to_amount( pair, - limits['cost']['min'] + limits['cost'][limit] ) ) - if (limits['amount']['min'] is not None): - min_stake_amounts.append( + if (limits['amount'][limit] is not None): + stake_limits.append( self._contracts_to_amount( pair, - limits['amount']['min'] * price + limits['amount'][limit] * price ) ) - if not min_stake_amounts: - return None + if not stake_limits: + return None if isMin else float('inf') # reserve some percent defined in config (5% default) + stoploss amount_reserve_percent = 1.0 + self._config.get('amount_reserve_percent', @@ -718,9 +744,9 @@ class Exchange: # for cost (quote, stake currency), so max() is used here. # See also #2575 at github. return self._get_stake_amount_considering_leverage( - max(min_stake_amounts) * amount_reserve_percent, + max(stake_limits) * amount_reserve_percent, leverage or 1.0 - ) + ) if isMin else min(stake_limits) # TODO-lev: Account 4 max_reserve_percent in max limits? def _get_stake_amount_considering_leverage(self, stake_amount: float, leverage: float): """ @@ -2119,40 +2145,6 @@ class Exchange: raise OperationalException( "Freqtrade only supports isolated futures for leverage trading") - def get_max_amount_tradable(self, pair: str, price: float) -> float: - ''' - Gets the maximum amount of a currency that the exchange will let you trade - ''' - try: - market = self.markets[pair] - except KeyError: - raise ValueError(f"Can't get market information for symbol {pair}") - - market = self.markets[pair] - limits = market['limits'] - max_amounts = [] - - if (limits['cost']['max'] is not None): - max_amounts.append( - self._contracts_to_amount( - pair, - limits['cost']['max'] / price - ) - ) - - if (limits['amount']['max'] is not None): - max_amounts.append( - self._contracts_to_amount( - pair, - limits['amount']['max'] - ) - ) - - if not max_amounts: - return float('inf') - else: - return min(max_amounts) - def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool: return exchange_name in ccxt_exchanges(ccxt_module) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index b81e2a206..83d2fe98e 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -4044,7 +4044,7 @@ def test_liquidation_price( ), 2), expected) -def test_get_max_amount_tradable( +def test_get_max_pair_stake_amount( mocker, default_conf, ): @@ -4154,14 +4154,14 @@ def test_get_max_amount_tradable( } mocker.patch('freqtrade.exchange.Exchange.markets', markets) - assert exchange.get_max_amount_tradable('XRP/USDT:USDT', 2.0) == 10000 - assert exchange.get_max_amount_tradable('LTC/USDT:USDT', 2.0) == float('inf') - assert exchange.get_max_amount_tradable('ETH/USDT:USDT', 2.0) == 100 - assert exchange.get_max_amount_tradable('DOGE/USDT:USDT', 2.0) == 250 - assert exchange.get_max_amount_tradable('LUNA/USDT:USDT', 2.0) == 2.5 + assert exchange.get_max_pair_stake_amount('XRP/USDT:USDT', 2.0, 0.0) == 20000 + assert exchange.get_max_pair_stake_amount('LTC/USDT:USDT', 2.0, 0.0) == float('inf') + assert exchange.get_max_pair_stake_amount('ETH/USDT:USDT', 2.0, 0.0) == 200 + assert exchange.get_max_pair_stake_amount('DOGE/USDT:USDT', 2.0, 0.0) == 500 + assert exchange.get_max_pair_stake_amount('LUNA/USDT:USDT', 2.0, 0.0) == 5.0 default_conf['trading_mode'] = 'spot' exchange = get_patched_exchange(mocker, default_conf, api_mock) mocker.patch('freqtrade.exchange.Exchange.markets', markets) - assert exchange.get_max_amount_tradable('BTC/USDT', 2.0) == 10000 - assert exchange.get_max_amount_tradable('ADA/USDT', 2.0) == 250 + assert exchange.get_max_pair_stake_amount('BTC/USDT', 2.0, 0.0) == 20000 + assert exchange.get_max_pair_stake_amount('ADA/USDT', 2.0, 0.0) == 500 From 6b6b35ac1c0f0250a002dc0c9ec98da43e586877 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Tue, 1 Feb 2022 20:39:22 -0600 Subject: [PATCH 07/19] check for max stake limit in freqtradebot and backtesting --- freqtrade/freqtradebot.py | 22 ++++++++++++++----- freqtrade/optimize/backtesting.py | 18 +++++++++++---- tests/conftest.py | 12 +++++----- tests/optimize/test_backtest_detail.py | 1 + tests/optimize/test_backtesting.py | 8 +++++++ .../test_backtesting_adjust_position.py | 1 + 6 files changed, 47 insertions(+), 15 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 6f39c23f7..44665ff61 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -543,12 +543,19 @@ class FreqtradeBot(LoggingMixin): min_stake_amount = self.exchange.get_min_pair_stake_amount(trade.pair, current_rate, self.strategy.stoploss) - max_stake_amount = self.wallets.get_available_stake_amount() + max_stake_amount = self.exchange.get_max_pair_stake_amount(trade.pair, + current_rate, + self.strategy.stoploss) + if max_stake_amount is None: + # * Should never be executed + raise OperationalException(f'max_stake_amount is None for {trade}') + stake_available = self.wallets.get_available_stake_amount() logger.debug(f"Calling adjust_trade_position for pair {trade.pair}") stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, default_retval=None)( trade=trade, current_time=datetime.now(timezone.utc), current_rate=current_rate, - current_profit=current_profit, min_stake=min_stake_amount, max_stake=max_stake_amount) + current_profit=current_profit, min_stake=min_stake_amount, + max_stake=min(max_stake_amount, stake_available)) if stake_amount is not None and stake_amount > 0.0: # We should increase our position @@ -845,15 +852,20 @@ class FreqtradeBot(LoggingMixin): # We do however also need min-stake to determine leverage, therefore this is ignored as # edge-case for now. min_stake_amount = self.exchange.get_min_pair_stake_amount( - pair, enter_limit_requested, self.strategy.stoploss,) + pair, enter_limit_requested, self.strategy.stoploss) + max_stake_amount = self.exchange.get_max_pair_stake_amount( + pair, enter_limit_requested, self.strategy.stoploss) if not self.edge and trade is None: - max_stake_amount = self.wallets.get_available_stake_amount() + stake_available = self.wallets.get_available_stake_amount() + if max_stake_amount is None: + # * Should never be executed + raise OperationalException(f'max_stake_amount is None for {trade}') stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount, default_retval=stake_amount)( pair=pair, current_time=datetime.now(timezone.utc), current_rate=enter_limit_requested, proposed_stake=stake_amount, - min_stake=min_stake_amount, max_stake=max_stake_amount, + min_stake=min_stake_amount, max_stake=min(max_stake_amount, stake_available), entry_tag=entry_tag, side=trade_side ) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 6fac22cdf..e973b1fbe 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -413,11 +413,16 @@ class Backtesting: current_profit = trade.calc_profit_ratio(row[OPEN_IDX]) min_stake = self.exchange.get_min_pair_stake_amount(trade.pair, row[OPEN_IDX], -0.1) - max_stake = self.wallets.get_available_stake_amount() + max_stake = self.exchange.get_max_pair_stake_amount(trade.pair, row[OPEN_IDX], -0.1) + if max_stake is None: + # * Should never be executed + raise OperationalException(f'max_stake_amount is None for {trade}') + stake_available = self.wallets.get_available_stake_amount() stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, default_retval=None)( trade=trade, current_time=row[DATE_IDX].to_pydatetime(), current_rate=row[OPEN_IDX], - current_profit=current_profit, min_stake=min_stake, max_stake=max_stake) + current_profit=current_profit, min_stake=min_stake, + max_stake=min(max_stake, stake_available)) # Check if we should increase our position if stake_amount is not None and stake_amount > 0.0: @@ -551,7 +556,11 @@ class Backtesting: propose_rate = min(max(propose_rate, row[LOW_IDX]), row[HIGH_IDX]) min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, propose_rate, -0.05) or 0 - max_stake_amount = self.wallets.get_available_stake_amount() + max_stake_amount = self.exchange.get_max_pair_stake_amount(pair, propose_rate, -0.05) or 0 + if max_stake_amount is None: + # * Should never be executed + raise OperationalException(f'max_stake_amount is None for {trade}') + stake_available = self.wallets.get_available_stake_amount() pos_adjust = trade is not None if not pos_adjust: @@ -563,7 +572,8 @@ class Backtesting: stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount, default_retval=stake_amount)( pair=pair, current_time=current_time, current_rate=propose_rate, - proposed_stake=stake_amount, min_stake=min_stake_amount, max_stake=max_stake_amount, + proposed_stake=stake_amount, min_stake=min_stake_amount, + max_stake=min(stake_available, max_stake_amount), entry_tag=entry_tag, side=direction) stake_amount = self.wallets.validate_stake_amount(pair, stake_amount, min_stake_amount) diff --git a/tests/conftest.py b/tests/conftest.py index 15705e987..9e85260b2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -587,7 +587,7 @@ def get_markets(): 'limits': { 'amount': { 'min': 0.01, - 'max': 1000, + 'max': 100000000, }, 'price': { 'min': None, @@ -622,7 +622,7 @@ def get_markets(): 'limits': { 'amount': { 'min': 0.01, - 'max': 1000, + 'max': 100000000, }, 'price': { 'min': None, @@ -690,7 +690,7 @@ def get_markets(): 'limits': { 'amount': { 'min': 0.01, - 'max': 1000, + 'max': 100000000, }, 'price': { 'min': None, @@ -725,7 +725,7 @@ def get_markets(): 'limits': { 'amount': { 'min': 0.01, - 'max': 1000, + 'max': 100000000, }, 'price': { 'min': None, @@ -760,7 +760,7 @@ def get_markets(): 'limits': { 'amount': { 'min': 0.01, - 'max': 1000, + 'max': 100000000, }, 'price': { 'min': None, @@ -988,7 +988,7 @@ def get_markets(): 'limits': { 'amount': { 'min': 0.01, - 'max': 1000, + 'max': 100000000000, }, 'price': { 'min': None, diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index 86a67a25e..b89fbe8aa 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -609,6 +609,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: mocker.patch("freqtrade.exchange.Exchange.get_fee", return_value=0.0) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch("freqtrade.exchange.Binance.get_max_leverage", return_value=100) patch_exchange(mocker) frame = _build_backtest_dataframe(data.data) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index ce58cf72a..e48722626 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -500,6 +500,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: default_conf['use_sell_signal'] = False mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) default_conf['stake_amount'] = 'unlimited' default_conf['max_open_trades'] = 2 @@ -566,6 +567,7 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None: default_conf['use_sell_signal'] = False mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) default_conf['timeframe_detail'] = '1m' default_conf['max_open_trades'] = 2 @@ -659,6 +661,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: default_conf['use_sell_signal'] = False mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) @@ -724,6 +727,7 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None default_conf['use_sell_signal'] = False mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) @@ -772,6 +776,7 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad default_conf['enable_protections'] = True mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) tests = [ ['sine', 9], ['raise', 10], @@ -806,6 +811,7 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir, default_conf['enable_protections'] = True mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) # While buy-signals are unrealistic, running backtesting # over and over again should not cause different results @@ -846,6 +852,7 @@ def test_backtest_only_sell(mocker, default_conf, testdatadir): def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir): mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC', datadir=testdatadir) @@ -892,6 +899,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) return dataframe mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) patch_exchange(mocker) diff --git a/tests/optimize/test_backtesting_adjust_position.py b/tests/optimize/test_backtesting_adjust_position.py index 91b55cdc0..f8586ffb6 100644 --- a/tests/optimize/test_backtesting_adjust_position.py +++ b/tests/optimize/test_backtesting_adjust_position.py @@ -17,6 +17,7 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> default_conf['use_sell_signal'] = False mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) default_conf.update({ "stake_amount": 100.0, From 8c680d75b96c222349e5524dabdfa791f5c8b9ef Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Thu, 3 Feb 2022 00:10:14 -0600 Subject: [PATCH 08/19] moved max_stake_amount check for None to exchange.get_max_pair_stake_amount --- freqtrade/exchange/exchange.py | 9 +++++++-- freqtrade/freqtradebot.py | 6 ------ freqtrade/optimize/backtesting.py | 6 ------ 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 3adb174b1..65f15b98b 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -691,8 +691,13 @@ class Exchange: pair: str, price: float, stoploss: float - ) -> Optional[float]: - return self._get_stake_amount_limit(pair, price, stoploss, 'max') + ) -> float: + max_stake_amount = self._get_stake_amount_limit(pair, price, stoploss, 'max') + if max_stake_amount is None: + # * Should never be executed + raise OperationalException(f'{self.name}.get_max_pair_stake_amount should' + 'never set max_stake_amount to None') + return max_stake_amount def _get_stake_amount_limit( self, diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 44665ff61..073f4d3f3 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -546,9 +546,6 @@ class FreqtradeBot(LoggingMixin): max_stake_amount = self.exchange.get_max_pair_stake_amount(trade.pair, current_rate, self.strategy.stoploss) - if max_stake_amount is None: - # * Should never be executed - raise OperationalException(f'max_stake_amount is None for {trade}') stake_available = self.wallets.get_available_stake_amount() logger.debug(f"Calling adjust_trade_position for pair {trade.pair}") stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, @@ -858,9 +855,6 @@ class FreqtradeBot(LoggingMixin): if not self.edge and trade is None: stake_available = self.wallets.get_available_stake_amount() - if max_stake_amount is None: - # * Should never be executed - raise OperationalException(f'max_stake_amount is None for {trade}') stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount, default_retval=stake_amount)( pair=pair, current_time=datetime.now(timezone.utc), diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index e973b1fbe..ecfc5c342 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -414,9 +414,6 @@ class Backtesting: current_profit = trade.calc_profit_ratio(row[OPEN_IDX]) min_stake = self.exchange.get_min_pair_stake_amount(trade.pair, row[OPEN_IDX], -0.1) max_stake = self.exchange.get_max_pair_stake_amount(trade.pair, row[OPEN_IDX], -0.1) - if max_stake is None: - # * Should never be executed - raise OperationalException(f'max_stake_amount is None for {trade}') stake_available = self.wallets.get_available_stake_amount() stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, default_retval=None)( @@ -557,9 +554,6 @@ class Backtesting: min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, propose_rate, -0.05) or 0 max_stake_amount = self.exchange.get_max_pair_stake_amount(pair, propose_rate, -0.05) or 0 - if max_stake_amount is None: - # * Should never be executed - raise OperationalException(f'max_stake_amount is None for {trade}') stake_available = self.wallets.get_available_stake_amount() pos_adjust = trade is not None From c5cfd971f54dfe8307c9569cab86979e4baa50de Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Thu, 3 Feb 2022 00:52:39 -0600 Subject: [PATCH 09/19] get_max_pair_stake_amount_tests --- tests/exchange/test_exchange.py | 84 +++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 35 deletions(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 83d2fe98e..b6e077fbe 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -342,7 +342,7 @@ def test_price_get_one_pip(default_conf, mocker, price, precision_mode, precisio assert pytest.approx(exchange.price_get_one_pip(pair, price)) == expected -def test_get_min_pair_stake_amount(mocker, default_conf) -> None: +def test__get_stake_amount_limit(mocker, default_conf) -> None: exchange = get_patched_exchange(mocker, default_conf, id="binance") stoploss = -0.05 @@ -356,7 +356,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: with pytest.raises(ValueError, match=r'.*get market information.*'): exchange.get_min_pair_stake_amount('BNB/BTC', 1, stoploss) - # no cost Min + # no cost/amount Min markets["ETH/BTC"]["limits"] = { 'cost': {'min': None, 'max': None}, 'amount': {'min': None, 'max': None}, @@ -367,51 +367,33 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: ) result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss) assert result is None + result = exchange.get_max_pair_stake_amount('ETH/BTC', 1, stoploss) + assert result == float('inf') - # no amount Min + # min/max cost is set markets["ETH/BTC"]["limits"] = { - 'cost': {'min': None, 'max': None}, - 'amount': {'min': None, 'max': None}, - } - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) - result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss) - assert result is None - - # empty 'cost'/'amount' section - markets["ETH/BTC"]["limits"] = { - 'cost': {'min': None, 'max': None}, - 'amount': {'min': None, 'max': None}, - } - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) - result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss) - assert result is None - - # min cost is set - markets["ETH/BTC"]["limits"] = { - 'cost': {'min': 2, 'max': None}, + 'cost': {'min': 2, 'max': 10000}, 'amount': {'min': None, 'max': None}, } mocker.patch( 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets) ) + # min result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, 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) + # max + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2, stoploss) + assert result == 10000 # min amount is set markets["ETH/BTC"]["limits"] = { 'cost': {'min': None, 'max': None}, - 'amount': {'min': 2, 'max': None}, + 'amount': {'min': 2, 'max': 10000}, } mocker.patch( 'freqtrade.exchange.Exchange.markets', @@ -423,6 +405,9 @@ 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) + # max + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2, stoploss) + assert result == 20000 # min amount and cost are set (cost is minimal) markets["ETH/BTC"]["limits"] = { @@ -442,8 +427,8 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: # min amount and cost are set (amount is minial) markets["ETH/BTC"]["limits"] = { - 'cost': {'min': 8, 'max': None}, - 'amount': {'min': 2, 'max': None}, + 'cost': {'min': 8, 'max': 10000}, + 'amount': {'min': 2, 'max': 500}, } mocker.patch( 'freqtrade.exchange.Exchange.markets', @@ -455,6 +440,9 @@ 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) + # Max + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2, stoploss) + assert result == 1000 result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4) expected_result = max(8, 2 * 2) * 1.5 @@ -462,6 +450,9 @@ 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) + # Max + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2, stoploss) + assert result == 1000 # Really big stoploss result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1) @@ -470,6 +461,9 @@ 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) + # Max + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2, stoploss) + assert result == 1000 markets["ETH/BTC"]["contractSize"] = '0.01' default_conf['trading_mode'] = 'futures' @@ -483,6 +477,9 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: # Contract size 0.01 result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1) assert isclose(result, expected_result * 0.01) + # Max + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2, -1) + assert result == 10 markets["ETH/BTC"]["contractSize"] = '10' mocker.patch( @@ -492,6 +489,9 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: # With Leverage, Contract size 10 result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0) assert isclose(result, (expected_result/12) * 10.0) + # Max + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2, -1) + assert result == 10000 def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: @@ -499,10 +499,10 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: stoploss = -0.05 markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}} - # Real Binance data + # ~Real Binance data markets["ETH/BTC"]["limits"] = { - 'cost': {'min': 0.0001}, - 'amount': {'min': 0.001} + 'cost': {'min': 0.0001, 'max': 4000}, + 'amount': {'min': 0.001, 'max': 10000}, } mocker.patch( 'freqtrade.exchange.Exchange.markets', @@ -511,9 +511,23 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss) expected_result = max(0.0001, 0.001 * 0.020405) * (1+0.05) / (1-abs(stoploss)) assert round(result, 8) == round(expected_result, 8) + # Max + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2.0, stoploss) + assert result == 4000 + + # Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss, 3.0) assert round(result, 8) == round(expected_result/3, 8) + # Contract_size + markets["ETH/BTC"]["contractSize"] = 0.1 + result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss, 3.0) + assert round(result, 8) == round((expected_result/3), 8) + + # Max + result = exchange.get_max_pair_stake_amount('ETH/BTC', 12.0, stoploss) + assert result == 4000 + def test_set_sandbox(default_conf, mocker): """ @@ -633,7 +647,7 @@ def test_reload_markets_exception(default_conf, mocker, caplog): assert log_has_re(r"Could not reload markets.*", caplog) -@pytest.mark.parametrize("stake_currency", ['ETH', 'BTC', 'USDT']) +@ pytest.mark.parametrize("stake_currency", ['ETH', 'BTC', 'USDT']) def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog): default_conf['stake_currency'] = stake_currency api_mock = MagicMock() From 7465037906266eb3144f06c64828d6f97ce16bb6 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Thu, 3 Feb 2022 01:22:23 -0600 Subject: [PATCH 10/19] freqtradebot.execute_entry test for too high stake amount --- freqtrade/freqtradebot.py | 11 +++++++---- freqtrade/optimize/backtesting.py | 2 +- tests/optimize/test_backtesting.py | 1 + tests/test_freqtradebot.py | 16 ++++++++++++++++ 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 073f4d3f3..596e96bb5 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -825,10 +825,13 @@ class FreqtradeBot(LoggingMixin): return trade def get_valid_enter_price_and_stake( - self, pair: str, price: Optional[float], stake_amount: float, - side: str, trade_side: str, - entry_tag: Optional[str], - trade: Optional[Trade]) -> Tuple[float, float]: + self, pair: str, price: Optional[float], stake_amount: float, + side: str, trade_side: str, + entry_tag: Optional[str], + trade: Optional[Trade] + ) -> Tuple[float, float]: + # TODO: This method has no tests + if price: enter_limit_requested = price else: diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index ecfc5c342..701db34eb 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -410,7 +410,7 @@ class Backtesting: def _get_adjust_trade_entry_for_candle(self, trade: LocalTrade, row: Tuple ) -> LocalTrade: - + # TODO: Write tests current_profit = trade.calc_profit_ratio(row[OPEN_IDX]) min_stake = self.exchange.get_min_pair_stake_amount(trade.pair, row[OPEN_IDX], -0.1) max_stake = self.exchange.get_max_pair_stake_amount(trade.pair, row[OPEN_IDX], -0.1) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index e48722626..acd1fe3b4 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -497,6 +497,7 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti def test_backtest__enter_trade(default_conf, fee, mocker) -> None: + # TODO-lev: test max_pair_stake_amount default_conf['use_sell_signal'] = False mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 32b7d543b..3ee9d57eb 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -932,6 +932,22 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, assert trade.open_rate_requested == 10 assert trade.isolated_liq == liq_price + # In case of too high stake amount + + order['status'] = 'open' + order['id'] = '55672' + + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_max_pair_stake_amount=MagicMock(return_value=500), + ) + freqtrade.exchange.get_max_pair_stake_amount = MagicMock(return_value=500) + + assert freqtrade.execute_entry(pair, 2000, is_short=is_short) + trade = Trade.query.all()[9] + trade.is_short = is_short + assert trade.stake_amount == 500 + @pytest.mark.parametrize("is_short", [False, True]) def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_order, is_short) -> None: From 16c2d54482f30911de7209a4a6d85b0aaa7cabf8 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Thu, 3 Feb 2022 01:38:14 -0600 Subject: [PATCH 11/19] updated margin_modes --- freqtrade/constants.py | 2 +- tests/exchange/test_exchange.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index fcf78c0e9..9c00dc7e3 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -46,7 +46,7 @@ DEFAULT_DATAFRAME_COLUMNS = ['date', 'open', 'high', 'low', 'close', 'volume'] # it has wide consequences for stored trades files DEFAULT_TRADES_COLUMNS = ['timestamp', 'id', 'type', 'side', 'price', 'amount', 'cost'] TRADING_MODES = ['spot', 'margin', 'futures'] -MARGIN_MODES = ['cross', 'isolated'] +MARGIN_MODES = ['cross', 'isolated', ''] LAST_BT_RESULT_FN = '.last_result.json' FTHYPT_FILEVERSION = 'fthypt_fileversion' diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index b6e077fbe..a3167573c 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -4063,7 +4063,7 @@ def test_get_max_pair_stake_amount( default_conf, ): api_mock = MagicMock() - default_conf['collateral'] = 'isolated' + default_conf['margin_mode'] = 'isolated' default_conf['trading_mode'] = 'futures' exchange = get_patched_exchange(mocker, default_conf, api_mock) markets = { From a50f4d2c57b409345379c71607bde09953af7a21 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Thu, 3 Feb 2022 18:28:08 -0600 Subject: [PATCH 12/19] Exchange.createOrder added * as second param --- freqtrade/exchange/exchange.py | 3 ++- tests/exchange/test_exchange.py | 12 ++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 65f15b98b..b3098d3d4 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -935,6 +935,7 @@ class Exchange: def create_order( self, + *, pair: str, ordertype: str, side: str, @@ -967,7 +968,7 @@ class Exchange: side, amount, rate_for_order, - params + params, ) self._log_exchange_response('create_order', order) order = self._order_contracts_to_amount(order) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index a3167573c..ad24efc35 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1271,7 +1271,8 @@ def test_buy_prod(default_conf, mocker, exchange_name): side="buy", amount=1, rate=200, - time_in_force=time_in_force) + time_in_force=time_in_force + ) assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' assert api_mock.create_order.call_args[0][1] == order_type assert api_mock.create_order.call_args[0][2] == 'buy' @@ -2532,7 +2533,14 @@ def test_cancel_order_dry_run(default_conf, mocker, exchange_name): assert exchange.cancel_order(order_id='123', pair='TKN/BTC') == {} assert exchange.cancel_stoploss_order(order_id='123', pair='TKN/BTC') == {} - order = exchange.create_order('ETH/BTC', 'limit', "buy", 5, 0.55, 'gtc') + order = exchange.create_order( + pair='ETH/BTC', + ordertype='limit', + side='buy', + amount=5, + rate=0.55, + time_in_force='gtc', + ) cancel_order = exchange.cancel_order(order_id=order['id'], pair='ETH/BTC') assert order['id'] == cancel_order['id'] From 30c476e3c1f396e3a89851b2de7e59ca9e2d1cf5 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Thu, 3 Feb 2022 19:22:37 -0600 Subject: [PATCH 13/19] freqtradebot.get_valid_enter_price_and_stake gets min of stake_amount and max_stake_amount before calling wallets.validate_stake_amount --- freqtrade/freqtradebot.py | 1 + tests/test_freqtradebot.py | 1 + 2 files changed, 2 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 596e96bb5..df62305f5 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -866,6 +866,7 @@ class FreqtradeBot(LoggingMixin): entry_tag=entry_tag, side=trade_side ) + stake_amount = min(stake_amount, max_stake_amount) stake_amount = self.wallets.validate_stake_amount(pair, stake_amount, min_stake_amount) return enter_limit_requested, stake_amount diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 3ee9d57eb..9053f9cc8 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -743,6 +743,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, (open_rate - (wallet_balance / position)) / (1 - (mm_ratio + taker_fee_rate)) (10 - (2 / 1)) / (1 - (0.01 + 0.0006)) = 8.085708510208207 """ + # TODO: Parametrize this test open_order = limit_order_open[enter_side(is_short)] order = limit_order[enter_side(is_short)] default_conf_usdt['trading_mode'] = trading_mode From 3ee2b7978c4f6ca86fcd64c35698ab510b57e75c Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Thu, 3 Feb 2022 19:48:54 -0600 Subject: [PATCH 14/19] wallets.validate_stake_amount added param max_stake_available --- freqtrade/freqtradebot.py | 8 ++++++-- freqtrade/optimize/backtesting.py | 9 +++++++-- freqtrade/wallets.py | 4 ++-- tests/optimize/test_backtesting.py | 5 +++++ tests/test_wallets.py | 32 +++++++++++++++++++----------- 5 files changed, 40 insertions(+), 18 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index df62305f5..d0db65e95 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -866,8 +866,12 @@ class FreqtradeBot(LoggingMixin): entry_tag=entry_tag, side=trade_side ) - stake_amount = min(stake_amount, max_stake_amount) - stake_amount = self.wallets.validate_stake_amount(pair, stake_amount, min_stake_amount) + stake_amount = self.wallets.validate_stake_amount( + pair=pair, + stake_amount=stake_amount, + min_stake_amount=min_stake_amount, + max_stake_amount=max_stake_amount, + ) return enter_limit_requested, stake_amount diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 701db34eb..56894e13b 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -553,7 +553,7 @@ class Backtesting: propose_rate = min(max(propose_rate, row[LOW_IDX]), row[HIGH_IDX]) min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, propose_rate, -0.05) or 0 - max_stake_amount = self.exchange.get_max_pair_stake_amount(pair, propose_rate, -0.05) or 0 + max_stake_amount = self.exchange.get_max_pair_stake_amount(pair, propose_rate, -0.05) stake_available = self.wallets.get_available_stake_amount() pos_adjust = trade is not None @@ -570,7 +570,12 @@ class Backtesting: max_stake=min(stake_available, max_stake_amount), entry_tag=entry_tag, side=direction) - stake_amount = self.wallets.validate_stake_amount(pair, stake_amount, min_stake_amount) + stake_amount = self.wallets.validate_stake_amount( + pair=pair, + stake_amount=stake_amount, + min_stake_amount=min_stake_amount, + max_stake_amount=max_stake_amount, + ) if not stake_amount: # In case of pos adjust, still return the original trade diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 98a39ea2d..e6939a7d9 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -238,12 +238,12 @@ class Wallets: return self._check_available_stake_amount(stake_amount, available_amount) - def validate_stake_amount(self, pair, stake_amount, min_stake_amount): + def validate_stake_amount(self, pair, stake_amount, min_stake_amount, max_stake_amount): if not stake_amount: logger.debug(f"Stake amount is {stake_amount}, ignoring possible trade for {pair}.") return 0 - max_stake_amount = self.get_available_stake_amount() + max_stake_amount = min(max_stake_amount, self.get_available_stake_amount()) if min_stake_amount > max_stake_amount: if self._log: diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index acd1fe3b4..ca1d20cfb 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -548,6 +548,11 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: assert trade.stake_amount == 495 assert trade.is_short is True + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=300.0) + trade = backtesting._enter_trade(pair, row=row, direction='long') + assert trade + assert trade.stake_amount == 300.0 + # Stake-amount too high! mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=600.0) diff --git a/tests/test_wallets.py b/tests/test_wallets.py index 3e02cdb09..369197f6b 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -180,23 +180,31 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_r assert result == 0 -@pytest.mark.parametrize('stake_amount,min_stake_amount,max_stake_amount,expected', [ - (22, 11, 50, 22), - (100, 11, 500, 100), - (1000, 11, 500, 500), # Above max-stake - (20, 15, 10, 0), # Minimum stake > max-stake - (9, 11, 100, 11), # Below min stake - (1, 15, 10, 0), # Below min stake and min_stake > max_stake - (20, 50, 100, 0), # Below min stake and stake * 1.3 > min_stake +@pytest.mark.parametrize('stake_amount,min_stake,stake_available,max_stake,expected', [ + (22, 11, 50, 10000, 22), + (100, 11, 500, 10000, 100), + (1000, 11, 500, 10000, 500), # Above stake_available + (700, 11, 1000, 400, 400), # Above max_stake, below stake available + (20, 15, 10, 10000, 0), # Minimum stake > stake_available + (9, 11, 100, 10000, 11), # Below min stake + (1, 15, 10, 10000, 0), # Below min stake and min_stake > stake_available + (20, 50, 100, 10000, 0), # Below min stake and stake * 1.3 > min_stake ]) -def test_validate_stake_amount(mocker, default_conf, - stake_amount, min_stake_amount, max_stake_amount, expected): +def test_validate_stake_amount( + mocker, + default_conf, + stake_amount, + min_stake, + stake_available, + max_stake, + expected, +): freqtrade = get_patched_freqtradebot(mocker, default_conf) mocker.patch("freqtrade.wallets.Wallets.get_available_stake_amount", - return_value=max_stake_amount) - res = freqtrade.wallets.validate_stake_amount('XRP/USDT', stake_amount, min_stake_amount) + return_value=stake_available) + res = freqtrade.wallets.validate_stake_amount('XRP/USDT', stake_amount, min_stake, max_stake) assert res == expected From 6afad6c99fdb48d21929d6cc5c97fb47c6a9e430 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 4 Feb 2022 07:20:27 +0100 Subject: [PATCH 15/19] Small change to todo comment --- tests/exchange/test_exchange.py | 5 ++--- tests/test_freqtradebot.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index ad24efc35..7be9a2207 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -647,7 +647,7 @@ def test_reload_markets_exception(default_conf, mocker, caplog): assert log_has_re(r"Could not reload markets.*", caplog) -@ pytest.mark.parametrize("stake_currency", ['ETH', 'BTC', 'USDT']) +@pytest.mark.parametrize("stake_currency", ['ETH', 'BTC', 'USDT']) def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog): default_conf['stake_currency'] = stake_currency api_mock = MagicMock() @@ -1271,8 +1271,7 @@ def test_buy_prod(default_conf, mocker, exchange_name): side="buy", amount=1, rate=200, - time_in_force=time_in_force - ) + time_in_force=time_in_force) assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' assert api_mock.create_order.call_args[0][1] == order_type assert api_mock.create_order.call_args[0][2] == 'buy' diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 9053f9cc8..d26c1e65a 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -743,7 +743,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, (open_rate - (wallet_balance / position)) / (1 - (mm_ratio + taker_fee_rate)) (10 - (2 / 1)) / (1 - (0.01 + 0.0006)) = 8.085708510208207 """ - # TODO: Parametrize this test + # TODO: Split this test into multiple tests to improve readability open_order = limit_order_open[enter_side(is_short)] order = limit_order[enter_side(is_short)] default_conf_usdt['trading_mode'] = trading_mode From 1824ee6b73bae9e0dacf672b4a67381d87dc6f25 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 4 Feb 2022 04:41:41 -0600 Subject: [PATCH 16/19] removed TODO --- freqtrade/freqtradebot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d0db65e95..e6e1ae5e1 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -830,7 +830,6 @@ class FreqtradeBot(LoggingMixin): entry_tag: Optional[str], trade: Optional[Trade] ) -> Tuple[float, float]: - # TODO: This method has no tests if price: enter_limit_requested = price From c0a593280eb3e425d1d8cd66c2d4f532867bc390 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 4 Feb 2022 04:54:16 -0600 Subject: [PATCH 17/19] test_exchange.test_cancel_order_dry_run pass leverage to create_order --- tests/exchange/test_exchange.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index fda66b32e..4e8a4fe6a 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2558,6 +2558,7 @@ def test_cancel_order_dry_run(default_conf, mocker, exchange_name): amount=5, rate=0.55, time_in_force='gtc', + leverage=1.0, ) cancel_order = exchange.cancel_order(order_id=order['id'], pair='ETH/BTC') From 8b5782767630a446cbb54eeb8b4835ce3180904b Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 4 Feb 2022 14:26:15 -0600 Subject: [PATCH 18/19] exchange.get_max_pair_stake_amount hard set leverage to 0 --- freqtrade/exchange/exchange.py | 3 +-- freqtrade/freqtradebot.py | 7 ++----- freqtrade/optimize/backtesting.py | 4 ++-- tests/exchange/test_exchange.py | 34 +++++++++++++++---------------- 4 files changed, 22 insertions(+), 26 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 7a89ac9a6..490792627 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -690,9 +690,8 @@ class Exchange: self, pair: str, price: float, - stoploss: float ) -> float: - max_stake_amount = self._get_stake_amount_limit(pair, price, stoploss, 'max') + max_stake_amount = self._get_stake_amount_limit(pair, price, 0.0, 'max') if max_stake_amount is None: # * Should never be executed raise OperationalException(f'{self.name}.get_max_pair_stake_amount should' diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ce76bcdaf..f5e9883ad 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -543,9 +543,7 @@ class FreqtradeBot(LoggingMixin): min_stake_amount = self.exchange.get_min_pair_stake_amount(trade.pair, current_rate, self.strategy.stoploss) - max_stake_amount = self.exchange.get_max_pair_stake_amount(trade.pair, - current_rate, - self.strategy.stoploss) + max_stake_amount = self.exchange.get_max_pair_stake_amount(trade.pair, current_rate) stake_available = self.wallets.get_available_stake_amount() logger.debug(f"Calling adjust_trade_position for pair {trade.pair}") stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, @@ -852,8 +850,7 @@ class FreqtradeBot(LoggingMixin): # edge-case for now. min_stake_amount = self.exchange.get_min_pair_stake_amount( pair, enter_limit_requested, self.strategy.stoploss) - max_stake_amount = self.exchange.get_max_pair_stake_amount( - pair, enter_limit_requested, self.strategy.stoploss) + max_stake_amount = self.exchange.get_max_pair_stake_amount(pair, enter_limit_requested) if not self.edge and trade is None: stake_available = self.wallets.get_available_stake_amount() diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 872f05f26..1bb64155e 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -417,7 +417,7 @@ class Backtesting: # TODO: Write tests current_profit = trade.calc_profit_ratio(row[OPEN_IDX]) min_stake = self.exchange.get_min_pair_stake_amount(trade.pair, row[OPEN_IDX], -0.1) - max_stake = self.exchange.get_max_pair_stake_amount(trade.pair, row[OPEN_IDX], -0.1) + max_stake = self.exchange.get_max_pair_stake_amount(trade.pair, row[OPEN_IDX]) stake_available = self.wallets.get_available_stake_amount() stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, default_retval=None)( @@ -557,7 +557,7 @@ class Backtesting: propose_rate = min(max(propose_rate, row[LOW_IDX]), row[HIGH_IDX]) min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, propose_rate, -0.05) or 0 - max_stake_amount = self.exchange.get_max_pair_stake_amount(pair, propose_rate, -0.05) + max_stake_amount = self.exchange.get_max_pair_stake_amount(pair, propose_rate) stake_available = self.wallets.get_available_stake_amount() pos_adjust = trade is not None diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 4e8a4fe6a..35ca9a9c3 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -367,7 +367,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: ) result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss) assert result is None - result = exchange.get_max_pair_stake_amount('ETH/BTC', 1, stoploss) + result = exchange.get_max_pair_stake_amount('ETH/BTC', 1) assert result == float('inf') # min/max cost is set @@ -387,7 +387,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss, 3.0) assert isclose(result, expected_result/3) # max - result = exchange.get_max_pair_stake_amount('ETH/BTC', 2, stoploss) + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) assert result == 10000 # min amount is set @@ -406,7 +406,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 5.0) assert isclose(result, expected_result/5) # max - result = exchange.get_max_pair_stake_amount('ETH/BTC', 2, stoploss) + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) assert result == 20000 # min amount and cost are set (cost is minimal) @@ -441,7 +441,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 7.0) assert isclose(result, expected_result/7.0) # Max - result = exchange.get_max_pair_stake_amount('ETH/BTC', 2, stoploss) + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) assert result == 1000 result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4) @@ -451,7 +451,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4, 8.0) assert isclose(result, expected_result/8.0) # Max - result = exchange.get_max_pair_stake_amount('ETH/BTC', 2, stoploss) + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) assert result == 1000 # Really big stoploss @@ -462,7 +462,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0) assert isclose(result, expected_result/12) # Max - result = exchange.get_max_pair_stake_amount('ETH/BTC', 2, stoploss) + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) assert result == 1000 markets["ETH/BTC"]["contractSize"] = '0.01' @@ -478,7 +478,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1) assert isclose(result, expected_result * 0.01) # Max - result = exchange.get_max_pair_stake_amount('ETH/BTC', 2, -1) + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) assert result == 10 markets["ETH/BTC"]["contractSize"] = '10' @@ -490,7 +490,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0) assert isclose(result, (expected_result/12) * 10.0) # Max - result = exchange.get_max_pair_stake_amount('ETH/BTC', 2, -1) + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) assert result == 10000 @@ -512,7 +512,7 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: expected_result = max(0.0001, 0.001 * 0.020405) * (1+0.05) / (1-abs(stoploss)) assert round(result, 8) == round(expected_result, 8) # Max - result = exchange.get_max_pair_stake_amount('ETH/BTC', 2.0, stoploss) + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2.0) assert result == 4000 # Leverage @@ -525,7 +525,7 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: assert round(result, 8) == round((expected_result/3), 8) # Max - result = exchange.get_max_pair_stake_amount('ETH/BTC', 12.0, stoploss) + result = exchange.get_max_pair_stake_amount('ETH/BTC', 12.0) assert result == 4000 @@ -4196,14 +4196,14 @@ def test_get_max_pair_stake_amount( } mocker.patch('freqtrade.exchange.Exchange.markets', markets) - assert exchange.get_max_pair_stake_amount('XRP/USDT:USDT', 2.0, 0.0) == 20000 - assert exchange.get_max_pair_stake_amount('LTC/USDT:USDT', 2.0, 0.0) == float('inf') - assert exchange.get_max_pair_stake_amount('ETH/USDT:USDT', 2.0, 0.0) == 200 - assert exchange.get_max_pair_stake_amount('DOGE/USDT:USDT', 2.0, 0.0) == 500 - assert exchange.get_max_pair_stake_amount('LUNA/USDT:USDT', 2.0, 0.0) == 5.0 + assert exchange.get_max_pair_stake_amount('XRP/USDT:USDT', 2.0) == 20000 + assert exchange.get_max_pair_stake_amount('LTC/USDT:USDT', 2.0) == float('inf') + assert exchange.get_max_pair_stake_amount('ETH/USDT:USDT', 2.0) == 200 + assert exchange.get_max_pair_stake_amount('DOGE/USDT:USDT', 2.0) == 500 + assert exchange.get_max_pair_stake_amount('LUNA/USDT:USDT', 2.0) == 5.0 default_conf['trading_mode'] = 'spot' exchange = get_patched_exchange(mocker, default_conf, api_mock) mocker.patch('freqtrade.exchange.Exchange.markets', markets) - assert exchange.get_max_pair_stake_amount('BTC/USDT', 2.0, 0.0) == 20000 - assert exchange.get_max_pair_stake_amount('ADA/USDT', 2.0, 0.0) == 500 + assert exchange.get_max_pair_stake_amount('BTC/USDT', 2.0) == 20000 + assert exchange.get_max_pair_stake_amount('ADA/USDT', 2.0) == 500 From e0d42ad9a75b8cba4e80cad43ca84751609b300e Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 5 Feb 2022 18:29:48 -0600 Subject: [PATCH 19/19] Update backtesting.py --- freqtrade/optimize/backtesting.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 1bb64155e..70dc1a3ab 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -414,7 +414,6 @@ class Backtesting: def _get_adjust_trade_entry_for_candle(self, trade: LocalTrade, row: Tuple ) -> LocalTrade: - # TODO: Write tests current_profit = trade.calc_profit_ratio(row[OPEN_IDX]) min_stake = self.exchange.get_min_pair_stake_amount(trade.pair, row[OPEN_IDX], -0.1) max_stake = self.exchange.get_max_pair_stake_amount(trade.pair, row[OPEN_IDX])