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