2018-02-09 07:35:38 +00:00
|
|
|
# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, unused-argument
|
2017-11-24 22:58:35 +00:00
|
|
|
|
2018-03-17 21:44:47 +00:00
|
|
|
import random
|
2019-08-16 11:04:07 +00:00
|
|
|
from pathlib import Path
|
2020-04-25 13:37:13 +00:00
|
|
|
from unittest.mock import MagicMock, PropertyMock
|
2018-03-17 21:44:47 +00:00
|
|
|
|
2018-02-08 19:49:43 +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
|
|
|
|
|
2019-12-30 14:02:17 +00:00
|
|
|
from freqtrade import constants
|
2020-09-28 17:43:15 +00:00
|
|
|
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_backtesting
|
2019-07-11 18:23:23 +00:00
|
|
|
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
|
2019-12-27 09:40:14 +00:00
|
|
|
from freqtrade.data.converter import clean_ohlcv_dataframe
|
2019-03-24 14:23:14 +00:00
|
|
|
from freqtrade.data.dataprovider import DataProvider
|
2019-12-17 22:06:03 +00:00
|
|
|
from freqtrade.data.history import get_timerange
|
2019-12-30 14:02:17 +00:00
|
|
|
from freqtrade.exceptions import DependencyException, OperationalException
|
2019-05-25 18:01:43 +00:00
|
|
|
from freqtrade.optimize.backtesting import Backtesting
|
2020-02-11 22:39:15 +00:00
|
|
|
from freqtrade.resolvers import StrategyResolver
|
2018-12-25 13:35:48 +00:00
|
|
|
from freqtrade.state import RunMode
|
2018-12-13 05:35:28 +00:00
|
|
|
from freqtrade.strategy.interface import SellType
|
2019-09-08 07:54:15 +00:00
|
|
|
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
|
|
|
|
patched_configuration_load_config_file)
|
2017-11-24 22:58:35 +00:00
|
|
|
|
2020-09-28 17:43:15 +00:00
|
|
|
|
2019-10-05 13:34:31 +00:00
|
|
|
ORDER_TYPES = [
|
|
|
|
{
|
|
|
|
'buy': 'limit',
|
|
|
|
'sell': 'limit',
|
|
|
|
'stoploss': 'limit',
|
|
|
|
'stoploss_on_exchange': False
|
|
|
|
},
|
|
|
|
{
|
|
|
|
'buy': 'limit',
|
|
|
|
'sell': 'limit',
|
|
|
|
'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):
|
2019-10-19 13:21:47 +00:00
|
|
|
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
2019-12-27 09:40:14 +00:00
|
|
|
data = history.load_pair_history(pair='UNITTEST/BTC', datadir=testdatadir,
|
|
|
|
timeframe='1m', timerange=timerange,
|
|
|
|
drop_incomplete=False,
|
|
|
|
fill_up_missing=False)
|
2018-12-15 14:34:25 +00:00
|
|
|
|
2017-12-30 10:55:23 +00:00
|
|
|
base = 0.001
|
2017-12-28 14:58:02 +00:00
|
|
|
if what == 'raise':
|
2019-12-27 09:40:14 +00:00
|
|
|
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
|
|
|
|
|
2017-12-28 14:58:02 +00:00
|
|
|
if what == 'lower':
|
2019-12-27 09:40:14 +00:00
|
|
|
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
|
|
|
|
|
2017-12-28 14:58:02 +00:00
|
|
|
if what == 'sine':
|
2017-12-30 10:55:23 +00:00
|
|
|
hz = 0.1 # frequency
|
2019-12-27 09:40:14 +00:00
|
|
|
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',
|
|
|
|
fill_missing=True)}
|
2017-12-28 14:58:02 +00:00
|
|
|
|
2017-12-28 19:05:33 +00:00
|
|
|
|
2020-12-02 06:42:39 +00:00
|
|
|
def simple_backtest(config, contour, mocker, testdatadir) -> None:
|
2018-06-17 20:32:56 +00:00
|
|
|
patch_exchange(mocker)
|
2020-06-01 18:33:26 +00:00
|
|
|
config['timeframe'] = '1m'
|
2018-04-21 22:19:11 +00:00
|
|
|
backtesting = Backtesting(config)
|
2018-02-09 07:35:38 +00:00
|
|
|
|
2019-09-07 18:56:03 +00:00
|
|
|
data = load_data_test(contour, testdatadir)
|
2020-03-08 10:35:31 +00:00
|
|
|
processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
|
2019-12-17 22:06:03 +00:00
|
|
|
min_date, max_date = get_timerange(processed)
|
2017-12-28 14:58:02 +00:00
|
|
|
assert isinstance(processed, dict)
|
2018-02-09 07:35:38 +00:00
|
|
|
results = backtesting.backtest(
|
2019-12-13 23:12:44 +00:00
|
|
|
processed=processed,
|
|
|
|
stake_amount=config['stake_amount'],
|
|
|
|
start_date=min_date,
|
|
|
|
end_date=max_date,
|
|
|
|
max_open_trades=1,
|
|
|
|
position_stacking=False,
|
2020-11-25 08:53:13 +00:00
|
|
|
enable_protections=config.get('enable_protections', False),
|
2018-02-09 07:35:38 +00:00
|
|
|
)
|
2017-12-28 14:58:02 +00:00
|
|
|
# results :: <class 'pandas.core.frame.DataFrame'>
|
2020-12-02 06:42:39 +00:00
|
|
|
return results
|
2017-12-28 14:58:02 +00:00
|
|
|
|
2017-12-30 10:55:23 +00:00
|
|
|
|
2018-02-08 19:49:43 +00:00
|
|
|
# FIX: fixturize this?
|
2019-12-13 23:12:44 +00:00
|
|
|
def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC'):
|
2019-11-02 19:19:13 +00:00
|
|
|
data = history.load_data(datadir=datadir, timeframe='1m', pairs=[pair])
|
2018-06-07 07:21:07 +00:00
|
|
|
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)
|
2020-03-08 10:35:31 +00:00
|
|
|
processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
|
2019-12-17 22:06:03 +00:00
|
|
|
min_date, max_date = get_timerange(processed)
|
2018-03-04 00:42:37 +00:00
|
|
|
return {
|
2018-10-16 18:34:20 +00:00
|
|
|
'processed': processed,
|
2019-12-13 23:12:44 +00:00
|
|
|
'stake_amount': conf['stake_amount'],
|
2018-10-16 18:34:20 +00:00
|
|
|
'start_date': min_date,
|
|
|
|
'end_date': max_date,
|
2019-12-13 23:12:44 +00:00
|
|
|
'max_open_trades': 10,
|
|
|
|
'position_stacking': False,
|
2018-03-04 00:42:37 +00:00
|
|
|
}
|
2018-02-08 19:49:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _trend(signals, buy_value, sell_value):
|
|
|
|
n = len(signals['low'])
|
|
|
|
buy = np.zeros(n)
|
|
|
|
sell = np.zeros(n)
|
|
|
|
for i in range(0, len(signals['buy'])):
|
|
|
|
if random.random() > 0.5: # Both buy and sell signals at same timeframe
|
|
|
|
buy[i] = buy_value
|
|
|
|
sell[i] = sell_value
|
|
|
|
signals['buy'] = buy
|
|
|
|
signals['sell'] = sell
|
|
|
|
return signals
|
|
|
|
|
|
|
|
|
2018-07-29 19:07:21 +00:00
|
|
|
def _trend_alternate(dataframe=None, metadata=None):
|
2018-02-08 19:49:43 +00:00
|
|
|
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
|
|
|
|
signals['buy'] = buy
|
|
|
|
signals['sell'] = sell
|
|
|
|
return dataframe
|
|
|
|
|
|
|
|
|
2018-02-09 07:35:38 +00:00
|
|
|
# Unit tests
|
2020-01-26 12:33:13 +00:00
|
|
|
def test_setup_optimize_configuration_without_arguments(mocker, default_conf, caplog) -> None:
|
2019-07-14 22:44:25 +00:00
|
|
|
patched_configuration_load_config_file(mocker, default_conf)
|
2018-02-09 07:35:38 +00:00
|
|
|
|
|
|
|
args = [
|
2019-09-14 11:18:52 +00:00
|
|
|
'backtesting',
|
2018-02-09 07:35:38 +00:00
|
|
|
'--config', 'config.json',
|
2018-03-24 19:44:04 +00:00
|
|
|
'--strategy', 'DefaultStrategy',
|
2018-02-09 07:35:38 +00:00
|
|
|
]
|
|
|
|
|
2020-01-26 12:33:13 +00:00
|
|
|
config = setup_optimize_configuration(get_args(args), RunMode.BACKTEST)
|
2018-02-09 07:35:38 +00:00
|
|
|
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
|
2019-08-11 18:16:52 +00:00
|
|
|
assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog)
|
2020-06-01 18:33:26 +00:00
|
|
|
assert 'timeframe' in config
|
2019-08-11 18:16:52 +00:00
|
|
|
assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog)
|
2018-02-09 07:35:38 +00:00
|
|
|
|
2018-07-17 18:26:59 +00:00
|
|
|
assert 'position_stacking' not in config
|
2019-08-11 18:16:52 +00:00
|
|
|
assert not log_has('Parameter --enable-position-stacking detected ...', caplog)
|
2018-02-09 07:35:38 +00:00
|
|
|
|
|
|
|
assert 'timerange' not in config
|
|
|
|
assert 'export' not in config
|
2018-12-25 13:35:48 +00:00
|
|
|
assert 'runmode' in config
|
|
|
|
assert config['runmode'] == RunMode.BACKTEST
|
2018-02-09 07:35:38 +00:00
|
|
|
|
|
|
|
|
2019-01-01 13:07:40 +00:00
|
|
|
def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) -> None:
|
2019-07-14 22:44:25 +00:00
|
|
|
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
|
|
|
)
|
2018-02-09 07:35:38 +00:00
|
|
|
|
|
|
|
args = [
|
2019-09-14 11:18:52 +00:00
|
|
|
'backtesting',
|
2018-02-09 07:35:38 +00:00
|
|
|
'--config', 'config.json',
|
2018-03-24 19:44:04 +00:00
|
|
|
'--strategy', 'DefaultStrategy',
|
2018-02-09 07:35:38 +00:00
|
|
|
'--datadir', '/foo/bar',
|
2020-06-01 18:33:26 +00:00
|
|
|
'--timeframe', '1m',
|
2018-07-17 18:26:59 +00:00
|
|
|
'--enable-position-stacking',
|
2018-07-17 19:05:03 +00:00
|
|
|
'--disable-max-market-positions',
|
2018-02-09 07:35:38 +00:00
|
|
|
'--timerange', ':100',
|
2018-06-03 12:52:03 +00:00
|
|
|
'--export', '/bar/foo',
|
2019-10-05 13:34:31 +00:00
|
|
|
'--export-filename', 'foo_bar.json',
|
|
|
|
'--fee', '0',
|
2018-02-09 07:35:38 +00:00
|
|
|
]
|
|
|
|
|
2020-01-26 12:33:13 +00:00
|
|
|
config = setup_optimize_configuration(get_args(args), RunMode.BACKTEST)
|
2018-02-09 07:35:38 +00:00
|
|
|
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
|
|
|
|
|
2019-08-11 18:16:52 +00:00
|
|
|
assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog)
|
2020-06-01 18:33:26 +00:00
|
|
|
assert 'timeframe' in config
|
|
|
|
assert log_has('Parameter -i/--timeframe detected ... Using timeframe: 1m ...',
|
2019-08-11 18:16:52 +00:00
|
|
|
caplog)
|
2018-02-09 07:35:38 +00:00
|
|
|
|
2018-07-17 18:26:59 +00:00
|
|
|
assert 'position_stacking' in config
|
2019-08-11 18:16:52 +00:00
|
|
|
assert log_has('Parameter --enable-position-stacking detected ...', caplog)
|
2018-07-17 19:05:03 +00:00
|
|
|
|
|
|
|
assert 'use_max_market_positions' in config
|
2019-08-11 18:16:52 +00:00
|
|
|
assert log_has('Parameter --disable-max-market-positions detected ...', caplog)
|
|
|
|
assert log_has('max_open_trades set to unlimited ...', caplog)
|
2018-02-09 07:35:38 +00:00
|
|
|
|
|
|
|
assert 'timerange' in config
|
2019-08-11 18:16:52 +00:00
|
|
|
assert log_has('Parameter --timerange detected: {} ...'.format(config['timerange']), caplog)
|
2018-02-09 07:35:38 +00:00
|
|
|
|
|
|
|
assert 'export' in config
|
2019-08-11 18:16:52 +00:00
|
|
|
assert log_has('Parameter --export detected: {} ...'.format(config['export']), caplog)
|
2018-06-03 12:52:03 +00:00
|
|
|
assert 'exportfilename' in config
|
2020-03-15 08:39:45 +00:00
|
|
|
assert isinstance(config['exportfilename'], Path)
|
2019-08-11 18:16:52 +00:00
|
|
|
assert log_has('Storing backtest results to {} ...'.format(config['exportfilename']), caplog)
|
2018-02-09 07:35:38 +00:00
|
|
|
|
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)
|
|
|
|
|
2018-02-09 07:35:38 +00:00
|
|
|
|
2020-01-26 12:33:13 +00:00
|
|
|
def test_setup_optimize_configuration_unlimited_stake_amount(mocker, default_conf, caplog) -> None:
|
2018-07-30 12:40:52 +00:00
|
|
|
default_conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
|
2018-06-03 22:48:26 +00:00
|
|
|
|
2019-07-14 22:44:25 +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', 'DefaultStrategy',
|
|
|
|
]
|
|
|
|
|
2020-03-10 09:42:11 +00:00
|
|
|
with pytest.raises(DependencyException, match=r'.`stake_amount`.*'):
|
2020-01-26 12:33:13 +00:00
|
|
|
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:
|
2018-02-09 07:35:38 +00:00
|
|
|
start_mock = MagicMock()
|
2018-06-17 20:29:57 +00:00
|
|
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
2018-06-17 20:32:56 +00:00
|
|
|
patch_exchange(mocker)
|
2018-02-09 07:35:38 +00:00
|
|
|
mocker.patch('freqtrade.optimize.backtesting.Backtesting.start', start_mock)
|
2019-07-14 22:44:25 +00:00
|
|
|
patched_configuration_load_config_file(mocker, default_conf)
|
2019-07-11 21:39:42 +00:00
|
|
|
|
2018-02-09 07:35:38 +00:00
|
|
|
args = [
|
2019-09-14 11:18:52 +00:00
|
|
|
'backtesting',
|
2018-02-09 07:35:38 +00:00
|
|
|
'--config', 'config.json',
|
2018-03-24 19:44:04 +00:00
|
|
|
'--strategy', 'DefaultStrategy',
|
2018-02-09 07:35:38 +00:00
|
|
|
]
|
2020-02-10 09:35:48 +00:00
|
|
|
pargs = get_args(args)
|
|
|
|
start_backtesting(pargs)
|
2019-08-11 18:16:52 +00:00
|
|
|
assert log_has('Starting freqtrade in Backtesting mode', caplog)
|
2018-02-09 07:35:38 +00:00
|
|
|
assert start_mock.call_count == 1
|
|
|
|
|
|
|
|
|
2019-03-06 18:55:34 +00:00
|
|
|
@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)
|
2018-06-22 18:55:09 +00:00
|
|
|
get_fee = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
|
2018-02-09 07:35:38 +00:00
|
|
|
backtesting = Backtesting(default_conf)
|
|
|
|
assert backtesting.config == default_conf
|
2019-11-03 09:01:05 +00:00
|
|
|
assert backtesting.timeframe == '5m'
|
2020-03-08 10:35:31 +00:00
|
|
|
assert callable(backtesting.strategy.ohlcvdata_to_dataframe)
|
2019-09-18 19:57:37 +00:00
|
|
|
assert callable(backtesting.strategy.advise_buy)
|
|
|
|
assert callable(backtesting.strategy.advise_sell)
|
2019-03-24 14:23:14 +00:00
|
|
|
assert isinstance(backtesting.strategy.dp, DataProvider)
|
2018-06-22 18:55:09 +00:00
|
|
|
get_fee.assert_called()
|
2018-06-23 04:58:25 +00:00
|
|
|
assert backtesting.fee == 0.5
|
2019-03-06 18:55:34 +00:00
|
|
|
assert not backtesting.strategy.order_types["stoploss_on_exchange"]
|
2018-02-09 07:35:38 +00:00
|
|
|
|
|
|
|
|
2020-06-02 08:02:55 +00:00
|
|
|
def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None:
|
2019-08-06 04:56:08 +00:00
|
|
|
patch_exchange(mocker)
|
2020-06-01 18:33:26 +00:00
|
|
|
del default_conf['timeframe']
|
2019-08-06 04:56:08 +00:00
|
|
|
default_conf['strategy_list'] = ['DefaultStrategy',
|
2019-08-27 04:41:07 +00:00
|
|
|
'SampleStrategy']
|
2019-08-06 04:56:08 +00:00
|
|
|
|
|
|
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
|
|
|
|
with pytest.raises(OperationalException):
|
|
|
|
Backtesting(default_conf)
|
|
|
|
log_has("Ticker-interval needs to be set in either configuration "
|
2019-08-11 18:16:52 +00:00
|
|
|
"or as cli argument `--ticker-interval 5m`", caplog)
|
2019-08-06 04:56:08 +00:00
|
|
|
|
|
|
|
|
2020-03-08 10:35:31 +00:00
|
|
|
def test_data_with_fee(default_conf, mocker, testdatadir) -> None:
|
2019-10-05 13:34:31 +00:00
|
|
|
patch_exchange(mocker)
|
|
|
|
default_conf['fee'] = 0.1234
|
|
|
|
|
|
|
|
fee_mock = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
|
|
|
|
backtesting = Backtesting(default_conf)
|
|
|
|
assert backtesting.fee == 0.1234
|
|
|
|
assert fee_mock.call_count == 0
|
|
|
|
|
2020-07-15 17:20:07 +00:00
|
|
|
default_conf['fee'] = 0.0
|
|
|
|
backtesting = Backtesting(default_conf)
|
|
|
|
assert backtesting.fee == 0.0
|
|
|
|
assert fee_mock.call_count == 0
|
|
|
|
|
2019-10-05 13:34:31 +00:00
|
|
|
|
2020-03-08 10:35: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)
|
2019-10-19 13:21:47 +00:00
|
|
|
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
2020-03-08 10:35:31 +00:00
|
|
|
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)
|
2020-03-08 10:35:31 +00:00
|
|
|
processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
|
|
|
|
assert len(processed['UNITTEST/BTC']) == 102
|
2018-03-02 13:46:32 +00:00
|
|
|
|
2018-07-16 05:11:17 +00:00
|
|
|
# Load strategy to compare the result between Backtesting function and strategy are the same
|
2020-02-11 22:39:15 +00:00
|
|
|
default_conf.update({'strategy': 'DefaultStrategy'})
|
|
|
|
strategy = StrategyResolver.load_strategy(default_conf)
|
|
|
|
|
2020-03-08 10:35:31 +00:00
|
|
|
processed2 = strategy.ohlcvdata_to_dataframe(data)
|
|
|
|
assert processed['UNITTEST/BTC'].equals(processed2['UNITTEST/BTC'])
|
2018-03-02 13:46:32 +00:00
|
|
|
|
|
|
|
|
2019-09-07 18:56:03 +00:00
|
|
|
def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
|
2019-12-17 22:06:03 +00:00
|
|
|
def get_timerange(input1):
|
2018-03-02 13:46:32 +00:00
|
|
|
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)
|
2020-03-15 14:36:53 +00:00
|
|
|
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest')
|
2020-06-01 07:24:27 +00:00
|
|
|
mocker.patch('freqtrade.optimize.backtesting.generate_backtest_stats')
|
2020-03-15 14:36:53 +00:00
|
|
|
mocker.patch('freqtrade.optimize.backtesting.show_backtest_results')
|
2020-12-23 15:54:35 +00:00
|
|
|
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
2020-04-25 13:37:13 +00:00
|
|
|
PropertyMock(return_value=['UNITTEST/BTC']))
|
2018-02-09 07:35:38 +00:00
|
|
|
|
2020-06-01 18:33:26 +00:00
|
|
|
default_conf['timeframe'] = '1m'
|
2019-09-07 18:56:03 +00:00
|
|
|
default_conf['datadir'] = testdatadir
|
2018-07-30 12:40:52 +00:00
|
|
|
default_conf['export'] = None
|
2019-10-19 13:21:47 +00:00
|
|
|
default_conf['timerange'] = '-1510694220'
|
2018-02-09 07:35:38 +00:00
|
|
|
|
2018-07-30 12:40:52 +00:00
|
|
|
backtesting = Backtesting(default_conf)
|
2020-10-11 17:50:37 +00:00
|
|
|
backtesting.strategy.bot_loop_start = MagicMock()
|
2018-02-09 07:35:38 +00:00
|
|
|
backtesting.start()
|
|
|
|
# check the logs, that will contain the backtest result
|
|
|
|
exists = [
|
2020-06-09 06:14:18 +00:00
|
|
|
'Backtesting with data from 2017-11-14 21:17:00 '
|
|
|
|
'up to 2017-11-14 22:59:00 (0 days)..'
|
2018-02-09 07:35:38 +00:00
|
|
|
]
|
|
|
|
for line in exists:
|
2019-08-11 18:16:52 +00:00
|
|
|
assert log_has(line, caplog)
|
2020-08-30 08:07:58 +00:00
|
|
|
assert backtesting.strategy.dp._pairlists is not None
|
2020-10-11 17:50:37 +00:00
|
|
|
assert backtesting.strategy.bot_loop_start.call_count == 1
|
2018-02-09 07:35:38 +00:00
|
|
|
|
|
|
|
|
2019-09-07 19:06:20 +00:00
|
|
|
def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) -> None:
|
2019-12-17 22:06:03 +00:00
|
|
|
def get_timerange(input1):
|
2018-06-11 17:50:43 +00:00
|
|
|
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
|
|
|
|
|
2019-12-28 08:59:47 +00:00
|
|
|
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)
|
2020-03-15 14:36:53 +00:00
|
|
|
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest')
|
2020-12-23 15:54:35 +00:00
|
|
|
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
2020-04-25 13:37:13 +00:00
|
|
|
PropertyMock(return_value=['UNITTEST/BTC']))
|
2018-06-11 17:50:43 +00:00
|
|
|
|
2020-06-01 18:33:26 +00:00
|
|
|
default_conf['timeframe'] = "1m"
|
2019-09-07 19:06:20 +00:00
|
|
|
default_conf['datadir'] = testdatadir
|
2018-07-30 12:40:52 +00:00
|
|
|
default_conf['export'] = None
|
|
|
|
default_conf['timerange'] = '20180101-20180102'
|
2018-06-11 17:50:43 +00:00
|
|
|
|
2018-07-30 12:40:52 +00:00
|
|
|
backtesting = Backtesting(default_conf)
|
2019-10-23 18:27:51 +00:00
|
|
|
with pytest.raises(OperationalException, match='No data found. Terminating.'):
|
|
|
|
backtesting.start()
|
2018-06-11 17:50:43 +00:00
|
|
|
|
|
|
|
|
2020-04-25 13:37:13 +00:00
|
|
|
def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) -> None:
|
2020-04-27 05:56:17 +00:00
|
|
|
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
2020-04-25 13:37:13 +00:00
|
|
|
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')
|
2020-12-23 15:54:35 +00:00
|
|
|
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
2020-04-25 13:37:13 +00:00
|
|
|
PropertyMock(return_value=[]))
|
|
|
|
|
2020-06-01 18:33:26 +00:00
|
|
|
default_conf['timeframe'] = "1m"
|
2020-04-25 13:37:13 +00:00
|
|
|
default_conf['datadir'] = testdatadir
|
|
|
|
default_conf['export'] = None
|
|
|
|
default_conf['timerange'] = '20180101-20180102'
|
|
|
|
|
|
|
|
with pytest.raises(OperationalException, match='No pair in whitelist.'):
|
|
|
|
Backtesting(default_conf)
|
|
|
|
|
2020-04-27 05:56:17 +00:00
|
|
|
default_conf['pairlists'] = [{"method": "VolumePairList", "number_assets": 5}]
|
|
|
|
with pytest.raises(OperationalException, match='VolumePairList not allowed for backtesting.'):
|
|
|
|
Backtesting(default_conf)
|
|
|
|
|
2020-04-25 13:37:13 +00:00
|
|
|
|
2020-06-07 14:02:08 +00:00
|
|
|
def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, tickers) -> None:
|
|
|
|
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
|
|
|
mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers)
|
|
|
|
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
|
|
|
|
mocker.patch('freqtrade.data.history.get_timerange', get_timerange)
|
|
|
|
patch_exchange(mocker)
|
|
|
|
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest')
|
2020-12-23 15:54:35 +00:00
|
|
|
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
2020-06-07 14:02:08 +00:00
|
|
|
PropertyMock(return_value=['XRP/BTC']))
|
2020-12-23 15:54:35 +00:00
|
|
|
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.refresh_pairlist')
|
2020-06-07 14:02:08 +00:00
|
|
|
|
|
|
|
default_conf['ticker_interval'] = "1m"
|
|
|
|
default_conf['datadir'] = testdatadir
|
|
|
|
default_conf['export'] = None
|
|
|
|
# Use stoploss from strategy
|
|
|
|
del default_conf['stoploss']
|
|
|
|
default_conf['timerange'] = '20180101-20180102'
|
|
|
|
|
|
|
|
default_conf['pairlists'] = [{"method": "VolumePairList", "number_assets": 5}]
|
|
|
|
with pytest.raises(OperationalException, match='VolumePairList not allowed for backtesting.'):
|
|
|
|
Backtesting(default_conf)
|
|
|
|
|
2020-12-16 18:24:47 +00:00
|
|
|
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)
|
|
|
|
|
2020-06-07 14:06:20 +00:00
|
|
|
# Multiple strategies
|
|
|
|
default_conf['strategy_list'] = ['DefaultStrategy', 'TestStrategyLegacy']
|
|
|
|
with pytest.raises(OperationalException,
|
|
|
|
match='PrecisionFilter not allowed for backtesting multiple strategies.'):
|
|
|
|
Backtesting(default_conf)
|
|
|
|
|
2020-06-07 14:02:08 +00:00
|
|
|
|
2019-09-07 18:56:03 +00:00
|
|
|
def test_backtest(default_conf, fee, mocker, testdatadir) -> None:
|
2019-10-05 10:29:59 +00:00
|
|
|
default_conf['ask_strategy']['use_sell_signal'] = False
|
2018-06-17 20:29:57 +00:00
|
|
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
2018-06-17 20:32:56 +00:00
|
|
|
patch_exchange(mocker)
|
2018-04-21 22:19:11 +00:00
|
|
|
backtesting = Backtesting(default_conf)
|
2018-07-06 17:45:58 +00:00
|
|
|
pair = 'UNITTEST/BTC'
|
2019-10-19 13:21:47 +00:00
|
|
|
timerange = TimeRange('date', None, 1517227800, 0)
|
2019-11-02 19:19:13 +00:00
|
|
|
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'],
|
2018-12-15 14:34:25 +00:00
|
|
|
timerange=timerange)
|
2020-03-08 10:35:31 +00:00
|
|
|
processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
|
|
|
|
min_date, max_date = get_timerange(processed)
|
2018-02-09 07:35:38 +00:00
|
|
|
results = backtesting.backtest(
|
2020-03-08 10:35:31 +00:00
|
|
|
processed=processed,
|
2019-12-13 23:12:44 +00:00
|
|
|
stake_amount=default_conf['stake_amount'],
|
|
|
|
start_date=min_date,
|
|
|
|
end_date=max_date,
|
|
|
|
max_open_trades=10,
|
|
|
|
position_stacking=False,
|
2018-02-09 07:35:38 +00:00
|
|
|
)
|
2017-12-30 10:55:23 +00:00
|
|
|
assert not results.empty
|
2018-06-13 04:42:24 +00:00
|
|
|
assert len(results) == 2
|
2017-12-28 14:58:02 +00:00
|
|
|
|
2018-07-05 21:22:35 +00:00
|
|
|
expected = pd.DataFrame(
|
2018-07-06 17:45:58 +00:00
|
|
|
{'pair': [pair, pair],
|
2018-11-01 12:14:59 +00:00
|
|
|
'profit_percent': [0.0, 0.0],
|
|
|
|
'profit_abs': [0.0, 0.0],
|
2020-06-26 07:21:28 +00:00
|
|
|
'open_date': pd.to_datetime([Arrow(2018, 1, 29, 18, 40, 0).datetime,
|
2019-01-27 09:40:52 +00:00
|
|
|
Arrow(2018, 1, 30, 3, 30, 0).datetime], utc=True
|
|
|
|
),
|
2020-06-26 18:21:08 +00:00
|
|
|
'open_rate': [0.104445, 0.10302485],
|
|
|
|
'open_fee': [0.0025, 0.0025],
|
2020-06-26 07:21:28 +00:00
|
|
|
'close_date': pd.to_datetime([Arrow(2018, 1, 29, 22, 35, 0).datetime,
|
2019-01-27 09:40:52 +00:00
|
|
|
Arrow(2018, 1, 30, 4, 10, 0).datetime], utc=True),
|
2020-06-26 18:21:08 +00:00
|
|
|
'close_rate': [0.104969, 0.103541],
|
|
|
|
'close_fee': [0.0025, 0.0025],
|
2020-06-26 19:04:40 +00:00
|
|
|
'amount': [0.00957442, 0.0097064],
|
2019-01-12 12:45:43 +00:00
|
|
|
'trade_duration': [235, 40],
|
2018-07-05 21:22:35 +00:00
|
|
|
'open_at_end': [False, False],
|
2018-07-11 18:03:40 +00:00
|
|
|
'sell_reason': [SellType.ROI, SellType.ROI]
|
|
|
|
})
|
2018-07-05 21:22:35 +00:00
|
|
|
pd.testing.assert_frame_equal(results, expected)
|
2020-03-08 10:35:31 +00:00
|
|
|
data_pair = processed[pair]
|
2018-07-06 17:45:58 +00:00
|
|
|
for _, t in results.iterrows():
|
2020-06-26 07:21:28 +00:00
|
|
|
ln = data_pair.loc[data_pair["date"] == t["open_date"]]
|
2018-07-08 18:18:34 +00:00
|
|
|
# Check open trade rate alignes to open rate
|
2018-07-06 17:45:58 +00:00
|
|
|
assert ln is not None
|
2018-07-08 18:18:34 +00:00
|
|
|
assert round(ln.iloc[0]["open"], 6) == round(t["open_rate"], 6)
|
2018-11-01 12:14:59 +00:00
|
|
|
# check close trade rate alignes to close rate or is between high and low
|
2020-06-26 07:21:28 +00:00
|
|
|
ln = data_pair.loc[data_pair["date"] == t["close_date"]]
|
2018-11-01 12:14:59 +00:00
|
|
|
assert (round(ln.iloc[0]["open"], 6) == round(t["close_rate"], 6) or
|
|
|
|
round(ln.iloc[0]["low"], 6) < round(
|
|
|
|
t["close_rate"], 6) < round(ln.iloc[0]["high"], 6))
|
2018-07-05 21:22:35 +00:00
|
|
|
|
2017-12-28 19:05:33 +00:00
|
|
|
|
2020-06-02 08:02:55 +00:00
|
|
|
def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None:
|
2019-10-05 10:29:59 +00:00
|
|
|
default_conf['ask_strategy']['use_sell_signal'] = False
|
2018-06-17 20:29:57 +00:00
|
|
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
2018-06-17 20:32:56 +00:00
|
|
|
patch_exchange(mocker)
|
2018-04-21 22:19:11 +00:00
|
|
|
backtesting = Backtesting(default_conf)
|
2018-02-09 07:35:38 +00:00
|
|
|
|
2019-11-02 19:19:13 +00:00
|
|
|
# Run a backtesting for an exiting 1min timeframe
|
2019-10-19 13:21:47 +00:00
|
|
|
timerange = TimeRange.parse_timerange('1510688220-1510700340')
|
2019-11-02 19:19:13 +00:00
|
|
|
data = history.load_data(datadir=testdatadir, timeframe='1m', pairs=['UNITTEST/BTC'],
|
2018-12-15 14:34:25 +00:00
|
|
|
timerange=timerange)
|
2020-03-08 10:35:31 +00:00
|
|
|
processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
|
2019-12-17 22:06:03 +00:00
|
|
|
min_date, max_date = get_timerange(processed)
|
2018-02-09 07:35:38 +00:00
|
|
|
results = backtesting.backtest(
|
2019-12-13 23:12:44 +00:00
|
|
|
processed=processed,
|
|
|
|
stake_amount=default_conf['stake_amount'],
|
|
|
|
start_date=min_date,
|
|
|
|
end_date=max_date,
|
|
|
|
max_open_trades=1,
|
|
|
|
position_stacking=False,
|
2018-02-09 07:35:38 +00:00
|
|
|
)
|
|
|
|
assert not results.empty
|
2018-06-13 04:42:24 +00:00
|
|
|
assert len(results) == 1
|
2018-02-09 07:35:38 +00:00
|
|
|
|
|
|
|
|
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)
|
2018-02-09 07:35:38 +00:00
|
|
|
|
2019-09-07 18:56:03 +00:00
|
|
|
dict_of_tickerrows = load_data_test('raise', testdatadir)
|
2020-03-08 10:35:31 +00:00
|
|
|
dataframes = backtesting.strategy.ohlcvdata_to_dataframe(dict_of_tickerrows)
|
2018-02-03 16:15:40 +00:00
|
|
|
dataframe = dataframes['UNITTEST/BTC']
|
2017-12-30 10:55:23 +00:00
|
|
|
cols = dataframe.columns
|
|
|
|
# assert the dataframe got some of the indicator columns
|
|
|
|
for col in ['close', 'high', 'low', 'open', 'date',
|
2019-09-13 17:49:13 +00:00
|
|
|
'ema10', 'rsi', 'fastd', 'plus_di']:
|
2017-12-30 10:55:23 +00:00
|
|
|
assert col in cols
|
2017-12-28 19:05:33 +00:00
|
|
|
|
2017-12-28 14:58:02 +00:00
|
|
|
|
2020-11-25 08:53:13 +00:00
|
|
|
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.
|
2020-11-25 08:53:13 +00:00
|
|
|
default_conf['protections'] = [
|
|
|
|
{
|
|
|
|
"method": "CooldownPeriod",
|
|
|
|
"stop_duration": 3,
|
|
|
|
}]
|
|
|
|
|
|
|
|
default_conf['enable_protections'] = True
|
|
|
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
|
|
|
tests = [
|
|
|
|
['sine', 9],
|
|
|
|
['raise', 10],
|
|
|
|
['lower', 0],
|
|
|
|
['sine', 9],
|
|
|
|
['raise', 10],
|
|
|
|
]
|
|
|
|
# While buy-signals are unrealistic, running backtesting
|
|
|
|
# over and over again should not cause different results
|
2017-12-28 14:58:02 +00:00
|
|
|
for [contour, numres] in tests:
|
2020-12-02 06:42:39 +00:00
|
|
|
assert len(simple_backtest(default_conf, contour, mocker, testdatadir)) == numres
|
|
|
|
|
|
|
|
|
|
|
|
@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('freqtrade.exchange.Exchange.get_fee', fee)
|
|
|
|
# While buy-signals are unrealistic, running backtesting
|
|
|
|
# over and over again should not cause different results
|
|
|
|
assert len(simple_backtest(default_conf, contour, mocker, testdatadir)) == expected
|
2017-12-28 14:58:02 +00:00
|
|
|
|
2017-12-28 19:05:33 +00:00
|
|
|
|
2019-09-07 18:56:03 +00:00
|
|
|
def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
|
2018-02-08 19:49:43 +00:00
|
|
|
# Override the default buy trend function in our default_strategy
|
2018-06-26 05:08:09 +00:00
|
|
|
def fun(dataframe=None, pair=None):
|
2018-02-08 19:49:43 +00:00
|
|
|
buy_value = 1
|
|
|
|
sell_value = 1
|
|
|
|
return _trend(dataframe, buy_value, sell_value)
|
|
|
|
|
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)
|
2019-09-18 19:57:37 +00:00
|
|
|
backtesting.strategy.advise_buy = fun # Override
|
|
|
|
backtesting.strategy.advise_sell = fun # Override
|
2019-12-13 23:12:44 +00:00
|
|
|
results = backtesting.backtest(**backtest_conf)
|
2018-02-08 19:49:43 +00:00
|
|
|
assert results.empty
|
|
|
|
|
|
|
|
|
2019-09-07 18:56:03 +00:00
|
|
|
def test_backtest_only_sell(mocker, default_conf, testdatadir):
|
2018-02-08 19:49:43 +00:00
|
|
|
# Override the default buy trend function in our default_strategy
|
2018-06-26 05:08:09 +00:00
|
|
|
def fun(dataframe=None, pair=None):
|
2018-02-08 19:49:43 +00:00
|
|
|
buy_value = 0
|
|
|
|
sell_value = 1
|
|
|
|
return _trend(dataframe, buy_value, sell_value)
|
|
|
|
|
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)
|
2019-09-18 19:57:37 +00:00
|
|
|
backtesting.strategy.advise_buy = fun # Override
|
|
|
|
backtesting.strategy.advise_sell = fun # Override
|
2019-12-13 23:12:44 +00:00
|
|
|
results = backtesting.backtest(**backtest_conf)
|
2018-02-08 19:49:43 +00:00
|
|
|
assert results.empty
|
|
|
|
|
|
|
|
|
2019-09-07 18:56:03 +00:00
|
|
|
def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
|
2018-06-17 20:29:57 +00:00
|
|
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
2019-09-07 18:56:03 +00:00
|
|
|
backtest_conf = _make_backtest_conf(mocker, conf=default_conf,
|
|
|
|
pair='UNITTEST/BTC', datadir=testdatadir)
|
2020-06-01 18:33:26 +00:00
|
|
|
default_conf['timeframe'] = '1m'
|
2018-04-21 22:19:11 +00:00
|
|
|
backtesting = Backtesting(default_conf)
|
2019-09-18 19:57:37 +00:00
|
|
|
backtesting.strategy.advise_buy = _trend_alternate # Override
|
|
|
|
backtesting.strategy.advise_sell = _trend_alternate # Override
|
2019-12-13 23:12:44 +00:00
|
|
|
results = backtesting.backtest(**backtest_conf)
|
2018-10-17 04:54:07 +00:00
|
|
|
# 200 candles in backtest data
|
|
|
|
# won't buy on first (shifted by 1)
|
|
|
|
# 100 buys signals
|
2018-12-15 14:34:25 +00:00
|
|
|
assert len(results) == 100
|
2018-06-13 04:42:24 +00:00
|
|
|
# One trade was force-closed at the end
|
2018-10-16 18:35:08 +00:00
|
|
|
assert len(results.loc[results.open_at_end]) == 0
|
2018-02-08 19:49:43 +00:00
|
|
|
|
|
|
|
|
2019-03-23 13:50:18 +00:00
|
|
|
@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
|
|
|
"""
|
2020-05-18 09:40:25 +00:00
|
|
|
if metadata['pair'] in ('ETH/BTC', 'LTC/BTC'):
|
2019-03-23 13:50:18 +00:00
|
|
|
multi = 20
|
|
|
|
else:
|
|
|
|
multi = 18
|
2018-11-05 19:03:04 +00:00
|
|
|
dataframe['buy'] = np.where(dataframe.index % multi == 0, 1, 0)
|
|
|
|
dataframe['sell'] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0)
|
|
|
|
return dataframe
|
|
|
|
|
|
|
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
2018-11-06 19:37:59 +00:00
|
|
|
patch_exchange(mocker)
|
2019-04-04 17:44:03 +00:00
|
|
|
|
2018-11-05 19:03:04 +00:00
|
|
|
pairs = ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC', 'NXT/BTC']
|
2019-11-02 19:19:13 +00:00
|
|
|
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=pairs)
|
2019-04-04 17:44:03 +00:00
|
|
|
# Only use 500 lines to increase performance
|
2018-11-05 19:03:04 +00:00
|
|
|
data = trim_dictlist(data, -500)
|
2019-04-04 17:44:03 +00:00
|
|
|
|
|
|
|
# Remove data for one pair from the beginning of the data
|
2019-05-08 18:33:22 +00:00
|
|
|
data[pair] = data[pair][tres:].reset_index()
|
2020-06-01 18:33:26 +00:00
|
|
|
default_conf['timeframe'] = '5m'
|
2018-11-05 19:03:04 +00:00
|
|
|
|
|
|
|
backtesting = Backtesting(default_conf)
|
2019-09-18 19:57:37 +00:00
|
|
|
backtesting.strategy.advise_buy = _trend_alternate_hold # Override
|
|
|
|
backtesting.strategy.advise_sell = _trend_alternate_hold # Override
|
2018-11-05 19:03:04 +00:00
|
|
|
|
2020-03-08 10:35:31 +00:00
|
|
|
processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
|
|
|
|
min_date, max_date = get_timerange(processed)
|
2018-11-05 19:03:04 +00:00
|
|
|
backtest_conf = {
|
2020-03-08 10:35:31 +00:00
|
|
|
'processed': processed,
|
2019-12-13 23:12:44 +00:00
|
|
|
'stake_amount': default_conf['stake_amount'],
|
2018-11-05 19:03:04 +00:00
|
|
|
'start_date': min_date,
|
|
|
|
'end_date': max_date,
|
2019-12-13 23:12:44 +00:00
|
|
|
'max_open_trades': 3,
|
|
|
|
'position_stacking': False,
|
2018-11-05 19:03:04 +00:00
|
|
|
}
|
|
|
|
|
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
|
2019-10-30 12:35:55 +00:00
|
|
|
assert len(evaluate_result_multi(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
|
2019-10-30 12:35:55 +00:00
|
|
|
assert len(evaluate_result_multi(results, '5m', 3)) == 0
|
2018-11-05 19:03:04 +00:00
|
|
|
|
|
|
|
backtest_conf = {
|
2020-03-08 10:35:31 +00:00
|
|
|
'processed': processed,
|
2019-12-13 23:12:44 +00:00
|
|
|
'stake_amount': default_conf['stake_amount'],
|
2018-11-05 19:03:04 +00:00
|
|
|
'start_date': min_date,
|
|
|
|
'end_date': max_date,
|
2019-12-13 23:12:44 +00:00
|
|
|
'max_open_trades': 1,
|
|
|
|
'position_stacking': False,
|
2018-11-05 19:03:04 +00:00
|
|
|
}
|
2019-12-13 23:12:44 +00:00
|
|
|
results = backtesting.backtest(**backtest_conf)
|
2019-10-30 12:35:55 +00:00
|
|
|
assert len(evaluate_result_multi(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):
|
2018-08-19 17:39:22 +00:00
|
|
|
|
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')
|
2020-12-23 15:54:35 +00:00
|
|
|
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
2020-04-25 13:37:13 +00:00
|
|
|
PropertyMock(return_value=['UNITTEST/BTC']))
|
2019-07-14 22:44:25 +00:00
|
|
|
patched_configuration_load_config_file(mocker, default_conf)
|
2018-03-04 00:42:37 +00:00
|
|
|
|
|
|
|
args = [
|
2019-09-14 11:18:52 +00:00
|
|
|
'backtesting',
|
2018-03-04 00:42:37 +00:00
|
|
|
'--config', 'config.json',
|
2018-03-24 19:44:04 +00:00
|
|
|
'--strategy', 'DefaultStrategy',
|
2019-09-08 07:54:15 +00:00
|
|
|
'--datadir', str(testdatadir),
|
2020-06-01 18:33:26 +00:00
|
|
|
'--timeframe', '1m',
|
2019-10-19 13:21:47 +00:00
|
|
|
'--timerange', '1510694220-1510700340',
|
2018-07-17 19:05:03 +00:00
|
|
|
'--enable-position-stacking',
|
|
|
|
'--disable-max-market-positions'
|
2018-03-04 00:42:37 +00:00
|
|
|
]
|
|
|
|
args = get_args(args)
|
2019-05-25 18:01:43 +00:00
|
|
|
start_backtesting(args)
|
2018-02-08 19:49:43 +00:00
|
|
|
# check the logs, that will contain the backtest result
|
2018-03-04 00:42:37 +00:00
|
|
|
exists = [
|
2020-06-01 18:33:26 +00:00
|
|
|
'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) ...',
|
2019-10-19 13:21:47 +00:00
|
|
|
'Parameter --timerange detected: 1510694220-1510700340 ...',
|
2019-09-08 07:54:15 +00:00
|
|
|
f'Using data directory: {testdatadir} ...',
|
2020-06-09 06:14:18 +00:00
|
|
|
'Loading data from 2017-11-14 20:57:00 '
|
|
|
|
'up to 2017-11-14 22:58:00 (0 days)..',
|
|
|
|
'Backtesting with data from 2017-11-14 21:17:00 '
|
|
|
|
'up to 2017-11-14 22:58:00 (0 days)..',
|
2018-07-17 18:26:59 +00:00
|
|
|
'Parameter --enable-position-stacking detected ...'
|
2018-03-04 00:42:37 +00:00
|
|
|
]
|
|
|
|
|
2018-02-08 19:49:43 +00:00
|
|
|
for line in exists:
|
2019-08-11 18:16:52 +00:00
|
|
|
assert log_has(line, caplog)
|
2018-07-28 05:55:59 +00:00
|
|
|
|
|
|
|
|
2020-05-11 17:22:19 +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):
|
2018-08-19 17:39:22 +00:00
|
|
|
|
2019-12-27 09:29:06 +00:00
|
|
|
patch_exchange(mocker)
|
2020-09-25 19:00:58 +00:00
|
|
|
backtestmock = MagicMock(return_value=pd.DataFrame(columns=BT_DATA_COLUMNS + ['profit_abs']))
|
2020-12-23 15:54:35 +00:00
|
|
|
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
2020-04-25 13:37:13 +00:00
|
|
|
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,
|
2020-05-25 18:46:31 +00:00
|
|
|
generate_pair_metrics=MagicMock(),
|
2020-05-25 18:00:05 +00:00
|
|
|
generate_sell_reason_stats=sell_reason_mock,
|
2020-06-07 09:35:02 +00:00
|
|
|
generate_strategy_metrics=strat_summary,
|
2020-07-03 18:15:20 +00:00
|
|
|
generate_daily_stats=MagicMock(),
|
2020-05-25 18:00:05 +00:00
|
|
|
)
|
2019-07-14 22:44:25 +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'),
|
2020-06-01 18:33:26 +00:00
|
|
|
'--timeframe', '1m',
|
2019-10-19 13:21:47 +00:00
|
|
|
'--timerange', '1510694220-1510700340',
|
2018-07-28 05:55:59 +00:00
|
|
|
'--enable-position-stacking',
|
|
|
|
'--disable-max-market-positions',
|
|
|
|
'--strategy-list',
|
|
|
|
'DefaultStrategy',
|
2020-02-19 18:42:04 +00:00
|
|
|
'TestStrategyLegacy',
|
2018-07-28 05:55:59 +00:00
|
|
|
]
|
|
|
|
args = get_args(args)
|
2019-05-25 18:01:43 +00:00
|
|
|
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 = [
|
2020-06-01 18:33:26 +00:00
|
|
|
'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) ...',
|
2019-10-19 13:21:47 +00:00
|
|
|
'Parameter --timerange detected: 1510694220-1510700340 ...',
|
2019-09-08 07:54:15 +00:00
|
|
|
f'Using data directory: {testdatadir} ...',
|
2020-06-09 06:14:18 +00:00
|
|
|
'Loading data from 2017-11-14 20:57:00 '
|
|
|
|
'up to 2017-11-14 22:58:00 (0 days)..',
|
|
|
|
'Backtesting with data from 2017-11-14 21:17:00 '
|
|
|
|
'up to 2017-11-14 22:58:00 (0 days)..',
|
2018-07-28 05:55:59 +00:00
|
|
|
'Parameter --enable-position-stacking detected ...',
|
|
|
|
'Running backtesting for Strategy DefaultStrategy',
|
2020-02-19 18:42:04 +00:00
|
|
|
'Running backtesting for Strategy TestStrategyLegacy',
|
2018-07-28 05:55:59 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
for line in exists:
|
2019-08-11 18:16:52 +00:00
|
|
|
assert log_has(line, caplog)
|
2020-05-25 18:46:31 +00:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.filterwarnings("ignore:deprecated")
|
|
|
|
def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdatadir, capsys):
|
|
|
|
|
|
|
|
patch_exchange(mocker)
|
|
|
|
backtestmock = MagicMock(side_effect=[
|
|
|
|
pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'],
|
|
|
|
'profit_percent': [0.0, 0.0],
|
|
|
|
'profit_abs': [0.0, 0.0],
|
2020-06-26 07:21:28 +00:00
|
|
|
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
|
2020-05-25 18:46:31 +00:00
|
|
|
'2018-01-30 03:30:00', ], utc=True
|
|
|
|
),
|
2020-06-26 07:21:28 +00:00
|
|
|
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
|
2020-05-25 18:46:31 +00:00
|
|
|
'2018-01-30 05:35:00', ], utc=True),
|
|
|
|
'trade_duration': [235, 40],
|
|
|
|
'open_at_end': [False, False],
|
|
|
|
'open_rate': [0.104445, 0.10302485],
|
|
|
|
'close_rate': [0.104969, 0.103541],
|
|
|
|
'sell_reason': [SellType.ROI, SellType.ROI]
|
|
|
|
}),
|
|
|
|
pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
|
|
|
|
'profit_percent': [0.03, 0.01, 0.1],
|
|
|
|
'profit_abs': [0.01, 0.02, 0.2],
|
2020-06-26 07:21:28 +00:00
|
|
|
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
|
2020-05-25 18:46:31 +00:00
|
|
|
'2018-01-30 03:30:00',
|
|
|
|
'2018-01-30 05:30:00'], utc=True
|
|
|
|
),
|
2020-06-26 07:21:28 +00:00
|
|
|
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
|
2020-05-25 18:46:31 +00:00
|
|
|
'2018-01-30 05:35:00',
|
|
|
|
'2018-01-30 08:30:00'], utc=True),
|
|
|
|
'trade_duration': [47, 40, 20],
|
|
|
|
'open_at_end': [False, False, False],
|
|
|
|
'open_rate': [0.104445, 0.10302485, 0.122541],
|
|
|
|
'close_rate': [0.104969, 0.103541, 0.123541],
|
|
|
|
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
|
|
|
}),
|
|
|
|
])
|
2020-12-23 15:54:35 +00:00
|
|
|
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
2020-05-25 18:46:31 +00:00
|
|
|
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'),
|
2020-06-01 18:33:26 +00:00
|
|
|
'--timeframe', '1m',
|
2020-05-25 18:46:31 +00:00
|
|
|
'--timerange', '1510694220-1510700340',
|
|
|
|
'--enable-position-stacking',
|
|
|
|
'--disable-max-market-positions',
|
|
|
|
'--strategy-list',
|
|
|
|
'DefaultStrategy',
|
|
|
|
'TestStrategyLegacy',
|
|
|
|
]
|
|
|
|
args = get_args(args)
|
|
|
|
start_backtesting(args)
|
|
|
|
|
|
|
|
# check the logs, that will contain the backtest result
|
|
|
|
exists = [
|
2020-06-01 18:33:26 +00:00
|
|
|
'Parameter -i/--timeframe detected ... Using timeframe: 1m ...',
|
2020-05-25 18:46:31 +00:00
|
|
|
'Ignoring max_open_trades (--disable-max-market-positions was used) ...',
|
|
|
|
'Parameter --timerange detected: 1510694220-1510700340 ...',
|
|
|
|
f'Using data directory: {testdatadir} ...',
|
2020-06-09 06:14:18 +00:00
|
|
|
'Loading data from 2017-11-14 20:57:00 '
|
|
|
|
'up to 2017-11-14 22:58:00 (0 days)..',
|
|
|
|
'Backtesting with data from 2017-11-14 21:17:00 '
|
|
|
|
'up to 2017-11-14 22:58:00 (0 days)..',
|
2020-05-25 18:46:31 +00:00
|
|
|
'Parameter --enable-position-stacking detected ...',
|
|
|
|
'Running backtesting for Strategy DefaultStrategy',
|
|
|
|
'Running backtesting for Strategy TestStrategyLegacy',
|
|
|
|
]
|
|
|
|
|
|
|
|
for line in exists:
|
|
|
|
assert log_has(line, caplog)
|
|
|
|
|
|
|
|
captured = capsys.readouterr()
|
|
|
|
assert 'BACKTESTING REPORT' in captured.out
|
|
|
|
assert 'SELL REASON STATS' in captured.out
|
|
|
|
assert 'LEFT OPEN TRADES REPORT' in captured.out
|
|
|
|
assert 'STRATEGY SUMMARY' in captured.out
|