2018-02-12 06:10:21 +00:00
|
|
|
# pragma pylint: disable=missing-docstring, C0103
|
|
|
|
import argparse
|
2019-09-30 07:48:00 +00:00
|
|
|
from pathlib import Path
|
|
|
|
from unittest.mock import MagicMock
|
2018-03-17 21:44:47 +00:00
|
|
|
|
2018-02-12 06:10:21 +00:00
|
|
|
import pytest
|
|
|
|
|
2020-01-26 12:41:04 +00:00
|
|
|
from freqtrade.commands import Arguments
|
2020-10-28 15:58:39 +00:00
|
|
|
from freqtrade.commands.cli_options import check_int_nonzero, check_int_positive
|
2018-02-12 06:10:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
# Parse common command-line-arguments. Used for all tools
|
2019-09-14 11:47:33 +00:00
|
|
|
def test_parse_args_none() -> None:
|
|
|
|
arguments = Arguments(['trade'])
|
2018-02-12 06:10:21 +00:00
|
|
|
assert isinstance(arguments, Arguments)
|
2019-09-04 14:38:33 +00:00
|
|
|
x = arguments.get_parsed_arg()
|
2019-09-12 18:25:27 +00:00
|
|
|
assert isinstance(x, dict)
|
2018-02-12 06:10:21 +00:00
|
|
|
assert isinstance(arguments.parser, argparse.ArgumentParser)
|
|
|
|
|
|
|
|
|
2020-02-14 19:04:05 +00:00
|
|
|
def test_parse_args_defaults(mocker) -> None:
|
2020-08-26 18:52:09 +00:00
|
|
|
mocker.patch.object(Path, 'is_file', MagicMock(side_effect=[False, True]))
|
2019-09-14 11:47:33 +00:00
|
|
|
args = Arguments(['trade']).get_parsed_arg()
|
2020-08-26 18:52:09 +00:00
|
|
|
assert args['config'] == ['config.json']
|
|
|
|
assert args['strategy_path'] is None
|
|
|
|
assert args['datadir'] is None
|
|
|
|
assert args['verbosity'] == 0
|
2018-02-12 06:10:21 +00:00
|
|
|
|
|
|
|
|
2020-02-14 19:04:05 +00:00
|
|
|
def test_parse_args_default_userdatadir(mocker) -> None:
|
2020-08-26 18:52:09 +00:00
|
|
|
mocker.patch.object(Path, 'is_file', MagicMock(return_value=True))
|
2020-02-14 19:04:05 +00:00
|
|
|
args = Arguments(['trade']).get_parsed_arg()
|
|
|
|
# configuration defaults to user_data if that is available.
|
2020-08-26 18:52:09 +00:00
|
|
|
assert args['config'] == [str(Path('user_data/config.json'))]
|
|
|
|
assert args['strategy_path'] is None
|
|
|
|
assert args['datadir'] is None
|
|
|
|
assert args['verbosity'] == 0
|
2020-02-14 19:04:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_parse_args_userdatadir(mocker) -> None:
|
2020-08-26 18:52:09 +00:00
|
|
|
mocker.patch.object(Path, 'is_file', MagicMock(return_value=True))
|
2020-02-14 19:04:05 +00:00
|
|
|
args = Arguments(['trade', '--user-data-dir', 'user_data']).get_parsed_arg()
|
|
|
|
# configuration defaults to user_data if that is available.
|
2020-08-26 18:52:09 +00:00
|
|
|
assert args['config'] == [str(Path('user_data/config.json'))]
|
|
|
|
assert args['strategy_path'] is None
|
|
|
|
assert args['datadir'] is None
|
|
|
|
assert args['verbosity'] == 0
|
2020-02-14 19:04:05 +00:00
|
|
|
|
2020-02-14 19:13:36 +00:00
|
|
|
|
2018-02-12 06:10:21 +00:00
|
|
|
def test_parse_args_config() -> None:
|
2019-09-14 11:38:26 +00:00
|
|
|
args = Arguments(['trade', '-c', '/dev/null']).get_parsed_arg()
|
2020-08-26 18:52:09 +00:00
|
|
|
assert args['config'] == ['/dev/null']
|
2018-02-12 06:10:21 +00:00
|
|
|
|
2019-09-14 11:38:26 +00:00
|
|
|
args = Arguments(['trade', '--config', '/dev/null']).get_parsed_arg()
|
2020-08-26 18:52:09 +00:00
|
|
|
assert args['config'] == ['/dev/null']
|
2019-02-20 14:54:20 +00:00
|
|
|
|
2019-09-14 11:38:26 +00:00
|
|
|
args = Arguments(['trade', '--config', '/dev/null',
|
2019-09-04 14:38:33 +00:00
|
|
|
'--config', '/dev/zero'],).get_parsed_arg()
|
2020-08-26 18:52:09 +00:00
|
|
|
assert args['config'] == ['/dev/null', '/dev/zero']
|
2018-02-12 06:10:21 +00:00
|
|
|
|
|
|
|
|
2018-06-07 18:30:13 +00:00
|
|
|
def test_parse_args_db_url() -> None:
|
2019-09-14 11:38:26 +00:00
|
|
|
args = Arguments(['trade', '--db-url', 'sqlite:///test.sqlite']).get_parsed_arg()
|
2020-08-26 18:52:09 +00:00
|
|
|
assert args['db_url'] == 'sqlite:///test.sqlite'
|
2018-06-07 18:30:13 +00:00
|
|
|
|
|
|
|
|
2018-02-12 06:10:21 +00:00
|
|
|
def test_parse_args_verbose() -> None:
|
2019-09-14 11:38:26 +00:00
|
|
|
args = Arguments(['trade', '-v']).get_parsed_arg()
|
2020-08-26 18:52:09 +00:00
|
|
|
assert args['verbosity'] == 1
|
2018-02-12 06:10:21 +00:00
|
|
|
|
2019-09-14 11:38:26 +00:00
|
|
|
args = Arguments(['trade', '--verbose']).get_parsed_arg()
|
2020-08-26 18:52:09 +00:00
|
|
|
assert args['verbosity'] == 1
|
2018-02-12 06:10:21 +00:00
|
|
|
|
|
|
|
|
2019-06-18 22:53:38 +00:00
|
|
|
def test_common_scripts_options() -> None:
|
2019-09-04 14:38:33 +00:00
|
|
|
args = Arguments(['download-data', '-p', 'ETH/BTC', 'XRP/BTC']).get_parsed_arg()
|
2019-08-16 12:42:44 +00:00
|
|
|
|
2020-08-26 18:52:09 +00:00
|
|
|
assert args['pairs'] == ['ETH/BTC', 'XRP/BTC']
|
|
|
|
assert 'func' in args
|
2018-02-12 06:10:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_parse_args_version() -> None:
|
|
|
|
with pytest.raises(SystemExit, match=r'0'):
|
2019-09-04 14:38:33 +00:00
|
|
|
Arguments(['--version']).get_parsed_arg()
|
2018-02-12 06:10:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_parse_args_invalid() -> None:
|
|
|
|
with pytest.raises(SystemExit, match=r'2'):
|
2019-09-04 14:38:33 +00:00
|
|
|
Arguments(['-c']).get_parsed_arg()
|
2018-02-12 06:10:21 +00:00
|
|
|
|
|
|
|
|
2018-03-25 14:28:04 +00:00
|
|
|
def test_parse_args_strategy() -> None:
|
2019-09-14 11:38:26 +00:00
|
|
|
args = Arguments(['trade', '--strategy', 'SomeStrategy']).get_parsed_arg()
|
2020-08-26 18:52:09 +00:00
|
|
|
assert args['strategy'] == 'SomeStrategy'
|
2018-03-25 14:28:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_parse_args_strategy_invalid() -> None:
|
|
|
|
with pytest.raises(SystemExit, match=r'2'):
|
2019-09-04 14:38:33 +00:00
|
|
|
Arguments(['--strategy']).get_parsed_arg()
|
2018-03-25 14:28:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_parse_args_strategy_path() -> None:
|
2019-09-14 11:38:26 +00:00
|
|
|
args = Arguments(['trade', '--strategy-path', '/some/path']).get_parsed_arg()
|
2020-08-26 18:52:09 +00:00
|
|
|
assert args['strategy_path'] == '/some/path'
|
2018-03-25 14:28:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_parse_args_strategy_path_invalid() -> None:
|
|
|
|
with pytest.raises(SystemExit, match=r'2'):
|
2019-09-04 14:38:33 +00:00
|
|
|
Arguments(['--strategy-path']).get_parsed_arg()
|
2018-03-25 14:28:04 +00:00
|
|
|
|
|
|
|
|
2018-02-12 06:10:21 +00:00
|
|
|
def test_parse_args_backtesting_invalid() -> None:
|
|
|
|
with pytest.raises(SystemExit, match=r'2'):
|
2019-09-04 14:38:33 +00:00
|
|
|
Arguments(['backtesting --ticker-interval']).get_parsed_arg()
|
2018-02-12 06:10:21 +00:00
|
|
|
|
|
|
|
with pytest.raises(SystemExit, match=r'2'):
|
2019-09-04 14:38:33 +00:00
|
|
|
Arguments(['backtesting --ticker-interval', 'abc']).get_parsed_arg()
|
2018-02-12 06:10:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_parse_args_backtesting_custom() -> None:
|
|
|
|
args = [
|
|
|
|
'backtesting',
|
2019-09-14 11:38:26 +00:00
|
|
|
'-c', 'test_conf.json',
|
2018-03-24 09:21:59 +00:00
|
|
|
'--ticker-interval', '1m',
|
2018-07-28 04:40:39 +00:00
|
|
|
'--strategy-list',
|
|
|
|
'DefaultStrategy',
|
2019-08-27 04:41:07 +00:00
|
|
|
'SampleStrategy'
|
2018-07-28 04:40:39 +00:00
|
|
|
]
|
2019-09-04 14:38:33 +00:00
|
|
|
call_args = Arguments(args).get_parsed_arg()
|
2020-08-26 18:52:09 +00:00
|
|
|
assert call_args['config'] == ['test_conf.json']
|
|
|
|
assert call_args['verbosity'] == 0
|
|
|
|
assert call_args['command'] == 'backtesting'
|
|
|
|
assert call_args['func'] is not None
|
|
|
|
assert call_args['timeframe'] == '1m'
|
|
|
|
assert type(call_args['strategy_list']) is list
|
|
|
|
assert len(call_args['strategy_list']) == 2
|
2018-02-12 06:10:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_parse_args_hyperopt_custom() -> None:
|
2018-03-04 08:51:22 +00:00
|
|
|
args = [
|
|
|
|
'hyperopt',
|
2019-09-14 11:38:26 +00:00
|
|
|
'-c', 'test_conf.json',
|
2018-03-04 08:51:22 +00:00
|
|
|
'--epochs', '20',
|
|
|
|
'--spaces', 'buy'
|
|
|
|
]
|
2019-09-04 14:38:33 +00:00
|
|
|
call_args = Arguments(args).get_parsed_arg()
|
2020-08-26 18:52:09 +00:00
|
|
|
assert call_args['config'] == ['test_conf.json']
|
|
|
|
assert call_args['epochs'] == 20
|
|
|
|
assert call_args['verbosity'] == 0
|
|
|
|
assert call_args['command'] == 'hyperopt'
|
|
|
|
assert call_args['spaces'] == ['buy']
|
|
|
|
assert call_args['func'] is not None
|
|
|
|
assert callable(call_args['func'])
|
2018-06-02 21:47:20 +00:00
|
|
|
|
|
|
|
|
2019-05-29 18:57:14 +00:00
|
|
|
def test_download_data_options() -> None:
|
2018-06-02 21:47:20 +00:00
|
|
|
args = [
|
2019-08-16 12:42:44 +00:00
|
|
|
'download-data',
|
2019-09-14 11:38:26 +00:00
|
|
|
'--datadir', 'datadir/directory',
|
2019-08-16 12:42:44 +00:00
|
|
|
'--pairs-file', 'file_with_pairs',
|
2018-06-02 21:47:20 +00:00
|
|
|
'--days', '30',
|
|
|
|
'--exchange', 'binance'
|
|
|
|
]
|
2019-09-12 18:25:27 +00:00
|
|
|
pargs = Arguments(args).get_parsed_arg()
|
2019-08-16 12:42:44 +00:00
|
|
|
|
2020-08-26 18:52:09 +00:00
|
|
|
assert pargs['pairs_file'] == 'file_with_pairs'
|
|
|
|
assert pargs['datadir'] == 'datadir/directory'
|
|
|
|
assert pargs['days'] == 30
|
|
|
|
assert pargs['exchange'] == 'binance'
|
2019-05-23 17:53:42 +00:00
|
|
|
|
|
|
|
|
2019-06-16 17:35:15 +00:00
|
|
|
def test_plot_dataframe_options() -> None:
|
|
|
|
args = [
|
2019-08-22 18:23:38 +00:00
|
|
|
'plot-dataframe',
|
2021-01-22 18:18:21 +00:00
|
|
|
'-c', 'config_bittrex.json.example',
|
2019-08-22 18:23:38 +00:00
|
|
|
'--indicators1', 'sma10', 'sma100',
|
|
|
|
'--indicators2', 'macd', 'fastd', 'fastk',
|
2019-06-16 17:35:15 +00:00
|
|
|
'--plot-limit', '30',
|
2019-06-16 11:19:06 +00:00
|
|
|
'-p', 'UNITTEST/BTC',
|
2019-06-16 17:35:15 +00:00
|
|
|
]
|
2019-09-04 14:38:33 +00:00
|
|
|
pargs = Arguments(args).get_parsed_arg()
|
2019-08-22 18:23:38 +00:00
|
|
|
|
2020-08-26 18:52:09 +00:00
|
|
|
assert pargs['indicators1'] == ['sma10', 'sma100']
|
|
|
|
assert pargs['indicators2'] == ['macd', 'fastd', 'fastk']
|
|
|
|
assert pargs['plot_limit'] == 30
|
|
|
|
assert pargs['pairs'] == ['UNITTEST/BTC']
|
2019-06-16 11:19:06 +00:00
|
|
|
|
2019-06-16 17:35:15 +00:00
|
|
|
|
2021-05-30 14:11:24 +00:00
|
|
|
@pytest.mark.parametrize('auto_open_arg', [True, False])
|
|
|
|
def test_plot_profit_options(auto_open_arg: bool) -> None:
|
2019-08-31 13:26:34 +00:00
|
|
|
args = [
|
|
|
|
'plot-profit',
|
|
|
|
'-p', 'UNITTEST/BTC',
|
|
|
|
'--trade-source', 'DB',
|
2020-08-26 18:52:09 +00:00
|
|
|
'--db-url', 'sqlite:///whatever.sqlite',
|
2019-08-31 13:26:34 +00:00
|
|
|
]
|
2021-05-30 14:11:24 +00:00
|
|
|
if auto_open_arg:
|
|
|
|
args.append('--auto-open')
|
2019-09-04 14:38:33 +00:00
|
|
|
pargs = Arguments(args).get_parsed_arg()
|
2019-08-31 13:26:34 +00:00
|
|
|
|
2020-08-26 18:52:09 +00:00
|
|
|
assert pargs['trade_source'] == 'DB'
|
|
|
|
assert pargs['pairs'] == ['UNITTEST/BTC']
|
|
|
|
assert pargs['db_url'] == 'sqlite:///whatever.sqlite'
|
2021-05-30 14:11:24 +00:00
|
|
|
assert pargs['plot_auto_open'] == auto_open_arg
|
2019-08-31 13:26:34 +00:00
|
|
|
|
|
|
|
|
2019-09-30 07:48:00 +00:00
|
|
|
def test_config_notallowed(mocker) -> None:
|
2020-08-26 18:52:09 +00:00
|
|
|
mocker.patch.object(Path, 'is_file', MagicMock(return_value=False))
|
2019-09-30 07:48:00 +00:00
|
|
|
args = [
|
|
|
|
'create-userdir',
|
|
|
|
]
|
|
|
|
pargs = Arguments(args).get_parsed_arg()
|
|
|
|
|
2020-08-26 18:52:09 +00:00
|
|
|
assert 'config' not in pargs
|
2019-09-30 07:48:00 +00:00
|
|
|
|
|
|
|
# When file exists:
|
2020-08-26 18:52:09 +00:00
|
|
|
mocker.patch.object(Path, 'is_file', MagicMock(return_value=True))
|
2019-09-30 07:48:00 +00:00
|
|
|
args = [
|
|
|
|
'create-userdir',
|
|
|
|
]
|
|
|
|
pargs = Arguments(args).get_parsed_arg()
|
|
|
|
# config is not added even if it exists, since create-userdir is in the notallowed list
|
2020-08-26 18:52:09 +00:00
|
|
|
assert 'config' not in pargs
|
2019-09-30 07:48:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_config_notrequired(mocker) -> None:
|
2020-08-26 18:52:09 +00:00
|
|
|
mocker.patch.object(Path, 'is_file', MagicMock(return_value=False))
|
2019-09-30 07:48:00 +00:00
|
|
|
args = [
|
|
|
|
'download-data',
|
|
|
|
]
|
|
|
|
pargs = Arguments(args).get_parsed_arg()
|
|
|
|
|
2020-08-26 18:52:09 +00:00
|
|
|
assert pargs['config'] is None
|
2019-09-30 07:48:00 +00:00
|
|
|
|
|
|
|
# When file exists:
|
2020-08-26 18:52:09 +00:00
|
|
|
mocker.patch.object(Path, 'is_file', MagicMock(side_effect=[False, True]))
|
2019-09-30 07:48:00 +00:00
|
|
|
args = [
|
|
|
|
'download-data',
|
|
|
|
]
|
|
|
|
pargs = Arguments(args).get_parsed_arg()
|
|
|
|
# config is added if it exists
|
2020-08-26 18:52:09 +00:00
|
|
|
assert pargs['config'] == ['config.json']
|
2019-09-30 07:48:00 +00:00
|
|
|
|
|
|
|
|
2019-05-23 17:53:42 +00:00
|
|
|
def test_check_int_positive() -> None:
|
2020-08-26 18:52:09 +00:00
|
|
|
assert check_int_positive('3') == 3
|
|
|
|
assert check_int_positive('1') == 1
|
|
|
|
assert check_int_positive('100') == 100
|
2019-05-25 11:16:00 +00:00
|
|
|
|
|
|
|
with pytest.raises(argparse.ArgumentTypeError):
|
2020-08-26 18:52:09 +00:00
|
|
|
check_int_positive('-2')
|
2019-05-25 11:16:00 +00:00
|
|
|
|
|
|
|
with pytest.raises(argparse.ArgumentTypeError):
|
2020-08-26 18:52:09 +00:00
|
|
|
check_int_positive('0')
|
2019-05-25 11:16:00 +00:00
|
|
|
|
2020-10-28 15:29:08 +00:00
|
|
|
with pytest.raises(argparse.ArgumentTypeError):
|
|
|
|
check_int_positive(0)
|
|
|
|
|
2019-05-24 04:22:27 +00:00
|
|
|
with pytest.raises(argparse.ArgumentTypeError):
|
2020-08-26 18:52:09 +00:00
|
|
|
check_int_positive('3.5')
|
2019-05-24 04:22:27 +00:00
|
|
|
|
|
|
|
with pytest.raises(argparse.ArgumentTypeError):
|
2020-08-26 18:52:09 +00:00
|
|
|
check_int_positive('DeadBeef')
|
2020-10-28 15:58:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_check_int_nonzero() -> None:
|
|
|
|
assert check_int_nonzero('3') == 3
|
|
|
|
assert check_int_nonzero('1') == 1
|
|
|
|
assert check_int_nonzero('100') == 100
|
|
|
|
|
|
|
|
assert check_int_nonzero('-2') == -2
|
|
|
|
|
|
|
|
with pytest.raises(argparse.ArgumentTypeError):
|
|
|
|
check_int_nonzero('0')
|
|
|
|
|
|
|
|
with pytest.raises(argparse.ArgumentTypeError):
|
|
|
|
check_int_nonzero(0)
|
|
|
|
|
|
|
|
with pytest.raises(argparse.ArgumentTypeError):
|
|
|
|
check_int_nonzero('3.5')
|
|
|
|
|
|
|
|
with pytest.raises(argparse.ArgumentTypeError):
|
|
|
|
check_int_nonzero('DeadBeef')
|