From e94da7ca41c817ac96373232f9da3448c235a330 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 15 Oct 2018 22:02:23 +0200 Subject: [PATCH 01/16] inverse backtest logic to loop over time - not pairs (more realistic) --- freqtrade/optimize/backtesting.py | 40 +++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 6fcde64fa..212c675fd 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -66,6 +66,7 @@ class Backtesting(object): if self.config.get('strategy_list', None): # Force one interval self.ticker_interval = str(self.config.get('ticker_interval')) + self.ticker_interval_mins = constants.TICKER_INTERVAL_MINUTES[self.ticker_interval] for strat in list(self.config['strategy_list']): stratconf = deepcopy(self.config) stratconf['strategy'] = strat @@ -86,6 +87,8 @@ class Backtesting(object): """ self.strategy = strategy self.ticker_interval = self.config.get('ticker_interval') + self.ticker_interval_mins = constants.TICKER_INTERVAL_MINUTES[self.ticker_interval] + self.tickerdata_to_dataframe = strategy.tickerdata_to_dataframe self.advise_buy = strategy.advise_buy self.advise_sell = strategy.advise_sell @@ -280,8 +283,13 @@ class Backtesting(object): processed = args['processed'] max_open_trades = args.get('max_open_trades', 0) position_stacking = args.get('position_stacking', False) + start_date = args.get('start_date') + end_date = args.get('end_date') trades = [] trade_count_lock: Dict = {} + ticker: Dict = {} + pairs = [] + # Create ticker dict for pair, pair_data in processed.items(): pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run @@ -296,15 +304,29 @@ class Backtesting(object): # Convert from Pandas to list for performance reasons # (Looping Pandas is slow.) - ticker = [x for x in ticker_data.itertuples()] + ticker[pair] = [x for x in ticker_data.itertuples()] + pairs.append(pair) + + lock_pair_until: Dict = {} + tmp = start_date + timedelta(minutes=self.ticker_interval_mins) + index = 0 + # Loop timerange and test per pair + while tmp < end_date: + # print(f"time: {tmp}") + for i, pair in enumerate(ticker): + try: + row = ticker[pair][index] + except IndexError: + # missing Data for one pair ... + # TODO:howto handle this + # logger.warning(f"i: {index} - {tmp} did not exist for {pair}") + continue - lock_pair_until = None - for index, row in enumerate(ticker): if row.buy == 0 or row.sell == 1: continue # skip rows where no buy signal or that would immediately sell off if not position_stacking: - if lock_pair_until is not None and row.date <= lock_pair_until: + if pair in lock_pair_until and row.date <= lock_pair_until[pair]: continue if max_open_trades > 0: # Check if max_open_trades has already been reached for the given date @@ -313,17 +335,19 @@ class Backtesting(object): trade_count_lock[row.date] = trade_count_lock.get(row.date, 0) + 1 - trade_entry = self._get_sell_trade_entry(pair, row, ticker[index + 1:], + trade_entry = self._get_sell_trade_entry(pair, row, ticker[pair][index + 1:], trade_count_lock, args) if trade_entry: - lock_pair_until = trade_entry.close_time + lock_pair_until[pair] = trade_entry.close_time trades.append(trade_entry) else: # Set lock_pair_until to end of testing period if trade could not be closed # This happens only if the buy-signal was with the last candle - lock_pair_until = ticker_data.iloc[-1].date + lock_pair_until[pair] = end_date + tmp += timedelta(minutes=self.ticker_interval_mins) + index += 1 return DataFrame.from_records(trades, columns=BacktestResult._fields) def start(self) -> None: @@ -390,6 +414,8 @@ class Backtesting(object): 'processed': preprocessed, 'max_open_trades': max_open_trades, 'position_stacking': self.config.get('position_stacking', False), + 'start_date': min_date, + 'end_date': max_date, } ) From 96efd12a312c14afae0034c4dae2d99972059e6f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Oct 2018 19:35:16 +0200 Subject: [PATCH 02/16] add new options to hyperopt --- freqtrade/optimize/hyperopt.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index b2d05d603..9766f5acb 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -276,11 +276,14 @@ class Hyperopt(Backtesting): self.strategy.stoploss = params['stoploss'] processed = load(TICKERDATA_PICKLE) + min_date, max_date = Backtesting.get_timeframe(processed) results = self.backtest( { 'stake_amount': self.config['stake_amount'], 'processed': processed, 'position_stacking': self.config.get('position_stacking', True), + 'start_date': min_date, + 'end_date': max_date, } ) result_explanation = self.format_results(results) From 6729dfa6d3ed8fec31f371f936f475184256b5dd Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Oct 2018 20:32:33 +0200 Subject: [PATCH 03/16] Add get_timeframe mock for hyperopt --- freqtrade/tests/optimize/test_hyperopt.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index c93f2d316..3ea374bf4 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -1,4 +1,5 @@ # pragma pylint: disable=missing-docstring,W0212,C0103 +from datetime import datetime import os from unittest.mock import MagicMock @@ -291,6 +292,10 @@ def test_generate_optimizer(mocker, default_conf) -> None: 'freqtrade.optimize.hyperopt.Hyperopt.backtest', MagicMock(return_value=backtest_result) ) + mocker.patch( + 'freqtrade.optimize.backtesting.Backtesting.get_timeframe', + MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))) + ) patch_exchange(mocker) mocker.patch('freqtrade.optimize.hyperopt.load', MagicMock()) From 03cda8e23eccbeccf8f66d618605d2413b3c33f1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Oct 2018 20:09:12 +0200 Subject: [PATCH 04/16] remove meaningless backtesting test --- freqtrade/tests/optimize/test_backtesting.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 20f2a6582..2d2b2d4a7 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -449,7 +449,7 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None: ) default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC'] - default_conf['ticker_interval'] = "1m" + default_conf['ticker_interval'] = '1m' default_conf['live'] = False default_conf['datadir'] = None default_conf['export'] = None @@ -587,21 +587,6 @@ def test_backtest_pricecontours(default_conf, fee, mocker) -> None: simple_backtest(default_conf, contour, numres, mocker) -# Test backtest using offline data (testdata directory) -def test_backtest_ticks(default_conf, fee, mocker): - mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - patch_exchange(mocker) - ticks = [1, 5] - fun = Backtesting(default_conf).advise_buy - for _ in ticks: - backtest_conf = _make_backtest_conf(mocker, conf=default_conf) - backtesting = Backtesting(default_conf) - backtesting.advise_buy = fun # Override - backtesting.advise_sell = fun # Override - results = backtesting.backtest(backtest_conf) - assert not results.empty - - def test_backtest_clash_buy_sell(mocker, default_conf): # Override the default buy trend function in our default_strategy def fun(dataframe=None, pair=None): From db17ccef2b9e89982d20cbfe11623f47a702095d Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Oct 2018 20:34:20 +0200 Subject: [PATCH 05/16] Adapt backtesting-tests to new backtest-logic --- freqtrade/tests/optimize/test_backtesting.py | 32 +++++++++++++++----- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 2d2b2d4a7..e3e078d72 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -86,17 +86,21 @@ def load_data_test(what): def simple_backtest(config, contour, num_results, mocker) -> None: patch_exchange(mocker) + config['ticker_interval'] = '1m' backtesting = Backtesting(config) data = load_data_test(contour) processed = backtesting.strategy.tickerdata_to_dataframe(data) + min_date, max_date = Backtesting.get_timeframe(processed) assert isinstance(processed, dict) results = backtesting.backtest( { 'stake_amount': config['stake_amount'], 'processed': processed, 'max_open_trades': 1, - 'position_stacking': False + 'position_stacking': False, + 'start_date': min_date, + 'end_date': max_date, } ) # results :: @@ -123,12 +127,16 @@ def _make_backtest_conf(mocker, conf=None, pair='UNITTEST/BTC', record=None): data = trim_dictlist(data, -201) patch_exchange(mocker) backtesting = Backtesting(conf) + processed = backtesting.strategy.tickerdata_to_dataframe(data) + min_date, max_date = Backtesting.get_timeframe(processed) return { 'stake_amount': conf['stake_amount'], - 'processed': backtesting.strategy.tickerdata_to_dataframe(data), + 'processed': processed, 'max_open_trades': 10, 'position_stacking': False, - 'record': record + 'record': record, + 'start_date': min_date, + 'end_date': max_date, } @@ -505,12 +513,15 @@ def test_backtest(default_conf, fee, mocker) -> None: data = optimize.load_data(None, ticker_interval='5m', pairs=['UNITTEST/BTC']) data = trim_dictlist(data, -200) data_processed = backtesting.strategy.tickerdata_to_dataframe(data) + min_date, max_date = Backtesting.get_timeframe(data_processed) results = backtesting.backtest( { 'stake_amount': default_conf['stake_amount'], 'processed': data_processed, 'max_open_trades': 10, - 'position_stacking': False + 'position_stacking': False, + 'start_date': min_date, + 'end_date': max_date, } ) assert not results.empty @@ -554,12 +565,16 @@ def test_backtest_1min_ticker_interval(default_conf, fee, mocker) -> None: # 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) + processed = backtesting.strategy.tickerdata_to_dataframe(data) + min_date, max_date = Backtesting.get_timeframe(processed) results = backtesting.backtest( { 'stake_amount': default_conf['stake_amount'], - 'processed': backtesting.strategy.tickerdata_to_dataframe(data), + 'processed': processed, 'max_open_trades': 1, - 'position_stacking': False + 'position_stacking': False, + 'start_date': min_date, + 'end_date': max_date, } ) assert not results.empty @@ -582,7 +597,10 @@ def test_processed(default_conf, mocker) -> None: def test_backtest_pricecontours(default_conf, fee, mocker) -> None: mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - tests = [['raise', 18], ['lower', 0], ['sine', 19]] + tests = [['raise', 18], ['lower', 0], ['sine', 16]] + # We need to enable sell-signal - otherwise it sells on ROI!! + default_conf['experimental'] = {"use_sell_signal": True} + for [contour, numres] in tests: simple_backtest(default_conf, contour, numres, mocker) From 83a8d79115b1056c1469338da4965817243e33f2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Oct 2018 20:35:08 +0200 Subject: [PATCH 06/16] Fix alternate buy/sell (this should respect the sell signal!) --- freqtrade/tests/optimize/test_backtesting.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index e3e078d72..20c117a8e 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -638,14 +638,16 @@ def test_backtest_only_sell(mocker, default_conf): def test_backtest_alternate_buy_sell(default_conf, fee, mocker): mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC') + # We need to enable sell-signal - otherwise it sells on ROI!! + default_conf['experimental'] = {"use_sell_signal": True} backtesting = Backtesting(default_conf) backtesting.advise_buy = _trend_alternate # Override backtesting.advise_sell = _trend_alternate # Override results = backtesting.backtest(backtest_conf) backtesting._store_backtest_result("test_.json", results) - assert len(results) == 4 + assert len(results) == 21 # One trade was force-closed at the end - assert len(results.loc[results.open_at_end]) == 1 + assert len(results.loc[results.open_at_end]) == 0 def test_backtest_record(default_conf, fee, mocker): From 66487f2a133decf3ef887f732fc58b1927c9079a Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Oct 2018 20:36:24 +0200 Subject: [PATCH 07/16] require start/end-date argument in backtest --- freqtrade/optimize/backtesting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 212c675fd..49f6375f7 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -283,8 +283,8 @@ class Backtesting(object): processed = args['processed'] max_open_trades = args.get('max_open_trades', 0) position_stacking = args.get('position_stacking', False) - start_date = args.get('start_date') - end_date = args.get('end_date') + start_date = args['start_date'] + end_date = args['end_date'] trades = [] trade_count_lock: Dict = {} ticker: Dict = {} From 2371d1e696b3a6491cbc2883b0fa523b491b9896 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 17 Oct 2018 06:54:07 +0200 Subject: [PATCH 08/16] Fix backtest test (don't use 8m file if we use 1m tickers) --- freqtrade/tests/optimize/test_backtesting.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 20c117a8e..d0c8a88aa 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -640,12 +640,16 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker): backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC') # We need to enable sell-signal - otherwise it sells on ROI!! default_conf['experimental'] = {"use_sell_signal": True} + default_conf['ticker_interval'] = '1m' backtesting = Backtesting(default_conf) backtesting.advise_buy = _trend_alternate # Override backtesting.advise_sell = _trend_alternate # Override results = backtesting.backtest(backtest_conf) backtesting._store_backtest_result("test_.json", results) - assert len(results) == 21 + # 200 candles in backtest data + # won't buy on first (shifted by 1) + # 100 buys signals + assert len(results) == 99 # One trade was force-closed at the end assert len(results.loc[results.open_at_end]) == 0 From fa4c199aa63d13837688ed0b7f3fa2d0a7d627a3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 4 Nov 2018 13:23:59 +0100 Subject: [PATCH 09/16] fix some mismatches after rebase --- freqtrade/tests/optimize/test_backtesting.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index d0c8a88aa..b0d4f2cbd 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -13,6 +13,7 @@ from arrow import Arrow from freqtrade import DependencyException, constants, optimize from freqtrade.arguments import Arguments, TimeRange +from freqtrade.optimize import get_timeframe from freqtrade.optimize.backtesting import (Backtesting, setup_configuration, start) from freqtrade.tests.conftest import log_has, patch_exchange @@ -91,7 +92,7 @@ def simple_backtest(config, contour, num_results, mocker) -> None: data = load_data_test(contour) processed = backtesting.strategy.tickerdata_to_dataframe(data) - min_date, max_date = Backtesting.get_timeframe(processed) + min_date, max_date = get_timeframe(processed) assert isinstance(processed, dict) results = backtesting.backtest( { @@ -128,7 +129,7 @@ def _make_backtest_conf(mocker, conf=None, pair='UNITTEST/BTC', record=None): patch_exchange(mocker) backtesting = Backtesting(conf) processed = backtesting.strategy.tickerdata_to_dataframe(data) - min_date, max_date = Backtesting.get_timeframe(processed) + min_date, max_date = get_timeframe(processed) return { 'stake_amount': conf['stake_amount'], 'processed': processed, @@ -513,7 +514,7 @@ def test_backtest(default_conf, fee, mocker) -> None: data = optimize.load_data(None, ticker_interval='5m', pairs=['UNITTEST/BTC']) data = trim_dictlist(data, -200) data_processed = backtesting.strategy.tickerdata_to_dataframe(data) - min_date, max_date = Backtesting.get_timeframe(data_processed) + min_date, max_date = get_timeframe(data_processed) results = backtesting.backtest( { 'stake_amount': default_conf['stake_amount'], @@ -566,7 +567,7 @@ def test_backtest_1min_ticker_interval(default_conf, fee, mocker) -> None: data = optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC']) data = trim_dictlist(data, -200) processed = backtesting.strategy.tickerdata_to_dataframe(data) - min_date, max_date = Backtesting.get_timeframe(processed) + min_date, max_date = get_timeframe(processed) results = backtesting.backtest( { 'stake_amount': default_conf['stake_amount'], From 9cd2ed5a16ae7ed5c617dd345ade46d7b8d6d18c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 4 Nov 2018 13:43:09 +0100 Subject: [PATCH 10/16] fix hyperopt get_timeframe mock --- freqtrade/optimize/hyperopt.py | 4 ++-- freqtrade/tests/optimize/test_hyperopt.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 9766f5acb..edf73fcf3 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -23,7 +23,7 @@ from skopt.space import Categorical, Dimension, Integer, Real import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration -from freqtrade.optimize import load_data +from freqtrade.optimize import load_data, get_timeframe from freqtrade.optimize.backtesting import Backtesting logger = logging.getLogger(__name__) @@ -276,7 +276,7 @@ class Hyperopt(Backtesting): self.strategy.stoploss = params['stoploss'] processed = load(TICKERDATA_PICKLE) - min_date, max_date = Backtesting.get_timeframe(processed) + min_date, max_date = get_timeframe(processed) results = self.backtest( { 'stake_amount': self.config['stake_amount'], diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index 3ea374bf4..703b88fc1 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -6,7 +6,7 @@ from unittest.mock import MagicMock import pandas as pd import pytest -from freqtrade.optimize.__init__ import load_tickerdata_file +from freqtrade.optimize import load_tickerdata_file from freqtrade.optimize.hyperopt import Hyperopt, start from freqtrade.strategy.resolver import StrategyResolver from freqtrade.tests.conftest import log_has, patch_exchange @@ -293,7 +293,7 @@ def test_generate_optimizer(mocker, default_conf) -> None: MagicMock(return_value=backtest_result) ) mocker.patch( - 'freqtrade.optimize.backtesting.Backtesting.get_timeframe', + 'freqtrade.optimize.hyperopt.get_timeframe', MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))) ) patch_exchange(mocker) From 93429a58b2d605215b4157cb8409cb845ded97ed Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 4 Nov 2018 13:45:22 +0100 Subject: [PATCH 11/16] remove TODO --- freqtrade/optimize/backtesting.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 49f6375f7..4dc3119d3 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -318,8 +318,7 @@ class Backtesting(object): row = ticker[pair][index] except IndexError: # missing Data for one pair ... - # TODO:howto handle this - # logger.warning(f"i: {index} - {tmp} did not exist for {pair}") + # Warnings for this are shown by `validate_backtest_data` continue if row.buy == 0 or row.sell == 1: From 56dcf080a906645df2a2872618c3666c129d8973 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 5 Nov 2018 20:03:04 +0100 Subject: [PATCH 12/16] Add explicit test for parallel trades --- freqtrade/tests/optimize/test_backtesting.py | 71 ++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index b0d4f2cbd..e03e011f3 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -655,6 +655,77 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker): assert len(results.loc[results.open_at_end]) == 0 +def test_backtest_multi_pair(default_conf, fee, mocker): + + def evaluate_result_multi(results, freq, max_open_trades): + # Find overlapping trades by expanding each trade once per period + # and then counting overlaps + dates = [pd.Series(pd.date_range(row[1].open_time, row[1].close_time, freq=freq)) + for row in results[['open_time', 'close_time']].iterrows()] + deltas = [len(x) for x in dates] + dates = pd.Series(pd.concat(dates).values, name='date') + df2 = pd.DataFrame(np.repeat(results.values, deltas, axis=0), columns=results.columns) + + df2 = df2.astype(dtype={"open_time": "datetime64", "close_time": "datetime64"}) + df2 = pd.concat([dates, df2], axis=1) + df2 = df2.set_index('date') + df_final = df2.resample(freq)[['pair']].count() + return df_final[df_final['pair'] > max_open_trades] + + def _trend_alternate_hold(dataframe=None, metadata=None): + """ + Buy every 8th candle - sell every other 8th -2 (hold on to pairs a bit) + """ + multi = 8 + dataframe['buy'] = np.where(dataframe.index % multi == 0, 1, 0) + dataframe['sell'] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0) + if metadata['pair'] in('ETH/BTC', 'LTC/BTC'): + dataframe['buy'] = dataframe['buy'].shift(-4) + dataframe['sell'] = dataframe['sell'].shift(-4) + return dataframe + + mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + pairs = ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC', 'NXT/BTC'] + data = optimize.load_data(None, ticker_interval='5m', pairs=pairs) + data = trim_dictlist(data, -500) + # We need to enable sell-signal - otherwise it sells on ROI!! + default_conf['experimental'] = {"use_sell_signal": True} + default_conf['ticker_interval'] = '5m' + + backtesting = Backtesting(default_conf) + backtesting.advise_buy = _trend_alternate_hold # Override + backtesting.advise_sell = _trend_alternate_hold # Override + + data_processed = backtesting.strategy.tickerdata_to_dataframe(data) + min_date, max_date = get_timeframe(data_processed) + backtest_conf = { + 'stake_amount': default_conf['stake_amount'], + 'processed': data_processed, + 'max_open_trades': 3, + 'position_stacking': False, + 'start_date': min_date, + 'end_date': max_date, + } + + results = backtesting.backtest(backtest_conf) + + # Make sure we have parallel trades + assert len(evaluate_result_multi(results, '5min', 2)) > 0 + # make sure we don't have trades with more than configured max_open_trades + assert len(evaluate_result_multi(results, '5min', 3)) == 0 + + backtest_conf = { + 'stake_amount': default_conf['stake_amount'], + 'processed': data_processed, + 'max_open_trades': 1, + 'position_stacking': False, + 'start_date': min_date, + 'end_date': max_date, + } + results = backtesting.backtest(backtest_conf) + assert len(evaluate_result_multi(results, '5min', 1)) == 0 + + def test_backtest_record(default_conf, fee, mocker): names = [] records = [] From 272ff51d512d0c06832812f985744a7cbea72719 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 6 Nov 2018 20:37:59 +0100 Subject: [PATCH 13/16] correctly patch exchange --- freqtrade/tests/optimize/test_backtesting.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index e03e011f3..aa1f144cd 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -685,6 +685,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker): return dataframe mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + patch_exchange(mocker) pairs = ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC', 'NXT/BTC'] data = optimize.load_data(None, ticker_interval='5m', pairs=pairs) data = trim_dictlist(data, -500) From 5c5fe4c13a4b92be65e0e52903bb804f0e397f09 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 9 Nov 2018 07:14:43 +0100 Subject: [PATCH 14/16] Fix test --- freqtrade/tests/optimize/test_backtesting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index aa1f144cd..03d874f5f 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -598,7 +598,7 @@ def test_processed(default_conf, mocker) -> None: def test_backtest_pricecontours(default_conf, fee, mocker) -> None: mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - tests = [['raise', 18], ['lower', 0], ['sine', 16]] + tests = [['raise', 18], ['lower', 0], ['sine', 19]] # We need to enable sell-signal - otherwise it sells on ROI!! default_conf['experimental'] = {"use_sell_signal": True} From 292962d64dfb88d9d0b4b55aab9f5883577867b3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 9 Nov 2018 19:34:18 +0100 Subject: [PATCH 15/16] Fix tests --- freqtrade/tests/optimize/__init__.py | 5 +++-- freqtrade/tests/optimize/test_backtest_detail.py | 7 ++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/freqtrade/tests/optimize/__init__.py b/freqtrade/tests/optimize/__init__.py index 2b7222e88..58ea7c343 100644 --- a/freqtrade/tests/optimize/__init__.py +++ b/freqtrade/tests/optimize/__init__.py @@ -4,9 +4,10 @@ import arrow from pandas import DataFrame from freqtrade.strategy.interface import SellType +from freqtrade.constants import TICKER_INTERVAL_MINUTES ticker_start_time = arrow.get(2018, 10, 3) -ticker_interval_in_minute = 60 +tests_ticker_interval = "1h" class BTrade(NamedTuple): @@ -31,7 +32,7 @@ class BTContainer(NamedTuple): def _get_frame_time_from_offset(offset): return ticker_start_time.shift( - minutes=(offset * ticker_interval_in_minute)).datetime + minutes=(offset * TICKER_INTERVAL_MINUTES[tests_ticker_interval])).datetime def _build_backtest_dataframe(ticker_with_signals): diff --git a/freqtrade/tests/optimize/test_backtest_detail.py b/freqtrade/tests/optimize/test_backtest_detail.py index 806c136bc..7db6913f3 100644 --- a/freqtrade/tests/optimize/test_backtest_detail.py +++ b/freqtrade/tests/optimize/test_backtest_detail.py @@ -6,10 +6,11 @@ from pandas import DataFrame import pytest +from freqtrade.optimize import get_timeframe from freqtrade.optimize.backtesting import Backtesting from freqtrade.strategy.interface import SellType from freqtrade.tests.optimize import (BTrade, BTContainer, _build_backtest_dataframe, - _get_frame_time_from_offset) + _get_frame_time_from_offset, tests_ticker_interval) from freqtrade.tests.conftest import patch_exchange @@ -147,6 +148,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: """ default_conf["stoploss"] = data.stop_loss default_conf["minimal_roi"] = {"0": data.roi} + default_conf['ticker_interval'] = tests_ticker_interval mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.0)) patch_exchange(mocker) frame = _build_backtest_dataframe(data.data) @@ -158,11 +160,14 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: pair = 'UNITTEST/BTC' # Dummy data as we mock the analyze functions data_processed = {pair: DataFrame()} + min_date, max_date = get_timeframe({pair: frame}) results = backtesting.backtest( { 'stake_amount': default_conf['stake_amount'], 'processed': data_processed, 'max_open_trades': 10, + 'start_date': min_date, + 'end_date': max_date, } ) print(results.T) From 59cd4fe0ef735bb18d2d3ab484616921670176c0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 9 Nov 2018 19:34:46 +0100 Subject: [PATCH 16/16] Remove boilerplate comments --- freqtrade/tests/optimize/test_backtest_detail.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/freqtrade/tests/optimize/test_backtest_detail.py b/freqtrade/tests/optimize/test_backtest_detail.py index 7db6913f3..e5849319e 100644 --- a/freqtrade/tests/optimize/test_backtest_detail.py +++ b/freqtrade/tests/optimize/test_backtest_detail.py @@ -174,18 +174,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: assert len(results) == len(data.trades) assert round(results["profit_percent"].sum(), 3) == round(data.profit_perc, 3) - # if data.sell_r == SellType.STOP_LOSS: - # assert log_has("Stop loss hit.", caplog.record_tuples) - # else: - # assert not log_has("Stop loss hit.", caplog.record_tuples) - # log_test = (f'Force_selling still open trade UNITTEST/BTC with ' - # f'{results.iloc[-1].profit_percent} perc - {results.iloc[-1].profit_abs}') - # if data.sell_r == SellType.FORCE_SELL: - # assert log_has(log_test, - # caplog.record_tuples) - # else: - # assert not log_has(log_test, - # caplog.record_tuples) + for c, trade in enumerate(data.trades): res = results.iloc[c] assert res.sell_reason == trade.sell_reason