Merge pull request #4581 from freqtrade/fix/4578
Fix calculation error for min-trade-stake
This commit is contained in:
commit
0a9622a065
@ -156,6 +156,23 @@ Values set in the configuration file always overwrite values set in the strategy
|
|||||||
|
|
||||||
There are several methods to configure how much of the stake currency the bot will use to enter a trade. All methods respect the [available balance configuration](#available-balance) as explained below.
|
There are several methods to configure how much of the stake currency the bot will use to enter a trade. All methods respect the [available balance configuration](#available-balance) as explained below.
|
||||||
|
|
||||||
|
#### Minimum trade stake
|
||||||
|
|
||||||
|
The minimum stake amount will depend by exchange and pair, and is usually listed in the exchange support pages.
|
||||||
|
Assuming the minimum tradable amount for XRP/USD is 20 XRP (given by the exchange), and the price is 0.4$.
|
||||||
|
|
||||||
|
The minimum stake amount to buy this pair is therefore `20 * 0.6 ~= 12`.
|
||||||
|
This exchange has also a limit on USD - where all orders must be > 10$ - which however does not apply in this case.
|
||||||
|
|
||||||
|
To guarantee safe execution, freqtrade will not allow buying with a stake-amount of 10.1$, instead, it'll make sure that there's enough space to place a stoploss below the pair (+ an offset, defined by `amount_reserve_percent`, which defaults to 5%).
|
||||||
|
|
||||||
|
With a stoploss of 10% - we'd therefore end up with a value of ~13.8$ (`12 * (1 + 0.05 + 0.1)`).
|
||||||
|
|
||||||
|
To limit this calculation in case of large stoploss values, the calculated minimum stake-limit will never be more than 50% above the real limit.
|
||||||
|
|
||||||
|
!!! Warning
|
||||||
|
Since the limits on exchanges are usually stable and are not updated often, some pairs can show pretty high minimum limits, simply because the price increased a lot since the last limit adjustment by the exchange.
|
||||||
|
|
||||||
#### Available balance
|
#### Available balance
|
||||||
|
|
||||||
By default, the bot assumes that the `complete amount - 1%` is at it's disposal, and when using [dynamic stake amount](#dynamic-stake-amount), it will split the complete balance into `max_open_trades` buckets per trade.
|
By default, the bot assumes that the `complete amount - 1%` is at it's disposal, and when using [dynamic stake amount](#dynamic-stake-amount), it will split the complete balance into `max_open_trades` buckets per trade.
|
||||||
|
@ -531,16 +531,16 @@ class Exchange:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# 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',
|
||||||
DEFAULT_AMOUNT_RESERVE_PERCENT)
|
DEFAULT_AMOUNT_RESERVE_PERCENT)
|
||||||
amount_reserve_percent += stoploss
|
amount_reserve_percent += abs(stoploss)
|
||||||
# it should not be more than 50%
|
# it should not be more than 50%
|
||||||
amount_reserve_percent = max(amount_reserve_percent, 0.5)
|
amount_reserve_percent = max(min(amount_reserve_percent, 1.5), 1)
|
||||||
|
|
||||||
# The value returned should satisfy both limits: for amount (base currency) and
|
# The value returned should satisfy both limits: for amount (base currency) and
|
||||||
# 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 max(min_stake_amounts) / amount_reserve_percent
|
return max(min_stake_amounts) * amount_reserve_percent
|
||||||
|
|
||||||
def dry_run_order(self, pair: str, ordertype: str, side: str, amount: float,
|
def dry_run_order(self, pair: str, ordertype: str, side: str, amount: float,
|
||||||
rate: float, params: Dict = {}) -> Dict[str, Any]:
|
rate: float, params: Dict = {}) -> Dict[str, Any]:
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
from math import isclose
|
||||||
from random import randint
|
from random import randint
|
||||||
from unittest.mock import MagicMock, Mock, PropertyMock, patch
|
from unittest.mock import MagicMock, Mock, PropertyMock, patch
|
||||||
|
|
||||||
@ -370,7 +371,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
|
|||||||
PropertyMock(return_value=markets)
|
PropertyMock(return_value=markets)
|
||||||
)
|
)
|
||||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss)
|
result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss)
|
||||||
assert result == 2 / 0.9
|
assert isclose(result, 2 * 1.1)
|
||||||
|
|
||||||
# min amount is set
|
# min amount is set
|
||||||
markets["ETH/BTC"]["limits"] = {
|
markets["ETH/BTC"]["limits"] = {
|
||||||
@ -382,7 +383,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
|
|||||||
PropertyMock(return_value=markets)
|
PropertyMock(return_value=markets)
|
||||||
)
|
)
|
||||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
|
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
|
||||||
assert result == 2 * 2 / 0.9
|
assert isclose(result, 2 * 2 * 1.1)
|
||||||
|
|
||||||
# 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"] = {
|
||||||
@ -394,7 +395,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
|
|||||||
PropertyMock(return_value=markets)
|
PropertyMock(return_value=markets)
|
||||||
)
|
)
|
||||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
|
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
|
||||||
assert result == max(2, 2 * 2) / 0.9
|
assert isclose(result, max(2, 2 * 2) * 1.1)
|
||||||
|
|
||||||
# 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"] = {
|
||||||
@ -406,7 +407,14 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
|
|||||||
PropertyMock(return_value=markets)
|
PropertyMock(return_value=markets)
|
||||||
)
|
)
|
||||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
|
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
|
||||||
assert result == max(8, 2 * 2) / 0.9
|
assert isclose(result, max(8, 2 * 2) * 1.1)
|
||||||
|
|
||||||
|
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4)
|
||||||
|
assert isclose(result, max(8, 2 * 2) * 1.45)
|
||||||
|
|
||||||
|
# Really big stoploss
|
||||||
|
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1)
|
||||||
|
assert isclose(result, max(8, 2 * 2) * 1.5)
|
||||||
|
|
||||||
|
|
||||||
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:
|
||||||
@ -424,7 +432,7 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None:
|
|||||||
PropertyMock(return_value=markets)
|
PropertyMock(return_value=markets)
|
||||||
)
|
)
|
||||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss)
|
result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss)
|
||||||
assert round(result, 8) == round(max(0.0001, 0.001 * 0.020405) / 0.9, 8)
|
assert round(result, 8) == round(max(0.0001, 0.001 * 0.020405) * 1.1, 8)
|
||||||
|
|
||||||
|
|
||||||
def test_set_sandbox(default_conf, mocker):
|
def test_set_sandbox(default_conf, mocker):
|
||||||
|
Loading…
Reference in New Issue
Block a user