From 1976aaf13e366da4c9b6008fc3f5ddd169bb6d1c Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Sun, 22 Mar 2020 02:22:06 +0100 Subject: [PATCH 01/91] initial push --- docs/utils.md | 10 +++++--- freqtrade/commands/arguments.py | 1 + freqtrade/commands/cli_options.py | 12 +++++++++ freqtrade/commands/hyperopt_commands.py | 32 ++++++++++++++++++++++-- freqtrade/configuration/configuration.py | 6 +++++ tests/commands/test_commands.py | 28 +++++++++++++++++++++ 6 files changed, 84 insertions(+), 5 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index 269b9affd..03924c581 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -426,9 +426,9 @@ usage: freqtrade hyperopt-list [-h] [-v] [--logfile FILE] [-V] [-c PATH] [--max-trades INT] [--min-avg-time FLOAT] [--max-avg-time FLOAT] [--min-avg-profit FLOAT] [--max-avg-profit FLOAT] - [--min-total-profit FLOAT] - [--max-total-profit FLOAT] [--no-color] - [--print-json] [--no-details] + [--min-total-profit FLOAT] [--max-total-profit FLOAT] + [--min-objective FLOAT] [--max-objective FLOAT] + [--no-color] [--print-json] [--no-details] [--export-csv FILE] optional arguments: @@ -447,6 +447,10 @@ optional arguments: Select epochs on above total profit. --max-total-profit FLOAT Select epochs on below total profit. + --min-objective FLOAT + Select epochs on above objective (- is added by default). + --max-objective FLOAT + Select epochs on below objective (- is added by default). --no-color Disable colorization of hyperopt results. May be useful if you are redirecting output to a file. --print-json Print best result detailization in JSON format. diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 8c64c5857..9edd143a6 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -69,6 +69,7 @@ ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_list_min_avg_time", "hyperopt_list_max_avg_time", "hyperopt_list_min_avg_profit", "hyperopt_list_max_avg_profit", "hyperopt_list_min_total_profit", "hyperopt_list_max_total_profit", + "hyperopt_list_min_objective", "hyperopt_list_max_objective", "print_colorized", "print_json", "hyperopt_list_no_details", "export_csv"] diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 5cf1b7fce..1402e64ef 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -484,6 +484,18 @@ AVAILABLE_CLI_OPTIONS = { type=float, metavar='FLOAT', ), + "hyperopt_list_min_objective": Arg( + '--min-objective', + help='Select epochs on above objective.', + type=float, + metavar='FLOAT', + ), + "hyperopt_list_max_objective": Arg( + '--max-objective', + help='Select epochs on below objective.', + type=float, + metavar='FLOAT', + ), "hyperopt_list_no_details": Arg( '--no-details', help='Do not print best epoch details.', diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index 5b2388252..dd9de19e7 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -35,9 +35,16 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: 'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None), 'filter_max_avg_profit': config.get('hyperopt_list_max_avg_profit', None), 'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', None), - 'filter_max_total_profit': config.get('hyperopt_list_max_total_profit', None) + 'filter_max_total_profit': config.get('hyperopt_list_max_total_profit', None), + 'filter_min_objective': config.get('hyperopt_list_min_objective', None), + 'filter_max_objective': config.get('hyperopt_list_max_objective', None) } + if filteroptions['filter_min_objective'] is not None: + filteroptions['filter_min_objective'] = -filteroptions['filter_min_objective'] + if filteroptions['filter_max_objective'] is not None: + filteroptions['filter_max_objective'] = -filteroptions['filter_max_objective'] + trials_file = (config['user_data_dir'] / 'hyperopt_results' / 'hyperopt_results.pickle') @@ -92,9 +99,16 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: 'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None), 'filter_max_avg_profit': config.get('hyperopt_list_max_avg_profit', None), 'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', None), - 'filter_max_total_profit': config.get('hyperopt_list_max_total_profit', None) + 'filter_max_total_profit': config.get('hyperopt_list_max_total_profit', None), + 'filter_min_objective': config.get('hyperopt_list_min_objective', None), + 'filter_max_objective': config.get('hyperopt_list_max_objective', None) } + if filteroptions['filter_min_objective'] is not None: + filteroptions['filter_min_objective'] = -filteroptions['filter_min_objective'] + if filteroptions['filter_max_objective'] is not None: + filteroptions['filter_max_objective'] = -filteroptions['filter_max_objective'] + # Previous evaluations trials = Hyperopt.load_previous_results(trials_file) total_epochs = len(trials) @@ -175,6 +189,20 @@ def _hyperopt_filter_trials(trials: List, filteroptions: dict) -> List: x for x in trials if x['results_metrics']['profit'] < filteroptions['filter_max_total_profit'] ] + if filteroptions['filter_min_objective'] is not None: + trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] + # trials = [x for x in trials if x['loss'] != 20] + trials = [ + x for x in trials + if x['loss'] < filteroptions['filter_min_objective'] + ] + if filteroptions['filter_max_objective'] is not None: + trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] + # trials = [x for x in trials if x['loss'] != 20] + trials = [ + x for x in trials + if x['loss'] > filteroptions['filter_max_objective'] + ] logger.info(f"{len(trials)} " + ("best " if filteroptions['only_best'] else "") + diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index e5515670d..c26610336 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -334,6 +334,12 @@ class Configuration: self._args_to_config(config, argname='hyperopt_list_max_total_profit', logstring='Parameter --max-total-profit detected: {}') + self._args_to_config(config, argname='hyperopt_list_min_objective', + logstring='Parameter --min-objective detected: {}') + + self._args_to_config(config, argname='hyperopt_list_max_objective', + logstring='Parameter --max-objective detected: {}') + self._args_to_config(config, argname='hyperopt_list_no_details', logstring='Parameter --no-details detected: {}') diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 4530cd03d..2825a4679 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -868,6 +868,34 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results): pargs['config'] = None start_hyperopt_list(pargs) captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 1/12", " 2/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12", + " 9/12", " 11/12"]) + assert all(x not in captured.out + for x in [" 4/12", " 10/12", " 12/12"]) + args = [ + "hyperopt-list", + "--no-details", + "--min-objective", "0.1" + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 10/12"]) + assert all(x not in captured.out + for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", + " 9/12", " 11/12", " 12/12"]) + args = [ + "hyperopt-list", + "--no-details", + "--max-objective", "0.1" + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() assert all(x in captured.out for x in [" 1/12", " 2/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", " 11/12"]) From bf96ef08e0b7bf5c9d47a297f386f64d905a90be Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Sun, 22 Mar 2020 09:39:38 +0100 Subject: [PATCH 02/91] added # flake8: noqa C901 --- freqtrade/commands/hyperopt_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index dd9de19e7..c5cf9a969 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -10,7 +10,7 @@ from freqtrade.state import RunMode logger = logging.getLogger(__name__) - +# flake8: noqa C901 def start_hyperopt_list(args: Dict[str, Any]) -> None: """ List hyperopt epochs previously evaluated From 7143cac64f7c9d3ccd5f14d2ae689016157dcee3 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Mon, 23 Mar 2020 09:41:01 +0100 Subject: [PATCH 03/91] fixed wording of all in cli_options --- freqtrade/commands/cli_options.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 1402e64ef..e1927a901 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -450,49 +450,49 @@ AVAILABLE_CLI_OPTIONS = { ), "hyperopt_list_min_avg_time": Arg( '--min-avg-time', - help='Select epochs on above average time.', + help='Select epochs above average time.', type=float, metavar='FLOAT', ), "hyperopt_list_max_avg_time": Arg( '--max-avg-time', - help='Select epochs on under average time.', + help='Select epochs under average time.', type=float, metavar='FLOAT', ), "hyperopt_list_min_avg_profit": Arg( '--min-avg-profit', - help='Select epochs on above average profit.', + help='Select epochs above average profit.', type=float, metavar='FLOAT', ), "hyperopt_list_max_avg_profit": Arg( '--max-avg-profit', - help='Select epochs on below average profit.', + help='Select epochs below average profit.', type=float, metavar='FLOAT', ), "hyperopt_list_min_total_profit": Arg( '--min-total-profit', - help='Select epochs on above total profit.', + help='Select epochs above total profit.', type=float, metavar='FLOAT', ), "hyperopt_list_max_total_profit": Arg( '--max-total-profit', - help='Select epochs on below total profit.', + help='Select epochs below total profit.', type=float, metavar='FLOAT', ), "hyperopt_list_min_objective": Arg( '--min-objective', - help='Select epochs on above objective.', + help='Select epochs above objective.', type=float, metavar='FLOAT', ), "hyperopt_list_max_objective": Arg( '--max-objective', - help='Select epochs on below objective.', + help='Select epochs below objective.', type=float, metavar='FLOAT', ), From 0a87fe76a363db2056aee42fed2d3dd4902b48fa Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Mon, 23 Mar 2020 11:17:56 +0100 Subject: [PATCH 04/91] unified language --- freqtrade/commands/cli_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index e1927a901..47e7187a8 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -456,7 +456,7 @@ AVAILABLE_CLI_OPTIONS = { ), "hyperopt_list_max_avg_time": Arg( '--max-avg-time', - help='Select epochs under average time.', + help='Select epochs below average time.', type=float, metavar='FLOAT', ), From ef4426a65c08f767314a1a789d3b06c7b9a98072 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Fri, 27 Mar 2020 03:01:51 +0100 Subject: [PATCH 05/91] added comma --- freqtrade/commands/hyperopt_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index c5cf9a969..33abb7823 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -37,7 +37,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: 'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', None), 'filter_max_total_profit': config.get('hyperopt_list_max_total_profit', None), 'filter_min_objective': config.get('hyperopt_list_min_objective', None), - 'filter_max_objective': config.get('hyperopt_list_max_objective', None) + 'filter_max_objective': config.get('hyperopt_list_max_objective', None), } if filteroptions['filter_min_objective'] is not None: From 2fb3d94938e8d3bbe4759b8ef670f40e4c3356b0 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Sat, 22 Feb 2020 15:49:18 +0100 Subject: [PATCH 06/91] added wins/draws/losses --- freqtrade/optimize/hyperopt.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index fcf50af6a..3f704b33c 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -533,10 +533,14 @@ class Hyperopt: 'total_profit': total_profit, } - def _calculate_results_metrics(self, backtesting_results: DataFrame) -> Dict: + def _calculate_results_metrics(self, backtesting_results: DataFrame) -> Dict: return { 'trade_count': len(backtesting_results.index), + 'wins': len(backtesting_results[backtesting_results.profit_percent > 0]), + 'draws': len(backtesting_results[backtesting_results.profit_percent == 0]), + 'losses': len(backtesting_results[backtesting_results.profit_percent < 0]), 'avg_profit': backtesting_results.profit_percent.mean() * 100.0, + 'median_profit': backtesting_results.profit_percent.median() * 100.0, 'total_profit': backtesting_results.profit_abs.sum(), 'profit': backtesting_results.profit_percent.sum() * 100.0, 'duration': backtesting_results.trade_duration.mean(), @@ -548,7 +552,11 @@ class Hyperopt: """ stake_cur = self.config['stake_currency'] return (f"{results_metrics['trade_count']:6d} trades. " + f"{results_metrics['wins']:6d} wins. " + f"{results_metrics['draws']:6d} draws. " + f"{results_metrics['losses']:6d} losses. " f"Avg profit {results_metrics['avg_profit']: 6.2f}%. " + f"Median profit {results_metrics['median_profit']: 6.2f}%. " f"Total profit {results_metrics['total_profit']: 11.8f} {stake_cur} " f"({results_metrics['profit']: 7.2f}\N{GREEK CAPITAL LETTER SIGMA}%). " f"Avg duration {results_metrics['duration']:5.1f} min." From 6147498fd425d151f162e7ed0ebc855df4e1785d Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Sat, 22 Feb 2020 15:51:36 +0100 Subject: [PATCH 07/91] fixed indent --- freqtrade/optimize/hyperopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 3f704b33c..f2221d6a7 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -533,7 +533,7 @@ class Hyperopt: 'total_profit': total_profit, } - def _calculate_results_metrics(self, backtesting_results: DataFrame) -> Dict: + def _calculate_results_metrics(self, backtesting_results: DataFrame) -> Dict: return { 'trade_count': len(backtesting_results.index), 'wins': len(backtesting_results[backtesting_results.profit_percent > 0]), From 72b088d85f9b4573d35c2d165d1504aeb83caec8 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Mon, 2 Mar 2020 02:50:27 +0100 Subject: [PATCH 08/91] added test --- tests/optimize/test_hyperopt.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index b5106be0c..cdbe7f161 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -740,8 +740,10 @@ def test_generate_optimizer(mocker, default_conf) -> None: } response_expected = { 'loss': 1.9840569076926293, - 'results_explanation': (' 1 trades. Avg profit 2.31%. Total profit 0.00023300 BTC ' - '( 2.31\N{GREEK CAPITAL LETTER SIGMA}%). Avg duration 100.0 min.' + 'results_explanation': (' 1 trades. 1 wins. 0 draws. 0 losses. ' + 'Avg profit 2.31%. Median profit 2.31%. Total profit ' + '0.00023300 BTC ( 2.31\N{GREEK CAPITAL LETTER SIGMA}%). ' + 'Avg duration 100.0 min.' ).encode(locale.getpreferredencoding(), 'replace').decode('utf-8'), 'params_details': {'buy': {'adx-enabled': False, 'adx-value': 0, @@ -772,10 +774,14 @@ def test_generate_optimizer(mocker, default_conf) -> None: 'trailing_stop_positive_offset': 0.07}}, 'params_dict': optimizer_param, 'results_metrics': {'avg_profit': 2.3117, + 'draws': 0, 'duration': 100.0, + 'losses': 0, + 'median_profit': 2.3117, 'profit': 2.3117, 'total_profit': 0.000233, - 'trade_count': 1}, + 'trade_count': 1, + 'wins': 1}, 'total_profit': 0.00023300 } From 181b12b3a811bf461f602cfc7b111cc13ebaaaee Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Sat, 22 Feb 2020 15:49:18 +0100 Subject: [PATCH 09/91] added wins/draws/losses --- freqtrade/optimize/hyperopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index f2221d6a7..3f704b33c 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -533,7 +533,7 @@ class Hyperopt: 'total_profit': total_profit, } - def _calculate_results_metrics(self, backtesting_results: DataFrame) -> Dict: + def _calculate_results_metrics(self, backtesting_results: DataFrame) -> Dict: return { 'trade_count': len(backtesting_results.index), 'wins': len(backtesting_results[backtesting_results.profit_percent > 0]), From c9711678fd414e9ff7bb82496fe1ec6c98a9f5a8 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Sat, 22 Feb 2020 15:51:36 +0100 Subject: [PATCH 10/91] fixed indent --- freqtrade/optimize/hyperopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 3f704b33c..f2221d6a7 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -533,7 +533,7 @@ class Hyperopt: 'total_profit': total_profit, } - def _calculate_results_metrics(self, backtesting_results: DataFrame) -> Dict: + def _calculate_results_metrics(self, backtesting_results: DataFrame) -> Dict: return { 'trade_count': len(backtesting_results.index), 'wins': len(backtesting_results[backtesting_results.profit_percent > 0]), From f3824d970bb511d1a230fe6de713f735b81c80c7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 May 2020 20:18:38 +0200 Subject: [PATCH 11/91] Use dict for symbol_is_pair --- freqtrade/commands/list_commands.py | 2 +- freqtrade/exchange/exchange.py | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index e5131f9b2..bc4bd694f 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -163,7 +163,7 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None: tabular_data.append({'Id': v['id'], 'Symbol': v['symbol'], 'Base': v['base'], 'Quote': v['quote'], 'Active': market_is_active(v), - **({'Is pair': symbol_is_pair(v['symbol'])} + **({'Is pair': symbol_is_pair(v)} if not pairs_only else {})}) if (args.get('print_one_column', False) or diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index af745e8d0..09f700bbb 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -214,7 +214,7 @@ class Exchange: if quote_currencies: markets = {k: v for k, v in markets.items() if v['quote'] in quote_currencies} if pairs_only: - markets = {k: v for k, v in markets.items() if symbol_is_pair(v['symbol'])} + markets = {k: v for k, v in markets.items() if symbol_is_pair(v)} if active_only: markets = {k: v for k, v in markets.items() if market_is_active(v)} return markets @@ -1210,7 +1210,7 @@ def timeframe_to_next_date(timeframe: str, date: datetime = None) -> datetime: return datetime.fromtimestamp(new_timestamp, tz=timezone.utc) -def symbol_is_pair(market_symbol: str, base_currency: str = None, +def symbol_is_pair(market_symbol: Dict[str, Any], base_currency: str = None, quote_currency: str = None) -> bool: """ Check if the market symbol is a pair, i.e. that its symbol consists of the base currency and the @@ -1218,10 +1218,12 @@ def symbol_is_pair(market_symbol: str, base_currency: str = None, it also checks that the symbol contains appropriate base and/or quote currency part before and after the separating character correspondingly. """ - symbol_parts = market_symbol.split('/') + symbol_parts = market_symbol['symbol'].split('/') return (len(symbol_parts) == 2 and - (symbol_parts[0] == base_currency if base_currency else len(symbol_parts[0]) > 0) and - (symbol_parts[1] == quote_currency if quote_currency else len(symbol_parts[1]) > 0)) + (market_symbol.get('base') == base_currency + if base_currency else len(symbol_parts[0]) > 0) and + (market_symbol.get('quote') == quote_currency + if quote_currency else len(symbol_parts[1]) > 0)) def market_is_active(market: Dict) -> bool: From b22e3a67d86b27d06ab2c1b4bee0d8f0b2f0f382 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jun 2020 20:29:48 +0200 Subject: [PATCH 12/91] rename symbol_is_pair to market_is_tradable Make it part of the exchange class, so subclasses can override this --- freqtrade/commands/list_commands.py | 4 ++-- freqtrade/exchange/__init__.py | 3 +-- freqtrade/exchange/exchange.py | 31 +++++++++++++---------------- freqtrade/exchange/ftx.py | 12 ++++++++++- freqtrade/exchange/kraken.py | 12 ++++++++++- 5 files changed, 39 insertions(+), 23 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index bc4bd694f..503f8a4ee 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -14,7 +14,7 @@ from freqtrade.configuration import setup_utils_configuration from freqtrade.constants import USERPATH_HYPEROPTS, USERPATH_STRATEGIES from freqtrade.exceptions import OperationalException from freqtrade.exchange import (available_exchanges, ccxt_exchanges, - market_is_active, symbol_is_pair) + market_is_active) from freqtrade.misc import plural from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.state import RunMode @@ -163,7 +163,7 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None: tabular_data.append({'Id': v['id'], 'Symbol': v['symbol'], 'Base': v['base'], 'Quote': v['quote'], 'Active': market_is_active(v), - **({'Is pair': symbol_is_pair(v)} + **({'Is pair': exchange.market_is_tradable(v)} if not pairs_only else {})}) if (args.get('print_one_column', False) or diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index a39f8f5df..bdf1f91ec 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -12,8 +12,7 @@ from freqtrade.exchange.exchange import (timeframe_to_seconds, timeframe_to_msecs, timeframe_to_next_date, timeframe_to_prev_date) -from freqtrade.exchange.exchange import (market_is_active, - symbol_is_pair) +from freqtrade.exchange.exchange import (market_is_active) from freqtrade.exchange.kraken import Kraken from freqtrade.exchange.binance import Binance from freqtrade.exchange.bibox import Bibox diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 09f700bbb..bec8b9686 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -214,7 +214,7 @@ class Exchange: if quote_currencies: markets = {k: v for k, v in markets.items() if v['quote'] in quote_currencies} if pairs_only: - markets = {k: v for k, v in markets.items() if symbol_is_pair(v)} + markets = {k: v for k, v in markets.items() if self.symbol_is_pair(v)} if active_only: markets = {k: v for k, v in markets.items() if market_is_active(v)} return markets @@ -238,6 +238,19 @@ class Exchange: """ return self.markets.get(pair, {}).get('base', '') + def market_is_tradable(self, market: Dict[str, Any]) -> bool: + """ + Check if the market symbol is tradable by Freqtrade. + By default, checks if it's splittable by `/` and both sides correspond to base / quote + """ + symbol_parts = market['symbol'].split('/') + return (len(symbol_parts) == 2 and + len(symbol_parts[0]) > 0 and + len(symbol_parts[1]) > 0 and + symbol_parts[0] == market.get('base') and + symbol_parts[1] == market.get('quote') + ) + def klines(self, pair_interval: Tuple[str, str], copy: bool = True) -> DataFrame: if pair_interval in self._klines: return self._klines[pair_interval].copy() if copy else self._klines[pair_interval] @@ -1210,22 +1223,6 @@ def timeframe_to_next_date(timeframe: str, date: datetime = None) -> datetime: return datetime.fromtimestamp(new_timestamp, tz=timezone.utc) -def symbol_is_pair(market_symbol: Dict[str, Any], base_currency: str = None, - quote_currency: str = None) -> bool: - """ - Check if the market symbol is a pair, i.e. that its symbol consists of the base currency and the - quote currency separated by '/' character. If base_currency and/or quote_currency is passed, - it also checks that the symbol contains appropriate base and/or quote currency part before - and after the separating character correspondingly. - """ - symbol_parts = market_symbol['symbol'].split('/') - return (len(symbol_parts) == 2 and - (market_symbol.get('base') == base_currency - if base_currency else len(symbol_parts[0]) > 0) and - (market_symbol.get('quote') == quote_currency - if quote_currency else len(symbol_parts[1]) > 0)) - - def market_is_active(market: Dict) -> bool: """ Return True if the market is active. diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 75915122b..cad11bbfa 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -1,6 +1,6 @@ """ FTX exchange subclass """ import logging -from typing import Dict +from typing import Any, Dict from freqtrade.exchange import Exchange @@ -12,3 +12,13 @@ class Ftx(Exchange): _ft_has: Dict = { "ohlcv_candle_limit": 1500, } + + def market_is_tradable(self, market: Dict[str, Any]) -> bool: + """ + Check if the market symbol is tradable by Freqtrade. + Default checks + check if pair is darkpool pair. + """ + parent_check = super().market_is_tradable(market) + + return (parent_check and + market.get('spot', False) is True) diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 932d82a27..af75ef9b2 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -1,6 +1,6 @@ """ Kraken exchange subclass """ import logging -from typing import Dict +from typing import Any, Dict import ccxt @@ -21,6 +21,16 @@ class Kraken(Exchange): "trades_pagination_arg": "since", } + def market_is_tradable(self, market: Dict[str, Any]) -> bool: + """ + Check if the market symbol is tradable by Freqtrade. + Default checks + check if pair is darkpool pair. + """ + parent_check = super().market_is_tradable(market) + + return (parent_check and + market.get('darkpool', False) is False) + @retrier def get_balances(self) -> dict: if self._config['dry_run']: From b74a3addc65514aa8f7494ca995caa4bee74c4b5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jun 2020 20:30:31 +0200 Subject: [PATCH 13/91] Update tests --- freqtrade/exchange/ftx.py | 2 +- tests/exchange/test_exchange.py | 55 +++++++++++++++++++++------------ 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index cad11bbfa..e5f083fb6 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -16,7 +16,7 @@ class Ftx(Exchange): def market_is_tradable(self, market: Dict[str, Any]) -> bool: """ Check if the market symbol is tradable by Freqtrade. - Default checks + check if pair is darkpool pair. + Default checks + check if pair is spot pair (no futures trading yet). """ parent_check = super().market_is_tradable(market) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index e40f691a8..b87acc27c 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -15,7 +15,7 @@ from freqtrade.exceptions import (DependencyException, InvalidOrderException, OperationalException, TemporaryError) from freqtrade.exchange import Binance, Exchange, Kraken from freqtrade.exchange.common import API_RETRY_COUNT -from freqtrade.exchange.exchange import (market_is_active, symbol_is_pair, +from freqtrade.exchange.exchange import (market_is_active, timeframe_to_minutes, timeframe_to_msecs, timeframe_to_next_date, @@ -2117,25 +2117,42 @@ def test_timeframe_to_next_date(): assert timeframe_to_next_date("5m") > date -@pytest.mark.parametrize("market_symbol,base_currency,quote_currency,expected_result", [ - ("BTC/USDT", None, None, True), - ("USDT/BTC", None, None, True), - ("BTCUSDT", None, None, False), - ("BTC/USDT", None, "USDT", True), - ("USDT/BTC", None, "USDT", False), - ("BTCUSDT", None, "USDT", False), - ("BTC/USDT", "BTC", None, True), - ("USDT/BTC", "BTC", None, False), - ("BTCUSDT", "BTC", None, False), - ("BTC/USDT", "BTC", "USDT", True), - ("BTC/USDT", "USDT", "BTC", False), - ("BTC/USDT", "BTC", "USD", False), - ("BTCUSDT", "BTC", "USDT", False), - ("BTC/", None, None, False), - ("/USDT", None, None, False), +@pytest.mark.parametrize("market_symbol,base,quote,exchange,add_dict,expected_result", [ + ("BTC/USDT", 'BTC', 'USDT', "binance", {}, True), + ("USDT/BTC", 'USDT', 'BTC', "binance", {}, True), + ("USDT/BTC", 'BTC', 'USDT', "binance", {}, False), # Reversed currencies + ("BTCUSDT", 'BTC', 'USDT', "binance", {}, False), # No seperating / + ("BTCUSDT", None, "USDT", "binance", {}, False), # + ("USDT/BTC", "BTC", None, "binance", {}, False), + ("BTCUSDT", "BTC", None, "binance", {}, False), + ("BTC/USDT", "BTC", "USDT", "binance", {}, True), + ("BTC/USDT", "USDT", "BTC", "binance", {}, False), # reversed currencies + ("BTC/USDT", "BTC", "USD", "binance", {}, False), # Wrong quote currency + ("BTC/", "BTC", 'UNK', "binance", {}, False), + ("/USDT", 'UNK', 'USDT', "binance", {}, False), + ("BTC/EUR", 'BTC', 'EUR', "kraken", {"darkpool": False}, True), + ("EUR/BTC", 'EUR', 'BTC', "kraken", {"darkpool": False}, True), + ("EUR/BTC", 'BTC', 'EUR', "kraken", {"darkpool": False}, False), # Reversed currencies + ("BTC/EUR", 'BTC', 'USD', "kraken", {"darkpool": False}, False), # wrong quote currency + ("BTC/EUR", 'BTC', 'EUR', "kraken", {"darkpool": True}, False), # no darkpools + ("BTC/EUR.d", 'BTC', 'EUR', "kraken", {"darkpool": True}, False), # no darkpools + ("BTC/USD", 'BTC', 'USD', "ftx", {'spot': True}, True), + ("USD/BTC", 'USD', 'BTC', "ftx", {'spot': True}, True), + ("BTC/USD", 'BTC', 'USDT', "ftx", {'spot': True}, False), # Wrong quote currency + ("BTC/USD", 'USD', 'BTC', "ftx", {'spot': True}, False), # Reversed currencies + ("BTC/USD", 'BTC', 'USD', "ftx", {'spot': False}, False), # Can only trade spot markets + ("BTC-PERP", 'BTC', 'USD', "ftx", {'spot': False}, False), # Can only trade spot markets ]) -def test_symbol_is_pair(market_symbol, base_currency, quote_currency, expected_result) -> None: - assert symbol_is_pair(market_symbol, base_currency, quote_currency) == expected_result +def test_market_is_tradable(mocker, default_conf, market_symbol, base, + quote, add_dict, exchange, expected_result) -> None: + ex = get_patched_exchange(mocker, default_conf, id=exchange) + market = { + 'symbol': market_symbol, + 'base': base, + 'quote': quote, + **(add_dict), + } + assert ex.market_is_tradable(market) == expected_result @pytest.mark.parametrize("market,expected_result", [ From 08049d23b48242c5c468102b4ae8cb182a9236cc Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jun 2020 20:41:29 +0200 Subject: [PATCH 14/91] Use "market_is_tradable" for whitelist validation --- freqtrade/exchange/exchange.py | 2 +- freqtrade/pairlist/IPairList.py | 5 +++++ tests/pairlist/test_pairlist.py | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index bec8b9686..a2bb8627a 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -214,7 +214,7 @@ class Exchange: if quote_currencies: markets = {k: v for k, v in markets.items() if v['quote'] in quote_currencies} if pairs_only: - markets = {k: v for k, v in markets.items() if self.symbol_is_pair(v)} + markets = {k: v for k, v in markets.items() if self.market_is_tradable(v)} if active_only: markets = {k: v for k, v in markets.items() if market_is_active(v)} return markets diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index f48a7dcfd..61fdc73ad 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -159,6 +159,11 @@ class IPairList(ABC): f"{self._exchange.name}. Removing it from whitelist..") continue + if not self._exchange.market_is_tradable(markets[pair]): + logger.warning(f"Pair {pair} is not tradable with Freqtrade." + "Removing it from whitelist..") + continue + if self._exchange.get_pair_quote_currency(pair) != self._config['stake_currency']: logger.warning(f"Pair {pair} is not compatible with your stake currency " f"{self._config['stake_currency']}. Removing it from whitelist..") diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 421f06911..c6d1eeb38 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -400,7 +400,9 @@ def test_pairlist_class(mocker, whitelist_conf, markets, pairlist): # BCH/BTC not available (['ETH/BTC', 'TKN/BTC', 'BCH/BTC'], "is not compatible with exchange"), # BTT/BTC is inactive - (['ETH/BTC', 'TKN/BTC', 'BTT/BTC'], "Market is not active") + (['ETH/BTC', 'TKN/BTC', 'BTT/BTC'], "Market is not active"), + # XLTCUSDT is not a valid pair + (['ETH/BTC', 'TKN/BTC', 'XLTCUSDT'], "is not tradable with Freqtrade"), ]) def test__whitelist_for_active_markets(mocker, whitelist_conf, markets, pairlist, whitelist, caplog, log_message, tickers): From 2f759825e4c670af51e4e17b074f96f48ff5ce16 Mon Sep 17 00:00:00 2001 From: Confucius-The-Great <40965179+qkum@users.noreply.github.com> Date: Tue, 30 Jun 2020 11:01:00 +0200 Subject: [PATCH 15/91] Update faq.md Major changes :) --- docs/faq.md | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 151b2c054..cc43e326d 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,5 +1,9 @@ # Freqtrade FAQ +## Beginner Tips & Tricks + +#1 When you work with your strategy & hyperopt file you should use a real programmer software like Pycharm. If you by accident moved some code and freqtrade says error and you cant find the place where you moved something, or you cant find line 180 where you messed something up. Then a program like Pycharm shows you where line 180 is in your strategy file so you can fix the problem, or Pycharm shows you with some color marking that "here is a line of code that does not belong here" and you found your error in no time! This will save you many hours of problemsolving when working with the bot. Pycharm also got a usefull "Debug" feature that can tell you exactly what command on that line is making the error :) + ## Freqtrade common issues ### The bot does not start @@ -15,10 +19,12 @@ This could have the following reasons: ### I have waited 5 minutes, why hasn't the bot made any trades yet?! -Depending on the buy strategy, the amount of whitelisted coins, the +#1 Depending on the buy strategy, the amount of whitelisted coins, the situation of the market etc, it can take up to hours to find good entry position for a trade. Be patient! +#2 Or it may because you made an human error? Like writing --dry-run when you wanted to trade live?. Maybe an error with the exchange API? Or something else. You will have to do the hard work of finding out the root cause of the problem :) + ### I have made 12 trades already, why is my total profit negative?! I understand your disappointment but unfortunately 12 trades is just @@ -129,25 +135,25 @@ to find a great result (unless if you are very lucky), so you probably have to run it for 10.000 or more. But it will take an eternity to compute. -We recommend you to run it at least 10.000 epochs: +We recommend you to run between 500-1000 epochs over and over untill you hit at least 10.000 epocs in total. You can best judge by looking at the results - if the bot keep discovering more profitable strategies or not. ```bash -freqtrade hyperopt -e 10000 +freqtrade hyperopt -e 1000 ``` or if you want intermediate result to see ```bash -for i in {1..100}; do freqtrade hyperopt -e 100; done +for i in {1..100}; do freqtrade hyperopt -e 1000; done ``` -### Why it is so long to run hyperopt? +### Why does it take so long time to run hyperopt? -Finding a great Hyperopt results takes time. +#1 Discovering a great strategy with Hyperopt takes time. Study www.freqtrade.io, the Freqtrade Github page, join the Freqtrade Discord - or something totally else. While you patiently wait for the most advanced, public known, crypto bot, in the world, to hand you a possible golden strategy specially designed just for you =) -If you wonder why it takes a while to find great hyperopt results +#2 If you wonder why it can take from 20 minutes to days to do 1000 epocs here are some answers: -This answer was written during the under the release 0.15.1, when we had: +This answer was written during the release 0.15.1, when we had: - 8 triggers - 9 guards: let's say we evaluate even 10 values from each @@ -157,7 +163,10 @@ The following calculation is still very rough and not very precise but it will give the idea. With only these triggers and guards there is already 8\*10^9\*10 evaluations. A roughly total of 80 billion evals. Did you run 100 000 evals? Congrats, you've done roughly 1 / 100 000 th -of the search space. +of the search space. If we assume that the bot never test the same strategy more than once. + +#3 The time it takes to run 1000 hyperopt epocs depends on things like: The cpu, harddisk, ram, motherboard, indicator settings, indicator count, amount of coins that hyperopt test strategies on, trade count - can be 650 trades in a year or 10.0000 trades depending on if the strategy aims for a high profit rarely or a low profit many many many times. Example: 4% profit 650 times vs 0,3% profit a trade 10.000 times in a year. If we assume you set the --timerange to 365 days. +Example: freqtrade --config config_mcd_1.json --strategy mcd_1 --hyperopt mcd_hyperopt_1 -e 1000 --timerange 20190601-20200601 ## Edge module From d13cb4c05569e1116f07f5fef0fb896f42c13b7d Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 15 Jul 2020 19:49:51 +0200 Subject: [PATCH 16/91] Introduce safe_value_fallback_2 --- freqtrade/exchange/exchange.py | 6 ++--- freqtrade/freqtradebot.py | 4 ++-- freqtrade/misc.py | 16 ++++++++++++- tests/test_misc.py | 42 +++++++++++++++++++++++----------- 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index a3a548176..5c4e4c530 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -24,7 +24,7 @@ from freqtrade.exceptions import (DDosProtection, ExchangeError, InvalidOrderException, OperationalException, RetryableOrderError, TemporaryError) from freqtrade.exchange.common import BAD_EXCHANGES, retrier, retrier_async -from freqtrade.misc import deep_merge_dicts, safe_value_fallback +from freqtrade.misc import deep_merge_dicts, safe_value_fallback2 CcxtModuleType = Any @@ -1139,7 +1139,7 @@ class Exchange: if fee_curr in self.get_pair_base_currency(order['symbol']): # Base currency - divide by amount return round( - order['fee']['cost'] / safe_value_fallback(order, order, 'filled', 'amount'), 8) + order['fee']['cost'] / safe_value_fallback2(order, order, 'filled', 'amount'), 8) elif fee_curr in self.get_pair_quote_currency(order['symbol']): # Quote currency - divide by cost return round(order['fee']['cost'] / order['cost'], 8) if order['cost'] else None @@ -1152,7 +1152,7 @@ class Exchange: comb = self.get_valid_pair_combination(fee_curr, self._config['stake_currency']) tick = self.fetch_ticker(comb) - fee_to_quote_rate = safe_value_fallback(tick, tick, 'last', 'ask') + fee_to_quote_rate = safe_value_fallback2(tick, tick, 'last', 'ask') return round((order['fee']['cost'] * fee_to_quote_rate) / order['cost'], 8) except ExchangeError: return None diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ab7c2b527..9a0b3c40f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -20,7 +20,7 @@ from freqtrade.edge import Edge from freqtrade.exceptions import (DependencyException, ExchangeError, InvalidOrderException, PricingError) from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date -from freqtrade.misc import safe_value_fallback +from freqtrade.misc import safe_value_fallback2 from freqtrade.pairlist.pairlistmanager import PairListManager from freqtrade.persistence import Trade from freqtrade.resolvers import ExchangeResolver, StrategyResolver @@ -984,7 +984,7 @@ class FreqtradeBot: logger.info('Buy order %s for %s.', reason, trade) # Using filled to determine the filled amount - filled_amount = safe_value_fallback(corder, order, 'filled', 'filled') + filled_amount = safe_value_fallback2(corder, order, 'filled', 'filled') if isclose(filled_amount, 0.0, abs_tol=constants.MATH_CLOSE_PREC): logger.info('Buy order fully cancelled. Removing %s from database.', trade) diff --git a/freqtrade/misc.py b/freqtrade/misc.py index ac6084eb7..623f6cb8f 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -134,7 +134,21 @@ def round_dict(d, n): return {k: (round(v, n) if isinstance(v, float) else v) for k, v in d.items()} -def safe_value_fallback(dict1: dict, dict2: dict, key1: str, key2: str, default_value=None): +def safe_value_fallback(obj: dict, key1: str, key2: str, default_value=None): + """ + Search a value in obj, return this if it's not None. + Then search key2 in obj - return that if it's not none - then use default_value. + Else falls back to None. + """ + if key1 in obj and obj[key1] is not None: + return obj[key1] + else: + if key2 in obj and obj[key2] is not None: + return obj[key2] + return default_value + + +def safe_value_fallback2(dict1: dict, dict2: dict, key1: str, key2: str, default_value=None): """ Search a value in dict1, return this if it's not None. Fall back to dict2 - return key2 from dict2 if it's not None. diff --git a/tests/test_misc.py b/tests/test_misc.py index 9fd6164d5..a185cbba4 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -11,7 +11,7 @@ from freqtrade.misc import (datesarray_to_datetimearray, file_dump_json, file_load_json, format_ms_time, pair_to_filename, plural, render_template, render_template_with_fallback, safe_value_fallback, - shorten_date) + safe_value_fallback2, shorten_date) def test_shorten_date() -> None: @@ -96,24 +96,40 @@ def test_format_ms_time() -> None: def test_safe_value_fallback(): + dict1 = {'keya': None, 'keyb': 2, 'keyc': 5, 'keyd': None} + assert safe_value_fallback(dict1, 'keya', 'keyb') == 2 + assert safe_value_fallback(dict1, 'keyb', 'keya') == 2 + + assert safe_value_fallback(dict1, 'keyb', 'keyc') == 2 + assert safe_value_fallback(dict1, 'keya', 'keyc') == 5 + + assert safe_value_fallback(dict1, 'keyc', 'keyb') == 5 + + assert safe_value_fallback(dict1, 'keya', 'keyd') is None + + assert safe_value_fallback(dict1, 'keyNo', 'keyNo') is None + assert safe_value_fallback(dict1, 'keyNo', 'keyNo', 55) == 55 + + +def test_safe_value_fallback2(): dict1 = {'keya': None, 'keyb': 2, 'keyc': 5, 'keyd': None} dict2 = {'keya': 20, 'keyb': None, 'keyc': 6, 'keyd': None} - assert safe_value_fallback(dict1, dict2, 'keya', 'keya') == 20 - assert safe_value_fallback(dict2, dict1, 'keya', 'keya') == 20 + assert safe_value_fallback2(dict1, dict2, 'keya', 'keya') == 20 + assert safe_value_fallback2(dict2, dict1, 'keya', 'keya') == 20 - assert safe_value_fallback(dict1, dict2, 'keyb', 'keyb') == 2 - assert safe_value_fallback(dict2, dict1, 'keyb', 'keyb') == 2 + assert safe_value_fallback2(dict1, dict2, 'keyb', 'keyb') == 2 + assert safe_value_fallback2(dict2, dict1, 'keyb', 'keyb') == 2 - assert safe_value_fallback(dict1, dict2, 'keyc', 'keyc') == 5 - assert safe_value_fallback(dict2, dict1, 'keyc', 'keyc') == 6 + assert safe_value_fallback2(dict1, dict2, 'keyc', 'keyc') == 5 + assert safe_value_fallback2(dict2, dict1, 'keyc', 'keyc') == 6 - assert safe_value_fallback(dict1, dict2, 'keyd', 'keyd') is None - assert safe_value_fallback(dict2, dict1, 'keyd', 'keyd') is None - assert safe_value_fallback(dict2, dict1, 'keyd', 'keyd', 1234) == 1234 + assert safe_value_fallback2(dict1, dict2, 'keyd', 'keyd') is None + assert safe_value_fallback2(dict2, dict1, 'keyd', 'keyd') is None + assert safe_value_fallback2(dict2, dict1, 'keyd', 'keyd', 1234) == 1234 - assert safe_value_fallback(dict1, dict2, 'keyNo', 'keyNo') is None - assert safe_value_fallback(dict2, dict1, 'keyNo', 'keyNo') is None - assert safe_value_fallback(dict2, dict1, 'keyNo', 'keyNo', 1234) == 1234 + assert safe_value_fallback2(dict1, dict2, 'keyNo', 'keyNo') is None + assert safe_value_fallback2(dict2, dict1, 'keyNo', 'keyNo') is None + assert safe_value_fallback2(dict2, dict1, 'keyNo', 'keyNo', 1234) == 1234 def test_plural() -> None: From c826f7a7077715f71416dd77001d927b858b0184 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 15 Jul 2020 20:15:29 +0200 Subject: [PATCH 17/91] Add amount_requested to database --- freqtrade/persistence.py | 9 ++++++--- tests/test_persistence.py | 6 ++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index a6c1de402..bdbb7628a 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -86,7 +86,7 @@ def check_migrate(engine) -> None: logger.debug(f'trying {table_back_name}') # Check for latest column - if not has_column(cols, 'timeframe'): + if not has_column(cols, 'amount_requested'): logger.info(f'Running database migration - backup available as {table_back_name}') fee_open = get_column_def(cols, 'fee_open', 'fee') @@ -119,6 +119,7 @@ def check_migrate(engine) -> None: cols, 'close_profit_abs', f"(amount * close_rate * (1 - {fee_close})) - {open_trade_price}") sell_order_status = get_column_def(cols, 'sell_order_status', 'null') + amount_requested = get_column_def(cols, 'amount_requested', 'amount') # Schema migration necessary engine.execute(f"alter table trades rename to {table_back_name}") @@ -134,7 +135,7 @@ def check_migrate(engine) -> None: fee_open, fee_open_cost, fee_open_currency, fee_close, fee_close_cost, fee_open_currency, open_rate, open_rate_requested, close_rate, close_rate_requested, close_profit, - stake_amount, amount, open_date, close_date, open_order_id, + stake_amount, amount, amount_requested, open_date, close_date, open_order_id, stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct, stoploss_order_id, stoploss_last_update, max_rate, min_rate, sell_reason, sell_order_status, strategy, @@ -153,7 +154,7 @@ def check_migrate(engine) -> None: {fee_close_cost} fee_close_cost, {fee_close_currency} fee_close_currency, open_rate, {open_rate_requested} open_rate_requested, close_rate, {close_rate_requested} close_rate_requested, close_profit, - stake_amount, amount, open_date, close_date, open_order_id, + stake_amount, amount, {amount_requested}, open_date, close_date, open_order_id, {stop_loss} stop_loss, {stop_loss_pct} stop_loss_pct, {initial_stop_loss} initial_stop_loss, {initial_stop_loss_pct} initial_stop_loss_pct, @@ -215,6 +216,7 @@ class Trade(_DECL_BASE): close_profit_abs = Column(Float) stake_amount = Column(Float, nullable=False) amount = Column(Float) + amount_requested = Column(Float) open_date = Column(DateTime, nullable=False, default=datetime.utcnow) close_date = Column(DateTime) open_order_id = Column(String) @@ -256,6 +258,7 @@ class Trade(_DECL_BASE): 'is_open': self.is_open, 'exchange': self.exchange, 'amount': round(self.amount, 8), + 'amount_requested': round(self.amount_requested, 8), 'stake_amount': round(self.stake_amount, 8), 'strategy': self.strategy, 'ticker_interval': self.timeframe, # DEPRECATED diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 8dd27e53a..c39b2015e 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -457,6 +457,7 @@ def test_migrate_old(mocker, default_conf, fee): assert trade.close_rate_requested is None assert trade.is_open == 1 assert trade.amount == amount + assert trade.amount_requested == amount assert trade.stake_amount == default_conf.get("stake_amount") assert trade.pair == "ETC/BTC" assert trade.exchange == "bittrex" @@ -546,6 +547,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert trade.close_rate_requested is None assert trade.is_open == 1 assert trade.amount == amount + assert trade.amount_requested == amount assert trade.stake_amount == default_conf.get("stake_amount") assert trade.pair == "ETC/BTC" assert trade.exchange == "binance" @@ -725,6 +727,7 @@ def test_to_json(default_conf, fee): pair='ETH/BTC', stake_amount=0.001, amount=123.0, + amount_requested=123.0, fee_open=fee.return_value, fee_close=fee.return_value, open_date=arrow.utcnow().shift(hours=-2).datetime, @@ -757,6 +760,7 @@ def test_to_json(default_conf, fee): 'close_rate': None, 'close_rate_requested': None, 'amount': 123.0, + 'amount_requested': 123.0, 'stake_amount': 0.001, 'close_profit': None, 'close_profit_abs': None, @@ -786,6 +790,7 @@ def test_to_json(default_conf, fee): pair='XRP/BTC', stake_amount=0.001, amount=100.0, + amount_requested=101.0, fee_open=fee.return_value, fee_close=fee.return_value, open_date=arrow.utcnow().shift(hours=-2).datetime, @@ -808,6 +813,7 @@ def test_to_json(default_conf, fee): 'open_rate': 0.123, 'close_rate': 0.125, 'amount': 100.0, + 'amount_requested': 101.0, 'stake_amount': 0.001, 'stop_loss': None, 'stop_loss_abs': None, From eafab38db3b35d66ba927b0bc69934c40178bdde Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 15 Jul 2020 20:20:14 +0200 Subject: [PATCH 18/91] Complete implementation of amount_requested --- freqtrade/freqtradebot.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 9a0b3c40f..c1149b8c2 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -532,6 +532,7 @@ class FreqtradeBot: # we assume the order is executed at the price requested buy_limit_filled_price = buy_limit_requested + amount_requested = amount if order_status == 'expired' or order_status == 'rejected': order_tif = self.strategy.order_time_in_force['buy'] @@ -568,6 +569,7 @@ class FreqtradeBot: pair=pair, stake_amount=stake_amount, amount=amount, + amount_requested=amount_requested, fee_open=fee, fee_close=fee, open_rate=buy_limit_filled_price, From c1c018d8feb1843c18271f821dd5d36185db3d13 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 15 Jul 2020 20:27:00 +0200 Subject: [PATCH 19/91] Fix tests that require amount_requested --- tests/conftest.py | 3 +++ tests/rpc/test_rpc.py | 2 ++ tests/rpc/test_rpc_apiserver.py | 3 +++ 3 files changed, 8 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 43dc8ca78..8501a98b9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -176,6 +176,7 @@ def create_mock_trades(fee): pair='ETH/BTC', stake_amount=0.001, amount=123.0, + amount_requested=123.0, fee_open=fee.return_value, fee_close=fee.return_value, open_rate=0.123, @@ -188,6 +189,7 @@ def create_mock_trades(fee): pair='ETC/BTC', stake_amount=0.001, amount=123.0, + amount_requested=123.0, fee_open=fee.return_value, fee_close=fee.return_value, open_rate=0.123, @@ -204,6 +206,7 @@ def create_mock_trades(fee): pair='ETC/BTC', stake_amount=0.001, amount=123.0, + amount_requested=124.0, fee_open=fee.return_value, fee_close=fee.return_value, open_rate=0.123, diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index de9327ba9..ddbbe395c 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -80,6 +80,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'close_rate': None, 'current_rate': 1.099e-05, 'amount': 91.07468124, + 'amount_requested': 91.07468124, 'stake_amount': 0.001, 'close_profit': None, 'close_profit_pct': None, @@ -143,6 +144,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'close_rate': None, 'current_rate': ANY, 'amount': 91.07468124, + 'amount_requested': 91.07468124, 'stake_amount': 0.001, 'close_profit': None, 'close_profit_pct': None, diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 355b63f48..13c11d29d 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -520,6 +520,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets): assert_response(rc) assert len(rc.json) == 1 assert rc.json == [{'amount': 91.07468124, + 'amount_requested': 91.07468124, 'base_currency': 'BTC', 'close_date': None, 'close_date_hum': None, @@ -641,6 +642,7 @@ def test_api_forcebuy(botclient, mocker, fee): fbuy_mock = MagicMock(return_value=Trade( pair='ETH/ETH', amount=1, + amount_requested=1, exchange='bittrex', stake_amount=1, open_rate=0.245441, @@ -657,6 +659,7 @@ def test_api_forcebuy(botclient, mocker, fee): data='{"pair": "ETH/BTC"}') assert_response(rc) assert rc.json == {'amount': 1, + 'amount_requested': 1, 'trade_id': None, 'close_date': None, 'close_date_hum': None, From 3721736aafbcbd06b8cfd1e94e8244360d481b7b Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 15 Jul 2020 20:28:07 +0200 Subject: [PATCH 20/91] Convert to real amount before placing order to keep the correct amount in the database --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index c1149b8c2..5c0de94a1 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -523,7 +523,7 @@ class FreqtradeBot: time_in_force=time_in_force): logger.info(f"User requested abortion of buying {pair}") return False - + amount = self.exchange.amount_to_precision(pair, amount) order = self.exchange.buy(pair=pair, ordertype=order_type, amount=amount, rate=buy_limit_requested, time_in_force=time_in_force) From 98f2e79f27292b8e8d6feac5c6cd00660635c069 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 15 Jul 2020 20:51:52 +0200 Subject: [PATCH 21/91] Adjust tests to use correctly trimmed amount --- freqtrade/persistence.py | 2 +- tests/rpc/test_rpc.py | 8 ++++---- tests/rpc/test_rpc_apiserver.py | 6 +++--- tests/rpc/test_rpc_telegram.py | 12 ++++++------ tests/test_freqtradebot.py | 20 ++++++++++---------- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index bdbb7628a..245b1c790 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -276,7 +276,7 @@ class Trade(_DECL_BASE): 'open_timestamp': int(self.open_date.timestamp() * 1000), 'open_rate': self.open_rate, 'open_rate_requested': self.open_rate_requested, - 'open_trade_price': self.open_trade_price, + 'open_trade_price': round(self.open_trade_price, 8), 'close_date_hum': (arrow.get(self.close_date).humanize() if self.close_date else None), diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index ddbbe395c..2d5370e1e 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -79,8 +79,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'open_rate': 1.098e-05, 'close_rate': None, 'current_rate': 1.099e-05, - 'amount': 91.07468124, - 'amount_requested': 91.07468124, + 'amount': 91.07468123, + 'amount_requested': 91.07468123, 'stake_amount': 0.001, 'close_profit': None, 'close_profit_pct': None, @@ -143,8 +143,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'open_rate': 1.098e-05, 'close_rate': None, 'current_rate': ANY, - 'amount': 91.07468124, - 'amount_requested': 91.07468124, + 'amount': 91.07468123, + 'amount_requested': 91.07468123, 'stake_amount': 0.001, 'close_profit': None, 'close_profit_pct': None, diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 13c11d29d..c7259bdc6 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -519,8 +519,8 @@ def test_api_status(botclient, mocker, ticker, fee, markets): rc = client_get(client, f"{BASE_URI}/status") assert_response(rc) assert len(rc.json) == 1 - assert rc.json == [{'amount': 91.07468124, - 'amount_requested': 91.07468124, + assert rc.json == [{'amount': 91.07468123, + 'amount_requested': 91.07468123, 'base_currency': 'BTC', 'close_date': None, 'close_date_hum': None, @@ -696,7 +696,7 @@ def test_api_forcebuy(botclient, mocker, fee): 'min_rate': None, 'open_order_id': '123456', 'open_rate_requested': None, - 'open_trade_price': 0.2460546025, + 'open_trade_price': 0.24605460, 'sell_reason': None, 'sell_order_status': None, 'strategy': None, diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 0a4352f5b..669c6dc89 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -690,7 +690,7 @@ def test_reload_config_handle(default_conf, update, mocker) -> None: assert 'reloading config' in msg_mock.call_args_list[0][0][0] -def test_forcesell_handle(default_conf, update, ticker, fee, +def test_telegram_forcesell_handle(default_conf, update, ticker, fee, ticker_sell_up, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) @@ -729,7 +729,7 @@ def test_forcesell_handle(default_conf, update, ticker, fee, 'pair': 'ETH/BTC', 'gain': 'profit', 'limit': 1.173e-05, - 'amount': 91.07468123861567, + 'amount': 91.07468123, 'order_type': 'limit', 'open_rate': 1.098e-05, 'current_rate': 1.173e-05, @@ -743,8 +743,8 @@ def test_forcesell_handle(default_conf, update, ticker, fee, } == last_msg -def test_forcesell_down_handle(default_conf, update, ticker, fee, - ticker_sell_down, mocker) -> None: +def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee, + ticker_sell_down, mocker) -> None: mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) @@ -788,7 +788,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee, 'pair': 'ETH/BTC', 'gain': 'loss', 'limit': 1.043e-05, - 'amount': 91.07468123861567, + 'amount': 91.07468123, 'order_type': 'limit', 'open_rate': 1.098e-05, 'current_rate': 1.043e-05, @@ -836,7 +836,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None 'pair': 'ETH/BTC', 'gain': 'loss', 'limit': 1.099e-05, - 'amount': 91.07468123861567, + 'amount': 91.07468123, 'order_type': 'limit', 'open_rate': 1.098e-05, 'current_rate': 1.099e-05, diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index ada0d87fd..685b269d7 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -595,7 +595,7 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, freqtrade.create_trade('ETH/BTC') rate, amount = buy_mock.call_args[1]['rate'], buy_mock.call_args[1]['amount'] - assert rate * amount >= default_conf['stake_amount'] + assert rate * amount <= default_conf['stake_amount'] def test_create_trade_too_small_stake_amount(default_conf, ticker, limit_buy_order, @@ -782,7 +782,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order, assert trade.open_date is not None assert trade.exchange == 'bittrex' assert trade.open_rate == 0.00001098 - assert trade.amount == 91.07468123861567 + assert trade.amount == 91.07468123 assert log_has( 'Buy signal found: about create a new trade with stake_amount: 0.001 ...', caplog @@ -1009,7 +1009,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order) -> None: call_args = buy_mm.call_args_list[0][1] assert call_args['pair'] == pair assert call_args['rate'] == bid - assert call_args['amount'] == stake_amount / bid + assert call_args['amount'] == round(stake_amount / bid, 8) buy_rate_mock.reset_mock() # Should create an open trade with an open order id @@ -1029,7 +1029,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order) -> None: call_args = buy_mm.call_args_list[1][1] assert call_args['pair'] == pair assert call_args['rate'] == fix_price - assert call_args['amount'] == stake_amount / fix_price + assert call_args['amount'] == round(stake_amount / fix_price, 8) # In case of closed order limit_buy_order['status'] = 'closed' @@ -1407,7 +1407,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, assert freqtrade.handle_stoploss_on_exchange(trade) is False cancel_order_mock.assert_called_once_with(100, 'ETH/BTC') - stoploss_order_mock.assert_called_once_with(amount=85.32423208191126, + stoploss_order_mock.assert_called_once_with(amount=85.32423208, pair='ETH/BTC', order_types=freqtrade.strategy.order_types, stop_price=0.00002346 * 0.95) @@ -1595,7 +1595,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, # stoploss should be set to 1% as trailing is on assert trade.stop_loss == 0.00002346 * 0.99 cancel_order_mock.assert_called_once_with(100, 'NEO/BTC') - stoploss_order_mock.assert_called_once_with(amount=2132892.491467577, + stoploss_order_mock.assert_called_once_with(amount=2132892.49146757, pair='NEO/BTC', order_types=freqtrade.strategy.order_types, stop_price=0.00002346 * 0.99) @@ -2577,7 +2577,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N 'pair': 'ETH/BTC', 'gain': 'profit', 'limit': 1.172e-05, - 'amount': 91.07468123861567, + 'amount': 91.07468123, 'order_type': 'limit', 'open_rate': 1.098e-05, 'current_rate': 1.173e-05, @@ -2626,7 +2626,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) 'pair': 'ETH/BTC', 'gain': 'loss', 'limit': 1.044e-05, - 'amount': 91.07468123861567, + 'amount': 91.07468123, 'order_type': 'limit', 'open_rate': 1.098e-05, 'current_rate': 1.043e-05, @@ -2682,7 +2682,7 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe 'pair': 'ETH/BTC', 'gain': 'loss', 'limit': 1.08801e-05, - 'amount': 91.07468123861567, + 'amount': 91.07468123, 'order_type': 'limit', 'open_rate': 1.098e-05, 'current_rate': 1.043e-05, @@ -2887,7 +2887,7 @@ def test_execute_sell_market_order(default_conf, ticker, fee, 'pair': 'ETH/BTC', 'gain': 'profit', 'limit': 1.172e-05, - 'amount': 91.07468123861567, + 'amount': 91.07468123, 'order_type': 'market', 'open_rate': 1.098e-05, 'current_rate': 1.173e-05, From de46744aa9c80437767e86cf77aa95e8c43cddbd Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 15 Jul 2020 21:02:31 +0200 Subject: [PATCH 22/91] Use filled before amount for order data closes #3579 --- freqtrade/exchange/exchange.py | 1 - freqtrade/freqtradebot.py | 11 ++++++----- freqtrade/persistence.py | 3 ++- tests/rpc/test_rpc_telegram.py | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 5c4e4c530..e6ea75a63 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1167,7 +1167,6 @@ class Exchange: return (order['fee']['cost'], order['fee']['currency'], self.calculate_fee_rate(order)) - # calculate rate ? (order['fee']['cost'] / (order['amount'] * order['price'])) def is_exchange_bad(exchange_name: str) -> bool: diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5c0de94a1..5da7223c4 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -20,7 +20,7 @@ from freqtrade.edge import Edge from freqtrade.exceptions import (DependencyException, ExchangeError, InvalidOrderException, PricingError) from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date -from freqtrade.misc import safe_value_fallback2 +from freqtrade.misc import safe_value_fallback, safe_value_fallback2 from freqtrade.pairlist.pairlistmanager import PairListManager from freqtrade.persistence import Trade from freqtrade.resolvers import ExchangeResolver, StrategyResolver @@ -553,14 +553,14 @@ class FreqtradeBot: order['filled'], order['amount'], order['remaining'] ) stake_amount = order['cost'] - amount = order['amount'] + amount = order['filled'] buy_limit_filled_price = order['price'] order_id = None # in case of FOK the order may be filled immediately and fully elif order_status == 'closed': stake_amount = order['cost'] - amount = order['amount'] + amount = safe_value_fallback(order, 'filled', 'amount') buy_limit_filled_price = order['price'] # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL @@ -1249,7 +1249,8 @@ class FreqtradeBot: # Try update amount (binance-fix) try: new_amount = self.get_real_amount(trade, order, order_amount) - if not isclose(order['amount'], new_amount, abs_tol=constants.MATH_CLOSE_PREC): + if not isclose(safe_value_fallback(order, 'filled', 'amount'), new_amount, + abs_tol=constants.MATH_CLOSE_PREC): order['amount'] = new_amount order.pop('filled', None) trade.recalc_open_trade_price() @@ -1295,7 +1296,7 @@ class FreqtradeBot: """ # Init variables if order_amount is None: - order_amount = order['amount'] + order_amount = safe_value_fallback(order, 'filled', 'amount') # Only run for closed orders if trade.fee_updated(order.get('side', '')) or order['status'] == 'open': return order_amount diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 245b1c790..7dc533c07 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -17,6 +17,7 @@ from sqlalchemy.orm.session import sessionmaker from sqlalchemy.pool import StaticPool from freqtrade.exceptions import OperationalException +from freqtrade.misc import safe_value_fallback logger = logging.getLogger(__name__) @@ -376,7 +377,7 @@ class Trade(_DECL_BASE): if order_type in ('market', 'limit') and order['side'] == 'buy': # Update open rate and actual amount self.open_rate = Decimal(order['price']) - self.amount = Decimal(order.get('filled', order['amount'])) + self.amount = Decimal(safe_value_fallback(order, 'filled', 'amount')) self.recalc_open_trade_price() logger.info('%s_BUY has been fulfilled for %s.', order_type.upper(), self) self.open_order_id = None diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 669c6dc89..08d4dc7ec 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -691,7 +691,7 @@ def test_reload_config_handle(default_conf, update, mocker) -> None: def test_telegram_forcesell_handle(default_conf, update, ticker, fee, - ticker_sell_up, mocker) -> None: + ticker_sell_up, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) From dd3a2675b53c30e9f3ff23e9799d2cfd41352fb0 Mon Sep 17 00:00:00 2001 From: thopd88 <20041501+thopd88@users.noreply.github.com> Date: Sun, 19 Jul 2020 22:02:53 +0700 Subject: [PATCH 23/91] Add telegram trades command to list recent trades --- freqtrade/rpc/telegram.py | 43 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 13cc1afaf..72dbd2ea7 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -92,6 +92,7 @@ class Telegram(RPC): CommandHandler('stop', self._stop), CommandHandler('forcesell', self._forcesell), CommandHandler('forcebuy', self._forcebuy), + CommandHandler('trades', self._trades), CommandHandler('performance', self._performance), CommandHandler('daily', self._daily), CommandHandler('count', self._count), @@ -496,6 +497,48 @@ class Telegram(RPC): except RPCException as e: self._send_msg(str(e)) + @authorized_only + def _trades(self, update: Update, context: CallbackContext) -> None: + """ + Handler for /trades + Returns last n recent trades. + :param bot: telegram bot + :param update: message update + :return: None + """ + stake_cur = self._config['stake_currency'] + fiat_disp_cur = self._config.get('fiat_display_currency', '') + try: + nrecent = int(context.args[0]) + except (TypeError, ValueError, IndexError): + nrecent = 10 + try: + trades = self._rpc_trade_history( + nrecent + ) + trades_tab = tabulate( + [[trade['open_date'], + trade['pair'], + f"{trade['open_rate']}", + f"{trade['stake_amount']}", + trade['close_date'], + f"{trade['close_rate']}", + f"{trade['close_profit_abs']}"] for trade in trades['trades']], + headers=[ + 'Open Date', + 'Pair', + 'Open rate', + 'Stake Amount', + 'Close date', + 'Close rate', + 'Profit', + ], + tablefmt='simple') + message = f'{nrecent} recent trades:\n
{trades_tab}
' + self._send_msg(message, parse_mode=ParseMode.HTML) + except RPCException as e: + self._send_msg(str(e)) + @authorized_only def _performance(self, update: Update, context: CallbackContext) -> None: """ From 08fdd7d86331c5a2c55c69c130350a7b21a104e3 Mon Sep 17 00:00:00 2001 From: thopd88 <20041501+thopd88@users.noreply.github.com> Date: Sun, 19 Jul 2020 22:10:59 +0700 Subject: [PATCH 24/91] Add telegram /delete command to delete tradeid code inspired from _rpc_forcesell --- freqtrade/rpc/rpc.py | 22 ++++++++++++++++++++++ freqtrade/rpc/telegram.py | 19 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index c73fcbf54..2a3feea25 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -537,6 +537,28 @@ class RPC: else: return None + def _rpc_delete(self, trade_id: str) -> Dict[str, str]: + """ + Handler for delete . + Delete the given trade + """ + def _exec_delete(trade: Trade) -> None: + Trade.session.delete(trade) + Trade.session.flush() + + with self._freqtrade._sell_lock: + trade = Trade.get_trades( + trade_filter=[Trade.id == trade_id, ] + ).first() + if not trade: + logger.warning('delete: Invalid argument received') + raise RPCException('invalid argument') + + _exec_delete(trade) + Trade.session.flush() + self._freqtrade.wallets.update() + return {'result': f'Deleted trade {trade_id}.'} + def _rpc_performance(self) -> List[Dict[str, Any]]: """ Handler for performance. diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 72dbd2ea7..474376938 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -93,6 +93,7 @@ class Telegram(RPC): CommandHandler('forcesell', self._forcesell), CommandHandler('forcebuy', self._forcebuy), CommandHandler('trades', self._trades), + CommandHandler('delete', self._delete), CommandHandler('performance', self._performance), CommandHandler('daily', self._daily), CommandHandler('count', self._count), @@ -497,6 +498,24 @@ class Telegram(RPC): except RPCException as e: self._send_msg(str(e)) + @authorized_only + def _delete(self, update: Update, context: CallbackContext) -> None: + """ + Handler for /delete . + Delete the given trade + :param bot: telegram bot + :param update: message update + :return: None + """ + + trade_id = context.args[0] if len(context.args) > 0 else None + try: + msg = self._rpc_delete(trade_id) + self._send_msg('Delete Result: `{result}`'.format(**msg)) + + except RPCException as e: + self._send_msg(str(e)) + @authorized_only def _trades(self, update: Update, context: CallbackContext) -> None: """ From 28f4a1101e58e38df1fddc22ff573e852a80de33 Mon Sep 17 00:00:00 2001 From: thopd88 <20041501+thopd88@users.noreply.github.com> Date: Mon, 20 Jul 2020 10:54:17 +0700 Subject: [PATCH 25/91] Revert "Add telegram /delete command to delete tradeid" This reverts commit 08fdd7d86331c5a2c55c69c130350a7b21a104e3. --- freqtrade/rpc/rpc.py | 22 ---------------------- freqtrade/rpc/telegram.py | 19 ------------------- 2 files changed, 41 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 2a3feea25..c73fcbf54 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -537,28 +537,6 @@ class RPC: else: return None - def _rpc_delete(self, trade_id: str) -> Dict[str, str]: - """ - Handler for delete . - Delete the given trade - """ - def _exec_delete(trade: Trade) -> None: - Trade.session.delete(trade) - Trade.session.flush() - - with self._freqtrade._sell_lock: - trade = Trade.get_trades( - trade_filter=[Trade.id == trade_id, ] - ).first() - if not trade: - logger.warning('delete: Invalid argument received') - raise RPCException('invalid argument') - - _exec_delete(trade) - Trade.session.flush() - self._freqtrade.wallets.update() - return {'result': f'Deleted trade {trade_id}.'} - def _rpc_performance(self) -> List[Dict[str, Any]]: """ Handler for performance. diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 474376938..72dbd2ea7 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -93,7 +93,6 @@ class Telegram(RPC): CommandHandler('forcesell', self._forcesell), CommandHandler('forcebuy', self._forcebuy), CommandHandler('trades', self._trades), - CommandHandler('delete', self._delete), CommandHandler('performance', self._performance), CommandHandler('daily', self._daily), CommandHandler('count', self._count), @@ -498,24 +497,6 @@ class Telegram(RPC): except RPCException as e: self._send_msg(str(e)) - @authorized_only - def _delete(self, update: Update, context: CallbackContext) -> None: - """ - Handler for /delete . - Delete the given trade - :param bot: telegram bot - :param update: message update - :return: None - """ - - trade_id = context.args[0] if len(context.args) > 0 else None - try: - msg = self._rpc_delete(trade_id) - self._send_msg('Delete Result: `{result}`'.format(**msg)) - - except RPCException as e: - self._send_msg(str(e)) - @authorized_only def _trades(self, update: Update, context: CallbackContext) -> None: """ From eaa7370174e1b5188192e99bb205f2f03e8063b0 Mon Sep 17 00:00:00 2001 From: thopd88 <20041501+thopd88@users.noreply.github.com> Date: Mon, 20 Jul 2020 11:08:18 +0700 Subject: [PATCH 26/91] add /delete command --- freqtrade/rpc/rpc.py | 22 ++++++++++++++++++++++ freqtrade/rpc/telegram.py | 20 ++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index c73fcbf54..b76259ad2 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -536,6 +536,28 @@ class RPC: return trade else: return None + + def _rpc_delete(self, trade_id: str) -> Dict[str, str]: + """ + Handler for delete . + Delete the given trade + """ + def _exec_delete(trade: Trade) -> None: + Trade.session.delete(trade) + Trade.session.flush() + + with self._freqtrade._sell_lock: + trade = Trade.get_trades( + trade_filter=[Trade.id == trade_id, ] + ).first() + if not trade: + logger.warning('delete: Invalid argument received') + raise RPCException('invalid argument') + + _exec_delete(trade) + Trade.session.flush() + self._freqtrade.wallets.update() + return {'result': f'Deleted trade {trade_id}.'} def _rpc_performance(self) -> List[Dict[str, Any]]: """ diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 13cc1afaf..7d006c4e5 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -92,6 +92,7 @@ class Telegram(RPC): CommandHandler('stop', self._stop), CommandHandler('forcesell', self._forcesell), CommandHandler('forcebuy', self._forcebuy), + CommandHandler('delete', self._delete), CommandHandler('performance', self._performance), CommandHandler('daily', self._daily), CommandHandler('count', self._count), @@ -496,6 +497,24 @@ class Telegram(RPC): except RPCException as e: self._send_msg(str(e)) + @authorized_only + def _delete(self, update: Update, context: CallbackContext) -> None: + """ + Handler for /delete . + Delete the given trade + :param bot: telegram bot + :param update: message update + :return: None + """ + + trade_id = context.args[0] if len(context.args) > 0 else None + try: + msg = self._rpc_delete(trade_id) + self._send_msg('Delete Result: `{result}`'.format(**msg)) + + except RPCException as e: + self._send_msg(str(e)) + @authorized_only def _performance(self, update: Update, context: CallbackContext) -> None: """ @@ -613,6 +632,7 @@ class Telegram(RPC): "*/forcesell |all:* `Instantly sells the given trade or all trades, " "regardless of profit`\n" f"{forcebuy_text if self._config.get('forcebuy_enable', False) else ''}" + "*/delete :* `Instantly delete the given trade in the database`\n" "*/performance:* `Show performance of each finished trade grouped by pair`\n" "*/daily :* `Shows profit or loss per day, over the last n days`\n" "*/count:* `Show number of trades running compared to allowed number of trades`" From 4c97527b041234d6865d24fb490963b1c17b88ea Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 Jul 2020 19:11:15 +0200 Subject: [PATCH 27/91] FIx failing test --- freqtrade/rpc/telegram.py | 2 -- tests/rpc/test_rpc_telegram.py | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 72dbd2ea7..343b26072 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -506,8 +506,6 @@ class Telegram(RPC): :param update: message update :return: None """ - stake_cur = self._config['stake_currency'] - fiat_disp_cur = self._config.get('fiat_display_currency', '') try: nrecent = int(context.args[0]) except (TypeError, ValueError, IndexError): diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 0a4352f5b..1ea211584 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -60,7 +60,7 @@ def test__init__(default_conf, mocker) -> None: assert telegram._config == default_conf -def test_init(default_conf, mocker, caplog) -> None: +def test_telegram_init(default_conf, mocker, caplog) -> None: start_polling = MagicMock() mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock(return_value=start_polling)) @@ -72,7 +72,7 @@ def test_init(default_conf, mocker, caplog) -> None: assert start_polling.start_polling.call_count == 1 message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], " - "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], " + "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['trades'], " "['performance'], ['daily'], ['count'], ['reload_config', 'reload_conf'], " "['show_config', 'show_conf'], ['stopbuy'], ['whitelist'], ['blacklist'], " "['edge'], ['help'], ['version']]") From 47748961697994c82fa30b8b825699382ae0d446 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 Jul 2020 19:39:12 +0200 Subject: [PATCH 28/91] Evaluate average before price in order returns --- freqtrade/exchange/exchange.py | 1 + freqtrade/freqtradebot.py | 4 ++-- freqtrade/persistence.py | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index e6ea75a63..9858eb518 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -475,6 +475,7 @@ class Exchange: "id": order_id, 'pair': pair, 'price': rate, + 'average': rate, 'amount': _amount, 'cost': _amount * rate, 'type': ordertype, diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5da7223c4..8c5b5b460 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -554,14 +554,14 @@ class FreqtradeBot: ) stake_amount = order['cost'] amount = order['filled'] - buy_limit_filled_price = order['price'] + buy_limit_filled_price = safe_value_fallback(order, 'average', 'price') order_id = None # in case of FOK the order may be filled immediately and fully elif order_status == 'closed': stake_amount = order['cost'] amount = safe_value_fallback(order, 'filled', 'amount') - buy_limit_filled_price = order['price'] + buy_limit_filled_price = safe_value_fallback(order, 'average', 'price') # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 7dc533c07..fdb816eab 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -369,20 +369,20 @@ class Trade(_DECL_BASE): """ order_type = order['type'] # Ignore open and cancelled orders - if order['status'] == 'open' or order['price'] is None: + if order['status'] == 'open' or safe_value_fallback(order, 'average', 'price') is None: return logger.info('Updating trade (id=%s) ...', self.id) if order_type in ('market', 'limit') and order['side'] == 'buy': # Update open rate and actual amount - self.open_rate = Decimal(order['price']) + self.open_rate = Decimal(safe_value_fallback(order, 'average', 'price')) self.amount = Decimal(safe_value_fallback(order, 'filled', 'amount')) self.recalc_open_trade_price() logger.info('%s_BUY has been fulfilled for %s.', order_type.upper(), self) self.open_order_id = None elif order_type in ('market', 'limit') and order['side'] == 'sell': - self.close(order['price']) + self.close(safe_value_fallback(order, 'average', 'price')) logger.info('%s_SELL has been fulfilled for %s.', order_type.upper(), self) elif order_type in ('stop_loss_limit', 'stop-loss', 'stop'): self.stoploss_order_id = None From 0502fe0496382e6cc2a04161ad3a15f4f9f40e26 Mon Sep 17 00:00:00 2001 From: thopd88 <20041501+thopd88@users.noreply.github.com> Date: Thu, 23 Jul 2020 09:36:05 +0700 Subject: [PATCH 29/91] New /trades 3 columns and exclude open trades --- freqtrade/rpc/telegram.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 343b26072..87a0cdd62 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -5,6 +5,7 @@ This module manage Telegram communication """ import json import logging +import arrow from typing import Any, Callable, Dict from tabulate import tabulate @@ -506,6 +507,7 @@ class Telegram(RPC): :param update: message update :return: None """ + stake_cur = self._config['stake_currency'] try: nrecent = int(context.args[0]) except (TypeError, ValueError, IndexError): @@ -515,21 +517,13 @@ class Telegram(RPC): nrecent ) trades_tab = tabulate( - [[trade['open_date'], + [[arrow.get(trade['open_date']).humanize(), trade['pair'], - f"{trade['open_rate']}", - f"{trade['stake_amount']}", - trade['close_date'], - f"{trade['close_rate']}", - f"{trade['close_profit_abs']}"] for trade in trades['trades']], + f"{(100 * trade['close_profit']):.2f}% ({trade['close_profit_abs']})"] for trade in trades['trades'] if trade['close_profit'] is not None], headers=[ 'Open Date', 'Pair', - 'Open rate', - 'Stake Amount', - 'Close date', - 'Close rate', - 'Profit', + f'Profit ({stake_cur})', ], tablefmt='simple') message = f'{nrecent} recent trades:\n
{trades_tab}
' From a3daf8e41c209e061a3e50f1b5fcba3802bf6421 Mon Sep 17 00:00:00 2001 From: thopd88 <20041501+thopd88@users.noreply.github.com> Date: Thu, 23 Jul 2020 09:47:53 +0700 Subject: [PATCH 30/91] Fix line too long --- freqtrade/rpc/telegram.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 87a0cdd62..943d092db 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -519,7 +519,8 @@ class Telegram(RPC): trades_tab = tabulate( [[arrow.get(trade['open_date']).humanize(), trade['pair'], - f"{(100 * trade['close_profit']):.2f}% ({trade['close_profit_abs']})"] for trade in trades['trades'] if trade['close_profit'] is not None], + f"{(100 * trade['close_profit']):.2f}% ({trade['close_profit_abs']})"] + for trade in trades['trades'] if trade['close_profit'] is not None], headers=[ 'Open Date', 'Pair', From 0bad55637e5e5907d9133df6973c1926056ac6d0 Mon Sep 17 00:00:00 2001 From: thopd88 <20041501+thopd88@users.noreply.github.com> Date: Thu, 23 Jul 2020 10:12:52 +0700 Subject: [PATCH 31/91] fix flake8 indent error --- freqtrade/rpc/telegram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 943d092db..66583fa53 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -520,7 +520,7 @@ class Telegram(RPC): [[arrow.get(trade['open_date']).humanize(), trade['pair'], f"{(100 * trade['close_profit']):.2f}% ({trade['close_profit_abs']})"] - for trade in trades['trades'] if trade['close_profit'] is not None], + for trade in trades['trades'] if trade['close_profit'] is not None], headers=[ 'Open Date', 'Pair', From 0f18b2a0d45d3056cd52830f3081b359895bb044 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 23 Jul 2020 07:12:14 +0200 Subject: [PATCH 32/91] Add test and fix case where no trades were closed yet --- freqtrade/rpc/telegram.py | 3 ++- tests/rpc/test_rpc_telegram.py | 35 ++++++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 66583fa53..153be1e25 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -527,7 +527,8 @@ class Telegram(RPC): f'Profit ({stake_cur})', ], tablefmt='simple') - message = f'{nrecent} recent trades:\n
{trades_tab}
' + message = (f"{min(trades['trades_count'], nrecent)} recent trades:\n" + + (f"
{trades_tab}
" if trades['trades_count'] > 0 else '')) self._send_msg(message, parse_mode=ParseMode.HTML) except RPCException as e: self._send_msg(str(e)) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 1ea211584..f011b631d 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -21,8 +21,9 @@ from freqtrade.rpc import RPCMessageType from freqtrade.rpc.telegram import Telegram, authorized_only from freqtrade.state import State from freqtrade.strategy.interface import SellType -from tests.conftest import (get_patched_freqtradebot, log_has, patch_exchange, - patch_get_signal, patch_whitelist) +from tests.conftest import (create_mock_trades, get_patched_freqtradebot, + log_has, patch_exchange, patch_get_signal, + patch_whitelist) class DummyCls(Telegram): @@ -1143,6 +1144,36 @@ def test_edge_enabled(edge_conf, update, mocker) -> None: assert 'Pair Winrate Expectancy Stoploss' in msg_mock.call_args_list[0][0][0] +def test_telegram_trades(mocker, update, default_conf, fee): + msg_mock = MagicMock() + mocker.patch.multiple( + 'freqtrade.rpc.telegram.Telegram', + _init=MagicMock(), + _send_msg=msg_mock + ) + + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + telegram = Telegram(freqtradebot) + context = MagicMock() + context.args = [] + + telegram._trades(update=update, context=context) + assert "0 recent trades:" in msg_mock.call_args_list[0][0][0] + assert "
" not in msg_mock.call_args_list[0][0][0]
+
+    msg_mock.reset_mock()
+    create_mock_trades(fee)
+
+    context = MagicMock()
+    context.args = [5]
+    telegram._trades(update=update, context=context)
+    msg_mock.call_count == 1
+    assert "3 recent trades:" in msg_mock.call_args_list[0][0][0]
+    assert "Profit (" in msg_mock.call_args_list[0][0][0]
+    assert "Open Date" in msg_mock.call_args_list[0][0][0]
+    assert "
" in msg_mock.call_args_list[0][0][0]
+
+
 def test_help_handle(default_conf, update, mocker) -> None:
     msg_mock = MagicMock()
     mocker.patch.multiple(

From 8300eb59d4b448b4f04da34b80efd603a32a6002 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Thu, 23 Jul 2020 07:49:44 +0200
Subject: [PATCH 33/91] Extend create_mock_trades to create 4 trades

2 closed, and 2 open trades
---
 tests/commands/test_commands.py |  2 +-
 tests/conftest.py               | 14 ++++++++++++++
 tests/data/test_btanalysis.py   |  2 +-
 tests/test_freqtradebot.py      |  2 +-
 tests/test_persistence.py       |  6 +++---
 5 files changed, 20 insertions(+), 6 deletions(-)

diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py
index ffced956d..3ec7e4798 100644
--- a/tests/commands/test_commands.py
+++ b/tests/commands/test_commands.py
@@ -1089,7 +1089,7 @@ def test_show_trades(mocker, fee, capsys, caplog):
     pargs = get_args(args)
     pargs['config'] = None
     start_show_trades(pargs)
-    assert log_has("Printing 3 Trades: ", caplog)
+    assert log_has("Printing 4 Trades: ", caplog)
     captured = capsys.readouterr()
     assert "Trade(id=1" in captured.out
     assert "Trade(id=2" in captured.out
diff --git a/tests/conftest.py b/tests/conftest.py
index 43dc8ca78..fe8d54480 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -199,6 +199,20 @@ def create_mock_trades(fee):
     )
     Trade.session.add(trade)
 
+    trade = Trade(
+        pair='XRP/BTC',
+        stake_amount=0.001,
+        amount=123.0,
+        fee_open=fee.return_value,
+        fee_close=fee.return_value,
+        open_rate=0.05,
+        close_rate=0.06,
+        close_profit=0.01,
+        exchange='bittrex',
+        is_open=False,
+    )
+    Trade.session.add(trade)
+
     # Simulate prod entry
     trade = Trade(
         pair='ETC/BTC',
diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py
index b65db7fd8..718c02f05 100644
--- a/tests/data/test_btanalysis.py
+++ b/tests/data/test_btanalysis.py
@@ -43,7 +43,7 @@ def test_load_trades_from_db(default_conf, fee, mocker):
 
     trades = load_trades_from_db(db_url=default_conf['db_url'])
     assert init_mock.call_count == 1
-    assert len(trades) == 3
+    assert len(trades) == 4
     assert isinstance(trades, DataFrame)
     assert "pair" in trades.columns
     assert "open_time" in trades.columns
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
index ada0d87fd..54e33be4d 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -4090,7 +4090,7 @@ def test_cancel_all_open_orders(mocker, default_conf, fee, limit_buy_order, limi
     freqtrade = get_patched_freqtradebot(mocker, default_conf)
     create_mock_trades(fee)
     trades = Trade.query.all()
-    assert len(trades) == 3
+    assert len(trades) == 4
     freqtrade.cancel_all_open_orders()
     assert buy_mock.call_count == 1
     assert sell_mock.call_count == 1
diff --git a/tests/test_persistence.py b/tests/test_persistence.py
index 8dd27e53a..ab23243a5 100644
--- a/tests/test_persistence.py
+++ b/tests/test_persistence.py
@@ -989,7 +989,7 @@ def test_get_overall_performance(fee):
     create_mock_trades(fee)
     res = Trade.get_overall_performance()
 
-    assert len(res) == 1
+    assert len(res) == 2
     assert 'pair' in res[0]
     assert 'profit' in res[0]
     assert 'count' in res[0]
@@ -1004,5 +1004,5 @@ def test_get_best_pair(fee):
     create_mock_trades(fee)
     res = Trade.get_best_pair()
     assert len(res) == 2
-    assert res[0] == 'ETC/BTC'
-    assert res[1] == 0.005
+    assert res[0] == 'XRP/BTC'
+    assert res[1] == 0.01

From fdc84eef5905d81a69076199990d3e7bf999e938 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Thu, 23 Jul 2020 07:50:45 +0200
Subject: [PATCH 34/91] /trades shall only return closed trades

---
 freqtrade/rpc/rpc.py            |  5 +++--
 freqtrade/rpc/telegram.py       |  2 +-
 tests/rpc/test_rpc.py           | 11 +++++------
 tests/rpc/test_rpc_apiserver.py |  8 ++++----
 tests/rpc/test_rpc_telegram.py  |  2 +-
 5 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index c73fcbf54..b39d5aec4 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -252,9 +252,10 @@ class RPC:
     def _rpc_trade_history(self, limit: int) -> Dict:
         """ Returns the X last trades """
         if limit > 0:
-            trades = Trade.get_trades().order_by(Trade.id.desc()).limit(limit)
+            trades = Trade.get_trades([Trade.is_open.is_(False)]).order_by(
+                Trade.id.desc()).limit(limit)
         else:
-            trades = Trade.get_trades().order_by(Trade.id.desc()).all()
+            trades = Trade.get_trades([Trade.is_open.is_(False)]).order_by(Trade.id.desc()).all()
 
         output = [trade.to_json() for trade in trades]
 
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 153be1e25..17f0e21f9 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -520,7 +520,7 @@ class Telegram(RPC):
                 [[arrow.get(trade['open_date']).humanize(),
                   trade['pair'],
                   f"{(100 * trade['close_profit']):.2f}% ({trade['close_profit_abs']})"]
-                 for trade in trades['trades'] if trade['close_profit'] is not None],
+                 for trade in trades['trades']],
                 headers=[
                     'Open Date',
                     'Pair',
diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py
index de9327ba9..e5859fcd9 100644
--- a/tests/rpc/test_rpc.py
+++ b/tests/rpc/test_rpc.py
@@ -284,12 +284,11 @@ def test_rpc_trade_history(mocker, default_conf, markets, fee):
     assert isinstance(trades['trades'][1], dict)
 
     trades = rpc._rpc_trade_history(0)
-    assert len(trades['trades']) == 3
-    assert trades['trades_count'] == 3
-    # The first trade is for ETH ... sorting is descending
-    assert trades['trades'][-1]['pair'] == 'ETH/BTC'
-    assert trades['trades'][0]['pair'] == 'ETC/BTC'
-    assert trades['trades'][1]['pair'] == 'ETC/BTC'
+    assert len(trades['trades']) == 2
+    assert trades['trades_count'] == 2
+    # The first closed trade is for ETC ... sorting is descending
+    assert trades['trades'][-1]['pair'] == 'ETC/BTC'
+    assert trades['trades'][0]['pair'] == 'XRP/BTC'
 
 
 def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py
index 355b63f48..f4d7b8ca3 100644
--- a/tests/rpc/test_rpc_apiserver.py
+++ b/tests/rpc/test_rpc_apiserver.py
@@ -368,12 +368,12 @@ def test_api_trades(botclient, mocker, ticker, fee, markets):
 
     rc = client_get(client, f"{BASE_URI}/trades")
     assert_response(rc)
-    assert len(rc.json['trades']) == 3
-    assert rc.json['trades_count'] == 3
-    rc = client_get(client, f"{BASE_URI}/trades?limit=2")
-    assert_response(rc)
     assert len(rc.json['trades']) == 2
     assert rc.json['trades_count'] == 2
+    rc = client_get(client, f"{BASE_URI}/trades?limit=1")
+    assert_response(rc)
+    assert len(rc.json['trades']) == 1
+    assert rc.json['trades_count'] == 1
 
 
 def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index f011b631d..cfe0ade6f 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -1168,7 +1168,7 @@ def test_telegram_trades(mocker, update, default_conf, fee):
     context.args = [5]
     telegram._trades(update=update, context=context)
     msg_mock.call_count == 1
-    assert "3 recent trades:" in msg_mock.call_args_list[0][0][0]
+    assert "2 recent trades:" in msg_mock.call_args_list[0][0][0]
     assert "Profit (" in msg_mock.call_args_list[0][0][0]
     assert "Open Date" in msg_mock.call_args_list[0][0][0]
     assert "
" in msg_mock.call_args_list[0][0][0]

From e0c14e6214a79e25d0c6b1d6b189001d89d89e4f Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Thu, 23 Jul 2020 07:54:45 +0200
Subject: [PATCH 35/91] Add /trades to help (so users know about it)

---
 docs/telegram-usage.md    | 1 +
 freqtrade/rpc/telegram.py | 1 +
 2 files changed, 2 insertions(+)

diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md
index f423a9376..250293d25 100644
--- a/docs/telegram-usage.md
+++ b/docs/telegram-usage.md
@@ -56,6 +56,7 @@ official commands. You can ask at any moment for help with `/help`.
 | `/show_config` | | Shows part of the current configuration with relevant settings to operation
 | `/status` | | Lists all open trades
 | `/status table` | | List all open trades in a table format. Pending buy orders are marked with an asterisk (*) Pending sell orders are marked with a double asterisk (**)
+| `/trades [limit]` | | List all recently closed trades in a table format.
 | `/count` | | Displays number of trades used and available
 | `/profit` | | Display a summary of your profit/loss from close trades and some stats about your performance
 | `/forcesell ` | | Instantly sells the given trade  (Ignoring `minimum_roi`).
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 17f0e21f9..ab784c962 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -646,6 +646,7 @@ class Telegram(RPC):
                    "         *table :* `will display trades in a table`\n"
                    "                `pending buy orders are marked with an asterisk (*)`\n"
                    "                `pending sell orders are marked with a double asterisk (**)`\n"
+                   "*/trades [limit]:* `Lists last closed trades (limited to 10 by default)`\n"
                    "*/profit:* `Lists cumulative profit from all finished trades`\n"
                    "*/forcesell |all:* `Instantly sells the given trade or all trades, "
                    "regardless of profit`\n"

From 6ce4fd7aff2c9cd78d86b4830e172526eb26877d Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sun, 26 Jul 2020 08:37:10 +0000
Subject: [PATCH 36/91] Bump arrow from 0.15.7 to 0.15.8

Bumps [arrow](https://github.com/arrow-py/arrow) from 0.15.7 to 0.15.8.
- [Release notes](https://github.com/arrow-py/arrow/releases)
- [Changelog](https://github.com/arrow-py/arrow/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/arrow-py/arrow/compare/0.15.7...0.15.8)

Signed-off-by: dependabot[bot] 
---
 requirements-common.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements-common.txt b/requirements-common.txt
index d5c5fd832..63b2c0441 100644
--- a/requirements-common.txt
+++ b/requirements-common.txt
@@ -3,7 +3,7 @@
 ccxt==1.31.37
 SQLAlchemy==1.3.18
 python-telegram-bot==12.8
-arrow==0.15.7
+arrow==0.15.8
 cachetools==4.1.1
 requests==2.24.0
 urllib3==1.25.9

From d1d6f69e43b31491edfc01efb74a12da1349e57e Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sun, 26 Jul 2020 08:37:13 +0000
Subject: [PATCH 37/91] Bump scipy from 1.5.1 to 1.5.2

Bumps [scipy](https://github.com/scipy/scipy) from 1.5.1 to 1.5.2.
- [Release notes](https://github.com/scipy/scipy/releases)
- [Commits](https://github.com/scipy/scipy/compare/v1.5.1...v1.5.2)

Signed-off-by: dependabot[bot] 
---
 requirements-hyperopt.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt
index 4773d9877..ce08f08e0 100644
--- a/requirements-hyperopt.txt
+++ b/requirements-hyperopt.txt
@@ -2,7 +2,7 @@
 -r requirements.txt
 
 # Required for hyperopt
-scipy==1.5.1
+scipy==1.5.2
 scikit-learn==0.23.1
 scikit-optimize==0.7.4
 filelock==3.0.12

From 2ff03e173d3634128f47666a3777e0f838ad9374 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sun, 26 Jul 2020 08:37:17 +0000
Subject: [PATCH 38/91] Bump numpy from 1.19.0 to 1.19.1

Bumps [numpy](https://github.com/numpy/numpy) from 1.19.0 to 1.19.1.
- [Release notes](https://github.com/numpy/numpy/releases)
- [Changelog](https://github.com/numpy/numpy/blob/master/doc/HOWTO_RELEASE.rst.txt)
- [Commits](https://github.com/numpy/numpy/compare/v1.19.0...v1.19.1)

Signed-off-by: dependabot[bot] 
---
 requirements.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements.txt b/requirements.txt
index 1e61d165f..2392d4cb2 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
 # Load common requirements
 -r requirements-common.txt
 
-numpy==1.19.0
+numpy==1.19.1
 pandas==1.0.5

From 838743bf01590b885e5bb1ddf5cacf009d02f673 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sun, 26 Jul 2020 08:37:25 +0000
Subject: [PATCH 39/91] Bump mkdocs-material from 5.4.0 to 5.5.0

Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 5.4.0 to 5.5.0.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/5.4.0...5.5.0)

Signed-off-by: dependabot[bot] 
---
 docs/requirements-docs.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt
index 3a236ee87..2a2405f8e 100644
--- a/docs/requirements-docs.txt
+++ b/docs/requirements-docs.txt
@@ -1,2 +1,2 @@
-mkdocs-material==5.4.0
+mkdocs-material==5.5.0
 mdx_truly_sane_lists==1.2

From 63e7490a55baae19beed1652c0bb0ba7c15e0cc9 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sun, 26 Jul 2020 08:37:45 +0000
Subject: [PATCH 40/91] Bump plotly from 4.8.2 to 4.9.0

Bumps [plotly](https://github.com/plotly/plotly.py) from 4.8.2 to 4.9.0.
- [Release notes](https://github.com/plotly/plotly.py/releases)
- [Changelog](https://github.com/plotly/plotly.py/blob/master/CHANGELOG.md)
- [Commits](https://github.com/plotly/plotly.py/compare/v4.8.2...v4.9.0)

Signed-off-by: dependabot[bot] 
---
 requirements-plot.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements-plot.txt b/requirements-plot.txt
index ec5af3dbf..51d14d636 100644
--- a/requirements-plot.txt
+++ b/requirements-plot.txt
@@ -1,5 +1,5 @@
 # Include all requirements to run the bot.
 -r requirements.txt
 
-plotly==4.8.2
+plotly==4.9.0
 

From b4d22f10001f8ad2c977bf1e47a79e39fc5094b6 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sun, 26 Jul 2020 08:53:36 +0000
Subject: [PATCH 41/91] Bump urllib3 from 1.25.9 to 1.25.10

Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.25.9 to 1.25.10.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/master/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/1.25.9...1.25.10)

Signed-off-by: dependabot[bot] 
---
 requirements-common.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements-common.txt b/requirements-common.txt
index 63b2c0441..e0a9af77e 100644
--- a/requirements-common.txt
+++ b/requirements-common.txt
@@ -6,7 +6,7 @@ python-telegram-bot==12.8
 arrow==0.15.8
 cachetools==4.1.1
 requests==2.24.0
-urllib3==1.25.9
+urllib3==1.25.10
 wrapt==1.12.1
 jsonschema==3.2.0
 TA-Lib==0.4.18

From dbcccac6cd08bac45cec087f691300b77e962a2b Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sun, 26 Jul 2020 08:53:51 +0000
Subject: [PATCH 42/91] Bump ccxt from 1.31.37 to 1.32.3

Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.31.37 to 1.32.3.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst)
- [Commits](https://github.com/ccxt/ccxt/compare/1.31.37...1.32.3)

Signed-off-by: dependabot[bot] 
---
 requirements-common.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements-common.txt b/requirements-common.txt
index 63b2c0441..db06fee98 100644
--- a/requirements-common.txt
+++ b/requirements-common.txt
@@ -1,6 +1,6 @@
 # requirements without requirements installable via conda
 # mainly used for Raspberry pi installs
-ccxt==1.31.37
+ccxt==1.32.3
 SQLAlchemy==1.3.18
 python-telegram-bot==12.8
 arrow==0.15.8

From 7318d02ebc1331adba985d4c2525344c262437b1 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 27 Jul 2020 07:05:17 +0000
Subject: [PATCH 43/91] Bump ccxt from 1.32.3 to 1.32.7

Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.32.3 to 1.32.7.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst)
- [Commits](https://github.com/ccxt/ccxt/compare/1.32.3...1.32.7)

Signed-off-by: dependabot[bot] 
---
 requirements-common.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements-common.txt b/requirements-common.txt
index ba41d3ac2..942fe3792 100644
--- a/requirements-common.txt
+++ b/requirements-common.txt
@@ -1,6 +1,6 @@
 # requirements without requirements installable via conda
 # mainly used for Raspberry pi installs
-ccxt==1.32.3
+ccxt==1.32.7
 SQLAlchemy==1.3.18
 python-telegram-bot==12.8
 arrow==0.15.8

From 14cb29aae1d5fba8fe7d735ddfff7f0aa451434a Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Tue, 28 Jul 2020 08:16:55 +0200
Subject: [PATCH 44/91] Add test for remove_pumps in edge

---
 tests/edge/test_edge.py | 95 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 95 insertions(+)

diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py
index cf9cb6fe1..7373778ad 100644
--- a/tests/edge/test_edge.py
+++ b/tests/edge/test_edge.py
@@ -409,3 +409,98 @@ def test_process_expectancy(mocker, edge_conf, fee, risk_reward_ratio, expectanc
     final = edge._process_expectancy(trades_df)
     assert len(final) == 0
     assert isinstance(final, dict)
+
+
+def test_process_expectancy_remove_pumps(mocker, edge_conf, fee,):
+    edge_conf['edge']['min_trade_number'] = 2
+    edge_conf['edge']['remove_pumps'] = True
+    freqtrade = get_patched_freqtradebot(mocker, edge_conf)
+
+    freqtrade.exchange.get_fee = fee
+    edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
+
+    trades = [
+        {'pair': 'TEST/BTC',
+         'stoploss': -0.9,
+         'profit_percent': '',
+         'profit_abs': '',
+         'open_time': np.datetime64('2018-10-03T00:05:00.000000000'),
+         'close_time': np.datetime64('2018-10-03T00:10:00.000000000'),
+         'open_index': 1,
+         'close_index': 1,
+         'trade_duration': '',
+         'open_rate': 17,
+         'close_rate': 15,
+         'exit_type': 'sell_signal'},
+
+        {'pair': 'TEST/BTC',
+         'stoploss': -0.9,
+         'profit_percent': '',
+         'profit_abs': '',
+         'open_time': np.datetime64('2018-10-03T00:20:00.000000000'),
+         'close_time': np.datetime64('2018-10-03T00:25:00.000000000'),
+         'open_index': 4,
+         'close_index': 4,
+         'trade_duration': '',
+         'open_rate': 20,
+         'close_rate': 10,
+         'exit_type': 'sell_signal'},
+        {'pair': 'TEST/BTC',
+         'stoploss': -0.9,
+         'profit_percent': '',
+         'profit_abs': '',
+         'open_time': np.datetime64('2018-10-03T00:20:00.000000000'),
+         'close_time': np.datetime64('2018-10-03T00:25:00.000000000'),
+         'open_index': 4,
+         'close_index': 4,
+         'trade_duration': '',
+         'open_rate': 20,
+         'close_rate': 10,
+         'exit_type': 'sell_signal'},
+        {'pair': 'TEST/BTC',
+         'stoploss': -0.9,
+         'profit_percent': '',
+         'profit_abs': '',
+         'open_time': np.datetime64('2018-10-03T00:20:00.000000000'),
+         'close_time': np.datetime64('2018-10-03T00:25:00.000000000'),
+         'open_index': 4,
+         'close_index': 4,
+         'trade_duration': '',
+         'open_rate': 20,
+         'close_rate': 10,
+         'exit_type': 'sell_signal'},
+        {'pair': 'TEST/BTC',
+         'stoploss': -0.9,
+         'profit_percent': '',
+         'profit_abs': '',
+         'open_time': np.datetime64('2018-10-03T00:20:00.000000000'),
+         'close_time': np.datetime64('2018-10-03T00:25:00.000000000'),
+         'open_index': 4,
+         'close_index': 4,
+         'trade_duration': '',
+         'open_rate': 20,
+         'close_rate': 10,
+         'exit_type': 'sell_signal'},
+
+        {'pair': 'TEST/BTC',
+         'stoploss': -0.9,
+         'profit_percent': '',
+         'profit_abs': '',
+         'open_time': np.datetime64('2018-10-03T00:30:00.000000000'),
+         'close_time': np.datetime64('2018-10-03T00:40:00.000000000'),
+         'open_index': 6,
+         'close_index': 7,
+         'trade_duration': '',
+         'open_rate': 26,
+         'close_rate': 134,
+         'exit_type': 'sell_signal'}
+    ]
+
+    trades_df = DataFrame(trades)
+    trades_df = edge._fill_calculable_fields(trades_df)
+    final = edge._process_expectancy(trades_df)
+
+    assert 'TEST/BTC' in final
+    assert final['TEST/BTC'].stoploss == -0.9
+    assert final['TEST/BTC'].nb_trades == len(trades_df) - 1
+    assert round(final['TEST/BTC'].winrate, 10) == 0.0

From d1cbc567e4895111f877827c9007030a8cf843c1 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Tue, 28 Jul 2020 13:41:09 +0200
Subject: [PATCH 45/91] Fix filtering for bumped pairs

---
 freqtrade/edge/edge_positioning.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py
index 41252ee51..1993eded3 100644
--- a/freqtrade/edge/edge_positioning.py
+++ b/freqtrade/edge/edge_positioning.py
@@ -281,8 +281,8 @@ class Edge:
         #
         # Removing Pumps
         if self.edge_config.get('remove_pumps', False):
-            results = results.groupby(['pair', 'stoploss']).apply(
-                lambda x: x[x['profit_abs'] < 2 * x['profit_abs'].std() + x['profit_abs'].mean()])
+            results = results[results['profit_abs'] < 2 * results['profit_abs'].std()
+                              + results['profit_abs'].mean()]
         ##########################################################################
 
         # Removing trades having a duration more than X minutes (set in config)

From 071e82043aabb4e63aa4fdbe8426ceadb449c015 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sat, 1 Aug 2020 15:59:50 +0200
Subject: [PATCH 46/91] Better handle cancelled buy orders

---
 freqtrade/exchange/exchange.py  |  2 +-
 freqtrade/freqtradebot.py       |  6 ++++++
 tests/exchange/test_exchange.py |  2 +-
 tests/test_freqtradebot.py      | 29 +++++++++++++++++++++++------
 4 files changed, 31 insertions(+), 8 deletions(-)

diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
index 04ad10a68..c3779ff8e 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -999,7 +999,7 @@ class Exchange:
             if self.is_cancel_order_result_suitable(corder):
                 return corder
         except InvalidOrderException:
-            logger.warning(f"Could not cancel order {order_id}.")
+            logger.warning(f"Could not cancel order {order_id} for {pair}.")
         try:
             order = self.fetch_order(order_id, pair)
         except InvalidOrderException:
diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index a6d96ef77..b866842b7 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -976,6 +976,12 @@ class FreqtradeBot:
             reason = constants.CANCEL_REASON['TIMEOUT']
             corder = self.exchange.cancel_order_with_result(trade.open_order_id, trade.pair,
                                                             trade.amount)
+            # Avoid race condition where the order could not be cancelled coz its already filled.
+            # Simply bailing here is the only safe way - as this order will then be
+            # handled in the next iteration.
+            if corder.get('status') not in ('canceled', 'closed'):
+                logger.warning(f"Order {trade.open_order_id} for {trade.pair} not cancelled.")
+                return False
         else:
             # Order was cancelled already, so we can reuse the existing dict
             corder = order
diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py
index 60c4847f6..4f5cfa9e1 100644
--- a/tests/exchange/test_exchange.py
+++ b/tests/exchange/test_exchange.py
@@ -1818,7 +1818,7 @@ def test_cancel_order_with_result_error(default_conf, mocker, exchange_name, cap
 
     res = exchange.cancel_order_with_result('1234', 'ETH/BTC', 1541)
     assert isinstance(res, dict)
-    assert log_has("Could not cancel order 1234.", caplog)
+    assert log_has("Could not cancel order 1234 for ETH/BTC.", caplog)
     assert log_has("Could not fetch cancelled order 1234.", caplog)
     assert res['amount'] == 1541
 
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
index fd57eae6f..d93672ea7 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -2023,11 +2023,16 @@ def test_check_handle_timedout_buy_usercustom(default_conf, ticker, limit_buy_or
 
     rpc_mock = patch_RPCManager(mocker)
     cancel_order_mock = MagicMock(return_value=limit_buy_order_old)
+    cancel_buy_order = deepcopy(limit_buy_order_old)
+    cancel_buy_order['status'] = 'canceled'
+    cancel_order_wr_mock = MagicMock(return_value=cancel_buy_order)
+
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker,
         fetch_order=MagicMock(return_value=limit_buy_order_old),
+        cancel_order_with_result=cancel_order_wr_mock,
         cancel_order=cancel_order_mock,
         get_fee=fee
     )
@@ -2060,7 +2065,7 @@ def test_check_handle_timedout_buy_usercustom(default_conf, ticker, limit_buy_or
     freqtrade.strategy.check_buy_timeout = MagicMock(return_value=True)
     # Trade should be closed since the function returns true
     freqtrade.check_handle_timedout()
-    assert cancel_order_mock.call_count == 1
+    assert cancel_order_wr_mock.call_count == 1
     assert rpc_mock.call_count == 1
     trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
     nb_trades = len(trades)
@@ -2071,7 +2076,9 @@ def test_check_handle_timedout_buy_usercustom(default_conf, ticker, limit_buy_or
 def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, open_trade,
                                    fee, mocker) -> None:
     rpc_mock = patch_RPCManager(mocker)
-    cancel_order_mock = MagicMock(return_value=limit_buy_order_old)
+    limit_buy_cancel = deepcopy(limit_buy_order_old)
+    limit_buy_cancel['status'] = 'canceled'
+    cancel_order_mock = MagicMock(return_value=limit_buy_cancel)
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
@@ -2259,7 +2266,10 @@ def test_check_handle_cancelled_sell(default_conf, ticker, limit_sell_order_old,
 def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old_partial,
                                        open_trade, mocker) -> None:
     rpc_mock = patch_RPCManager(mocker)
-    cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial)
+    limit_buy_canceled = deepcopy(limit_buy_order_old_partial)
+    limit_buy_canceled['status'] = 'canceled'
+
+    cancel_order_mock = MagicMock(return_value=limit_buy_canceled)
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
@@ -2392,7 +2402,11 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke
 def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
-    cancel_order_mock = MagicMock(return_value=limit_buy_order)
+    cancel_buy_order = deepcopy(limit_buy_order)
+    cancel_buy_order['status'] = 'canceled'
+    del cancel_buy_order['filled']
+
+    cancel_order_mock = MagicMock(return_value=cancel_buy_order)
     mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock)
 
     freqtrade = FreqtradeBot(default_conf)
@@ -2412,9 +2426,12 @@ def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> Non
     assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
     assert cancel_order_mock.call_count == 1
 
-    limit_buy_order['filled'] = 2
-    mocker.patch('freqtrade.exchange.Exchange.cancel_order', side_effect=InvalidOrderException)
+    # Order remained open for some reason (cancel failed)
+    cancel_buy_order['status'] = 'open'
+    cancel_order_mock = MagicMock(return_value=cancel_buy_order)
+    mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock)
     assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
+    assert log_has_re(r"Order .* for .* not cancelled.", caplog)
 
 
 @pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'],

From 99bfa839ebcc6673ec435c53250b27b0ca9d437b Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sun, 2 Aug 2020 10:12:15 +0200
Subject: [PATCH 47/91] Improve logging for sell exception

---
 freqtrade/freqtradebot.py  | 2 +-
 tests/test_freqtradebot.py | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index b866842b7..967f68b90 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -660,7 +660,7 @@ class FreqtradeBot:
                     trades_closed += 1
 
             except DependencyException as exception:
-                logger.warning('Unable to sell trade: %s', exception)
+                logger.warning('Unable to sell trade %s: %s', trade.pair, exception)
 
         # Updating wallets if any trade occured
         if trades_closed:
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
index d93672ea7..15dd5d4ee 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -1660,6 +1660,7 @@ def test_exit_positions_exception(mocker, default_conf, limit_buy_order, caplog)
     trade = MagicMock()
     trade.open_order_id = None
     trade.open_fee = 0.001
+    trade.pair = 'ETH/BTC'
     trades = [trade]
 
     # Test raise of DependencyException exception
@@ -1669,7 +1670,7 @@ def test_exit_positions_exception(mocker, default_conf, limit_buy_order, caplog)
     )
     n = freqtrade.exit_positions(trades)
     assert n == 0
-    assert log_has('Unable to sell trade: ', caplog)
+    assert log_has('Unable to sell trade ETH/BTC: ', caplog)
 
 
 def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> None:

From 6c77feee8501a5290a4e6941ae873a31da92edcf Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sun, 2 Aug 2020 10:18:19 +0200
Subject: [PATCH 48/91] Improve some exchange logs

---
 freqtrade/exchange/exchange.py  |  4 ++--
 tests/exchange/test_exchange.py | 12 ++++++++++++
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
index c3779ff8e..dcdb36c84 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -1022,10 +1022,10 @@ class Exchange:
             return self._api.fetch_order(order_id, pair)
         except ccxt.OrderNotFound as e:
             raise RetryableOrderError(
-                f'Order not found (id: {order_id}). Message: {e}') from e
+                f'Order not found (pair: {pair} id: {order_id}). Message: {e}') from e
         except ccxt.InvalidOrder as e:
             raise InvalidOrderException(
-                f'Tried to get an invalid order (id: {order_id}). Message: {e}') from e
+                f'Tried to get an invalid order (pair: {pair} id: {order_id}). Message: {e}') from e
         except ccxt.DDoSProtection as e:
             raise DDosProtection(e) from e
         except (ccxt.NetworkError, ccxt.ExchangeError) as e:
diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py
index 4f5cfa9e1..673399fa6 100644
--- a/tests/exchange/test_exchange.py
+++ b/tests/exchange/test_exchange.py
@@ -2315,6 +2315,18 @@ def test_calculate_fee_rate(mocker, default_conf, order, expected) -> None:
     (3, 3, 1),
     (0, 1, 2),
     (1, 1, 1),
+    (0, 4, 17),
+    (1, 4, 10),
+    (2, 4, 5),
+    (3, 4, 2),
+    (4, 4, 1),
+    (0, 5, 26),
+    (1, 5, 17),
+    (2, 5, 10),
+    (3, 5, 5),
+    (4, 5, 2),
+    (5, 5, 1),
+
 ])
 def test_calculate_backoff(retrycount, max_retries, expected):
     assert calculate_backoff(retrycount, max_retries) == expected

From 3915101d2d8d0a7c4d3f0bfcc547cddbefa27205 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sun, 2 Aug 2020 10:24:10 +0200
Subject: [PATCH 49/91] Add more backoff to fetch_order endpoint

---
 freqtrade/exchange/exchange.py  | 2 +-
 freqtrade/exchange/ftx.py       | 2 +-
 tests/exchange/test_exchange.py | 5 +++--
 tests/exchange/test_ftx.py      | 1 +
 4 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
index dcdb36c84..ec787ca3a 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -1008,7 +1008,7 @@ class Exchange:
 
         return order
 
-    @retrier
+    @retrier(retries=5)
     def fetch_order(self, order_id: str, pair: str) -> Dict:
         if self._config['dry_run']:
             try:
diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py
index b75f77ca4..01e8267ad 100644
--- a/freqtrade/exchange/ftx.py
+++ b/freqtrade/exchange/ftx.py
@@ -78,7 +78,7 @@ class Ftx(Exchange):
         except ccxt.BaseError as e:
             raise OperationalException(e) from e
 
-    @retrier
+    @retrier(retries=5)
     def fetch_stoploss_order(self, order_id: str, pair: str) -> Dict:
         if self._config['dry_run']:
             try:
diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py
index 673399fa6..350c2d3cb 100644
--- a/tests/exchange/test_exchange.py
+++ b/tests/exchange/test_exchange.py
@@ -1896,10 +1896,10 @@ def test_fetch_order(default_conf, mocker, exchange_name):
         assert tm.call_args_list[1][0][0] == 2
         assert tm.call_args_list[2][0][0] == 5
         assert tm.call_args_list[3][0][0] == 10
-    assert api_mock.fetch_order.call_count == API_RETRY_COUNT + 1
+    assert api_mock.fetch_order.call_count == 6
 
     ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
-                           'fetch_order', 'fetch_order',
+                           'fetch_order', 'fetch_order', retries=6,
                            order_id='_', pair='TKN/BTC')
 
 
@@ -1932,6 +1932,7 @@ def test_fetch_stoploss_order(default_conf, mocker, exchange_name):
 
     ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
                            'fetch_stoploss_order', 'fetch_order',
+                           retries=6,
                            order_id='_', pair='TKN/BTC')
 
 
diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py
index eb7d83be3..bed92d276 100644
--- a/tests/exchange/test_ftx.py
+++ b/tests/exchange/test_ftx.py
@@ -154,4 +154,5 @@ def test_fetch_stoploss_order(default_conf, mocker):
 
     ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'ftx',
                            'fetch_stoploss_order', 'fetch_orders',
+                           retries=6,
                            order_id='_', pair='TKN/BTC')

From 5ff09a06c7a74875184aea4cfc159d32825b4566 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 3 Aug 2020 07:17:30 +0000
Subject: [PATCH 50/91] Bump pytest from 5.4.3 to 6.0.1

Bumps [pytest](https://github.com/pytest-dev/pytest) from 5.4.3 to 6.0.1.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/5.4.3...6.0.1)

Signed-off-by: dependabot[bot] 
---
 requirements-dev.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements-dev.txt b/requirements-dev.txt
index 9f9be638d..c02a439d3 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -8,7 +8,7 @@ flake8==3.8.3
 flake8-type-annotations==0.1.0
 flake8-tidy-imports==4.1.0
 mypy==0.782
-pytest==5.4.3
+pytest==6.0.1
 pytest-asyncio==0.14.0
 pytest-cov==2.10.0
 pytest-mock==3.2.0

From 809b3ddafc5863caab228cd75c10f5ae8e496e5a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 3 Aug 2020 07:17:31 +0000
Subject: [PATCH 51/91] Bump mkdocs-material from 5.5.0 to 5.5.1

Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 5.5.0 to 5.5.1.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/5.5.0...5.5.1)

Signed-off-by: dependabot[bot] 
---
 docs/requirements-docs.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt
index 2a2405f8e..c30661b6a 100644
--- a/docs/requirements-docs.txt
+++ b/docs/requirements-docs.txt
@@ -1,2 +1,2 @@
-mkdocs-material==5.5.0
+mkdocs-material==5.5.1
 mdx_truly_sane_lists==1.2

From 1855a444fa29f574aa29f5f6e936c9bfd1d879e0 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 3 Aug 2020 07:17:32 +0000
Subject: [PATCH 52/91] Bump pandas from 1.0.5 to 1.1.0

Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.0.5 to 1.1.0.
- [Release notes](https://github.com/pandas-dev/pandas/releases)
- [Changelog](https://github.com/pandas-dev/pandas/blob/master/RELEASE.md)
- [Commits](https://github.com/pandas-dev/pandas/compare/v1.0.5...v1.1.0)

Signed-off-by: dependabot[bot] 
---
 requirements.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements.txt b/requirements.txt
index 2392d4cb2..d65f90325 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,4 +2,4 @@
 -r requirements-common.txt
 
 numpy==1.19.1
-pandas==1.0.5
+pandas==1.1.0

From b3f04d89d259c49f8db954aababe9e812f6f4ca6 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 3 Aug 2020 07:17:50 +0000
Subject: [PATCH 53/91] Bump ccxt from 1.32.7 to 1.32.45

Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.32.7 to 1.32.45.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst)
- [Commits](https://github.com/ccxt/ccxt/compare/1.32.7...1.32.45)

Signed-off-by: dependabot[bot] 
---
 requirements-common.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements-common.txt b/requirements-common.txt
index 942fe3792..62cde9dbc 100644
--- a/requirements-common.txt
+++ b/requirements-common.txt
@@ -1,6 +1,6 @@
 # requirements without requirements installable via conda
 # mainly used for Raspberry pi installs
-ccxt==1.32.7
+ccxt==1.32.45
 SQLAlchemy==1.3.18
 python-telegram-bot==12.8
 arrow==0.15.8

From a33346c6b660c518a70bdde9a56dc805046d08b8 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Mon, 3 Aug 2020 19:21:46 +0200
Subject: [PATCH 54/91] Fix testing errors - which surfaced with pytest 6.0.1

---
 freqtrade/exchange/exchange.py | 4 ++--
 tests/test_freqtradebot.py     | 2 ++
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
index 04ad10a68..c3fe3e601 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -258,8 +258,8 @@ class Exchange:
                 api.urls['api'] = api.urls['test']
                 logger.info("Enabled Sandbox API on %s", name)
             else:
-                logger.warning(name, "No Sandbox URL in CCXT, exiting. "
-                                     "Please check your config.json")
+                logger.warning(f"No Sandbox URL in CCXT for {name}, exiting. "
+                                "Please check your config.json")
                 raise OperationalException(f'Exchange {name} does not provide a sandbox api')
 
     def _load_async_markets(self, reload: bool = False) -> None:
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
index fd57eae6f..dcddf34e3 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -1726,6 +1726,7 @@ def test_update_trade_state_withorderdict(default_conf, trades_for_order, limit_
         amount=amount,
         exchange='binance',
         open_rate=0.245441,
+        open_date=arrow.utcnow().datetime,
         fee_open=fee.return_value,
         fee_close=fee.return_value,
         open_order_id="123456",
@@ -1816,6 +1817,7 @@ def test_update_trade_state_sell(default_conf, trades_for_order, limit_sell_orde
         open_rate=0.245441,
         fee_open=0.0025,
         fee_close=0.0025,
+        open_date=arrow.utcnow().datetime,
         open_order_id="123456",
         is_open=True,
     )

From a3688b159fcf703a5cc390d0ae70d884db717762 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Mon, 3 Aug 2020 19:28:57 +0200
Subject: [PATCH 55/91] Improve formatting

---
 freqtrade/exchange/exchange.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
index c3fe3e601..c4ed75878 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -258,8 +258,8 @@ class Exchange:
                 api.urls['api'] = api.urls['test']
                 logger.info("Enabled Sandbox API on %s", name)
             else:
-                logger.warning(f"No Sandbox URL in CCXT for {name}, exiting. "
-                                "Please check your config.json")
+                logger.warning(
+                    f"No Sandbox URL in CCXT for {name}, exiting. Please check your config.json")
                 raise OperationalException(f'Exchange {name} does not provide a sandbox api')
 
     def _load_async_markets(self, reload: bool = False) -> None:

From 215972c68f3efa6397a6d5a6b17fa2c4ea1a3bea Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Tue, 4 Aug 2020 14:41:22 +0200
Subject: [PATCH 56/91] Implement /delete for api-server

---
 freqtrade/rpc/api_server.py | 11 +++++++++++
 freqtrade/rpc/rpc.py        |  4 ++--
 freqtrade/rpc/telegram.py   |  2 +-
 3 files changed, 14 insertions(+), 3 deletions(-)

diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py
index 351842e10..f7481fee4 100644
--- a/freqtrade/rpc/api_server.py
+++ b/freqtrade/rpc/api_server.py
@@ -200,6 +200,8 @@ class ApiServer(RPC):
                               view_func=self._ping, methods=['GET'])
         self.app.add_url_rule(f'{BASE_URI}/trades', 'trades',
                               view_func=self._trades, methods=['GET'])
+        self.app.add_url_rule(f'{BASE_URI}/trades/', 'trades_delete',
+                              view_func=self._trades_delete, methods=['DELETE'])
         # Combined actions and infos
         self.app.add_url_rule(f'{BASE_URI}/blacklist', 'blacklist', view_func=self._blacklist,
                               methods=['GET', 'POST'])
@@ -424,6 +426,15 @@ class ApiServer(RPC):
         results = self._rpc_trade_history(limit)
         return self.rest_dump(results)
 
+    @require_login
+    @rpc_catch_errors
+    def _trades_delete(self, tradeid):
+        """
+        Handler for DELETE /trades/ endpoint.
+        """
+        result = self._rpc_delete(tradeid)
+        return self.rest_dump(result)
+
     @require_login
     @rpc_catch_errors
     def _whitelist(self):
diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index f6627ed16..b79cac243 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -537,7 +537,7 @@ class RPC:
             return trade
         else:
             return None
-            
+
     def _rpc_delete(self, trade_id: str) -> Dict[str, str]:
         """
         Handler for delete .
@@ -558,7 +558,7 @@ class RPC:
             _exec_delete(trade)
             Trade.session.flush()
             self._freqtrade.wallets.update()
-            return {'result': f'Deleted trade {trade_id}.'}
+            return {'result_msg': f'Deleted trade {trade_id}.'}
 
     def _rpc_performance(self) -> List[Dict[str, Any]]:
         """
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 20c1cc9dc..293e3a686 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -547,7 +547,7 @@ class Telegram(RPC):
         trade_id = context.args[0] if len(context.args) > 0 else None
         try:
             msg = self._rpc_delete(trade_id)
-            self._send_msg('Delete Result: `{result}`'.format(**msg))
+            self._send_msg('Delete Result: `{result_msg}`'.format(**msg))
 
         except RPCException as e:
             self._send_msg(str(e))

From 26c7341b7d712b4f1bb671722ee0bf0f6e55a42b Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Tue, 4 Aug 2020 14:41:38 +0200
Subject: [PATCH 57/91] Add test for api-server DELETE trade

---
 tests/rpc/test_rpc_apiserver.py | 38 ++++++++++++++++++++++++++++++++-
 1 file changed, 37 insertions(+), 1 deletion(-)

diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py
index f4d7b8ca3..99f17383f 100644
--- a/tests/rpc/test_rpc_apiserver.py
+++ b/tests/rpc/test_rpc_apiserver.py
@@ -50,6 +50,12 @@ def client_get(client, url):
                                     'Origin': 'http://example.com'})
 
 
+def client_delete(client, url):
+    # Add fake Origin to ensure CORS kicks in
+    return client.delete(url, headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS),
+                                       'Origin': 'http://example.com'})
+
+
 def assert_response(response, expected_code=200, needs_cors=True):
     assert response.status_code == expected_code
     assert response.content_type == "application/json"
@@ -352,7 +358,7 @@ def test_api_daily(botclient, mocker, ticker, fee, markets):
     assert rc.json['data'][0]['date'] == str(datetime.utcnow().date())
 
 
-def test_api_trades(botclient, mocker, ticker, fee, markets):
+def test_api_trades(botclient, mocker, fee, markets):
     ftbot, client = botclient
     patch_get_signal(ftbot, (True, False))
     mocker.patch.multiple(
@@ -376,6 +382,36 @@ def test_api_trades(botclient, mocker, ticker, fee, markets):
     assert rc.json['trades_count'] == 1
 
 
+def test_api_delete_trade(botclient, mocker, fee, markets):
+    ftbot, client = botclient
+    patch_get_signal(ftbot, (True, False))
+    mocker.patch.multiple(
+        'freqtrade.exchange.Exchange',
+        markets=PropertyMock(return_value=markets)
+    )
+    rc = client_delete(client, f"{BASE_URI}/trades/1")
+    # Error - trade won't exist yet.
+    assert_response(rc, 502)
+
+    create_mock_trades(fee)
+    trades = Trade.query.all()
+    assert len(trades) > 2
+
+    rc = client_delete(client, f"{BASE_URI}/trades/1")
+    assert_response(rc)
+    assert rc.json['result_msg'] == 'Deleted trade 1.'
+    assert len(trades) - 1 == len(Trade.query.all())
+
+    rc = client_delete(client, f"{BASE_URI}/trades/1")
+    # Trade is gone now.
+    assert_response(rc, 502)
+    assert len(trades) - 1 == len(Trade.query.all())
+    rc = client_delete(client, f"{BASE_URI}/trades/2")
+    assert_response(rc)
+    assert rc.json['result_msg'] == 'Deleted trade 2.'
+    assert len(trades) - 2 == len(Trade.query.all())
+
+
 def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
     ftbot, client = botclient
     patch_get_signal(ftbot, (True, False))

From 4b0164770c50d3efdef5fb979bdcf71452f01ef6 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Tue, 4 Aug 2020 14:49:59 +0200
Subject: [PATCH 58/91] Add test for /delete

---
 freqtrade/rpc/telegram.py      |  4 ++--
 tests/rpc/test_rpc_telegram.py | 27 +++++++++++++++++++++++++++
 2 files changed, 29 insertions(+), 2 deletions(-)

diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 293e3a686..19d3e1bd9 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -94,7 +94,7 @@ class Telegram(RPC):
             CommandHandler('forcesell', self._forcesell),
             CommandHandler('forcebuy', self._forcebuy),
             CommandHandler('trades', self._trades),
-            CommandHandler('delete', self._delete),
+            CommandHandler('delete', self._delete_trade),
             CommandHandler('performance', self._performance),
             CommandHandler('daily', self._daily),
             CommandHandler('count', self._count),
@@ -535,7 +535,7 @@ class Telegram(RPC):
             self._send_msg(str(e))
 
     @authorized_only
-    def _delete(self, update: Update, context: CallbackContext) -> None:
+    def _delete_trade(self, update: Update, context: CallbackContext) -> None:
         """
         Handler for /delete .
         Delete the given trade
diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index 62a4f49a1..c8ac05d0d 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -1177,6 +1177,33 @@ def test_telegram_trades(mocker, update, default_conf, fee):
     assert "
" in msg_mock.call_args_list[0][0][0]
 
 
+def test_telegram_delete_trade(mocker, update, default_conf, fee):
+    msg_mock = MagicMock()
+    mocker.patch.multiple(
+        'freqtrade.rpc.telegram.Telegram',
+        _init=MagicMock(),
+        _send_msg=msg_mock
+    )
+
+    freqtradebot = get_patched_freqtradebot(mocker, default_conf)
+    telegram = Telegram(freqtradebot)
+    context = MagicMock()
+    context.args = []
+
+    telegram._delete_trade(update=update, context=context)
+    assert "invalid argument" in msg_mock.call_args_list[0][0][0]
+
+    msg_mock.reset_mock()
+    create_mock_trades(fee)
+
+    context = MagicMock()
+    context.args = [1]
+    telegram._delete_trade(update=update, context=context)
+    msg_mock.call_count == 1
+    assert "Delete Result" in msg_mock.call_args_list[0][0][0]
+    assert "Deleted trade 1." in msg_mock.call_args_list[0][0][0]
+
+
 def test_help_handle(default_conf, update, mocker) -> None:
     msg_mock = MagicMock()
     mocker.patch.multiple(

From b954af33cfa06387684297c3f6cce35813603414 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Tue, 4 Aug 2020 16:01:41 +0200
Subject: [PATCH 59/91] Fix type erorr in callable

---
 freqtrade/rpc/api_server.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py
index f7481fee4..f28a35ff0 100644
--- a/freqtrade/rpc/api_server.py
+++ b/freqtrade/rpc/api_server.py
@@ -56,7 +56,7 @@ def require_login(func: Callable[[Any, Any], Any]):
 
 
 # Type should really be Callable[[ApiServer], Any], but that will create a circular dependency
-def rpc_catch_errors(func: Callable[[Any], Any]):
+def rpc_catch_errors(func: Callable[..., Any]):
 
     def func_wrapper(obj, *args, **kwargs):
 

From 9163c7f3d3bb0854bcaceea5cef2980ea8ef6a19 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Tue, 4 Aug 2020 19:43:05 +0200
Subject: [PATCH 60/91] Improve api response

---
 freqtrade/rpc/api_server.py     |  4 ++++
 tests/rpc/test_rpc_apiserver.py | 17 ++++++++++++++---
 2 files changed, 18 insertions(+), 3 deletions(-)

diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py
index f28a35ff0..06926ac35 100644
--- a/freqtrade/rpc/api_server.py
+++ b/freqtrade/rpc/api_server.py
@@ -431,6 +431,10 @@ class ApiServer(RPC):
     def _trades_delete(self, tradeid):
         """
         Handler for DELETE /trades/ endpoint.
+        Removes the trade from the database (tries to cancel open orders first!)
+        get:
+          param:
+            tradeid: Numeric trade-id assigned to the trade.
         """
         result = self._rpc_delete(tradeid)
         return self.rest_dump(result)
diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py
index 99f17383f..ccdec6fdc 100644
--- a/tests/rpc/test_rpc_apiserver.py
+++ b/tests/rpc/test_rpc_apiserver.py
@@ -385,31 +385,42 @@ def test_api_trades(botclient, mocker, fee, markets):
 def test_api_delete_trade(botclient, mocker, fee, markets):
     ftbot, client = botclient
     patch_get_signal(ftbot, (True, False))
+    stoploss_mock = MagicMock()
+    cancel_mock = MagicMock()
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
-        markets=PropertyMock(return_value=markets)
+        markets=PropertyMock(return_value=markets),
+        cancel_order=cancel_mock,
+        cancel_stoploss_order=stoploss_mock,
     )
     rc = client_delete(client, f"{BASE_URI}/trades/1")
     # Error - trade won't exist yet.
     assert_response(rc, 502)
 
     create_mock_trades(fee)
+    ftbot.strategy.order_types['stoploss_on_exchange'] = True
     trades = Trade.query.all()
+    trades[1].stoploss_order_id = '1234'
     assert len(trades) > 2
 
     rc = client_delete(client, f"{BASE_URI}/trades/1")
     assert_response(rc)
-    assert rc.json['result_msg'] == 'Deleted trade 1.'
+    assert rc.json['result_msg'] == 'Deleted trade 1. Closed 1 open orders.'
     assert len(trades) - 1 == len(Trade.query.all())
+    assert cancel_mock.call_count == 1
 
+    cancel_mock.reset_mock()
     rc = client_delete(client, f"{BASE_URI}/trades/1")
     # Trade is gone now.
     assert_response(rc, 502)
+    assert cancel_mock.call_count == 0
+
     assert len(trades) - 1 == len(Trade.query.all())
     rc = client_delete(client, f"{BASE_URI}/trades/2")
     assert_response(rc)
-    assert rc.json['result_msg'] == 'Deleted trade 2.'
+    assert rc.json['result_msg'] == 'Deleted trade 2. Closed 2 open orders.'
     assert len(trades) - 2 == len(Trade.query.all())
+    assert stoploss_mock.call_count == 1
 
 
 def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):

From 817f5289db94895a6db6e0b045fcf40aa75047b1 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Tue, 4 Aug 2020 19:43:22 +0200
Subject: [PATCH 61/91] /delete should Cancel open orders (and stoploss orders)

---
 freqtrade/rpc/rpc.py  | 46 +++++++++++++++++++++++-----------
 tests/rpc/test_rpc.py | 57 ++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 88 insertions(+), 15 deletions(-)

diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index b79cac243..58fdebe85 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -11,9 +11,9 @@ from typing import Any, Dict, List, Optional, Tuple
 import arrow
 from numpy import NAN, mean
 
-from freqtrade.exceptions import ExchangeError, PricingError
-
-from freqtrade.exchange import timeframe_to_msecs, timeframe_to_minutes
+from freqtrade.exceptions import (ExchangeError, InvalidOrderException,
+                                  PricingError)
+from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs
 from freqtrade.misc import shorten_date
 from freqtrade.persistence import Trade
 from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
@@ -541,24 +541,42 @@ class RPC:
     def _rpc_delete(self, trade_id: str) -> Dict[str, str]:
         """
         Handler for delete .
-        Delete the given trade
+        Delete the given trade and close eventually existing open orders.
         """
-        def _exec_delete(trade: Trade) -> None:
-            Trade.session.delete(trade)
-            Trade.session.flush()
-
         with self._freqtrade._sell_lock:
-            trade = Trade.get_trades(
-                trade_filter=[Trade.id == trade_id, ]
-            ).first()
+            c_count = 0
+            trade = Trade.get_trades(trade_filter=[Trade.id == trade_id]).first()
             if not trade:
-                logger.warning('delete: Invalid argument received')
+                logger.warning('delete trade: Invalid argument received')
                 raise RPCException('invalid argument')
 
-            _exec_delete(trade)
+            # Try cancelling regular order if that exists
+            if trade.open_order_id:
+                try:
+                    self._freqtrade.exchange.cancel_order(trade.open_order_id, trade.pair)
+                    c_count += 1
+                except (ExchangeError, InvalidOrderException):
+                    pass
+
+            # cancel stoploss on exchange ...
+            if (self._freqtrade.strategy.order_types.get('stoploss_on_exchange')
+                    and trade.stoploss_order_id):
+                try:
+                    self._freqtrade.exchange.cancel_stoploss_order(trade.stoploss_order_id,
+                                                                   trade.pair)
+                    c_count += 1
+                except (ExchangeError, InvalidOrderException):
+                    pass
+
+            Trade.session.delete(trade)
             Trade.session.flush()
             self._freqtrade.wallets.update()
-            return {'result_msg': f'Deleted trade {trade_id}.'}
+            return {
+                'result': 'success',
+                'trade_id': trade_id,
+                'result_msg': f'Deleted trade {trade_id}. Closed {c_count} open orders.',
+                'cancel_order_count': c_count,
+            }
 
     def _rpc_performance(self) -> List[Dict[str, Any]]:
         """
diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py
index e5859fcd9..b4a781459 100644
--- a/tests/rpc/test_rpc.py
+++ b/tests/rpc/test_rpc.py
@@ -8,7 +8,7 @@ import pytest
 from numpy import isnan
 
 from freqtrade.edge import PairInfo
-from freqtrade.exceptions import ExchangeError, TemporaryError
+from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError
 from freqtrade.persistence import Trade
 from freqtrade.rpc import RPC, RPCException
 from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
@@ -291,6 +291,61 @@ def test_rpc_trade_history(mocker, default_conf, markets, fee):
     assert trades['trades'][0]['pair'] == 'XRP/BTC'
 
 
+def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog):
+    mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
+    stoploss_mock = MagicMock()
+    cancel_mock = MagicMock()
+    mocker.patch.multiple(
+        'freqtrade.exchange.Exchange',
+        markets=PropertyMock(return_value=markets),
+        cancel_order=cancel_mock,
+        cancel_stoploss_order=stoploss_mock,
+    )
+
+    freqtradebot = get_patched_freqtradebot(mocker, default_conf)
+    freqtradebot.strategy.order_types['stoploss_on_exchange'] = True
+    create_mock_trades(fee)
+    rpc = RPC(freqtradebot)
+    with pytest.raises(RPCException, match='invalid argument'):
+        rpc._rpc_delete('200')
+
+    create_mock_trades(fee)
+    trades = Trade.query.all()
+    trades[1].stoploss_order_id = '1234'
+    trades[2].stoploss_order_id = '1234'
+    assert len(trades) > 2
+
+    res = rpc._rpc_delete('1')
+    assert isinstance(res, dict)
+    assert res['result'] == 'success'
+    assert res['trade_id'] == '1'
+    assert res['cancel_order_count'] == 1
+    assert cancel_mock.call_count == 1
+    assert stoploss_mock.call_count == 0
+    cancel_mock.reset_mock()
+    stoploss_mock.reset_mock()
+
+    res = rpc._rpc_delete('2')
+    assert isinstance(res, dict)
+    assert cancel_mock.call_count == 1
+    assert stoploss_mock.call_count == 1
+    assert res['cancel_order_count'] == 2
+
+    stoploss_mock = mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
+                                 side_effect=InvalidOrderException)
+
+    res = rpc._rpc_delete('3')
+    assert stoploss_mock.call_count == 1
+    stoploss_mock.reset_mock()
+
+    cancel_mock = mocker.patch('freqtrade.exchange.Exchange.cancel_order',
+                               side_effect=InvalidOrderException)
+
+    res = rpc._rpc_delete('4')
+    assert cancel_mock.call_count == 1
+    assert stoploss_mock.call_count == 0
+
+
 def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
                               limit_buy_order, limit_sell_order, mocker) -> None:
     mocker.patch.multiple(

From 075c73b9e38e7f71ce5eb32e2bc8930b1c947fa0 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Tue, 4 Aug 2020 19:56:49 +0200
Subject: [PATCH 62/91] Improve formatting of telegram message

---
 freqtrade/rpc/rpc.py           | 4 ++--
 freqtrade/rpc/telegram.py      | 5 ++++-
 tests/rpc/test_rpc_telegram.py | 2 +-
 3 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index 58fdebe85..8a1ff7e96 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -6,7 +6,7 @@ from abc import abstractmethod
 from datetime import date, datetime, timedelta
 from enum import Enum
 from math import isnan
-from typing import Any, Dict, List, Optional, Tuple
+from typing import Any, Dict, List, Optional, Tuple, Union
 
 import arrow
 from numpy import NAN, mean
@@ -538,7 +538,7 @@ class RPC:
         else:
             return None
 
-    def _rpc_delete(self, trade_id: str) -> Dict[str, str]:
+    def _rpc_delete(self, trade_id: str) -> Dict[str, Union[str, int]]:
         """
         Handler for delete .
         Delete the given trade and close eventually existing open orders.
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 19d3e1bd9..dde19fddb 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -547,7 +547,10 @@ class Telegram(RPC):
         trade_id = context.args[0] if len(context.args) > 0 else None
         try:
             msg = self._rpc_delete(trade_id)
-            self._send_msg('Delete Result: `{result_msg}`'.format(**msg))
+            self._send_msg((
+                'Delete Result: `{result_msg}`'
+                'Please make sure to take care of this asset on the exchange manually.'
+            ).format(**msg))
 
         except RPCException as e:
             self._send_msg(str(e))
diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index c8ac05d0d..ac8dc62c6 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -1200,8 +1200,8 @@ def test_telegram_delete_trade(mocker, update, default_conf, fee):
     context.args = [1]
     telegram._delete_trade(update=update, context=context)
     msg_mock.call_count == 1
-    assert "Delete Result" in msg_mock.call_args_list[0][0][0]
     assert "Deleted trade 1." in msg_mock.call_args_list[0][0][0]
+    assert "Please make sure to take care of this asset" in msg_mock.call_args_list[0][0][0]
 
 
 def test_help_handle(default_conf, update, mocker) -> None:

From 8ed3b81c618293106ad821585ad3fbe89d8685e5 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Tue, 4 Aug 2020 19:57:28 +0200
Subject: [PATCH 63/91] Implement /delete in rest client

---
 docs/rest-api.md          | 43 +++++++++++++++++++++------------------
 docs/telegram-usage.md    |  1 +
 freqtrade/rpc/telegram.py |  2 +-
 scripts/rest_client.py    | 12 +++++++++++
 4 files changed, 37 insertions(+), 21 deletions(-)

diff --git a/docs/rest-api.md b/docs/rest-api.md
index a8d902b53..2887a9ffc 100644
--- a/docs/rest-api.md
+++ b/docs/rest-api.md
@@ -106,26 +106,29 @@ python3 scripts/rest_client.py --config rest_config.json  [optional par
 
 ## Available commands
 
-|  Command | Default | Description |
-|----------|---------|-------------|
-| `start` | | Starts the trader
-| `stop` | | Stops the trader
-| `stopbuy` | | Stops the trader from opening new trades. Gracefully closes open trades according to their rules.
-| `reload_config` | | Reloads the configuration file
-| `show_config` | | Shows part of the current configuration with relevant settings to operation
-| `status` | | Lists all open trades
-| `count` | | Displays number of trades used and available
-| `profit` | | Display a summary of your profit/loss from close trades and some stats about your performance
-| `forcesell ` | | Instantly sells the given trade  (Ignoring `minimum_roi`).
-| `forcesell all` | | Instantly sells all open trades (Ignoring `minimum_roi`).
-| `forcebuy  [rate]` | | Instantly buys the given pair. Rate is optional. (`forcebuy_enable` must be set to True)
-| `performance` | | Show performance of each finished trade grouped by pair
-| `balance` | | Show account balance per currency
-| `daily ` | 7 | Shows profit or loss per day, over the last n days
-| `whitelist` | | Show the current whitelist
-| `blacklist [pair]` | | Show the current blacklist, or adds a pair to the blacklist.
-| `edge` | | Show validated pairs by Edge if it is enabled.
-| `version` | | Show version
+|  Command | Description |
+|----------|-------------|
+| `ping` | Simple command testing the API Readiness - requires no authentication.
+| `start` | Starts the trader
+| `stop` | Stops the trader
+| `stopbuy` | Stops the trader from opening new trades. Gracefully closes open trades according to their rules.
+| `reload_config` | Reloads the configuration file
+| `trades` | List last trades.
+| `delete_trade ` | Remove trade from the database. Tries to close open orders. Requires manual handling of this trade on the exchange.
+| `show_config` | Shows part of the current configuration with relevant settings to operation
+| `status` | Lists all open trades
+| `count` | Displays number of trades used and available
+| `profit` | Display a summary of your profit/loss from close trades and some stats about your performance
+| `forcesell ` | Instantly sells the given trade  (Ignoring `minimum_roi`).
+| `forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`).
+| `forcebuy  [rate]` | Instantly buys the given pair. Rate is optional. (`forcebuy_enable` must be set to True)
+| `performance` | Show performance of each finished trade grouped by pair
+| `balance` | Show account balance per currency
+| `daily ` | Shows profit or loss per day, over the last n days (n defaults to 7)
+| `whitelist` | Show the current whitelist
+| `blacklist [pair]` | Show the current blacklist, or adds a pair to the blacklist.
+| `edge` | Show validated pairs by Edge if it is enabled.
+| `version` | Show version
 
 Possible commands can be listed from the rest-client script using the `help` command.
 
diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md
index 250293d25..b81ef012b 100644
--- a/docs/telegram-usage.md
+++ b/docs/telegram-usage.md
@@ -57,6 +57,7 @@ official commands. You can ask at any moment for help with `/help`.
 | `/status` | | Lists all open trades
 | `/status table` | | List all open trades in a table format. Pending buy orders are marked with an asterisk (*) Pending sell orders are marked with a double asterisk (**)
 | `/trades [limit]` | | List all recently closed trades in a table format.
+| `/delete ` | | Delete a specific trade from the Database. Tries to close open orders. Requires manual handling of this trade on the exchange.
 | `/count` | | Displays number of trades used and available
 | `/profit` | | Display a summary of your profit/loss from close trades and some stats about your performance
 | `/forcesell ` | | Instantly sells the given trade  (Ignoring `minimum_roi`).
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index dde19fddb..f1d3cde21 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -548,7 +548,7 @@ class Telegram(RPC):
         try:
             msg = self._rpc_delete(trade_id)
             self._send_msg((
-                'Delete Result: `{result_msg}`'
+                '`{result_msg}`\n'
                 'Please make sure to take care of this asset on the exchange manually.'
             ).format(**msg))
 
diff --git a/scripts/rest_client.py b/scripts/rest_client.py
index 1f96bcb69..51ea596f6 100755
--- a/scripts/rest_client.py
+++ b/scripts/rest_client.py
@@ -62,6 +62,9 @@ class FtRestClient():
     def _get(self, apipath, params: dict = None):
         return self._call("GET", apipath, params=params)
 
+    def _delete(self, apipath, params: dict = None):
+        return self._call("DELETE", apipath, params=params)
+
     def _post(self, apipath, params: dict = None, data: dict = None):
         return self._call("POST", apipath, params=params, data=data)
 
@@ -164,6 +167,15 @@ class FtRestClient():
         """
         return self._get("trades", params={"limit": limit} if limit else 0)
 
+    def delete_trade(self, trade_id):
+        """Delete trade from the database.
+        Tries to close open orders. Requires manual handling of this asset on the exchange.
+
+        :param trade_id: Deletes the trade with this ID from the database.
+        :return: json object
+        """
+        return self._delete("trades/{}".format(trade_id))
+
     def whitelist(self):
         """Show the current whitelist.
 

From 5082acc33f1220f95490c56c4fa1d8571ae6faf4 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Thu, 6 Aug 2020 07:54:54 +0200
Subject: [PATCH 64/91] Fix typos in documentation

---
 docs/rest-api.md       | 2 +-
 docs/telegram-usage.md | 8 +++++---
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/docs/rest-api.md b/docs/rest-api.md
index 2887a9ffc..68754f79a 100644
--- a/docs/rest-api.md
+++ b/docs/rest-api.md
@@ -46,7 +46,7 @@ secrets.token_hex()
 
 ### Configuration with docker
 
-If you run your bot using docker, you'll need to have the bot listen to incomming connections. The security is then handled by docker.
+If you run your bot using docker, you'll need to have the bot listen to incoming connections. The security is then handled by docker.
 
 ``` json
     "api_server": {
diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md
index b81ef012b..b050a7a60 100644
--- a/docs/telegram-usage.md
+++ b/docs/telegram-usage.md
@@ -9,7 +9,7 @@ Telegram user id.
 
 Start a chat with the [Telegram BotFather](https://telegram.me/BotFather)
 
-Send the message `/newbot`. 
+Send the message `/newbot`.
 
 *BotFather response:*
 
@@ -115,6 +115,7 @@ For each open trade, the bot will send you the following message.
 ### /status table
 
 Return the status of all open trades in a table format.
+
 ```
    ID  Pair      Since    Profit
 ----  --------  -------  --------
@@ -125,6 +126,7 @@ Return the status of all open trades in a table format.
 ### /count
 
 Return the number of trades used and available.
+
 ```
 current    max
 ---------  -----
@@ -210,7 +212,7 @@ Shows the current whitelist
 
 Shows the current blacklist.
 If Pair is set, then this pair will be added to the pairlist.
-Also supports multiple pairs, seperated by a space.
+Also supports multiple pairs, separated by a space.
 Use `/reload_config` to reset the blacklist.
 
 > Using blacklist `StaticPairList` with 2 pairs  
@@ -218,7 +220,7 @@ Use `/reload_config` to reset the blacklist.
 
 ### /edge
 
-Shows pairs validated by Edge along with their corresponding winrate, expectancy and stoploss values.
+Shows pairs validated by Edge along with their corresponding win-rate, expectancy and stoploss values.
 
 > **Edge only validated following pairs:**
 ```

From eba73307e42ce6b94d5bed393c40c0bfbbafd05c Mon Sep 17 00:00:00 2001
From: Fredrik81 
Date: Fri, 7 Aug 2020 01:13:36 +0200
Subject: [PATCH 65/91] Update strategy_methods_advanced.j2

Fix def confirm_trade_exit arguments
---
 freqtrade/templates/subtemplates/strategy_methods_advanced.j2 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2
index c7ce41bb7..5ca6e6971 100644
--- a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2
+++ b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2
@@ -34,7 +34,7 @@ def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: f
     """
     return True
 
-def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
+def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float,
                        rate: float, time_in_force: str, sell_reason: str, **kwargs) -> bool:
     """
     Called right before placing a regular sell order.

From f3ce54150e92f66c70b82f91276f10fab7e801a4 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sat, 8 Aug 2020 15:06:13 +0200
Subject: [PATCH 66/91] Simplify Telegram table

---
 docs/telegram-usage.md | 48 +++++++++++++++++++++---------------------
 1 file changed, 24 insertions(+), 24 deletions(-)

diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md
index b050a7a60..9776b26ba 100644
--- a/docs/telegram-usage.md
+++ b/docs/telegram-usage.md
@@ -47,30 +47,30 @@ Per default, the Telegram bot shows predefined commands. Some commands
 are only available by sending them to the bot. The table below list the
 official commands. You can ask at any moment for help with `/help`.
 
-|  Command | Default | Description |
-|----------|---------|-------------|
-| `/start` | | Starts the trader
-| `/stop` | | Stops the trader
-| `/stopbuy` | | Stops the trader from opening new trades. Gracefully closes open trades according to their rules.
-| `/reload_config` | | Reloads the configuration file
-| `/show_config` | | Shows part of the current configuration with relevant settings to operation
-| `/status` | | Lists all open trades
-| `/status table` | | List all open trades in a table format. Pending buy orders are marked with an asterisk (*) Pending sell orders are marked with a double asterisk (**)
-| `/trades [limit]` | | List all recently closed trades in a table format.
-| `/delete ` | | Delete a specific trade from the Database. Tries to close open orders. Requires manual handling of this trade on the exchange.
-| `/count` | | Displays number of trades used and available
-| `/profit` | | Display a summary of your profit/loss from close trades and some stats about your performance
-| `/forcesell ` | | Instantly sells the given trade  (Ignoring `minimum_roi`).
-| `/forcesell all` | | Instantly sells all open trades (Ignoring `minimum_roi`).
-| `/forcebuy  [rate]` | | Instantly buys the given pair. Rate is optional. (`forcebuy_enable` must be set to True)
-| `/performance` | | Show performance of each finished trade grouped by pair
-| `/balance` | | Show account balance per currency
-| `/daily ` | 7 | Shows profit or loss per day, over the last n days
-| `/whitelist` | | Show the current whitelist
-| `/blacklist [pair]` | | Show the current blacklist, or adds a pair to the blacklist.
-| `/edge` | | Show validated pairs by Edge if it is enabled.
-| `/help` | | Show help message
-| `/version` | | Show version
+|  Command | Description |
+|----------|-------------|
+| `/start` | Starts the trader
+| `/stop` | Stops the trader
+| `/stopbuy` | Stops the trader from opening new trades. Gracefully closes open trades according to their rules.
+| `/reload_config` | Reloads the configuration file
+| `/show_config` | Shows part of the current configuration with relevant settings to operation
+| `/status` | Lists all open trades
+| `/status table` | List all open trades in a table format. Pending buy orders are marked with an asterisk (*) Pending sell orders are marked with a double asterisk (**)
+| `/trades [limit]` | List all recently closed trades in a table format.
+| `/delete ` | Delete a specific trade from the Database. Tries to close open orders. Requires manual handling of this trade on the exchange.
+| `/count` | Displays number of trades used and available
+| `/profit` | Display a summary of your profit/loss from close trades and some stats about your performance
+| `/forcesell ` | Instantly sells the given trade  (Ignoring `minimum_roi`).
+| `/forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`).
+| `/forcebuy  [rate]` | Instantly buys the given pair. Rate is optional. (`forcebuy_enable` must be set to True)
+| `/performance` | Show performance of each finished trade grouped by pair
+| `/balance` | Show account balance per currency
+| `/daily ` | Shows profit or loss per day, over the last n days (n defaults to 7)
+| `/whitelist` | Show the current whitelist
+| `/blacklist [pair]` | Show the current blacklist, or adds a pair to the blacklist.
+| `/edge` | Show validated pairs by Edge if it is enabled.
+| `/help` | Show help message
+| `/version` | Show version
 
 ## Telegram commands in action
 

From 17613f203a2f898b8388a7eb85c82ac1acf5c5e8 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 10 Aug 2020 06:17:04 +0000
Subject: [PATCH 67/91] Bump mkdocs-material from 5.5.1 to 5.5.3

Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 5.5.1 to 5.5.3.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/5.5.1...5.5.3)

Signed-off-by: dependabot[bot] 
---
 docs/requirements-docs.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt
index c30661b6a..4068e364b 100644
--- a/docs/requirements-docs.txt
+++ b/docs/requirements-docs.txt
@@ -1,2 +1,2 @@
-mkdocs-material==5.5.1
+mkdocs-material==5.5.3
 mdx_truly_sane_lists==1.2

From 1afe4df7be5234630475ebdffc6d875c2467ddf1 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 10 Aug 2020 06:17:36 +0000
Subject: [PATCH 68/91] Bump ccxt from 1.32.45 to 1.32.88

Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.32.45 to 1.32.88.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst)
- [Commits](https://github.com/ccxt/ccxt/compare/1.32.45...1.32.88)

Signed-off-by: dependabot[bot] 
---
 requirements-common.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements-common.txt b/requirements-common.txt
index 62cde9dbc..b7e71eada 100644
--- a/requirements-common.txt
+++ b/requirements-common.txt
@@ -1,6 +1,6 @@
 # requirements without requirements installable via conda
 # mainly used for Raspberry pi installs
-ccxt==1.32.45
+ccxt==1.32.88
 SQLAlchemy==1.3.18
 python-telegram-bot==12.8
 arrow==0.15.8

From c9c43d2f0b6adc59811a292efe95448d411f1499 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Tue, 11 Aug 2020 15:27:41 +0200
Subject: [PATCH 69/91] Move log-message of retrying before decrementing count

Otherwise the message is always one round "late".
---
 freqtrade/exchange/common.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/freqtrade/exchange/common.py b/freqtrade/exchange/common.py
index 0610e8447..cbab742b7 100644
--- a/freqtrade/exchange/common.py
+++ b/freqtrade/exchange/common.py
@@ -107,9 +107,9 @@ def retrier_async(f):
         except TemporaryError as ex:
             logger.warning('%s() returned exception: "%s"', f.__name__, ex)
             if count > 0:
+                logger.warning('retrying %s() still for %s times', f.__name__, count)
                 count -= 1
                 kwargs.update({'count': count})
-                logger.warning('retrying %s() still for %s times', f.__name__, count)
                 if isinstance(ex, DDosProtection):
                     backoff_delay = calculate_backoff(count + 1, API_RETRY_COUNT)
                     logger.debug(f"Applying DDosProtection backoff delay: {backoff_delay}")
@@ -131,9 +131,9 @@ def retrier(_func=None, retries=API_RETRY_COUNT):
             except (TemporaryError, RetryableOrderError) as ex:
                 logger.warning('%s() returned exception: "%s"', f.__name__, ex)
                 if count > 0:
+                    logger.warning('retrying %s() still for %s times', f.__name__, count)
                     count -= 1
                     kwargs.update({'count': count})
-                    logger.warning('retrying %s() still for %s times', f.__name__, count)
                     if isinstance(ex, DDosProtection) or isinstance(ex, RetryableOrderError):
                         # increasing backoff
                         backoff_delay = calculate_backoff(count + 1, retries)

From d77c53960dfc789a5ae013ac89a04b26f4f4456f Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Tue, 11 Aug 2020 19:27:25 +0200
Subject: [PATCH 70/91] Show API backoff in logs to better investigate eventual
 problems)

---
 freqtrade/exchange/common.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/freqtrade/exchange/common.py b/freqtrade/exchange/common.py
index cbab742b7..3bba9be72 100644
--- a/freqtrade/exchange/common.py
+++ b/freqtrade/exchange/common.py
@@ -112,7 +112,7 @@ def retrier_async(f):
                 kwargs.update({'count': count})
                 if isinstance(ex, DDosProtection):
                     backoff_delay = calculate_backoff(count + 1, API_RETRY_COUNT)
-                    logger.debug(f"Applying DDosProtection backoff delay: {backoff_delay}")
+                    logger.info(f"Applying DDosProtection backoff delay: {backoff_delay}")
                     await asyncio.sleep(backoff_delay)
                 return await wrapper(*args, **kwargs)
             else:
@@ -137,7 +137,7 @@ def retrier(_func=None, retries=API_RETRY_COUNT):
                     if isinstance(ex, DDosProtection) or isinstance(ex, RetryableOrderError):
                         # increasing backoff
                         backoff_delay = calculate_backoff(count + 1, retries)
-                        logger.debug(f"Applying DDosProtection backoff delay: {backoff_delay}")
+                        logger.info(f"Applying DDosProtection backoff delay: {backoff_delay}")
                         time.sleep(backoff_delay)
                     return wrapper(*args, **kwargs)
                 else:

From 77541935a8d686b0b1feb1ce345261a2c40436e8 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Tue, 11 Aug 2020 20:10:43 +0200
Subject: [PATCH 71/91] Fix small merge mistake

---
 freqtrade/commands/hyperopt_commands.py | 16 ++++-----
 tests/commands/test_commands.py         | 44 ++++++++++++++++---------
 2 files changed, 37 insertions(+), 23 deletions(-)

diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py
index 7b079bdfe..b769100be 100755
--- a/freqtrade/commands/hyperopt_commands.py
+++ b/freqtrade/commands/hyperopt_commands.py
@@ -183,17 +183,17 @@ def _hyperopt_filter_epochs(epochs: List, filteroptions: dict) -> List:
             if x['results_metrics']['profit'] < filteroptions['filter_max_total_profit']
         ]
     if filteroptions['filter_min_objective'] is not None:
-        trials = [x for x in trials if x['results_metrics']['trade_count'] > 0]
-        # trials = [x for x in trials if x['loss'] != 20]
-        trials = [
-            x for x in trials
+        epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
+        # epochs = [x for x in epochs if x['loss'] != 20]
+        epochs = [
+            x for x in epochs
             if x['loss'] < filteroptions['filter_min_objective']
         ]
     if filteroptions['filter_max_objective'] is not None:
-        trials = [x for x in trials if x['results_metrics']['trade_count'] > 0]
-        # trials = [x for x in trials if x['loss'] != 20]
-        trials = [
-            x for x in trials
+        epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
+        # epochs = [x for x in epochs if x['loss'] != 20]
+        epochs = [
+            x for x in epochs
             if x['loss'] > filteroptions['filter_max_objective']
         ]
 
diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py
index 4ef4ec6c5..1eb017465 100644
--- a/tests/commands/test_commands.py
+++ b/tests/commands/test_commands.py
@@ -736,7 +736,8 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
 
     args = [
         "hyperopt-list",
-        "--no-details"
+        "--no-details",
+        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -749,7 +750,8 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
     args = [
         "hyperopt-list",
         "--best",
-        "--no-details"
+        "--no-details",
+        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -763,7 +765,8 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
     args = [
         "hyperopt-list",
         "--profitable",
-        "--no-details"
+        "--no-details",
+        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -776,7 +779,8 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
                          " 11/12", " 12/12"])
     args = [
         "hyperopt-list",
-        "--profitable"
+        "--profitable",
+        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -792,7 +796,8 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
         "hyperopt-list",
         "--no-details",
         "--no-color",
-        "--min-trades", "20"
+        "--min-trades", "20",
+        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -806,7 +811,8 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
         "hyperopt-list",
         "--profitable",
         "--no-details",
-        "--max-trades", "20"
+        "--max-trades", "20",
+        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -821,7 +827,8 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
         "hyperopt-list",
         "--profitable",
         "--no-details",
-        "--min-avg-profit", "0.11"
+        "--min-avg-profit", "0.11",
+        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -835,7 +842,8 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
     args = [
         "hyperopt-list",
         "--no-details",
-        "--max-avg-profit", "0.10"
+        "--max-avg-profit", "0.10",
+        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -849,7 +857,8 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
     args = [
         "hyperopt-list",
         "--no-details",
-        "--min-total-profit", "0.4"
+        "--min-total-profit", "0.4",
+        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -863,7 +872,8 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
     args = [
         "hyperopt-list",
         "--no-details",
-        "--max-total-profit", "0.4"
+        "--max-total-profit", "0.4",
+        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -877,7 +887,8 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
     args = [
         "hyperopt-list",
         "--no-details",
-        "--min-objective", "0.1"
+        "--min-objective", "0.1",
+        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -891,7 +902,8 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
     args = [
         "hyperopt-list",
         "--no-details",
-        "--max-objective", "0.1"
+        "--max-objective", "0.1",
+        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -906,7 +918,8 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
         "hyperopt-list",
         "--profitable",
         "--no-details",
-        "--min-avg-time", "2000"
+        "--min-avg-time", "2000",
+        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -920,7 +933,8 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
     args = [
         "hyperopt-list",
         "--no-details",
-        "--max-avg-time", "1500"
+        "--max-avg-time", "1500",
+        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -934,7 +948,7 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
     args = [
         "hyperopt-list",
         "--no-details",
-        "--export-csv", "test_file.csv"
+        "--export-csv", "test_file.csv",
     ]
     pargs = get_args(args)
     pargs['config'] = None

From f51c03aa864e4489bb55886cf86b3be0bc413216 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Tue, 11 Aug 2020 20:29:47 +0200
Subject: [PATCH 72/91] Revert changes to color using --no-color

---
 tests/commands/test_commands.py | 14 --------------
 1 file changed, 14 deletions(-)

diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py
index 1eb017465..69d80d2cd 100644
--- a/tests/commands/test_commands.py
+++ b/tests/commands/test_commands.py
@@ -737,7 +737,6 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
     args = [
         "hyperopt-list",
         "--no-details",
-        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -751,7 +750,6 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
         "hyperopt-list",
         "--best",
         "--no-details",
-        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -766,7 +764,6 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
         "hyperopt-list",
         "--profitable",
         "--no-details",
-        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -780,7 +777,6 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
     args = [
         "hyperopt-list",
         "--profitable",
-        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -797,7 +793,6 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
         "--no-details",
         "--no-color",
         "--min-trades", "20",
-        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -812,7 +807,6 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
         "--profitable",
         "--no-details",
         "--max-trades", "20",
-        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -828,7 +822,6 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
         "--profitable",
         "--no-details",
         "--min-avg-profit", "0.11",
-        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -843,7 +836,6 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
         "hyperopt-list",
         "--no-details",
         "--max-avg-profit", "0.10",
-        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -858,7 +850,6 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
         "hyperopt-list",
         "--no-details",
         "--min-total-profit", "0.4",
-        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -873,7 +864,6 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
         "hyperopt-list",
         "--no-details",
         "--max-total-profit", "0.4",
-        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -888,7 +878,6 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
         "hyperopt-list",
         "--no-details",
         "--min-objective", "0.1",
-        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -903,7 +892,6 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
         "hyperopt-list",
         "--no-details",
         "--max-objective", "0.1",
-        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -919,7 +907,6 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
         "--profitable",
         "--no-details",
         "--min-avg-time", "2000",
-        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None
@@ -934,7 +921,6 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
         "hyperopt-list",
         "--no-details",
         "--max-avg-time", "1500",
-        "--no-color",
     ]
     pargs = get_args(args)
     pargs['config'] = None

From 56655b97cfc52116c7f990405dee6d5ebaea2ce7 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Tue, 11 Aug 2020 20:37:01 +0200
Subject: [PATCH 73/91] Refactor hyperopt_filter method

---
 freqtrade/commands/hyperopt_commands.py | 38 ++++++++++++++++++++++---
 1 file changed, 34 insertions(+), 4 deletions(-)

diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py
index b769100be..a724443cf 100755
--- a/freqtrade/commands/hyperopt_commands.py
+++ b/freqtrade/commands/hyperopt_commands.py
@@ -10,7 +10,7 @@ from freqtrade.state import RunMode
 
 logger = logging.getLogger(__name__)
 
-# flake8: noqa C901
+
 def start_hyperopt_list(args: Dict[str, Any]) -> None:
     """
     List hyperopt epochs previously evaluated
@@ -47,7 +47,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
     epochs = Hyperopt.load_previous_results(results_file)
     total_epochs = len(epochs)
 
-    epochs = _hyperopt_filter_epochs(epochs, filteroptions)
+    epochs = hyperopt_filter_epochs(epochs, filteroptions)
 
     if print_colorized:
         colorama_init(autoreset=True)
@@ -108,7 +108,7 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None:
     epochs = Hyperopt.load_previous_results(results_file)
     total_epochs = len(epochs)
 
-    epochs = _hyperopt_filter_epochs(epochs, filteroptions)
+    epochs = hyperopt_filter_epochs(epochs, filteroptions)
     filtered_epochs = len(epochs)
 
     if n > filtered_epochs:
@@ -128,7 +128,7 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None:
                                      header_str="Epoch details")
 
 
-def _hyperopt_filter_epochs(epochs: List, filteroptions: dict) -> List:
+def hyperopt_filter_epochs(epochs: List, filteroptions: dict) -> List:
     """
     Filter our items from the list of hyperopt results
     """
@@ -136,6 +136,20 @@ def _hyperopt_filter_epochs(epochs: List, filteroptions: dict) -> List:
         epochs = [x for x in epochs if x['is_best']]
     if filteroptions['only_profitable']:
         epochs = [x for x in epochs if x['results_metrics']['profit'] > 0]
+
+    epochs = _hyperopt_filter_epochs_trade_count(epochs, filteroptions)
+
+    epochs = _hyperopt_filter_epochs_duration(epochs, filteroptions)
+
+    epochs = _hyperopt_filter_epochs_profit(epochs, filteroptions)
+
+    epochs = _hyperopt_filter_epochs_objective(epochs, filteroptions)
+
+    return epochs
+
+
+def _hyperopt_filter_epochs_trade_count(epochs: List, filteroptions: dict) -> List:
+
     if filteroptions['filter_min_trades'] > 0:
         epochs = [
             x for x in epochs
@@ -146,6 +160,11 @@ def _hyperopt_filter_epochs(epochs: List, filteroptions: dict) -> List:
             x for x in epochs
             if x['results_metrics']['trade_count'] < filteroptions['filter_max_trades']
         ]
+    return epochs
+
+
+def _hyperopt_filter_epochs_duration(epochs: List, filteroptions: dict) -> List:
+
     if filteroptions['filter_min_avg_time'] is not None:
         epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
         epochs = [
@@ -158,6 +177,12 @@ def _hyperopt_filter_epochs(epochs: List, filteroptions: dict) -> List:
             x for x in epochs
             if x['results_metrics']['duration'] < filteroptions['filter_max_avg_time']
         ]
+
+    return epochs
+
+
+def _hyperopt_filter_epochs_profit(epochs: List, filteroptions: dict) -> List:
+
     if filteroptions['filter_min_avg_profit'] is not None:
         epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
         epochs = [
@@ -182,6 +207,11 @@ def _hyperopt_filter_epochs(epochs: List, filteroptions: dict) -> List:
             x for x in epochs
             if x['results_metrics']['profit'] < filteroptions['filter_max_total_profit']
         ]
+    return epochs
+
+
+def _hyperopt_filter_epochs_objective(epochs: List, filteroptions: dict) -> List:
+
     if filteroptions['filter_min_objective'] is not None:
         epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
         # epochs = [x for x in epochs if x['loss'] != 20]

From 2dc36bb79eece79fd20c7ffac92fb1c12ba4177e Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Tue, 11 Aug 2020 20:52:18 +0200
Subject: [PATCH 74/91] Remove inversion of min/max objective selection

---
 freqtrade/commands/hyperopt_commands.py | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py
index a724443cf..d37c1f13b 100755
--- a/freqtrade/commands/hyperopt_commands.py
+++ b/freqtrade/commands/hyperopt_commands.py
@@ -99,11 +99,6 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None:
         'filter_max_objective': config.get('hyperopt_list_max_objective', None)
     }
 
-    if filteroptions['filter_min_objective'] is not None:
-        filteroptions['filter_min_objective'] = -filteroptions['filter_min_objective']
-    if filteroptions['filter_max_objective'] is not None:
-        filteroptions['filter_max_objective'] = -filteroptions['filter_max_objective']
-
     # Previous evaluations
     epochs = Hyperopt.load_previous_results(results_file)
     total_epochs = len(epochs)

From 2fed066e767bb3c3015af5d4a9f31ad588619fad Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Wed, 12 Aug 2020 10:39:53 +0200
Subject: [PATCH 75/91] Simplify objective code formatting

---
 freqtrade/commands/hyperopt_commands.py | 21 +++++++--------------
 1 file changed, 7 insertions(+), 14 deletions(-)

diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py
index d37c1f13b..4fae51e28 100755
--- a/freqtrade/commands/hyperopt_commands.py
+++ b/freqtrade/commands/hyperopt_commands.py
@@ -140,6 +140,10 @@ def hyperopt_filter_epochs(epochs: List, filteroptions: dict) -> List:
 
     epochs = _hyperopt_filter_epochs_objective(epochs, filteroptions)
 
+    logger.info(f"{len(epochs)} " +
+                ("best " if filteroptions['only_best'] else "") +
+                ("profitable " if filteroptions['only_profitable'] else "") +
+                "epochs found.")
     return epochs
 
 
@@ -209,22 +213,11 @@ def _hyperopt_filter_epochs_objective(epochs: List, filteroptions: dict) -> List
 
     if filteroptions['filter_min_objective'] is not None:
         epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
-        # epochs = [x for x in epochs if x['loss'] != 20]
-        epochs = [
-            x for x in epochs
-            if x['loss'] < filteroptions['filter_min_objective']
-        ]
+
+        epochs = [x for x in epochs if x['loss'] < filteroptions['filter_min_objective']]
     if filteroptions['filter_max_objective'] is not None:
         epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
-        # epochs = [x for x in epochs if x['loss'] != 20]
-        epochs = [
-            x for x in epochs
-            if x['loss'] > filteroptions['filter_max_objective']
-        ]
 
-    logger.info(f"{len(epochs)} " +
-                ("best " if filteroptions['only_best'] else "") +
-                ("profitable " if filteroptions['only_profitable'] else "") +
-                "epochs found.")
+        epochs = [x for x in epochs if x['loss'] > filteroptions['filter_max_objective']]
 
     return epochs

From 1f1a819b292ecf927c2d332a1fd788ac5b7359a6 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Wed, 12 Aug 2020 11:21:00 +0200
Subject: [PATCH 76/91] Remove unused 3rd argument to create_stoploss call

---
 freqtrade/freqtradebot.py  | 10 ++++------
 tests/test_freqtradebot.py |  2 +-
 2 files changed, 5 insertions(+), 7 deletions(-)

diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index 967f68b90..3168976e2 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -768,7 +768,7 @@ class FreqtradeBot:
         logger.debug('Found no sell signal for %s.', trade)
         return False
 
-    def create_stoploss_order(self, trade: Trade, stop_price: float, rate: float) -> bool:
+    def create_stoploss_order(self, trade: Trade, stop_price: float) -> bool:
         """
         Abstracts creating stoploss orders from the logic.
         Handles errors and updates the trade database object.
@@ -831,14 +831,13 @@ class FreqtradeBot:
             stoploss = self.edge.stoploss(pair=trade.pair) if self.edge else self.strategy.stoploss
             stop_price = trade.open_rate * (1 + stoploss)
 
-            if self.create_stoploss_order(trade=trade, stop_price=stop_price, rate=stop_price):
+            if self.create_stoploss_order(trade=trade, stop_price=stop_price):
                 trade.stoploss_last_update = datetime.now()
                 return False
 
         # If stoploss order is canceled for some reason we add it
         if stoploss_order and stoploss_order['status'] in ('canceled', 'cancelled'):
-            if self.create_stoploss_order(trade=trade, stop_price=trade.stop_loss,
-                                          rate=trade.stop_loss):
+            if self.create_stoploss_order(trade=trade, stop_price=trade.stop_loss):
                 return False
             else:
                 trade.stoploss_order_id = None
@@ -875,8 +874,7 @@ class FreqtradeBot:
                                      f"for pair {trade.pair}")
 
                 # Create new stoploss order
-                if not self.create_stoploss_order(trade=trade, stop_price=trade.stop_loss,
-                                                  rate=trade.stop_loss):
+                if not self.create_stoploss_order(trade=trade, stop_price=trade.stop_loss):
                     logger.warning(f"Could not create trailing stoploss order "
                                    f"for pair {trade.pair}.")
 
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
index 5c225bbc0..87071be3e 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -1301,7 +1301,7 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf, caplog, fee,
     freqtrade.enter_positions()
     trade = Trade.query.first()
     caplog.clear()
-    freqtrade.create_stoploss_order(trade, 200, 199)
+    freqtrade.create_stoploss_order(trade, 200)
     assert trade.stoploss_order_id is None
     assert trade.sell_reason == SellType.EMERGENCY_SELL.value
     assert log_has("Unable to place a stoploss order on exchange. ", caplog)

From 6dfa159a914968d6a8838020521bfb1da5f1a905 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Wed, 12 Aug 2020 14:11:19 +0200
Subject: [PATCH 77/91] Small comment adjustments in exchange class

---
 freqtrade/exchange/exchange.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
index 8438941f7..f8bac3a8d 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -974,7 +974,7 @@ class Exchange:
         except ccxt.BaseError as e:
             raise OperationalException(e) from e
 
-    # Assign method to fetch_stoploss_order to allow easy overriding in other classes
+    # Assign method to cancel_stoploss_order to allow easy overriding in other classes
     cancel_stoploss_order = cancel_order
 
     def is_cancel_order_result_suitable(self, corder) -> bool:
@@ -1040,10 +1040,10 @@ class Exchange:
     @retrier
     def fetch_l2_order_book(self, pair: str, limit: int = 100) -> dict:
         """
-        get order book level 2 from exchange
-
-        Notes:
-        20180619: bittrex doesnt support limits -.-
+        Get L2 order book from exchange.
+        Can be limited to a certain amount (if supported).
+        Returns a dict in the format
+        {'asks': [price, volume], 'bids': [price, volume]}
         """
         try:
 

From faa2bbb5553a19b21341c031e9eef46508551254 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Wed, 12 Aug 2020 14:25:50 +0200
Subject: [PATCH 78/91] Document exception hierarchy

---
 docs/developer.md          | 29 +++++++++++++++++++++++++++++
 freqtrade/exceptions.py    | 16 ++++++++--------
 freqtrade/freqtradebot.py  |  4 ++--
 freqtrade/rpc/rpc.py       |  6 +++---
 tests/test_freqtradebot.py |  3 ++-
 5 files changed, 44 insertions(+), 14 deletions(-)

diff --git a/docs/developer.md b/docs/developer.md
index 036109d5b..ce454cec2 100644
--- a/docs/developer.md
+++ b/docs/developer.md
@@ -85,6 +85,35 @@ docker-compose exec freqtrade_develop /bin/bash
 
 ![image](https://user-images.githubusercontent.com/419355/65456522-ba671a80-de06-11e9-9598-df9ca0d8dcac.png)
 
+## ErrorHandling
+
+Freqtrade Exceptions all inherit from `FreqtradeException`.
+This general class of error should however not be used directly, instead, multiple specialized sub-Exceptions exist.
+
+Below is an outline of exception inheritance hierarchy:
+
+```
++ FreqtradeException
+|
++---+ OperationalException
+|
++---+ DependencyException
+|   |
+|   +---+ PricingError
+|   |
+|   +---+ ExchangeError
+|       |
+|       +---+ TemporaryError
+|       |
+|       +---+ DDosProtection
+|       |
+|       +---+ InvalidOrderException
+|           |
+|           +---+ RetryableOrderError
+|
++---+ StrategyError
+```
+
 ## Modules
 
 ### Dynamic Pairlist
diff --git a/freqtrade/exceptions.py b/freqtrade/exceptions.py
index c85fccc4b..e2bc969a9 100644
--- a/freqtrade/exceptions.py
+++ b/freqtrade/exceptions.py
@@ -29,7 +29,14 @@ class PricingError(DependencyException):
     """
 
 
-class InvalidOrderException(FreqtradeException):
+class ExchangeError(DependencyException):
+    """
+    Error raised out of the exchange.
+    Has multiple Errors to determine the appropriate error.
+    """
+
+
+class InvalidOrderException(ExchangeError):
     """
     This is returned when the order is not valid. Example:
     If stoploss on exchange order is hit, then trying to cancel the order
@@ -44,13 +51,6 @@ class RetryableOrderError(InvalidOrderException):
     """
 
 
-class ExchangeError(DependencyException):
-    """
-    Error raised out of the exchange.
-    Has multiple Errors to determine the appropriate error.
-    """
-
-
 class TemporaryError(ExchangeError):
     """
     Temporary network or exchange related error.
diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index 3168976e2..557aefe94 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -919,7 +919,7 @@ class FreqtradeBot:
                 if not trade.open_order_id:
                     continue
                 order = self.exchange.fetch_order(trade.open_order_id, trade.pair)
-            except (ExchangeError, InvalidOrderException):
+            except (ExchangeError):
                 logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc())
                 continue
 
@@ -952,7 +952,7 @@ class FreqtradeBot:
         for trade in Trade.get_open_order_trades():
             try:
                 order = self.exchange.fetch_order(trade.open_order_id, trade.pair)
-            except (DependencyException, InvalidOrderException):
+            except (ExchangeError):
                 logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc())
                 continue
 
diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index 8a1ff7e96..f4e20c16f 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -11,7 +11,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union
 import arrow
 from numpy import NAN, mean
 
-from freqtrade.exceptions import (ExchangeError, InvalidOrderException,
+from freqtrade.exceptions import (ExchangeError,
                                   PricingError)
 from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs
 from freqtrade.misc import shorten_date
@@ -555,7 +555,7 @@ class RPC:
                 try:
                     self._freqtrade.exchange.cancel_order(trade.open_order_id, trade.pair)
                     c_count += 1
-                except (ExchangeError, InvalidOrderException):
+                except (ExchangeError):
                     pass
 
             # cancel stoploss on exchange ...
@@ -565,7 +565,7 @@ class RPC:
                     self._freqtrade.exchange.cancel_stoploss_order(trade.stoploss_order_id,
                                                                    trade.pair)
                     c_count += 1
-                except (ExchangeError, InvalidOrderException):
+                except (ExchangeError):
                     pass
 
             Trade.session.delete(trade)
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
index 87071be3e..2c6d2314c 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -1,6 +1,7 @@
 # pragma pylint: disable=missing-docstring, C0103
 # pragma pylint: disable=protected-access, too-many-lines, invalid-name, too-many-arguments
 
+from freqtrade.exchange.exchange import Exchange
 import logging
 import time
 from copy import deepcopy
@@ -4107,7 +4108,7 @@ def test_sync_wallet_dry_run(mocker, default_conf, ticker, fee, limit_buy_order,
 def test_cancel_all_open_orders(mocker, default_conf, fee, limit_buy_order, limit_sell_order):
     default_conf['cancel_open_orders_on_exit'] = True
     mocker.patch('freqtrade.exchange.Exchange.fetch_order',
-                 side_effect=[DependencyException(), limit_sell_order, limit_buy_order])
+                 side_effect=[ExchangeError(), limit_sell_order, limit_buy_order])
     buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_buy')
     sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_sell')
 

From 815d88fd4a4f2b133653df7fa75d6fa2c40c69ed Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Wed, 12 Aug 2020 15:32:56 +0200
Subject: [PATCH 79/91] Fix test after merge, fix forgotten 'amount'

---
 freqtrade/freqtradebot.py | 2 +-
 freqtrade/persistence.py  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index b4ef2b086..816d24e18 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -553,7 +553,7 @@ class FreqtradeBot:
                                order['filled'], order['amount'], order['remaining']
                                )
                 stake_amount = order['cost']
-                amount = order['filled']
+                amount = safe_value_fallback(order, 'filled', 'amount')
                 buy_limit_filled_price = safe_value_fallback(order, 'average', 'price')
                 order_id = None
 
diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py
index fdb816eab..28753ed48 100644
--- a/freqtrade/persistence.py
+++ b/freqtrade/persistence.py
@@ -259,7 +259,7 @@ class Trade(_DECL_BASE):
             'is_open': self.is_open,
             'exchange': self.exchange,
             'amount': round(self.amount, 8),
-            'amount_requested': round(self.amount_requested, 8),
+            'amount_requested': round(self.amount_requested, 8) if self.amount_requested else None,
             'stake_amount': round(self.stake_amount, 8),
             'strategy': self.strategy,
             'ticker_interval': self.timeframe,  # DEPRECATED

From 3afd5b631e39a85d3c0536979f8e529c6c82a917 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Wed, 12 Aug 2020 15:34:29 +0200
Subject: [PATCH 80/91] Remove erroneous import

---
 tests/test_freqtradebot.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
index 2c6d2314c..ec59ca5b0 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -1,7 +1,6 @@
 # pragma pylint: disable=missing-docstring, C0103
 # pragma pylint: disable=protected-access, too-many-lines, invalid-name, too-many-arguments
 
-from freqtrade.exchange.exchange import Exchange
 import logging
 import time
 from copy import deepcopy

From 827c31d4bc4e7511acdf5d0f37313a1343251c8a Mon Sep 17 00:00:00 2001
From: Blackhawke 
Date: Wed, 12 Aug 2020 09:42:16 -0700
Subject: [PATCH 81/91] Re-arranged the introduction to better explain the
 theory of operation and the limitations of Edge. Added paragraphs at the
 bottom of "running edge independently" to better explain Edge's order of
 operations processing and potential differences between historical output and
 live/dry-run operation.

---
 docs/edge.md | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/docs/edge.md b/docs/edge.md
index c91e72a3a..ccbae1cb1 100644
--- a/docs/edge.md
+++ b/docs/edge.md
@@ -2,11 +2,13 @@
 
 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.
 
-!!! Warning
+  !!! Warning
     Edge positioning is not compatible with dynamic (volume-based) whitelist.
 
-!!! Note
-    Edge does not consider anything else than buy/sell/stoploss signals. So trailing stoploss, ROI, and everything else are ignored in its calculation.
+  !!! Note
+ 1. Edge does not consider anything other than *its own* buy/sell/stoploss signals. It ignores the stoploss, trailing stoploss, and ROI settings in the strategy configuration file.
+
+ 2. Therefore, it is important to understand that Edge can improve the performance of some trading strategies but *decrease* the performance of others.
 
 ## Introduction
 
@@ -89,7 +91,7 @@ You can also use this value to evaluate the effectiveness of modifications to th
 
 ## How does it work?
 
-If enabled in config, Edge will go through historical data with a range of stoplosses in order to find buy and sell/stoploss signals. It then calculates win rate and expectancy over *N* trades for each stoploss. Here is an example:
+Edge combines dynamic stoploss, dynamic positions, and whitelist generation into one isolated module which is then applied to the trading strategy. If enabled in config, Edge will go through historical data with a range of stoplosses in order to find buy and sell/stoploss signals. It then calculates win rate and expectancy over *N* trades for each stoploss. Here is an example:
 
 | Pair   |      Stoploss      |  Win Rate | Risk Reward Ratio | Expectancy |
 |----------|:-------------:|-------------:|------------------:|-----------:|
@@ -186,6 +188,10 @@ An example of its output:
 | 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 |
 
+Edge produced the above table by comparing ``calculate_since_number_of_days`` to ``minimum_expectancy`` to find ``min_trade_number``. Historical information based on the config file. The time frame Edge uses for its comparisons can be further limited by using the ``--timeframe`` switch.
+
+In live and dry-run modes, after the ``process_throttle_secs`` has passed, Edge will again process ``calculate_since_number_of_days`` against ``minimum_expectancy`` to find ``min_trade_number``. If no ``min_trade_number`` is found, the bot will return "whitelist empty". Depending on the trade strategy being deployed, "whitelist empty" may be return much of the time---or *all* of the time. The use of Edge may also cause trading to occur in bursts, though this is rare.
+
 ### Update cached pairs with the latest data
 
 Edge requires historic data the same way as backtesting does.

From 1dabade883f13dcdff990b6069fe9d8a1aab498c Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Thu, 13 Aug 2020 08:02:36 +0200
Subject: [PATCH 82/91] small rewording of FAQ documentation

---
 docs/faq.md | 24 +++++++++++++++---------
 1 file changed, 15 insertions(+), 9 deletions(-)

diff --git a/docs/faq.md b/docs/faq.md
index cc43e326d..514b01085 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -19,11 +19,11 @@ This could have the following reasons:
 
 ### I have waited 5 minutes, why hasn't the bot made any trades yet?!
 
-#1 Depending on the buy strategy, the amount of whitelisted coins, the
+* Depending on the buy strategy, the amount of whitelisted coins, the
 situation of the market etc, it can take up to hours to find good entry
 position for a trade. Be patient!
 
-#2 Or it may because you made an human error? Like writing --dry-run when you wanted to trade live?. Maybe an error with the exchange API? Or something else. You will have to do the hard work of finding out the root cause of the problem :) 
+* Or it may because of a configuration error? Best check the logs, it's usually telling you if the bot is simply not getting buy signals (only heartbeat messages), or if there is something wrong (errors / exceptions in the log).
 
 ### I have made 12 trades already, why is my total profit negative?!
 
@@ -135,7 +135,9 @@ to find a great result (unless if you are very lucky), so you probably
 have to run it for 10.000 or more. But it will take an eternity to
 compute.
 
-We recommend you to run between 500-1000 epochs over and over untill you hit at least 10.000 epocs in total. You can best judge by looking at the results - if the bot keep discovering more profitable strategies or not. 
+Since hyperopt uses Bayesian search, running for too many epochs may not produce greater results.
+
+It's therefore recommended to run between 500-1000 epochs over and over until you hit at least 10.000 epocs in total (or are satisfied with the result). You can best judge by looking at the results - if the bot keeps discovering better strategies, it's best to keep on going. 
 
 ```bash
 freqtrade hyperopt -e 1000
@@ -147,11 +149,11 @@ or if you want intermediate result to see
 for i in {1..100}; do freqtrade hyperopt -e 1000; done
 ```
 
-### Why does it take so long time to run hyperopt?
+### Why does it take a long time to run hyperopt?
 
-#1 Discovering a great strategy with Hyperopt takes time. Study www.freqtrade.io, the Freqtrade Github page, join the Freqtrade Discord - or something totally else. While you patiently wait for the most advanced, public known, crypto bot, in the world, to hand you a possible golden strategy specially designed just for you =) 
+* Discovering a great strategy with Hyperopt takes time. Study www.freqtrade.io, the Freqtrade Documentation page, join the Freqtrade [Slack community](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE) - or the Freqtrade [discord community](https://discord.gg/X89cVG). While you patiently wait for the most advanced, free crypto bot in the world, to hand you a possible golden strategy specially designed just for you.
 
-#2 If you wonder why it can take from 20 minutes to days to do 1000 epocs here are some answers:
+* If you wonder why it can take from 20 minutes to days to do 1000 epocs here are some answers:
 
 This answer was written during the release 0.15.1, when we had:
 
@@ -163,10 +165,14 @@ The following calculation is still very rough and not very precise
 but it will give the idea. With only these triggers and guards there is
 already 8\*10^9\*10 evaluations. A roughly total of 80 billion evals.
 Did you run 100 000 evals? Congrats, you've done roughly 1 / 100 000 th
-of the search space. If we assume that the bot never test the same strategy more than once.
+of the search space, assuming that the bot never tests the same parameters more than once.
 
-#3 The time it takes to run 1000 hyperopt epocs depends on things like: The cpu, harddisk, ram, motherboard, indicator settings, indicator count, amount of coins that hyperopt test strategies on, trade count - can be 650 trades in a year or 10.0000 trades depending on if the strategy aims for a high profit rarely or a low profit many many many times. Example: 4% profit 650 times vs 0,3% profit a trade 10.000 times in a year. If we assume you set the --timerange to 365 days. 
-Example: freqtrade --config config_mcd_1.json --strategy mcd_1 --hyperopt mcd_hyperopt_1 -e 1000 --timerange 20190601-20200601 
+* The time it takes to run 1000 hyperopt epocs depends on things like: The available cpu, harddisk, ram, timeframe, timerange, indicator settings, indicator count, amount of coins that hyperopt test strategies on and the resulting trade count - which can be 650 trades in a year or 10.0000 trades depending if the strategy aims for big profits by trading rarely or for many low profit trades. 
+
+Example: 4% profit 650 times vs 0,3% profit a trade 10.000 times in a year. If we assume you set the --timerange to 365 days. 
+
+Example: 
+`freqtrade --config config.json --strategy SampleStrategy --hyperopt SampleHyperopt -e 1000 --timerange 20190601-20200601`
 
 ## Edge module
 

From e45e41adb457d90db1b4c281275779f44f9d6157 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Thu, 13 Aug 2020 08:05:05 +0200
Subject: [PATCH 83/91] Improve docs test to catch !!! errors

---
 tests/test_docs.sh | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/tests/test_docs.sh b/tests/test_docs.sh
index 09e142b99..8a354daad 100755
--- a/tests/test_docs.sh
+++ b/tests/test_docs.sh
@@ -2,7 +2,8 @@
 # Test Documentation boxes -
 # !!! : is not allowed!
 # !!!  "title" - Title needs to be quoted!
-grep -Er '^!{3}\s\S+:|^!{3}\s\S+\s[^"]' docs/*
+# !!!  Spaces at the beginning are not allowed
+grep -Er '^!{3}\s\S+:|^!{3}\s\S+\s[^"]|^\s+!{3}\s\S+' docs/*
 
 if  [ $? -ne 0 ]; then
     echo "Docs test success."

From 6b85b1a34d65a42a6e24347d9137a6b4f604aef9 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Thu, 13 Aug 2020 08:06:57 +0200
Subject: [PATCH 84/91] Don't only recommend pycharm, but keep it open to other
 editors too.

---
 docs/faq.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/faq.md b/docs/faq.md
index 514b01085..48f52a566 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -2,7 +2,7 @@
 
 ## Beginner Tips & Tricks
 
-#1 When you work with your strategy & hyperopt file you should use a real programmer software like Pycharm. If you by accident moved some code and freqtrade says error and you cant find the place where you moved something, or you cant find line 180 where you messed something up. Then a program like Pycharm shows you where line 180 is in your strategy file so you can fix the problem, or Pycharm shows you with some color marking that "here is a line of code that does not belong here" and you found your error in no time! This will save you many hours of problemsolving when working with the bot. Pycharm also got a usefull "Debug" feature that can tell you exactly what command on that line is making the error :) 
+* When you work with your strategy & hyperopt file you should use a proper code editor like vscode or Pycharm. A good code editor will provide syntax highlighting as well as line numbers, making it easy to find syntax errors (most likely, pointed out by Freqtrade during startup).
 
 ## Freqtrade common issues
 

From 4109b31dac08b607603590c798b41fad745f6bdf Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Fri, 14 Aug 2020 06:46:34 +0200
Subject: [PATCH 85/91] Update wording in documentation

---
 docs/developer.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/developer.md b/docs/developer.md
index ce454cec2..f09ae2c76 100644
--- a/docs/developer.md
+++ b/docs/developer.md
@@ -88,7 +88,7 @@ docker-compose exec freqtrade_develop /bin/bash
 ## ErrorHandling
 
 Freqtrade Exceptions all inherit from `FreqtradeException`.
-This general class of error should however not be used directly, instead, multiple specialized sub-Exceptions exist.
+This general class of error should however not be used directly. Instead, multiple specialized sub-Exceptions exist.
 
 Below is an outline of exception inheritance hierarchy:
 

From d76ee432461a438126439b01e69d00eea3a045fc Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Fri, 14 Aug 2020 07:12:57 +0200
Subject: [PATCH 86/91] Show wins / draws / losses in hyperopt table

---
 freqtrade/optimize/hyperopt.py  | 17 +++++++++++++----
 tests/optimize/test_hyperopt.py |  1 +
 2 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py
index 522b217f7..fbd523904 100644
--- a/freqtrade/optimize/hyperopt.py
+++ b/freqtrade/optimize/hyperopt.py
@@ -312,11 +312,16 @@ class Hyperopt:
 
         trials = json_normalize(results, max_level=1)
         trials['Best'] = ''
+        if 'results_metrics.winsdrawslosses' not in trials.columns:
+            # Ensure compatibility with older versions of hyperopt results
+            trials['results_metrics.winsdrawslosses'] = 'N/A'
+
         trials = trials[['Best', 'current_epoch', 'results_metrics.trade_count',
+                         'results_metrics.winsdrawslosses',
                          'results_metrics.avg_profit', 'results_metrics.total_profit',
                          'results_metrics.profit', 'results_metrics.duration',
                          'loss', 'is_initial_point', 'is_best']]
-        trials.columns = ['Best', 'Epoch', 'Trades', 'Avg profit', 'Total profit',
+        trials.columns = ['Best', 'Epoch', 'Trades', 'W/D/L', 'Avg profit', 'Total profit',
                           'Profit', 'Avg duration', 'Objective', 'is_initial_point', 'is_best']
         trials['is_profit'] = False
         trials.loc[trials['is_initial_point'], 'Best'] = '*     '
@@ -558,11 +563,15 @@ class Hyperopt:
         }
 
     def _calculate_results_metrics(self, backtesting_results: DataFrame) -> Dict:
+        wins = len(backtesting_results[backtesting_results.profit_percent > 0])
+        draws = len(backtesting_results[backtesting_results.profit_percent == 0])
+        losses = len(backtesting_results[backtesting_results.profit_percent < 0])
         return {
             'trade_count': len(backtesting_results.index),
-            'wins': len(backtesting_results[backtesting_results.profit_percent > 0]),
-            'draws': len(backtesting_results[backtesting_results.profit_percent == 0]),
-            'losses': len(backtesting_results[backtesting_results.profit_percent < 0]),
+            'wins': wins,
+            'draws': draws,
+            'losses': losses,
+            'winsdrawslosses': f"{wins}/{draws}/{losses}",
             'avg_profit': backtesting_results.profit_percent.mean() * 100.0,
             'median_profit': backtesting_results.profit_percent.median() * 100.0,
             'total_profit': backtesting_results.profit_abs.sum(),
diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py
index 4b178ca11..bd86e315f 100644
--- a/tests/optimize/test_hyperopt.py
+++ b/tests/optimize/test_hyperopt.py
@@ -781,6 +781,7 @@ def test_generate_optimizer(mocker, default_conf) -> None:
                             'draws': 0,
                             'duration': 100.0,
                             'losses': 0,
+                            'winsdrawslosses': '1/0/0',
                             'median_profit': 2.3117,
                             'profit': 2.3117,
                             'total_profit': 0.000233,

From b98107375edc44ff6d90bfb67c038d839a5f0d47 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Fri, 14 Aug 2020 07:31:14 +0200
Subject: [PATCH 87/91] Improve formatting of result string to be a bit
 conciser

---
 freqtrade/optimize/hyperopt.py  | 5 ++---
 tests/optimize/test_hyperopt.py | 2 +-
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py
index fbd523904..6d11e543b 100644
--- a/freqtrade/optimize/hyperopt.py
+++ b/freqtrade/optimize/hyperopt.py
@@ -585,9 +585,8 @@ class Hyperopt:
         """
         stake_cur = self.config['stake_currency']
         return (f"{results_metrics['trade_count']:6d} trades. "
-                f"{results_metrics['wins']:6d} wins. "
-                f"{results_metrics['draws']:6d} draws. "
-                f"{results_metrics['losses']:6d} losses. "
+                f"{results_metrics['wins']}/{results_metrics['draws']}"
+                f"/{results_metrics['losses']} Wins/Draws/Losses. "
                 f"Avg profit {results_metrics['avg_profit']: 6.2f}%. "
                 f"Median profit {results_metrics['median_profit']: 6.2f}%. "
                 f"Total profit {results_metrics['total_profit']: 11.8f} {stake_cur} "
diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py
index bd86e315f..a6541f55b 100644
--- a/tests/optimize/test_hyperopt.py
+++ b/tests/optimize/test_hyperopt.py
@@ -744,7 +744,7 @@ def test_generate_optimizer(mocker, default_conf) -> None:
     }
     response_expected = {
         'loss': 1.9840569076926293,
-        'results_explanation': ('     1 trades.      1 wins.      0 draws.      0 losses. '
+        'results_explanation': ('     1 trades. 1/0/0 Wins/Draws/Losses. '
                                 'Avg profit   2.31%. Median profit   2.31%. Total profit  '
                                 '0.00023300 BTC (   2.31\N{GREEK CAPITAL LETTER SIGMA}%). '
                                 'Avg duration 100.0 min.'

From 47b215fe0aa23f6222b0b1e107e017d3009cf4e8 Mon Sep 17 00:00:00 2001
From: Blackhawke 
Date: Fri, 14 Aug 2020 09:25:53 -0700
Subject: [PATCH 88/91] Update docs/edge.md

Co-authored-by: Matthias 
---
 docs/edge.md | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/docs/edge.md b/docs/edge.md
index ccbae1cb1..1695732af 100644
--- a/docs/edge.md
+++ b/docs/edge.md
@@ -190,7 +190,9 @@ An example of its output:
 
 Edge produced the above table by comparing ``calculate_since_number_of_days`` to ``minimum_expectancy`` to find ``min_trade_number``. Historical information based on the config file. The time frame Edge uses for its comparisons can be further limited by using the ``--timeframe`` switch.
 
-In live and dry-run modes, after the ``process_throttle_secs`` has passed, Edge will again process ``calculate_since_number_of_days`` against ``minimum_expectancy`` to find ``min_trade_number``. If no ``min_trade_number`` is found, the bot will return "whitelist empty". Depending on the trade strategy being deployed, "whitelist empty" may be return much of the time---or *all* of the time. The use of Edge may also cause trading to occur in bursts, though this is rare.
+In live and dry-run modes, after the `process_throttle_secs` has passed, Edge will again process `calculate_since_number_of_days` against `minimum_expectancy` to find `min_trade_number`. If no `min_trade_number` is found, the bot will return "whitelist empty". Depending on the trade strategy being deployed, "whitelist empty" may be return much of the time - or *all* of the time. The use of Edge may also cause trading to occur in bursts, though this is rare.
+
+If you encounter "whitelist empty" a lot, condsider tuning `calculate_since_number_of_days`, `minimum_expectancy`  and `min_trade_number` to align to the trading frequency of your strategy.
 
 ### Update cached pairs with the latest data
 

From a14ce9d7d9df45bf5ba9ce8e02c559ae8477c064 Mon Sep 17 00:00:00 2001
From: Blackhawke 
Date: Fri, 14 Aug 2020 09:26:28 -0700
Subject: [PATCH 89/91] Update docs/edge.md

Co-authored-by: Matthias 
---
 docs/edge.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/edge.md b/docs/edge.md
index 1695732af..dad23bfd6 100644
--- a/docs/edge.md
+++ b/docs/edge.md
@@ -188,7 +188,7 @@ An example of its output:
 | 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 |
 
-Edge produced the above table by comparing ``calculate_since_number_of_days`` to ``minimum_expectancy`` to find ``min_trade_number``. Historical information based on the config file. The time frame Edge uses for its comparisons can be further limited by using the ``--timeframe`` switch.
+Edge produced the above table by comparing `calculate_since_number_of_days` to `minimum_expectancy` to find `min_trade_number` historical information based on the config file. The timerange Edge uses for its comparisons can be further limited by using the `--timerange` switch.
 
 In live and dry-run modes, after the `process_throttle_secs` has passed, Edge will again process `calculate_since_number_of_days` against `minimum_expectancy` to find `min_trade_number`. If no `min_trade_number` is found, the bot will return "whitelist empty". Depending on the trade strategy being deployed, "whitelist empty" may be return much of the time - or *all* of the time. The use of Edge may also cause trading to occur in bursts, though this is rare.
 

From f3cedc849aa223407890d4c216cfec17cdfda02d Mon Sep 17 00:00:00 2001
From: Blackhawke 
Date: Fri, 14 Aug 2020 09:27:04 -0700
Subject: [PATCH 90/91] Update docs/edge.md

Co-authored-by: Matthias 
---
 docs/edge.md | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/docs/edge.md b/docs/edge.md
index dad23bfd6..2bc43a4e8 100644
--- a/docs/edge.md
+++ b/docs/edge.md
@@ -5,10 +5,9 @@ This page explains how to use Edge Positioning module in your bot in order to en
   !!! Warning
     Edge positioning is not compatible with dynamic (volume-based) whitelist.
 
-  !!! Note
- 1. Edge does not consider anything other than *its own* buy/sell/stoploss signals. It ignores the stoploss, trailing stoploss, and ROI settings in the strategy configuration file.
-
- 2. Therefore, it is important to understand that Edge can improve the performance of some trading strategies but *decrease* the performance of others.
+!!! Note
+    Edge does not consider anything other than *its own* buy/sell/stoploss signals. It ignores the stoploss, trailing stoploss, and ROI settings in the strategy configuration file.
+    Therefore, it is important to understand that Edge can improve the performance of some trading strategies but *decrease* the performance of others.
 
 ## Introduction
 

From 5d691b5ee3872129c3cd6a7bdfd70836e79e72fd Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Fri, 14 Aug 2020 19:34:22 +0200
Subject: [PATCH 91/91] Fix warning box typo

---
 docs/edge.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/edge.md b/docs/edge.md
index 2bc43a4e8..dcb559f96 100644
--- a/docs/edge.md
+++ b/docs/edge.md
@@ -2,7 +2,7 @@
 
 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.
 
-  !!! Warning
+!!! Warning
     Edge positioning is not compatible with dynamic (volume-based) whitelist.
 
 !!! Note