diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 83a8ee833..31fea17fc 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -44,11 +44,11 @@ optional arguments: ### How to use a different config file? -The bot allows you to select which config file you want to use. Per +The bot allows you to select which config file you want to use. Per default, the bot will load the file `./config.json` ```bash -python3 ./freqtrade/main.py -c path/far/far/away/config.json +python3 ./freqtrade/main.py -c path/far/far/away/config.json ``` ### How to use --strategy? @@ -61,7 +61,7 @@ The bot will search your strategy file within `user_data/strategies` and `freqtr To load a strategy, simply pass the class name (e.g.: `CustomStrategy`) in this parameter. -**Example:** +**Example:** In `user_data/strategies` you have a file `my_awesome_strategy.py` which has a strategy class called `AwesomeStrategy` to load it: @@ -69,7 +69,7 @@ a strategy class called `AwesomeStrategy` to load it: python3 ./freqtrade/main.py --strategy AwesomeStrategy ``` -If the bot does not find your strategy file, it will display in an error +If the bot does not find your strategy file, it will display in an error message the reason (File not found, or errors in your code). Learn more about strategy file in [optimize your bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md). @@ -84,37 +84,37 @@ python3 ./freqtrade/main.py --strategy AwesomeStrategy --strategy-path /some/fol #### How to install a strategy? -This is very simple. Copy paste your strategy file into the folder +This is very simple. Copy paste your strategy file into the folder `user_data/strategies` or use `--strategy-path`. And voila, the bot is ready to use it. ### How to use --dynamic-whitelist? -Per default `--dynamic-whitelist` will retrieve the 20 currencies based +Per default `--dynamic-whitelist` will retrieve the 20 currencies based on BaseVolume. This value can be changed when you run the script. -**By Default** -Get the 20 currencies based on BaseVolume. +**By Default** +Get the 20 currencies based on BaseVolume. ```bash python3 ./freqtrade/main.py --dynamic-whitelist ``` -**Customize the number of currencies to retrieve** -Get the 30 currencies based on BaseVolume. +**Customize the number of currencies to retrieve** +Get the 30 currencies based on BaseVolume. ```bash python3 ./freqtrade/main.py --dynamic-whitelist 30 ``` -**Exception** +**Exception** `--dynamic-whitelist` must be greater than 0. If you enter 0 or a negative value (e.g -2), `--dynamic-whitelist` will use the default value (20). ### How to use --db-url? -When you run the bot in Dry-run mode, per default no transactions are -stored in a database. If you want to store your bot actions in a DB +When you run the bot in Dry-run mode, per default no transactions are +stored in a database. If you want to store your bot actions in a DB using `--db-url`. This can also be used to specify a custom database in production mode. Example command: @@ -170,15 +170,15 @@ optional arguments: ### How to use --refresh-pairs-cached parameter? -The first time your run Backtesting, it will take the pairs you have -set in your config file and download data from Bittrex. +The first time your run Backtesting, it will take the pairs you have +set in your config file and download data from Bittrex. -If for any reason you want to update your data set, you use -`--refresh-pairs-cached` to force Backtesting to update the data it has. +If for any reason you want to update your data set, you use +`--refresh-pairs-cached` to force Backtesting to update the data it has. **Use it only if you want to update your data set. You will not be able to come back to the previous version.** -To test your strategy with latest data, we recommend continuing using +To test your strategy with latest data, we recommend continuing using the parameter `-l` or `--live`. ## Hyperopt commands @@ -211,6 +211,31 @@ optional arguments: ``` +## Edge commands + +To know your trade expectacny and winrate against historical data, you can use Edge. + +``` +usage: main.py edge [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE] [-r] + [--stoplosses STOPLOSS_RANGE] + +optional arguments: + -h, --help show this help message and exit + -i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL + specify ticker interval (1m, 5m, 30m, 1h, 1d) + --timerange TIMERANGE + specify what timerange of data to use. + -r, --refresh-pairs-cached + refresh the pairs files in tests/testdata with the + latest data from the exchange. Use it if you want to + run your edge with up-to-date data. + --stoplosses STOPLOSS_RANGE + defines a range of stoploss against which edge will + assess the strategythe format is "min,max,step" + (without any space).example: + --stoplosses=-0.01,-0.1,-0.001 +``` + ## A parameter missing in the configuration? All parameters for `main.py`, `backtesting`, `hyperopt` are referenced @@ -218,5 +243,5 @@ in [misc.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/misc. ## Next step -The optimal strategy of the bot will change with time depending of the market trends. The next step is to +The optimal strategy of the bot will change with time depending of the market trends. The next step is to [optimize your bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md). diff --git a/docs/edge.md b/docs/edge.md index f74f8fdcc..e5575554b 100644 --- a/docs/edge.md +++ b/docs/edge.md @@ -3,12 +3,14 @@ This page explains how to use Edge Positioning module in your bot in order to enter into a trade only if the trade has a reasonable win rate and risk reward ratio, and consequently adjust your position size and stoploss. **NOTICE:** Edge positioning is not compatible with dynamic whitelist. it overrides dynamic whitelist. +**NOTICE2:** Edge won't consider anything else than buy/sell/stoploss signals. So trailing stoploss, ROI, and everything else will be ignored in its calculation. ## Table of Contents - [Introduction](#introduction) - [How does it work?](#how-does-it-work?) - [Configurations](#configurations) +- [Running Edge independently](#running-edge-independently) ## Introduction Trading is all about probability. No one can claim that he has a strategy working all the time. You have to assume that sometimes you lose.

