Merge pull request #5255 from freqtrade/improve_dynamic_stake

Improve dynamic stake with multiple bots on the same exchange
This commit is contained in:
Matthias 2021-07-14 06:45:48 +02:00 committed by GitHub
commit 3451687135
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 78 additions and 18 deletions

View File

@ -52,6 +52,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `stake_currency` | **Required.** Crypto-currency used for trading. <br> **Datatype:** String | `stake_currency` | **Required.** Crypto-currency used for trading. <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](#configuring-amount-per-trade). <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). <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`. | `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`.
| `available_capital` | Available starting capital for the bot. Useful when running multiple bots on the same exchange account.[More information below](#configuring-amount-per-trade). <br> **Datatype:** Positive float.
| `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 | `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) | `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. | `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.
@ -192,9 +193,25 @@ You can configure the "untouched" amount by using the `tradable_balance_ratio` s
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. 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.
!!! Danger
This setting should **not** be used when running multiple bots on the same account. Please look at [Available Capital to the bot](#assign-available-capital) instead.
!!! Warning !!! 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). 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).
#### Assign available Capital
To fully utilize compounding profits when using multiple bots on the same exchange account, you'll want to limit each bot to a certain starting balance.
This can be accomplished by setting `available_capital` to the desired starting balance.
Assuming your account has 10.000 USDT and you want to run 2 different strategies on this exchange.
You'd set `available_capital=5000` - granting each bot an initial capital of 5000 USDT.
The bot will then split this starting balance equally into `max_open_trades` buckets.
Profitable trades will result in increased stake-sizes for this bot - without affecting stake-sizes of the other bot.
!!! Warning "Incompatible with `tradable_balance_ratio`"
Setting this option will replace any configuration of `tradable_balance_ratio`.
#### Amend last stake amount #### Amend last stake amount
Assuming we have the tradable balance of 1000 USDT, `stake_amount=400`, and `max_open_trades=3`. Assuming we have the tradable balance of 1000 USDT, `stake_amount=400`, and `max_open_trades=3`.

View File

@ -113,6 +113,10 @@ CONF_SCHEMA = {
'maximum': 1, 'maximum': 1,
'default': 0.99 'default': 0.99
}, },
'available_capital': {
'type': 'number',
'minimum': 0,
},
'amend_last_stake_amount': {'type': 'boolean', 'default': False}, 'amend_last_stake_amount': {'type': 'boolean', 'default': False},
'last_stake_amount_min_ratio': { 'last_stake_amount_min_ratio': {
'type': 'number', 'minimum': 0.0, 'maximum': 1.0, 'default': 0.5 'type': 'number', 'minimum': 0.0, 'maximum': 1.0, 'default': 0.5

View File

@ -801,6 +801,19 @@ class Trade(_DECL_BASE, LocalTrade):
Trade.is_open.is_(False), Trade.is_open.is_(False),
]).all() ]).all()
@staticmethod
def get_total_closed_profit() -> float:
"""
Retrieves total realized profit
"""
if Trade.use_db:
total_profit = Trade.query.with_entities(
func.sum(Trade.close_profit_abs)).filter(Trade.is_open.is_(False)).scalar()
else:
total_profit = sum(
t.close_profit_abs for t in LocalTrade.get_trades_proxy(is_open=False))
return total_profit or 0
@staticmethod @staticmethod
def total_open_trades_stakes() -> float: def total_open_trades_stakes() -> float:
""" """

View File

