diff --git a/docs/backtesting.md b/docs/backtesting.md index 19814303b..68782bb9c 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -11,14 +11,15 @@ Now you have good Buy and Sell strategies and some historic data, you want to te real data. This is what we call [backtesting](https://en.wikipedia.org/wiki/Backtesting). -Backtesting will use the crypto-currencies (pairs) from your config file -and load ticker data from `user_data/data/` by default. -If no data is available for the exchange / pair / ticker interval combination, backtesting will -ask you to download them first using `freqtrade download-data`. +Backtesting will use the crypto-currencies (pairs) from your config file and load ticker data from `user_data/data/` by default. +If no data is available for the exchange / pair / ticker interval combination, backtesting will ask you to download them first using `freqtrade download-data`. For details on downloading, please refer to the [Data Downloading](data-download.md) section in the documentation. The result of backtesting will confirm if your bot has better odds of making a profit than a loss. +!!! Tip "Using dynamic pairlists for backtesting" + While using dynamic pairlists during backtesting is not possible, a dynamic pairlist using current data can be generated via the [`test-pairlist`](utils.md#test-pairlist) command, and needs to be specified as `"pair_whitelist"` attribute in the configuration. + ### Run a backtesting against the currencies listed in your config file #### With 5 min tickers (Per default) diff --git a/docs/configuration.md b/docs/configuration.md index 024760fb9..5ad1a886e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -405,6 +405,9 @@ Inactive markets and blacklisted pairs are always removed from the resulting `pa * [`PrecisionFilter`](#precision-filter) * [`PriceFilter`](#price-pair-filter) +!!! Tip "Testing pairlists" + Pairlist configurations can be quite tricky to get right. Best use the [`test-pairlist`](utils.md#test-pairlist) subcommand to test your configuration quickly. + #### Static Pair List By default, the `StaticPairList` method is used, which uses a statically defined pair whitelist from the configuration. diff --git a/docs/utils.md b/docs/utils.md index ca4b645a5..4ad32055a 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -43,20 +43,6 @@ The file will be named inline with your class name, and will not overwrite exist Results will be located in `user_data/strategies/.py`. -### Sample usage of new-strategy - -```bash -freqtrade new-strategy --strategy AwesomeStrategy -``` - -With custom user directory - -```bash -freqtrade new-strategy --userdir ~/.freqtrade/ --strategy AwesomeStrategy -``` - -### new-strategy complete options - ``` output usage: freqtrade new-strategy [-h] [--userdir PATH] [-s NAME] [--template {full,minimal}] @@ -75,6 +61,18 @@ optional arguments: ``` +### Sample usage of new-strategy + +```bash +freqtrade new-strategy --strategy AwesomeStrategy +``` + +With custom user directory + +```bash +freqtrade new-strategy --userdir ~/.freqtrade/ --strategy AwesomeStrategy +``` + ## Create new hyperopt Creates a new hyperopt from a template similar to SampleHyperopt. @@ -82,20 +80,6 @@ The file will be named inline with your class name, and will not overwrite exist Results will be located in `user_data/hyperopts/.py`. -### Sample usage of new-hyperopt - -```bash -freqtrade new-hyperopt --hyperopt AwesomeHyperopt -``` - -With custom user directory - -```bash -freqtrade new-hyperopt --userdir ~/.freqtrade/ --hyperopt AwesomeHyperopt -``` - -### new-hyperopt complete options - ``` output usage: freqtrade new-hyperopt [-h] [--userdir PATH] [--hyperopt NAME] [--template {full,minimal}] @@ -112,6 +96,18 @@ optional arguments: `full`. ``` +### Sample usage of new-hyperopt + +```bash +freqtrade new-hyperopt --hyperopt AwesomeHyperopt +``` + +With custom user directory + +```bash +freqtrade new-hyperopt --userdir ~/.freqtrade/ --hyperopt AwesomeHyperopt +``` + ## List Exchanges Use the `list-exchanges` subcommand to see the exchanges available for the bot. @@ -234,3 +230,35 @@ $ freqtrade -c config_binance.json list-pairs --all --base BTC ETH --quote USDT ``` $ freqtrade list-markets --exchange kraken --all ``` + +## Test pairlist + +Use the `test-pairlist` subcommand to test the configuration of [dynamic pairlists](configuration.md#pairlists). + +Requires a configuration with specified `pairlists` attribute. +Can be used to generate static pairlists to be used during backtesting / hyperopt. + +``` +usage: freqtrade test-pairlist [-h] [-c PATH] + [--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]] + [-1] [--print-json] + +optional arguments: + -h, --help show this help message and exit + -c PATH, --config PATH + Specify configuration file (default: `config.json`). + Multiple --config options may be used. Can be set to + `-` to read config from stdin. + --quote QUOTE_CURRENCY [QUOTE_CURRENCY ...] + Specify quote currency(-ies). Space-separated list. + -1, --one-column Print output in one column. + --print-json Print list of pairs or market symbols in JSON format. +``` + +### Examples + +Show whitelist when using a [dynamic pairlist](configuration.md#pairlists). + +``` +freqtrade test-pairlist --config config.json --quote USDT BTC +``` diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index b23366d7a..25e4386a8 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -37,6 +37,8 @@ ARGS_LIST_TIMEFRAMES = ["exchange", "print_one_column"] ARGS_LIST_PAIRS = ["exchange", "print_list", "list_pairs_print_json", "print_one_column", "print_csv", "base_currencies", "quote_currencies", "list_pairs_all"] +ARGS_TEST_PAIRLIST = ["config", "quote_currencies", "print_one_column", "list_pairs_print_json"] + ARGS_CREATE_USERDIR = ["user_data_dir", "reset"] ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"] @@ -63,6 +65,7 @@ class Arguments: """ Arguments Class. Manage the arguments received by the cli """ + def __init__(self, args: Optional[List[str]]) -> None: self.args = args self._parsed_arg: Optional[argparse.Namespace] = None @@ -122,7 +125,7 @@ class Arguments: from freqtrade.utils import (start_create_userdir, start_download_data, start_list_exchanges, start_list_markets, start_new_hyperopt, start_new_strategy, - start_list_timeframes, start_trading) + start_list_timeframes, start_test_pairlist, start_trading) from freqtrade.plot.plot_utils import start_plot_dataframe, start_plot_profit subparsers = self.parser.add_subparsers(dest='command', @@ -211,6 +214,14 @@ class Arguments: list_pairs_cmd.set_defaults(func=partial(start_list_markets, pairs_only=True)) self._build_args(optionlist=ARGS_LIST_PAIRS, parser=list_pairs_cmd) + # Add test-pairlist subcommand + test_pairlist_cmd = subparsers.add_parser( + 'test-pairlist', + help='Test your pairlist configuration.', + ) + test_pairlist_cmd.set_defaults(func=start_test_pairlist) + self._build_args(optionlist=ARGS_TEST_PAIRLIST, parser=test_pairlist_cmd) + # Add download-data subcommand download_data_cmd = subparsers.add_parser( 'download-data', diff --git a/freqtrade/pairlist/PrecisionFilter.py b/freqtrade/pairlist/PrecisionFilter.py index d7b2c96ae..aedcc5a88 100644 --- a/freqtrade/pairlist/PrecisionFilter.py +++ b/freqtrade/pairlist/PrecisionFilter.py @@ -48,6 +48,7 @@ class PrecisionFilter(IPairList): """ Filters and sorts pairlists and assigns and returns them again. """ + stoploss = None if self._config.get('stoploss') is not None: # Precalculate sanitized stoploss value to avoid recalculation for every pair stoploss = 1 - abs(self._config.get('stoploss')) diff --git a/freqtrade/utils.py b/freqtrade/utils.py index c71080d5a..9a00f1f26 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -322,3 +322,35 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None: args.get('list_pairs_print_json', False) or args.get('print_csv', False)): print(f"{summary_str}.") + + +def start_test_pairlist(args: Dict[str, Any]) -> None: + """ + Test Pairlist configuration + """ + from freqtrade.pairlist.pairlistmanager import PairListManager + config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) + + exchange = ExchangeResolver(config['exchange']['name'], config, validate=False).exchange + + quote_currencies = args.get('quote_currencies') + if not quote_currencies: + quote_currencies = [config.get('stake_currency')] + results = {} + for curr in quote_currencies: + config['stake_currency'] = curr + # Do not use ticker_interval set in the config + pairlists = PairListManager(exchange, config) + pairlists.refresh_pairlist() + results[curr] = pairlists.whitelist + + for curr, pairlist in results.items(): + if not args.get('print_one_column', False): + print(f"Pairs for {curr}: ") + + if args.get('print_one_column', False): + print('\n'.join(pairlist)) + elif args.get('list_pairs_print_json', False): + print(rapidjson.dumps(list(pairlist), default=str)) + else: + print(pairlist) diff --git a/tests/test_utils.py b/tests/test_utils.py index 1258c939c..67ec8409b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -10,8 +10,9 @@ from freqtrade.utils import (setup_utils_configuration, start_create_userdir, start_download_data, start_list_exchanges, start_list_markets, start_list_timeframes, start_new_hyperopt, start_new_strategy, - start_trading) -from tests.conftest import get_args, log_has, log_has_re, patch_exchange + start_test_pairlist, start_trading) +from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, + patched_configuration_load_config_file) def test_setup_utils_configuration(): @@ -573,7 +574,7 @@ def test_download_data_no_exchange(mocker, caplog): ) args = [ "download-data", - ] + ] pargs = get_args(args) pargs['config'] = None with pytest.raises(OperationalException, @@ -623,3 +624,36 @@ def test_download_data_trades(mocker, caplog): assert dl_mock.call_args[1]['timerange'].starttype == "date" assert dl_mock.call_count == 1 assert convert_mock.call_count == 1 + + +def test_start_test_pairlist(mocker, caplog, markets, tickers, default_conf, capsys): + mocker.patch.multiple('freqtrade.exchange.Exchange', + markets=PropertyMock(return_value=markets), + exchange_has=MagicMock(return_value=True), + get_tickers=tickers + ) + + default_conf['pairlists'] = [ + { + "method": "VolumePairList", + "number_assets": 5, + "sort_key": "quoteVolume", + }, + {"method": "PrecisionFilter"}, + {"method": "PriceFilter", "low_price_ratio": 0.02}, + ] + + patched_configuration_load_config_file(mocker, default_conf) + args = [ + 'test-pairlist', + '-c', 'config.json.example' + ] + + start_test_pairlist(get_args(args)) + + assert log_has_re(r"^Using resolved pairlist VolumePairList.*", caplog) + assert log_has_re(r"^Using resolved pairlist PrecisionFilter.*", caplog) + assert log_has_re(r"^Using resolved pairlist PriceFilter.*", caplog) + captured = capsys.readouterr() + assert re.match(r"Pairs for .*", captured.out) + assert re.match("['ETH/BTC', 'TKN/BTC', 'BLK/BTC', 'LTC/BTC', 'XRP/BTC']", captured.out)