Merge pull request #852 from freqtrade/timeframe_class

Refactor Timeframe fake-type into NamedTuple
This commit is contained in:
Michael Egger 2018-06-07 16:19:44 +02:00 committed by GitHub
commit c75b70463b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 82 additions and 64 deletions

View File

@ -7,11 +7,23 @@ import argparse
import logging import logging
import re import re
import arrow import arrow
from typing import List, Tuple, Optional from typing import List, Optional, NamedTuple
from freqtrade import __version__, constants from freqtrade import __version__, constants
class TimeRange(NamedTuple):
"""
NamedTuple Defining timerange inputs.
[start/stop]type defines if [start/stop]ts shall be used.
if *type is none, don't use corresponding startvalue.
"""
starttype: Optional[str] = None
stoptype: Optional[str] = None
startts: int = 0
stopts: int = 0
class Arguments(object): class Arguments(object):
""" """
Arguments Class. Manage the arguments received by the cli Arguments Class. Manage the arguments received by the cli
@ -222,15 +234,14 @@ class Arguments(object):
self.hyperopt_options(hyperopt_cmd) self.hyperopt_options(hyperopt_cmd)
@staticmethod @staticmethod
def parse_timerange(text: Optional[str]) -> \ def parse_timerange(text: Optional[str]) -> TimeRange:
Optional[Tuple[Tuple, Optional[int], Optional[int]]]:
""" """
Parse the value of the argument --timerange to determine what is the range desired Parse the value of the argument --timerange to determine what is the range desired
:param text: value from --timerange :param text: value from --timerange
:return: Start and End range period :return: Start and End range period
""" """
if text is None: if text is None:
return None return TimeRange()
syntax = [(r'^-(\d{8})$', (None, 'date')), syntax = [(r'^-(\d{8})$', (None, 'date')),
(r'^(\d{8})-$', ('date', None)), (r'^(\d{8})-$', ('date', None)),
(r'^(\d{8})-(\d{8})$', ('date', 'date')), (r'^(\d{8})-(\d{8})$', ('date', 'date')),
@ -246,8 +257,8 @@ class Arguments(object):
if match: # Regex has matched if match: # Regex has matched
rvals = match.groups() rvals = match.groups()
index = 0 index = 0
start: Optional[int] = None start: int = 0
stop: Optional[int] = None stop: int = 0
if stype[0]: if stype[0]:
starts = rvals[index] starts = rvals[index]
if stype[0] == 'date': if stype[0] == 'date':
@ -263,7 +274,7 @@ class Arguments(object):
else arrow.get(stops, 'YYYYMMDD').timestamp else arrow.get(stops, 'YYYYMMDD').timestamp
else: else:
stop = int(stops) stop = int(stops)
return stype, start, stop return TimeRange(stype[0], stype[1], start, stop)
raise Exception('Incorrect syntax for timerange "%s"' % text) raise Exception('Incorrect syntax for timerange "%s"' % text)
def scripts_options(self) -> None: def scripts_options(self) -> None:

View File

@ -9,39 +9,40 @@ import arrow
from freqtrade import misc, constants from freqtrade import misc, constants
from freqtrade.exchange import get_ticker_history from freqtrade.exchange import get_ticker_history
from freqtrade.arguments import TimeRange
from user_data.hyperopt_conf import hyperopt_optimize_conf from user_data.hyperopt_conf import hyperopt_optimize_conf
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def trim_tickerlist(tickerlist: List[Dict], timerange: Tuple[Tuple, int, int]) -> List[Dict]: def trim_tickerlist(tickerlist: List[Dict], timerange: TimeRange) -> List[Dict]:
if not tickerlist: if not tickerlist:
return tickerlist return tickerlist
stype, start, stop = timerange
start_index = 0 start_index = 0
stop_index = len(tickerlist) stop_index = len(tickerlist)
if stype[0] == 'line': if timerange.starttype == 'line':
stop_index = start stop_index = timerange.startts
if stype[0] == 'index': if timerange.starttype == 'index':
start_index = start start_index = timerange.startts
elif stype[0] == 'date': elif timerange.starttype == 'date':
while start_index < len(tickerlist) and tickerlist[start_index][0] < start * 1000: while (start_index < len(tickerlist) and
tickerlist[start_index][0] < timerange.startts * 1000):
start_index += 1 start_index += 1
if stype[1] == 'line': if timerange.stoptype == 'line':
start_index = len(tickerlist) + stop start_index = len(tickerlist) + timerange.stopts
if stype[1] == 'index': if timerange.stoptype == 'index':
stop_index = stop stop_index = timerange.stopts
elif stype[1] == 'date': elif timerange.stoptype == 'date':
while stop_index > 0 and tickerlist[stop_index-1][0] > stop * 1000: while (stop_index > 0 and
tickerlist[stop_index-1][0] > timerange.stopts * 1000):
stop_index -= 1 stop_index -= 1
if start_index > stop_index: if start_index > stop_index:
raise ValueError(f'The timerange [{start},{stop}] is incorrect') raise ValueError(f'The timerange [{timerange.startts},{timerange.stopts}] is incorrect')
return tickerlist[start_index:stop_index] return tickerlist[start_index:stop_index]
@ -49,7 +50,7 @@ def trim_tickerlist(tickerlist: List[Dict], timerange: Tuple[Tuple, int, int]) -
def load_tickerdata_file( def load_tickerdata_file(
datadir: str, pair: str, datadir: str, pair: str,
ticker_interval: str, ticker_interval: str,
timerange: Optional[Tuple[Tuple, int, int]] = None) -> Optional[List[Dict]]: timerange: Optional[TimeRange] = None) -> Optional[List[Dict]]:
""" """
Load a pair from file, Load a pair from file,
:return dict OR empty if unsuccesful :return dict OR empty if unsuccesful
@ -84,7 +85,7 @@ def load_data(datadir: str,
ticker_interval: str, ticker_interval: str,
pairs: Optional[List[str]] = None, pairs: Optional[List[str]] = None,
refresh_pairs: Optional[bool] = False, refresh_pairs: Optional[bool] = False,
timerange: Optional[Tuple[Tuple, int, int]] = None) -> Dict[str, List]: timerange: TimeRange = TimeRange()) -> Dict[str, List]:
""" """
Loads ticker history data for the given parameters Loads ticker history data for the given parameters
:return: dict :return: dict
@ -124,7 +125,7 @@ def make_testdata_path(datadir: str) -> str:
def download_pairs(datadir, pairs: List[str], def download_pairs(datadir, pairs: List[str],
ticker_interval: str, ticker_interval: str,
timerange: Optional[Tuple[Tuple, int, int]] = None) -> bool: timerange: TimeRange = TimeRange()) -> bool:
"""For each pairs passed in parameters, download the ticker intervals""" """For each pairs passed in parameters, download the ticker intervals"""
for pair in pairs: for pair in pairs:
try: try:
@ -144,7 +145,7 @@ def download_pairs(datadir, pairs: List[str],
def load_cached_data_for_updating(filename: str, def load_cached_data_for_updating(filename: str,
tick_interval: str, tick_interval: str,
timerange: Optional[Tuple[Tuple, int, int]]) -> Tuple[ timerange: Optional[TimeRange]) -> Tuple[
List[Any], List[Any],
Optional[int]]: Optional[int]]:
""" """
@ -155,10 +156,10 @@ def load_cached_data_for_updating(filename: str,
# user sets timerange, so find the start time # user sets timerange, so find the start time
if timerange: if timerange:
if timerange[0][0] == 'date': if timerange.starttype == 'date':
since_ms = timerange[1] * 1000 since_ms = timerange.startts * 1000
elif timerange[0][1] == 'line': elif timerange.stoptype == 'line':
num_minutes = timerange[2] * constants.TICKER_INTERVAL_MINUTES[tick_interval] num_minutes = timerange.stopts * constants.TICKER_INTERVAL_MINUTES[tick_interval]
since_ms = arrow.utcnow().shift(minutes=num_minutes).timestamp * 1000 since_ms = arrow.utcnow().shift(minutes=num_minutes).timestamp * 1000
# read the cached file # read the cached file
@ -188,7 +189,7 @@ def load_cached_data_for_updating(filename: str,
def download_backtesting_testdata(datadir: str, def download_backtesting_testdata(datadir: str,
pair: str, pair: str,
tick_interval: str = '5m', tick_interval: str = '5m',
timerange: Optional[Tuple[Tuple, int, int]] = None) -> None: timerange: Optional[TimeRange] = None) -> None:
""" """
Download the latest ticker intervals from the exchange for the pairs passed in parameters Download the latest ticker intervals from the exchange for the pairs passed in parameters

View File

@ -221,7 +221,7 @@ class Backtesting(object):
timerange = Arguments.parse_timerange(None if self.config.get( timerange = Arguments.parse_timerange(None if self.config.get(
'timerange') is None else str(self.config.get('timerange'))) 'timerange') is None else str(self.config.get('timerange')))
data = optimize.load_data( # type: ignore # timerange will be refactored data = optimize.load_data(
self.config['datadir'], self.config['datadir'],
pairs=pairs, pairs=pairs,
ticker_interval=self.ticker_interval, ticker_interval=self.ticker_interval,

View File

@ -497,7 +497,7 @@ class Hyperopt(Backtesting):
def start(self) -> None: def start(self) -> None:
timerange = Arguments.parse_timerange(None if self.config.get( timerange = Arguments.parse_timerange(None if self.config.get(
'timerange') is None else str(self.config.get('timerange'))) 'timerange') is None else str(self.config.get('timerange')))
data = load_data( # type: ignore # timerange will be refactored data = load_data(
datadir=str(self.config.get('datadir')), datadir=str(self.config.get('datadir')),
pairs=self.config['exchange']['pair_whitelist'], pairs=self.config['exchange']['pair_whitelist'],
ticker_interval=self.ticker_interval, ticker_interval=self.ticker_interval,

View File

@ -13,7 +13,7 @@ from arrow import Arrow
from freqtrade import optimize from freqtrade import optimize
from freqtrade.analyze import Analyze from freqtrade.analyze import Analyze
from freqtrade.arguments import Arguments from freqtrade.arguments import Arguments, TimeRange
from freqtrade.optimize.backtesting import Backtesting, start, setup_configuration from freqtrade.optimize.backtesting import Backtesting, start, setup_configuration
from freqtrade.tests.conftest import log_has from freqtrade.tests.conftest import log_has
@ -30,7 +30,7 @@ def trim_dictlist(dict_list, num):
def load_data_test(what): def load_data_test(what):
timerange = ((None, 'line'), None, -100) timerange = TimeRange(None, 'line', 0, -100)
data = optimize.load_data(None, ticker_interval='1m', data = optimize.load_data(None, ticker_interval='1m',
pairs=['UNITTEST/BTC'], timerange=timerange) pairs=['UNITTEST/BTC'], timerange=timerange)
pair = data['UNITTEST/BTC'] pair = data['UNITTEST/BTC']
@ -311,7 +311,7 @@ def test_tickerdata_to_dataframe(default_conf, mocker) -> None:
Test Backtesting.tickerdata_to_dataframe() method Test Backtesting.tickerdata_to_dataframe() method
""" """
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
timerange = ((None, 'line'), None, -100) timerange = TimeRange(None, 'line', 0, -100)
tick = optimize.load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange) tick = optimize.load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange)
tickerlist = {'UNITTEST/BTC': tick} tickerlist = {'UNITTEST/BTC': tick}

View File

@ -11,6 +11,7 @@ from freqtrade.misc import file_dump_json
from freqtrade.optimize.__init__ import make_testdata_path, download_pairs, \ from freqtrade.optimize.__init__ import make_testdata_path, download_pairs, \
download_backtesting_testdata, load_tickerdata_file, trim_tickerlist, \ download_backtesting_testdata, load_tickerdata_file, trim_tickerlist, \
load_cached_data_for_updating load_cached_data_for_updating
from freqtrade.arguments import TimeRange
from freqtrade.tests.conftest import log_has from freqtrade.tests.conftest import log_has
# Change this if modifying UNITTEST/BTC testdatafile # Change this if modifying UNITTEST/BTC testdatafile
@ -176,7 +177,7 @@ def test_load_cached_data_for_updating(mocker) -> None:
# timeframe starts earlier than the cached data # timeframe starts earlier than the cached data
# should fully update data # should fully update data
timerange = (('date', None), test_data[0][0] / 1000 - 1, None) timerange = TimeRange('date', None, test_data[0][0] / 1000 - 1, 0)
data, start_ts = load_cached_data_for_updating(test_filename, data, start_ts = load_cached_data_for_updating(test_filename,
'1m', '1m',
timerange) timerange)
@ -187,13 +188,13 @@ def test_load_cached_data_for_updating(mocker) -> None:
num_lines = (test_data[-1][0] - test_data[1][0]) / 1000 / 60 + 120 num_lines = (test_data[-1][0] - test_data[1][0]) / 1000 / 60 + 120
data, start_ts = load_cached_data_for_updating(test_filename, data, start_ts = load_cached_data_for_updating(test_filename,
'1m', '1m',
((None, 'line'), None, -num_lines)) TimeRange(None, 'line', 0, -num_lines))
assert data == [] assert data == []
assert start_ts < test_data[0][0] - 1 assert start_ts < test_data[0][0] - 1
# timeframe starts in the center of the cached data # timeframe starts in the center of the cached data
# should return the chached data w/o the last item # should return the chached data w/o the last item
timerange = (('date', None), test_data[0][0] / 1000 + 1, None) timerange = TimeRange('date', None, test_data[0][0] / 1000 + 1, 0)
data, start_ts = load_cached_data_for_updating(test_filename, data, start_ts = load_cached_data_for_updating(test_filename,
'1m', '1m',
timerange) timerange)
@ -202,7 +203,7 @@ def test_load_cached_data_for_updating(mocker) -> None:
# same with 'line' timeframe # same with 'line' timeframe
num_lines = (test_data[-1][0] - test_data[1][0]) / 1000 / 60 + 30 num_lines = (test_data[-1][0] - test_data[1][0]) / 1000 / 60 + 30
timerange = ((None, 'line'), None, -num_lines) timerange = TimeRange(None, 'line', 0, -num_lines)
data, start_ts = load_cached_data_for_updating(test_filename, data, start_ts = load_cached_data_for_updating(test_filename,
'1m', '1m',
timerange) timerange)
@ -211,7 +212,7 @@ def test_load_cached_data_for_updating(mocker) -> None:
# timeframe starts after the chached data # timeframe starts after the chached data
# should return the chached data w/o the last item # should return the chached data w/o the last item
timerange = (('date', None), test_data[-1][0] / 1000 + 1, None) timerange = TimeRange('date', None, test_data[-1][0] / 1000 + 1, 0)
data, start_ts = load_cached_data_for_updating(test_filename, data, start_ts = load_cached_data_for_updating(test_filename,
'1m', '1m',
timerange) timerange)
@ -220,7 +221,7 @@ def test_load_cached_data_for_updating(mocker) -> None:
# same with 'line' timeframe # same with 'line' timeframe
num_lines = 30 num_lines = 30
timerange = ((None, 'line'), None, -num_lines) timerange = TimeRange(None, 'line', 0, -num_lines)
data, start_ts = load_cached_data_for_updating(test_filename, data, start_ts = load_cached_data_for_updating(test_filename,
'1m', '1m',
timerange) timerange)
@ -230,7 +231,7 @@ def test_load_cached_data_for_updating(mocker) -> None:
# no timeframe is set # no timeframe is set
# should return the chached data w/o the last item # should return the chached data w/o the last item
num_lines = 30 num_lines = 30
timerange = ((None, 'line'), None, -num_lines) timerange = TimeRange(None, 'line', 0, -num_lines)
data, start_ts = load_cached_data_for_updating(test_filename, data, start_ts = load_cached_data_for_updating(test_filename,
'1m', '1m',
timerange) timerange)
@ -239,7 +240,7 @@ def test_load_cached_data_for_updating(mocker) -> None:
# no datafile exist # no datafile exist
# should return timestamp start time # should return timestamp start time
timerange = (('date', None), now_ts - 10000, None) timerange = TimeRange('date', None, now_ts - 10000, 0)
data, start_ts = load_cached_data_for_updating(test_filename + 'unexist', data, start_ts = load_cached_data_for_updating(test_filename + 'unexist',
'1m', '1m',
timerange) timerange)
@ -248,7 +249,7 @@ def test_load_cached_data_for_updating(mocker) -> None:
# same with 'line' timeframe # same with 'line' timeframe
num_lines = 30 num_lines = 30
timerange = ((None, 'line'), None, -num_lines) timerange = TimeRange(None, 'line', 0, -num_lines)
data, start_ts = load_cached_data_for_updating(test_filename + 'unexist', data, start_ts = load_cached_data_for_updating(test_filename + 'unexist',
'1m', '1m',
timerange) timerange)
@ -343,7 +344,7 @@ def test_trim_tickerlist() -> None:
# Test the pattern ^(-\d+)$ # Test the pattern ^(-\d+)$
# This pattern uses the latest N elements # This pattern uses the latest N elements
timerange = ((None, 'line'), None, -5) timerange = TimeRange(None, 'line', 0, -5)
ticker = trim_tickerlist(ticker_list, timerange) ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker) ticker_len = len(ticker)
@ -353,7 +354,7 @@ def test_trim_tickerlist() -> None:
# Test the pattern ^(\d+)-$ # Test the pattern ^(\d+)-$
# This pattern keep X element from the end # This pattern keep X element from the end
timerange = (('line', None), 5, None) timerange = TimeRange('line', None, 5, 0)
ticker = trim_tickerlist(ticker_list, timerange) ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker) ticker_len = len(ticker)
@ -363,7 +364,7 @@ def test_trim_tickerlist() -> None:
# Test the pattern ^(\d+)-(\d+)$ # Test the pattern ^(\d+)-(\d+)$
# This pattern extract a window # This pattern extract a window
timerange = (('index', 'index'), 5, 10) timerange = TimeRange('index', 'index', 5, 10)
ticker = trim_tickerlist(ticker_list, timerange) ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker) ticker_len = len(ticker)
@ -374,7 +375,7 @@ def test_trim_tickerlist() -> None:
# Test the pattern ^(\d{8})-(\d{8})$ # Test the pattern ^(\d{8})-(\d{8})$
# This pattern extract a window between the dates # This pattern extract a window between the dates
timerange = (('date', 'date'), ticker_list[5][0] / 1000, ticker_list[10][0] / 1000 - 1) timerange = TimeRange('date', 'date', ticker_list[5][0] / 1000, ticker_list[10][0] / 1000 - 1)
ticker = trim_tickerlist(ticker_list, timerange) ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker) ticker_len = len(ticker)
@ -385,7 +386,7 @@ def test_trim_tickerlist() -> None:
# Test the pattern ^-(\d{8})$ # Test the pattern ^-(\d{8})$
# This pattern extracts elements from the start to the date # This pattern extracts elements from the start to the date
timerange = ((None, 'date'), None, ticker_list[10][0] / 1000 - 1) timerange = TimeRange(None, 'date', 0, ticker_list[10][0] / 1000 - 1)
ticker = trim_tickerlist(ticker_list, timerange) ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker) ticker_len = len(ticker)
@ -395,7 +396,7 @@ def test_trim_tickerlist() -> None:
# Test the pattern ^(\d{8})-$ # Test the pattern ^(\d{8})-$
# This pattern extracts elements from the date to now # This pattern extracts elements from the date to now
timerange = (('date', None), ticker_list[10][0] / 1000 - 1, None) timerange = TimeRange('date', None, ticker_list[10][0] / 1000 - 1, None)
ticker = trim_tickerlist(ticker_list, timerange) ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker) ticker_len = len(ticker)
@ -405,7 +406,7 @@ def test_trim_tickerlist() -> None:
# Test a wrong pattern # Test a wrong pattern
# This pattern must return the list unchanged # This pattern must return the list unchanged
timerange = ((None, None), None, 5) timerange = TimeRange(None, None, None, 5)
ticker = trim_tickerlist(ticker_list, timerange) ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker) ticker_len = len(ticker)

