From 5623ea3ac65ff832f5b680abf00592c7e0ba77fd Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sat, 9 Jun 2018 21:44:20 +0200 Subject: [PATCH 01/46] Add forcesell at end of backtest period --- freqtrade/optimize/backtesting.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 028a4f521..2b1bb98a0 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -130,6 +130,21 @@ class Backtesting(object): (sell_row.date - buy_row.date).seconds // 60 ), \ sell_row.date + if partial_ticker: + # no sell condition found - trade stil open at end of backtest period + sell_row = partial_ticker[-1] + logger.info('Force_selling still open trade %s with %s perc - %s', pair, + trade.calc_profit_percent(rate=sell_row.close), + trade.calc_profit(rate=sell_row.close)) + return \ + sell_row, \ + ( + pair, + trade.calc_profit_percent(rate=sell_row.close), + trade.calc_profit(rate=sell_row.close), + (sell_row.date - buy_row.date).seconds // 60 + ), \ + sell_row.date return None def backtest(self, args: Dict) -> DataFrame: @@ -170,6 +185,7 @@ class Backtesting(object): ticker_data.drop(ticker_data.head(1).index, inplace=True) + # TODO: why convert from Pandas to list?? ticker = [x for x in ticker_data.itertuples()] lock_pair_until = None @@ -202,6 +218,11 @@ class Backtesting(object): row.date.strftime('%s'), row2.date.strftime('%s'), index, trade_entry[3])) + 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 + # For now export inside backtest(), maybe change so that backtest() # returns a tuple like: (dataframe, records, logs, etc) if record and record.find('trades') >= 0: From 24a875ed465442100befb6f07d626e7caeb37a25 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sat, 9 Jun 2018 21:44:57 +0200 Subject: [PATCH 02/46] remove experimental parameters - they are read by analyze.py anyway --- freqtrade/optimize/backtesting.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 2b1bb98a0..56a9ca9bc 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -277,16 +277,12 @@ class Backtesting(object): ) # Execute backtest and print results - sell_profit_only = self.config.get('experimental', {}).get('sell_profit_only', False) - use_sell_signal = self.config.get('experimental', {}).get('use_sell_signal', False) results = self.backtest( { 'stake_amount': self.config.get('stake_amount'), 'processed': preprocessed, 'max_open_trades': max_open_trades, 'realistic': self.config.get('realistic_simulation', False), - 'sell_profit_only': sell_profit_only, - 'use_sell_signal': use_sell_signal, 'record': self.config.get('export'), 'recordfn': self.config.get('exportfilename'), } From 3094acc7fb33559f7212e313677710131af9ec9f Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sun, 10 Jun 2018 08:58:28 +0200 Subject: [PATCH 03/46] update comment --- freqtrade/optimize/backtesting.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 56a9ca9bc..d6748bd76 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -185,7 +185,8 @@ class Backtesting(object): ticker_data.drop(ticker_data.head(1).index, inplace=True) - # TODO: why convert from Pandas to list?? + # Convert from Pandas to list for performance reasons + # (Looping Pandas is slow.) ticker = [x for x in ticker_data.itertuples()] lock_pair_until = None From c1b2e06edad52dc91602088c67c4a97b05780b16 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sun, 10 Jun 2018 09:07:04 +0200 Subject: [PATCH 04/46] simplify return from _get_sell_trade_entry --- freqtrade/optimize/backtesting.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index d6748bd76..acb419ef5 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -128,8 +128,7 @@ class Backtesting(object): trade.calc_profit_percent(rate=sell_row.close), trade.calc_profit(rate=sell_row.close), (sell_row.date - buy_row.date).seconds // 60 - ), \ - sell_row.date + ) if partial_ticker: # no sell condition found - trade stil open at end of backtest period sell_row = partial_ticker[-1] @@ -143,8 +142,7 @@ class Backtesting(object): trade.calc_profit_percent(rate=sell_row.close), trade.calc_profit(rate=sell_row.close), (sell_row.date - buy_row.date).seconds // 60 - ), \ - sell_row.date + ) return None def backtest(self, args: Dict) -> DataFrame: @@ -208,8 +206,8 @@ class Backtesting(object): trade_count_lock, args) if ret: - row2, trade_entry, next_date = ret - lock_pair_until = next_date + row2, trade_entry = ret + lock_pair_until = row2.date trades.append(trade_entry) if record: # Note, need to be json.dump friendly From 9c57d3aa8b98c95fbd31fb80e2323fa80bb537ac Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sun, 10 Jun 2018 13:15:25 +0200 Subject: [PATCH 05/46] add BacktestresultTuple --- freqtrade/optimize/backtesting.py | 88 +++++++++++++++++-------------- 1 file changed, 49 insertions(+), 39 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index acb419ef5..574b7b283 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -6,7 +6,7 @@ This module contains the backtesting logic import logging import operator from argparse import Namespace -from typing import Dict, Tuple, Any, List, Optional +from typing import Dict, Tuple, Any, List, Optional, NamedTuple import arrow from pandas import DataFrame @@ -23,6 +23,18 @@ from freqtrade.persistence import Trade logger = logging.getLogger(__name__) +class BacktestResult(NamedTuple): + """ + NamedTuple Defining BacktestResults inputs. + """ + pair: str + profit_percent: float + profit_abs: float + open_time: float + close_time: float + trade_duration: float + + class Backtesting(object): """ Backtesting class, this class contains all the logic to run a backtest @@ -73,15 +85,15 @@ class Backtesting(object): headers = ['pair', 'buy count', 'avg profit %', 'total profit ' + stake_currency, 'avg duration', 'profit', 'loss'] for pair in data: - result = results[results.currency == pair] + result = results[results.pair == pair] tabular_data.append([ pair, len(result.index), result.profit_percent.mean() * 100.0, - result.profit_BTC.sum(), - result.duration.mean(), - len(result[result.profit_BTC > 0]), - len(result[result.profit_BTC < 0]) + result.profit_abs.sum(), + result.trade_duration.mean(), + len(result[result.profit_abs > 0]), + len(result[result.profit_abs < 0]) ]) # Append Total @@ -89,16 +101,16 @@ class Backtesting(object): 'TOTAL', len(results.index), results.profit_percent.mean() * 100.0, - results.profit_BTC.sum(), - results.duration.mean(), - len(results[results.profit_BTC > 0]), - len(results[results.profit_BTC < 0]) + results.profit_abs.sum(), + results.trade_duration.mean(), + len(results[results.profit_abs > 0]), + len(results[results.profit_abs < 0]) ]) return tabulate(tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="pipe") def _get_sell_trade_entry( self, pair: str, buy_row: DataFrame, - partial_ticker: List, trade_count_lock: Dict, args: Dict) -> Optional[Tuple]: + partial_ticker: List, trade_count_lock: Dict, args: Dict) -> Optional[BacktestResult]: stake_amount = args['stake_amount'] max_open_trades = args.get('max_open_trades', 0) @@ -121,28 +133,27 @@ class Backtesting(object): buy_signal = sell_row.buy if self.analyze.should_sell(trade, sell_row.close, sell_row.date, buy_signal, sell_row.sell): - return \ - sell_row, \ - ( - pair, - trade.calc_profit_percent(rate=sell_row.close), - trade.calc_profit(rate=sell_row.close), - (sell_row.date - buy_row.date).seconds // 60 - ) + + return BacktestResult(pair=pair, + profit_percent=trade.calc_profit_percent(rate=sell_row.close), + profit_abs=trade.calc_profit(rate=sell_row.close), + open_time=buy_row.date, + close_time=sell_row.date, + trade_duration=(sell_row.date - buy_row.date).seconds // 60 + ) if partial_ticker: # no sell condition found - trade stil open at end of backtest period sell_row = partial_ticker[-1] - logger.info('Force_selling still open trade %s with %s perc - %s', pair, - trade.calc_profit_percent(rate=sell_row.close), - trade.calc_profit(rate=sell_row.close)) - return \ - sell_row, \ - ( - pair, - trade.calc_profit_percent(rate=sell_row.close), - trade.calc_profit(rate=sell_row.close), - (sell_row.date - buy_row.date).seconds // 60 - ) + btr = BacktestResult(pair=pair, + profit_percent=trade.calc_profit_percent(rate=sell_row.close), + profit_abs=trade.calc_profit(rate=sell_row.close), + open_time=buy_row.date, + close_time=sell_row.date, + trade_duration=(sell_row.date - buy_row.date).seconds // 60 + ) + logger.info('Force_selling still open trade %s with %s perc - %s', btr.pair, + btr.profit_percent, btr.profit_abs) + return btr return None def backtest(self, args: Dict) -> DataFrame: @@ -202,20 +213,19 @@ class Backtesting(object): trade_count_lock[row.date] = trade_count_lock.get(row.date, 0) + 1 - ret = self._get_sell_trade_entry(pair, row, ticker[index + 1:], - trade_count_lock, args) + trade_entry = self._get_sell_trade_entry(pair, row, ticker[index + 1:], + trade_count_lock, args) - if ret: - row2, trade_entry = ret - lock_pair_until = row2.date + if trade_entry: + lock_pair_until = trade_entry.close_time trades.append(trade_entry) if record: # Note, need to be json.dump friendly # record a tuple of pair, current_profit_percent, # entry-date, duration - records.append((pair, trade_entry[1], - row.date.strftime('%s'), - row2.date.strftime('%s'), + records.append((pair, trade_entry.profit_percent, + trade_entry.open_time.strftime('%s'), + trade_entry.close_time.strftime('%s'), index, trade_entry[3])) else: # Set lock_pair_until to end of testing period if trade could not be closed @@ -228,7 +238,7 @@ class Backtesting(object): logger.info('Dumping backtest results to %s', recordfilename) file_dump_json(recordfilename, records) labels = ['currency', 'profit_percent', 'profit_BTC', 'duration'] - return DataFrame.from_records(trades, columns=labels) + return DataFrame.from_records(trades, columns=BacktestResult._fields) def start(self) -> None: """ From 7b5a2946e5b8636a4fd6d181b976951b03ede96f Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sun, 10 Jun 2018 13:19:32 +0200 Subject: [PATCH 06/46] adjust for forcesell backtesting --- 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 33d9703de..72de5eb9c 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -538,7 +538,7 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker): 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) == 4 def test_backtest_record(default_conf, fee, mocker): From c9476fade8180bb57b3fb7f123a25badd174a48d Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sun, 10 Jun 2018 13:20:41 +0200 Subject: [PATCH 07/46] adjust tests for forcesell --- 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 72de5eb9c..ad328edc2 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -478,7 +478,7 @@ def test_processed(default_conf, mocker) -> None: def test_backtest_pricecontours(default_conf, fee, mocker) -> None: mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee) - tests = [['raise', 17], ['lower', 0], ['sine', 16]] + tests = [['raise', 18], ['lower', 0], ['sine', 16]] for [contour, numres] in tests: simple_backtest(default_conf, contour, numres, mocker) From 17c0ceec04b8cb9f826a80bee1a321bef77874b1 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sun, 10 Jun 2018 13:22:24 +0200 Subject: [PATCH 08/46] adjust tests for backtestresult type --- freqtrade/tests/optimize/test_backtesting.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index ad328edc2..d5ac6e01e 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -353,10 +353,10 @@ def test_generate_text_table(default_conf, mocker): results = pd.DataFrame( { - 'currency': ['ETH/BTC', 'ETH/BTC'], + 'pair': ['ETH/BTC', 'ETH/BTC'], 'profit_percent': [0.1, 0.2], - 'profit_BTC': [0.2, 0.4], - 'duration': [10, 30], + 'profit_abs': [0.2, 0.4], + 'trade_duration': [10, 30], 'profit': [2, 0], 'loss': [0, 0] } From 322a528c12aacc4712c5d0685d1d582dd3e6747a Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sun, 10 Jun 2018 13:25:16 +0200 Subject: [PATCH 09/46] fix bug with backtestResult --- freqtrade/optimize/backtesting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 574b7b283..cd020a6db 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -226,7 +226,7 @@ class Backtesting(object): records.append((pair, trade_entry.profit_percent, trade_entry.open_time.strftime('%s'), trade_entry.close_time.strftime('%s'), - index, trade_entry[3])) + index, trade_entry.trade_duration)) 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 From aff1ede46bf88817e402d090a289bb39b699e291 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sun, 10 Jun 2018 13:25:52 +0200 Subject: [PATCH 10/46] Fix last backtesting test --- freqtrade/tests/optimize/test_backtesting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index d5ac6e01e..7c617c38f 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -560,12 +560,12 @@ def test_backtest_record(default_conf, fee, mocker): 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) == 4 # Assert file_dump_json was only called once assert names == ['backtest-result.json'] records = records[0] # Ensure records are of correct type - assert len(records) == 3 + assert len(records) == 4 # ('UNITTEST/BTC', 0.00331158, '1510684320', '1510691700', 0, 117) # Below follows just a typecheck of the schema/type of trade-records oix = None From 31025216f94f58f217841f1215a7869a684bbd2c Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sun, 10 Jun 2018 13:32:07 +0200 Subject: [PATCH 11/46] fix type of open/close timestmap --- freqtrade/optimize/backtesting.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index cd020a6db..68480c997 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -6,6 +6,7 @@ This module contains the backtesting logic import logging import operator from argparse import Namespace +from datetime import datetime from typing import Dict, Tuple, Any, List, Optional, NamedTuple import arrow @@ -30,8 +31,8 @@ class BacktestResult(NamedTuple): pair: str profit_percent: float profit_abs: float - open_time: float - close_time: float + open_time: datetime + close_time: datetime trade_duration: float From b81588307f0e14d6e6ec82073e86f4e7f98cbaf7 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sun, 10 Jun 2018 13:37:53 +0200 Subject: [PATCH 12/46] Add "open_at_end" parameter --- freqtrade/optimize/backtesting.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 68480c997..f87340829 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -34,6 +34,7 @@ class BacktestResult(NamedTuple): open_time: datetime close_time: datetime trade_duration: float + open_at_end: bool class Backtesting(object): @@ -140,7 +141,8 @@ class Backtesting(object): profit_abs=trade.calc_profit(rate=sell_row.close), open_time=buy_row.date, close_time=sell_row.date, - trade_duration=(sell_row.date - buy_row.date).seconds // 60 + trade_duration=(sell_row.date - buy_row.date).seconds // 60, + open_at_end=False ) if partial_ticker: # no sell condition found - trade stil open at end of backtest period @@ -150,7 +152,8 @@ class Backtesting(object): profit_abs=trade.calc_profit(rate=sell_row.close), open_time=buy_row.date, close_time=sell_row.date, - trade_duration=(sell_row.date - buy_row.date).seconds // 60 + trade_duration=(sell_row.date - buy_row.date).seconds // 60, + open_at_end=True ) logger.info('Force_selling still open trade %s with %s perc - %s', btr.pair, btr.profit_percent, btr.profit_abs) From 1cd7ac55a8d742f7c7e2278685cdc412205eacd0 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sun, 10 Jun 2018 13:45:16 +0200 Subject: [PATCH 13/46] Added "left open trades" report --- freqtrade/optimize/backtesting.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index f87340829..cc20e9789 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -311,6 +311,17 @@ class Backtesting(object): ) ) + logger.info( + '\n==================================== ' + 'LEFT OPEN TRADES REPORT' + ' ====================================\n' + '%s', + self._generate_text_table( + data, + results.loc[results.open_at_end == True] + ) + ) + def setup_configuration(args: Namespace) -> Dict[str, Any]: """ From 27ee8f73604b52da8e7a5865d52949afd9b99a7b Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sun, 10 Jun 2018 13:55:48 +0200 Subject: [PATCH 14/46] make flake happy --- 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 cc20e9789..e526a6ec4 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -241,7 +241,6 @@ class Backtesting(object): if record and record.find('trades') >= 0: logger.info('Dumping backtest results to %s', recordfilename) file_dump_json(recordfilename, records) - labels = ['currency', 'profit_percent', 'profit_BTC', 'duration'] return DataFrame.from_records(trades, columns=BacktestResult._fields) def start(self) -> None: @@ -318,7 +317,7 @@ class Backtesting(object): '%s', self._generate_text_table( data, - results.loc[results.open_at_end == True] + results.loc[results.open_at_end] ) ) From 4710210cffe52fcac5406d1d1840732253c1073f Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sun, 10 Jun 2018 13:56:10 +0200 Subject: [PATCH 15/46] fix hyperopt to use new backtesting result tuple --- freqtrade/optimize/hyperopt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 878acc2dc..e952458a7 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -451,7 +451,7 @@ class Hyperopt(Backtesting): total_profit = results.profit_percent.sum() trade_count = len(results.index) - trade_duration = results.duration.mean() + trade_duration = results.trade_duration.mean() if trade_count == 0 or trade_duration > self.max_accepted_trade_duration: print('.', end='') @@ -488,10 +488,10 @@ class Hyperopt(Backtesting): 'Total profit {: 11.8f} {} ({:.4f}Σ%). Avg duration {:5.1f} mins.').format( len(results.index), results.profit_percent.mean() * 100.0, - results.profit_BTC.sum(), + results.profit_abs.sum(), self.config['stake_currency'], results.profit_percent.sum(), - results.duration.mean(), + results.trade_duration.mean(), ) def start(self) -> None: From 9cc087c788bd56fc230d8bafa6993886d7ceb514 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sun, 10 Jun 2018 13:56:23 +0200 Subject: [PATCH 16/46] update hyperopt tests to support new structure --- freqtrade/tests/optimize/test_hyperopt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index 3edfe4393..62fd17b82 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -400,7 +400,7 @@ def test_format_results(init_hyperopt): ('LTC/BTC', 1, 1, 123), ('XPR/BTC', -1, -2, -246) ] - labels = ['currency', 'profit_percent', 'profit_BTC', 'duration'] + labels = ['currency', 'profit_percent', 'profit_abs', 'trade_duration'] df = pd.DataFrame.from_records(trades, columns=labels) result = _HYPEROPT.format_results(df) @@ -530,7 +530,7 @@ def test_generate_optimizer(mocker, init_hyperopt, default_conf) -> None: trades = [ ('POWR/BTC', 0.023117, 0.000233, 100) ] - labels = ['currency', 'profit_percent', 'profit_BTC', 'duration'] + labels = ['currency', 'profit_percent', 'profit_abs', 'trade_duration'] backtest_result = pd.DataFrame.from_records(trades, columns=labels) mocker.patch( From 12e455cbf5616462d0ca198eb2122e13ba17add8 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sun, 10 Jun 2018 20:52:42 +0200 Subject: [PATCH 17/46] add buy/sell index to backtest result --- freqtrade/optimize/backtesting.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index e526a6ec4..ffd6e2768 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -33,6 +33,8 @@ class BacktestResult(NamedTuple): profit_abs: float open_time: datetime close_time: datetime + open_index: int + close_index: int trade_duration: float open_at_end: bool @@ -142,6 +144,8 @@ class Backtesting(object): open_time=buy_row.date, close_time=sell_row.date, trade_duration=(sell_row.date - buy_row.date).seconds // 60, + open_index=buy_row.index, + close_index=sell_row.index, open_at_end=False ) if partial_ticker: @@ -153,6 +157,8 @@ class Backtesting(object): open_time=buy_row.date, close_time=sell_row.date, trade_duration=(sell_row.date - buy_row.date).seconds // 60, + open_index=buy_row.index, + close_index=sell_row.index, open_at_end=True ) logger.info('Force_selling still open trade %s with %s perc - %s', btr.pair, From bfde33c945f4374fc8ca69075d41a2d9c731d6e9 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Tue, 12 Jun 2018 21:12:55 +0200 Subject: [PATCH 18/46] Use timestamp() instead of strftime this will avoid a bug shifting epoch time by 1 hour: https://stackoverflow.com/questions/11743019/convert-python-datetime-to-epoch-with-strftime --- 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 ffd6e2768..f7fade102 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -234,8 +234,8 @@ class Backtesting(object): # record a tuple of pair, current_profit_percent, # entry-date, duration records.append((pair, trade_entry.profit_percent, - trade_entry.open_time.strftime('%s'), - trade_entry.close_time.strftime('%s'), + trade_entry.open_time.timestamp(), + trade_entry.close_time.timestamp(), index, trade_entry.trade_duration)) else: # Set lock_pair_until to end of testing period if trade could not be closed From e3ced7c15e0d1dc0582485147d59ca5122efebfc Mon Sep 17 00:00:00 2001 From: xmatthias Date: Tue, 12 Jun 2018 22:29:30 +0200 Subject: [PATCH 19/46] extract export from backtest function --- freqtrade/optimize/backtesting.py | 47 +++++++++++++++---------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index f7fade102..1146d6b00 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -112,6 +112,21 @@ class Backtesting(object): ]) return tabulate(tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="pipe") + def _store_backtest_result(self, recordfilename: Optional[str], results: DataFrame) -> None: + + records = [] + print(results) + for index, trade_entry in results.iterrows(): + pass + records.append((trade_entry.pair, trade_entry.profit_percent, + trade_entry.open_time.timestamp(), + trade_entry.close_time.timestamp(), + trade_entry.open_index - 1, trade_entry.trade_duration)) + + if records: + logger.info('Dumping backtest results to %s', recordfilename) + file_dump_json(recordfilename, records) + def _get_sell_trade_entry( self, pair: str, buy_row: DataFrame, partial_ticker: List, trade_count_lock: Dict, args: Dict) -> Optional[BacktestResult]: @@ -144,8 +159,8 @@ class Backtesting(object): open_time=buy_row.date, close_time=sell_row.date, trade_duration=(sell_row.date - buy_row.date).seconds // 60, - open_index=buy_row.index, - close_index=sell_row.index, + open_index=buy_row.Index, + close_index=sell_row.Index, open_at_end=False ) if partial_ticker: @@ -157,8 +172,8 @@ class Backtesting(object): open_time=buy_row.date, close_time=sell_row.date, trade_duration=(sell_row.date - buy_row.date).seconds // 60, - open_index=buy_row.index, - close_index=sell_row.index, + open_index=buy_row.Index, + close_index=sell_row.Index, open_at_end=True ) logger.info('Force_selling still open trade %s with %s perc - %s', btr.pair, @@ -179,17 +194,12 @@ class Backtesting(object): processed: a processed dictionary with format {pair, data} max_open_trades: maximum number of concurrent trades (default: 0, disabled) realistic: do we try to simulate realistic trades? (default: True) - sell_profit_only: sell if profit only - use_sell_signal: act on sell-signal :return: DataFrame """ headers = ['date', 'buy', 'open', 'close', 'sell'] processed = args['processed'] max_open_trades = args.get('max_open_trades', 0) realistic = args.get('realistic', False) - record = args.get('record', None) - recordfilename = args.get('recordfn', 'backtest-result.json') - records = [] trades = [] trade_count_lock: Dict = {} for pair, pair_data in processed.items(): @@ -229,24 +239,11 @@ class Backtesting(object): if trade_entry: lock_pair_until = trade_entry.close_time trades.append(trade_entry) - if record: - # Note, need to be json.dump friendly - # record a tuple of pair, current_profit_percent, - # entry-date, duration - records.append((pair, trade_entry.profit_percent, - trade_entry.open_time.timestamp(), - trade_entry.close_time.timestamp(), - index, trade_entry.trade_duration)) 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 - # For now export inside backtest(), maybe change so that backtest() - # returns a tuple like: (dataframe, records, logs, etc) - if record and record.find('trades') >= 0: - logger.info('Dumping backtest results to %s', recordfilename) - file_dump_json(recordfilename, records) return DataFrame.from_records(trades, columns=BacktestResult._fields) def start(self) -> None: @@ -301,10 +298,12 @@ class Backtesting(object): 'processed': preprocessed, 'max_open_trades': max_open_trades, 'realistic': self.config.get('realistic_simulation', False), - 'record': self.config.get('export'), - 'recordfn': self.config.get('exportfilename'), } ) + + if self.config.get('export', False): + self._store_backtest_result(self.config.get('exportfilename'), results) + logger.info( '\n==================================== ' 'BACKTESTING REPORT' From 8d8e6dcffc60cabd6c135d95337401d9abb5b69b Mon Sep 17 00:00:00 2001 From: xmatthias Date: Wed, 13 Jun 2018 06:31:42 +0200 Subject: [PATCH 20/46] Add test for extracted backtest_results test --- freqtrade/tests/optimize/test_backtesting.py | 26 +++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 7c617c38f..e0342851f 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -550,16 +550,24 @@ def test_backtest_record(default_conf, fee, mocker): 'freqtrade.optimize.backtesting.file_dump_json', new=lambda n, r: (names.append(n), records.append(r)) ) - backtest_conf = _make_backtest_conf( - mocker, - conf=default_conf, - pair='UNITTEST/BTC', - record="trades" - ) + backtesting = Backtesting(default_conf) - backtesting.populate_buy_trend = _trend_alternate # Override - backtesting.populate_sell_trend = _trend_alternate # Override - results = backtesting.backtest(backtest_conf) + results = pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC", + "UNITTEST/BTC", "UNITTEST/BTC"], + "profit_percent": [0.003312, 0.010801, 0.013803, 0.002780], + "profit_abs": [0.000003, 0.000011, 0.000014, 0.000003], + "open_time": [Arrow(2017, 11, 14, 19, 32, 00).datetime, + Arrow(2017, 11, 14, 21, 36, 00).datetime, + Arrow(2017, 11, 14, 22, 12, 00).datetime, + Arrow(2017, 11, 14, 22, 44, 00).datetime], + "close_time": [Arrow(2017, 11, 14, 21, 35, 00).datetime, + Arrow(2017, 11, 14, 22, 10, 00).datetime, + Arrow(2017, 11, 14, 22, 43, 00).datetime, + Arrow(2017, 11, 14, 22, 58, 00).datetime], + "open_index": [1, 119, 153, 185], + "close_index": [118, 151, 184, 199], + "trade_duration": [123, 34, 31, 14]}) + backtesting._store_backtest_result("backtest-result.json", results) assert len(results) == 4 # Assert file_dump_json was only called once assert names == ['backtest-result.json'] From 0f117d480e4f8822c76bbda2e4cd24300b495f50 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Wed, 13 Jun 2018 06:42:24 +0200 Subject: [PATCH 21/46] improve backtesting-tests * assert length of result specifically * add assert for "open_at_end" --- freqtrade/tests/optimize/test_backtesting.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index e0342851f..15f1d978e 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -435,6 +435,7 @@ def test_backtest(default_conf, fee, mocker) -> None: } ) assert not results.empty + assert len(results) == 2 def test_backtest_1min_ticker_interval(default_conf, fee, mocker) -> None: @@ -457,6 +458,7 @@ def test_backtest_1min_ticker_interval(default_conf, fee, mocker) -> None: } ) assert not results.empty + assert len(results) == 1 def test_processed(default_conf, mocker) -> None: @@ -538,7 +540,10 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker): backtesting.populate_buy_trend = _trend_alternate # Override backtesting.populate_sell_trend = _trend_alternate # Override results = backtesting.backtest(backtest_conf) + backtesting._store_backtest_result("test_.json", results) assert len(results) == 4 + # One trade was force-closed at the end + assert len(results.loc[results.open_at_end]) == 1 def test_backtest_record(default_conf, fee, mocker): From 6e68c3b230490e47cb84b27ba1ebb9fd176d32b9 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Wed, 13 Jun 2018 06:52:17 +0200 Subject: [PATCH 22/46] fix backtesting.md formatting --- docs/backtesting.md | 52 +++++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 8364d77e4..e46999d4b 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -1,17 +1,19 @@ # Backtesting + This page explains how to validate your strategy performance by using Backtesting. ## Table of Contents + - [Test your strategy with Backtesting](#test-your-strategy-with-backtesting) - [Understand the backtesting result](#understand-the-backtesting-result) ## Test your strategy with Backtesting + Now you have good Buy and Sell strategies, you want to test it against real data. This is what we call [backtesting](https://en.wikipedia.org/wiki/Backtesting). - Backtesting will use the crypto-currencies (pair) from your config file and load static tickers located in [/freqtrade/tests/testdata](https://github.com/freqtrade/freqtrade/tree/develop/freqtrade/tests/testdata). @@ -19,70 +21,80 @@ If the 5 min and 1 min ticker for the crypto-currencies to test is not already in the `testdata` folder, backtesting will download them automatically. Testdata files will not be updated until you specify it. -The result of backtesting will confirm you if your bot as more chance to -make a profit than a loss. - +The result of backtesting will confirm you if your bot as more chance to make a profit than a loss. The backtesting is very easy with freqtrade. ### Run a backtesting against the currencies listed in your config file -**With 5 min tickers (Per default)** +#### With 5 min tickers (Per default) + ```bash python3 ./freqtrade/main.py backtesting --realistic-simulation ``` -**With 1 min tickers** +#### With 1 min tickers + ```bash python3 ./freqtrade/main.py backtesting --realistic-simulation --ticker-interval 1m ``` -**Update cached pairs with the latest data** +#### Update cached pairs with the latest data + ```bash python3 ./freqtrade/main.py backtesting --realistic-simulation --refresh-pairs-cached ``` -**With live data (do not alter your testdata files)** +#### With live data (do not alter your testdata files) + ```bash python3 ./freqtrade/main.py backtesting --realistic-simulation --live ``` -**Using a different on-disk ticker-data source** +#### Using a different on-disk ticker-data source + ```bash python3 ./freqtrade/main.py backtesting --datadir freqtrade/tests/testdata-20180101 ``` -**With a (custom) strategy file** +#### With a (custom) strategy file + ```bash python3 ./freqtrade/main.py -s TestStrategy backtesting ``` + Where `-s TestStrategy` refers to the class name within the strategy file `test_strategy.py` found in the `freqtrade/user_data/strategies` directory -**Exporting trades to file** +#### Exporting trades to file + ```bash python3 ./freqtrade/main.py backtesting --export trades ``` -**Exporting trades to file specifying a custom filename** +#### Exporting trades to file specifying a custom filename + ```bash python3 ./freqtrade/main.py backtesting --export trades --export-filename=backtest_teststrategy.json ``` +#### Running backtest with smaller testset -**Running backtest with smaller testset** Use the `--timerange` argument to change how much of the testset you want to use. The last N ticks/timeframes will be used. Example: + ```bash python3 ./freqtrade/main.py backtesting --timerange=-200 ``` -***Advanced use of timerange*** +#### Advanced use of timerange + Doing `--timerange=-200` will get the last 200 timeframes from your inputdata. You can also specify specific dates, or a range span indexed by start and stop. The full timerange specification: + - Use last 123 tickframes of data: `--timerange=-123` - Use first 123 tickframes of data: `--timerange=123-` - Use tickframes from line 123 through 456: `--timerange=123-456` @@ -92,11 +104,12 @@ The full timerange specification: - Use tickframes between POSIX timestamps 1527595200 1527618600: `--timerange=1527595200-1527618600` +#### Downloading new set of ticker data -**Downloading new set of ticker data** To download new set of backtesting ticker data, you can use a download script. If you are using Binance for example: + - create a folder `user_data/data/binance` and copy `pairs.json` in that folder. - update the `pairs.json` to contain the currency pairs you are interested in. @@ -119,14 +132,14 @@ This will download ticker data for all the currency pairs you defined in `pairs. - To download ticker data for only 10 days, use `--days 10`. - Use `--timeframes` to specify which tickers to download. Default is `--timeframes 1m 5m` which will download 1-minute and 5-minute tickers. - -For help about backtesting usage, please refer to -[Backtesting commands](#backtesting-commands). +For help about backtesting usage, please refer to [Backtesting commands](#backtesting-commands). ## Understand the backtesting result + The most important in the backtesting is to understand the result. A backtesting result will look like that: + ``` ====================== BACKTESTING REPORT ================================ pair buy count avg profit % total profit BTC avg duration @@ -146,6 +159,7 @@ TOTAL 419 -0.41 -0.00348593 52.9 The last line will give you the overall performance of your strategy, here: + ``` TOTAL 419 -0.41 -0.00348593 52.9 ``` @@ -161,6 +175,7 @@ strategy, your sell strategy, and also by the `minimal_roi` and As for an example if your minimal_roi is only `"0": 0.01`. You cannot expect the bot to make more profit than 1% (because it will sell every time a trade will reach 1%). + ```json "minimal_roi": { "0": 0.01 @@ -173,6 +188,7 @@ profit. Hence, keep in mind that your performance is a mix of your strategies, your configuration, and the crypto-currency you have set up. ## Next step + Great, your strategy is profitable. What if the bot can give your the optimal parameters to use for your strategy? Your next step is to learn [how to find optimal parameters with Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md) From 6357812743defe2cd3208d00dc37b4415409a841 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Wed, 13 Jun 2018 06:57:49 +0200 Subject: [PATCH 23/46] fix backtest report able --- freqtrade/optimize/backtesting.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 1146d6b00..5df4fb28a 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -305,9 +305,9 @@ class Backtesting(object): self._store_backtest_result(self.config.get('exportfilename'), results) logger.info( - '\n==================================== ' + '\n======================================== ' 'BACKTESTING REPORT' - ' ====================================\n' + ' =========================================\n' '%s', self._generate_text_table( data, @@ -316,9 +316,9 @@ class Backtesting(object): ) logger.info( - '\n==================================== ' + '\n====================================== ' 'LEFT OPEN TRADES REPORT' - ' ====================================\n' + ' ======================================\n' '%s', self._generate_text_table( data, From e22da45474aee9487d2e2a38556bcac987bd88b2 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Wed, 13 Jun 2018 07:00:05 +0200 Subject: [PATCH 24/46] update documentation with forcesell at the end of the backtest period --- docs/backtesting.md | 49 ++++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index e46999d4b..127b4ee20 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -141,22 +141,43 @@ The most important in the backtesting is to understand the result. A backtesting result will look like that: ``` -====================== BACKTESTING REPORT ================================ -pair buy count avg profit % total profit BTC avg duration --------- ----------- -------------- ------------------ -------------- -ETH/BTC 56 -0.67 -0.00075455 62.3 -LTC/BTC 38 -0.48 -0.00036315 57.9 -ETC/BTC 42 -1.15 -0.00096469 67.0 -DASH/BTC 72 -0.62 -0.00089368 39.9 -ZEC/BTC 45 -0.46 -0.00041387 63.2 -XLM/BTC 24 -0.88 -0.00041846 47.7 -NXT/BTC 24 0.68 0.00031833 40.2 -POWR/BTC 35 0.98 0.00064887 45.3 -ADA/BTC 43 -0.39 -0.00032292 55.0 -XMR/BTC 40 -0.40 -0.00032181 47.4 -TOTAL 419 -0.41 -0.00348593 52.9 +======================================== BACKTESTING REPORT ========================================= +| pair | buy count | avg profit % | total profit BTC | avg duration | profit | loss | +|:---------|------------:|---------------:|-------------------:|---------------:|---------:|-------:| +| ETH/BTC | 44 | 0.18 | 0.00159118 | 50.9 | 44 | 0 | +| LTC/BTC | 27 | 0.10 | 0.00051931 | 103.1 | 26 | 1 | +| ETC/BTC | 24 | 0.05 | 0.00022434 | 166.0 | 22 | 2 | +| DASH/BTC | 29 | 0.18 | 0.00103223 | 192.2 | 29 | 0 | +| ZEC/BTC | 65 | -0.02 | -0.00020621 | 202.7 | 62 | 3 | +| XLM/BTC | 35 | 0.02 | 0.00012877 | 242.4 | 32 | 3 | +| BCH/BTC | 12 | 0.62 | 0.00149284 | 50.0 | 12 | 0 | +| POWR/BTC | 21 | 0.26 | 0.00108215 | 134.8 | 21 | 0 | +| ADA/BTC | 54 | -0.19 | -0.00205202 | 191.3 | 47 | 7 | +| XMR/BTC | 24 | -0.43 | -0.00206013 | 120.6 | 20 | 4 | +| TOTAL | 335 | 0.03 | 0.00175246 | 157.9 | 315 | 20 | +2018-06-13 06:57:27,347 - freqtrade.optimize.backtesting - INFO - +====================================== LEFT OPEN TRADES REPORT ====================================== +| pair | buy count | avg profit % | total profit BTC | avg duration | profit | loss | +|:---------|------------:|---------------:|-------------------:|---------------:|---------:|-------:| +| ETH/BTC | 3 | 0.16 | 0.00009619 | 25.0 | 3 | 0 | +| LTC/BTC | 1 | -1.00 | -0.00020118 | 1085.0 | 0 | 1 | +| ETC/BTC | 2 | -1.80 | -0.00071933 | 1092.5 | 0 | 2 | +| DASH/BTC | 0 | nan | 0.00000000 | nan | 0 | 0 | +| ZEC/BTC | 3 | -4.27 | -0.00256826 | 1301.7 | 0 | 3 | +| XLM/BTC | 3 | -1.11 | -0.00066744 | 965.0 | 0 | 3 | +| BCH/BTC | 0 | nan | 0.00000000 | nan | 0 | 0 | +| POWR/BTC | 0 | nan | 0.00000000 | nan | 0 | 0 | +| ADA/BTC | 7 | -3.58 | -0.00503604 | 850.0 | 0 | 7 | +| XMR/BTC | 4 | -3.79 | -0.00303456 | 291.2 | 0 | 4 | +| TOTAL | 23 | -2.63 | -0.01213062 | 750.4 | 3 | 20 | + ``` +The 1st table will contain all trades the bot made. + +The 2nd table will contain all trades the bot had to `forcesell` at the end of the backtest period to prsent a full picture. +These trades are also included in the first table, but are extracted separately for clarity. + The last line will give you the overall performance of your strategy, here: From e600be4f568b8f3712e01a9381553ac618507611 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Wed, 13 Jun 2018 19:43:33 +0200 Subject: [PATCH 25/46] Reduce force-sell verbosity --- 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 5df4fb28a..dbbdd4b80 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -176,8 +176,8 @@ class Backtesting(object): close_index=sell_row.Index, open_at_end=True ) - logger.info('Force_selling still open trade %s with %s perc - %s', btr.pair, - btr.profit_percent, btr.profit_abs) + logger.debug('Force_selling still open trade %s with %s perc - %s', btr.pair, + btr.profit_percent, btr.profit_abs) return btr return None From c0289ad8449b22800cc2272ac6180d486eca621f Mon Sep 17 00:00:00 2001 From: xmatthias Date: Wed, 13 Jun 2018 19:53:12 +0200 Subject: [PATCH 26/46] use list comprehension to build list --- freqtrade/optimize/backtesting.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index dbbdd4b80..df68c1f31 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -114,14 +114,11 @@ class Backtesting(object): def _store_backtest_result(self, recordfilename: Optional[str], results: DataFrame) -> None: - records = [] - print(results) - for index, trade_entry in results.iterrows(): - pass - records.append((trade_entry.pair, trade_entry.profit_percent, - trade_entry.open_time.timestamp(), - trade_entry.close_time.timestamp(), - trade_entry.open_index - 1, trade_entry.trade_duration)) + records = [(trade_entry.pair, trade_entry.profit_percent, + trade_entry.open_time.timestamp(), + trade_entry.close_time.timestamp(), + trade_entry.open_index - 1, trade_entry.trade_duration) + for index, trade_entry in results.iterrows()] if records: logger.info('Dumping backtest results to %s', recordfilename) From ea805a8fb7e5f12e02d781955d86bd639f48424e Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 14 Jun 2018 14:22:06 +0200 Subject: [PATCH 27/46] Update ccxt from 1.14.186 to 1.14.196 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 491297708..b1821f785 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.14.186 +ccxt==1.14.196 SQLAlchemy==1.2.8 python-telegram-bot==10.1.0 arrow==0.12.1 From 5c3e37412e2d9976252f41a9ebb84f65148ae42e Mon Sep 17 00:00:00 2001 From: xmatthias Date: Thu, 14 Jun 2018 21:20:16 +0200 Subject: [PATCH 28/46] update docs --- docs/backtesting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 127b4ee20..1efb46b43 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -21,7 +21,7 @@ If the 5 min and 1 min ticker for the crypto-currencies to test is not already in the `testdata` folder, backtesting will download them automatically. Testdata files will not be updated until you specify it. -The result of backtesting will confirm you if your bot as more chance to make a profit than a loss. +The result of backtesting will confirm you if your bot has better odds of making a profit than a loss. The backtesting is very easy with freqtrade. From 1e208e39b08f2dd1eb0016cda01305a4b42aaae5 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 15 Jun 2018 14:23:07 +0200 Subject: [PATCH 29/46] Update ccxt from 1.14.196 to 1.14.198 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b1821f785..bd5b3b10a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.14.196 +ccxt==1.14.198 SQLAlchemy==1.2.8 python-telegram-bot==10.1.0 arrow==0.12.1 From e8fd11d6cecf825188a1bc2f2b6210dd8c1ffdba Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 15 Jun 2018 14:23:08 +0200 Subject: [PATCH 30/46] Update requests from 2.19.0 to 2.19.1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bd5b3b10a..e8240fd97 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ SQLAlchemy==1.2.8 python-telegram-bot==10.1.0 arrow==0.12.1 cachetools==2.1.0 -requests==2.19.0 +requests==2.19.1 urllib3==1.22 wrapt==1.10.11 pandas==0.23.1 From a8d25266f962ac9989d570017b42c297b7325774 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 15 Jun 2018 14:23:07 +0200 Subject: [PATCH 31/46] Update ccxt from 1.14.196 to 1.14.198 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b1821f785..bd5b3b10a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.14.196 +ccxt==1.14.198 SQLAlchemy==1.2.8 python-telegram-bot==10.1.0 arrow==0.12.1 From af16830a38e10a9ec0395d2c1bb38239646ebf13 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 15 Jun 2018 14:23:08 +0200 Subject: [PATCH 32/46] Update requests from 2.19.0 to 2.19.1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bd5b3b10a..e8240fd97 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ SQLAlchemy==1.2.8 python-telegram-bot==10.1.0 arrow==0.12.1 cachetools==2.1.0 -requests==2.19.0 +requests==2.19.1 urllib3==1.22 wrapt==1.10.11 pandas==0.23.1 From c1f8f641e6a696b513a8eb81d9e246d86b861df0 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Fri, 15 Jun 2018 10:45:19 +0300 Subject: [PATCH 33/46] remove use of hyperopt_conf.py --- docs/bot-usage.md | 5 ++- freqtrade/optimize/__init__.py | 10 ++---- freqtrade/optimize/hyperopt.py | 6 +--- freqtrade/tests/optimize/test_hyperopt.py | 7 ---- freqtrade/tests/optimize/test_optimize.py | 2 -- user_data/hyperopt_conf.py | 42 ----------------------- 6 files changed, 6 insertions(+), 66 deletions(-) delete mode 100644 user_data/hyperopt_conf.py diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 8079d9816..0ca99ec0a 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -160,9 +160,8 @@ the parameter `-l` or `--live`. ## Hyperopt commands -It is possible to use hyperopt for trading strategy optimization. -Hyperopt uses an internal json config return by `hyperopt_optimize_conf()` -located in `freqtrade/optimize/hyperopt_conf.py`. +To optimize your strategy, you can use hyperopt parameter hyperoptimization +to find optimal parameter values for your stategy. ``` usage: main.py hyperopt [-h] [-i TICKER_INTERVAL] [--realistic-simulation] diff --git a/freqtrade/optimize/__init__.py b/freqtrade/optimize/__init__.py index fc5d53114..867e8c7dc 100644 --- a/freqtrade/optimize/__init__.py +++ b/freqtrade/optimize/__init__.py @@ -11,8 +11,6 @@ from freqtrade import misc, constants from freqtrade.exchange import get_ticker_history from freqtrade.arguments import TimeRange -from user_data.hyperopt_conf import hyperopt_optimize_conf - logger = logging.getLogger(__name__) @@ -83,7 +81,7 @@ def load_tickerdata_file( def load_data(datadir: str, ticker_interval: str, - pairs: Optional[List[str]] = None, + pairs: List[str], refresh_pairs: Optional[bool] = False, timerange: TimeRange = TimeRange(None, None, 0, 0)) -> Dict[str, List]: """ @@ -92,14 +90,12 @@ def load_data(datadir: str, """ result = {} - _pairs = pairs or hyperopt_optimize_conf()['exchange']['pair_whitelist'] - # If the user force the refresh of pairs if refresh_pairs: logger.info('Download data for all pairs and store them in %s', datadir) - download_pairs(datadir, _pairs, ticker_interval, timerange=timerange) + download_pairs(datadir, pairs, ticker_interval, timerange=timerange) - for pair in _pairs: + for pair in pairs: pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange) if pairdata: result[pair] = pairdata diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 878acc2dc..5acd05766 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -27,7 +27,6 @@ from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration from freqtrade.optimize import load_data from freqtrade.optimize.backtesting import Backtesting -from user_data.hyperopt_conf import hyperopt_optimize_conf logger = logging.getLogger(__name__) @@ -596,11 +595,8 @@ def start(args: Namespace) -> None: # Monkey patch the configuration with hyperopt_conf.py configuration = Configuration(args) logger.info('Starting freqtrade in Hyperopt mode') + config = configuration.load_config() - optimize_config = hyperopt_optimize_conf() - config = configuration._load_common_config(optimize_config) - config = configuration._load_backtesting_config(config) - config = configuration._load_hyperopt_config(config) config['exchange']['key'] = '' config['exchange']['secret'] = '' diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index 3edfe4393..4ef5762e1 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -23,8 +23,6 @@ def init_hyperopt(default_conf, mocker): global _HYPEROPT_INITIALIZED, _HYPEROPT if not _HYPEROPT_INITIALIZED: mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) - mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', - MagicMock(return_value=default_conf)) mocker.patch('freqtrade.exchange.validate_pairs', MagicMock()) _HYPEROPT = Hyperopt(default_conf) _HYPEROPT_INITIALIZED = True @@ -64,8 +62,6 @@ def test_start(mocker, default_conf, caplog) -> None: """ start_mock = MagicMock() mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.start', start_mock) - mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', - MagicMock(return_value=default_conf)) mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) args = [ @@ -182,7 +178,6 @@ def test_fmin_best_results(mocker, init_hyperopt, default_conf, caplog) -> None: mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock()) mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result) - mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf) mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) StrategyResolver({'strategy': 'DefaultStrategy'}) @@ -227,7 +222,6 @@ def test_fmin_throw_value_error(mocker, init_hyperopt, default_conf, caplog) -> conf.update({'epochs': 1}) conf.update({'timerange': None}) conf.update({'spaces': 'all'}) - mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf) mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) StrategyResolver({'strategy': 'DefaultStrategy'}) @@ -270,7 +264,6 @@ def test_resuming_previous_hyperopt_results_succeeds(mocker, init_hyperopt, defa mocker.patch('freqtrade.optimize.hyperopt.sorted', return_value=trials.results) mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock()) mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={}) - mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf) mocker.patch('freqtrade.exchange.validate_pairs', MagicMock()) StrategyResolver({'strategy': 'DefaultStrategy'}) diff --git a/freqtrade/tests/optimize/test_optimize.py b/freqtrade/tests/optimize/test_optimize.py index 3f358cfb8..bac8a6b36 100644 --- a/freqtrade/tests/optimize/test_optimize.py +++ b/freqtrade/tests/optimize/test_optimize.py @@ -326,8 +326,6 @@ def test_load_tickerdata_file() -> None: def test_init(default_conf, mocker) -> None: - conf = {'exchange': {'pair_whitelist': []}} - mocker.patch('freqtrade.optimize.hyperopt_optimize_conf', return_value=conf) assert {} == optimize.load_data( '', pairs=[], diff --git a/user_data/hyperopt_conf.py b/user_data/hyperopt_conf.py deleted file mode 100644 index c3a6e2a29..000000000 --- a/user_data/hyperopt_conf.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -File that contains the configuration for Hyperopt -""" - - -def hyperopt_optimize_conf() -> dict: - """ - This function is used to define which parameters Hyperopt must used. - The "pair_whitelist" is only used is your are using Hyperopt with MongoDB, - without MongoDB, Hyperopt will use the pair your have set in your config file. - :return: - """ - return { - 'max_open_trades': 3, - 'stake_currency': 'BTC', - 'stake_amount': 0.01, - "minimal_roi": { - '40': 0.0, - '30': 0.01, - '20': 0.02, - '0': 0.04, - }, - 'stoploss': -0.10, - "bid_strategy": { - "ask_last_balance": 0.0 - }, - "exchange": { - "name": "bittrex", - "pair_whitelist": [ - "ETH/BTC", - "LTC/BTC", - "ETC/BTC", - "DASH/BTC", - "ZEC/BTC", - "XLM/BTC", - "NXT/BTC", - "POWR/BTC", - "ADA/BTC", - "XMR/BTC" - ] - } - } From 0c85febe76bb61cad1f7e376d7188fd634ac4f29 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Fri, 15 Jun 2018 10:59:09 +0300 Subject: [PATCH 34/46] remove all mongodb related code --- docs/bot-usage.md | 7 ++-- docs/hyperopt.md | 36 ------------------- docs/installation.md | 28 ++------------- freqtrade/arguments.py | 6 ---- freqtrade/configuration.py | 5 --- freqtrade/optimize/hyperopt.py | 36 ++++++------------- freqtrade/tests/optimize/test_hyperopt.py | 31 ---------------- .../tests/optimize/test_hyperopt_config.py | 16 --------- freqtrade/tests/test_configuration.py | 5 --- scripts/start-hyperopt-worker.py | 27 -------------- scripts/start-mongodb.py | 21 ----------- 11 files changed, 16 insertions(+), 202 deletions(-) delete mode 100644 freqtrade/tests/optimize/test_hyperopt_config.py delete mode 100755 scripts/start-hyperopt-worker.py delete mode 100755 scripts/start-mongodb.py diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 0ca99ec0a..25fc78f0a 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -165,7 +165,7 @@ to find optimal parameter values for your stategy. ``` usage: main.py hyperopt [-h] [-i TICKER_INTERVAL] [--realistic-simulation] - [--timerange TIMERANGE] [-e INT] [--use-mongodb] + [--timerange TIMERANGE] [-e INT] [-s {all,buy,roi,stoploss} [{all,buy,roi,stoploss} ...]] optional arguments: @@ -175,11 +175,8 @@ optional arguments: --realistic-simulation uses max_open_trades from config to simulate real world limitations - --timerange TIMERANGE - specify what timerange of data to use. + --timerange TIMERANGE specify what timerange of data to use. -e INT, --epochs INT specify number of epochs (default: 100) - --use-mongodb parallelize evaluations with mongodb (requires mongod - in PATH) -s {all,buy,roi,stoploss} [{all,buy,roi,stoploss} ...], --spaces {all,buy,roi,stoploss} [{all,buy,roi,stoploss} ...] Specify which parameters to hyperopt. Space separate list. Default: all diff --git a/docs/hyperopt.md b/docs/hyperopt.md index a079e34df..2ad94896a 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -9,7 +9,6 @@ parameters with Hyperopt. - [Advanced Hyperopt notions](#advanced-notions) - [Understand the Guards and Triggers](#understand-the-guards-and-triggers) - [Execute Hyperopt](#execute-hyperopt) - - [Hyperopt with MongoDB](#hyperopt-with-mongoDB) - [Understand the hyperopts result](#understand-the-backtesting-result) ## Prepare Hyperopt @@ -194,41 +193,6 @@ Legal values are: - `stoploss`: search for the best stoploss value - space-separated list of any of the above values for example `--spaces roi stoploss` -### Hyperopt with MongoDB -Hyperopt with MongoDB, is like Hyperopt under steroids. As you saw by -executing the previous command is the execution takes a long time. -To accelerate it you can use hyperopt with MongoDB. - -To run hyperopt with MongoDb you will need 3 terminals. - -**Terminal 1: Start MongoDB** -```bash -cd -source .env/bin/activate -python3 scripts/start-mongodb.py -``` - -**Terminal 2: Start Hyperopt worker** -```bash -cd -source .env/bin/activate -python3 scripts/start-hyperopt-worker.py -``` - -**Terminal 3: Start Hyperopt with MongoDB** -```bash -cd -source .env/bin/activate -python3 ./freqtrade/main.py -c config.json hyperopt --use-mongodb -``` - -**Re-run an Hyperopt** -To re-run Hyperopt you have to delete the existing MongoDB table. -```bash -cd -rm -rf .hyperopt/mongodb/ -``` - ## Understand the hyperopts result Once Hyperopt is completed you can use the result to adding new buy signal. Given following result from hyperopt: diff --git a/docs/installation.md b/docs/installation.md index 9818529f6..b6688885b 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -225,17 +225,7 @@ cd .. rm -rf ./ta-lib* ``` -#### 3. [Optional] Install MongoDB - -Install MongoDB if you plan to optimize your strategy with Hyperopt. - -```bash -sudo apt-get install mongodb-org -``` - -> Complete tutorial from Digital Ocean: [How to Install MongoDB on Ubuntu 16.04](https://www.digitalocean.com/community/tutorials/how-to-install-mongodb-on-ubuntu-16-04). - -#### 4. Install FreqTrade +#### 3. Install FreqTrade Clone the git repository: @@ -243,7 +233,7 @@ Clone the git repository: git clone https://github.com/freqtrade/freqtrade.git ``` -#### 5. Configure `freqtrade` as a `systemd` service +#### 4. Configure `freqtrade` as a `systemd` service From the freqtrade repo... copy `freqtrade.service` to your systemd user directory (usually `~/.config/systemd/user`) and update `WorkingDirectory` and `ExecStart` to match your setup. @@ -267,19 +257,7 @@ sudo loginctl enable-linger "$USER" brew install python3 git wget ta-lib ``` -#### 2. [Optional] Install MongoDB - -Install MongoDB if you plan to optimize your strategy with Hyperopt. - -```bash -curl -O https://fastdl.mongodb.org/osx/mongodb-osx-ssl-x86_64-3.4.10.tgz -tar -zxvf mongodb-osx-ssl-x86_64-3.4.10.tgz -mkdir -p /env/mongodb -cp -R -n mongodb-osx-x86_64-3.4.10/ /env/mongodb -export PATH=/env/mongodb/bin:$PATH -``` - -#### 3. Install FreqTrade +#### 2. Install FreqTrade Clone the git repository: diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index 331bb73a0..b392fb53e 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -203,12 +203,6 @@ class Arguments(object): type=int, metavar='INT', ) - parser.add_argument( - '--use-mongodb', - help='parallelize evaluations with mongodb (requires mongod in PATH)', - dest='mongodb', - action='store_true', - ) parser.add_argument( '-s', '--spaces', help='Specify which parameters to hyperopt. Space separate list. \ diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index 1f14df560..7c3a5eb4b 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -188,11 +188,6 @@ class Configuration(object): logger.info('Parameter --epochs detected ...') logger.info('Will run Hyperopt with for %s epochs ...', config.get('epochs')) - # If --mongodb is used we add it to the configuration - if 'mongodb' in self.args and self.args.mongodb: - config.update({'mongodb': self.args.mongodb}) - logger.info('Parameter --use-mongodb detected ...') - # If --spaces is used we add it to the configuration if 'spaces' in self.args and self.args.spaces: config.update({'spaces': self.args.spaces}) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 5acd05766..978d1fe2c 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -19,7 +19,6 @@ from typing import Dict, Any, Callable, Optional import numpy import talib.abstract as ta from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, hp, space_eval, tpe -from hyperopt.mongoexp import MongoTrials from pandas import DataFrame import freqtrade.vendor.qtpylib.indicators as qtpylib @@ -507,32 +506,20 @@ class Hyperopt(Backtesting): self.analyze.populate_indicators = Hyperopt.populate_indicators # type: ignore self.processed = self.tickerdata_to_dataframe(data) - if self.config.get('mongodb'): - logger.info('Using mongodb ...') + logger.info('Preparing Trials..') + signal.signal(signal.SIGINT, self.signal_handler) + # read trials file if we have one + if os.path.exists(self.trials_file) and os.path.getsize(self.trials_file) > 0: + self.trials = self.read_trials() + + self.current_tries = len(self.trials.results) + self.total_tries += self.current_tries logger.info( - 'Start scripts/start-mongodb.sh and start-hyperopt-worker.sh manually!' + 'Continuing with trials. Current: %d, Total: %d', + self.current_tries, + self.total_tries ) - db_name = 'freqtrade_hyperopt' - self.trials = MongoTrials( - arg='mongo://127.0.0.1:1234/{}/jobs'.format(db_name), - exp_key='exp1' - ) - else: - logger.info('Preparing Trials..') - signal.signal(signal.SIGINT, self.signal_handler) - # read trials file if we have one - if os.path.exists(self.trials_file) and os.path.getsize(self.trials_file) > 0: - self.trials = self.read_trials() - - self.current_tries = len(self.trials.results) - self.total_tries += self.current_tries - logger.info( - 'Continuing with trials. Current: %d, Total: %d', - self.current_tries, - self.total_tries - ) - try: best_parameters = fmin( fn=self.generate_optimizer, @@ -588,7 +575,6 @@ def start(args: Namespace) -> None: """ # Remove noisy log messages - logging.getLogger('hyperopt.mongoexp').setLevel(logging.WARNING) logging.getLogger('hyperopt.tpe').setLevel(logging.WARNING) # Initialize configuration diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index 4ef5762e1..ce7cd77c8 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -247,7 +247,6 @@ def test_resuming_previous_hyperopt_results_succeeds(mocker, init_hyperopt, defa conf = deepcopy(default_conf) conf.update({'config': 'config.json.example'}) conf.update({'epochs': 1}) - conf.update({'mongodb': False}) conf.update({'timerange': None}) conf.update({'spaces': 'all'}) @@ -341,7 +340,6 @@ def test_start_calls_fmin(mocker, init_hyperopt, default_conf) -> None: conf = deepcopy(default_conf) conf.update({'config': 'config.json.example'}) conf.update({'epochs': 1}) - conf.update({'mongodb': False}) conf.update({'timerange': None}) conf.update({'spaces': 'all'}) @@ -353,35 +351,6 @@ def test_start_calls_fmin(mocker, init_hyperopt, default_conf) -> None: mock_fmin.assert_called_once() -def test_start_uses_mongotrials(mocker, init_hyperopt, default_conf) -> None: - mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock()) - mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={}) - mock_mongotrials = mocker.patch( - 'freqtrade.optimize.hyperopt.MongoTrials', - return_value=create_trials(mocker) - ) - - conf = deepcopy(default_conf) - conf.update({'config': 'config.json.example'}) - conf.update({'epochs': 1}) - conf.update({'mongodb': True}) - conf.update({'timerange': None}) - conf.update({'spaces': 'all'}) - mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf) - mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) - - hyperopt = Hyperopt(conf) - hyperopt.tickerdata_to_dataframe = MagicMock() - - hyperopt.start() - mock_mongotrials.assert_called_once() - mock_fmin.assert_called_once() - - -# test log_trials_result -# test buy_strategy_generator def populate_buy_trend -# test optimizer if 'ro_t1' in params - def test_format_results(init_hyperopt): """ Test Hyperopt.format_results() diff --git a/freqtrade/tests/optimize/test_hyperopt_config.py b/freqtrade/tests/optimize/test_hyperopt_config.py deleted file mode 100644 index aa9424826..000000000 --- a/freqtrade/tests/optimize/test_hyperopt_config.py +++ /dev/null @@ -1,16 +0,0 @@ -# pragma pylint: disable=missing-docstring,W0212 - -from user_data.hyperopt_conf import hyperopt_optimize_conf - - -def test_hyperopt_optimize_conf(): - hyperopt_conf = hyperopt_optimize_conf() - - assert "max_open_trades" in hyperopt_conf - assert "stake_currency" in hyperopt_conf - assert "stake_amount" in hyperopt_conf - assert "minimal_roi" in hyperopt_conf - assert "stoploss" in hyperopt_conf - assert "bid_strategy" in hyperopt_conf - assert "exchange" in hyperopt_conf - assert "pair_whitelist" in hyperopt_conf['exchange'] diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index caaddbf25..212df2e96 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -310,7 +310,6 @@ def test_hyperopt_with_arguments(mocker, default_conf, caplog) -> None: arglist = [ 'hyperopt', '--epochs', '10', - '--use-mongodb', '--spaces', 'all', ] @@ -324,10 +323,6 @@ def test_hyperopt_with_arguments(mocker, default_conf, caplog) -> None: assert log_has('Parameter --epochs detected ...', caplog.record_tuples) assert log_has('Will run Hyperopt with for 10 epochs ...', caplog.record_tuples) - assert 'mongodb' in config - assert config['mongodb'] is True - assert log_has('Parameter --use-mongodb detected ...', caplog.record_tuples) - assert 'spaces' in config assert config['spaces'] == ['all'] assert log_has('Parameter -s/--spaces detected: [\'all\']', caplog.record_tuples) diff --git a/scripts/start-hyperopt-worker.py b/scripts/start-hyperopt-worker.py deleted file mode 100755 index 8b0ae6326..000000000 --- a/scripts/start-hyperopt-worker.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python3 -import multiprocessing -import os -import subprocess - -PROC_COUNT = multiprocessing.cpu_count() - 1 -DB_NAME = 'freqtrade_hyperopt' -WORK_DIR = os.path.join( - os.path.sep, - os.path.abspath(os.path.dirname(__file__)), - '..', '.hyperopt', 'worker' -) -if not os.path.exists(WORK_DIR): - os.makedirs(WORK_DIR) - -# Spawn workers -command = [ - 'hyperopt-mongo-worker', - '--mongo=127.0.0.1:1234/{}'.format(DB_NAME), - '--poll-interval=0.1', - '--workdir={}'.format(WORK_DIR), -] -processes = [subprocess.Popen(command) for i in range(PROC_COUNT)] - -# Join all workers -for proc in processes: - proc.wait() diff --git a/scripts/start-mongodb.py b/scripts/start-mongodb.py deleted file mode 100755 index 910ee9233..000000000 --- a/scripts/start-mongodb.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python3 - -import os -import subprocess - - -DB_PATH = os.path.join( - os.path.sep, - os.path.abspath(os.path.dirname(__file__)), - '..', '.hyperopt', 'mongodb' -) -if not os.path.exists(DB_PATH): - os.makedirs(DB_PATH) - -subprocess.Popen([ - 'mongod', - '--bind_ip=127.0.0.1', - '--port=1234', - '--nohttpinterface', - '--dbpath={}'.format(DB_PATH), -]).wait() From 7e2e7946c5b12abffd222420d74156208ef41e59 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Sat, 16 Jun 2018 09:06:14 +0300 Subject: [PATCH 35/46] also unit tests now need config.json --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 88121945f..3f041f5dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,16 +16,15 @@ install: - pip install --upgrade flake8 coveralls pytest-random-order mypy - pip install -r requirements.txt - pip install -e . +- cp config.json.example config.json jobs: include: - script: - pytest --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/ - coveralls - script: - - cp config.json.example config.json - python freqtrade/main.py --datadir freqtrade/tests/testdata backtesting - script: - - cp config.json.example config.json - python freqtrade/main.py --datadir freqtrade/tests/testdata hyperopt -e 5 - script: flake8 freqtrade - script: mypy freqtrade From fa00157d12f7faad9cd778d4d825019aa88e5ba4 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sat, 16 Jun 2018 13:42:25 +0200 Subject: [PATCH 36/46] Fix fiat_convert missing mockups --- freqtrade/tests/test_fiat_convert.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/freqtrade/tests/test_fiat_convert.py b/freqtrade/tests/test_fiat_convert.py index 24f0f776b..2fb9219ca 100644 --- a/freqtrade/tests/test_fiat_convert.py +++ b/freqtrade/tests/test_fiat_convert.py @@ -40,7 +40,8 @@ def test_pair_convertion_object(): assert pair_convertion.price == 30000.123 -def test_fiat_convert_is_supported(): +def test_fiat_convert_is_supported(mocker): + patch_coinmarketcap(mocker) fiat_convert = CryptoToFiatConverter() assert fiat_convert._is_supported_fiat(fiat='USD') is True assert fiat_convert._is_supported_fiat(fiat='usd') is True @@ -48,7 +49,9 @@ def test_fiat_convert_is_supported(): assert fiat_convert._is_supported_fiat(fiat='ABC') is False -def test_fiat_convert_add_pair(): +def test_fiat_convert_add_pair(mocker): + patch_coinmarketcap(mocker) + fiat_convert = CryptoToFiatConverter() pair_len = len(fiat_convert._pairs) @@ -70,11 +73,8 @@ def test_fiat_convert_add_pair(): def test_fiat_convert_find_price(mocker): - api_mock = MagicMock(return_value={ - 'price_usd': 12345.0, - 'price_eur': 13000.2 - }) - mocker.patch('freqtrade.fiat_convert.Market.ticker', api_mock) + patch_coinmarketcap(mocker) + fiat_convert = CryptoToFiatConverter() with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'): @@ -92,17 +92,15 @@ def test_fiat_convert_find_price(mocker): def test_fiat_convert_unsupported_crypto(mocker, caplog): mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[]) + patch_coinmarketcap(mocker) fiat_convert = CryptoToFiatConverter() assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0 assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog.record_tuples) def test_fiat_convert_get_price(mocker): - api_mock = MagicMock(return_value={ - 'price_usd': 28000.0, - 'price_eur': 15000.0 - }) - mocker.patch('freqtrade.fiat_convert.Market.ticker', api_mock) + patch_coinmarketcap(mocker) + mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=28000.0) fiat_convert = CryptoToFiatConverter() @@ -172,8 +170,9 @@ def test_fiat_init_network_exception(mocker): assert length_cryptomap == 0 -def test_fiat_convert_without_network(): +def test_fiat_convert_without_network(mocker): # Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap + patch_coinmarketcap(mocker) fiat_convert = CryptoToFiatConverter() @@ -186,6 +185,7 @@ def test_fiat_convert_without_network(): def test_convert_amount(mocker): + patch_coinmarketcap(mocker) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter.get_price', return_value=12345.0) fiat_convert = CryptoToFiatConverter() From 7564f7e526cfa8a1f8bf04155d26e3e9e9eadade Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sat, 16 Jun 2018 13:49:03 +0200 Subject: [PATCH 37/46] fix hyperopt test when no config.json exists --- freqtrade/tests/optimize/test_hyperopt.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index f8dce5fd6..9b66a29e2 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -61,6 +61,10 @@ def test_start(mocker, default_conf, caplog) -> None: Test start() function """ start_mock = MagicMock() + mocker.patch( + 'freqtrade.configuration.Configuration._load_config_file', + lambda *args, **kwargs: default_conf + ) mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.start', start_mock) mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) From 17801871b15ea76710528c8c30499bc9e8b8a314 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sat, 16 Jun 2018 14:23:06 +0200 Subject: [PATCH 38/46] Update ccxt from 1.14.198 to 1.14.201 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e8240fd97..7e01b10aa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.14.198 +ccxt==1.14.201 SQLAlchemy==1.2.8 python-telegram-bot==10.1.0 arrow==0.12.1 From 972736f0abd34945b4505a351aece6e4436c82a2 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sat, 16 Jun 2018 20:55:35 +0200 Subject: [PATCH 39/46] increase test-coverate for configureation --- freqtrade/tests/test_configuration.py | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index 212df2e96..019c0c09d 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -13,6 +13,7 @@ from jsonschema import ValidationError from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration +from freqtrade.constants import DEFAULT_DB_PROD_URL, DEFAULT_DB_DRYRUN_URL from freqtrade.tests.conftest import log_has from freqtrade import OperationalException @@ -140,6 +141,43 @@ def test_load_config_with_params(default_conf, mocker) -> None: assert validated_conf.get('strategy_path') == '/some/path' assert validated_conf.get('db_url') == 'sqlite:///someurl' + conf = default_conf.copy() + conf["dry_run"] = False + del conf["db_url"] + mocker.patch('freqtrade.configuration.open', mocker.mock_open( + read_data=json.dumps(conf) + )) + + arglist = [ + '--dynamic-whitelist', '10', + '--strategy', 'TestStrategy', + '--strategy-path', '/some/path' + ] + args = Arguments(arglist, '').get_parsed_arg() + + configuration = Configuration(args) + validated_conf = configuration.load_config() + assert validated_conf.get('db_url') == DEFAULT_DB_PROD_URL + + # Test dry=run with ProdURL + conf = default_conf.copy() + conf["dry_run"] = True + conf["db_url"] = DEFAULT_DB_PROD_URL + mocker.patch('freqtrade.configuration.open', mocker.mock_open( + read_data=json.dumps(conf) + )) + + arglist = [ + '--dynamic-whitelist', '10', + '--strategy', 'TestStrategy', + '--strategy-path', '/some/path' + ] + args = Arguments(arglist, '').get_parsed_arg() + + configuration = Configuration(args) + validated_conf = configuration.load_config() + assert validated_conf.get('db_url') == DEFAULT_DB_DRYRUN_URL + def test_load_custom_strategy(default_conf, mocker) -> None: """ From 7cfd99d17f574de139ccd11e5cdd3b2a5b4ca124 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sat, 16 Jun 2018 21:00:45 +0200 Subject: [PATCH 40/46] exclude __main__.py from coveralls - if __name__ == '__main__' is close to untestable - and should do nothing other than calling another function. --- .coveragerc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 95eea4f8f..80ed2af23 100644 --- a/.coveragerc +++ b/.coveragerc @@ -2,4 +2,5 @@ omit = scripts/* freqtrade/tests/* - freqtrade/vendor/* \ No newline at end of file + freqtrade/vendor/* + freatrade/__main__.py From 90a7fb603d50e52aced0cfaf1ed6221c469a8e10 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sat, 16 Jun 2018 21:28:41 +0200 Subject: [PATCH 41/46] fix typo in coverage-omit --- .coveragerc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 80ed2af23..4bd5b63fa 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,4 +3,4 @@ omit = scripts/* freqtrade/tests/* freqtrade/vendor/* - freatrade/__main__.py + freqtrade/__main__.py From ad0549414b7434fbd31841dbffe36dbd34dce1ac Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sun, 17 Jun 2018 11:34:12 +0200 Subject: [PATCH 42/46] Revert "also unit tests now need config.json" This reverts commit 7e2e7946c5b12abffd222420d74156208ef41e59. --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3f041f5dd..88121945f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,15 +16,16 @@ install: - pip install --upgrade flake8 coveralls pytest-random-order mypy - pip install -r requirements.txt - pip install -e . -- cp config.json.example config.json jobs: include: - script: - pytest --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/ - coveralls - script: + - cp config.json.example config.json - python freqtrade/main.py --datadir freqtrade/tests/testdata backtesting - script: + - cp config.json.example config.json - python freqtrade/main.py --datadir freqtrade/tests/testdata hyperopt -e 5 - script: flake8 freqtrade - script: mypy freqtrade From fef267a0dcf8f9256961866a48dba763f1652699 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sun, 17 Jun 2018 14:23:05 +0200 Subject: [PATCH 43/46] Update ccxt from 1.14.201 to 1.14.202 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7e01b10aa..9d572304d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.14.201 +ccxt==1.14.202 SQLAlchemy==1.2.8 python-telegram-bot==10.1.0 arrow==0.12.1 From 9bc833166781a44b98818234a29f905d8395deb5 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 18 Jun 2018 14:23:05 +0200 Subject: [PATCH 44/46] Update ccxt from 1.14.202 to 1.14.211 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9d572304d..96f3169c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.14.202 +ccxt==1.14.211 SQLAlchemy==1.2.8 python-telegram-bot==10.1.0 arrow==0.12.1 From a7be15d72f9a3d534042040fdf19e1a3f4974c57 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Mon, 18 Jun 2018 22:42:14 +0200 Subject: [PATCH 45/46] Update Documentation to include backtesting with docker --- docs/installation.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/installation.md b/docs/installation.md index b6688885b..87d3dc5cf 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -184,6 +184,26 @@ docker start freqtrade You do not need to rebuild the image for configuration changes, it will suffice to edit `config.json` and restart the container. +### 7. Backtest with docker + +The following assumes that the above steps (1-4) have been completed successfully. +Also, backtest-data should be available at `~/.freqtrade/user_data/`. + + +``` bash +docker run -d \ + --name freqtrade \ + -v /etc/localtime:/etc/localtime:ro \ + -v ~/.freqtrade/config.json:/freqtrade/config.json \ + -v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \ + -v ~/.freqtrade/user_data/:/freqtrade/user_data/ \ + freqtrade --strategy AwsomelyProfitableStrategy backtesting +``` + +Head over to the [Backtesting Documentation](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md) for more details. + +*Note*: Additional parameters can be appended after the image name (`freqtrade` in the above example). + ------ ## Custom Installation From e66b861c9e93cec7079dbca6ab09ee7d1febeedb Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 19 Jun 2018 14:23:05 +0200 Subject: [PATCH 46/46] Update ccxt from 1.14.211 to 1.14.224 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 96f3169c8..104634890 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.14.211 +ccxt==1.14.224 SQLAlchemy==1.2.8 python-telegram-bot==10.1.0 arrow==0.12.1