# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103 import logging import ccxt import math from unittest.mock import MagicMock import pandas as pd from freqtrade import exchange, optimize from freqtrade.optimize import preprocess from freqtrade.optimize.backtesting import backtest, generate_text_table, get_timeframe import freqtrade.optimize.backtesting as backtesting def trim_dictlist(dict_list, num): new = {} for pair, pair_data in dict_list.items(): new[pair] = pair_data[num:] return new def test_generate_text_table(): results = pd.DataFrame( { 'currency': ['ETH/BTC', 'ETH/BTC'], 'profit_percent': [0.1, 0.2], 'profit_BTC': [0.2, 0.4], 'duration': [10, 30], 'profit': [2, 0], 'loss': [0, 0] } ) print(generate_text_table({'ETH/BTC': {}}, results, 'BTC', '5m')) assert generate_text_table({'ETH/BTC': {}}, results, 'BTC', '5m') == ( 'pair buy count avg profit % total profit BTC avg duration profit loss\n' # noqa '------- ----------- -------------- ------------------ -------------- -------- ------\n' # noqa 'ETH/BTC 2 15.00 0.60000000 100.0 2 0\n' # noqa 'TOTAL 2 15.00 0.60000000 100.0 2 0') # noqa def test_get_timeframe(default_strategy): data = preprocess(optimize.load_data( None, ticker_interval='1m', pairs=['UNITTEST/BTC'])) min_date, max_date = get_timeframe(data) assert min_date.isoformat() == '2017-11-04T23:02:00+00:00' assert max_date.isoformat() == '2017-11-14T22:59:00+00:00' def test_backtest(default_strategy, default_conf, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.multiple('freqtrade.optimize.backtesting.exchange', get_fee=MagicMock(return_value=0.0025)) exchange._API = ccxt.binance() data = optimize.load_data(None, ticker_interval='5m', pairs=['UNITTEST/BTC']) data = trim_dictlist(data, -200) results = backtest({'stake_amount': default_conf['stake_amount'], 'processed': optimize.preprocess(data), 'max_open_trades': 10, 'realistic': True}) assert not results.empty def test_backtest_1min_ticker_interval(default_strategy, default_conf, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.multiple('freqtrade.optimize.backtesting.exchange', get_fee=MagicMock(return_value=0.0025)) exchange._API = ccxt.binance() # Run a backtesting for an exiting 5min ticker_interval data = optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC']) data = trim_dictlist(data, -200) results = backtest({'stake_amount': default_conf['stake_amount'], 'processed': optimize.preprocess(data), 'max_open_trades': 1, 'realistic': True}) assert not results.empty def load_data_test(what): timerange = ((None, 'line'), None, -100) data = optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC'], timerange=timerange) pair = data['UNITTEST/BTC'] datalen = len(pair) # Depending on the what parameter we now adjust the # loaded data looks: # pair :: [{'O': 0.123, 'H': 0.123, 'L': 0.123, # 'C': 0.123, 'V': 123.123, # 'T': '2017-11-04T23:02:00.000000'}] base = 0.001 if what == 'raise': return {'UNITTEST/BTC': [{'T': pair[x]['T'], # Keep old dates 'V': pair[x]['V'], # Keep old volume 'O': x * base, # But replace O,H,L,C 'H': x * base + 0.0001, 'L': x * base - 0.0001, 'C': x * base} for x in range(0, datalen)]} if what == 'lower': return {'UNITTEST/BTC': [{'T': pair[x]['T'], # Keep old dates 'V': pair[x]['V'], # Keep old volume 'O': 1 - x * base, # But replace O,H,L,C 'H': 1 - x * base + 0.0001, 'L': 1 - x * base - 0.0001, 'C': 1 - x * base} for x in range(0, datalen)]} if what == 'sine': hz = 0.1 # frequency return {'UNITTEST/BTC': [{'T': pair[x]['T'], # Keep old dates 'V': pair[x]['V'], # Keep old volume # But replace O,H,L,C 'O': math.sin(x * hz) / 1000 + base, 'H': math.sin(x * hz) / 1000 + base + 0.0001, 'L': math.sin(x * hz) / 1000 + base - 0.0001, 'C': math.sin(x * hz) / 1000 + base} for x in range(0, datalen)]} return data def simple_backtest(config, contour, num_results): data = load_data_test(contour) processed = optimize.preprocess(data) assert isinstance(processed, dict) results = backtest({'stake_amount': config['stake_amount'], 'processed': processed, 'max_open_trades': 1, 'realistic': True}) # results :: assert len(results) == num_results def test_processed(default_conf, mocker, default_strategy): mocker.patch.dict('freqtrade.main._CONF', default_conf) dict_of_tickerrows = load_data_test('raise') dataframes = optimize.preprocess(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', 'ema50', 'ao', 'macd', 'plus_dm']: assert col in cols def test_backtest_pricecontours(default_conf, mocker, default_strategy): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.multiple('freqtrade.optimize.backtesting.exchange', get_fee=MagicMock(return_value=0.0025)) tests = [['raise', 17], ['lower', 0], ['sine', 17]] for [contour, numres] in tests: simple_backtest(default_conf, contour, numres) def mocked_load_data(datadir, pairs=[], ticker_interval=0, refresh_pairs=False, timerange=None): tickerdata = optimize.load_tickerdata_file(datadir, 'UNITTEST/BTC', '1m', timerange=timerange) pairdata = {'UNITTEST/BTC': tickerdata} return pairdata def test_backtest_start(default_conf, mocker, caplog): default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC'] mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch('freqtrade.misc.load_config', new=lambda s: default_conf) mocker.patch.multiple('freqtrade.optimize', load_data=mocked_load_data) args = MagicMock() args.ticker_interval = '1m' args.level = 10 args.live = False args.datadir = None args.export = None args.timerange = '-100' # needed due to MagicMock malleability backtesting.start(args) # check the logs, that will contain the backtest result exists = ['Using max_open_trades: 1 ...', 'Using stake_amount: 0.001 ...', 'Measuring data from 2017-11-14T21:17:00+00:00 ' 'up to 2017-11-14T22:59:00+00:00 (0 days)..'] for line in exists: assert ('freqtrade.optimize.backtesting', logging.INFO, line) in caplog.record_tuples