stable/tests/optimize/test_backtesting.py

1996 lines
81 KiB
Python
Raw Normal View History

# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, unused-argument
2018-03-17 21:44:47 +00:00
import random
from copy import deepcopy
from datetime import datetime, timedelta, timezone
from pathlib import Path
from unittest.mock import MagicMock, PropertyMock
2018-03-17 21:44:47 +00:00
import numpy as np
2018-03-17 21:44:47 +00:00
import pandas as pd
2018-07-04 07:31:35 +00:00
import pytest
2018-03-17 21:44:47 +00:00
from arrow import Arrow
from freqtrade import constants
2020-09-28 17:43:15 +00:00
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_backtesting
from freqtrade.configuration import TimeRange
2018-12-13 05:35:28 +00:00
from freqtrade.data import history
2020-09-25 19:00:58 +00:00
from freqtrade.data.btanalysis import BT_DATA_COLUMNS, evaluate_result_multi
from freqtrade.data.converter import clean_ohlcv_dataframe
from freqtrade.data.dataprovider import DataProvider
2019-12-17 22:06:03 +00:00
from freqtrade.data.history import get_timerange
2023-01-20 08:37:28 +00:00
from freqtrade.enums import CandleType, ExitType, RunMode
2021-02-22 05:54:33 +00:00
from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.exchange.exchange import timeframe_to_next_date
from freqtrade.optimize.backtest_caching import get_strategy_run_id
from freqtrade.optimize.backtesting import Backtesting
2023-01-20 08:37:28 +00:00
from freqtrade.persistence import LocalTrade, Trade
from freqtrade.resolvers import StrategyResolver
from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, get_args, log_has, log_has_re,
patch_exchange, patched_configuration_load_config_file)
2020-09-28 17:43:15 +00:00
2019-10-05 13:34:31 +00:00
ORDER_TYPES = [
{
2022-03-09 06:39:32 +00:00
'entry': 'limit',
'exit': 'limit',
2019-10-05 13:34:31 +00:00
'stoploss': 'limit',
'stoploss_on_exchange': False
},
{
2022-03-09 06:39:32 +00:00
'entry': 'limit',
'exit': 'limit',
2019-10-05 13:34:31 +00:00
'stoploss': 'limit',
'stoploss_on_exchange': True
}]
2018-01-28 07:38:41 +00:00
def trim_dictlist(dict_list, num):
2018-01-17 17:19:39 +00:00
new = {}
2018-01-28 07:38:41 +00:00
for pair, pair_data in dict_list.items():
2019-05-08 18:33:22 +00:00
new[pair] = pair_data[num:].reset_index()
2018-01-17 17:19:39 +00:00
return new
2019-09-07 18:56:03 +00:00
def load_data_test(what, testdatadir):
timerange = TimeRange.parse_timerange('1510694220-1510700340')
data = history.load_pair_history(pair='UNITTEST/BTC', datadir=testdatadir,
timeframe='1m', timerange=timerange,
drop_incomplete=False,
fill_up_missing=False)
base = 0.001
if what == 'raise':
data.loc[:, 'open'] = data.index * base
data.loc[:, 'high'] = data.index * base + 0.0001
data.loc[:, 'low'] = data.index * base - 0.0001
data.loc[:, 'close'] = data.index * base
if what == 'lower':
data.loc[:, 'open'] = 1 - data.index * base
data.loc[:, 'high'] = 1 - data.index * base + 0.0001
data.loc[:, 'low'] = 1 - data.index * base - 0.0001
data.loc[:, 'close'] = 1 - data.index * base
if what == 'sine':
hz = 0.1 # frequency
data.loc[:, 'open'] = np.sin(data.index * hz) / 1000 + base
data.loc[:, 'high'] = np.sin(data.index * hz) / 1000 + base + 0.0001
data.loc[:, 'low'] = np.sin(data.index * hz) / 1000 + base - 0.0001
data.loc[:, 'close'] = np.sin(data.index * hz) / 1000 + base
return {'UNITTEST/BTC': clean_ohlcv_dataframe(data, timeframe='1m', pair='UNITTEST/BTC',
2022-09-28 18:23:56 +00:00
fill_missing=True, drop_incomplete=True)}
# FIX: fixturize this?
2019-12-13 23:12:44 +00:00
def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC'):
data = history.load_data(datadir=datadir, timeframe='1m', pairs=[pair])
data = trim_dictlist(data, -201)
2018-06-17 20:32:56 +00:00
patch_exchange(mocker)
2018-04-21 22:19:11 +00:00
backtesting = Backtesting(conf)
backtesting._set_strategy(backtesting.strategylist[0])
processed = backtesting.strategy.advise_all_indicators(data)
2019-12-17 22:06:03 +00:00
min_date, max_date = get_timerange(processed)
return {
'processed': processed,
'start_date': min_date,
'end_date': max_date,
}
def _trend(signals, buy_value, sell_value):
n = len(signals['low'])
buy = np.zeros(n)
sell = np.zeros(n)
2021-09-05 06:36:22 +00:00
for i in range(0, len(signals['date'])):
if random.random() > 0.5: # Both buy and sell signals at same timeframe
buy[i] = buy_value
sell[i] = sell_value
2021-08-24 04:45:09 +00:00
signals['enter_long'] = buy
signals['exit_long'] = sell
signals['enter_short'] = 0
signals['exit_short'] = 0
return signals
def _trend_alternate(dataframe=None, metadata=None):
signals = dataframe
low = signals['low']
n = len(low)
buy = np.zeros(n)
sell = np.zeros(n)
for i in range(0, len(buy)):
if i % 2 == 0:
buy[i] = 1
else:
sell[i] = 1
2021-08-24 04:45:09 +00:00
signals['enter_long'] = buy
signals['exit_long'] = sell
signals['enter_short'] = 0
signals['exit_short'] = 0
return dataframe
# Unit tests
2020-01-26 12:33:13 +00:00
def test_setup_optimize_configuration_without_arguments(mocker, default_conf, caplog) -> None:
patched_configuration_load_config_file(mocker, default_conf)
args = [
2019-09-14 11:18:52 +00:00
'backtesting',
'--config', 'config.json',
'--strategy', CURRENT_TEST_STRATEGY,
'--export', 'none'
]
2020-01-26 12:33:13 +00:00
config = setup_optimize_configuration(get_args(args), RunMode.BACKTEST)
assert 'max_open_trades' in config
assert 'stake_currency' in config
assert 'stake_amount' in config
assert 'exchange' in config
assert 'pair_whitelist' in config['exchange']
assert 'datadir' in config
assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog)
assert 'timeframe' in config
assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog)
assert 'position_stacking' not in config
assert not log_has('Parameter --enable-position-stacking detected ...', caplog)
assert 'timerange' not in config
assert 'export' in config
assert config['export'] == 'none'
2018-12-25 13:35:48 +00:00
assert 'runmode' in config
assert config['runmode'] == RunMode.BACKTEST
2019-01-01 13:07:40 +00:00
def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) -> None:
patched_configuration_load_config_file(mocker, default_conf)
2019-07-11 22:41:09 +00:00
mocker.patch(
2019-07-12 00:26:27 +00:00
'freqtrade.configuration.configuration.create_datadir',
lambda c, x: x
2019-07-11 22:41:09 +00:00
)
args = [
2019-09-14 11:18:52 +00:00
'backtesting',
'--config', 'config.json',
'--strategy', CURRENT_TEST_STRATEGY,
'--datadir', '/foo/bar',
'--timeframe', '1m',
'--enable-position-stacking',
2018-07-17 19:05:03 +00:00
'--disable-max-market-positions',
'--timerange', ':100',
2019-10-05 13:34:31 +00:00
'--export-filename', 'foo_bar.json',
'--fee', '0',
]
2020-01-26 12:33:13 +00:00
config = setup_optimize_configuration(get_args(args), RunMode.BACKTEST)
assert 'max_open_trades' in config
assert 'stake_currency' in config
assert 'stake_amount' in config
assert 'exchange' in config
assert 'pair_whitelist' in config['exchange']
assert 'datadir' in config
2018-12-25 13:35:48 +00:00
assert config['runmode'] == RunMode.BACKTEST
assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog)
assert 'timeframe' in config
assert log_has('Parameter -i/--timeframe detected ... Using timeframe: 1m ...',
caplog)
assert 'position_stacking' in config
assert log_has('Parameter --enable-position-stacking detected ...', caplog)
2018-07-17 19:05:03 +00:00
assert 'use_max_market_positions' in config
assert log_has('Parameter --disable-max-market-positions detected ...', caplog)
assert log_has('max_open_trades set to unlimited ...', caplog)
assert 'timerange' in config
assert log_has('Parameter --timerange detected: {} ...'.format(config['timerange']), caplog)
assert 'export' in config
2018-06-03 12:52:03 +00:00
assert 'exportfilename' in config
assert isinstance(config['exportfilename'], Path)
assert log_has('Storing backtest results to {} ...'.format(config['exportfilename']), caplog)
2019-10-05 13:34:31 +00:00
assert 'fee' in config
assert log_has('Parameter --fee detected, setting fee to: {} ...'.format(config['fee']), caplog)
2021-02-26 18:48:06 +00:00
def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog) -> None:
2018-06-03 22:48:26 +00:00
patched_configuration_load_config_file(mocker, default_conf)
2018-06-03 22:48:26 +00:00
args = [
2019-09-14 11:18:52 +00:00
'backtesting',
2018-06-03 22:48:26 +00:00
'--config', 'config.json',
'--strategy', CURRENT_TEST_STRATEGY,
2021-02-26 18:48:06 +00:00
'--stake-amount', '1',
'--starting-balance', '2'
2018-06-03 22:48:26 +00:00
]
conf = setup_optimize_configuration(get_args(args), RunMode.BACKTEST)
assert isinstance(conf, dict)
2018-06-03 22:48:26 +00:00
2021-02-26 18:48:06 +00:00
args = [
'backtesting',
'--config', 'config.json',
'--strategy', CURRENT_TEST_STRATEGY,
2021-02-26 18:48:06 +00:00
'--stake-amount', '1',
'--starting-balance', '0.5'
]
with pytest.raises(OperationalException, match=r"Starting balance .* smaller .*"):
setup_optimize_configuration(get_args(args), RunMode.BACKTEST)
2018-06-03 22:48:26 +00:00
2018-04-21 22:19:11 +00:00
def test_start(mocker, fee, default_conf, caplog) -> None:
start_mock = MagicMock()
mocker.patch(f'{EXMS}.get_fee', fee)
2018-06-17 20:32:56 +00:00
patch_exchange(mocker)
mocker.patch('freqtrade.optimize.backtesting.Backtesting.start', start_mock)
patched_configuration_load_config_file(mocker, default_conf)
2019-07-11 21:39:42 +00:00
args = [
2019-09-14 11:18:52 +00:00
'backtesting',
'--config', 'config.json',
'--strategy', CURRENT_TEST_STRATEGY,
]
2020-02-10 09:35:48 +00:00
pargs = get_args(args)
start_backtesting(pargs)
assert log_has('Starting freqtrade in Backtesting mode', caplog)
assert start_mock.call_count == 1
@pytest.mark.parametrize("order_types", ORDER_TYPES)
def test_backtesting_init(mocker, default_conf, order_types) -> None:
"""
Check that stoploss_on_exchange is set to False while backtesting
since backtesting assumes a perfect stoploss anyway.
"""
default_conf["order_types"] = order_types
2018-06-17 20:32:56 +00:00
patch_exchange(mocker)
get_fee = mocker.patch(f'{EXMS}.get_fee', MagicMock(return_value=0.5))
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
assert backtesting.config == default_conf
assert backtesting.timeframe == '5m'
assert callable(backtesting.strategy.advise_all_indicators)
2021-09-22 18:42:31 +00:00
assert callable(backtesting.strategy.advise_entry)
assert callable(backtesting.strategy.advise_exit)
assert isinstance(backtesting.strategy.dp, DataProvider)
get_fee.assert_called()
2018-06-23 04:58:25 +00:00
assert backtesting.fee == 0.5
assert not backtesting.strategy.order_types["stoploss_on_exchange"]
2022-04-25 23:46:40 +00:00
assert backtesting.strategy.bot_started is True
def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None:
patch_exchange(mocker)
del default_conf['timeframe']
default_conf['strategy_list'] = [CURRENT_TEST_STRATEGY,
'HyperoptableStrategy']
mocker.patch(f'{EXMS}.get_fee', MagicMock(return_value=0.5))
with pytest.raises(OperationalException,
match=r"Timeframe needs to be set in either configuration"):
Backtesting(default_conf)
def test_data_with_fee(default_conf, mocker) -> None:
2019-10-05 13:34:31 +00:00
patch_exchange(mocker)
default_conf['fee'] = 0.1234
fee_mock = mocker.patch(f'{EXMS}.get_fee', MagicMock(return_value=0.5))
2019-10-05 13:34:31 +00:00
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
2019-10-05 13:34:31 +00:00
assert backtesting.fee == 0.1234
assert fee_mock.call_count == 0
default_conf['fee'] = 0.0
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
assert backtesting.fee == 0.0
assert fee_mock.call_count == 0
2019-10-05 13:34:31 +00:00
def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None:
2018-06-17 20:32:56 +00:00
patch_exchange(mocker)
timerange = TimeRange.parse_timerange('1510694220-1510700340')
data = history.load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange,
fill_up_missing=True)
2018-04-21 22:19:11 +00:00
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
processed = backtesting.strategy.advise_all_indicators(data)
2022-09-28 18:23:56 +00:00
assert len(processed['UNITTEST/BTC']) == 103
# Load strategy to compare the result between Backtesting function and strategy are the same
strategy = StrategyResolver.load_strategy(default_conf)
processed2 = strategy.advise_all_indicators(data)
assert processed['UNITTEST/BTC'].equals(processed2['UNITTEST/BTC'])
def test_backtest_abort(default_conf, mocker, testdatadir) -> None:
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
backtesting.check_abort()
backtesting.abort = True
with pytest.raises(DependencyException, match="Stop requested"):
backtesting.check_abort()
# abort flag resets
assert backtesting.abort is False
assert backtesting.progress.progress == 0
2023-03-26 09:30:44 +00:00
def test_backtesting_start(default_conf, mocker, caplog) -> None:
2019-12-17 22:06:03 +00:00
def get_timerange(input1):
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
2019-12-17 22:06:03 +00:00
mocker.patch('freqtrade.data.history.get_timerange', get_timerange)
2018-06-17 20:32:56 +00:00
patch_exchange(mocker)
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest')
2020-06-01 07:24:27 +00:00
mocker.patch('freqtrade.optimize.backtesting.generate_backtest_stats')
mocker.patch('freqtrade.optimize.backtesting.show_backtest_results')
2021-02-09 19:03:03 +00:00
sbs = mocker.patch('freqtrade.optimize.backtesting.store_backtest_stats')
sbc = mocker.patch('freqtrade.optimize.backtesting.store_backtest_signal_candles')
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['UNITTEST/BTC']))
default_conf['timeframe'] = '1m'
default_conf['export'] = 'signals'
2021-02-09 19:03:03 +00:00
default_conf['exportfilename'] = 'export.txt'
default_conf['timerange'] = '-1510694220'
default_conf['runmode'] = RunMode.BACKTEST
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.bot_loop_start = MagicMock()
2023-03-26 09:30:44 +00:00
backtesting.strategy.bot_start = MagicMock()
backtesting.start()
# check the logs, that will contain the backtest result
exists = [
'Backtesting with data from 2017-11-14 21:17:00 '
2021-05-06 18:49:48 +00:00
'up to 2017-11-14 22:59:00 (0 days).'
]
for line in exists:
assert log_has(line, caplog)
assert backtesting.strategy.dp._pairlists is not None
2023-03-26 09:30:44 +00:00
assert backtesting.strategy.bot_start.call_count == 1
assert backtesting.strategy.bot_loop_start.call_count == 0
2021-02-09 19:03:03 +00:00
assert sbs.call_count == 1
assert sbc.call_count == 1
def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) -> None:
2019-12-17 22:06:03 +00:00
def get_timerange(input1):
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
mocker.patch('freqtrade.data.history.history_utils.load_pair_history',
MagicMock(return_value=pd.DataFrame()))
2019-12-17 22:06:03 +00:00
mocker.patch('freqtrade.data.history.get_timerange', get_timerange)
2018-06-17 20:32:56 +00:00
patch_exchange(mocker)
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest')
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['UNITTEST/BTC']))
default_conf['timeframe'] = "1m"
default_conf['export'] = 'none'
default_conf['timerange'] = '20180101-20180102'
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
2019-10-23 18:27:51 +00:00
with pytest.raises(OperationalException, match='No data found. Terminating.'):
backtesting.start()
def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) -> None:
mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True))
mocker.patch('freqtrade.data.history.history_utils.load_pair_history',
MagicMock(return_value=pd.DataFrame()))
mocker.patch('freqtrade.data.history.get_timerange', get_timerange)
patch_exchange(mocker)
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest')
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=[]))
default_conf['timeframe'] = "1m"
default_conf['export'] = 'none'
default_conf['timerange'] = '20180101-20180102'
with pytest.raises(OperationalException, match='No pair in whitelist.'):
Backtesting(default_conf)
default_conf['pairlists'] = [{"method": "VolumePairList", "number_assets": 5}]
2021-11-25 07:56:56 +00:00
with pytest.raises(OperationalException,
match=r'VolumePairList not allowed for backtesting\..*StaticPairList.*'):
Backtesting(default_conf)
2021-08-14 14:56:49 +00:00
default_conf.update({
'pairlists': [{"method": "StaticPairList"}],
'timeframe_detail': '1d',
})
with pytest.raises(OperationalException,
match='Detail timeframe must be smaller than strategy timeframe.'):
Backtesting(default_conf)
2020-06-07 14:02:08 +00:00
def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, tickers) -> None:
mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True))
mocker.patch(f'{EXMS}.get_tickers', tickers)
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
2020-06-07 14:02:08 +00:00
mocker.patch('freqtrade.data.history.get_timerange', get_timerange)
patch_exchange(mocker)
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest')
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
2020-06-07 14:02:08 +00:00
PropertyMock(return_value=['XRP/BTC']))
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.refresh_pairlist')
2020-06-07 14:02:08 +00:00
default_conf['ticker_interval'] = "1m"
default_conf['export'] = 'none'
2020-06-07 14:02:08 +00:00
# Use stoploss from strategy
del default_conf['stoploss']
default_conf['timerange'] = '20180101-20180102'
default_conf['pairlists'] = [{"method": "VolumePairList", "number_assets": 5}]
2021-11-25 07:56:56 +00:00
with pytest.raises(OperationalException,
2022-09-26 15:10:23 +00:00
match=r'VolumePairList not allowed for backtesting\..*StaticPairList.*'):
2020-06-07 14:02:08 +00:00
Backtesting(default_conf)
default_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PerformanceFilter"}]
with pytest.raises(OperationalException,
match='PerformanceFilter not allowed for backtesting.'):
Backtesting(default_conf)
2020-06-07 14:02:08 +00:00
default_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PrecisionFilter"}, ]
Backtesting(default_conf)
# Multiple strategies
default_conf['strategy_list'] = [CURRENT_TEST_STRATEGY, 'StrategyTestV2']
with pytest.raises(OperationalException,
match='PrecisionFilter not allowed for backtesting multiple strategies.'):
Backtesting(default_conf)
2020-06-07 14:02:08 +00:00
def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
2022-04-05 18:07:58 +00:00
default_conf['use_exit_signal'] = False
mocker.patch(f'{EXMS}.get_fee', fee)
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'))
2021-02-22 05:54:33 +00:00
patch_exchange(mocker)
default_conf['stake_amount'] = 'unlimited'
2021-04-21 18:17:30 +00:00
default_conf['max_open_trades'] = 2
2021-02-22 05:54:33 +00:00
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
2021-02-22 05:54:33 +00:00
pair = 'UNITTEST/BTC'
row = [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0),
1, # Buy
2021-02-22 05:54:33 +00:00
0.001, # Open
0.0011, # Close
0, # Sell
0.00099, # Low
0.0012, # High
2021-07-20 12:08:14 +00:00
'', # Buy Signal Name
2021-02-22 05:54:33 +00:00
]
2021-08-24 04:45:09 +00:00
trade = backtesting._enter_trade(pair, row=row, direction='long')
2021-02-22 05:54:33 +00:00
assert isinstance(trade, LocalTrade)
assert trade.stake_amount == 495
2021-04-21 18:17:30 +00:00
# Fake 2 trades, so there's not enough amount for the next trade left.
LocalTrade.trades_open.append(trade)
LocalTrade.trades_open.append(trade)
backtesting.wallets.update()
2021-08-24 04:45:09 +00:00
trade = backtesting._enter_trade(pair, row=row, direction='long')
2021-02-22 05:54:33 +00:00
assert trade is None
2021-04-21 18:17:30 +00:00
LocalTrade.trades_open.pop()
2021-08-24 04:45:09 +00:00
trade = backtesting._enter_trade(pair, row=row, direction='long')
2021-04-21 18:17:30 +00:00
assert trade is not None
2021-02-22 05:54:33 +00:00
2021-07-11 12:10:41 +00:00
backtesting.strategy.custom_stake_amount = lambda **kwargs: 123.5
backtesting.wallets.update()
2021-08-24 04:45:09 +00:00
trade = backtesting._enter_trade(pair, row=row, direction='long')
2021-07-11 12:10:41 +00:00
assert trade
assert trade.stake_amount == 123.5
# In case of error - use proposed stake
backtesting.strategy.custom_stake_amount = lambda **kwargs: 20 / 0
2021-08-24 04:45:09 +00:00
trade = backtesting._enter_trade(pair, row=row, direction='long')
2021-07-11 12:10:41 +00:00
assert trade
assert trade.stake_amount == 495
2021-08-24 04:45:09 +00:00
assert trade.is_short is False
trade = backtesting._enter_trade(pair, row=row, direction='short')
assert trade
assert trade.stake_amount == 495
assert trade.is_short is True
2021-07-11 12:10:41 +00:00
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=300.0)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert trade
assert trade.stake_amount == 300.0
def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None:
2022-04-05 18:07:58 +00:00
default_conf_usdt['use_exit_signal'] = False
mocker.patch(f'{EXMS}.get_fee', fee)
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_leverage", return_value=100)
2022-08-24 18:44:48 +00:00
mocker.patch("freqtrade.optimize.backtesting.price_to_precision", lambda p, *args: p)
patch_exchange(mocker)
2022-03-02 06:14:36 +00:00
default_conf_usdt['stake_amount'] = 300
default_conf_usdt['max_open_trades'] = 2
default_conf_usdt['trading_mode'] = 'futures'
default_conf_usdt['margin_mode'] = 'isolated'
2022-03-02 06:00:07 +00:00
default_conf_usdt['stake_currency'] = 'USDT'
default_conf_usdt['exchange']['pair_whitelist'] = ['.*']
backtesting = Backtesting(default_conf_usdt)
backtesting._set_strategy(backtesting.strategylist[0])
2022-08-20 09:47:15 +00:00
pair = 'ETH/USDT:USDT'
row = [
2022-03-14 10:39:11 +00:00
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0),
2022-08-24 18:44:48 +00:00
0.1, # Open
0.12, # High
0.099, # Low
0.11, # Close
2022-03-14 10:39:11 +00:00
1, # enter_long
0, # exit_long
1, # enter_short
0, # exit_hsort
'', # Long Signal Name
'', # Short Signal Name
'', # Exit Signal Name
]
2022-02-27 20:28:28 +00:00
backtesting.strategy.leverage = MagicMock(return_value=5.0)
mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt",
2022-02-27 20:28:28 +00:00
return_value=(0.01, 0.01))
# leverage = 5
2022-08-24 18:44:48 +00:00
# ep1(trade.open_rate) = 0.1
# position(trade.amount) = 15000
2022-02-27 20:28:28 +00:00
# stake_amount = 300 -> wb = 300 / 5 = 60
# mmr = 0.01
# cum_b = 0.01
# side_1: -1 if is_short else 1
# liq_buffer = 0.05
2022-02-27 20:28:28 +00:00
#
2022-03-02 06:14:36 +00:00
# Binance, Long
# liquidation_price
# = ((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position))
2022-08-24 18:44:48 +00:00
# = ((300 + 0.01) - (1 * 15000 * 0.1)) / ((15000 * 0.01) - (1 * 15000))
# = 0.0008080740740740741
# freqtrade_liquidation_price = liq + (abs(open_rate - liq) * liq_buffer * side_1)
2022-08-24 18:44:48 +00:00
# = 0.08080740740740741 + ((0.1 - 0.08080740740740741) * 0.05 * 1)
# = 0.08176703703703704
2022-02-27 20:28:28 +00:00
trade = backtesting._enter_trade(pair, row=row, direction='long')
2022-08-24 18:44:48 +00:00
assert pytest.approx(trade.liquidation_price) == 0.081767037
2022-02-27 20:28:28 +00:00
2022-03-02 06:14:36 +00:00
# Binance, Short
# liquidation_price
# = ((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position))
2022-08-24 18:44:48 +00:00
# = ((300 + 0.01) - ((-1) * 15000 * 0.1)) / ((15000 * 0.01) - ((-1) * 15000))
# = 0.0011881254125412541
# freqtrade_liquidation_price = liq + (abs(open_rate - liq) * liq_buffer * side_1)
2022-08-24 18:44:48 +00:00
# = 0.11881254125412541 + (abs(0.1 - 0.11881254125412541) * 0.05 * -1)
# = 0.11787191419141915
2022-03-02 06:14:36 +00:00
2022-02-27 20:28:28 +00:00
trade = backtesting._enter_trade(pair, row=row, direction='short')
2022-08-24 18:44:48 +00:00
assert pytest.approx(trade.liquidation_price) == 0.11787191
2022-02-27 20:28:28 +00:00
2021-02-22 05:54:33 +00:00
# Stake-amount too high!
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=600.0)
2021-02-22 05:54:33 +00:00
2021-08-24 04:45:09 +00:00
trade = backtesting._enter_trade(pair, row=row, direction='long')
2021-02-22 05:54:33 +00:00
assert trade is None
2021-04-21 18:17:30 +00:00
# Stake-amount throwing error
2021-02-22 05:54:33 +00:00
mocker.patch("freqtrade.wallets.Wallets.get_trade_stake_amount",
side_effect=DependencyException)
2021-08-24 04:45:09 +00:00
trade = backtesting._enter_trade(pair, row=row, direction='long')
2021-02-22 05:54:33 +00:00
assert trade is None
2023-01-21 18:46:27 +00:00
def test_backtest__check_trade_exit(default_conf, fee, mocker) -> None:
2022-04-05 18:07:58 +00:00
default_conf['use_exit_signal'] = False
mocker.patch(f'{EXMS}.get_fee', fee)
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'))
patch_exchange(mocker)
default_conf['timeframe_detail'] = '1m'
default_conf['max_open_trades'] = 2
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
pair = 'UNITTEST/BTC'
row = [
pd.Timestamp(year=2020, month=1, day=1, hour=4, minute=55, tzinfo=timezone.utc),
200, # Open
201.5, # High
2021-09-04 18:23:51 +00:00
195, # Low
201, # Close
1, # enter_long
0, # exit_long
0, # enter_short
0, # exit_hsort
'', # Long Signal Name
'', # Short Signal Name
2021-10-21 14:25:38 +00:00
'', # Exit Signal Name
]
2021-09-04 18:23:51 +00:00
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert isinstance(trade, LocalTrade)
row_sell = [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0, tzinfo=timezone.utc),
200, # Open
210.5, # High
2021-09-04 18:23:51 +00:00
195, # Low
201, # Close
0, # enter_long
0, # exit_long
0, # enter_short
0, # exit_short
'', # long Signal Name
'', # Short Signal Name
2021-10-21 14:25:38 +00:00
'', # Exit Signal Name
2021-11-06 14:24:52 +00:00
]
# No data available.
2023-01-21 18:46:27 +00:00
res = backtesting._check_trade_exit(trade, row_sell)
assert res is not None
2022-03-24 19:33:47 +00:00
assert res.exit_reason == ExitType.ROI.value
assert res.close_date_utc == datetime(2020, 1, 1, 5, 0, tzinfo=timezone.utc)
# Enter new trade
2021-09-04 18:23:51 +00:00
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert isinstance(trade, LocalTrade)
# Assign empty ... no result.
backtesting.detail_data[pair] = pd.DataFrame(
2021-09-04 18:23:51 +00:00
[], columns=['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long',
2021-11-06 14:24:52 +00:00
'enter_short', 'exit_short', 'long_tag', 'short_tag', 'exit_tag'])
2023-01-21 18:46:27 +00:00
res = backtesting._check_trade_exit(trade, row)
assert res is None
2021-01-24 08:56:27 +00:00
def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
2022-04-05 18:07:58 +00:00
default_conf['use_exit_signal'] = False
default_conf['max_open_trades'] = 10
mocker.patch(f'{EXMS}.get_fee', fee)
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'))
2018-06-17 20:32:56 +00:00
patch_exchange(mocker)
2018-04-21 22:19:11 +00:00
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
pair = 'UNITTEST/BTC'
timerange = TimeRange('date', None, 1517227800, 0)
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'],
timerange=timerange)
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
result = backtesting.backtest(
processed=deepcopy(processed),
2019-12-13 23:12:44 +00:00
start_date=min_date,
end_date=max_date,
)
results = result['results']
assert not results.empty
assert len(results) == 2
expected = pd.DataFrame(
{'pair': [pair, pair],
2021-01-24 08:56:27 +00:00
'stake_amount': [0.001, 0.001],
'max_stake_amount': [0.001, 0.001],
2021-01-24 08:56:27 +00:00
'amount': [0.00957442, 0.0097064],
2020-06-26 07:21:28 +00:00
'open_date': pd.to_datetime([Arrow(2018, 1, 29, 18, 40, 0).datetime,
Arrow(2018, 1, 30, 3, 30, 0).datetime], utc=True
),
2020-06-26 07:21:28 +00:00
'close_date': pd.to_datetime([Arrow(2018, 1, 29, 22, 35, 0).datetime,
Arrow(2018, 1, 30, 4, 10, 0).datetime], utc=True),
2021-01-24 08:56:27 +00:00
'open_rate': [0.104445, 0.10302485],
2020-06-26 18:21:08 +00:00
'close_rate': [0.104969, 0.103541],
2021-01-24 08:56:27 +00:00
'fee_open': [0.0025, 0.0025],
'fee_close': [0.0025, 0.0025],
'trade_duration': [235, 40],
2021-01-24 08:56:27 +00:00
'profit_ratio': [0.0, 0.0],
'profit_abs': [0.0, 0.0],
2022-03-24 19:33:47 +00:00
'exit_reason': [ExitType.ROI.value, ExitType.ROI.value],
2021-01-24 08:56:27 +00:00
'initial_stop_loss_abs': [0.0940005, 0.09272236],
'initial_stop_loss_ratio': [-0.1, -0.1],
'stop_loss_abs': [0.0940005, 0.09272236],
'stop_loss_ratio': [-0.1, -0.1],
'min_rate': [0.10370188, 0.10300000000000001],
2021-01-24 08:56:27 +00:00
'max_rate': [0.10501, 0.1038888],
'is_open': [False, False],
'enter_tag': [None, None],
"leverage": [1.0, 1.0],
2021-11-18 19:34:59 +00:00
"is_short": [False, False],
2022-05-26 05:17:22 +00:00
'open_timestamp': [1517251200000, 1517283000000],
'close_timestamp': [1517265300000, 1517285400000],
'orders': [
[
{'amount': 0.00957442, 'safe_price': 0.104445, 'ft_order_side': 'buy',
'order_filled_timestamp': 1517251200000, 'ft_is_entry': True},
{'amount': 0.00957442, 'safe_price': 0.10496853383458644, 'ft_order_side': 'sell',
'order_filled_timestamp': 1517265300000, 'ft_is_entry': False}
], [
{'amount': 0.0097064, 'safe_price': 0.10302485, 'ft_order_side': 'buy',
'order_filled_timestamp': 1517283000000, 'ft_is_entry': True},
{'amount': 0.0097064, 'safe_price': 0.10354126528822055, 'ft_order_side': 'sell',
'order_filled_timestamp': 1517285400000, 'ft_is_entry': False}
]
]
2018-07-11 18:03:40 +00:00
})
pd.testing.assert_frame_equal(results, expected)
2022-05-26 05:17:22 +00:00
assert 'orders' in results.columns
data_pair = processed[pair]
for _, t in results.iterrows():
2022-05-26 05:17:22 +00:00
assert len(t['orders']) == 2
2020-06-26 07:21:28 +00:00
ln = data_pair.loc[data_pair["date"] == t["open_date"]]
2022-11-05 19:01:05 +00:00
# Check open trade rate aligns to open rate
assert not ln.empty
2018-07-08 18:18:34 +00:00
assert round(ln.iloc[0]["open"], 6) == round(t["open_rate"], 6)
2022-11-05 19:01:05 +00:00
# check close trade rate aligns to close rate or is between high and low
ln1 = data_pair.loc[data_pair["date"] == t["close_date"]]
assert (round(ln1.iloc[0]["open"], 6) == round(t["close_rate"], 6) or
round(ln1.iloc[0]["low"], 6) < round(
t["close_rate"], 6) < round(ln1.iloc[0]["high"], 6))
2022-11-05 19:01:05 +00:00
@pytest.mark.parametrize('use_detail', [True, False])
def test_backtest_one_detail(default_conf_usdt, fee, mocker, testdatadir, use_detail) -> None:
default_conf_usdt['use_exit_signal'] = False
mocker.patch(f'{EXMS}.get_fee', fee)
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'))
2022-11-05 19:01:05 +00:00
if use_detail:
default_conf_usdt['timeframe_detail'] = '1m'
patch_exchange(mocker)
def advise_entry(df, *args, **kwargs):
# Mock function to force several entries
df.loc[(df['rsi'] < 40), 'enter_long'] = 1
return df
def custom_entry_price(proposed_rate, **kwargs):
return proposed_rate * 0.997
default_conf_usdt['max_open_trades'] = 10
2022-11-05 19:01:05 +00:00
backtesting = Backtesting(default_conf_usdt)
backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.populate_entry_trend = advise_entry
backtesting.strategy.custom_entry_price = custom_entry_price
pair = 'XRP/ETH'
# Pick a timerange adapted to the pair we use to test
timerange = TimeRange.parse_timerange('20191010-20191013')
2023-01-20 07:27:35 +00:00
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=[pair],
2022-11-05 19:01:05 +00:00
timerange=timerange)
if use_detail:
2023-01-20 07:27:35 +00:00
data_1m = history.load_data(datadir=testdatadir, timeframe='1m', pairs=[pair],
2022-11-05 19:01:05 +00:00
timerange=timerange)
backtesting.detail_data = data_1m
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
result = backtesting.backtest(
processed=deepcopy(processed),
start_date=min_date,
end_date=max_date,
)
results = result['results']
assert not results.empty
# Timeout settings from default_conf = entry: 10, exit: 30
assert len(results) == (2 if use_detail else 3)
assert 'orders' in results.columns
data_pair = processed[pair]
data_1m_pair = data_1m[pair] if use_detail else pd.DataFrame()
late_entry = 0
for _, t in results.iterrows():
assert len(t['orders']) == 2
entryo = t['orders'][0]
entry_ts = datetime.fromtimestamp(entryo['order_filled_timestamp'] // 1000, tz=timezone.utc)
if entry_ts > t['open_date']:
late_entry += 1
# Get "entry fill" candle
ln = (data_1m_pair.loc[data_1m_pair["date"] == entry_ts]
if use_detail else data_pair.loc[data_pair["date"] == entry_ts])
# Check open trade rate aligns to open rate
assert not ln.empty
# assert round(ln.iloc[0]["open"], 6) == round(t["open_rate"], 6)
assert round(ln.iloc[0]["low"], 6) <= round(
t["open_rate"], 6) <= round(ln.iloc[0]["high"], 6)
# check close trade rate aligns to close rate or is between high and low
ln1 = data_pair.loc[data_pair["date"] == t["close_date"]]
if use_detail:
ln1_1m = data_1m_pair.loc[data_1m_pair["date"] == t["close_date"]]
assert not ln1.empty or not ln1_1m.empty
else:
assert not ln1.empty
ln2 = ln1_1m if ln1.empty else ln1
assert (round(ln2.iloc[0]["low"], 6) <= round(
t["close_rate"], 6) <= round(ln2.iloc[0]["high"], 6))
assert late_entry > 0
2023-01-20 08:37:28 +00:00
@pytest.mark.parametrize('use_detail', [True, False])
def test_backtest_one_detail_futures(
default_conf_usdt, fee, mocker, testdatadir, use_detail) -> None:
default_conf_usdt['use_exit_signal'] = False
default_conf_usdt['trading_mode'] = 'futures'
default_conf_usdt['margin_mode'] = 'isolated'
default_conf_usdt['candle_type_def'] = CandleType.FUTURES
mocker.patch(f'{EXMS}.get_fee', fee)
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'))
2023-01-20 08:37:28 +00:00
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['XRP/USDT:USDT']))
mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt",
2023-01-20 08:37:28 +00:00
return_value=(0.01, 0.01))
default_conf_usdt['timeframe'] = '1h'
if use_detail:
default_conf_usdt['timeframe_detail'] = '5m'
patch_exchange(mocker)
def advise_entry(df, *args, **kwargs):
# Mock function to force several entries
df.loc[(df['rsi'] < 40), 'enter_long'] = 1
return df
def custom_entry_price(proposed_rate, **kwargs):
return proposed_rate * 0.997
default_conf_usdt['max_open_trades'] = 10
backtesting = Backtesting(default_conf_usdt)
backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.populate_entry_trend = advise_entry
backtesting.strategy.custom_entry_price = custom_entry_price
pair = 'XRP/USDT:USDT'
# Pick a timerange adapted to the pair we use to test
timerange = TimeRange.parse_timerange('20211117-20211119')
data = history.load_data(datadir=Path(testdatadir), timeframe='1h', pairs=[pair],
timerange=timerange, candle_type=CandleType.FUTURES)
backtesting.load_bt_data_detail()
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
result = backtesting.backtest(
processed=deepcopy(processed),
start_date=min_date,
end_date=max_date,
)
results = result['results']
assert not results.empty
# Timeout settings from default_conf = entry: 10, exit: 30
assert len(results) == (5 if use_detail else 2)
assert 'orders' in results.columns
data_pair = processed[pair]
data_1m_pair = backtesting.detail_data[pair] if use_detail else pd.DataFrame()
late_entry = 0
for _, t in results.iterrows():
assert len(t['orders']) == 2
entryo = t['orders'][0]
entry_ts = datetime.fromtimestamp(entryo['order_filled_timestamp'] // 1000, tz=timezone.utc)
if entry_ts > t['open_date']:
late_entry += 1
# Get "entry fill" candle
ln = (data_1m_pair.loc[data_1m_pair["date"] == entry_ts]
if use_detail else data_pair.loc[data_pair["date"] == entry_ts])
# Check open trade rate aligns to open rate
assert not ln.empty
assert round(ln.iloc[0]["low"], 6) <= round(
t["open_rate"], 6) <= round(ln.iloc[0]["high"], 6)
# check close trade rate aligns to close rate or is between high and low
ln1 = data_pair.loc[data_pair["date"] == t["close_date"]]
if use_detail:
ln1_1m = data_1m_pair.loc[data_1m_pair["date"] == t["close_date"]]
assert not ln1.empty or not ln1_1m.empty
else:
assert not ln1.empty
ln2 = ln1_1m if ln1.empty else ln1
assert (round(ln2.iloc[0]["low"], 6) <= round(
t["close_rate"], 6) <= round(ln2.iloc[0]["high"], 6))
assert -0.0181 < Trade.trades[1].funding_fees < -0.01
# assert late_entry > 0
@pytest.mark.parametrize('use_detail', [True, False])
def test_backtest_one_detail_futures_funding_fees(
default_conf_usdt, fee, mocker, testdatadir, use_detail) -> None:
default_conf_usdt['use_exit_signal'] = False
default_conf_usdt['trading_mode'] = 'futures'
default_conf_usdt['margin_mode'] = 'isolated'
default_conf_usdt['candle_type_def'] = CandleType.FUTURES
default_conf_usdt['minimal_roi'] = {'0': 1}
default_conf_usdt['dry_run_wallet'] = 100000
mocker.patch(f'{EXMS}.get_fee', fee)
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('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['XRP/USDT:USDT']))
mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt",
return_value=(0.01, 0.01))
default_conf_usdt['timeframe'] = '1h'
if use_detail:
default_conf_usdt['timeframe_detail'] = '5m'
patch_exchange(mocker)
def advise_entry(df, *args, **kwargs):
# Mock function to force several entries
df.loc[:, 'enter_long'] = 1
return df
def adjust_trade_position(trade, current_time, **kwargs):
if current_time > datetime(2021, 11, 18, 2, 0, 0, tzinfo=timezone.utc):
return None
return default_conf_usdt['stake_amount']
default_conf_usdt['max_open_trades'] = 1
backtesting = Backtesting(default_conf_usdt)
backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.populate_entry_trend = advise_entry
backtesting.strategy.adjust_trade_position = adjust_trade_position
backtesting.strategy.leverage = lambda **kwargs: 1
backtesting.strategy.position_adjustment_enable = True
pair = 'XRP/USDT:USDT'
# Pick a timerange adapted to the pair we use to test
timerange = TimeRange.parse_timerange('20211117-20211119')
data = history.load_data(datadir=Path(testdatadir), timeframe='1h', pairs=[pair],
timerange=timerange, candle_type=CandleType.FUTURES)
backtesting.load_bt_data_detail()
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
result = backtesting.backtest(
processed=deepcopy(processed),
start_date=min_date,
end_date=max_date,
)
results = result['results']
assert not results.empty
# Only one result - as we're not selling.
assert len(results) == 1
assert 'orders' in results.columns
for t in Trade.trades:
# At least 4 adjustment orders
assert t.nr_of_successful_entries >= 6
# Funding fees will vary depending on the number of adjustment orders
# That number is a lot higher with detail data.
assert -20 < t.funding_fees < -0.1
def test_backtest_timedout_entry_orders(default_conf, fee, mocker, testdatadir) -> None:
# This strategy intentionally places unfillable orders.
default_conf['strategy'] = 'StrategyTestV3CustomEntryPrice'
default_conf['startup_candle_count'] = 0
# Cancel unfilled order after 4 minutes on 5m timeframe.
default_conf["unfilledtimeout"] = {"entry": 4}
mocker.patch(f'{EXMS}.get_fee', fee)
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'))
patch_exchange(mocker)
default_conf['max_open_trades'] = 1
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
# Testing dataframe contains 11 candles. Expecting 10 timed out orders.
timerange = TimeRange('date', 'date', 1517227800, 1517231100)
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'],
timerange=timerange)
min_date, max_date = get_timerange(data)
result = backtesting.backtest(
processed=deepcopy(data),
start_date=min_date,
end_date=max_date,
)
assert result['timedout_entry_orders'] == 10
def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None:
2022-04-05 18:07:58 +00:00
default_conf['use_exit_signal'] = False
default_conf['max_open_trades'] = 1
mocker.patch(f'{EXMS}.get_fee', fee)
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'))
2018-06-17 20:32:56 +00:00
patch_exchange(mocker)
2018-04-21 22:19:11 +00:00
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
# Run a backtesting for an exiting 1min timeframe
timerange = TimeRange.parse_timerange('1510688220-1510700340')
data = history.load_data(datadir=testdatadir, timeframe='1m', pairs=['UNITTEST/BTC'],
timerange=timerange)
processed = backtesting.strategy.advise_all_indicators(data)
2019-12-17 22:06:03 +00:00
min_date, max_date = get_timerange(processed)
results = backtesting.backtest(
2019-12-13 23:12:44 +00:00
processed=processed,
start_date=min_date,
end_date=max_date,
)
assert not results['results'].empty
assert len(results['results']) == 1
2022-04-02 14:12:19 +00:00
def test_backtest_trim_no_data_left(default_conf, fee, mocker, testdatadir) -> None:
2022-04-05 18:07:58 +00:00
default_conf['use_exit_signal'] = False
default_conf['max_open_trades'] = 10
mocker.patch(f'{EXMS}.get_fee', fee)
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'))
2022-04-02 14:12:19 +00:00
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
timerange = TimeRange('date', None, 1517227800, 0)
backtesting.required_startup = 100
backtesting.timerange = timerange
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'],
timerange=timerange)
df = data['UNITTEST/BTC']
2022-09-27 08:10:58 +00:00
df['date'] = df.loc[:, 'date'] - timedelta(days=1)
2022-04-02 14:12:19 +00:00
# Trimming 100 candles, so after 2nd trimming, no candle is left.
df = df.iloc[:100]
data['XRP/USDT'] = df
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
backtesting.backtest(
processed=deepcopy(processed),
start_date=min_date,
end_date=max_date,
)
2019-09-07 18:56:03 +00:00
def test_processed(default_conf, mocker, testdatadir) -> None:
2018-06-17 20:32:56 +00:00
patch_exchange(mocker)
2018-04-21 22:19:11 +00:00
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
2019-09-07 18:56:03 +00:00
dict_of_tickerrows = load_data_test('raise', testdatadir)
dataframes = backtesting.strategy.advise_all_indicators(dict_of_tickerrows)
dataframe = dataframes['UNITTEST/BTC']
cols = dataframe.columns
# assert the dataframe got some of the indicator columns
for col in ['close', 'high', 'low', 'open', 'date',
'ema10', 'rsi', 'fastd', 'plus_di']:
assert col in cols
def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadir) -> None:
2022-04-05 18:07:58 +00:00
default_conf['use_exit_signal'] = False
default_conf['max_open_trades'] = 10
mocker.patch(f'{EXMS}.get_fee', fee)
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=100000)
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
timerange = TimeRange('date', None, 1517227800, 0)
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'],
timerange=timerange)
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
global count
count = 0
def tmp_confirm_entry(pair, current_time, **kwargs):
dp = backtesting.strategy.dp
df, _ = dp.get_analyzed_dataframe(pair, backtesting.strategy.timeframe)
current_candle = df.iloc[-1].squeeze()
2022-02-11 16:02:04 +00:00
assert current_candle['enter_long'] == 1
candle_date = timeframe_to_next_date(backtesting.strategy.timeframe, current_candle['date'])
assert candle_date == current_time
# These asserts don't properly raise as they are nested,
# therefore we increment count and assert for that.
global count
count = count + 1
backtesting.strategy.confirm_trade_entry = tmp_confirm_entry
backtesting.backtest(
processed=deepcopy(processed),
start_date=min_date,
end_date=max_date,
)
assert count == 5
def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatadir) -> None:
2020-12-02 06:42:39 +00:00
# While this test IS a copy of test_backtest_pricecontours, it's needed to ensure
# results do not carry-over to the next run, which is not given by using parametrize.
patch_exchange(mocker)
default_conf['protections'] = [
{
"method": "CooldownPeriod",
"stop_duration": 3,
}]
default_conf['enable_protections'] = True
default_conf['timeframe'] = '1m'
default_conf['max_open_trades'] = 1
mocker.patch(f'{EXMS}.get_fee', fee)
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'))
tests = [
['sine', 9],
['raise', 10],
['lower', 0],
['sine', 9],
['raise', 10],
]
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
2022-03-09 06:03:37 +00:00
# While entry-signals are unrealistic, running backtesting
# over and over again should not cause different results
for [contour, numres] in tests:
2022-01-23 16:42:18 +00:00
# Debug output for random test failure
print(f"{contour}, {numres}")
data = load_data_test(contour, testdatadir)
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
assert isinstance(processed, dict)
results = backtesting.backtest(
processed=processed,
start_date=min_date,
end_date=max_date,
)
assert len(results['results']) == numres
2020-12-02 06:42:39 +00:00
@pytest.mark.parametrize('protections,contour,expected', [
(None, 'sine', 35),
(None, 'raise', 19),
(None, 'lower', 0),
(None, 'sine', 35),
(None, 'raise', 19),
([{"method": "CooldownPeriod", "stop_duration": 3}], 'sine', 9),
([{"method": "CooldownPeriod", "stop_duration": 3}], 'raise', 10),
([{"method": "CooldownPeriod", "stop_duration": 3}], 'lower', 0),
([{"method": "CooldownPeriod", "stop_duration": 3}], 'sine', 9),
([{"method": "CooldownPeriod", "stop_duration": 3}], 'raise', 10),
])
def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir,
protections, contour, expected) -> None:
if protections:
default_conf['protections'] = protections
default_conf['enable_protections'] = True
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_fee', fee)
2022-03-09 06:03:37 +00:00
# While entry-signals are unrealistic, running backtesting
2020-12-02 06:42:39 +00:00
# over and over again should not cause different results
patch_exchange(mocker)
default_conf['timeframe'] = '1m'
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
data = load_data_test(contour, testdatadir)
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
assert isinstance(processed, dict)
backtesting.strategy.max_open_trades = 1
backtesting.config.update({'max_open_trades': 1})
results = backtesting.backtest(
processed=processed,
start_date=min_date,
end_date=max_date,
)
assert len(results['results']) == expected
2019-09-07 18:56:03 +00:00
def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
# Override the default buy trend function in our StrategyTest
2018-06-26 05:08:09 +00:00
def fun(dataframe=None, pair=None):
buy_value = 1
sell_value = 1
return _trend(dataframe, buy_value, sell_value)
default_conf['max_open_trades'] = 10
2019-09-07 18:56:03 +00:00
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir)
2018-04-21 22:19:11 +00:00
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
2021-09-22 18:42:31 +00:00
backtesting.strategy.advise_entry = fun # Override
backtesting.strategy.advise_exit = fun # Override
result = backtesting.backtest(**backtest_conf)
assert result['results'].empty
2019-09-07 18:56:03 +00:00
def test_backtest_only_sell(mocker, default_conf, testdatadir):
# Override the default buy trend function in our StrategyTest
2018-06-26 05:08:09 +00:00
def fun(dataframe=None, pair=None):
buy_value = 0
sell_value = 1
return _trend(dataframe, buy_value, sell_value)
default_conf['max_open_trades'] = 10
2019-09-07 18:56:03 +00:00
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir)
2018-04-21 22:19:11 +00:00
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
2021-09-22 18:42:31 +00:00
backtesting.strategy.advise_entry = fun # Override
backtesting.strategy.advise_exit = fun # Override
result = backtesting.backtest(**backtest_conf)
assert result['results'].empty
2019-09-07 18:56:03 +00:00
def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
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_fee', fee)
default_conf['max_open_trades'] = 10
2019-09-07 18:56:03 +00:00
backtest_conf = _make_backtest_conf(mocker, conf=default_conf,
pair='UNITTEST/BTC', datadir=testdatadir)
default_conf['timeframe'] = '1m'
2018-04-21 22:19:11 +00:00
backtesting = Backtesting(default_conf)
backtesting.required_startup = 0
backtesting._set_strategy(backtesting.strategylist[0])
2021-09-22 18:42:31 +00:00
backtesting.strategy.advise_entry = _trend_alternate # Override
backtesting.strategy.advise_exit = _trend_alternate # Override
result = backtesting.backtest(**backtest_conf)
# 200 candles in backtest data
# won't buy on first (shifted by 1)
# 100 buys signals
results = result['results']
assert len(results) == 100
# Cached data should be 200
2021-12-02 18:05:06 +00:00
analyzed_df = backtesting.dataprovider.get_analyzed_dataframe('UNITTEST/BTC', '1m')[0]
assert len(analyzed_df) == 200
# Expect last candle to be 1 below end date (as the last candle is assumed as "incomplete"
# during backtesting)
expected_last_candle_date = backtest_conf['end_date'] - timedelta(minutes=1)
assert analyzed_df.iloc[-1]['date'].to_pydatetime() == expected_last_candle_date
# One trade was force-closed at the end
assert len(results.loc[results['is_open']]) == 0
@pytest.mark.parametrize("pair", ['ADA/BTC', 'LTC/BTC'])
@pytest.mark.parametrize("tres", [0, 20, 30])
2019-09-07 18:56:03 +00:00
def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir):
2018-11-05 19:03:04 +00:00
def _trend_alternate_hold(dataframe=None, metadata=None):
"""
2019-03-26 17:53:16 +00:00
Buy every xth candle - sell every other xth -2 (hold on to pairs a bit)
2018-11-05 19:03:04 +00:00
"""
if metadata['pair'] in ('ETH/BTC', 'LTC/BTC'):
multi = 20
else:
multi = 18
2021-08-24 04:45:09 +00:00
dataframe['enter_long'] = np.where(dataframe.index % multi == 0, 1, 0)
dataframe['exit_long'] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0)
dataframe['enter_short'] = 0
dataframe['exit_short'] = 0
2018-11-05 19:03:04 +00:00
return dataframe
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_fee', fee)
2018-11-06 19:37:59 +00:00
patch_exchange(mocker)
2018-11-05 19:03:04 +00:00
pairs = ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC', 'NXT/BTC']
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=pairs)
# Only use 500 lines to increase performance
2018-11-05 19:03:04 +00:00
data = trim_dictlist(data, -500)
# Remove data for one pair from the beginning of the data
if tres > 0:
data[pair] = data[pair][tres:].reset_index()
default_conf['timeframe'] = '5m'
default_conf['max_open_trades'] = 3
2018-11-05 19:03:04 +00:00
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
2021-09-22 18:42:31 +00:00
backtesting.strategy.advise_entry = _trend_alternate_hold # Override
backtesting.strategy.advise_exit = _trend_alternate_hold # Override
2018-11-05 19:03:04 +00:00
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
2018-11-05 19:03:04 +00:00
backtest_conf = {
'processed': deepcopy(processed),
2018-11-05 19:03:04 +00:00
'start_date': min_date,
'end_date': max_date,
}
2019-12-13 23:12:44 +00:00
results = backtesting.backtest(**backtest_conf)
2018-11-05 19:03:04 +00:00
# Make sure we have parallel trades
assert len(evaluate_result_multi(results['results'], '5m', 2)) > 0
2018-11-05 19:03:04 +00:00
# make sure we don't have trades with more than configured max_open_trades
assert len(evaluate_result_multi(results['results'], '5m', 3)) == 0
2018-11-05 19:03:04 +00:00
# Cached data correctly removed amounts
offset = 1 if tres == 0 else 0
removed_candles = len(data[pair]) - offset - backtesting.strategy.startup_candle_count
assert len(backtesting.dataprovider.get_analyzed_dataframe(pair, '5m')[0]) == removed_candles
assert len(
backtesting.dataprovider.get_analyzed_dataframe('NXT/BTC', '5m')[0]
) == len(data['NXT/BTC']) - 1 - backtesting.strategy.startup_candle_count
backtesting.strategy.max_open_trades = 1
backtesting.config.update({'max_open_trades': 1})
2018-11-05 19:03:04 +00:00
backtest_conf = {
'processed': deepcopy(processed),
2018-11-05 19:03:04 +00:00
'start_date': min_date,
'end_date': max_date,
}
2019-12-13 23:12:44 +00:00
results = backtesting.backtest(**backtest_conf)
assert len(evaluate_result_multi(results['results'], '5m', 1)) == 0
2018-11-05 19:03:04 +00:00
2019-09-08 07:54:15 +00:00
def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
2019-12-27 09:29:06 +00:00
patch_exchange(mocker)
2020-06-01 07:24:27 +00:00
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest')
mocker.patch('freqtrade.optimize.backtesting.generate_backtest_stats')
mocker.patch('freqtrade.optimize.backtesting.show_backtest_results')
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['UNITTEST/BTC']))
patched_configuration_load_config_file(mocker, default_conf)
args = [
2019-09-14 11:18:52 +00:00
'backtesting',
'--config', 'config.json',
'--strategy', CURRENT_TEST_STRATEGY,
2019-09-08 07:54:15 +00:00
'--datadir', str(testdatadir),
'--timeframe', '1m',
'--timerange', '1510694220-1510700340',
2018-07-17 19:05:03 +00:00
'--enable-position-stacking',
'--disable-max-market-positions'
]
args = get_args(args)
start_backtesting(args)
# check the logs, that will contain the backtest result
exists = [
'Parameter -i/--timeframe detected ... Using timeframe: 1m ...',
2018-07-17 19:05:03 +00:00
'Ignoring max_open_trades (--disable-max-market-positions was used) ...',
'Parameter --timerange detected: 1510694220-1510700340 ...',
2019-09-08 07:54:15 +00:00
f'Using data directory: {testdatadir} ...',
'Loading data from 2017-11-14 20:57:00 '
2022-09-28 18:23:56 +00:00
'up to 2017-11-14 22:59:00 (0 days).',
'Backtesting with data from 2017-11-14 21:17:00 '
2022-09-28 18:23:56 +00:00
'up to 2017-11-14 22:59:00 (0 days).',
'Parameter --enable-position-stacking detected ...'
]
for line in exists:
assert log_has(line, caplog)
2018-07-28 05:55:59 +00:00
@pytest.mark.filterwarnings("ignore:deprecated")
2019-09-08 07:54:15 +00:00
def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
2021-06-26 14:39:01 +00:00
default_conf.update({
2022-04-05 18:07:58 +00:00
"use_exit_signal": True,
"exit_profit_only": False,
"exit_profit_offset": 0.0,
"ignore_roi_if_entry_signal": False,
})
2019-12-27 09:29:06 +00:00
patch_exchange(mocker)
backtestmock = MagicMock(return_value={
'results': pd.DataFrame(columns=BT_DATA_COLUMNS),
'config': default_conf,
'locks': [],
2021-05-23 07:46:51 +00:00
'rejected_signals': 20,
2022-02-07 18:33:22 +00:00
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
2022-05-16 22:42:48 +00:00
'canceled_trade_entries': 0,
'canceled_entry_orders': 0,
'replaced_entry_orders': 0,
'final_balance': 1000,
2021-07-21 13:05:35 +00:00
})
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['UNITTEST/BTC']))
2018-07-28 05:55:59 +00:00
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
2020-06-07 09:35:02 +00:00
text_table_mock = MagicMock()
2020-05-25 18:00:05 +00:00
sell_reason_mock = MagicMock()
2020-06-07 09:35:02 +00:00
strattable_mock = MagicMock()
strat_summary = MagicMock()
2020-05-25 18:00:05 +00:00
mocker.patch.multiple('freqtrade.optimize.optimize_reports',
2020-06-07 09:35:02 +00:00
text_table_bt_results=text_table_mock,
text_table_strategy=strattable_mock,
generate_pair_metrics=MagicMock(),
2022-03-24 19:33:47 +00:00
generate_exit_reason_stats=sell_reason_mock,
2021-04-25 13:34:15 +00:00
generate_strategy_comparison=strat_summary,
2020-07-03 18:15:20 +00:00
generate_daily_stats=MagicMock(),
2020-05-25 18:00:05 +00:00
)
patched_configuration_load_config_file(mocker, default_conf)
2018-07-28 05:55:59 +00:00
args = [
2019-09-14 11:18:52 +00:00
'backtesting',
2018-07-28 05:55:59 +00:00
'--config', 'config.json',
2019-09-08 07:54:15 +00:00
'--datadir', str(testdatadir),
2020-02-19 06:15:55 +00:00
'--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'),
'--timeframe', '1m',
'--timerange', '1510694220-1510700340',
2018-07-28 05:55:59 +00:00
'--enable-position-stacking',
'--disable-max-market-positions',
'--strategy-list',
CURRENT_TEST_STRATEGY,
'StrategyTestV2',
2018-07-28 05:55:59 +00:00
]
args = get_args(args)
start_backtesting(args)
2018-07-28 05:55:59 +00:00
# 2 backtests, 4 tables
assert backtestmock.call_count == 2
2020-06-07 09:35:02 +00:00
assert text_table_mock.call_count == 4
assert strattable_mock.call_count == 1
2020-05-25 18:00:05 +00:00
assert sell_reason_mock.call_count == 2
2020-06-07 09:35:02 +00:00
assert strat_summary.call_count == 1
2018-07-28 05:55:59 +00:00
# check the logs, that will contain the backtest result
exists = [
'Parameter -i/--timeframe detected ... Using timeframe: 1m ...',
2018-07-28 05:55:59 +00:00
'Ignoring max_open_trades (--disable-max-market-positions was used) ...',
'Parameter --timerange detected: 1510694220-1510700340 ...',
2019-09-08 07:54:15 +00:00
f'Using data directory: {testdatadir} ...',
'Loading data from 2017-11-14 20:57:00 '
2022-09-28 18:23:56 +00:00
'up to 2017-11-14 22:59:00 (0 days).',
'Backtesting with data from 2017-11-14 21:17:00 '
2022-09-28 18:23:56 +00:00
'up to 2017-11-14 22:59:00 (0 days).',
2018-07-28 05:55:59 +00:00
'Parameter --enable-position-stacking detected ...',
f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}',
'Running backtesting for Strategy StrategyTestV2',
2018-07-28 05:55:59 +00:00
]
for line in exists:
assert log_has(line, caplog)
def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdatadir, capsys):
2021-06-26 14:39:01 +00:00
default_conf.update({
2022-04-05 18:07:58 +00:00
"use_exit_signal": True,
"exit_profit_only": False,
"exit_profit_offset": 0.0,
"ignore_roi_if_entry_signal": False,
})
patch_exchange(mocker)
result1 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'],
'profit_ratio': [0.0, 0.0],
'profit_abs': [0.0, 0.0],
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
'2018-01-30 03:30:00', ], utc=True
),
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
'2018-01-30 05:35:00', ], utc=True),
'trade_duration': [235, 40],
'is_open': [False, False],
'stake_amount': [0.01, 0.01],
'open_rate': [0.104445, 0.10302485],
'close_rate': [0.104969, 0.103541],
2021-11-18 19:34:59 +00:00
"is_short": [False, False],
2022-03-24 19:33:47 +00:00
'exit_reason': [ExitType.ROI, ExitType.ROI]
})
result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
'profit_ratio': [0.03, 0.01, 0.1],
'profit_abs': [0.01, 0.02, 0.2],
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
'2018-01-30 03:30:00',
'2018-01-30 05:30:00'], utc=True
),
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
'2018-01-30 05:35:00',
'2018-01-30 08:30:00'], utc=True),
'trade_duration': [47, 40, 20],
'is_open': [False, False, False],
'stake_amount': [0.01, 0.01, 0.01],
'open_rate': [0.104445, 0.10302485, 0.122541],
'close_rate': [0.104969, 0.103541, 0.123541],
2021-11-18 19:34:59 +00:00
"is_short": [False, False, False],
2022-03-24 19:33:47 +00:00
'exit_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS]
})
backtestmock = MagicMock(side_effect=[
{
'results': result1,
'config': default_conf,
'locks': [],
2021-05-23 07:46:51 +00:00
'rejected_signals': 20,
2022-02-07 18:33:22 +00:00
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
2022-05-16 22:42:48 +00:00
'canceled_trade_entries': 0,
'canceled_entry_orders': 0,
'replaced_entry_orders': 0,
'final_balance': 1000,
},
{
'results': result2,
'config': default_conf,
'locks': [],
2021-05-23 07:46:51 +00:00
'rejected_signals': 20,
2022-02-07 18:33:22 +00:00
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
2022-05-16 22:42:48 +00:00
'canceled_trade_entries': 0,
'canceled_entry_orders': 0,
'replaced_entry_orders': 0,
'final_balance': 1000,
}
])
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['UNITTEST/BTC']))
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
patched_configuration_load_config_file(mocker, default_conf)
args = [
'backtesting',
'--config', 'config.json',
'--datadir', str(testdatadir),
'--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'),
'--timeframe', '1m',
'--timerange', '1510694220-1510700340',
'--enable-position-stacking',
'--disable-max-market-positions',
2021-10-21 05:09:17 +00:00
'--breakdown', 'day',
'--strategy-list',
CURRENT_TEST_STRATEGY,
'StrategyTestV2',
]
args = get_args(args)
start_backtesting(args)
# check the logs, that will contain the backtest result
exists = [
'Parameter -i/--timeframe detected ... Using timeframe: 1m ...',
'Ignoring max_open_trades (--disable-max-market-positions was used) ...',
'Parameter --timerange detected: 1510694220-1510700340 ...',
f'Using data directory: {testdatadir} ...',
'Loading data from 2017-11-14 20:57:00 '
2022-09-28 18:23:56 +00:00
'up to 2017-11-14 22:59:00 (0 days).',
'Backtesting with data from 2017-11-14 21:17:00 '
2022-09-28 18:23:56 +00:00
'up to 2017-11-14 22:59:00 (0 days).',
'Parameter --enable-position-stacking detected ...',
f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}',
'Running backtesting for Strategy StrategyTestV2',
]
for line in exists:
assert log_has(line, caplog)
captured = capsys.readouterr()
assert 'BACKTESTING REPORT' in captured.out
assert 'EXIT REASON STATS' in captured.out
2021-10-21 05:09:17 +00:00
assert 'DAY BREAKDOWN' in captured.out
assert 'LEFT OPEN TRADES REPORT' in captured.out
2022-09-28 18:23:56 +00:00
assert '2017-11-14 21:17:00 -> 2017-11-14 22:59:00 | Max open trades : 1' in captured.out
assert 'STRATEGY SUMMARY' in captured.out
2021-08-14 14:56:49 +00:00
@pytest.mark.filterwarnings("ignore:deprecated")
def test_backtest_start_futures_noliq(default_conf_usdt, mocker,
caplog, testdatadir, capsys):
# Tests detail-data loading
default_conf_usdt.update({
"trading_mode": "futures",
"margin_mode": "isolated",
"use_exit_signal": True,
"exit_profit_only": False,
"exit_profit_offset": 0.0,
"ignore_roi_if_entry_signal": False,
"strategy": CURRENT_TEST_STRATEGY,
})
patch_exchange(mocker)
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
2023-01-13 19:44:32 +00:00
PropertyMock(return_value=['HULUMULU/USDT', 'XRP/USDT:USDT']))
# mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
patched_configuration_load_config_file(mocker, default_conf_usdt)
args = [
'backtesting',
'--config', 'config.json',
'--datadir', str(testdatadir),
'--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'),
'--timeframe', '1h',
]
args = get_args(args)
with pytest.raises(OperationalException, match=r"Pairs .* got no leverage tiers available\."):
start_backtesting(args)
2022-01-22 10:37:28 +00:00
@pytest.mark.filterwarnings("ignore:deprecated")
def test_backtest_start_nomock_futures(default_conf_usdt, mocker,
caplog, testdatadir, capsys):
2022-01-22 10:37:28 +00:00
# Tests detail-data loading
default_conf_usdt.update({
"trading_mode": "futures",
"margin_mode": "isolated",
2022-04-05 18:07:58 +00:00
"use_exit_signal": True,
"exit_profit_only": False,
"exit_profit_offset": 0.0,
"ignore_roi_if_entry_signal": False,
2022-01-22 10:37:28 +00:00
"strategy": CURRENT_TEST_STRATEGY,
})
patch_exchange(mocker)
2023-01-13 19:44:32 +00:00
result1 = pd.DataFrame({'pair': ['XRP/USDT:USDT', 'XRP/USDT:USDT'],
2022-01-22 10:37:28 +00:00
'profit_ratio': [0.0, 0.0],
'profit_abs': [0.0, 0.0],
'open_date': pd.to_datetime(['2021-11-18 18:00:00',
'2021-11-18 03:00:00', ], utc=True
),
'close_date': pd.to_datetime(['2021-11-18 20:00:00',
'2021-11-18 05:00:00', ], utc=True),
'trade_duration': [235, 40],
'is_open': [False, False],
'is_short': [False, False],
'stake_amount': [0.01, 0.01],
'open_rate': [0.104445, 0.10302485],
'close_rate': [0.104969, 0.103541],
2022-03-24 19:33:47 +00:00
'exit_reason': [ExitType.ROI, ExitType.ROI]
2022-01-22 10:37:28 +00:00
})
2023-01-13 19:44:32 +00:00
result2 = pd.DataFrame({'pair': ['XRP/USDT:USDT', 'XRP/USDT:USDT', 'XRP/USDT:USDT'],
2022-01-22 10:37:28 +00:00
'profit_ratio': [0.03, 0.01, 0.1],
'profit_abs': [0.01, 0.02, 0.2],
'open_date': pd.to_datetime(['2021-11-19 18:00:00',
'2021-11-19 03:00:00',
'2021-11-19 05:00:00'], utc=True
),
'close_date': pd.to_datetime(['2021-11-19 20:00:00',
'2021-11-19 05:00:00',
'2021-11-19 08:00:00'], utc=True),
'trade_duration': [47, 40, 20],
'is_open': [False, False, False],
'is_short': [False, False, False],
'stake_amount': [0.01, 0.01, 0.01],
'open_rate': [0.104445, 0.10302485, 0.122541],
'close_rate': [0.104969, 0.103541, 0.123541],
2022-03-24 19:33:47 +00:00
'exit_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS]
2022-01-22 10:37:28 +00:00
})
backtestmock = MagicMock(side_effect=[
{
'results': result1,
'config': default_conf_usdt,
'locks': [],
'rejected_signals': 20,
2022-02-11 16:02:04 +00:00
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
2022-05-16 22:42:48 +00:00
'canceled_trade_entries': 0,
'canceled_entry_orders': 0,
'replaced_entry_orders': 0,
2022-01-22 10:37:28 +00:00
'final_balance': 1000,
},
{
'results': result2,
'config': default_conf_usdt,
'locks': [],
'rejected_signals': 20,
2022-02-11 16:02:04 +00:00
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
2022-05-16 22:42:48 +00:00
'canceled_trade_entries': 0,
'canceled_entry_orders': 0,
'replaced_entry_orders': 0,
2022-01-22 10:37:28 +00:00
'final_balance': 1000,
}
])
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
2023-01-13 19:44:32 +00:00
PropertyMock(return_value=['XRP/USDT:USDT']))
2022-01-22 10:37:28 +00:00
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
patched_configuration_load_config_file(mocker, default_conf_usdt)
args = [
'backtesting',
'--config', 'config.json',
'--datadir', str(testdatadir),
'--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'),
'--timeframe', '1h',
]
args = get_args(args)
start_backtesting(args)
# check the logs, that will contain the backtest result
exists = [
'Parameter -i/--timeframe detected ... Using timeframe: 1h ...',
f'Using data directory: {testdatadir} ...',
'Loading data from 2021-11-17 01:00:00 '
2022-09-28 18:23:56 +00:00
'up to 2021-11-21 04:00:00 (4 days).',
2022-01-22 10:37:28 +00:00
'Backtesting with data from 2021-11-17 21:00:00 '
2022-09-28 18:23:56 +00:00
'up to 2021-11-21 04:00:00 (3 days).',
2023-01-13 19:44:32 +00:00
'XRP/USDT:USDT, funding_rate, 8h, data starts at 2021-11-18 00:00:00',
'XRP/USDT:USDT, mark, 8h, data starts at 2021-11-18 00:00:00',
2022-01-22 10:37:28 +00:00
f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}',
]
for line in exists:
assert log_has(line, caplog)
captured = capsys.readouterr()
assert 'BACKTESTING REPORT' in captured.out
assert 'EXIT REASON STATS' in captured.out
2022-01-22 10:37:28 +00:00
assert 'LEFT OPEN TRADES REPORT' in captured.out
2021-08-14 14:56:49 +00:00
@pytest.mark.filterwarnings("ignore:deprecated")
def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
caplog, testdatadir, capsys):
# Tests detail-data loading
default_conf.update({
2022-04-05 18:07:58 +00:00
"use_exit_signal": True,
"exit_profit_only": False,
"exit_profit_offset": 0.0,
"ignore_roi_if_entry_signal": False,
2021-08-14 14:56:49 +00:00
})
patch_exchange(mocker)
result1 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'],
'profit_ratio': [0.0, 0.0],
'profit_abs': [0.0, 0.0],
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
'2018-01-30 03:30:00', ], utc=True
),
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
'2018-01-30 05:35:00', ], utc=True),
'trade_duration': [235, 40],
'is_open': [False, False],
2021-11-18 19:34:59 +00:00
'is_short': [False, False],
2021-08-14 14:56:49 +00:00
'stake_amount': [0.01, 0.01],
'open_rate': [0.104445, 0.10302485],
'close_rate': [0.104969, 0.103541],
2022-03-24 19:33:47 +00:00
'exit_reason': [ExitType.ROI, ExitType.ROI]
2021-08-14 14:56:49 +00:00
})
result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
'profit_ratio': [0.03, 0.01, 0.1],
'profit_abs': [0.01, 0.02, 0.2],
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
'2018-01-30 03:30:00',
'2018-01-30 05:30:00'], utc=True
),
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
'2018-01-30 05:35:00',
'2018-01-30 08:30:00'], utc=True),
'trade_duration': [47, 40, 20],
'is_open': [False, False, False],
2021-11-18 19:34:59 +00:00
'is_short': [False, False, False],
2021-08-14 14:56:49 +00:00
'stake_amount': [0.01, 0.01, 0.01],
'open_rate': [0.104445, 0.10302485, 0.122541],
'close_rate': [0.104969, 0.103541, 0.123541],
2022-03-24 19:33:47 +00:00
'exit_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS]
2021-08-14 14:56:49 +00:00
})
backtestmock = MagicMock(side_effect=[
{
'results': result1,
'config': default_conf,
'locks': [],
'rejected_signals': 20,
2022-02-07 18:33:22 +00:00
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
2022-05-16 22:42:48 +00:00
'canceled_trade_entries': 0,
'canceled_entry_orders': 0,
'replaced_entry_orders': 0,
2021-08-14 14:56:49 +00:00
'final_balance': 1000,
},
{
'results': result2,
'config': default_conf,
'locks': [],
'rejected_signals': 20,
2022-02-07 18:33:22 +00:00
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
2022-05-16 22:42:48 +00:00
'canceled_trade_entries': 0,
'canceled_entry_orders': 0,
'replaced_entry_orders': 0,
2021-08-14 14:56:49 +00:00
'final_balance': 1000,
}
])
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['XRP/ETH']))
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
patched_configuration_load_config_file(mocker, default_conf)
args = [
'backtesting',
'--config', 'config.json',
'--datadir', str(testdatadir),
'--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'),
'--timeframe', '5m',
'--timeframe-detail', '1m',
'--strategy-list',
CURRENT_TEST_STRATEGY
]
2021-08-14 14:56:49 +00:00
args = get_args(args)
start_backtesting(args)
# check the logs, that will contain the backtest result
exists = [
'Parameter -i/--timeframe detected ... Using timeframe: 5m ...',
'Parameter --timeframe-detail detected, using 1m for intra-candle backtesting ...',
f'Using data directory: {testdatadir} ...',
'Loading data from 2019-10-11 00:00:00 '
2022-09-28 18:23:56 +00:00
'up to 2019-10-13 11:15:00 (2 days).',
2021-08-14 14:56:49 +00:00
'Backtesting with data from 2019-10-11 01:40:00 '
2022-09-28 18:23:56 +00:00
'up to 2019-10-13 11:15:00 (2 days).',
f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}',
2021-08-14 14:56:49 +00:00
]
for line in exists:
assert log_has(line, caplog)
captured = capsys.readouterr()
assert 'BACKTESTING REPORT' in captured.out
assert 'EXIT REASON STATS' in captured.out
2021-08-14 14:56:49 +00:00
assert 'LEFT OPEN TRADES REPORT' in captured.out
@pytest.mark.filterwarnings("ignore:deprecated")
@pytest.mark.parametrize('run_id', ['2', 'changed'])
@pytest.mark.parametrize('start_delta', [{'days': 0}, {'days': 1}, {'weeks': 1}, {'weeks': 4}])
@pytest.mark.parametrize('cache', constants.BACKTEST_CACHE_AGE)
def test_backtest_start_multi_strat_caching(default_conf, mocker, caplog, testdatadir, run_id,
start_delta, cache):
default_conf.update({
2022-04-05 18:07:58 +00:00
"use_exit_signal": True,
"exit_profit_only": False,
"exit_profit_offset": 0.0,
"ignore_roi_if_entry_signal": False,
})
patch_exchange(mocker)
backtestmock = MagicMock(return_value={
'results': pd.DataFrame(columns=BT_DATA_COLUMNS),
'config': default_conf,
'locks': [],
'rejected_signals': 20,
2022-02-07 18:33:22 +00:00
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
2022-05-16 22:42:48 +00:00
'canceled_trade_entries': 0,
'canceled_entry_orders': 0,
'replaced_entry_orders': 0,
'final_balance': 1000,
})
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['UNITTEST/BTC']))
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
2022-01-08 07:41:09 +00:00
mocker.patch('freqtrade.optimize.backtesting.show_backtest_results', MagicMock())
now = min_backtest_date = datetime.now(tz=timezone.utc)
start_time = now - timedelta(**start_delta) + timedelta(hours=1)
if cache == 'none':
min_backtest_date = now + timedelta(days=1)
elif cache == 'day':
min_backtest_date = now - timedelta(days=1)
elif cache == 'week':
min_backtest_date = now - timedelta(weeks=1)
elif cache == 'month':
min_backtest_date = now - timedelta(weeks=4)
load_backtest_metadata = MagicMock(return_value={
'StrategyTestV2': {'run_id': '1', 'backtest_start_time': now.timestamp()},
'StrategyTestV3': {'run_id': run_id, 'backtest_start_time': start_time.timestamp()}
})
load_backtest_stats = MagicMock(side_effect=[
{
'metadata': {'StrategyTestV2': {'run_id': '1'}},
'strategy': {'StrategyTestV2': {}},
'strategy_comparison': [{'key': 'StrategyTestV2'}]
},
{
'metadata': {'StrategyTestV3': {'run_id': '2'}},
'strategy': {'StrategyTestV3': {}},
'strategy_comparison': [{'key': 'StrategyTestV3'}]
}
])
mocker.patch('pathlib.Path.glob', return_value=[
Path(datetime.strftime(datetime.now(), 'backtest-result-%Y-%m-%d_%H-%M-%S.json'))])
mocker.patch.multiple('freqtrade.data.btanalysis',
load_backtest_metadata=load_backtest_metadata,
load_backtest_stats=load_backtest_stats)
2022-01-08 07:41:09 +00:00
mocker.patch('freqtrade.optimize.backtesting.get_strategy_run_id', side_effect=['1', '2', '2'])
patched_configuration_load_config_file(mocker, default_conf)
args = [
'backtesting',
'--config', 'config.json',
'--datadir', str(testdatadir),
'--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'),
'--timeframe', '1m',
'--timerange', '1510694220-1510700340',
'--enable-position-stacking',
'--disable-max-market-positions',
'--cache', cache,
'--strategy-list',
'StrategyTestV2',
'StrategyTestV3',
]
args = get_args(args)
start_backtesting(args)
# check the logs, that will contain the backtest result
exists = [
'Parameter -i/--timeframe detected ... Using timeframe: 1m ...',
'Parameter --timerange detected: 1510694220-1510700340 ...',
f'Using data directory: {testdatadir} ...',
'Loading data from 2017-11-14 20:57:00 '
2022-09-28 18:23:56 +00:00
'up to 2017-11-14 22:59:00 (0 days).',
'Parameter --enable-position-stacking detected ...',
]
for line in exists:
assert log_has(line, caplog)
if cache == 'none':
assert backtestmock.call_count == 2
exists = [
'Running backtesting for Strategy StrategyTestV2',
'Running backtesting for Strategy StrategyTestV3',
'Ignoring max_open_trades (--disable-max-market-positions was used) ...',
2022-09-28 18:23:56 +00:00
'Backtesting with data from 2017-11-14 21:17:00 up to 2017-11-14 22:59:00 (0 days).',
]
elif run_id == '2' and min_backtest_date < start_time:
assert backtestmock.call_count == 0
exists = [
'Reusing result of previous backtest for StrategyTestV2',
'Reusing result of previous backtest for StrategyTestV3',
]
else:
exists = [
'Reusing result of previous backtest for StrategyTestV2',
'Running backtesting for Strategy StrategyTestV3',
'Ignoring max_open_trades (--disable-max-market-positions was used) ...',
2022-09-28 18:23:56 +00:00
'Backtesting with data from 2017-11-14 21:17:00 up to 2017-11-14 22:59:00 (0 days).',
]
assert backtestmock.call_count == 1
for line in exists:
assert log_has(line, caplog)
def test_get_strategy_run_id(default_conf_usdt):
default_conf_usdt.update({
'strategy': 'StrategyTestV2',
'max_open_trades': float('inf')
})
strategy = StrategyResolver.load_strategy(default_conf_usdt)
x = get_strategy_run_id(strategy)
assert isinstance(x, str)