@ -70,9 +70,7 @@ class Wallets:
# If not backtesting... # If not backtesting...
# TODO: potentially remove the ._log workaround to determine backtest mode. # TODO: potentially remove the ._log workaround to determine backtest mode.
if self._log: if self._log:
closed_trades = Trade.get_trades_proxy(is_open=False) tot_profit = Trade.get_total_closed_profit()
tot_profit = sum(
[trade.close_profit_abs for trade in closed_trades if trade.close_profit_abs])
else: else:
tot_profit = LocalTrade.total_profit tot_profit = LocalTrade.total_profit
tot_in_trades = sum([trade.stake_amount for trade in open_trades]) tot_in_trades = sum([trade.stake_amount for trade in open_trades])
@ -138,10 +136,16 @@ class Wallets:
Calculated as Calculated as
(<open_trade stakes> + free amount) * tradable_balance_ratio (<open_trade stakes> + free amount) * tradable_balance_ratio
""" """
val_tied_up = Trade.total_open_trades_stakes()
if "available_capital" in self._config:
starting_balance = self._config['available_capital']
tot_profit = Trade.get_total_closed_profit()
available_amount = starting_balance + tot_profit
else:
# Ensure <tradable_balance_ratio>% is used from the overall balance # Ensure <tradable_balance_ratio>% is used from the overall balance
# Otherwise we'd risk lowering stakes with each open trade. # Otherwise we'd risk lowering stakes with each open trade.
# (tied up + current free) * ratio) - tied up # (tied up + current free) * ratio) - tied up
val_tied_up = Trade.total_open_trades_stakes()
available_amount = ((val_tied_up + self.get_free(self._config['stake_currency'])) * available_amount = ((val_tied_up + self.get_free(self._config['stake_currency'])) *
self._config['tradable_balance_ratio']) self._config['tradable_balance_ratio'])
return available_amount return available_amount
@ -154,10 +158,8 @@ class Wallets:
(<open_trade stakes> + free amount) * tradable_balance_ratio - <open_trade stakes> (<open_trade stakes> + free amount) * tradable_balance_ratio - <open_trade stakes>
""" """
# Ensure <tradable_balance_ratio>% is used from the overall balance free = self.get_free(self._config['stake_currency'])
# Otherwise we'd risk lowering stakes with each open trade. return min(self.get_total_stake_amount() - Trade.total_open_trades_stakes(), free)
# (tied up + current free) * ratio) - tied up
return self.get_total_stake_amount() - Trade.total_open_trades_stakes()
def _calculate_unlimited_stake_amount(self, available_amount: float, def _calculate_unlimited_stake_amount(self, available_amount: float,
val_tied_up: float) -> float: val_tied_up: float) -> float:

View File

@ -1124,6 +1124,21 @@ def test_total_open_trades_stakes(fee, use_db):
Trade.use_db = True Trade.use_db = True
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize('use_db', [True, False])
def test_get_total_closed_profit(fee, use_db):
Trade.use_db = use_db
Trade.reset_trades()
res = Trade.get_total_closed_profit()
assert res == 0
create_mock_trades(fee, use_db)
res = Trade.get_total_closed_profit()
assert res == 0.000739127
Trade.use_db = True
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize('use_db', [True, False]) @pytest.mark.parametrize('use_db', [True, False])
def test_get_trades_proxy(fee, use_db): def test_get_trades_proxy(fee, use_db):
@ -1298,6 +1313,7 @@ def test_Trade_object_idem():
'open_date', 'open_date',
'get_best_pair', 'get_best_pair',
'get_overall_performance', 'get_overall_performance',
'get_total_closed_profit',
'total_open_trades_stakes', 'total_open_trades_stakes',
'get_sold_trades_without_assigned_fees', 'get_sold_trades_without_assigned_fees',
'get_open_trades_without_assigned_fees', 'get_open_trades_without_assigned_fees',

View File

@ -121,13 +121,19 @@ def test_get_trade_stake_amount_no_stake_amount(default_conf, mocker) -> None:
freqtrade.wallets.get_trade_stake_amount('ETH/BTC') freqtrade.wallets.get_trade_stake_amount('ETH/BTC')
@pytest.mark.parametrize("balance_ratio,result1,result2", [ @pytest.mark.parametrize("balance_ratio,capital,result1,result2", [
(1, 50, 66.66666), (1, None, 50, 66.66666),
(0.99, 49.5, 66.0), (0.99, None, 49.5, 66.0),
(0.50, 25, 33.3333), (0.50, None, 25, 33.3333),
# Tests with capital ignore balance_ratio
(1, 100, 50, 0.0),
(0.99, 200, 50, 66.66666),
(0.99, 150, 50, 50),
(0.50, 50, 25, 0.0),
(0.50, 10, 5, 0.0),
]) ])
def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_ratio, result1, def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_ratio, capital,
result2, limit_buy_order_open, result1, result2, limit_buy_order_open,
fee, mocker) -> None: fee, mocker) -> None:
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
@ -141,6 +147,8 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_r
conf['dry_run_wallet'] = 100 conf['dry_run_wallet'] = 100
conf['max_open_trades'] = 2 conf['max_open_trades'] = 2
conf['tradable_balance_ratio'] = balance_ratio conf['tradable_balance_ratio'] = balance_ratio
if capital is not None:
conf['available_capital'] = capital
freqtrade = get_patched_freqtradebot(mocker, conf) freqtrade = get_patched_freqtradebot(mocker, conf)