Merge pull request #6316 from samgermain/max-amount

exchange.get_max_pair_stake_amount
This commit is contained in:
Matthias 2022-02-06 07:44:29 +01:00 committed by GitHub
commit 412fe65344
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 321 additions and 86 deletions

View File

@ -9,7 +9,7 @@ import logging
from copy import deepcopy from copy import deepcopy
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from math import ceil 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 arrow
import ccxt import ccxt
@ -677,33 +677,63 @@ class Exchange:
else: else:
return 1 / pow(10, precision) return 1 / pow(10, precision)
def get_min_pair_stake_amount(self, pair: str, price: float, stoploss: float, def get_min_pair_stake_amount(
leverage: Optional[float] = 1.0) -> Optional[float]: 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: try:
market = self.markets[pair] market = self.markets[pair]
except KeyError: except KeyError:
raise ValueError(f"Can't get market information for symbol {pair}") raise ValueError(f"Can't get market information for symbol {pair}")
min_stake_amounts = [] stake_limits = []
limits = market['limits'] limits = market['limits']
if (limits['cost']['min'] is not None): if (limits['cost'][limit] is not None):
min_stake_amounts.append( stake_limits.append(
self._contracts_to_amount( self._contracts_to_amount(
pair, pair,
limits['cost']['min'] limits['cost'][limit]
) )
) )
if (limits['amount']['min'] is not None): if (limits['amount'][limit] is not None):
min_stake_amounts.append( stake_limits.append(
self._contracts_to_amount( self._contracts_to_amount(
pair, pair,
limits['amount']['min'] * price limits['amount'][limit] * price
) )
) )
if not min_stake_amounts: if not stake_limits:
return None return None if isMin else float('inf')
# reserve some percent defined in config (5% default) + stoploss # reserve some percent defined in config (5% default) + stoploss
amount_reserve_percent = 1.0 + self._config.get('amount_reserve_percent', 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. # for cost (quote, stake currency), so max() is used here.
# See also #2575 at github. # See also #2575 at github.
return self._get_stake_amount_considering_leverage( return self._get_stake_amount_considering_leverage(
max(min_stake_amounts) * amount_reserve_percent, max(stake_limits) * amount_reserve_percent,
leverage or 1.0 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): def _get_stake_amount_considering_leverage(self, stake_amount: float, leverage: float):
""" """
@ -904,6 +934,7 @@ class Exchange:
def create_order( def create_order(
self, self,
*,
pair: str, pair: str,
ordertype: str, ordertype: str,
side: str, side: str,
@ -935,7 +966,7 @@ class Exchange:
side, side,
amount, amount,
rate_for_order, rate_for_order,
params params,
) )
self._log_exchange_response('create_order', order) self._log_exchange_response('create_order', order)
order = self._order_contracts_to_amount(order) order = self._order_contracts_to_amount(order)

View File

@ -543,12 +543,14 @@ class FreqtradeBot(LoggingMixin):
min_stake_amount = self.exchange.get_min_pair_stake_amount(trade.pair, min_stake_amount = self.exchange.get_min_pair_stake_amount(trade.pair,
current_rate, current_rate,
self.strategy.stoploss) 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}") logger.debug(f"Calling adjust_trade_position for pair {trade.pair}")
stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position,
default_retval=None)( default_retval=None)(
trade=trade, current_time=datetime.now(timezone.utc), current_rate=current_rate, 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: if stake_amount is not None and stake_amount > 0.0:
# We should increase our position # We should increase our position
@ -821,10 +823,12 @@ class FreqtradeBot(LoggingMixin):
return trade return trade
def get_valid_enter_price_and_stake( def get_valid_enter_price_and_stake(
self, pair: str, price: Optional[float], stake_amount: float, self, pair: str, price: Optional[float], stake_amount: float,
side: str, trade_side: str, side: str, trade_side: str,
entry_tag: Optional[str], entry_tag: Optional[str],
trade: Optional[Trade]) -> Tuple[float, float]: trade: Optional[Trade]
) -> Tuple[float, float]:
if price: if price:
enter_limit_requested = price enter_limit_requested = price
else: else:
@ -845,19 +849,25 @@ class FreqtradeBot(LoggingMixin):
# We do however also need min-stake to determine leverage, therefore this is ignored as # We do however also need min-stake to determine leverage, therefore this is ignored as
# edge-case for now. # edge-case for now.
min_stake_amount = self.exchange.get_min_pair_stake_amount( 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: 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, stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount,
default_retval=stake_amount)( default_retval=stake_amount)(
pair=pair, current_time=datetime.now(timezone.utc), pair=pair, current_time=datetime.now(timezone.utc),
current_rate=enter_limit_requested, proposed_stake=stake_amount, 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 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 return enter_limit_requested, stake_amount

View File

@ -414,14 +414,15 @@ class Backtesting:
def _get_adjust_trade_entry_for_candle(self, trade: LocalTrade, row: Tuple def _get_adjust_trade_entry_for_candle(self, trade: LocalTrade, row: Tuple
) -> LocalTrade: ) -> LocalTrade:
current_profit = trade.calc_profit_ratio(row[OPEN_IDX]) 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) 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, stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position,
default_retval=None)( default_retval=None)(
trade=trade, current_time=row[DATE_IDX].to_pydatetime(), current_rate=row[OPEN_IDX], 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 # Check if we should increase our position
if stake_amount is not None and stake_amount > 0.0: 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]) 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 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 pos_adjust = trade is not None
if not pos_adjust: if not pos_adjust:
@ -567,10 +569,16 @@ class Backtesting:
stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount, stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount,
default_retval=stake_amount)( default_retval=stake_amount)(
pair=pair, current_time=current_time, current_rate=propose_rate, 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) 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: if not stake_amount:
# In case of pos adjust, still return the original trade # In case of pos adjust, still return the original trade

