fix backtesting testsuite

This commit is contained in:
gcarq 2018-04-22 00:19:11 +02:00
parent bbe3bc4423
commit 9ab4953472

View File

@ -6,7 +6,6 @@ import random
from copy import deepcopy from copy import deepcopy
from typing import List from typing import List
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest
import numpy as np import numpy as np
import pandas as pd import pandas as pd
@ -18,19 +17,6 @@ from freqtrade.arguments import Arguments
from freqtrade.optimize.backtesting import Backtesting, start, setup_configuration from freqtrade.optimize.backtesting import Backtesting, start, setup_configuration
from freqtrade.tests.conftest import log_has from freqtrade.tests.conftest import log_has
# Avoid to reinit the same object again and again
_BACKTESTING = None
_BACKTESTING_INITIALIZED = False
@pytest.fixture(scope='function')
def init_backtesting(default_conf, mocker):
global _BACKTESTING_INITIALIZED, _BACKTESTING
if not _BACKTESTING_INITIALIZED:
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
_BACKTESTING = Backtesting(default_conf)
_BACKTESTING_INITIALIZED = True
def get_args(args) -> List[str]: def get_args(args) -> List[str]:
return Arguments(args, '').get_parsed_arg() return Arguments(args, '').get_parsed_arg()
@ -96,8 +82,9 @@ def load_data_test(what):
return data return data
def simple_backtest(config, contour, num_results) -> None: def simple_backtest(config, contour, num_results, mocker) -> None:
backtesting = _BACKTESTING mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
backtesting = Backtesting(config)
data = load_data_test(contour) data = load_data_test(contour)
processed = backtesting.tickerdata_to_dataframe(data) processed = backtesting.tickerdata_to_dataframe(data)
@ -128,12 +115,14 @@ def _load_pair_as_ticks(pair, tickfreq):
# FIX: fixturize this? # FIX: fixturize this?
def _make_backtest_conf(conf=None, pair='UNITTEST/BTC', record=None): def _make_backtest_conf(mocker, conf=None, pair='UNITTEST/BTC', record=None):
data = optimize.load_data(None, ticker_interval='8m', pairs=[pair]) data = optimize.load_data(None, ticker_interval='8m', pairs=[pair])
data = trim_dictlist(data, -200) data = trim_dictlist(data, -200)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
backtesting = Backtesting(conf)
return { return {
'stake_amount': conf['stake_amount'], 'stake_amount': conf['stake_amount'],
'processed': _BACKTESTING.tickerdata_to_dataframe(data), 'processed': backtesting.tickerdata_to_dataframe(data),
'max_open_trades': 10, 'max_open_trades': 10,
'realistic': True, 'realistic': True,
'record': record 'record': record
@ -169,21 +158,6 @@ def _trend_alternate(dataframe=None):
return dataframe return dataframe
def _run_backtest_1(fun, backtest_conf):
# strategy is a global (hidden as a singleton), so we
# emulate strategy being pure, by override/restore here
# if we dont do this, the override in strategy will carry over
# to other tests
old_buy = _BACKTESTING.populate_buy_trend
old_sell = _BACKTESTING.populate_sell_trend
_BACKTESTING.populate_buy_trend = fun # Override
_BACKTESTING.populate_sell_trend = fun # Override
results = _BACKTESTING.backtest(backtest_conf)
_BACKTESTING.populate_buy_trend = old_buy # restore override
_BACKTESTING.populate_sell_trend = old_sell # restore override
return results
# Unit tests # Unit tests
def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None: def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None:
""" """
@ -287,12 +261,13 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
) )
def test_start(mocker, init_backtesting, fee, default_conf, caplog) -> None: def test_start(mocker, fee, default_conf, caplog) -> None:
""" """
Test start() function Test start() function
""" """
start_mock = MagicMock() start_mock = MagicMock()
mocker.patch('freqtrade.exchange.get_fee', fee) mocker.patch('freqtrade.exchange.get_fee', fee)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
mocker.patch('freqtrade.optimize.backtesting.Backtesting.start', start_mock) mocker.patch('freqtrade.optimize.backtesting.Backtesting.start', start_mock)
mocker.patch('freqtrade.configuration.open', mocker.mock_open( mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf) read_data=json.dumps(default_conf)
@ -342,16 +317,16 @@ def test_backtesting_init(mocker, default_conf) -> None:
assert callable(backtesting.populate_sell_trend) assert callable(backtesting.populate_sell_trend)
def test_tickerdata_to_dataframe(init_backtesting, default_conf) -> None: def test_tickerdata_to_dataframe(default_conf, mocker) -> None:
""" """
Test Backtesting.tickerdata_to_dataframe() method Test Backtesting.tickerdata_to_dataframe() method
""" """
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
timerange = ((None, 'line'), None, -100) timerange = ((None, 'line'), None, -100)
tick = optimize.load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange) tick = optimize.load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange)
tickerlist = {'UNITTEST/BTC': tick} tickerlist = {'UNITTEST/BTC': tick}
backtesting = _BACKTESTING backtesting = Backtesting(default_conf)
data = backtesting.tickerdata_to_dataframe(tickerlist) data = backtesting.tickerdata_to_dataframe(tickerlist)
assert len(data['UNITTEST/BTC']) == 100 assert len(data['UNITTEST/BTC']) == 100
@ -361,11 +336,12 @@ def test_tickerdata_to_dataframe(init_backtesting, default_conf) -> None:
assert data['UNITTEST/BTC'].equals(data2['UNITTEST/BTC']) assert data['UNITTEST/BTC'].equals(data2['UNITTEST/BTC'])
def test_get_timeframe(init_backtesting) -> None: def test_get_timeframe(default_conf, mocker) -> None:
""" """
Test Backtesting.get_timeframe() method Test Backtesting.get_timeframe() method
""" """
backtesting = _BACKTESTING mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
backtesting = Backtesting(default_conf)
data = backtesting.tickerdata_to_dataframe( data = backtesting.tickerdata_to_dataframe(
optimize.load_data( optimize.load_data(
@ -379,11 +355,12 @@ def test_get_timeframe(init_backtesting) -> None:
assert max_date.isoformat() == '2017-11-14T22:59:00+00:00' assert max_date.isoformat() == '2017-11-14T22:59:00+00:00'
def test_generate_text_table(init_backtesting): def test_generate_text_table(default_conf, mocker):
""" """
Test Backtesting.generate_text_table() method Test Backtesting.generate_text_table() method
""" """
backtesting = _BACKTESTING mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
backtesting = Backtesting(default_conf)
results = pd.DataFrame( results = pd.DataFrame(
{ {
@ -451,13 +428,13 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
assert log_has(line, caplog.record_tuples) assert log_has(line, caplog.record_tuples)
def test_backtest(init_backtesting, default_conf, fee, mocker) -> None: def test_backtest(default_conf, fee, mocker) -> None:
""" """
Test Backtesting.backtest() method Test Backtesting.backtest() method
""" """
mocker.patch('freqtrade.exchange.get_fee', fee) mocker.patch('freqtrade.exchange.get_fee', fee)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
backtesting = _BACKTESTING backtesting = Backtesting(default_conf)
data = optimize.load_data(None, ticker_interval='5m', pairs=['UNITTEST/BTC']) data = optimize.load_data(None, ticker_interval='5m', pairs=['UNITTEST/BTC'])
data = trim_dictlist(data, -200) data = trim_dictlist(data, -200)
@ -472,13 +449,13 @@ def test_backtest(init_backtesting, default_conf, fee, mocker) -> None:
assert not results.empty assert not results.empty
def test_backtest_1min_ticker_interval(init_backtesting, default_conf, fee, mocker) -> None: def test_backtest_1min_ticker_interval(default_conf, fee, mocker) -> None:
""" """
Test Backtesting.backtest() method with 1 min ticker Test Backtesting.backtest() method with 1 min ticker
""" """
mocker.patch('freqtrade.exchange.get_fee', fee) mocker.patch('freqtrade.exchange.get_fee', fee)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
backtesting = _BACKTESTING backtesting = Backtesting(default_conf)
# Run a backtesting for an exiting 5min ticker_interval # Run a backtesting for an exiting 5min ticker_interval
data = optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC']) data = optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC'])
@ -494,11 +471,12 @@ def test_backtest_1min_ticker_interval(init_backtesting, default_conf, fee, mock
assert not results.empty assert not results.empty
def test_processed(init_backtesting) -> None: def test_processed(default_conf, mocker) -> None:
""" """
Test Backtesting.backtest() method with offline data Test Backtesting.backtest() method with offline data
""" """
backtesting = _BACKTESTING mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
backtesting = Backtesting(default_conf)
dict_of_tickerrows = load_data_test('raise') dict_of_tickerrows = load_data_test('raise')
dataframes = backtesting.tickerdata_to_dataframe(dict_of_tickerrows) dataframes = backtesting.tickerdata_to_dataframe(dict_of_tickerrows)
@ -510,69 +488,90 @@ def test_processed(init_backtesting) -> None:
assert col in cols assert col in cols
def test_backtest_pricecontours(init_backtesting, default_conf, fee, mocker) -> None: def test_backtest_pricecontours(default_conf, fee, mocker) -> None:
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee) mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
tests = [['raise', 17], ['lower', 0], ['sine', 17]] tests = [['raise', 17], ['lower', 0], ['sine', 17]]
for [contour, numres] in tests: for [contour, numres] in tests:
simple_backtest(default_conf, contour, numres) simple_backtest(default_conf, contour, numres, mocker)
# Test backtest using offline data (testdata directory) # Test backtest using offline data (testdata directory)
def test_backtest_ticks(init_backtesting, default_conf, fee, mocker): def test_backtest_ticks(default_conf, fee, mocker):
mocker.patch('freqtrade.exchange.get_fee', fee) mocker.patch('freqtrade.exchange.get_fee', fee)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
ticks = [1, 5] ticks = [1, 5]
fun = _BACKTESTING.populate_buy_trend fun = Backtesting(default_conf).populate_buy_trend
for _ in ticks: for _ in ticks:
backtest_conf = _make_backtest_conf(conf=default_conf) backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
results = _run_backtest_1(fun, backtest_conf) backtesting = Backtesting(default_conf)
backtesting.populate_buy_trend = fun # Override
backtesting.populate_sell_trend = fun # Override
results = backtesting.backtest(backtest_conf)
assert not results.empty assert not results.empty
def test_backtest_clash_buy_sell(init_backtesting, default_conf): def test_backtest_clash_buy_sell(mocker, default_conf):
# Override the default buy trend function in our default_strategy # Override the default buy trend function in our default_strategy
def fun(dataframe=None): def fun(dataframe=None):
buy_value = 1 buy_value = 1
sell_value = 1 sell_value = 1
return _trend(dataframe, buy_value, sell_value) return _trend(dataframe, buy_value, sell_value)
backtest_conf = _make_backtest_conf(conf=default_conf) mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
results = _run_backtest_1(fun, backtest_conf) backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
backtesting = Backtesting(default_conf)
backtesting.populate_buy_trend = fun # Override
backtesting.populate_sell_trend = fun # Override
results = backtesting.backtest(backtest_conf)
assert results.empty assert results.empty
def test_backtest_only_sell(init_backtesting, default_conf): def test_backtest_only_sell(mocker, default_conf):
# Override the default buy trend function in our default_strategy # Override the default buy trend function in our default_strategy
def fun(dataframe=None): def fun(dataframe=None):
buy_value = 0 buy_value = 0
sell_value = 1 sell_value = 1
return _trend(dataframe, buy_value, sell_value) return _trend(dataframe, buy_value, sell_value)
backtest_conf = _make_backtest_conf(conf=default_conf) mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
results = _run_backtest_1(fun, backtest_conf) backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
backtesting = Backtesting(default_conf)
backtesting.populate_buy_trend = fun # Override
backtesting.populate_sell_trend = fun # Override
results = backtesting.backtest(backtest_conf)
assert results.empty assert results.empty
def test_backtest_alternate_buy_sell(init_backtesting, default_conf, fee, mocker): def test_backtest_alternate_buy_sell(default_conf, fee, mocker):
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee) mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
backtest_conf = _make_backtest_conf(conf=default_conf, pair='UNITTEST/BTC') mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
results = _run_backtest_1(_trend_alternate, backtest_conf) backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC')
backtesting = Backtesting(default_conf)
backtesting.populate_buy_trend = _trend_alternate # Override
backtesting.populate_sell_trend = _trend_alternate # Override
results = backtesting.backtest(backtest_conf)
assert len(results) == 3 assert len(results) == 3
def test_backtest_record(init_backtesting, default_conf, fee, mocker): def test_backtest_record(default_conf, fee, mocker):
names = [] names = []
records = [] records = []
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee) mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
mocker.patch( mocker.patch(
'freqtrade.optimize.backtesting.file_dump_json', 'freqtrade.optimize.backtesting.file_dump_json',
new=lambda n, r: (names.append(n), records.append(r)) new=lambda n, r: (names.append(n), records.append(r))
) )
backtest_conf = _make_backtest_conf( backtest_conf = _make_backtest_conf(
mocker,
conf=default_conf, conf=default_conf,
pair='UNITTEST/BTC', pair='UNITTEST/BTC',
record="trades" record="trades"
) )
results = _run_backtest_1(_trend_alternate, backtest_conf) backtesting = Backtesting(default_conf)
backtesting.populate_buy_trend = _trend_alternate # Override
backtesting.populate_sell_trend = _trend_alternate # Override
results = backtesting.backtest(backtest_conf)
assert len(results) == 3 assert len(results) == 3
# Assert file_dump_json was only called once # Assert file_dump_json was only called once
assert names == ['backtest-result.json'] assert names == ['backtest-result.json']
@ -595,7 +594,7 @@ def test_backtest_record(init_backtesting, default_conf, fee, mocker):
assert dur > 0 assert dur > 0
def test_backtest_start_live(init_backtesting, default_conf, mocker, caplog): def test_backtest_start_live(default_conf, mocker, caplog):
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC'] conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
mocker.patch('freqtrade.exchange.get_ticker_history', mocker.patch('freqtrade.exchange.get_ticker_history',