Merge pull request #357 from kryofly/timeperiod

Timeperiod
This commit is contained in:
Gérald LONLAS 2018-01-18 20:26:44 -08:00 committed by GitHub
commit 14d16f2574
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 153 additions and 28 deletions

View File

@ -51,6 +51,29 @@ python3 ./freqtrade/main.py backtesting --realistic-simulation --live
python3 ./freqtrade/main.py backtesting --datadir freqtrade/tests/testdata-20180101 python3 ./freqtrade/main.py backtesting --datadir freqtrade/tests/testdata-20180101
``` ```
**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***
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: To update your testdata directory, or download into another testdata directory:
```bash ```bash
mkdir freqtrade/tests/testdata-20180113 mkdir freqtrade/tests/testdata-20180113

View File

@ -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 you have on-disk, use the --datadir PATH option. Default hyperopt will
use data from directory freqtrade/tests/testdata. 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
Hyperopt with MongoDB, is like Hyperopt under steroids. As you saw by Hyperopt with MongoDB, is like Hyperopt under steroids. As you saw by
executing the previous command is the execution takes a long time. executing the previous command is the execution takes a long time.

View File

@ -4,6 +4,7 @@ import json
import logging import logging
import time import time
import os import os
import re
from typing import Any, Callable, Dict, List from typing import Any, Callable, Dict, List
from jsonschema import Draft4Validator, validate from jsonschema import Draft4Validator, validate
@ -132,7 +133,7 @@ def parse_args(args: List[str], description: str):
dest='dry_run_db', dest='dry_run_db',
) )
parser.add_argument( parser.add_argument(
'-dd', '--datadir', '--datadir',
help='path to backtest data (default freqdata/tests/testdata', help='path to backtest data (default freqdata/tests/testdata',
dest='datadir', dest='datadir',
default=os.path.join('freqtrade', 'tests', 'testdata'), default=os.path.join('freqtrade', 'tests', 'testdata'),
@ -190,6 +191,13 @@ def build_subcommands(parser: argparse.ArgumentParser) -> None:
action='store_true', action='store_true',
dest='refresh_pairs', dest='refresh_pairs',
) )
backtesting_cmd.add_argument(
'--timerange',
help='Specify what timerange of data to use.',
default=None,
type=str,
dest='timerange',
)
# Add hyperopt subcommand # Add hyperopt subcommand
hyperopt_cmd = subparsers.add_parser('hyperopt', help='hyperopt module') hyperopt_cmd = subparsers.add_parser('hyperopt', help='hyperopt module')
@ -216,6 +224,43 @@ def build_subcommands(parser: argparse.ArgumentParser) -> None:
type=int, type=int,
metavar='INT', metavar='INT',
) )
hyperopt_cmd.add_argument(
'--timerange',
help='Specify what timerange of data to use.',
default=None,
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 # Required json-schema for user specified config

View File

@ -12,7 +12,20 @@ from freqtrade.analyze import populate_indicators, parse_ticker_dataframe
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def load_tickerdata_file(datadir, pair, ticker_interval): 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,
timerange=None):
""" """
Load a pair from file, Load a pair from file,
:return dict OR empty if unsuccesful :return dict OR empty if unsuccesful
@ -30,11 +43,15 @@ def load_tickerdata_file(datadir, pair, ticker_interval):
# Read the file, load the json # Read the file, load the json
with open(file) as tickerdata: with open(file) as tickerdata:
pairdata = json.load(tickerdata) pairdata = json.load(tickerdata)
if timerange:
pairdata = trim_tickerlist(pairdata, timerange)
return pairdata return pairdata
def load_data(datadir: str, ticker_interval: int = 5, pairs: Optional[List[str]] = None, def load_data(datadir: str, ticker_interval: int = 5,
refresh_pairs: Optional[bool] = False) -> Dict[str, List]: pairs: Optional[List[str]] = None,
refresh_pairs: Optional[bool] = False,
timerange=None) -> Dict[str, List]:
""" """
Loads ticker history data for the given parameters Loads ticker history data for the given parameters
:param ticker_interval: ticker interval in minutes :param ticker_interval: ticker interval in minutes
@ -51,16 +68,21 @@ def load_data(datadir: str, ticker_interval: int = 5, pairs: Optional[List[str]]
download_pairs(datadir, _pairs) download_pairs(datadir, _pairs)
for pair in _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: if not pairdata:
# download the tickerdata from exchange # download the tickerdata from exchange
download_backtesting_testdata(datadir, pair=pair, interval=ticker_interval) download_backtesting_testdata(datadir, pair=pair, interval=ticker_interval)
# and retry reading the pair # 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 result[pair] = pairdata
return result return result
def tickerdata_to_dataframe(data):
preprocessed = preprocess(data)
return preprocessed
def preprocess(tickerdata: Dict[str, List]) -> Dict[str, DataFrame]: def preprocess(tickerdata: Dict[str, List]) -> Dict[str, DataFrame]:
"""Creates a dataframe and populates indicators for given ticker data""" """Creates a dataframe and populates indicators for given ticker data"""
return {pair: populate_indicators(parse_ticker_dataframe(pair_data)) return {pair: populate_indicators(parse_ticker_dataframe(pair_data))

View File

@ -13,7 +13,6 @@ from freqtrade import exchange
from freqtrade.analyze import populate_buy_trend, populate_sell_trend from freqtrade.analyze import populate_buy_trend, populate_sell_trend
from freqtrade.exchange import Bittrex from freqtrade.exchange import Bittrex
from freqtrade.main import min_roi_reached from freqtrade.main import min_roi_reached
from freqtrade.optimize import preprocess
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -161,12 +160,13 @@ def start(args):
data[pair] = exchange.get_ticker_history(pair, args.ticker_interval) data[pair] = exchange.get_ticker_history(pair, args.ticker_interval)
else: else:
logger.info('Using local backtesting data (using whitelist in given config) ...') 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_currency: %s ...', config['stake_currency'])
logger.info('Using stake_amount: %s ...', config['stake_amount']) 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,
timerange=timerange)
max_open_trades = 0 max_open_trades = 0
if args.realistic_simulation: if args.realistic_simulation:
logger.info('Using max_open_trades: %s ...', config['max_open_trades']) logger.info('Using max_open_trades: %s ...', config['max_open_trades'])
@ -176,7 +176,7 @@ def start(args):
from freqtrade import main from freqtrade import main
main._CONF = config main._CONF = config
preprocessed = preprocess(data) preprocessed = optimize.tickerdata_to_dataframe(data)
# Print timeframe # Print timeframe
min_date, max_date = get_timeframe(preprocessed) min_date, max_date = get_timeframe(preprocessed)
logger.info('Measuring data from %s up to %s ...', min_date.isoformat(), max_date.isoformat()) logger.info('Measuring data from %s up to %s ...', min_date.isoformat(), max_date.isoformat())

View File

@ -15,7 +15,7 @@ from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, hp, space_eval, tpe
from hyperopt.mongoexp import MongoTrials from hyperopt.mongoexp import MongoTrials
from pandas import DataFrame from pandas import DataFrame
from freqtrade import main # noqa from freqtrade import main, misc # noqa
from freqtrade import exchange, optimize from freqtrade import exchange, optimize
from freqtrade.exchange import Bittrex from freqtrade.exchange import Bittrex
from freqtrade.misc import load_config from freqtrade.misc import load_config
@ -273,8 +273,11 @@ def start(args):
logger.info('Using config: %s ...', args.config) logger.info('Using config: %s ...', args.config)
config = load_config(args.config) config = load_config(args.config)
pairs = config['exchange']['pair_whitelist'] pairs = config['exchange']['pair_whitelist']
PROCESSED = optimize.preprocess(optimize.load_data( timerange = misc.parse_timerange(args.timerange)
args.datadir, pairs=pairs, ticker_interval=args.ticker_interval)) data = optimize.load_data(args.datadir, pairs=pairs,
ticker_interval=args.ticker_interval,
timerange=timerange)
PROCESSED = optimize.tickerdata_to_dataframe(data)
if args.mongodb: if args.mongodb:
logger.info('Using mongodb ...') logger.info('Using mongodb ...')

View File

@ -69,8 +69,8 @@ def test_backtest_1min_ticker_interval(default_conf, mocker):
def load_data_test(what): def load_data_test(what):
data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST']) timerange = ((None, 'line'), None, -100)
data = trim_dictlist(data, -100) data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST'], timerange=timerange)
pair = data['BTC_UNITEST'] pair = data['BTC_UNITEST']
datalen = len(pair) datalen = len(pair)
# Depending on the what parameter we now adjust the # Depending on the what parameter we now adjust the
@ -152,10 +152,10 @@ def test_backtest_pricecontours(default_conf, mocker):
simple_backtest(default_conf, contour, numres) simple_backtest(default_conf, contour, numres)
def mocked_load_data(datadir, pairs=[], ticker_interval=0, refresh_pairs=False): def mocked_load_data(datadir, pairs=[], ticker_interval=0, refresh_pairs=False, timerange=None):
tickerdata = optimize.load_tickerdata_file(datadir, 'BTC_UNITEST', 1) tickerdata = optimize.load_tickerdata_file(datadir, 'BTC_UNITEST', 1, timerange=timerange)
pairdata = {'BTC_UNITEST': tickerdata} pairdata = {'BTC_UNITEST': tickerdata}
return trim_dictlist(pairdata, -100) return pairdata
def test_backtest_start(default_conf, mocker, caplog): def test_backtest_start(default_conf, mocker, caplog):
@ -169,6 +169,7 @@ def test_backtest_start(default_conf, mocker, caplog):
args.level = 10 args.level = 10
args.live = False args.live = False
args.datadir = None args.datadir = None
args.timerange = '-100' # needed due to MagicMock malleability
backtesting.start(args) backtesting.start(args)
# check the logs, that will contain the backtest result # check the logs, that will contain the backtest result
exists = ['Using max_open_trades: 1 ...', exists = ['Using max_open_trades: 1 ...',

View File

@ -54,6 +54,7 @@ def create_trials(mocker):
def test_start_calls_fmin(mocker): def test_start_calls_fmin(mocker):
trials = create_trials(mocker) trials = create_trials(mocker)
mocker.patch('freqtrade.optimize.tickerdata_to_dataframe')
mocker.patch('freqtrade.optimize.hyperopt.TRIALS', return_value=trials) mocker.patch('freqtrade.optimize.hyperopt.TRIALS', return_value=trials)
mocker.patch('freqtrade.optimize.hyperopt.sorted', mocker.patch('freqtrade.optimize.hyperopt.sorted',
return_value=trials.results) return_value=trials.results)
@ -61,7 +62,8 @@ def test_start_calls_fmin(mocker):
mocker.patch('freqtrade.optimize.load_data') mocker.patch('freqtrade.optimize.load_data')
mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={}) 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) start(args)
mock_fmin.assert_called_once() mock_fmin.assert_called_once()
@ -70,11 +72,12 @@ def test_start_calls_fmin(mocker):
def test_start_uses_mongotrials(mocker): def test_start_uses_mongotrials(mocker):
mock_mongotrials = mocker.patch('freqtrade.optimize.hyperopt.MongoTrials', mock_mongotrials = mocker.patch('freqtrade.optimize.hyperopt.MongoTrials',
return_value=create_trials(mocker)) 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.load_data')
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={}) 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) start(args)
mock_mongotrials.assert_called_once() mock_mongotrials.assert_called_once()
@ -125,11 +128,12 @@ def test_fmin_best_results(mocker, caplog):
} }
mocker.patch('freqtrade.optimize.hyperopt.MongoTrials', return_value=create_trials(mocker)) 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.load_data')
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result) 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) start(args)
exists = [ exists = [
@ -147,11 +151,12 @@ def test_fmin_best_results(mocker, caplog):
def test_fmin_throw_value_error(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.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.load_data')
mocker.patch('freqtrade.optimize.hyperopt.fmin', side_effect=ValueError()) 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) start(args)
exists = [ exists = [
@ -185,7 +190,8 @@ def test_resuming_previous_hyperopt_results_succeeds(mocker):
return_value={}) return_value={})
args = mocker.Mock(epochs=1, args = mocker.Mock(epochs=1,
config='config.json.example', config='config.json.example',
mongodb=False) mongodb=False,
timerange=None)
start(args) start(args)

View File

@ -174,3 +174,11 @@ def test_load_tickerdata_file():
assert not load_tickerdata_file(None, 'BTC_UNITEST', 7) assert not load_tickerdata_file(None, 'BTC_UNITEST', 7)
tickerdata = load_tickerdata_file(None, 'BTC_UNITEST', 1) tickerdata = load_tickerdata_file(None, 'BTC_UNITEST', 1)
assert _btc_unittest_length == len(tickerdata) assert _btc_unittest_length == len(tickerdata)
def test_tickerdata_to_dataframe():
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)
assert 100 == len(data['BTC_UNITEST'])

View File

@ -8,7 +8,7 @@ import pytest
from jsonschema import ValidationError from jsonschema import ValidationError
from freqtrade.misc import (common_args_parser, load_config, parse_args, from freqtrade.misc import (common_args_parser, load_config, parse_args,
throttle) throttle, parse_timerange)
def test_throttle(): def test_throttle():
@ -133,6 +133,13 @@ def test_parse_args_hyperopt_custom(mocker):
assert call_args.func is not None 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): def test_load_config(default_conf, mocker):
file_mock = mocker.patch('freqtrade.misc.open', mocker.mock_open( file_mock = mocker.patch('freqtrade.misc.open', mocker.mock_open(
read_data=json.dumps(default_conf) read_data=json.dumps(default_conf)