From 06fa07e73e998b208c8af7b2ea9d26f36d36fed6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 14 Aug 2019 10:07:14 +0200 Subject: [PATCH 1/3] Move parse_timerange to TimeRange class --- freqtrade/configuration/arguments.py | 58 +------------------------ freqtrade/configuration/timerange.py | 65 ++++++++++++++++++++++++++++ freqtrade/tests/test_arguments.py | 26 +---------- freqtrade/tests/test_timerange.py | 28 ++++++++++++ 4 files changed, 95 insertions(+), 82 deletions(-) create mode 100644 freqtrade/configuration/timerange.py create mode 100644 freqtrade/tests/test_timerange.py diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index dd5a4290e..c129a7e47 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -2,10 +2,8 @@ This module contains the argument manager class """ import argparse -import re -from typing import List, NamedTuple, Optional +from typing import List, Optional -import arrow from freqtrade.configuration.cli_options import AVAILABLE_CLI_OPTIONS from freqtrade import constants @@ -43,18 +41,6 @@ ARGS_PLOT_PROFIT = (ARGS_COMMON + ARGS_STRATEGY + ["pairs", "timerange", "export", "exportfilename", "db_url", "trade_source"]) -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): """ Arguments Class. Manage the arguments received by the cli @@ -133,45 +119,3 @@ class Arguments(object): ) list_exchanges_cmd.set_defaults(func=start_list_exchanges) self._build_args(optionlist=ARGS_LIST_EXCHANGES, parser=list_exchanges_cmd) - - @staticmethod - def parse_timerange(text: Optional[str]) -> TimeRange: - """ - Parse the value of the argument --timerange to determine what is the range desired - :param text: value from --timerange - :return: Start and End range period - """ - if text is None: - return TimeRange(None, None, 0, 0) - syntax = [(r'^-(\d{8})$', (None, 'date')), - (r'^(\d{8})-$', ('date', None)), - (r'^(\d{8})-(\d{8})$', ('date', 'date')), - (r'^-(\d{10})$', (None, 'date')), - (r'^(\d{10})-$', ('date', None)), - (r'^(\d{10})-(\d{10})$', ('date', 'date')), - (r'^(-\d+)$', (None, 'line')), - (r'^(\d+)-$', ('line', None)), - (r'^(\d+)-(\d+)$', ('index', 'index'))] - for rex, stype in syntax: - # Apply the regular expression to text - match = re.match(rex, text) - if match: # Regex has matched - rvals = match.groups() - index = 0 - start: int = 0 - stop: int = 0 - if stype[0]: - starts = rvals[index] - if stype[0] == 'date' and len(starts) == 8: - start = arrow.get(starts, 'YYYYMMDD').timestamp - else: - start = int(starts) - index += 1 - if stype[1]: - stops = rvals[index] - if stype[1] == 'date' and len(stops) == 8: - stop = arrow.get(stops, 'YYYYMMDD').timestamp - else: - stop = int(stops) - return TimeRange(stype[0], stype[1], start, stop) - raise Exception('Incorrect syntax for timerange "%s"' % text) diff --git a/freqtrade/configuration/timerange.py b/freqtrade/configuration/timerange.py new file mode 100644 index 000000000..b44704682 --- /dev/null +++ b/freqtrade/configuration/timerange.py @@ -0,0 +1,65 @@ +""" +This module contains the argument manager class +""" +import re +from typing import Optional + +import arrow + + +class TimeRange(): + """ + object defining timerange inputs. + [start/stop]type defines if [start/stop]ts shall be used. + if *type is None, don't use corresponding startvalue. + """ + + def __init__(self, starttype: Optional[str], stoptype: Optional[str], + startts: int, stopts: int): + + self.starttype: Optional[str] = starttype + self.stoptype: Optional[str] = stoptype + self.startts: int = startts + self.stopts: int = stopts + + @staticmethod + def parse_timerange(text: Optional[str]): + """ + Parse the value of the argument --timerange to determine what is the range desired + :param text: value from --timerange + :return: Start and End range period + """ + if text is None: + return TimeRange(None, None, 0, 0) + syntax = [(r'^-(\d{8})$', (None, 'date')), + (r'^(\d{8})-$', ('date', None)), + (r'^(\d{8})-(\d{8})$', ('date', 'date')), + (r'^-(\d{10})$', (None, 'date')), + (r'^(\d{10})-$', ('date', None)), + (r'^(\d{10})-(\d{10})$', ('date', 'date')), + (r'^(-\d+)$', (None, 'line')), + (r'^(\d+)-$', ('line', None)), + (r'^(\d+)-(\d+)$', ('index', 'index'))] + for rex, stype in syntax: + # Apply the regular expression to text + match = re.match(rex, text) + if match: # Regex has matched + rvals = match.groups() + index = 0 + start: int = 0 + stop: int = 0 + if stype[0]: + starts = rvals[index] + if stype[0] == 'date' and len(starts) == 8: + start = arrow.get(starts, 'YYYYMMDD').timestamp + else: + start = int(starts) + index += 1 + if stype[1]: + stops = rvals[index] + if stype[1] == 'date' and len(stops) == 8: + stop = arrow.get(stops, 'YYYYMMDD').timestamp + else: + stop = int(stops) + return TimeRange(stype[0], stype[1], start, stop) + raise Exception('Incorrect syntax for timerange "%s"' % text) diff --git a/freqtrade/tests/test_arguments.py b/freqtrade/tests/test_arguments.py index bf744f72b..2cb7ff6d7 100644 --- a/freqtrade/tests/test_arguments.py +++ b/freqtrade/tests/test_arguments.py @@ -3,7 +3,7 @@ import argparse import pytest -from freqtrade.configuration import Arguments, TimeRange +from freqtrade.configuration import Arguments from freqtrade.configuration.arguments import ARGS_DOWNLOADER, ARGS_PLOT_DATAFRAME from freqtrade.configuration.cli_options import check_int_positive @@ -86,30 +86,6 @@ def test_parse_args_strategy_path_invalid() -> None: Arguments(['--strategy-path'], '').get_parsed_arg() -def test_parse_timerange_incorrect() -> None: - assert TimeRange(None, 'line', 0, -200) == Arguments.parse_timerange('-200') - assert TimeRange('line', None, 200, 0) == Arguments.parse_timerange('200-') - assert TimeRange('index', 'index', 200, 500) == Arguments.parse_timerange('200-500') - - assert TimeRange('date', None, 1274486400, 0) == Arguments.parse_timerange('20100522-') - assert TimeRange(None, 'date', 0, 1274486400) == Arguments.parse_timerange('-20100522') - timerange = Arguments.parse_timerange('20100522-20150730') - assert timerange == TimeRange('date', 'date', 1274486400, 1438214400) - - # Added test for unix timestamp - BTC genesis date - assert TimeRange('date', None, 1231006505, 0) == Arguments.parse_timerange('1231006505-') - assert TimeRange(None, 'date', 0, 1233360000) == Arguments.parse_timerange('-1233360000') - timerange = Arguments.parse_timerange('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.*'): - Arguments.parse_timerange('-') - - def test_parse_args_backtesting_invalid() -> None: with pytest.raises(SystemExit, match=r'2'): Arguments(['backtesting --ticker-interval'], '').get_parsed_arg() diff --git a/freqtrade/tests/test_timerange.py b/freqtrade/tests/test_timerange.py new file mode 100644 index 000000000..6599472fb --- /dev/null +++ b/freqtrade/tests/test_timerange.py @@ -0,0 +1,28 @@ +# pragma pylint: disable=missing-docstring, C0103 +import pytest + +from freqtrade.configuration import TimeRange + + +def test_parse_timerange_incorrect() -> None: + assert TimeRange(None, 'line', 0, -200) == TimeRange.parse_timerange('-200') + assert TimeRange('line', None, 200, 0) == TimeRange.parse_timerange('200-') + assert TimeRange('index', 'index', 200, 500) == TimeRange.parse_timerange('200-500') + + assert TimeRange('date', None, 1274486400, 0) == TimeRange.parse_timerange('20100522-') + assert TimeRange(None, 'date', 0, 1274486400) == TimeRange.parse_timerange('-20100522') + timerange = TimeRange.parse_timerange('20100522-20150730') + assert timerange == TimeRange('date', 'date', 1274486400, 1438214400) + + # Added test for unix timestamp - BTC genesis date + assert TimeRange('date', None, 1231006505, 0) == TimeRange.parse_timerange('1231006505-') + assert TimeRange(None, 'date', 0, 1233360000) == TimeRange.parse_timerange('-1233360000') + timerange = TimeRange.parse_timerange('1231006505-1233360000') + assert TimeRange('date', 'date', 1231006505, 1233360000) == timerange + + # TODO: Find solution for the following case (passing timestamp in ms) + timerange = TimeRange.parse_timerange('1231006505000-1233360000000') + assert TimeRange('date', 'date', 1231006505, 1233360000) != timerange + + with pytest.raises(Exception, match=r'Incorrect syntax.*'): + TimeRange.parse_timerange('-') From 51c3a31bb58c01696634360b5547fa09b3fc0341 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 14 Aug 2019 10:07:32 +0200 Subject: [PATCH 2/3] Correct imports and calls to parse_timerange --- freqtrade/configuration/__init__.py | 3 ++- freqtrade/edge/__init__.py | 4 ++-- freqtrade/optimize/backtesting.py | 4 ++-- freqtrade/optimize/edge_cli.py | 4 ++-- freqtrade/optimize/hyperopt.py | 4 ++-- freqtrade/plot/plotting.py | 4 ++-- freqtrade/tests/data/test_btanalysis.py | 4 ++-- freqtrade/tests/test_plotting.py | 6 +++--- scripts/download_backtest_data.py | 2 +- 9 files changed, 18 insertions(+), 17 deletions(-) diff --git a/freqtrade/configuration/__init__.py b/freqtrade/configuration/__init__.py index 548b508a7..7b476d173 100644 --- a/freqtrade/configuration/__init__.py +++ b/freqtrade/configuration/__init__.py @@ -1,2 +1,3 @@ -from freqtrade.configuration.arguments import Arguments, TimeRange # noqa: F401 +from freqtrade.configuration.arguments import Arguments # noqa: F401 +from freqtrade.configuration.timerange import TimeRange # noqa: F401 from freqtrade.configuration.configuration import Configuration # noqa: F401 diff --git a/freqtrade/edge/__init__.py b/freqtrade/edge/__init__.py index 7085663d6..2d3097ec4 100644 --- a/freqtrade/edge/__init__.py +++ b/freqtrade/edge/__init__.py @@ -10,7 +10,7 @@ import utils_find_1st as utf1st from pandas import DataFrame from freqtrade import constants, OperationalException -from freqtrade.configuration import Arguments, TimeRange +from freqtrade.configuration import TimeRange from freqtrade.data import history from freqtrade.strategy.interface import SellType @@ -75,7 +75,7 @@ class Edge(): self._stoploss_range_step ) - self._timerange: TimeRange = Arguments.parse_timerange("%s-" % arrow.now().shift( + self._timerange: TimeRange = TimeRange.parse_timerange("%s-" % arrow.now().shift( days=-1 * self._since_number_of_days).format('YYYYMMDD')) self.fee = self.exchange.get_fee() diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 252175269..8f40a6582 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -12,7 +12,7 @@ from typing import Any, Dict, List, NamedTuple, Optional from pandas import DataFrame from freqtrade import OperationalException -from freqtrade.configuration import Arguments +from freqtrade.configuration import TimeRange from freqtrade.data import history from freqtrade.data.dataprovider import DataProvider from freqtrade.exchange import timeframe_to_minutes @@ -404,7 +404,7 @@ class Backtesting(object): logger.info('Using stake_currency: %s ...', self.config['stake_currency']) logger.info('Using stake_amount: %s ...', self.config['stake_amount']) - timerange = Arguments.parse_timerange(None if self.config.get( + timerange = TimeRange.parse_timerange(None if self.config.get( 'timerange') is None else str(self.config.get('timerange'))) data = history.load_data( datadir=Path(self.config['datadir']) if self.config.get('datadir') else None, diff --git a/freqtrade/optimize/edge_cli.py b/freqtrade/optimize/edge_cli.py index 8d1fa381b..7e0d60843 100644 --- a/freqtrade/optimize/edge_cli.py +++ b/freqtrade/optimize/edge_cli.py @@ -9,7 +9,7 @@ from tabulate import tabulate from freqtrade import constants from freqtrade.edge import Edge -from freqtrade.configuration import Arguments +from freqtrade.configuration import TimeRange from freqtrade.exchange import Exchange from freqtrade.resolvers import StrategyResolver @@ -41,7 +41,7 @@ class EdgeCli(object): self.edge = Edge(config, self.exchange, self.strategy) self.edge._refresh_pairs = self.config.get('refresh_pairs', False) - self.timerange = Arguments.parse_timerange(None if self.config.get( + self.timerange = TimeRange.parse_timerange(None if self.config.get( 'timerange') is None else str(self.config.get('timerange'))) self.edge._timerange = self.timerange diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 550f13e28..772b4a10f 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -20,7 +20,7 @@ from pandas import DataFrame from skopt import Optimizer from skopt.space import Dimension -from freqtrade.configuration import Arguments +from freqtrade.configuration import TimeRange from freqtrade.data.history import load_data, get_timeframe from freqtrade.optimize.backtesting import Backtesting # Import IHyperOptLoss to allow users import from this file @@ -310,7 +310,7 @@ class Hyperopt(Backtesting): ) def start(self) -> None: - timerange = Arguments.parse_timerange(None if self.config.get( + timerange = TimeRange.parse_timerange(None if self.config.get( 'timerange') is None else str(self.config.get('timerange'))) data = load_data( datadir=Path(self.config['datadir']) if self.config.get('datadir') else None, diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index d03d3ae53..947b3003c 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -4,7 +4,7 @@ from typing import Dict, List, Optional import pandas as pd -from freqtrade.configuration import Arguments +from freqtrade.configuration import TimeRange from freqtrade.data import history from freqtrade.data.btanalysis import (combine_tickers_with_mean, create_cum_profit, load_trades) @@ -42,7 +42,7 @@ def init_plotscript(config): pairs = config["exchange"]["pair_whitelist"] # Set timerange to use - timerange = Arguments.parse_timerange(config.get("timerange")) + timerange = TimeRange.parse_timerange(config.get("timerange")) tickers = history.load_data( datadir=Path(str(config.get("datadir"))), diff --git a/freqtrade/tests/data/test_btanalysis.py b/freqtrade/tests/data/test_btanalysis.py index bad8db66f..cf8cae566 100644 --- a/freqtrade/tests/data/test_btanalysis.py +++ b/freqtrade/tests/data/test_btanalysis.py @@ -4,7 +4,7 @@ import pytest from arrow import Arrow from pandas import DataFrame, to_datetime -from freqtrade.configuration import Arguments, TimeRange +from freqtrade.configuration import TimeRange from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, combine_tickers_with_mean, create_cum_profit, @@ -121,7 +121,7 @@ def test_combine_tickers_with_mean(): def test_create_cum_profit(): filename = make_testdata_path(None) / "backtest-result_test.json" bt_data = load_backtest_data(filename) - timerange = Arguments.parse_timerange("20180110-20180112") + timerange = TimeRange.parse_timerange("20180110-20180112") df = load_pair_history(pair="POWR/BTC", ticker_interval='5m', datadir=None, timerange=timerange) diff --git a/freqtrade/tests/test_plotting.py b/freqtrade/tests/test_plotting.py index bfdd72215..cd72160f8 100644 --- a/freqtrade/tests/test_plotting.py +++ b/freqtrade/tests/test_plotting.py @@ -5,7 +5,7 @@ from unittest.mock import MagicMock import plotly.graph_objects as go from plotly.subplots import make_subplots -from freqtrade.configuration import Arguments, TimeRange +from freqtrade.configuration import TimeRange from freqtrade.data import history from freqtrade.data.btanalysis import create_cum_profit, load_backtest_data from freqtrade.plot.plotting import (add_indicators, add_profit, @@ -222,7 +222,7 @@ def test_generate_plot_file(mocker, caplog): def test_add_profit(): filename = history.make_testdata_path(None) / "backtest-result_test.json" bt_data = load_backtest_data(filename) - timerange = Arguments.parse_timerange("20180110-20180112") + timerange = TimeRange.parse_timerange("20180110-20180112") df = history.load_pair_history(pair="POWR/BTC", ticker_interval='5m', datadir=None, timerange=timerange) @@ -242,7 +242,7 @@ def test_add_profit(): def test_generate_profit_graph(): filename = history.make_testdata_path(None) / "backtest-result_test.json" trades = load_backtest_data(filename) - timerange = Arguments.parse_timerange("20180110-20180112") + timerange = TimeRange.parse_timerange("20180110-20180112") pairs = ["POWR/BTC", "XLM/BTC"] tickers = history.load_data(datadir=None, diff --git a/scripts/download_backtest_data.py b/scripts/download_backtest_data.py index 580592294..f77ad7422 100755 --- a/scripts/download_backtest_data.py +++ b/scripts/download_backtest_data.py @@ -105,7 +105,7 @@ if not pairs or args.pairs_file: timerange = TimeRange() if args.days: time_since = arrow.utcnow().shift(days=-args.days).strftime("%Y%m%d") - timerange = arguments.parse_timerange(f'{time_since}-') + timerange = TimeRange.parse_timerange(f'{time_since}-') logger.info(f'About to download pairs: {pairs}, intervals: {timeframes} to {dl_path}') From 096a6426dbf1142c5443371f9569e4f51870d148 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 14 Aug 2019 10:22:54 +0200 Subject: [PATCH 3/3] Override equality operator --- freqtrade/configuration/timerange.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/freqtrade/configuration/timerange.py b/freqtrade/configuration/timerange.py index b44704682..f980b71ea 100644 --- a/freqtrade/configuration/timerange.py +++ b/freqtrade/configuration/timerange.py @@ -14,14 +14,19 @@ class TimeRange(): if *type is None, don't use corresponding startvalue. """ - def __init__(self, starttype: Optional[str], stoptype: Optional[str], - startts: int, stopts: int): + def __init__(self, starttype: Optional[str] = None, stoptype: Optional[str] = None, + startts: int = 0, stopts: int = 0): self.starttype: Optional[str] = starttype self.stoptype: Optional[str] = stoptype self.startts: int = startts self.stopts: int = stopts + def __eq__(self, other): + """Override the default Equals behavior""" + return (self.starttype == other.starttype and self.stoptype == other.stoptype + and self.startts == other.startts and self.stopts == other.stopts) + @staticmethod def parse_timerange(text: Optional[str]): """