Merge pull request #2734 from freqtrade/relative_stake
Relative stake maximum tradable amount
This commit is contained in:
@@ -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']})"
|
||||
|
Reference in New Issue
Block a user