mirror of
https://github.com/freqtrade/freqtrade.git
synced 2025-12-14 20:01:18 +00:00
Merge pull request #10138 from freqtrade/backtest_max_fee
Backtest max fee
This commit is contained in:
@@ -171,6 +171,7 @@ CONF_SCHEMA = {
|
|||||||
'use_exit_signal': {'type': 'boolean'},
|
'use_exit_signal': {'type': 'boolean'},
|
||||||
'exit_profit_only': {'type': 'boolean'},
|
'exit_profit_only': {'type': 'boolean'},
|
||||||
'exit_profit_offset': {'type': 'number'},
|
'exit_profit_offset': {'type': 'number'},
|
||||||
|
'fee': {'type': 'number', 'minimum': 0, 'maximum': 0.1},
|
||||||
'ignore_roi_if_entry_signal': {'type': 'boolean'},
|
'ignore_roi_if_entry_signal': {'type': 'boolean'},
|
||||||
'ignore_buying_expired_candle_after': {'type': 'number'},
|
'ignore_buying_expired_candle_after': {'type': 'number'},
|
||||||
'trading_mode': {'type': 'string', 'enum': TRADING_MODES},
|
'trading_mode': {'type': 'string', 'enum': TRADING_MODES},
|
||||||
|
|||||||
@@ -134,8 +134,17 @@ class Backtesting:
|
|||||||
|
|
||||||
if config.get('fee', None) is not None:
|
if config.get('fee', None) is not None:
|
||||||
self.fee = config['fee']
|
self.fee = config['fee']
|
||||||
|
logger.info(f"Using fee {self.fee:.4%} from config.")
|
||||||
else:
|
else:
|
||||||
self.fee = self.exchange.get_fee(symbol=self.pairlists.whitelist[0])
|
fees = [
|
||||||
|
self.exchange.get_fee(
|
||||||
|
symbol=self.pairlists.whitelist[0],
|
||||||
|
taker_or_maker=mt, # type: ignore
|
||||||
|
)
|
||||||
|
for mt in ('taker', 'maker')
|
||||||
|
]
|
||||||
|
self.fee = max(fee for fee in fees if fee is not None)
|
||||||
|
logger.info(f"Using fee {self.fee:.4%} - worst case fee from exchange (lowest tier).")
|
||||||
self.precision_mode = self.exchange.precisionMode
|
self.precision_mode = self.exchange.precisionMode
|
||||||
|
|
||||||
if self.config.get('freqai_backtest_live_models', False):
|
if self.config.get('freqai_backtest_live_models', False):
|
||||||
|
|||||||
@@ -241,6 +241,7 @@ def patch_exchange(
|
|||||||
if api_mock:
|
if api_mock:
|
||||||
mocker.patch(f'{EXMS}._init_ccxt', return_value=api_mock)
|
mocker.patch(f'{EXMS}._init_ccxt', return_value=api_mock)
|
||||||
else:
|
else:
|
||||||
|
mocker.patch(f'{EXMS}.get_fee', return_value=0.0025)
|
||||||
mocker.patch(f'{EXMS}._init_ccxt', MagicMock())
|
mocker.patch(f'{EXMS}._init_ccxt', MagicMock())
|
||||||
mocker.patch(f'{EXMS}.timeframes', PropertyMock(
|
mocker.patch(f'{EXMS}.timeframes', PropertyMock(
|
||||||
return_value=['5m', '15m', '1h', '1d']))
|
return_value=['5m', '15m', '1h', '1d']))
|
||||||
|
|||||||
@@ -1121,12 +1121,12 @@ def test_create_dry_run_order_fees(
|
|||||||
price_side,
|
price_side,
|
||||||
fee,
|
fee,
|
||||||
):
|
):
|
||||||
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
f'{EXMS}.get_fee',
|
f'{EXMS}.get_fee',
|
||||||
side_effect=lambda symbol, taker_or_maker: 2.0 if taker_or_maker == 'taker' else 1.0
|
side_effect=lambda symbol, taker_or_maker: 2.0 if taker_or_maker == 'taker' else 1.0
|
||||||
)
|
)
|
||||||
mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=price_side == 'other')
|
mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=price_side == 'other')
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
|
||||||
|
|
||||||
order = exchange.create_dry_run_order(
|
order = exchange.create_dry_run_order(
|
||||||
pair='LTC/USDT',
|
pair='LTC/USDT',
|
||||||
|
|||||||
@@ -921,12 +921,12 @@ def test_backtest_results(default_conf, mocker, caplog, data: BTContainer) -> No
|
|||||||
default_conf["use_exit_signal"] = data.use_exit_signal
|
default_conf["use_exit_signal"] = data.use_exit_signal
|
||||||
default_conf["max_open_trades"] = 10
|
default_conf["max_open_trades"] = 10
|
||||||
|
|
||||||
|
patch_exchange(mocker)
|
||||||
mocker.patch(f"{EXMS}.get_fee", return_value=0.0)
|
mocker.patch(f"{EXMS}.get_fee", return_value=0.0)
|
||||||
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
|
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
|
||||||
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf'))
|
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf'))
|
||||||
mocker.patch(f"{EXMS}.get_max_leverage", return_value=100)
|
mocker.patch(f"{EXMS}.get_max_leverage", return_value=100)
|
||||||
mocker.patch(f"{EXMS}.calculate_funding_fees", return_value=0)
|
mocker.patch(f"{EXMS}.calculate_funding_fees", return_value=0)
|
||||||
patch_exchange(mocker)
|
|
||||||
frame = _build_backtest_dataframe(data.data)
|
frame = _build_backtest_dataframe(data.data)
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
# TODO: Should we initialize this properly??
|
# TODO: Should we initialize this properly??
|
||||||
|
|||||||
@@ -298,12 +298,12 @@ def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None:
|
|||||||
|
|
||||||
def test_data_with_fee(default_conf, mocker) -> None:
|
def test_data_with_fee(default_conf, mocker) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
default_conf['fee'] = 0.1234
|
default_conf['fee'] = 0.01234
|
||||||
|
|
||||||
fee_mock = mocker.patch(f'{EXMS}.get_fee', MagicMock(return_value=0.5))
|
fee_mock = mocker.patch(f'{EXMS}.get_fee', MagicMock(return_value=0.5))
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
backtesting._set_strategy(backtesting.strategylist[0])
|
backtesting._set_strategy(backtesting.strategylist[0])
|
||||||
assert backtesting.fee == 0.1234
|
assert backtesting.fee == 0.01234
|
||||||
assert fee_mock.call_count == 0
|
assert fee_mock.call_count == 0
|
||||||
|
|
||||||
default_conf['fee'] = 0.0
|
default_conf['fee'] = 0.0
|
||||||
@@ -620,12 +620,11 @@ def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None:
|
|||||||
assert trade is None
|
assert trade is None
|
||||||
|
|
||||||
|
|
||||||
def test_backtest__check_trade_exit(default_conf, fee, mocker) -> None:
|
def test_backtest__check_trade_exit(default_conf, mocker) -> None:
|
||||||
default_conf['use_exit_signal'] = False
|
default_conf['use_exit_signal'] = False
|
||||||
mocker.patch(f'{EXMS}.get_fee', fee)
|
patch_exchange(mocker)
|
||||||
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
|
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
|
||||||
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf'))
|
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf'))
|
||||||
patch_exchange(mocker)
|
|
||||||
default_conf['timeframe_detail'] = '1m'
|
default_conf['timeframe_detail'] = '1m'
|
||||||
default_conf['max_open_trades'] = 2
|
default_conf['max_open_trades'] = 2
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
@@ -683,14 +682,13 @@ def test_backtest__check_trade_exit(default_conf, fee, mocker) -> None:
|
|||||||
assert res is None
|
assert res is None
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
def test_backtest_one(default_conf, mocker, testdatadir) -> None:
|
||||||
default_conf['use_exit_signal'] = False
|
default_conf['use_exit_signal'] = False
|
||||||
default_conf['max_open_trades'] = 10
|
default_conf['max_open_trades'] = 10
|
||||||
|
|
||||||
mocker.patch(f'{EXMS}.get_fee', fee)
|
patch_exchange(mocker)
|
||||||
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
|
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
|
||||||
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf'))
|
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf'))
|
||||||
patch_exchange(mocker)
|
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
backtesting._set_strategy(backtesting.strategylist[0])
|
backtesting._set_strategy(backtesting.strategylist[0])
|
||||||
pair = 'UNITTEST/BTC'
|
pair = 'UNITTEST/BTC'
|
||||||
@@ -777,14 +775,13 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('use_detail', [True, False])
|
@pytest.mark.parametrize('use_detail', [True, False])
|
||||||
def test_backtest_one_detail(default_conf_usdt, fee, mocker, testdatadir, use_detail) -> None:
|
def test_backtest_one_detail(default_conf_usdt, mocker, testdatadir, use_detail) -> None:
|
||||||
default_conf_usdt['use_exit_signal'] = False
|
default_conf_usdt['use_exit_signal'] = False
|
||||||
mocker.patch(f'{EXMS}.get_fee', fee)
|
patch_exchange(mocker)
|
||||||
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
|
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
|
||||||
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf'))
|
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf'))
|
||||||
if use_detail:
|
if use_detail:
|
||||||
default_conf_usdt['timeframe_detail'] = '1m'
|
default_conf_usdt['timeframe_detail'] = '1m'
|
||||||
patch_exchange(mocker)
|
|
||||||
|
|
||||||
def advise_entry(df, *args, **kwargs):
|
def advise_entry(df, *args, **kwargs):
|
||||||
# Mock function to force several entries
|
# Mock function to force several entries
|
||||||
@@ -864,14 +861,14 @@ def test_backtest_one_detail(default_conf_usdt, fee, mocker, testdatadir, use_de
|
|||||||
(False, -0.01780296, 5),
|
(False, -0.01780296, 5),
|
||||||
])
|
])
|
||||||
def test_backtest_one_detail_futures(
|
def test_backtest_one_detail_futures(
|
||||||
default_conf_usdt, fee, mocker, testdatadir, use_detail, exp_funding_fee,
|
default_conf_usdt, mocker, testdatadir, use_detail, exp_funding_fee,
|
||||||
exp_ff_updates) -> None:
|
exp_ff_updates) -> None:
|
||||||
default_conf_usdt['use_exit_signal'] = False
|
default_conf_usdt['use_exit_signal'] = False
|
||||||
default_conf_usdt['trading_mode'] = 'futures'
|
default_conf_usdt['trading_mode'] = 'futures'
|
||||||
default_conf_usdt['margin_mode'] = 'isolated'
|
default_conf_usdt['margin_mode'] = 'isolated'
|
||||||
default_conf_usdt['candle_type_def'] = CandleType.FUTURES
|
default_conf_usdt['candle_type_def'] = CandleType.FUTURES
|
||||||
|
|
||||||
mocker.patch(f'{EXMS}.get_fee', fee)
|
patch_exchange(mocker)
|
||||||
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
|
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
|
||||||
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf'))
|
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf'))
|
||||||
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
||||||
@@ -881,7 +878,6 @@ def test_backtest_one_detail_futures(
|
|||||||
default_conf_usdt['timeframe'] = '1h'
|
default_conf_usdt['timeframe'] = '1h'
|
||||||
if use_detail:
|
if use_detail:
|
||||||
default_conf_usdt['timeframe_detail'] = '5m'
|
default_conf_usdt['timeframe_detail'] = '5m'
|
||||||
patch_exchange(mocker)
|
|
||||||
|
|
||||||
def advise_entry(df, *args, **kwargs):
|
def advise_entry(df, *args, **kwargs):
|
||||||
# Mock function to force several entries
|
# Mock function to force several entries
|
||||||
@@ -1237,19 +1233,18 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
|
|||||||
([{"method": "CooldownPeriod", "stop_duration": 3}], 'sine', 9),
|
([{"method": "CooldownPeriod", "stop_duration": 3}], 'sine', 9),
|
||||||
([{"method": "CooldownPeriod", "stop_duration": 3}], 'raise', 10),
|
([{"method": "CooldownPeriod", "stop_duration": 3}], 'raise', 10),
|
||||||
])
|
])
|
||||||
def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir,
|
def test_backtest_pricecontours(default_conf, mocker, testdatadir,
|
||||||
protections, contour, expected) -> None:
|
protections, contour, expected) -> None:
|
||||||
if protections:
|
if protections:
|
||||||
default_conf['protections'] = protections
|
default_conf['protections'] = protections
|
||||||
default_conf['enable_protections'] = True
|
default_conf['enable_protections'] = True
|
||||||
|
|
||||||
|
patch_exchange(mocker)
|
||||||
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
|
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
|
||||||
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf'))
|
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf'))
|
||||||
mocker.patch(f'{EXMS}.get_fee', fee)
|
|
||||||
# While entry-signals are unrealistic, running backtesting
|
# While entry-signals are unrealistic, running backtesting
|
||||||
# over and over again should not cause different results
|
# over and over again should not cause different results
|
||||||
|
|
||||||
patch_exchange(mocker)
|
|
||||||
default_conf['timeframe'] = '1m'
|
default_conf['timeframe'] = '1m'
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
backtesting._set_strategy(backtesting.strategylist[0])
|
backtesting._set_strategy(backtesting.strategylist[0])
|
||||||
|
|||||||
@@ -99,11 +99,11 @@ def test_edge_init(mocker, edge_conf) -> None:
|
|||||||
|
|
||||||
def test_edge_init_fee(mocker, edge_conf) -> None:
|
def test_edge_init_fee(mocker, edge_conf) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
edge_conf['fee'] = 0.1234
|
edge_conf['fee'] = 0.01234
|
||||||
edge_conf['stake_amount'] = 20
|
edge_conf['stake_amount'] = 20
|
||||||
fee_mock = mocker.patch(f'{EXMS}.get_fee', return_value=0.5)
|
fee_mock = mocker.patch(f'{EXMS}.get_fee', return_value=0.5)
|
||||||
edge_cli = EdgeCli(edge_conf)
|
edge_cli = EdgeCli(edge_conf)
|
||||||
assert edge_cli.edge.fee == 0.1234
|
assert edge_cli.edge.fee == 0.01234
|
||||||
assert fee_mock.call_count == 0
|
assert fee_mock.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -343,11 +343,11 @@ def test_initialize_single_lookahead_analysis(lookahead_conf, mocker, caplog):
|
|||||||
'no_bias', 'bias1'
|
'no_bias', 'bias1'
|
||||||
])
|
])
|
||||||
def test_biased_strategy(lookahead_conf, mocker, caplog, scenario) -> None:
|
def test_biased_strategy(lookahead_conf, mocker, caplog, scenario) -> None:
|
||||||
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.data.history.get_timerange', get_timerange)
|
mocker.patch('freqtrade.data.history.get_timerange', get_timerange)
|
||||||
mocker.patch(f'{EXMS}.get_fee', return_value=0.0)
|
mocker.patch(f'{EXMS}.get_fee', return_value=0.0)
|
||||||
mocker.patch(f'{EXMS}.get_min_pair_stake_amount', return_value=0.00001)
|
mocker.patch(f'{EXMS}.get_min_pair_stake_amount', return_value=0.00001)
|
||||||
mocker.patch(f'{EXMS}.get_max_pair_stake_amount', return_value=float('inf'))
|
mocker.patch(f'{EXMS}.get_max_pair_stake_amount', return_value=float('inf'))
|
||||||
patch_exchange(mocker)
|
|
||||||
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
||||||
PropertyMock(return_value=['UNITTEST/BTC']))
|
PropertyMock(return_value=['UNITTEST/BTC']))
|
||||||
lookahead_conf['pairs'] = ['UNITTEST/USDT']
|
lookahead_conf['pairs'] = ['UNITTEST/USDT']
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from freqtrade.data.history import get_timerange
|
|||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.optimize.analysis.recursive import RecursiveAnalysis
|
from freqtrade.optimize.analysis.recursive import RecursiveAnalysis
|
||||||
from freqtrade.optimize.analysis.recursive_helpers import RecursiveAnalysisSubFunctions
|
from freqtrade.optimize.analysis.recursive_helpers import RecursiveAnalysisSubFunctions
|
||||||
from tests.conftest import get_args, log_has_re, patch_exchange
|
from tests.conftest import EXMS, get_args, log_has_re, patch_exchange
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -152,8 +152,9 @@ def test_initialize_single_recursive_analysis(recursive_conf, mocker, caplog):
|
|||||||
'no_bias', 'bias1', 'bias2'
|
'no_bias', 'bias1', 'bias2'
|
||||||
])
|
])
|
||||||
def test_recursive_biased_strategy(recursive_conf, mocker, caplog, scenario) -> None:
|
def test_recursive_biased_strategy(recursive_conf, mocker, caplog, scenario) -> None:
|
||||||
mocker.patch('freqtrade.data.history.get_timerange', get_timerange)
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
mocker.patch(f'{EXMS}.get_fee', return_value=0.0)
|
||||||
|
mocker.patch('freqtrade.data.history.get_timerange', get_timerange)
|
||||||
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
||||||
PropertyMock(return_value=['UNITTEST/BTC']))
|
PropertyMock(return_value=['UNITTEST/BTC']))
|
||||||
recursive_conf['pairs'] = ['UNITTEST/BTC']
|
recursive_conf['pairs'] = ['UNITTEST/BTC']
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
|||||||
'funding_fee': ANY, 'ft_order_tag': None,
|
'funding_fee': ANY, 'ft_order_tag': None,
|
||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
EXMS,
|
EXMS,
|
||||||
@@ -111,7 +112,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
|||||||
_dry_is_price_crossed=MagicMock(side_effect=[False, True]),
|
_dry_is_price_crossed=MagicMock(side_effect=[False, True]),
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
|
||||||
patch_get_signal(freqtradebot)
|
patch_get_signal(freqtradebot)
|
||||||
rpc = RPC(freqtradebot)
|
rpc = RPC(freqtradebot)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user