diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 62f71b4ea..490792627 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,63 @@ 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, + ) -> float: + 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' + 'never set max_stake_amount to None') + return max_stake_amount + + 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 +748,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): """ @@ -904,6 +934,7 @@ class Exchange: def create_order( self, + *, pair: str, ordertype: str, side: str, @@ -935,7 +966,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/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 686737036..f5e9883ad 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -543,12 +543,14 @@ 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) + 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 @@ -821,10 +823,12 @@ 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]: + if price: enter_limit_requested = price else: @@ -845,19 +849,25 @@ 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) 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() 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 ) - 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 08b84f376..70dc1a3ab 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -414,14 +414,15 @@ class Backtesting: def _get_adjust_trade_entry_for_candle(self, trade: LocalTrade, row: Tuple ) -> LocalTrade: - 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]) + 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: @@ -555,7 +556,8 @@ 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) + stake_available = self.wallets.get_available_stake_amount() pos_adjust = trade is not None if not pos_adjust: @@ -567,10 +569,16 @@ 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) + 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/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/exchange/test_exchange.py b/tests/exchange/test_exchange.py index fae8bbece..35ca9a9c3 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + assert result == 4000 + def test_set_sandbox(default_conf, mocker): """ @@ -2537,7 +2551,15 @@ 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', + leverage=1.0, + ) cancel_order = exchange.cancel_order(order_id=order['id'], pair='ETH/BTC') assert order['id'] == cancel_order['id'] @@ -4062,3 +4084,126 @@ def test_liquidation_price( upnl_ex_1=upnl_ex_1, position=position, ), 2), expected) + + +def test_get_max_pair_stake_amount( + mocker, + default_conf, +): + api_mock = MagicMock() + default_conf['margin_mode'] = 'isolated' + default_conf['trading_mode'] = 'futures' + exchange = get_patched_exchange(mocker, default_conf, api_mock) + markets = { + 'XRP/USDT:USDT': { + 'limits': { + 'amount': { + 'min': 0.001, + 'max': 10000 + }, + 'cost': { + 'min': 5, + 'max': None + }, + }, + 'contractSize': None, + 'spot': False, + }, + 'LTC/USDT:USDT': { + 'limits': { + 'amount': { + 'min': 0.001, + 'max': None + }, + 'cost': { + 'min': 5, + 'max': None + }, + }, + 'contractSize': 0.01, + 'spot': False, + }, + 'ETH/USDT:USDT': { + 'limits': { + 'amount': { + 'min': 0.001, + 'max': 10000 + }, + 'cost': { + 'min': 5, + 'max': 30000, + }, + }, + 'contractSize': 0.01, + 'spot': False, + }, + 'BTC/USDT': { + 'limits': { + 'amount': { + 'min': 0.001, + 'max': 10000 + }, + 'cost': { + 'min': 5, + 'max': None + }, + }, + 'contractSize': 0.01, + 'spot': True, + }, + '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_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) == 20000 + assert exchange.get_max_pair_stake_amount('ADA/USDT', 2.0) == 500 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..ca1d20cfb 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -497,9 +497,11 @@ 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) + 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 @@ -546,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) @@ -566,6 +573,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 +667,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 +733,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 +782,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 +817,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 +858,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 +905,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, diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 91e941e2c..ef443a27f 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: 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 @@ -932,6 +933,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: 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