Merge pull request #2734 from freqtrade/relative_stake

Relative stake maximum tradable amount
This commit is contained in:
Matthias
2020-01-11 08:18:35 +01:00
committed by GitHub
15 changed files with 248 additions and 44 deletions

View File

@@ -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,

View File

@@ -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)

View File

@@ -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)

View File

@@ -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