@@ -149,3 +151,57 @@ Edge will filter out trades with long duration. If a trade is profitable after 1 #### remove_pumps Edge will remove sudden pumps in a given market while going through historical data. However, given that pumps happen very often in crypto markets, we recommend you keep this off.
(default to false) + + +## Running Edge independently +You can run Edge independently in order to see in details the result. Here is an example: +```bash +python3 ./freqtrade/main.py edge +``` + +An example of its output: + +| pair | stoploss | win rate | risk reward ratio | required risk reward | expectancy | total number of trades | average duration (min) | +|:----------|-----------:|-----------:|--------------------:|-----------------------:|-------------:|-------------------------:|-------------------------:| +| AGI/BTC | -0.02 | 0.64 | 5.86 | 0.56 | 3.41 | 14 | 54 | +| NXS/BTC | -0.03 | 0.64 | 2.99 | 0.57 | 1.54 | 11 | 26 | +| LEND/BTC | -0.02 | 0.82 | 2.05 | 0.22 | 1.50 | 11 | 36 | +| VIA/BTC | -0.01 | 0.55 | 3.01 | 0.83 | 1.19 | 11 | 48 | +| MTH/BTC | -0.09 | 0.56 | 2.82 | 0.80 | 1.12 | 18 | 52 | +| ARDR/BTC | -0.04 | 0.42 | 3.14 | 1.40 | 0.73 | 12 | 42 | +| BCPT/BTC | -0.01 | 0.71 | 1.34 | 0.40 | 0.67 | 14 | 30 | +| WINGS/BTC | -0.02 | 0.56 | 1.97 | 0.80 | 0.65 | 27 | 42 | +| VIBE/BTC | -0.02 | 0.83 | 0.91 | 0.20 | 0.59 | 12 | 35 | +| MCO/BTC | -0.02 | 0.79 | 0.97 | 0.27 | 0.55 | 14 | 31 | +| GNT/BTC | -0.02 | 0.50 | 2.06 | 1.00 | 0.53 | 18 | 24 | +| HOT/BTC | -0.01 | 0.17 | 7.72 | 4.81 | 0.50 | 209 | 7 | +| SNM/BTC | -0.03 | 0.71 | 1.06 | 0.42 | 0.45 | 17 | 38 | +| APPC/BTC | -0.02 | 0.44 | 2.28 | 1.27 | 0.44 | 25 | 43 | +| NEBL/BTC | -0.03 | 0.63 | 1.29 | 0.58 | 0.44 | 19 | 59 | + +### Update cached pairs with the latest data +```bash +python3 ./freqtrade/main.py edge --refresh-pairs-cached +``` + +### Precising stoploss range +```bash +python3 ./freqtrade/main.py edge --stoplosses=-0.01,-0.1,-0.001 #min,max,step +``` + +### Advanced use of timerange +```bash +python3 ./freqtrade/main.py edge --timerange=20181110-20181113 +``` + +Doing --timerange=-200 will get the last 200 timeframes from your inputdata. You can also specify specific dates, or a range span indexed by start and stop. + +The full timerange specification: + +* Use last 123 tickframes of data: --timerange=-123 +* Use first 123 tickframes of data: --timerange=123- +* Use tickframes from line 123 through 456: --timerange=123-456 +* Use tickframes till 2018/01/31: --timerange=-20180131 +* Use tickframes since 2018/01/31: --timerange=20180131- +* Use tickframes since 2018/01/31 till 2018/03/01 : --timerange=20180131-20180301 +* Use tickframes between POSIX timestamps 1527595200 1527618600: --timerange=1527595200-1527618600 \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index e6e643ba7..c64b9c188 100644 --- a/docs/index.md +++ b/docs/index.md @@ -21,6 +21,7 @@ Pull-request. Do not hesitate to reach us on - [Bot commands](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#bot-commands) - [Backtesting commands](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#backtesting-commands) - [Hyperopt commands](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#hyperopt-commands) + - [Edge commands](https://github.com/mishaker/freqtrade/blob/develop/docs/bot-usage.md#edge-commands) - [Bot Optimization](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md) - [Change your strategy](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md#change-your-strategy) - [Add more Indicator](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md#add-more-indicator) diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index bb571b4ea..8e26752fe 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -128,6 +128,22 @@ class Arguments(object): """ Parses given arguments for Backtesting scripts. """ + parser.add_argument( + '--eps', '--enable-position-stacking', + help='Allow buying the same pair multiple times (position stacking)', + action='store_true', + dest='position_stacking', + default=False + ) + + parser.add_argument( + '--dmmp', '--disable-max-market-positions', + help='Disable applying `max_open_trades` during backtest ' + '(same as setting `max_open_trades` to a very high number)', + action='store_false', + dest='use_max_market_positions', + default=True + ) parser.add_argument( '-l', '--live', help='using live data', @@ -171,6 +187,27 @@ class Arguments(object): metavar='PATH', ) + @staticmethod + def edge_options(parser: argparse.ArgumentParser) -> None: + """ + Parses given arguments for Backtesting scripts. + """ + parser.add_argument( + '-r', '--refresh-pairs-cached', + help='refresh the pairs files in tests/testdata with the latest data from the ' + 'exchange. Use it if you want to run your edge with up-to-date data.', + action='store_true', + dest='refresh_pairs', + ) + parser.add_argument( + '--stoplosses', + help='defines a range of stoploss against which edge will assess the strategy ' + 'the format is "min,max,step" (without any space).' + 'example: --stoplosses=-0.01,-0.1,-0.001', + type=str, + dest='stoploss_range', + ) + @staticmethod def optimizer_shared_options(parser: argparse.ArgumentParser) -> None: """ @@ -184,6 +221,20 @@ class Arguments(object): dest='ticker_interval', type=str, ) + + parser.add_argument( + '--timerange', + help='specify what timerange of data to use.', + default=None, + type=str, + dest='timerange', + ) + + @staticmethod + def hyperopt_options(parser: argparse.ArgumentParser) -> None: + """ + Parses given arguments for Hyperopt scripts. + """ parser.add_argument( '--eps', '--enable-position-stacking', help='Allow buying the same pair multiple times (position stacking)', @@ -200,20 +251,6 @@ class Arguments(object): dest='use_max_market_positions', default=True ) - - parser.add_argument( - '--timerange', - help='specify what timerange of data to use.', - default=None, - type=str, - dest='timerange', - ) - - @staticmethod - def hyperopt_options(parser: argparse.ArgumentParser) -> None: - """ - Parses given arguments for Hyperopt scripts. - """ parser.add_argument( '-e', '--epochs', help='specify number of epochs (default: %(default)d)', @@ -237,7 +274,7 @@ class Arguments(object): Builds and attaches all subcommands :return: None """ - from freqtrade.optimize import backtesting, hyperopt + from freqtrade.optimize import backtesting, hyperopt, edge_cli subparsers = self.parser.add_subparsers(dest='subparser') @@ -247,6 +284,12 @@ class Arguments(object): self.optimizer_shared_options(backtesting_cmd) self.backtesting_options(backtesting_cmd) + # Add edge subcommand + edge_cmd = subparsers.add_parser('edge', help='edge module') + edge_cmd.set_defaults(func=edge_cli.start) + self.optimizer_shared_options(edge_cmd) + self.edge_options(edge_cmd) + # Add hyperopt subcommand hyperopt_cmd = subparsers.add_parser('hyperopt', help='hyperopt module') hyperopt_cmd.set_defaults(func=hyperopt.start) diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index b2daa5f1a..0a7bb7f80 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -59,6 +59,9 @@ class Configuration(object): # Load Backtesting config = self._load_backtesting_config(config) + # Load Edge + config = self._load_edge_config(config) + # Load Hyperopt config = self._load_hyperopt_config(config) @@ -218,6 +221,32 @@ class Configuration(object): return config + def _load_edge_config(self, config: Dict[str, Any]) -> Dict[str, Any]: + """ + Extract information for sys.argv and load Edge configuration + :return: configuration as dictionary + """ + + # If --timerange is used we add it to the configuration + if 'timerange' in self.args and self.args.timerange: + config.update({'timerange': self.args.timerange}) + logger.info('Parameter --timerange detected: %s ...', self.args.timerange) + + # If --timerange is used we add it to the configuration + if 'stoploss_range' in self.args and self.args.stoploss_range: + txt_range = eval(self.args.stoploss_range) + config['edge'].update({'stoploss_range_min': txt_range[0]}) + config['edge'].update({'stoploss_range_max': txt_range[1]}) + config['edge'].update({'stoploss_range_step': txt_range[2]}) + logger.info('Parameter --stoplosses detected: %s ...', self.args.stoploss_range) + + # If -r/--refresh-pairs-cached is used we add it to the configuration + if 'refresh_pairs' in self.args and self.args.refresh_pairs: + config.update({'refresh_pairs': True}) + logger.info('Parameter -r/--refresh-pairs-cached detected ...') + + return config + def _load_hyperopt_config(self, config: Dict[str, Any]) -> Dict[str, Any]: """ Extract information for sys.argv and load Hyperopt configuration diff --git a/freqtrade/edge/__init__.py b/freqtrade/edge/__init__.py index 7b25a8306..dedaa19a3 100644 --- a/freqtrade/edge/__init__.py +++ b/freqtrade/edge/__init__.py @@ -33,7 +33,9 @@ class Edge(): # pair info data type _pair_info = namedtuple( 'pair_info', - ['stoploss', 'winrate', 'risk_reward_ratio', 'required_risk_reward', 'expectancy']) + ['stoploss', 'winrate', 'risk_reward_ratio', 'required_risk_reward', 'expectancy', + 'nb_trades', 'avg_trade_duration'] + ) def __init__(self, config: Dict[str, Any], exchange, strategy) -> None: @@ -53,6 +55,7 @@ class Edge(): self._allowed_risk: float = self.edge_config.get('allowed_risk') self._since_number_of_days: int = self.edge_config.get('calculate_since_number_of_days', 14) self._last_updated: int = 0 # Timestamp of pairs last updated time + self._refresh_pairs = True self._stoploss_range_min = float(self.edge_config.get('stoploss_range_min', -0.01)) self._stoploss_range_max = float(self.edge_config.get('stoploss_range_max', -0.05)) @@ -86,7 +89,7 @@ class Edge(): self.config['datadir'], pairs=pairs, ticker_interval=self.ticker_interval, - refresh_pairs=True, + refresh_pairs=self._refresh_pairs, exchange=self.exchange, timerange=self._timerange ) @@ -296,7 +299,9 @@ class Edge(): 'winrate': x.winrate, 'risk_reward_ratio': x.risk_reward_ratio, 'required_risk_reward': x.required_risk_reward, - 'expectancy': x.expectancy + 'expectancy': x.expectancy, + 'nb_trades': x.nb_trades, + 'avg_trade_duration': x.avg_trade_duration } final[x.pair] = self._pair_info(**info) diff --git a/freqtrade/optimize/edge_cli.py b/freqtrade/optimize/edge_cli.py new file mode 100644 index 000000000..59bcbc098 --- /dev/null +++ b/freqtrade/optimize/edge_cli.py @@ -0,0 +1,106 @@ +# pragma pylint: disable=missing-docstring, W0212, too-many-arguments + +""" +This module contains the backtesting logic +""" +import logging +from argparse import Namespace +from typing import Dict, Any +from tabulate import tabulate +from freqtrade.edge import Edge + +from freqtrade.configuration import Configuration +from freqtrade.arguments import Arguments +from freqtrade.exchange import Exchange +from freqtrade.strategy.resolver import StrategyResolver + +logger = logging.getLogger(__name__) + + +class EdgeCli(object): + """ + Backtesting class, this class contains all the logic to run a backtest + + To run a backtest: + backtesting = Backtesting(config) + backtesting.start() + """ + + def __init__(self, config: Dict[str, Any]) -> None: + self.config = config + + # Reset keys for edge + self.config['exchange']['key'] = '' + self.config['exchange']['secret'] = '' + self.config['exchange']['password'] = '' + self.config['exchange']['uid'] = '' + self.config['dry_run'] = True + self.exchange = Exchange(self.config) + self.strategy = StrategyResolver(self.config).strategy + + 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( + 'timerange') is None else str(self.config.get('timerange'))) + + self.edge._timerange = self.timerange + + def _generate_edge_table(self, results: dict) -> str: + + floatfmt = ('s', '.10g', '.2f', '.2f', '.2f', '.2f', 'd', '.d') + tabular_data = [] + headers = ['pair', 'stoploss', 'win rate', 'risk reward ratio', + 'required risk reward', 'expectancy', 'total number of trades', + 'average duration (min)'] + + for result in results.items(): + if result[1].nb_trades > 0: + tabular_data.append([ + result[0], + result[1].stoploss, + result[1].winrate, + result[1].risk_reward_ratio, + result[1].required_risk_reward, + result[1].expectancy, + result[1].nb_trades, + round(result[1].avg_trade_duration) + ]) + + return tabulate(tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="pipe") + + def start(self) -> None: + self.edge.calculate() + print('') # blank like for readability + print(self._generate_edge_table(self.edge._cached_pairs)) + + +def setup_configuration(args: Namespace) -> Dict[str, Any]: + """ + Prepare the configuration for the backtesting + :param args: Cli args from Arguments() + :return: Configuration + """ + configuration = Configuration(args) + config = configuration.get_config() + + # Ensure we do not use Exchange credentials + config['exchange']['key'] = '' + config['exchange']['secret'] = '' + + return config + + +def start(args: Namespace) -> None: + """ + Start Edge script + :param args: Cli args from Arguments() + :return: None + """ + # Initialize configuration + config = setup_configuration(args) + logger.info('Starting freqtrade in Edge mode') + + # Initialize Edge object + edge_cli = EdgeCli(config) + edge_cli.start() diff --git a/freqtrade/tests/edge/test_edge.py b/freqtrade/tests/edge/test_edge.py index a7b1882a5..14c9114c3 100644 --- a/freqtrade/tests/edge/test_edge.py +++ b/freqtrade/tests/edge/test_edge.py @@ -128,9 +128,9 @@ def test_adjust(mocker, default_conf): edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy) mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( return_value={ - 'E/F': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71), - 'C/D': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71), - 'N/O': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71) + 'E/F': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60), + 'C/D': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60), + 'N/O': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60) } )) @@ -143,9 +143,9 @@ def test_stoploss(mocker, default_conf): edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy) mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( return_value={ - 'E/F': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71), - 'C/D': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71), - 'N/O': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71) + 'E/F': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60), + 'C/D': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60), + 'N/O': Edge._pair_info(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60) } )) diff --git a/freqtrade/tests/optimize/test_edge_cli.py b/freqtrade/tests/optimize/test_edge_cli.py new file mode 100644 index 000000000..f8db3dec4 --- /dev/null +++ b/freqtrade/tests/optimize/test_edge_cli.py @@ -0,0 +1,140 @@ +# pragma pylint: disable=missing-docstring, C0103, C0330 +# pragma pylint: disable=protected-access, too-many-lines, invalid-name, too-many-arguments + +from unittest.mock import MagicMock +import json +from typing import List +from freqtrade.edge import Edge +from freqtrade.arguments import Arguments +from freqtrade.optimize.edge_cli import (EdgeCli, setup_configuration, start) +from freqtrade.tests.conftest import log_has, patch_exchange + + +def get_args(args) -> List[str]: + return Arguments(args, '').get_parsed_arg() + + +def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None: + mocker.patch('freqtrade.configuration.open', mocker.mock_open( + read_data=json.dumps(default_conf) + )) + + args = [ + '--config', 'config.json', + '--strategy', 'DefaultStrategy', + 'edge' + ] + + config = setup_configuration(get_args(args)) + assert 'max_open_trades' in config + assert 'stake_currency' in config + assert 'stake_amount' in config + assert 'exchange' in config + assert 'pair_whitelist' in config['exchange'] + assert 'datadir' in config + assert log_has( + 'Using data folder: {} ...'.format(config['datadir']), + caplog.record_tuples + ) + assert 'ticker_interval' in config + assert not log_has('Parameter -i/--ticker-interval detected ...', caplog.record_tuples) + + assert 'refresh_pairs' not in config + assert not log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples) + + assert 'timerange' not in config + assert 'stoploss_range' not in config + + +def test_setup_configuration_with_arguments(mocker, edge_conf, caplog) -> None: + mocker.patch('freqtrade.configuration.open', mocker.mock_open( + read_data=json.dumps(edge_conf) + )) + + args = [ + '--config', 'config.json', + '--strategy', 'DefaultStrategy', + '--datadir', '/foo/bar', + 'edge', + '--ticker-interval', '1m', + '--refresh-pairs-cached', + '--timerange', ':100', + '--stoplosses=-0.01,-0.10,-0.001' + ] + + config = setup_configuration(get_args(args)) + assert 'max_open_trades' in config + assert 'stake_currency' in config + assert 'stake_amount' in config + assert 'exchange' in config + assert 'pair_whitelist' in config['exchange'] + assert 'datadir' in config + assert log_has( + 'Using data folder: {} ...'.format(config['datadir']), + caplog.record_tuples + ) + assert 'ticker_interval' in config + assert log_has('Parameter -i/--ticker-interval detected ...', caplog.record_tuples) + assert log_has( + 'Using ticker_interval: 1m ...', + caplog.record_tuples + ) + + assert 'refresh_pairs' in config + assert log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples) + assert 'timerange' in config + assert log_has( + 'Parameter --timerange detected: {} ...'.format(config['timerange']), + caplog.record_tuples + ) + + +def test_start(mocker, fee, edge_conf, caplog) -> None: + start_mock = MagicMock() + mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + patch_exchange(mocker) + mocker.patch('freqtrade.optimize.edge_cli.EdgeCli.start', start_mock) + mocker.patch('freqtrade.configuration.open', mocker.mock_open( + read_data=json.dumps(edge_conf) + )) + args = [ + '--config', 'config.json', + '--strategy', 'DefaultStrategy', + 'edge' + ] + args = get_args(args) + start(args) + assert log_has( + 'Starting freqtrade in Edge mode', + caplog.record_tuples + ) + assert start_mock.call_count == 1 + + +def test_edge_init(mocker, edge_conf) -> None: + patch_exchange(mocker) + edge_cli = EdgeCli(edge_conf) + assert edge_cli.config == edge_conf + assert callable(edge_cli.edge.calculate) + + +def test_generate_edge_table(edge_conf, mocker): + patch_exchange(mocker) + edge_cli = EdgeCli(edge_conf) + + results = {} + info = { + 'stoploss': -0.01, + 'winrate': 0.60, + 'risk_reward_ratio': 2, + 'required_risk_reward': 1, + 'expectancy': 3, + 'nb_trades': 10, + 'avg_trade_duration': 60 + } + + results['ETH/BTC'] = Edge._pair_info(**info) + assert edge_cli._generate_edge_table(results).count(':|') == 7 + assert edge_cli._generate_edge_table(results).count('| ETH/BTC |') == 1 + assert edge_cli._generate_edge_table(results).count( + '| risk reward ratio | required risk reward | expectancy |') == 1