View File

@ -238,12 +238,12 @@ class Wallets:
return self._check_available_stake_amount(stake_amount, available_amount) 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: if not stake_amount:
logger.debug(f"Stake amount is {stake_amount}, ignoring possible trade for {pair}.") logger.debug(f"Stake amount is {stake_amount}, ignoring possible trade for {pair}.")
return 0 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 min_stake_amount > max_stake_amount:
if self._log: if self._log:

View File

@ -587,7 +587,7 @@ def get_markets():
'limits': { 'limits': {
'amount': { 'amount': {
'min': 0.01, 'min': 0.01,
'max': 1000, 'max': 100000000,
}, },
'price': { 'price': {
'min': None, 'min': None,
@ -622,7 +622,7 @@ def get_markets():
'limits': { 'limits': {
'amount': { 'amount': {
'min': 0.01, 'min': 0.01,
'max': 1000, 'max': 100000000,
}, },
'price': { 'price': {
'min': None, 'min': None,
@ -690,7 +690,7 @@ def get_markets():
'limits': { 'limits': {
'amount': { 'amount': {
'min': 0.01, 'min': 0.01,
'max': 1000, 'max': 100000000,
}, },
'price': { 'price': {
'min': None, 'min': None,
@ -725,7 +725,7 @@ def get_markets():
'limits': { 'limits': {
'amount': { 'amount': {
'min': 0.01, 'min': 0.01,
'max': 1000, 'max': 100000000,
}, },
'price': { 'price': {
'min': None, 'min': None,
@ -760,7 +760,7 @@ def get_markets():
'limits': { 'limits': {
'amount': { 'amount': {
'min': 0.01, 'min': 0.01,
'max': 1000, 'max': 100000000,
}, },
'price': { 'price': {
'min': None, 'min': None,
@ -988,7 +988,7 @@ def get_markets():
'limits': { 'limits': {
'amount': { 'amount': {
'min': 0.01, 'min': 0.01,
'max': 1000, 'max': 100000000000,
}, },
'price': { 'price': {
'min': None, 'min': None,

View File

@ -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 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") exchange = get_patched_exchange(mocker, default_conf, id="binance")
stoploss = -0.05 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.*'): with pytest.raises(ValueError, match=r'.*get market information.*'):
exchange.get_min_pair_stake_amount('BNB/BTC', 1, stoploss) exchange.get_min_pair_stake_amount('BNB/BTC', 1, stoploss)
# no cost Min # no cost/amount Min
markets["ETH/BTC"]["limits"] = { markets["ETH/BTC"]["limits"] = {
'cost': {'min': None, 'max': None}, 'cost': {'min': None, 'max': None},
'amount': {'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) result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss)
assert result is None 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"] = { markets["ETH/BTC"]["limits"] = {
'cost': {'min': None, 'max': None}, 'cost': {'min': 2, 'max': 10000},
'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},
'amount': {'min': None, 'max': None}, 'amount': {'min': None, 'max': None},
} }
mocker.patch( mocker.patch(
'freqtrade.exchange.Exchange.markets', 'freqtrade.exchange.Exchange.markets',
PropertyMock(return_value=markets) PropertyMock(return_value=markets)
) )
# min
result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss) result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss)
expected_result = 2 * (1+0.05) / (1-abs(stoploss)) expected_result = 2 * (1+0.05) / (1-abs(stoploss))
assert isclose(result, expected_result) assert isclose(result, expected_result)
# With Leverage # With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss, 3.0) result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss, 3.0)
assert isclose(result, expected_result/3) assert isclose(result, expected_result/3)
# max
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
assert result == 10000
# min amount is set # min amount is set
markets["ETH/BTC"]["limits"] = { markets["ETH/BTC"]["limits"] = {
'cost': {'min': None, 'max': None}, 'cost': {'min': None, 'max': None},
'amount': {'min': 2, 'max': None}, 'amount': {'min': 2, 'max': 10000},
} }
mocker.patch( mocker.patch(
'freqtrade.exchange.Exchange.markets', 'freqtrade.exchange.Exchange.markets',
@ -423,6 +405,9 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
# With Leverage # With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 5.0) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 5.0)
assert isclose(result, expected_result/5) 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) # min amount and cost are set (cost is minimal)
markets["ETH/BTC"]["limits"] = { 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) # min amount and cost are set (amount is minial)
markets["ETH/BTC"]["limits"] = { markets["ETH/BTC"]["limits"] = {
'cost': {'min': 8, 'max': None}, 'cost': {'min': 8, 'max': 10000},
'amount': {'min': 2, 'max': None}, 'amount': {'min': 2, 'max': 500},
} }
mocker.patch( mocker.patch(
'freqtrade.exchange.Exchange.markets', 'freqtrade.exchange.Exchange.markets',
@ -455,6 +440,9 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
# With Leverage # With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 7.0) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 7.0)
assert isclose(result, expected_result/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) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4)
expected_result = max(8, 2 * 2) * 1.5 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 # With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4, 8.0) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4, 8.0)
assert isclose(result, expected_result/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 # Really big stoploss
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1) 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 # With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0)
assert isclose(result, expected_result/12) 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' markets["ETH/BTC"]["contractSize"] = '0.01'
default_conf['trading_mode'] = 'futures' default_conf['trading_mode'] = 'futures'
@ -483,6 +477,9 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
# Contract size 0.01 # Contract size 0.01
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1)
assert isclose(result, expected_result * 0.01) 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' markets["ETH/BTC"]["contractSize"] = '10'
mocker.patch( mocker.patch(
@ -492,6 +489,9 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
# With Leverage, Contract size 10 # With Leverage, Contract size 10
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0)
assert isclose(result, (expected_result/12) * 10.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: 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 stoploss = -0.05
markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}} markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}}
# Real Binance data # ~Real Binance data
markets["ETH/BTC"]["limits"] = { markets["ETH/BTC"]["limits"] = {
'cost': {'min': 0.0001}, 'cost': {'min': 0.0001, 'max': 4000},
'amount': {'min': 0.001} 'amount': {'min': 0.001, 'max': 10000},
} }
mocker.patch( mocker.patch(
'freqtrade.exchange.Exchange.markets', '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) 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)) expected_result = max(0.0001, 0.001 * 0.020405) * (1+0.05) / (1-abs(stoploss))
assert round(result, 8) == round(expected_result, 8) 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) result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss, 3.0)
assert round(result, 8) == round(expected_result/3, 8) 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): 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_order(order_id='123', pair='TKN/BTC') == {}
assert exchange.cancel_stoploss_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') cancel_order = exchange.cancel_order(order_id=order['id'], pair='ETH/BTC')
assert order['id'] == cancel_order['id'] assert order['id'] == cancel_order['id']
@ -4062,3 +4084,126 @@ def test_liquidation_price(
upnl_ex_1=upnl_ex_1, upnl_ex_1=upnl_ex_1,
position=position, position=position,
), 2), expected) ), 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

View File

@ -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_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_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) mocker.patch("freqtrade.exchange.Binance.get_max_leverage", return_value=100)
patch_exchange(mocker) patch_exchange(mocker)
frame = _build_backtest_dataframe(data.data) frame = _build_backtest_dataframe(data.data)

View File

@ -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: def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
# TODO-lev: test max_pair_stake_amount
default_conf['use_sell_signal'] = False default_conf['use_sell_signal'] = False
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) 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_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) patch_exchange(mocker)
default_conf['stake_amount'] = 'unlimited' default_conf['stake_amount'] = 'unlimited'
default_conf['max_open_trades'] = 2 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.stake_amount == 495
assert trade.is_short is True 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! # Stake-amount too high!
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=600.0) 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 default_conf['use_sell_signal'] = False
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) 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_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) patch_exchange(mocker)
default_conf['timeframe_detail'] = '1m' default_conf['timeframe_detail'] = '1m'
default_conf['max_open_trades'] = 2 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 default_conf['use_sell_signal'] = False
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) 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_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) patch_exchange(mocker)
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0]) 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 default_conf['use_sell_signal'] = False
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) 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_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) patch_exchange(mocker)
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0]) 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 default_conf['enable_protections'] = True
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) 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_min_pair_stake_amount", return_value=0.00001)
mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf'))
tests = [ tests = [
['sine', 9], ['sine', 9],
['raise', 10], ['raise', 10],
@ -806,6 +817,7 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir,
default_conf['enable_protections'] = True 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_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) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
# While buy-signals are unrealistic, running backtesting # While buy-signals are unrealistic, running backtesting
# over and over again should not cause different results # 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): 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_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) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, backtest_conf = _make_backtest_conf(mocker, conf=default_conf,
pair='UNITTEST/BTC', datadir=testdatadir) pair='UNITTEST/BTC', datadir=testdatadir)
@ -892,6 +905,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
return dataframe return dataframe
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) 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) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
patch_exchange(mocker) patch_exchange(mocker)

