diff --git a/config_full.json.example b/config_full.json.example index d0f9d1260..5362ee943 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -4,7 +4,7 @@ "stake_amount": 0.05, "fiat_display_currency": "USD", "dry_run": false, - "ticker_interval": 5, + "ticker_interval": "5m", "minimal_roi": { "40": 0.0, "30": 0.01, diff --git a/docs/backtesting.md b/docs/backtesting.md index b8e4b5108..2bcaae40c 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -33,7 +33,7 @@ python3 ./freqtrade/main.py backtesting --realistic-simulation **With 1 min tickers** ```bash -python3 ./freqtrade/main.py backtesting --realistic-simulation --ticker-interval 1 +python3 ./freqtrade/main.py backtesting --realistic-simulation --ticker-interval 1m ``` **Reload your testdata files** diff --git a/docs/bot-usage.md b/docs/bot-usage.md index cf3258465..76e693592 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -118,7 +118,7 @@ optional arguments: -h, --help show this help message and exit -l, --live using live data -i INT, --ticker-interval INT - specify ticker interval in minutes (default: 5) + specify ticker interval (default: '5m') --realistic-simulation uses max_open_trades from config to simulate real world limitations diff --git a/docs/configuration.md b/docs/configuration.md index 5e3b15925..2beab348d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -17,7 +17,7 @@ The table below will list all configuration parameters. | `max_open_trades` | 3 | Yes | Number of trades open your bot will have. | `stake_currency` | BTC | Yes | Crypto-currency used for trading. | `stake_amount` | 0.05 | Yes | Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. -| `ticker_interval` | [1, 5, 30, 60, 1440] | No | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Defaut is 5 minutes +| `ticker_interval` | [1m, 5m, 30m, 1h, 1d] | No | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Default is 5 minutes | `fiat_display_currency` | USD | Yes | Fiat currency used to show your profits. More information below. | `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode. | `minimal_roi` | See below | No | Set the threshold in percent the bot will use to sell a trade. More information below. If set, this parameter will override `minimal_roi` from your strategy file. diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index e6f723dd9..07989b650 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -12,6 +12,7 @@ from freqtrade.exchange import get_ticker_history from freqtrade.logger import Logger from freqtrade.persistence import Trade from freqtrade.strategy.strategy import Strategy +from freqtrade.constants import Constants class SignalType(Enum): @@ -81,7 +82,7 @@ class Analyze(object): """ return self.strategy.populate_sell_trend(dataframe=dataframe) - def get_ticker_interval(self) -> int: + def get_ticker_interval(self) -> str: """ Return ticker interval to use :return: Ticker interval value to use @@ -100,7 +101,7 @@ class Analyze(object): dataframe = self.populate_sell_trend(dataframe) return dataframe - def get_signal(self, pair: str, interval: int) -> Tuple[bool, bool]: + def get_signal(self, pair: str, interval: str) -> Tuple[bool, bool]: """ Calculates current signal based several technical analysis indicators :param pair: pair in format ANT/BTC @@ -137,7 +138,8 @@ class Analyze(object): # Check if dataframe is out of date signal_date = arrow.get(latest['date']) - if signal_date < arrow.utcnow() - timedelta(minutes=(interval + 5)): + interval_minutes = Constants.TICKER_INTERVAL_MINUTES[interval] + if signal_date < arrow.utcnow() - timedelta(minutes=(interval_minutes + 5)): self.logger.warning( 'Outdated history for pair %s. Last tick is %s minutes old', pair, diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index c69135117..cf17ed096 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -135,10 +135,9 @@ class Arguments(object): def optimizer_shared_options(parser: argparse.ArgumentParser) -> None: parser.add_argument( '-i', '--ticker-interval', - help='specify ticker interval in minutes (1, 5, 30, 60, 1440)', + help='specify ticker interval (1m, 5m, 30m, 1h, 1d)', dest='ticker_interval', - type=int, - metavar='INT', + type=str, ) parser.add_argument( '--realistic-simulation', diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index 6df73e6a0..e08f02baf 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -117,7 +117,7 @@ class Configuration(object): if 'ticker_interval' in self.args and self.args.ticker_interval: config.update({'ticker_interval': self.args.ticker_interval}) self.logger.info('Parameter -i/--ticker-interval detected ...') - self.logger.info('Using ticker_interval: %d ...', config.get('ticker_interval')) + self.logger.info('Using ticker_interval: %s ...', config.get('ticker_interval')) # If -l/--live is used we add it to the configuration if 'live' in self.args and self.args.live: diff --git a/freqtrade/constants.py b/freqtrade/constants.py index af48ac4f9..802785891 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -16,12 +16,26 @@ class Constants(object): RETRY_TIMEOUT = 30 # sec DEFAULT_STRATEGY = 'default_strategy' + TICKER_INTERVAL_MINUTES = { + '1m': 1, + '5m': 5, + '15m': 15, + '30m': 30, + '1h': 60, + '2h': 120, + '4h': 240, + '6h': 360, + '12h': 720, + '1d': 1440, + '1w': 10080, + } + # Required json-schema for user specified config CONF_SCHEMA = { 'type': 'object', 'properties': { 'max_open_trades': {'type': 'integer', 'minimum': 1}, - 'ticker_interval': {'type': 'integer', 'enum': [1, 5, 30, 60, 1440]}, + 'ticker_interval': {'type': 'string', 'enum': list(TICKER_INTERVAL_MINUTES.keys())}, 'stake_currency': {'type': 'string', 'enum': ['BTC', 'ETH', 'USDT']}, 'stake_amount': {'type': 'number', 'minimum': 0.0005}, 'fiat_display_currency': {'type': 'string', 'enum': ['AUD', 'BRL', 'CAD', 'CHF', diff --git a/freqtrade/optimize/__init__.py b/freqtrade/optimize/__init__.py index eb75d7c70..8553eec5d 100644 --- a/freqtrade/optimize/__init__.py +++ b/freqtrade/optimize/__init__.py @@ -27,7 +27,7 @@ def trim_tickerlist(tickerlist: List[Dict], timerange: Tuple[Tuple, int, int]) - def load_tickerdata_file( datadir: str, pair: str, - ticker_interval: int, + ticker_interval: str, timerange: Optional[Tuple[Tuple, int, int]] = None) -> Optional[List[Dict]]: """ Load a pair from file, @@ -59,7 +59,8 @@ def load_tickerdata_file( return pairdata -def load_data(datadir: str, ticker_interval: int, +def load_data(datadir: str, + ticker_interval: str, pairs: Optional[List[str]] = None, refresh_pairs: Optional[bool] = False, timerange: Optional[Tuple[Tuple, int, int]] = None) -> Dict[str, List]: @@ -96,14 +97,14 @@ def make_testdata_path(datadir: str) -> str: ) -def download_pairs(datadir, pairs: List[str], ticker_interval: int) -> bool: +def download_pairs(datadir, pairs: List[str], ticker_interval: str) -> bool: """For each pairs passed in parameters, download the ticker intervals""" for pair in pairs: try: download_backtesting_testdata(datadir, pair=pair, interval=ticker_interval) except BaseException: logger.info( - 'Failed to download the pair: "%s", Interval: %s min', + 'Failed to download the pair: "%s", Interval: %s', pair, ticker_interval ) @@ -112,7 +113,7 @@ def download_pairs(datadir, pairs: List[str], ticker_interval: int) -> bool: # FIX: 20180110, suggest rename interval to tick_interval -def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) -> bool: +def download_backtesting_testdata(datadir: str, pair: str, interval: str = '5m') -> bool: """ Download the latest 1 and 5 ticker intervals from Bittrex for the pairs passed in parameters Based on @Rybolov work: https://github.com/rybolov/freqtrade-data @@ -122,7 +123,7 @@ def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) -> path = make_testdata_path(datadir) logger.info( - 'Download the pair: "%s", Interval: %s min', + 'Download the pair: "%s", Interval: %s', pair, interval ) @@ -143,7 +144,7 @@ def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) -> logger.debug("Current Start: None") logger.debug("Current End: None") - new_data = get_ticker_history(pair=pair, tick_interval=int(interval)) + new_data = get_ticker_history(pair=pair, tick_interval=interval) for row in new_data: if row not in data: data.append(row) diff --git a/freqtrade/strategy/strategy.py b/freqtrade/strategy/strategy.py index d7a89d1de..c3eeea017 100644 --- a/freqtrade/strategy/strategy.py +++ b/freqtrade/strategy/strategy.py @@ -65,7 +65,7 @@ class Strategy(object): # Optimal stoploss designed for the strategy self.stoploss = float(self.custom_strategy.stoploss) - self.ticker_interval = int(self.custom_strategy.ticker_interval) + self.ticker_interval = self.custom_strategy.ticker_interval def _load_strategy(self, strategy_name: str) -> None: """ diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 898babf44..f07503271 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -128,31 +128,32 @@ def ticker_sell_down(): @pytest.fixture def health(): - return MagicMock(return_value={ - "ETH/BTC": { - 'base': 'ETH', - 'active': True, - 'LastChecked': '2017-11-13T20:15:00.00', - 'Notice': None - }, - "TRST/BTC": { - 'base': 'TRST', - 'active': True, - 'LastChecked': '2017-11-13T20:15:00.00', - 'Notice': None - }, - "SWT/BTC": { - 'base': 'SWT', - 'active': True, - 'LastChecked': '2017-11-13T20:15:00.00', - 'Notice': None - }, - "BCC/BTC": { - 'base': 'BCC', - 'active': False, - 'LastChecked': '2017-11-13T20:15:00.00', - 'Notice': None - }}) + return MagicMock(return_value=[{ + 'Currency': 'BTC', + 'IsActive': True, + 'LastChecked': '2017-11-13T20:15:00.00', + 'Notice': None + }, { + 'Currency': 'ETH', + 'IsActive': True, + 'LastChecked': '2017-11-13T20:15:00.00', + 'Notice': None + }, { + 'Currency': 'TRST', + 'IsActive': True, + 'LastChecked': '2017-11-13T20:15:00.00', + 'Notice': None + }, { + 'Currency': 'SWT', + 'IsActive': True, + 'LastChecked': '2017-11-13T20:15:00.00', + 'Notice': None + }, { + 'Currency': 'BCC', + 'IsActive': False, + 'LastChecked': '2017-11-13T20:15:00.00', + 'Notice': None + }]) @pytest.fixture diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index dd700883f..dba40b2f5 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -416,7 +416,7 @@ def test_populate_indicators() -> None: """ Test Hyperopt.populate_indicators() """ - tick = load_tickerdata_file(None, 'UNITTEST/BTC', 1) + tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m') tickerlist = {'UNITTEST/BTC': tick} dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist) dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC']) @@ -431,7 +431,7 @@ def test_buy_strategy_generator() -> None: """ Test Hyperopt.buy_strategy_generator() """ - tick = load_tickerdata_file(None, 'UNITTEST/BTC', 1) + tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m') tickerlist = {'UNITTEST/BTC': tick} dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist) dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC']) diff --git a/freqtrade/tests/optimize/test_optimize.py b/freqtrade/tests/optimize/test_optimize.py index 45a5f4e2a..befb7fa23 100644 --- a/freqtrade/tests/optimize/test_optimize.py +++ b/freqtrade/tests/optimize/test_optimize.py @@ -167,7 +167,7 @@ def test_download_backtesting_testdata(ticker_history, mocker) -> None: # Download a 1 min ticker file file1 = 'freqtrade/tests/testdata/XEL_BTC-1m.json' _backup_file(file1) - download_backtesting_testdata(None, pair="XEL/BTC", interval=1) + download_backtesting_testdata(None, pair="XEL/BTC", interval='1m') assert os.path.isfile(file1) is True _clean_test_file(file1) @@ -175,7 +175,7 @@ def test_download_backtesting_testdata(ticker_history, mocker) -> None: file2 = 'freqtrade/tests/testdata/STORJ_BTC-5m.json' _backup_file(file2) - download_backtesting_testdata(None, pair="STORJ/BTC", interval=5) + download_backtesting_testdata(None, pair="STORJ/BTC", interval='5m') assert os.path.isfile(file2) is True _clean_test_file(file2) @@ -184,8 +184,8 @@ def test_download_backtesting_testdata2(mocker) -> None: tick = [{'T': 'bar'}, {'T': 'foo'}] mocker.patch('freqtrade.misc.file_dump_json', return_value=None) mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=tick) - assert download_backtesting_testdata(None, pair="UNITTEST/BTC", interval=1) - assert download_backtesting_testdata(None, pair="UNITTEST/BTC", interval=3) + assert download_backtesting_testdata(None, pair="UNITTEST/BTC", interval='1m') + assert download_backtesting_testdata(None, pair="UNITTEST/BTC", interval='3m') def test_load_tickerdata_file() -> None: diff --git a/freqtrade/tests/strategy/test_default_strategy.py b/freqtrade/tests/strategy/test_default_strategy.py index f9ce36639..00ddc34ea 100644 --- a/freqtrade/tests/strategy/test_default_strategy.py +++ b/freqtrade/tests/strategy/test_default_strategy.py @@ -9,7 +9,7 @@ from freqtrade.strategy.default_strategy import DefaultStrategy, class_name @pytest.fixture def result(): - with open('freqtrade/tests/testdata/ETH_BTC-1.json') as data_file: + with open('freqtrade/tests/testdata/ETH_BTC-1m.json') as data_file: return Analyze.parse_ticker_dataframe(json.load(data_file)) diff --git a/freqtrade/tests/test_analyze.py b/freqtrade/tests/test_analyze.py index f76d19896..e1a288bab 100644 --- a/freqtrade/tests/test_analyze.py +++ b/freqtrade/tests/test_analyze.py @@ -74,7 +74,7 @@ def test_returns_latest_buy_signal(mocker): return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}]) ) ) - assert _ANALYZE.get_signal('ETH/BTC', 5) == (True, False) + assert _ANALYZE.get_signal('ETH/BTC', '5m') == (True, False) mocker.patch.multiple( 'freqtrade.analyze.Analyze', @@ -82,7 +82,7 @@ def test_returns_latest_buy_signal(mocker): return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}]) ) ) - assert _ANALYZE.get_signal('ETH/BTC', 5) == (False, True) + assert _ANALYZE.get_signal('ETH/BTC', '5m') == (False, True) def test_returns_latest_sell_signal(mocker): @@ -94,7 +94,7 @@ def test_returns_latest_sell_signal(mocker): ) ) - assert _ANALYZE.get_signal('ETH/BTC', 5) == (False, True) + assert _ANALYZE.get_signal('ETH/BTC', '5m') == (False, True) mocker.patch.multiple( 'freqtrade.analyze.Analyze', @@ -102,13 +102,13 @@ def test_returns_latest_sell_signal(mocker): return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}]) ) ) - assert _ANALYZE.get_signal('ETH/BTC', 5) == (True, False) + assert _ANALYZE.get_signal('ETH/BTC', '5m') == (True, False) def test_get_signal_empty(default_conf, mocker, caplog): caplog.set_level(logging.INFO) mocker.patch('freqtrade.analyze.get_ticker_history', return_value=None) - assert (False, False) == _ANALYZE.get_signal('foo', int(default_conf['ticker_interval'])) + assert (False, False) == _ANALYZE.get_signal('foo', default_conf['ticker_interval']) assert log_has('Empty ticker history for pair foo', caplog.record_tuples) @@ -121,7 +121,7 @@ def test_get_signal_exception_valueerror(default_conf, mocker, caplog): side_effect=ValueError('xyz') ) ) - assert (False, False) == _ANALYZE.get_signal('foo', int(default_conf['ticker_interval'])) + assert (False, False) == _ANALYZE.get_signal('foo', default_conf['ticker_interval']) assert log_has('Unable to analyze ticker for pair foo: xyz', caplog.record_tuples) @@ -134,7 +134,7 @@ def test_get_signal_empty_dataframe(default_conf, mocker, caplog): return_value=DataFrame([]) ) ) - assert (False, False) == _ANALYZE.get_signal('xyz', int(default_conf['ticker_interval'])) + assert (False, False) == _ANALYZE.get_signal('xyz', default_conf['ticker_interval']) assert log_has('Empty dataframe for pair xyz', caplog.record_tuples) @@ -150,7 +150,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog): return_value=DataFrame(ticks) ) ) - assert (False, False) == _ANALYZE.get_signal('xyz', int(default_conf['ticker_interval'])) + assert (False, False) == _ANALYZE.get_signal('xyz', default_conf['ticker_interval']) assert log_has( 'Outdated history for pair xyz. Last tick is 11 minutes old', caplog.record_tuples @@ -166,7 +166,7 @@ def test_get_signal_handles_exceptions(mocker): ) ) - assert _ANALYZE.get_signal('ETH/BTC', 5) == (False, False) + assert _ANALYZE.get_signal('ETH/BTC', '5m') == (False, False) def test_parse_ticker_dataframe(ticker_history, ticker_history_without_bv): @@ -188,7 +188,7 @@ def test_tickerdata_to_dataframe(default_conf) -> None: analyze = Analyze(default_conf) timerange = ((None, 'line'), None, -100) - tick = load_tickerdata_file(None, 'UNITTEST/BTC', 1, timerange=timerange) + tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange) tickerlist = {'UNITTEST/BTC': tick} data = analyze.tickerdata_to_dataframe(tickerlist) assert len(data['UNITTEST/BTC']) == 100 diff --git a/freqtrade/tests/test_arguments.py b/freqtrade/tests/test_arguments.py index 3d0a59537..881129887 100644 --- a/freqtrade/tests/test_arguments.py +++ b/freqtrade/tests/test_arguments.py @@ -106,7 +106,7 @@ def test_parse_args_backtesting_custom() -> None: '-c', 'test_conf.json', 'backtesting', '--live', - '--ticker-interval', '1', + '--ticker-interval', '1m', '--refresh-pairs-cached'] call_args = Arguments(args, '').get_parsed_arg() assert call_args.config == 'test_conf.json' @@ -114,7 +114,7 @@ def test_parse_args_backtesting_custom() -> None: assert call_args.loglevel == logging.INFO assert call_args.subparser == 'backtesting' assert call_args.func is not None - assert call_args.ticker_interval == 1 + assert call_args.ticker_interval == '1m' assert call_args.refresh_pairs is True diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index a74bc9d42..c45fa94c8 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -257,7 +257,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non assert 'ticker_interval' in config assert log_has('Parameter -i/--ticker-interval detected ...', caplog.record_tuples) assert log_has( - 'Using ticker_interval: 1 ...', + 'Using ticker_interval: 1m ...', caplog.record_tuples ) diff --git a/freqtrade/tests/test_dataframe.py b/freqtrade/tests/test_dataframe.py index 48f483fce..47d89a112 100644 --- a/freqtrade/tests/test_dataframe.py +++ b/freqtrade/tests/test_dataframe.py @@ -10,7 +10,7 @@ _pairs = ['ETH/BTC'] def load_dataframe_pair(pairs): - ld = load_data(None, ticker_interval=5, pairs=pairs) + ld = load_data(None, ticker_interval='5m', pairs=pairs) assert isinstance(ld, dict) assert isinstance(pairs[0], str) dataframe = ld[pairs[0]] diff --git a/freqtrade/tests/test_misc.py b/freqtrade/tests/test_misc.py index 5585a087d..beb9c7667 100644 --- a/freqtrade/tests/test_misc.py +++ b/freqtrade/tests/test_misc.py @@ -50,7 +50,7 @@ def test_common_datearray(default_conf, mocker) -> None: mocker.patch('freqtrade.strategy.strategy.Strategy', MagicMock()) analyze = Analyze(default_conf) - tick = load_tickerdata_file(None, 'UNITTEST/BTC', 1) + tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m') tickerlist = {'UNITTEST/BTC': tick} dataframes = analyze.tickerdata_to_dataframe(tickerlist) diff --git a/freqtrade/tests/testdata/download_backtest_data.py b/freqtrade/tests/testdata/download_backtest_data.py index ddbc4af76..5e6ce0f92 100755 --- a/freqtrade/tests/testdata/download_backtest_data.py +++ b/freqtrade/tests/testdata/download_backtest_data.py @@ -17,7 +17,7 @@ parser.add_argument( ) args = parser.parse_args(sys.argv[1:]) -TICKER_INTERVALS = [1, 5] # ticker interval in minutes (currently implemented: 1 and 5) +TICKER_INTERVALS = ['1m', '5m'] # ticker interval in minutes (currently implemented: 1 and 5) PAIRS = [] if args.pair: diff --git a/scripts/plot_profit.py b/scripts/plot_profit.py index 3cbf147b0..b8a76a1ca 100755 --- a/scripts/plot_profit.py +++ b/scripts/plot_profit.py @@ -25,6 +25,7 @@ from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration from freqtrade.analyze import Analyze from freqtrade.logger import Logger +from freqtrade.constants import Constants import freqtrade.optimize as optimize import freqtrade.misc as misc @@ -187,11 +188,12 @@ def plot_profit(args: Namespace) -> None: plot(fig, filename='freqtrade-profit-plot.html') -def define_index(min_date: int, max_date: int, interval: int) -> int: +def define_index(min_date: int, max_date: int, interval: str) -> int: """ Return the index of a specific date """ - return int((max_date - min_date) / (interval * 60)) + interval_minutes = Constants.TICKER_INTERVAL_MINUTES[interval] + return int((max_date - min_date) / (interval_minutes * 60)) def plot_parse_args(args: List[str]) -> Namespace: