Merge pull request #2734 from freqtrade/relative_stake
Relative stake maximum tradable amount
This commit is contained in:
commit
90a9052377
@ -2,6 +2,7 @@
|
||||
"max_open_trades": 3,
|
||||
"stake_currency": "BTC",
|
||||
"stake_amount": 0.05,
|
||||
"tradable_balance_ratio": 0.99,
|
||||
"fiat_display_currency": "USD",
|
||||
"ticker_interval" : "5m",
|
||||
"dry_run": false,
|
||||
@ -59,7 +60,6 @@
|
||||
"enabled": false,
|
||||
"process_throttle_secs": 3600,
|
||||
"calculate_since_number_of_days": 7,
|
||||
"capital_available_percentage": 0.5,
|
||||
"allowed_risk": 0.01,
|
||||
"stoploss_range_min": -0.01,
|
||||
"stoploss_range_max": -0.1,
|
||||
|
@ -2,6 +2,7 @@
|
||||
"max_open_trades": 3,
|
||||
"stake_currency": "BTC",
|
||||
"stake_amount": 0.05,
|
||||
"tradable_balance_ratio": 0.99,
|
||||
"fiat_display_currency": "USD",
|
||||
"ticker_interval" : "5m",
|
||||
"dry_run": true,
|
||||
@ -64,7 +65,6 @@
|
||||
"enabled": false,
|
||||
"process_throttle_secs": 3600,
|
||||
"calculate_since_number_of_days": 7,
|
||||
"capital_available_percentage": 0.5,
|
||||
"allowed_risk": 0.01,
|
||||
"stoploss_range_min": -0.01,
|
||||
"stoploss_range_max": -0.1,
|
||||
|
@ -2,8 +2,11 @@
|
||||
"max_open_trades": 3,
|
||||
"stake_currency": "BTC",
|
||||
"stake_amount": 0.05,
|
||||
"tradable_balance_ratio": 0.99,
|
||||
"fiat_display_currency": "USD",
|
||||
"amount_reserve_percent" : 0.05,
|
||||
"amend_last_stake_amount": false,
|
||||
"last_stake_amount_min_ratio": 0.5,
|
||||
"dry_run": false,
|
||||
"ticker_interval": "5m",
|
||||
"trailing_stop": false,
|
||||
@ -96,7 +99,6 @@
|
||||
"enabled": false,
|
||||
"process_throttle_secs": 3600,
|
||||
"calculate_since_number_of_days": 7,
|
||||
"capital_available_percentage": 0.5,
|
||||
"allowed_risk": 0.01,
|
||||
"stoploss_range_min": -0.01,
|
||||
"stoploss_range_max": -0.1,
|
||||
|
@ -2,6 +2,7 @@
|
||||
"max_open_trades": 5,
|
||||
"stake_currency": "EUR",
|
||||
"stake_amount": 10,
|
||||
"tradable_balance_ratio": 0.99,
|
||||
"fiat_display_currency": "EUR",
|
||||
"ticker_interval" : "5m",
|
||||
"dry_run": true,
|
||||
@ -70,7 +71,6 @@
|
||||
"enabled": false,
|
||||
"process_throttle_secs": 3600,
|
||||
"calculate_since_number_of_days": 7,
|
||||
"capital_available_percentage": 0.5,
|
||||
"allowed_risk": 0.01,
|
||||
"stoploss_range_min": -0.01,
|
||||
"stoploss_range_max": -0.1,
|
||||
|
@ -40,9 +40,12 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
||||
|
||||
| Parameter | Description |
|
||||
|------------|-------------|
|
||||
| `max_open_trades` | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades).<br> ***Datatype:*** *Positive integer or -1.*
|
||||
| `max_open_trades` | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades). [More information below](#configuring-amount-per-trade).<br> ***Datatype:*** *Positive integer or -1.*
|
||||
| `stake_currency` | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *String*
|
||||
| `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Set it to `"unlimited"` to allow the bot to use all available balance. [More information below](#understand-stake_amount). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *Positive float or `"unlimited"`.*
|
||||
| `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Set it to `"unlimited"` to allow the bot to use all available balance. [More information below](#configuring-amount-per-trade). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *Positive float or `"unlimited"`.*
|
||||
| `tradable_balance_ratio` | Ratio of the total account balance the bot is allowed to trade. [More information below](#configuring-amount-per-trade). <br>*Defaults to `0.99` 99%).*<br> ***Datatype:*** *Positive float between `0.1` and `1.0`.*
|
||||
| `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade). <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
|
||||
| `last_stake_amount_min_ratio` | Defines minimum stake amount that has to be left and executed. Applies only to the last stake amount when it's amended to a reduced value (i.e. if `amend_last_stake_amount` is set to `true`). [More information below](#configuring-amount-per-trade). <br>*Defaults to `0.5`.* <br> ***Datatype:*** *Float (as ratio)*
|
||||
| `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals. <br>*Defaults to `0.05` (5%).* <br> ***Datatype:*** *Positive Float as ratio.*
|
||||
| `ticker_interval` | The ticker interval to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *String*
|
||||
| `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency). <br> ***Datatype:*** *String*
|
||||
@ -129,20 +132,58 @@ Values set in the configuration file always overwrite values set in the strategy
|
||||
* `sell_profit_only` (ask_strategy)
|
||||
* `ignore_roi_if_buy_signal` (ask_strategy)
|
||||
|
||||
### Understand stake_amount
|
||||
### Configuring amount per trade
|
||||
|
||||
The `stake_amount` configuration parameter is an amount of crypto-currency your bot will use for each trade.
|
||||
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.
|
||||
|
||||
The minimal configuration value is 0.0001. Please check your exchange's trading minimums to avoid problems.
|
||||
#### 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.
|
||||
Freqtrade will reserve 1% for eventual fees when entering a trade and will therefore not touch that by default.
|
||||
|
||||
You can configure the "untouched" amount by using the `tradable_balance_ratio` setting.
|
||||
|
||||
For example, if you have 10 ETH available in your wallet on the exchange and `tradable_balance_ratio=0.5` (which is 50%), then the bot will use a maximum amount of 5 ETH for trading and considers this as available balance. The rest of the wallet is untouched by the trades.
|
||||
|
||||
!!! Warning
|
||||
The `tradable_balance_ratio` setting applies to the current balance (free balance + tied up in trades). Therefore, assuming the starting balance of 1000, a configuration with `tradable_balance_ratio=0.99` will not guarantee that 10 currency units will always remain available on the exchange. For example, the free amount may reduce to 5 units if the total balance is reduced to 500 (either by a losing streak, or by withdrawing balance).
|
||||
|
||||
#### Amend last stake amount
|
||||
|
||||
Assuming we have the tradable balance of 1000 USDT, `stake_amount=400`, and `max_open_trades=3`.
|
||||
The bot would open 2 trades, and will be unable to fill the last trading slot, since the requested 400 USDT are no longer available, since 800 USDT are already tied in other trades.
|
||||
|
||||
To overcome this, the option `amend_last_stake_amount` can be set to `True`, which will enable the bot to reduce stake_amount to the available balance in order to fill the last trade slot.
|
||||
|
||||
In the example above this would mean:
|
||||
|
||||
- Trade1: 400 USDT
|
||||
- Trade2: 400 USDT
|
||||
- Trade3: 200 USDT
|
||||
|
||||
!!! Note
|
||||
This option only applies with [Static stake amount](#static-stake-amount) - since [Dynamic stake amount](#dynamic-stake-amount) divides the balances evenly.
|
||||
|
||||
!!! Note
|
||||
The minimum last stake amount can be configured using `amend_last_stake_amount` - which defaults to 0.5 (50%). This means that the minimum stake amount that's ever used is `stake_amount * 0.5`. This avoids very low stake amounts, that are close to the minimum tradable amount for the pair and can be refused by the exchange.
|
||||
|
||||
#### Static stake amount
|
||||
|
||||
The `stake_amount` configuration statically configures the amount of stake-currency your bot will use for each trade.
|
||||
|
||||
The minimal configuration value is 0.0001, however, please check your exchange's trading minimums for the stake currency you're using to avoid problems.
|
||||
|
||||
This setting works in combination with `max_open_trades`. The maximum capital engaged in trades is `stake_amount * max_open_trades`.
|
||||
For example, the bot will at most use (0.05 BTC x 3) = 0.15 BTC, assuming a configuration of `max_open_trades=3` and `stake_amount=0.05`.
|
||||
|
||||
To allow the bot to trade all the available `stake_currency` in your account set
|
||||
!!! Note
|
||||
This setting respects the [available balance configuration](#available-balance).
|
||||
|
||||
```json
|
||||
"stake_amount" : "unlimited",
|
||||
```
|
||||
#### Dynamic stake amount
|
||||
|
||||
Alternatively, you can use a dynamic stake amount, which will use the available balance on the exchange, and divide that equally by the amount of allowed trades (`max_open_trades`).
|
||||
|
||||
To configure this, set `stake_amount="unlimited"`. We also recommend to set `tradable_balance_ratio=0.99` (99%) - to keep a minimum balance for eventual fees.
|
||||
|
||||
In this case a trade amount is calculated as:
|
||||
|
||||
@ -150,6 +191,16 @@ In this case a trade amount is calculated as:
|
||||
currency_balance / (max_open_trades - current_open_trades)
|
||||
```
|
||||
|
||||
To allow the bot to trade all the available `stake_currency` in your account (minus `tradable_balance_ratio`) set
|
||||
|
||||
```json
|
||||
"stake_amount" : "unlimited",
|
||||
"tradable_balance_ratio": 0.99,
|
||||
```
|
||||
|
||||
!!! Note
|
||||
This configuration will allow increasing / decreasing stakes depending on the performance of the bot (lower stake if bot is loosing, higher stakes if the bot has a winning record, since higher balances are available).
|
||||
|
||||
!!! Note "When using Dry-Run Mode"
|
||||
When using `"stake_amount" : "unlimited",` in combination with Dry-Run, the balance will be simulated starting with a stake of `dry_run_wallet` which will evolve over time. It is therefore important to set `dry_run_wallet` to a sensible value (like 0.05 or 0.01 for BTC and 1000 or 100 for USDT, for example), otherwise it may simulate trades with 100 BTC (or more) or 0.05 USDT (or less) at once - which may not correspond to your real available balance or is less than the exchange minimal limit for the order amount for the stake currency.
|
||||
|
||||
|
@ -148,7 +148,7 @@ Edge module has following configuration options:
|
||||
| `enabled` | If true, then Edge will run periodically. <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
|
||||
| `process_throttle_secs` | How often should Edge run in seconds. <br>*Defaults to `3600` (once per hour).* <br> ***Datatype:*** *Integer*
|
||||
| `calculate_since_number_of_days` | Number of days of data against which Edge calculates Win Rate, Risk Reward and Expectancy. <br> **Note** that it downloads historical data so increasing this number would lead to slowing down the bot. <br>*Defaults to `7`.* <br> ***Datatype:*** *Integer*
|
||||
| `capital_available_percentage` | This is the percentage of the total capital on exchange in stake currency. <br>As an example if you have 10 ETH available in your wallet on the exchange and this value is 0.5 (which is 50%), then the bot will use a maximum amount of 5 ETH for trading and considers it as available capital. <br>*Defaults to `0.5`.* <br> ***Datatype:*** *Float*
|
||||
| `capital_available_percentage` | **DEPRECATED - [replaced with `tradable_balance_ratio`](configuration.md#Available balance)** This is the percentage of the total capital on exchange in stake currency. <br>As an example if you have 10 ETH available in your wallet on the exchange and this value is 0.5 (which is 50%), then the bot will use a maximum amount of 5 ETH for trading and considers it as available capital. <br>*Defaults to `0.5`.* <br> ***Datatype:*** *Float*
|
||||
| `allowed_risk` | Ratio of allowed risk per trade. <br>*Defaults to `0.01` (1%)).* <br> ***Datatype:*** *Float*
|
||||
| `stoploss_range_min` | Minimum stoploss. <br>*Defaults to `-0.01`.* <br> ***Datatype:*** *Float*
|
||||
| `stoploss_range_max` | Maximum stoploss. <br>*Defaults to `-0.10`.* <br> ***Datatype:*** *Float*
|
||||
|
@ -78,12 +78,24 @@ def validate_config_consistency(conf: Dict[str, Any]) -> None:
|
||||
_validate_trailing_stoploss(conf)
|
||||
_validate_edge(conf)
|
||||
_validate_whitelist(conf)
|
||||
_validate_unlimited_amount(conf)
|
||||
|
||||
# validate configuration before returning
|
||||
logger.info('Validating configuration ...')
|
||||
validate_config_schema(conf)
|
||||
|
||||
|
||||
def _validate_unlimited_amount(conf: Dict[str, Any]) -> None:
|
||||
"""
|
||||
If edge is disabled, either max_open_trades or stake_amount need to be set.
|
||||
:raise: OperationalException if config validation failed
|
||||
"""
|
||||
if (not conf.get('edge', {}).get('enabled')
|
||||
and conf.get('max_open_trades') == float('inf')
|
||||
and conf.get('stake_amount') == constants.UNLIMITED_STAKE_AMOUNT):
|
||||
raise OperationalException("`max_open_trades` and `stake_amount` cannot both be unlimited.")
|
||||
|
||||
|
||||
def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None:
|
||||
|
||||
if conf.get('stoploss') == 0.0:
|
||||
|
@ -80,3 +80,13 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
|
||||
f"Using precision_filter setting is deprecated and has been replaced by"
|
||||
"PrecisionFilter. Please refer to the docs on configuration details")
|
||||
config['pairlists'].append({'method': 'PrecisionFilter'})
|
||||
|
||||
if (config.get('edge', {}).get('enabled', False)
|
||||
and 'capital_available_percentage' in config.get('edge', {})):
|
||||
logger.warning(
|
||||
"DEPRECATED: "
|
||||
"Using 'edge.capital_available_percentage' has been deprecated in favor of "
|
||||
"'tradable_balance_ratio'. Please migrate your configuration to "
|
||||
"'tradable_balance_ratio' and remove 'capital_available_percentage' "
|
||||
"from the edge configuration."
|
||||
)
|
||||
|
@ -73,6 +73,16 @@ CONF_SCHEMA = {
|
||||
'minimum': 0.0001,
|
||||
'pattern': UNLIMITED_STAKE_AMOUNT
|
||||
},
|
||||
'tradable_balance_ratio': {
|
||||
'type': 'number',
|
||||
'minimum': 0.1,
|
||||
'maximum': 1,
|
||||
'default': 0.99
|
||||
},
|
||||
'amend_last_stake_amount': {'type': 'boolean', 'default': False},
|
||||
'last_stake_amount_min_ratio': {
|
||||
'type': 'number', 'minimum': 0.0, 'maximum': 1.0, 'default': 0.5
|
||||
},
|
||||
'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT},
|
||||
'dry_run': {'type': 'boolean'},
|
||||
'dry_run_wallet': {'type': 'number', 'default': DRY_RUN_WALLET},
|
||||
@ -266,7 +276,7 @@ CONF_SCHEMA = {
|
||||
'max_trade_duration_minute': {'type': 'integer'},
|
||||
'remove_pumps': {'type': 'boolean'}
|
||||
},
|
||||
'required': ['process_throttle_secs', 'allowed_risk', 'capital_available_percentage']
|
||||
'required': ['process_throttle_secs', 'allowed_risk']
|
||||
}
|
||||
},
|
||||
}
|
||||
@ -276,6 +286,8 @@ SCHEMA_TRADE_REQUIRED = [
|
||||
'max_open_trades',
|
||||
'stake_currency',
|
||||
'stake_amount',
|
||||
'tradable_balance_ratio',
|
||||
'last_stake_amount_min_ratio',
|
||||
'dry_run',
|
||||
'dry_run_wallet',
|
||||
'bid_strategy',
|
||||
|
@ -57,7 +57,9 @@ class Edge:
|
||||
if self.config['stake_amount'] != constants.UNLIMITED_STAKE_AMOUNT:
|
||||
raise OperationalException('Edge works only with unlimited stake amount')
|
||||
|
||||
self._capital_percentage: float = self.edge_config.get('capital_available_percentage')
|
||||
# Deprecated capital_available_percentage. Will use tradable_balance_ratio in the future.
|
||||
self._capital_percentage: float = self.edge_config.get(
|
||||
'capital_available_percentage', self.config['tradable_balance_ratio'])
|
||||
self._allowed_risk: float = self.edge_config.get('allowed_risk')
|
||||
self._since_number_of_days: int = self.edge_config.get('calculate_since_number_of_days', 14)
|
||||
self._last_updated: int = 0 # Timestamp of pairs last updated time
|
||||
|
@ -250,12 +250,16 @@ class FreqtradeBot:
|
||||
|
||||
return used_rate
|
||||
|
||||
def get_trade_stake_amount(self, pair) -> Optional[float]:
|
||||
def get_trade_stake_amount(self, pair) -> float:
|
||||
"""
|
||||
Calculate stake amount for the trade
|
||||
:return: float: Stake amount
|
||||
:raise: DependencyException if the available stake amount is too low
|
||||
"""
|
||||
stake_amount: Optional[float]
|
||||
stake_amount: float
|
||||
# Ensure wallets are uptodate.
|
||||
self.wallets.update()
|
||||
|
||||
if self.edge:
|
||||
stake_amount = self.edge.stake_amount(
|
||||
pair,
|
||||
@ -270,26 +274,52 @@ class FreqtradeBot:
|
||||
|
||||
return self._check_available_stake_amount(stake_amount)
|
||||
|
||||
def _calculate_unlimited_stake_amount(self) -> Optional[float]:
|
||||
def _get_available_stake_amount(self) -> float:
|
||||
"""
|
||||
Return the total currently available balance in stake currency,
|
||||
respecting tradable_balance_ratio.
|
||||
Calculated as
|
||||
<open_trade stakes> + free amount ) * tradable_balance_ratio - <open_trade stakes>
|
||||
"""
|
||||
val_tied_up = Trade.total_open_trades_stakes()
|
||||
|
||||
# Ensure <tradable_balance_ratio>% is used from the overall balance
|
||||
# Otherwise we'd risk lowering stakes with each open trade.
|
||||
# (tied up + current free) * ratio) - tied up
|
||||
available_amount = ((val_tied_up + self.wallets.get_free(self.config['stake_currency'])) *
|
||||
self.config['tradable_balance_ratio']) - val_tied_up
|
||||
return available_amount
|
||||
|
||||
def _calculate_unlimited_stake_amount(self) -> float:
|
||||
"""
|
||||
Calculate stake amount for "unlimited" stake amount
|
||||
:return: None if max number of trades reached
|
||||
:return: 0 if max number of trades reached, else stake_amount to use.
|
||||
"""
|
||||
free_open_trades = self.get_free_open_trades()
|
||||
if not free_open_trades:
|
||||
return None
|
||||
available_amount = self.wallets.get_free(self.config['stake_currency'])
|
||||
return 0
|
||||
|
||||
available_amount = self._get_available_stake_amount()
|
||||
|
||||
return available_amount / free_open_trades
|
||||
|
||||
def _check_available_stake_amount(self, stake_amount: Optional[float]) -> Optional[float]:
|
||||
def _check_available_stake_amount(self, stake_amount: float) -> float:
|
||||
"""
|
||||
Check if stake amount can be fulfilled with the available balance
|
||||
for the stake currency
|
||||
:return: float: Stake amount
|
||||
"""
|
||||
available_amount = self.wallets.get_free(self.config['stake_currency'])
|
||||
available_amount = self._get_available_stake_amount()
|
||||
|
||||
if stake_amount is not None and available_amount < stake_amount:
|
||||
if self.config['amend_last_stake_amount']:
|
||||
# Remaining amount needs to be at least stake_amount * last_stake_amount_min_ratio
|
||||
# Otherwise the remaining amount is too low to trade.
|
||||
if available_amount > (stake_amount * self.config['last_stake_amount_min_ratio']):
|
||||
stake_amount = min(stake_amount, available_amount)
|
||||
else:
|
||||
stake_amount = 0
|
||||
|
||||
if available_amount < stake_amount:
|
||||
raise DependencyException(
|
||||
f"Available balance ({available_amount} {self.config['stake_currency']}) is "
|
||||
f"lower than stake amount ({stake_amount} {self.config['stake_currency']})"
|
||||
|
@ -1317,12 +1317,12 @@ def buy_order_fee():
|
||||
def edge_conf(default_conf):
|
||||
conf = deepcopy(default_conf)
|
||||
conf['max_open_trades'] = -1
|
||||
conf['tradable_balance_ratio'] = 0.5
|
||||
conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
|
||||
conf['edge'] = {
|
||||
"enabled": True,
|
||||
"process_throttle_secs": 1800,
|
||||
"calculate_since_number_of_days": 14,
|
||||
"capital_available_percentage": 0.5,
|
||||
"allowed_risk": 0.01,
|
||||
"stoploss_range_min": -0.01,
|
||||
"stoploss_range_max": -0.1,
|
||||
|
@ -723,6 +723,14 @@ def test_validate_default_conf(default_conf) -> None:
|
||||
validate_config_schema(default_conf)
|
||||
|
||||
|
||||
def test_validate_max_open_trades(default_conf):
|
||||
default_conf['max_open_trades'] = float('inf')
|
||||
default_conf['stake_amount'] = 'unlimited'
|
||||
with pytest.raises(OperationalException, match='`max_open_trades` and `stake_amount` '
|
||||
'cannot both be unlimited.'):
|
||||
validate_config_consistency(default_conf)
|
||||
|
||||
|
||||
def test_validate_tsl(default_conf):
|
||||
default_conf['stoploss'] = 0.0
|
||||
with pytest.raises(OperationalException, match='The config stoploss needs to be different '
|
||||
@ -1029,6 +1037,17 @@ def test_process_deprecated_setting_pairlists(mocker, default_conf, caplog):
|
||||
assert log_has_re(r'DEPRECATED.*in pairlist is deprecated and must be moved*', caplog)
|
||||
|
||||
|
||||
def test_process_deprecated_setting_edge(mocker, edge_conf, caplog):
|
||||
patched_configuration_load_config_file(mocker, edge_conf)
|
||||
edge_conf.update({'edge': {
|
||||
'enabled': True,
|
||||
'capital_available_percentage': 0.5,
|
||||
}})
|
||||
|
||||
process_temporary_deprecated_settings(edge_conf)
|
||||
assert log_has_re(r"DEPRECATED.*Using 'edge.capital_available_percentage'*", caplog)
|
||||
|
||||
|
||||
def test_check_conflicting_settings(mocker, default_conf, caplog):
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
|
||||
|
@ -140,21 +140,65 @@ def test_get_trade_stake_amount(default_conf, ticker, mocker) -> None:
|
||||
assert result == default_conf['stake_amount']
|
||||
|
||||
|
||||
@pytest.mark.parametrize("amend_last,wallet,max_open,lsamr,expected", [
|
||||
(False, 0.002, 2, 0.5, [0.001, None]),
|
||||
(True, 0.002, 2, 0.5, [0.001, 0.00098]),
|
||||
(False, 0.003, 3, 0.5, [0.001, 0.001, None]),
|
||||
(True, 0.003, 3, 0.5, [0.001, 0.001, 0.00097]),
|
||||
(False, 0.0022, 3, 0.5, [0.001, 0.001, None]),
|
||||
(True, 0.0022, 3, 0.5, [0.001, 0.001, 0.0]),
|
||||
(True, 0.0027, 3, 0.5, [0.001, 0.001, 0.000673]),
|
||||
(True, 0.0022, 3, 1, [0.001, 0.001, 0.0]),
|
||||
])
|
||||
def test_check_available_stake_amount(default_conf, ticker, mocker, fee, limit_buy_order,
|
||||
amend_last, wallet, max_open, lsamr, expected) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 2),
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee
|
||||
)
|
||||
default_conf['dry_run_wallet'] = wallet
|
||||
|
||||
default_conf['amend_last_stake_amount'] = amend_last
|
||||
default_conf['last_stake_amount_min_ratio'] = lsamr
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
for i in range(0, max_open):
|
||||
|
||||
if expected[i] is not None:
|
||||
result = freqtrade.get_trade_stake_amount('ETH/BTC')
|
||||
assert pytest.approx(result) == expected[i]
|
||||
freqtrade.execute_buy('ETH/BTC', result)
|
||||
else:
|
||||
with pytest.raises(DependencyException):
|
||||
freqtrade.get_trade_stake_amount('ETH/BTC')
|
||||
|
||||
|
||||
def test_get_trade_stake_amount_no_stake_amount(default_conf, mocker) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
patch_wallet(mocker, free=default_conf['stake_amount'] * 0.5)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
with pytest.raises(DependencyException, match=r'.*stake amount.*'):
|
||||
freqtrade.get_trade_stake_amount('ETH/BTC')
|
||||
|
||||
|
||||
def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker,
|
||||
@pytest.mark.parametrize("balance_ratio,result1", [
|
||||
(1, 0.005),
|
||||
(0.99, 0.00495),
|
||||
(0.50, 0.0025),
|
||||
])
|
||||
def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_ratio, result1,
|
||||
limit_buy_order, fee, mocker) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
patch_wallet(mocker, free=default_conf['stake_amount'])
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
@ -164,32 +208,34 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker,
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
conf['stake_amount'] = UNLIMITED_STAKE_AMOUNT
|
||||
conf['dry_run_wallet'] = 0.01
|
||||
conf['max_open_trades'] = 2
|
||||
conf['tradable_balance_ratio'] = balance_ratio
|
||||
|
||||
freqtrade = FreqtradeBot(conf)
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
# no open trades, order amount should be 'balance / max_open_trades'
|
||||
result = freqtrade.get_trade_stake_amount('ETH/BTC')
|
||||
assert result == default_conf['stake_amount'] / conf['max_open_trades']
|
||||
assert result == result1
|
||||
|
||||
# create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)'
|
||||
freqtrade.execute_buy('ETH/BTC', result)
|
||||
|
||||
result = freqtrade.get_trade_stake_amount('LTC/BTC')
|
||||
assert result == default_conf['stake_amount'] / (conf['max_open_trades'] - 1)
|
||||
assert result == result1
|
||||
|
||||
# create 2 trades, order amount should be None
|
||||
freqtrade.execute_buy('LTC/BTC', result)
|
||||
|
||||
result = freqtrade.get_trade_stake_amount('XRP/BTC')
|
||||
assert result is None
|
||||
assert result == 0
|
||||
|
||||
# set max_open_trades = None, so do not trade
|
||||
conf['max_open_trades'] = 0
|
||||
freqtrade = FreqtradeBot(conf)
|
||||
result = freqtrade.get_trade_stake_amount('NEO/BTC')
|
||||
assert result is None
|
||||
assert result == 0
|
||||
|
||||
|
||||
def test_edge_called_in_process(mocker, edge_conf) -> None:
|
||||
@ -570,7 +616,7 @@ def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order,
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
assert not freqtrade.create_trade('ETH/BTC')
|
||||
assert freqtrade.get_trade_stake_amount('ETH/BTC') is None
|
||||
assert freqtrade.get_trade_stake_amount('ETH/BTC') == 0
|
||||
|
||||
|
||||
def test_enter_positions_no_pairs_left(default_conf, ticker, limit_buy_order, fee,
|
||||
@ -635,11 +681,15 @@ def test_create_trade_no_signal(default_conf, fee, mocker) -> None:
|
||||
|
||||
|
||||
@pytest.mark.parametrize("max_open", range(0, 5))
|
||||
def test_create_trades_multiple_trades(default_conf, ticker,
|
||||
fee, mocker, max_open) -> None:
|
||||
@pytest.mark.parametrize("tradable_balance_ratio,modifier", [(1.0, 1), (0.99, 0.8), (0.5, 0.5)])
|
||||
def test_create_trades_multiple_trades(default_conf, ticker, fee, mocker,
|
||||
max_open, tradable_balance_ratio, modifier) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
default_conf['max_open_trades'] = max_open
|
||||
default_conf['tradable_balance_ratio'] = tradable_balance_ratio
|
||||
default_conf['dry_run_wallet'] = 0.001 * max_open
|
||||
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
@ -650,10 +700,11 @@ def test_create_trades_multiple_trades(default_conf, ticker,
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
n = freqtrade.enter_positions()
|
||||
assert n == max_open
|
||||
|
||||
trades = Trade.get_open_trades()
|
||||
assert len(trades) == max_open
|
||||
# Expected trades should be max_open * a modified value
|
||||
# depending on the configured tradable_balance
|
||||
assert n == max(int(max_open * modifier), 0)
|
||||
assert len(trades) == max(int(max_open * modifier), 0)
|
||||
|
||||
|
||||
def test_create_trades_preopen(default_conf, ticker, fee, mocker) -> None:
|
||||
@ -3622,6 +3673,7 @@ def test_sync_wallet_dry_run(mocker, default_conf, ticker, fee, limit_buy_order,
|
||||
# Initialize to 2 times stake amount
|
||||
default_conf['dry_run_wallet'] = 0.002
|
||||
default_conf['max_open_trades'] = 2
|
||||
default_conf['tradable_balance_ratio'] = 1.0
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
@ -3643,5 +3695,5 @@ def test_sync_wallet_dry_run(mocker, default_conf, ticker, fee, limit_buy_order,
|
||||
n = bot.enter_positions()
|
||||
assert n == 0
|
||||
assert log_has_re(r"Unable to create trade for XRP/BTC: "
|
||||
r"Available balance \(0 BTC\) is lower than stake amount \(0.001 BTC\)",
|
||||
r"Available balance \(0.0 BTC\) is lower than stake amount \(0.001 BTC\)",
|
||||
caplog)
|
||||
|
@ -1,10 +1,11 @@
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.rpc.rpc import RPC
|
||||
from freqtrade.strategy.interface import SellCheckTuple, SellType
|
||||
from tests.conftest import get_patched_freqtradebot, patch_get_signal
|
||||
from freqtrade.rpc.rpc import RPC
|
||||
|
||||
|
||||
def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
@ -112,13 +113,22 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
assert not trade.is_open
|
||||
|
||||
|
||||
def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, mocker) -> None:
|
||||
@pytest.mark.parametrize("balance_ratio,result1", [
|
||||
(1, 200),
|
||||
(0.99, 198),
|
||||
])
|
||||
def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, mocker, balance_ratio,
|
||||
result1) -> None:
|
||||
"""
|
||||
Tests workflow
|
||||
Tests workflow unlimited stake-amount
|
||||
Buy 4 trades, forcebuy a 5th trade
|
||||
Sell one trade, calculated stake amount should now be lower than before since
|
||||
one trade was sold at a loss.
|
||||
"""
|
||||
default_conf['max_open_trades'] = 5
|
||||
default_conf['forcebuy_enable'] = True
|
||||
default_conf['stake_amount'] = 'unlimited'
|
||||
default_conf['tradable_balance_ratio'] = balance_ratio
|
||||
default_conf['dry_run_wallet'] = 1000
|
||||
default_conf['exchange']['name'] = 'binance'
|
||||
default_conf['telegram']['enabled'] = True
|
||||
@ -159,13 +169,15 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
|
||||
|
||||
trades = Trade.query.all()
|
||||
assert len(trades) == 4
|
||||
assert freqtrade.get_trade_stake_amount('XRP/BTC') == result1
|
||||
|
||||
rpc._rpc_forcebuy('TKN/BTC', None)
|
||||
|
||||
trades = Trade.query.all()
|
||||
assert len(trades) == 5
|
||||
|
||||
for trade in trades:
|
||||
assert trade.stake_amount == 200
|
||||
assert trade.stake_amount == result1
|
||||
# Reset trade open order id's
|
||||
trade.open_order_id = None
|
||||
trades = Trade.get_open_trades()
|
||||
@ -177,6 +189,8 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
|
||||
trades = Trade.get_open_trades()
|
||||
# One trade sold
|
||||
assert len(trades) == 4
|
||||
# stake-amount should now be reduced, since one trade was sold at a loss.
|
||||
assert freqtrade.get_trade_stake_amount('XRP/BTC') < result1
|
||||
# Validate that balance of sold trade is not in dry-run balances anymore.
|
||||
bals2 = freqtrade.wallets.get_all_balances()
|
||||
assert bals != bals2
|
||||
|
Loading…
Reference in New Issue
Block a user