View File

@ -17,6 +17,7 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) ->
default_conf['use_sell_signal'] = False default_conf['use_sell_signal'] = False
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) 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_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) patch_exchange(mocker)
default_conf.update({ default_conf.update({
"stake_amount": 100.0, "stake_amount": 100.0,

View File

@ -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)) (open_rate - (wallet_balance / position)) / (1 - (mm_ratio + taker_fee_rate))
(10 - (2 / 1)) / (1 - (0.01 + 0.0006)) = 8.085708510208207 (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)] open_order = limit_order_open[enter_side(is_short)]
order = limit_order[enter_side(is_short)] order = limit_order[enter_side(is_short)]
default_conf_usdt['trading_mode'] = trading_mode 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.open_rate_requested == 10
assert trade.isolated_liq == liq_price 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]) @pytest.mark.parametrize("is_short", [False, True])
def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_order, is_short) -> None: def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_order, is_short) -> None:

View File

@ -180,23 +180,31 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_r
assert result == 0 assert result == 0
@pytest.mark.parametrize('stake_amount,min_stake_amount,max_stake_amount,expected', [ @pytest.mark.parametrize('stake_amount,min_stake,stake_available,max_stake,expected', [
(22, 11, 50, 22), (22, 11, 50, 10000, 22),
(100, 11, 500, 100), (100, 11, 500, 10000, 100),
(1000, 11, 500, 500), # Above max-stake (1000, 11, 500, 10000, 500), # Above stake_available
(20, 15, 10, 0), # Minimum stake > max-stake (700, 11, 1000, 400, 400), # Above max_stake, below stake available
(9, 11, 100, 11), # Below min stake (20, 15, 10, 10000, 0), # Minimum stake > stake_available
(1, 15, 10, 0), # Below min stake and min_stake > max_stake (9, 11, 100, 10000, 11), # Below min stake
(20, 50, 100, 0), # Below min stake and stake * 1.3 > 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, def test_validate_stake_amount(
stake_amount, min_stake_amount, max_stake_amount, expected): mocker,
default_conf,
stake_amount,
min_stake,
stake_available,
max_stake,
expected,
):
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
mocker.patch("freqtrade.wallets.Wallets.get_available_stake_amount", mocker.patch("freqtrade.wallets.Wallets.get_available_stake_amount",
return_value=max_stake_amount) return_value=stake_available)
res = freqtrade.wallets.validate_stake_amount('XRP/USDT', stake_amount, min_stake_amount) res = freqtrade.wallets.validate_stake_amount('XRP/USDT', stake_amount, min_stake, max_stake)
assert res == expected assert res == expected