From feca87345f241bc906d8f40a578c8a5ec79a0756 Mon Sep 17 00:00:00 2001 From: kryofly Date: Wed, 10 Jan 2018 23:00:40 +0100 Subject: [PATCH 1/6] refactor --- freqtrade/optimize/__init__.py | 5 +++++ freqtrade/optimize/backtesting.py | 9 ++++----- freqtrade/optimize/hyperopt.py | 4 ++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/freqtrade/optimize/__init__.py b/freqtrade/optimize/__init__.py index 2d73c3215..aff4c372d 100644 --- a/freqtrade/optimize/__init__.py +++ b/freqtrade/optimize/__init__.py @@ -61,6 +61,11 @@ def load_data(datadir: str, ticker_interval: int = 5, pairs: Optional[List[str]] return result +def tickerdata_to_dataframe(data): + preprocessed = preprocess(data) + return preprocessed + + def preprocess(tickerdata: Dict[str, List]) -> Dict[str, DataFrame]: """Creates a dataframe and populates indicators for given ticker data""" return {pair: populate_indicators(parse_ticker_dataframe(pair_data)) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 315f960d8..5521ee98f 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -13,7 +13,6 @@ from freqtrade.analyze import populate_buy_trend, populate_sell_trend from freqtrade.exchange import Bittrex from freqtrade.main import min_roi_reached import freqtrade.misc as misc -from freqtrade.optimize import preprocess import freqtrade.optimize as optimize from freqtrade.persistence import Trade @@ -162,12 +161,12 @@ def start(args): data[pair] = exchange.get_ticker_history(pair, args.ticker_interval) else: logger.info('Using local backtesting data (using whitelist in given config) ...') - data = optimize.load_data(args.datadir, pairs=pairs, ticker_interval=args.ticker_interval, - refresh_pairs=args.refresh_pairs) - logger.info('Using stake_currency: %s ...', config['stake_currency']) logger.info('Using stake_amount: %s ...', config['stake_amount']) + data = optimize.load_data(args.datadir, pairs=pairs, ticker_interval=args.ticker_interval, + refresh_pairs=args.refresh_pairs) + max_open_trades = 0 if args.realistic_simulation: logger.info('Using max_open_trades: %s ...', config['max_open_trades']) @@ -177,7 +176,7 @@ def start(args): from freqtrade import main main._CONF = config - preprocessed = preprocess(data) + preprocessed = optimize.tickerdata_to_dataframe(data) # Print timeframe min_date, max_date = get_timeframe(preprocessed) logger.info('Measuring data from %s up to %s ...', min_date.isoformat(), max_date.isoformat()) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index cf46b96ad..b5490d72e 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -230,8 +230,8 @@ def start(args): logger.info('Using config: %s ...', args.config) config = load_config(args.config) pairs = config['exchange']['pair_whitelist'] - PROCESSED = optimize.preprocess(optimize.load_data( - args.datadir, pairs=pairs, ticker_interval=args.ticker_interval)) + data = optimize.load_data(args.datadir, pairs=pairs, ticker_interval=args.ticker_interval) + PROCESSED = optimize.tickerdata_to_dataframe(data) if args.mongodb: logger.info('Using mongodb ...') From b0f3fd7ffb434c472a61fadb421f46839e3d8f2a Mon Sep 17 00:00:00 2001 From: kryofly Date: Wed, 10 Jan 2018 23:03:05 +0100 Subject: [PATCH 2/6] timeperiod argument to backtesting and hyperopt --- freqtrade/misc.py | 14 ++++++++++++++ freqtrade/optimize/__init__.py | 11 ++++++++++- freqtrade/optimize/backtesting.py | 2 +- freqtrade/optimize/hyperopt.py | 2 +- freqtrade/tests/optimize/test_backtesting.py | 14 ++++---------- freqtrade/tests/optimize/test_hyperopt.py | 8 ++++---- freqtrade/tests/optimize/test_optimize.py | 7 +++++++ 7 files changed, 41 insertions(+), 17 deletions(-) diff --git a/freqtrade/misc.py b/freqtrade/misc.py index afc4334e8..97e1eca36 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -183,6 +183,13 @@ def build_subcommands(parser: argparse.ArgumentParser) -> None: action='store_true', dest='refresh_pairs', ) + backtesting_cmd.add_argument( + '-tp', '--timeperiod', + help='Use the last N ticks of data.', + default=None, + type=int, + dest='timeperiod', + ) # Add hyperopt subcommand hyperopt_cmd = subparsers.add_parser('hyperopt', help='hyperopt module') @@ -209,6 +216,13 @@ def build_subcommands(parser: argparse.ArgumentParser) -> None: type=int, metavar='INT', ) + hyperopt_cmd.add_argument( + '-tp', '--timeperiod', + help='Use the last N ticks of data.', + default=None, + type=int, + dest='timeperiod', + ) # Required json-schema for user specified config diff --git a/freqtrade/optimize/__init__.py b/freqtrade/optimize/__init__.py index aff4c372d..3f6a98ac1 100644 --- a/freqtrade/optimize/__init__.py +++ b/freqtrade/optimize/__init__.py @@ -12,6 +12,13 @@ from freqtrade.analyze import populate_indicators, parse_ticker_dataframe logger = logging.getLogger(__name__) +def trim_tickerlist(dl, num): + new = {} + for pair, pair_data in dl.items(): + new[pair] = pair_data[num:] + return new + + def load_tickerdata_file(datadir, pair, ticker_interval): """ Load a pair from file, @@ -61,7 +68,9 @@ def load_data(datadir: str, ticker_interval: int = 5, pairs: Optional[List[str]] return result -def tickerdata_to_dataframe(data): +def tickerdata_to_dataframe(data, timeperiod=None): + if timeperiod: + data = trim_tickerlist(data, timeperiod) preprocessed = preprocess(data) return preprocessed diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 5521ee98f..35b2c6d27 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -176,7 +176,7 @@ def start(args): from freqtrade import main main._CONF = config - preprocessed = optimize.tickerdata_to_dataframe(data) + preprocessed = optimize.tickerdata_to_dataframe(data, timeperiod=args.timeperiod) # Print timeframe min_date, max_date = get_timeframe(preprocessed) logger.info('Measuring data from %s up to %s ...', min_date.isoformat(), max_date.isoformat()) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index b5490d72e..a556258d0 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -231,7 +231,7 @@ def start(args): config = load_config(args.config) pairs = config['exchange']['pair_whitelist'] data = optimize.load_data(args.datadir, pairs=pairs, ticker_interval=args.ticker_interval) - PROCESSED = optimize.tickerdata_to_dataframe(data) + PROCESSED = optimize.tickerdata_to_dataframe(data, timeperiod=args.timeperiod) if args.mongodb: logger.info('Using mongodb ...') diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 5f899a48a..d4172d983 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -6,7 +6,7 @@ import pandas as pd from unittest.mock import MagicMock from freqtrade import exchange, optimize from freqtrade.exchange import Bittrex -from freqtrade.optimize import preprocess +from freqtrade.optimize import preprocess, trim_tickerlist from freqtrade.optimize.backtesting import backtest, generate_text_table, get_timeframe import freqtrade.optimize.backtesting as backtesting @@ -59,16 +59,9 @@ def test_backtest_1min_ticker_interval(default_conf, mocker): assert not results.empty -def trim_dictlist(dl, num): - new = {} - for pair, pair_data in dl.items(): - new[pair] = pair_data[num:] - return new - - def load_data_test(what): data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST']) - data = trim_dictlist(data, -100) + data = trim_tickerlist(data, -100) pair = data['BTC_UNITEST'] datalen = len(pair) # Depending on the what parameter we now adjust the @@ -152,7 +145,7 @@ def test_backtest_pricecontours(default_conf, mocker): def mocked_load_data(datadir, pairs=[], ticker_interval=0, refresh_pairs=False): tickerdata = optimize.load_tickerdata_file(datadir, 'BTC_UNITEST', 1) pairdata = {'BTC_UNITEST': tickerdata} - return trim_dictlist(pairdata, -100) + return trim_tickerlist(pairdata, -100) def test_backtest_start(default_conf, mocker, caplog): @@ -166,6 +159,7 @@ def test_backtest_start(default_conf, mocker, caplog): args.level = 10 args.live = False args.datadir = None + args.timeperiod = None # needed due to MagicMock malleability backtesting.start(args) # check the logs, that will contain the backtest result exists = ['Using max_open_trades: 1 ...', diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index a309af7fe..f8feda0f8 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -37,7 +37,7 @@ def create_trials(mocker): def test_start_calls_fmin(mocker): mocker.patch('freqtrade.optimize.hyperopt.Trials', return_value=create_trials(mocker)) - mocker.patch('freqtrade.optimize.preprocess') + mocker.patch('freqtrade.optimize.tickerdata_to_dataframe') mocker.patch('freqtrade.optimize.load_data') mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={}) @@ -50,7 +50,7 @@ def test_start_calls_fmin(mocker): def test_start_uses_mongotrials(mocker): mock_mongotrials = mocker.patch('freqtrade.optimize.hyperopt.MongoTrials', return_value=create_trials(mocker)) - mocker.patch('freqtrade.optimize.preprocess') + mocker.patch('freqtrade.optimize.tickerdata_to_dataframe') mocker.patch('freqtrade.optimize.load_data') mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={}) @@ -104,7 +104,7 @@ def test_fmin_best_results(mocker, caplog): } mocker.patch('freqtrade.optimize.hyperopt.MongoTrials', return_value=create_trials(mocker)) - mocker.patch('freqtrade.optimize.preprocess') + mocker.patch('freqtrade.optimize.tickerdata_to_dataframe') mocker.patch('freqtrade.optimize.load_data') mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result) @@ -126,7 +126,7 @@ def test_fmin_best_results(mocker, caplog): def test_fmin_throw_value_error(mocker, caplog): mocker.patch('freqtrade.optimize.hyperopt.MongoTrials', return_value=create_trials(mocker)) - mocker.patch('freqtrade.optimize.preprocess') + mocker.patch('freqtrade.optimize.tickerdata_to_dataframe') mocker.patch('freqtrade.optimize.load_data') mocker.patch('freqtrade.optimize.hyperopt.fmin', side_effect=ValueError()) diff --git a/freqtrade/tests/optimize/test_optimize.py b/freqtrade/tests/optimize/test_optimize.py index a5892f278..07e9a46b6 100644 --- a/freqtrade/tests/optimize/test_optimize.py +++ b/freqtrade/tests/optimize/test_optimize.py @@ -174,3 +174,10 @@ def test_load_tickerdata_file(): assert not load_tickerdata_file(None, 'BTC_UNITEST', 7) tickerdata = load_tickerdata_file(None, 'BTC_UNITEST', 1) assert _btc_unittest_length == len(tickerdata) + + +def test_tickerdata_to_dataframe(): + tick = load_tickerdata_file(None, 'BTC_UNITEST', 1) + tickerlist = {'BTC_UNITEST': tick} + data = optimize.tickerdata_to_dataframe(tickerlist, timeperiod=-100) + assert 100 == len(data['BTC_UNITEST']) From 94883202b8e108ab0e5244e866638dc2d579a019 Mon Sep 17 00:00:00 2001 From: kryofly Date: Thu, 11 Jan 2018 00:14:36 +0100 Subject: [PATCH 3/6] docs: --timeperiod argument --- docs/backtesting.md | 10 ++++++++++ docs/hyperopt.md | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/docs/backtesting.md b/docs/backtesting.md index c426e2b5c..31745877f 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -51,6 +51,16 @@ python3 ./freqtrade/main.py backtesting --realistic-simulation --live python3 ./freqtrade/main.py backtesting --datadir freqtrade/tests/testdata-20180101 ``` +**Running backtest with smaller testset** +Use the --timeperiod 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 --timeperiod -200 +``` + + For help about backtesting usage, please refer to [Backtesting commands](#backtesting-commands). diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 24a9dbc51..af564f0b6 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -168,6 +168,16 @@ If you would like to learn parameters using an alternate ticke-data that you have on-disk, use the --datadir PATH option. Default hyperopt will use data from directory freqtrade/tests/testdata. +### Running hyperopt with smaller testset + +Use the --timeperiod 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 hyperopt --timeperiod -200 +``` + ### 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. From 48432abff1cb7e9316e4e944c9f71a68611c4dc3 Mon Sep 17 00:00:00 2001 From: kryofly Date: Fri, 12 Jan 2018 19:48:52 +0100 Subject: [PATCH 4/6] remove two-letter options --- freqtrade/misc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 8dceee5c3..47b1705b3 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -127,7 +127,7 @@ def parse_args(args: List[str], description: str): dest='dry_run_db', ) parser.add_argument( - '-dd', '--datadir', + '--datadir', help='path to backtest data (default freqdata/tests/testdata', dest='datadir', default=os.path.join('freqtrade', 'tests', 'testdata'), @@ -186,7 +186,7 @@ def build_subcommands(parser: argparse.ArgumentParser) -> None: dest='refresh_pairs', ) backtesting_cmd.add_argument( - '-tp', '--timeperiod', + '--timeperiod', help='Use the last N ticks of data.', default=None, type=int, From 71bb348698e5f7de928ed6232fbae55fca789a73 Mon Sep 17 00:00:00 2001 From: kryofly Date: Mon, 15 Jan 2018 21:49:06 +0100 Subject: [PATCH 5/6] rename --timeperiod to --timerange --- docs/backtesting.md | 4 ++-- freqtrade/misc.py | 8 ++++---- freqtrade/optimize/__init__.py | 6 +++--- freqtrade/optimize/backtesting.py | 2 +- freqtrade/optimize/hyperopt.py | 2 +- freqtrade/tests/optimize/test_backtesting.py | 2 +- freqtrade/tests/optimize/test_optimize.py | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 800420ea6..7984e67bc 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -52,12 +52,12 @@ python3 ./freqtrade/main.py backtesting --datadir freqtrade/tests/testdata-20180 ``` **Running backtest with smaller testset** -Use the --timeperiod argument to change how much of the 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 --timeperiod -200 +python3 ./freqtrade/main.py backtesting --timerange -200 ``` **Update testdata directory diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 9c2bce84f..c019509d6 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -191,11 +191,11 @@ def build_subcommands(parser: argparse.ArgumentParser) -> None: dest='refresh_pairs', ) backtesting_cmd.add_argument( - '--timeperiod', + '--timerange', help='Use the last N ticks of data.', default=None, type=int, - dest='timeperiod', + dest='timerange', ) # Add hyperopt subcommand @@ -224,11 +224,11 @@ def build_subcommands(parser: argparse.ArgumentParser) -> None: metavar='INT', ) hyperopt_cmd.add_argument( - '-tp', '--timeperiod', + '-tp', '--timerange', help='Use the last N ticks of data.', default=None, type=int, - dest='timeperiod', + dest='timerange', ) diff --git a/freqtrade/optimize/__init__.py b/freqtrade/optimize/__init__.py index 3f6a98ac1..54d339b73 100644 --- a/freqtrade/optimize/__init__.py +++ b/freqtrade/optimize/__init__.py @@ -68,9 +68,9 @@ def load_data(datadir: str, ticker_interval: int = 5, pairs: Optional[List[str]] return result -def tickerdata_to_dataframe(data, timeperiod=None): - if timeperiod: - data = trim_tickerlist(data, timeperiod) +def tickerdata_to_dataframe(data, timerange=None): + if timerange: + data = trim_tickerlist(data, timerange) preprocessed = preprocess(data) return preprocessed diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index afcd0c539..5b8afc267 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -175,7 +175,7 @@ def start(args): from freqtrade import main main._CONF = config - preprocessed = optimize.tickerdata_to_dataframe(data, timeperiod=args.timeperiod) + preprocessed = optimize.tickerdata_to_dataframe(data, timerange=args.timerange) # Print timeframe min_date, max_date = get_timeframe(preprocessed) logger.info('Measuring data from %s up to %s ...', min_date.isoformat(), max_date.isoformat()) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 15019e16b..abba7c35f 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -260,7 +260,7 @@ def start(args): config = load_config(args.config) pairs = config['exchange']['pair_whitelist'] data = optimize.load_data(args.datadir, pairs=pairs, ticker_interval=args.ticker_interval) - PROCESSED = optimize.tickerdata_to_dataframe(data, timeperiod=args.timeperiod) + PROCESSED = optimize.tickerdata_to_dataframe(data, timerange=args.timerange) if args.mongodb: logger.info('Using mongodb ...') diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index d4172d983..c6bcdd713 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -159,7 +159,7 @@ def test_backtest_start(default_conf, mocker, caplog): args.level = 10 args.live = False args.datadir = None - args.timeperiod = None # needed due to MagicMock malleability + args.timerange = None # needed due to MagicMock malleability backtesting.start(args) # check the logs, that will contain the backtest result exists = ['Using max_open_trades: 1 ...', diff --git a/freqtrade/tests/optimize/test_optimize.py b/freqtrade/tests/optimize/test_optimize.py index 6decb1414..bfbc99745 100644 --- a/freqtrade/tests/optimize/test_optimize.py +++ b/freqtrade/tests/optimize/test_optimize.py @@ -179,5 +179,5 @@ def test_load_tickerdata_file(): def test_tickerdata_to_dataframe(): tick = load_tickerdata_file(None, 'BTC_UNITEST', 1) tickerlist = {'BTC_UNITEST': tick} - data = optimize.tickerdata_to_dataframe(tickerlist, timeperiod=-100) + data = optimize.tickerdata_to_dataframe(tickerlist, timerange=-100) assert 100 == len(data['BTC_UNITEST']) From 0e58ab7e012a684d9d2aefa4e15aefe6e27153b2 Mon Sep 17 00:00:00 2001 From: kryofly Date: Mon, 15 Jan 2018 22:25:02 +0100 Subject: [PATCH 6/6] more advanced use of --timerange --- docs/backtesting.md | 15 ++++++- freqtrade/misc.py | 41 +++++++++++++++++--- freqtrade/optimize/__init__.py | 34 +++++++++------- freqtrade/optimize/backtesting.py | 7 ++-- freqtrade/optimize/hyperopt.py | 9 +++-- freqtrade/tests/optimize/test_backtesting.py | 14 +++---- freqtrade/tests/optimize/test_hyperopt.py | 15 ++++--- freqtrade/tests/optimize/test_optimize.py | 5 ++- freqtrade/tests/test_misc.py | 9 ++++- 9 files changed, 109 insertions(+), 40 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 7984e67bc..9829a10c8 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -57,9 +57,22 @@ you want to use. The last N ticks/timeframes will be used. Example: ```bash -python3 ./freqtrade/main.py backtesting --timerange -200 +python3 ./freqtrade/main.py backtesting --timerange=-200 ``` +***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: + Not implemented yet! --timerange=-20180131 + Not implemented yet! --timerange=20180101- + Not implemented yet! --timerange=20180101-20181231 + Last 123 tickframes of data: --timerange=-123 + First 123 tickframes of data: --timerange=123- + Tickframes from line 123 through 456: --timerange=123-456 + + **Update testdata directory To update your testdata directory, or download into another testdata directory: ```bash diff --git a/freqtrade/misc.py b/freqtrade/misc.py index c019509d6..a9aeee80e 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -4,6 +4,7 @@ import json import logging import time import os +import re from typing import Any, Callable, Dict, List from jsonschema import Draft4Validator, validate @@ -192,9 +193,9 @@ def build_subcommands(parser: argparse.ArgumentParser) -> None: ) backtesting_cmd.add_argument( '--timerange', - help='Use the last N ticks of data.', + help='Specify what timerange of data to use.', default=None, - type=int, + type=str, dest='timerange', ) @@ -224,14 +225,44 @@ def build_subcommands(parser: argparse.ArgumentParser) -> None: metavar='INT', ) hyperopt_cmd.add_argument( - '-tp', '--timerange', - help='Use the last N ticks of data.', + '--timerange', + help='Specify what timerange of data to use.', default=None, - type=int, + type=str, dest='timerange', ) +def parse_timerange(text): + if text is None: + return None + syntax = [('^-(\d{8})$', (None, 'date')), + ('^(\d{8})-$', ('date', None)), + ('^(\d{8})-(\d{8})$', ('date', 'date')), + ('^(-\d+)$', (None, 'line')), + ('^(\d+)-$', ('line', None)), + ('^(\d+)-(\d+)$', ('index', 'index'))] + for rex, stype in syntax: + # Apply the regular expression to text + m = re.match(rex, text) + if m: # Regex has matched + rvals = m.groups() + n = 0 + start = None + stop = None + if stype[0]: + start = rvals[n] + if stype[0] != 'date': + start = int(start) + n += 1 + if stype[1]: + stop = rvals[n] + if stype[1] != 'date': + stop = int(stop) + return (stype, start, stop) + raise Exception('Incorrect syntax for timerange "%s"' % text) + + # Required json-schema for user specified config CONF_SCHEMA = { 'type': 'object', diff --git a/freqtrade/optimize/__init__.py b/freqtrade/optimize/__init__.py index 54d339b73..40269db46 100644 --- a/freqtrade/optimize/__init__.py +++ b/freqtrade/optimize/__init__.py @@ -12,14 +12,20 @@ from freqtrade.analyze import populate_indicators, parse_ticker_dataframe logger = logging.getLogger(__name__) -def trim_tickerlist(dl, num): - new = {} - for pair, pair_data in dl.items(): - new[pair] = pair_data[num:] - return new +def trim_tickerlist(tickerlist, timerange): + (stype, start, stop) = timerange + if stype == (None, 'line'): + return tickerlist[stop:] + elif stype == ('line', None): + return tickerlist[0:start] + elif stype == ('index', 'index'): + return tickerlist[start:stop] + else: + return tickerlist -def load_tickerdata_file(datadir, pair, ticker_interval): +def load_tickerdata_file(datadir, pair, ticker_interval, + timerange=None): """ Load a pair from file, :return dict OR empty if unsuccesful @@ -37,11 +43,15 @@ def load_tickerdata_file(datadir, pair, ticker_interval): # Read the file, load the json with open(file) as tickerdata: pairdata = json.load(tickerdata) + if timerange: + pairdata = trim_tickerlist(pairdata, timerange) return pairdata -def load_data(datadir: str, ticker_interval: int = 5, pairs: Optional[List[str]] = None, - refresh_pairs: Optional[bool] = False) -> Dict[str, List]: +def load_data(datadir: str, ticker_interval: int = 5, + pairs: Optional[List[str]] = None, + refresh_pairs: Optional[bool] = False, + timerange=None) -> Dict[str, List]: """ Loads ticker history data for the given parameters :param ticker_interval: ticker interval in minutes @@ -58,19 +68,17 @@ def load_data(datadir: str, ticker_interval: int = 5, pairs: Optional[List[str]] download_pairs(datadir, _pairs) for pair in _pairs: - pairdata = load_tickerdata_file(datadir, pair, ticker_interval) + pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange) if not pairdata: # download the tickerdata from exchange download_backtesting_testdata(datadir, pair=pair, interval=ticker_interval) # and retry reading the pair - pairdata = load_tickerdata_file(datadir, pair, ticker_interval) + pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange) result[pair] = pairdata return result -def tickerdata_to_dataframe(data, timerange=None): - if timerange: - data = trim_tickerlist(data, timerange) +def tickerdata_to_dataframe(data): preprocessed = preprocess(data) return preprocessed diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 5b8afc267..4f3d4bb24 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -163,9 +163,10 @@ def start(args): logger.info('Using stake_currency: %s ...', config['stake_currency']) logger.info('Using stake_amount: %s ...', config['stake_amount']) + timerange = misc.parse_timerange(args.timerange) data = optimize.load_data(args.datadir, pairs=pairs, ticker_interval=args.ticker_interval, - refresh_pairs=args.refresh_pairs) - + refresh_pairs=args.refresh_pairs, + timerange=timerange) max_open_trades = 0 if args.realistic_simulation: logger.info('Using max_open_trades: %s ...', config['max_open_trades']) @@ -175,7 +176,7 @@ def start(args): from freqtrade import main main._CONF = config - preprocessed = optimize.tickerdata_to_dataframe(data, timerange=args.timerange) + preprocessed = optimize.tickerdata_to_dataframe(data) # Print timeframe min_date, max_date = get_timeframe(preprocessed) logger.info('Measuring data from %s up to %s ...', min_date.isoformat(), max_date.isoformat()) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index abba7c35f..7a1744dc0 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -15,7 +15,7 @@ from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, hp, space_eval, tpe from hyperopt.mongoexp import MongoTrials from pandas import DataFrame -from freqtrade import main # noqa +from freqtrade import main, misc # noqa from freqtrade import exchange, optimize from freqtrade.exchange import Bittrex from freqtrade.misc import load_config @@ -259,8 +259,11 @@ def start(args): logger.info('Using config: %s ...', args.config) config = load_config(args.config) pairs = config['exchange']['pair_whitelist'] - data = optimize.load_data(args.datadir, pairs=pairs, ticker_interval=args.ticker_interval) - PROCESSED = optimize.tickerdata_to_dataframe(data, timerange=args.timerange) + timerange = misc.parse_timerange(args.timerange) + data = optimize.load_data(args.datadir, pairs=pairs, + ticker_interval=args.ticker_interval, + timerange=timerange) + PROCESSED = optimize.tickerdata_to_dataframe(data) if args.mongodb: logger.info('Using mongodb ...') diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index c6bcdd713..92170a184 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -6,7 +6,7 @@ import pandas as pd from unittest.mock import MagicMock from freqtrade import exchange, optimize from freqtrade.exchange import Bittrex -from freqtrade.optimize import preprocess, trim_tickerlist +from freqtrade.optimize import preprocess from freqtrade.optimize.backtesting import backtest, generate_text_table, get_timeframe import freqtrade.optimize.backtesting as backtesting @@ -60,8 +60,8 @@ def test_backtest_1min_ticker_interval(default_conf, mocker): def load_data_test(what): - data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST']) - data = trim_tickerlist(data, -100) + timerange = ((None, 'line'), None, -100) + data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST'], timerange=timerange) pair = data['BTC_UNITEST'] datalen = len(pair) # Depending on the what parameter we now adjust the @@ -142,10 +142,10 @@ def test_backtest_pricecontours(default_conf, mocker): simple_backtest(default_conf, contour, numres) -def mocked_load_data(datadir, pairs=[], ticker_interval=0, refresh_pairs=False): - tickerdata = optimize.load_tickerdata_file(datadir, 'BTC_UNITEST', 1) +def mocked_load_data(datadir, pairs=[], ticker_interval=0, refresh_pairs=False, timerange=None): + tickerdata = optimize.load_tickerdata_file(datadir, 'BTC_UNITEST', 1, timerange=timerange) pairdata = {'BTC_UNITEST': tickerdata} - return trim_tickerlist(pairdata, -100) + return pairdata def test_backtest_start(default_conf, mocker, caplog): @@ -159,7 +159,7 @@ def test_backtest_start(default_conf, mocker, caplog): args.level = 10 args.live = False args.datadir = None - args.timerange = None # needed due to MagicMock malleability + args.timerange = '-100' # needed due to MagicMock malleability backtesting.start(args) # check the logs, that will contain the backtest result exists = ['Using max_open_trades: 1 ...', diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index ac7b198e4..1c4cceee5 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -62,7 +62,8 @@ def test_start_calls_fmin(mocker): mocker.patch('freqtrade.optimize.load_data') mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={}) - args = mocker.Mock(epochs=1, config='config.json.example', mongodb=False) + args = mocker.Mock(epochs=1, config='config.json.example', mongodb=False, + timerange=None) start(args) mock_fmin.assert_called_once() @@ -75,7 +76,8 @@ def test_start_uses_mongotrials(mocker): mocker.patch('freqtrade.optimize.load_data') mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={}) - args = mocker.Mock(epochs=1, config='config.json.example', mongodb=True) + args = mocker.Mock(epochs=1, config='config.json.example', mongodb=True, + timerange=None) start(args) mock_mongotrials.assert_called_once() @@ -129,7 +131,8 @@ def test_fmin_best_results(mocker, caplog): mocker.patch('freqtrade.optimize.load_data') mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result) - args = mocker.Mock(epochs=1, config='config.json.example') + args = mocker.Mock(epochs=1, config='config.json.example', + timerange=None) start(args) exists = [ @@ -151,7 +154,8 @@ def test_fmin_throw_value_error(mocker, caplog): mocker.patch('freqtrade.optimize.load_data') mocker.patch('freqtrade.optimize.hyperopt.fmin', side_effect=ValueError()) - args = mocker.Mock(epochs=1, config='config.json.example') + args = mocker.Mock(epochs=1, config='config.json.example', + timerange=None) start(args) exists = [ @@ -185,7 +189,8 @@ def test_resuming_previous_hyperopt_results_succeeds(mocker): return_value={}) args = mocker.Mock(epochs=1, config='config.json.example', - mongodb=False) + mongodb=False, + timerange=None) start(args) diff --git a/freqtrade/tests/optimize/test_optimize.py b/freqtrade/tests/optimize/test_optimize.py index bfbc99745..61325797b 100644 --- a/freqtrade/tests/optimize/test_optimize.py +++ b/freqtrade/tests/optimize/test_optimize.py @@ -177,7 +177,8 @@ def test_load_tickerdata_file(): def test_tickerdata_to_dataframe(): - tick = load_tickerdata_file(None, 'BTC_UNITEST', 1) + timerange = ((None, 'line'), None, -100) + tick = load_tickerdata_file(None, 'BTC_UNITEST', 1, timerange=timerange) tickerlist = {'BTC_UNITEST': tick} - data = optimize.tickerdata_to_dataframe(tickerlist, timerange=-100) + data = optimize.tickerdata_to_dataframe(tickerlist) assert 100 == len(data['BTC_UNITEST']) diff --git a/freqtrade/tests/test_misc.py b/freqtrade/tests/test_misc.py index 63cfba627..74f611f5f 100644 --- a/freqtrade/tests/test_misc.py +++ b/freqtrade/tests/test_misc.py @@ -8,7 +8,7 @@ import pytest from jsonschema import ValidationError from freqtrade.misc import (common_args_parser, load_config, parse_args, - throttle) + throttle, parse_timerange) def test_throttle(): @@ -133,6 +133,13 @@ def test_parse_args_hyperopt_custom(mocker): assert call_args.func is not None +def test_parse_timerange_incorrect(): + assert ((None, 'line'), None, -200) == parse_timerange('-200') + assert (('line', None), 200, None) == parse_timerange('200-') + with pytest.raises(Exception, match=r'Incorrect syntax.*'): + parse_timerange('-') + + def test_load_config(default_conf, mocker): file_mock = mocker.patch('freqtrade.misc.open', mocker.mock_open( read_data=json.dumps(default_conf)