View File

@ -13,6 +13,7 @@ from pandas import DataFrame
from freqtrade.analyze import Analyze, SignalType from freqtrade.analyze import Analyze, SignalType
from freqtrade.optimize.__init__ import load_tickerdata_file from freqtrade.optimize.__init__ import load_tickerdata_file
from freqtrade.arguments import TimeRange
from freqtrade.tests.conftest import log_has from freqtrade.tests.conftest import log_has
# Avoid to reinit the same object again and again # Avoid to reinit the same object again and again
@ -183,7 +184,7 @@ def test_tickerdata_to_dataframe(default_conf) -> None:
""" """
analyze = Analyze(default_conf) analyze = Analyze(default_conf)
timerange = ((None, 'line'), None, -100) timerange = TimeRange(None, 'line', 0, -100)
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange) tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange)
tickerlist = {'UNITTEST/BTC': tick} tickerlist = {'UNITTEST/BTC': tick}
data = analyze.tickerdata_to_dataframe(tickerlist) data = analyze.tickerdata_to_dataframe(tickerlist)

View File

@ -9,7 +9,7 @@ import logging
import pytest import pytest
from freqtrade.arguments import Arguments from freqtrade.arguments import Arguments, TimeRange
def test_arguments_object() -> None: def test_arguments_object() -> None:
@ -107,20 +107,24 @@ def test_parse_args_dynamic_whitelist_invalid_values() -> None:
def test_parse_timerange_incorrect() -> None: def test_parse_timerange_incorrect() -> None:
assert ((None, 'line'), None, -200) == Arguments.parse_timerange('-200') assert TimeRange(None, 'line', 0, -200) == Arguments.parse_timerange('-200')
assert (('line', None), 200, None) == Arguments.parse_timerange('200-') assert TimeRange('line', None, 200, 0) == Arguments.parse_timerange('200-')
assert (('index', 'index'), 200, 500) == Arguments.parse_timerange('200-500') assert TimeRange('index', 'index', 200, 500) == Arguments.parse_timerange('200-500')
assert (('date', None), 1274486400, None) == Arguments.parse_timerange('20100522-') assert TimeRange('date', None, 1274486400, 0) == Arguments.parse_timerange('20100522-')
assert ((None, 'date'), None, 1274486400) == Arguments.parse_timerange('-20100522') assert TimeRange(None, 'date', 0, 1274486400) == Arguments.parse_timerange('-20100522')
timerange = Arguments.parse_timerange('20100522-20150730') timerange = Arguments.parse_timerange('20100522-20150730')
assert timerange == (('date', 'date'), 1274486400, 1438214400) assert timerange == TimeRange('date', 'date', 1274486400, 1438214400)
# Added test for unix timestamp - BTC genesis date # Added test for unix timestamp - BTC genesis date
assert (('date', None), 1231006505, None) == Arguments.parse_timerange('1231006505-') assert TimeRange('date', None, 1231006505, 0) == Arguments.parse_timerange('1231006505-')
assert ((None, 'date'), None, 1233360000) == Arguments.parse_timerange('-1233360000') assert TimeRange(None, 'date', 0, 1233360000) == Arguments.parse_timerange('-1233360000')
timerange = Arguments.parse_timerange('1231006505-1233360000') timerange = Arguments.parse_timerange('1231006505-1233360000')
assert timerange == (('date', 'date'), 1231006505, 1233360000) assert TimeRange('date', 'date', 1231006505, 1233360000) == timerange
# TODO: Find solution for the following case (passing timestamp in ms)
timerange = Arguments.parse_timerange('1231006505000-1233360000000')
assert TimeRange('date', 'date', 1231006505, 1233360000) != timerange
with pytest.raises(Exception, match=r'Incorrect syntax.*'): with pytest.raises(Exception, match=r'Incorrect syntax.*'):
Arguments.parse_timerange('-') Arguments.parse_timerange('-')