From 0e8f95effd2798655b11d31e6b1659e9acc445ca Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 28 May 2020 06:51:53 +0200 Subject: [PATCH 01/17] Improve blacklist adding with proper feedback --- freqtrade/rpc/rpc.py | 16 +++++++++++++--- tests/rpc/test_rpc.py | 14 ++++++++++++++ tests/rpc/test_rpc_apiserver.py | 8 ++++++-- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 248b4a421..2a49e109a 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -533,16 +533,26 @@ class RPC: def _rpc_blacklist(self, add: List[str] = None) -> Dict: """ Returns the currently active blacklist""" + errors = {} if add: stake_currency = self._freqtrade.config.get('stake_currency') for pair in add: - if (self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency - and pair not in self._freqtrade.pairlists.blacklist): - self._freqtrade.pairlists.blacklist.append(pair) + if self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency: + if pair not in self._freqtrade.pairlists.blacklist: + self._freqtrade.pairlists.blacklist.append(pair) + else: + errors[pair] = { + 'error_msg': f'Pair {pair} already in pairlist.'} + + else: + errors[pair] = { + 'error_msg': f"Pair {pair} does not match stake currency." + } res = {'method': self._freqtrade.pairlists.name_list, 'length': len(self._freqtrade.pairlists.blacklist), 'blacklist': self._freqtrade.pairlists.blacklist, + 'errors': errors, } return res diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index e94097545..bdd3c25d5 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -833,6 +833,20 @@ def test_rpc_blacklist(mocker, default_conf) -> None: assert ret['blacklist'] == default_conf['exchange']['pair_blacklist'] assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC', 'ETH/BTC'] + ret = rpc._rpc_blacklist(["ETH/BTC"]) + assert 'errors' in ret + assert isinstance(ret['errors'], dict) + assert ret['errors']['ETH/BTC']['error_msg'] == 'Pair ETH/BTC already in pairlist.' + + ret = rpc._rpc_blacklist(["ETH/ETH"]) + assert 'StaticPairList' in ret['method'] + assert len(ret['blacklist']) == 3 + assert ret['blacklist'] == default_conf['exchange']['pair_blacklist'] + assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC', 'ETH/BTC'] + assert 'errors' in ret + assert isinstance(ret['errors'], dict) + assert ret['errors']['ETH/ETH']['error_msg'] == 'Pair ETH/ETH does not match stake currency.' + def test_rpc_edge_disabled(mocker, default_conf) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index cc63bf6e8..85f0e5122 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -554,7 +554,9 @@ def test_api_blacklist(botclient, mocker): assert_response(rc) assert rc.json == {"blacklist": ["DOGE/BTC", "HOT/BTC"], "length": 2, - "method": ["StaticPairList"]} + "method": ["StaticPairList"], + "errors": {}, + } # Add ETH/BTC to blacklist rc = client_post(client, f"{BASE_URI}/blacklist", @@ -562,7 +564,9 @@ def test_api_blacklist(botclient, mocker): assert_response(rc) assert rc.json == {"blacklist": ["DOGE/BTC", "HOT/BTC", "ETH/BTC"], "length": 3, - "method": ["StaticPairList"]} + "method": ["StaticPairList"], + "errors": {}, + } def test_api_whitelist(botclient): From 7399c7e70caa80d2a052329e28a9d460bfdadce0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 28 May 2020 07:04:06 +0200 Subject: [PATCH 02/17] Provide blacklist feedback to telegram --- freqtrade/rpc/telegram.py | 5 +++++ tests/rpc/test_rpc_telegram.py | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 488fa9f37..6f092fcab 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -534,6 +534,11 @@ class Telegram(RPC): try: blacklist = self._rpc_blacklist(context.args) + errmsgs = [] + for pair, error in blacklist['errors'].items(): + errmsgs.append(f"Error adding `{pair}` to blacklist: `{error['error_msg']}`") + if errmsgs: + self._send_msg('\n'.join(errmsgs)) message = f"Blacklist contains {blacklist['length']} pairs\n" message += f"`{', '.join(blacklist['blacklist'])}`" diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 730bb2677..7d02a4339 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1085,6 +1085,18 @@ def test_blacklist_static(default_conf, update, mocker) -> None: in msg_mock.call_args_list[0][0][0]) assert freqtradebot.pairlists.blacklist == ["DOGE/BTC", "HOT/BTC", "ETH/BTC"] + msg_mock.reset_mock() + context = MagicMock() + context.args = ["ETH/ETH"] + telegram._blacklist(update=update, context=context) + assert msg_mock.call_count == 2 + assert ("Error adding `ETH/ETH` to blacklist: `Pair ETH/ETH does not match stake currency.`" + in msg_mock.call_args_list[0][0][0]) + + assert ("Blacklist contains 3 pairs\n`DOGE/BTC, HOT/BTC, ETH/BTC`" + in msg_mock.call_args_list[1][0][0]) + assert freqtradebot.pairlists.blacklist == ["DOGE/BTC", "HOT/BTC", "ETH/BTC"] + def test_edge_disabled(default_conf, update, mocker) -> None: msg_mock = MagicMock() From 57e951dbce90ff486c3267e62865fd0fa0399d06 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 May 2020 10:59:26 +0200 Subject: [PATCH 03/17] Add orderbook sell rate to sell_rate_cache --- freqtrade/freqtradebot.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d4afa1d60..4f63cc01d 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -719,6 +719,9 @@ class FreqtradeBot: raise PricingError from e logger.debug(f" order book {config_ask_strategy['price_side']} top {i}: " f"{sell_rate:0.8f}") + # Assign sell-rate to cache - otherwise sell-rate is never updated in the cache, + # resulting in outdated RPC messages + self._sell_rate_cache[trade.pair] = sell_rate if self._check_and_execute_sell(trade, sell_rate, buy, sell): return True From 7ad1c7e817ca115bf4bd8a79c000685ebc948730 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 May 2020 09:51:45 +0200 Subject: [PATCH 04/17] Allow lower verbosity level for api server Not logging all calls makes sense when running the UI otherwise this is VERY verbose, clogging up the log. --- config_full.json.example | 1 + docs/configuration.md | 1 + docs/rest-api.md | 1 + freqtrade/constants.py | 1 + freqtrade/loggers.py | 8 ++++++-- freqtrade/rpc/api_server.py | 3 --- 6 files changed, 10 insertions(+), 5 deletions(-) diff --git a/config_full.json.example b/config_full.json.example index 481742817..57ebb22e6 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -121,6 +121,7 @@ "enabled": false, "listen_ip_address": "127.0.0.1", "listen_port": 8080, + "verbosity": "info", "jwt_secret_key": "somethingrandom", "username": "freqtrader", "password": "SuperSecurePassword" diff --git a/docs/configuration.md b/docs/configuration.md index a1cb45e0f..3a8262fab 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -103,6 +103,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details.
**Datatype:** Boolean | `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details.
**Datatype:** IPv4 | `api_server.listen_port` | Bind Port. See the [API Server documentation](rest-api.md) for more details.
**Datatype:** Integer between 1024 and 65535 +| `api_server.verbosity` | Loggging verbosity. `info` will print all RPC Calls, while "error" will only display errors.
**Datatype:** Enum, either `info` or `error`. Defaults to `info`. | `api_server.username` | Username for API server. See the [API Server documentation](rest-api.md) for more details.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String | `api_server.password` | Password for API server. See the [API Server documentation](rest-api.md) for more details.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String | `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite:///tradesv3.dryrun.sqlite` if `dry_run` is `true`, and to `sqlite:///tradesv3.sqlite` for production instances.
**Datatype:** String, SQLAlchemy connect string diff --git a/docs/rest-api.md b/docs/rest-api.md index 7f1a95b12..ed5f355b4 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -11,6 +11,7 @@ Sample configuration: "enabled": true, "listen_ip_address": "127.0.0.1", "listen_port": 8080, + "verbosity": "info", "jwt_secret_key": "somethingrandom", "username": "Freqtrader", "password": "SuperSecret1!" diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 1984d4866..a91e3b19f 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -221,6 +221,7 @@ CONF_SCHEMA = { }, 'username': {'type': 'string'}, 'password': {'type': 'string'}, + 'verbosity': {'type': 'string', 'enum': ['error', 'info']}, }, 'required': ['enabled', 'listen_ip_address', 'listen_port', 'username', 'password'] }, diff --git a/freqtrade/loggers.py b/freqtrade/loggers.py index 153ce8c80..aa08ee8a7 100644 --- a/freqtrade/loggers.py +++ b/freqtrade/loggers.py @@ -11,7 +11,7 @@ from freqtrade.exceptions import OperationalException logger = logging.getLogger(__name__) -def _set_loggers(verbosity: int = 0) -> None: +def _set_loggers(verbosity: int = 0, api_verbosity: str = 'info') -> None: """ Set the logging level for third party libraries :return: None @@ -28,6 +28,10 @@ def _set_loggers(verbosity: int = 0) -> None: ) logging.getLogger('telegram').setLevel(logging.INFO) + logging.getLogger('werkzeug').setLevel( + logging.ERROR if api_verbosity == 'error' else logging.INFO + ) + def setup_logging(config: Dict[str, Any]) -> None: """ @@ -77,5 +81,5 @@ def setup_logging(config: Dict[str, Any]) -> None: format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=log_handlers ) - _set_loggers(verbosity) + _set_loggers(verbosity, config.get('api_server', {}).get('verbosity', 'info')) logger.info('Verbosity set to %s', verbosity) diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py index 23b6a85b0..9d0899ccd 100644 --- a/freqtrade/rpc/api_server.py +++ b/freqtrade/rpc/api_server.py @@ -360,7 +360,6 @@ class ApiServer(RPC): Returns a cumulative profit statistics :return: stats """ - logger.info("LocalRPC - Profit Command Called") stats = self._rpc_trade_statistics(self._config['stake_currency'], self._config.get('fiat_display_currency') @@ -377,8 +376,6 @@ class ApiServer(RPC): Returns a cumulative performance statistics :return: stats """ - logger.info("LocalRPC - performance Command Called") - stats = self._rpc_performance() return self.rest_dump(stats) From dc7f0f11877d69f8a4d8fc23c2a7cff3d739755d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 May 2020 09:57:31 +0200 Subject: [PATCH 05/17] Add api-server to default config samples --- config.json.example | 9 +++++++++ config_binance.json.example | 9 +++++++++ config_kraken.json.example | 9 +++++++++ freqtrade/templates/base_config.json.j2 | 9 +++++++++ tests/test_configuration.py | 5 ++++- 5 files changed, 40 insertions(+), 1 deletion(-) diff --git a/config.json.example b/config.json.example index d37a6b336..ae87164d6 100644 --- a/config.json.example +++ b/config.json.example @@ -76,6 +76,15 @@ "token": "your_telegram_token", "chat_id": "your_telegram_chat_id" }, + "api_server": { + "enabled": false, + "listen_ip_address": "127.0.0.1", + "listen_port": 8080, + "verbosity": "info", + "jwt_secret_key": "somethingrandom", + "username": "", + "password": "" + }, "initial_state": "running", "forcebuy_enable": false, "internals": { diff --git a/config_binance.json.example b/config_binance.json.example index 5d7b6b656..74207d565 100644 --- a/config_binance.json.example +++ b/config_binance.json.example @@ -81,6 +81,15 @@ "token": "your_telegram_token", "chat_id": "your_telegram_chat_id" }, + "api_server": { + "enabled": false, + "listen_ip_address": "127.0.0.1", + "listen_port": 8080, + "verbosity": "info", + "jwt_secret_key": "somethingrandom", + "username": "", + "password": "" + }, "initial_state": "running", "forcebuy_enable": false, "internals": { diff --git a/config_kraken.json.example b/config_kraken.json.example index 54fbf4a00..f1e522da5 100644 --- a/config_kraken.json.example +++ b/config_kraken.json.example @@ -87,6 +87,15 @@ "token": "your_telegram_token", "chat_id": "your_telegram_chat_id" }, + "api_server": { + "enabled": false, + "listen_ip_address": "127.0.0.1", + "listen_port": 8080, + "verbosity": "info", + "jwt_secret_key": "somethingrandom", + "username": "", + "password": "" + }, "initial_state": "running", "forcebuy_enable": false, "internals": { diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 index 6d3174347..5339595e8 100644 --- a/freqtrade/templates/base_config.json.j2 +++ b/freqtrade/templates/base_config.json.j2 @@ -53,6 +53,15 @@ "token": "{{ telegram_token }}", "chat_id": "{{ telegram_chat_id }}" }, + "api_server": { + "enabled": false, + "listen_ip_address": "127.0.0.1", + "listen_port": 8080, + "verbosity": "info", + "jwt_secret_key": "somethingrandom", + "username": "", + "password": "" + }, "initial_state": "running", "forcebuy_enable": false, "internals": { diff --git a/tests/test_configuration.py b/tests/test_configuration.py index edcbe4516..18fa607f2 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -634,6 +634,7 @@ def test_set_loggers() -> None: previous_value1 = logging.getLogger('requests').level previous_value2 = logging.getLogger('ccxt.base.exchange').level previous_value3 = logging.getLogger('telegram').level + previous_value3 = logging.getLogger('werkzeug').level _set_loggers() @@ -654,12 +655,14 @@ def test_set_loggers() -> None: assert logging.getLogger('requests').level is logging.DEBUG assert logging.getLogger('ccxt.base.exchange').level is logging.INFO assert logging.getLogger('telegram').level is logging.INFO + assert logging.getLogger('werkzeug').level is logging.INFO - _set_loggers(verbosity=3) + _set_loggers(verbosity=3, api_verbosity='error') assert logging.getLogger('requests').level is logging.DEBUG assert logging.getLogger('ccxt.base.exchange').level is logging.DEBUG assert logging.getLogger('telegram').level is logging.INFO + assert logging.getLogger('werkzeug').level is logging.ERROR @pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows") From 4087161d2b69b5b2d73751402fac9d1838f86de3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 May 2020 10:16:56 +0200 Subject: [PATCH 06/17] fix broken test --- tests/test_configuration.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 18fa607f2..bd43f84ca 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -634,7 +634,6 @@ def test_set_loggers() -> None: previous_value1 = logging.getLogger('requests').level previous_value2 = logging.getLogger('ccxt.base.exchange').level previous_value3 = logging.getLogger('telegram').level - previous_value3 = logging.getLogger('werkzeug').level _set_loggers() From f202e09b10e3568d89cbf9ac7e765acfcbe78efa Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 08:56:12 +0200 Subject: [PATCH 07/17] Extract conversion to trades list to it's own function --- freqtrade/optimize/optimize_reports.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 1fc4d721e..77101a1f6 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -18,10 +18,7 @@ def store_backtest_result(recordfilename: Path, all_results: Dict[str, DataFrame :param all_results: Dict of Dataframes, one results dataframe per strategy """ for strategy, results in all_results.items(): - records = [(t.pair, t.profit_percent, t.open_time.timestamp(), - t.close_time.timestamp(), t.open_index - 1, t.trade_duration, - t.open_rate, t.close_rate, t.open_at_end, t.sell_reason.value) - for index, t in results.iterrows()] + records = backtest_result_to_list(results) if records: filename = recordfilename @@ -34,6 +31,18 @@ def store_backtest_result(recordfilename: Path, all_results: Dict[str, DataFrame file_dump_json(filename, records) +def backtest_result_to_list(results: DataFrame) -> List[List]: + """ + Converts a list of Backtest-results to list + :param results: Dataframe containing results for one strategy + :return: List of Lists containing the trades + """ + return [[t.pair, t.profit_percent, t.open_time.timestamp(), + t.close_time.timestamp(), t.open_index - 1, t.trade_duration, + t.open_rate, t.close_rate, t.open_at_end, t.sell_reason.value] + for index, t in results.iterrows()] + + def _get_line_floatfmt() -> List[str]: """ Generate floatformat (goes in line with _generate_result_line()) From ceaf32d304294104a06a2db8e15b609ec772a71c Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 09:23:24 +0200 Subject: [PATCH 08/17] Extract backtesting report generation from show_backtest_Results --- freqtrade/optimize/optimize_reports.py | 42 +++++++++++++++++++------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 77101a1f6..5afdc6f77 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -255,12 +255,13 @@ def generate_edge_table(results: dict) -> str: floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore -def show_backtest_results(config: Dict, btdata: Dict[str, DataFrame], - all_results: Dict[str, DataFrame]): +def generate_backtest_stats(config: Dict, btdata: Dict[str, DataFrame], + all_results: Dict[str, DataFrame]): stake_currency = config['stake_currency'] max_open_trades = config['max_open_trades'] - + result = {'strategy': {}} for strategy, results in all_results.items(): + pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency, max_open_trades=max_open_trades, results=results, skip_nan=False) @@ -270,21 +271,43 @@ def show_backtest_results(config: Dict, btdata: Dict[str, DataFrame], max_open_trades=max_open_trades, results=results.loc[results['open_at_end']], skip_nan=True) + strat_stats = { + 'trades': backtest_result_to_list(results), + 'results_per_pair': pair_results, + 'sell_reason_summary': sell_reason_stats, + 'left_open_trades': left_open_results, + } + result['strategy'][strategy] = strat_stats + + strategy_results = generate_strategy_metrics(stake_currency=stake_currency, + max_open_trades=max_open_trades, + all_results=all_results) + + result['strategy_comparison'] = strategy_results + + return result + + +def show_backtest_results(config: Dict, backtest_stats: Dict): + stake_currency = config['stake_currency'] + + for strategy, results in backtest_stats['strategy'].items(): + # Print results print(f"Result for strategy {strategy}") - table = generate_text_table(pair_results, stake_currency=stake_currency) + table = generate_text_table(results['results_per_pair'], stake_currency=stake_currency) if isinstance(table, str): print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '=')) print(table) - table = generate_text_table_sell_reason(sell_reason_stats=sell_reason_stats, + table = generate_text_table_sell_reason(sell_reason_stats=results['sell_reason_summary'], stake_currency=stake_currency, ) if isinstance(table, str): print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '=')) print(table) - table = generate_text_table(left_open_results, stake_currency=stake_currency) + table = generate_text_table(results['left_open_trades'], stake_currency=stake_currency) if isinstance(table, str): print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '=')) print(table) @@ -292,13 +315,10 @@ def show_backtest_results(config: Dict, btdata: Dict[str, DataFrame], print('=' * len(table.splitlines()[0])) print() - if len(all_results) > 1: + if len(backtest_stats['strategy']) > 1: # Print Strategy summary table - strategy_results = generate_strategy_metrics(stake_currency=stake_currency, - max_open_trades=max_open_trades, - all_results=all_results) - table = generate_text_table_strategy(strategy_results, stake_currency) + table = generate_text_table_strategy(backtest_stats['strategy_comparison'], stake_currency) print(' STRATEGY SUMMARY '.center(len(table.splitlines()[0]), '=')) print(table) print('=' * len(table.splitlines()[0])) From 091693308ae1d16cb003aba2ff1fdea1d88b3be0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 09:24:27 +0200 Subject: [PATCH 09/17] Correctly call show_backtest_results --- freqtrade/optimize/backtesting.py | 6 ++++-- freqtrade/optimize/optimize_reports.py | 2 +- tests/optimize/test_backtesting.py | 6 ++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 3bf211d99..b47b38ea4 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -18,7 +18,8 @@ from freqtrade.data.converter import trim_dataframe from freqtrade.data.dataprovider import DataProvider from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds -from freqtrade.optimize.optimize_reports import (show_backtest_results, +from freqtrade.optimize.optimize_reports import (generate_backtest_stats, + show_backtest_results, store_backtest_result) from freqtrade.pairlist.pairlistmanager import PairListManager from freqtrade.persistence import Trade @@ -411,4 +412,5 @@ class Backtesting: if self.config.get('export', False): store_backtest_result(self.config['exportfilename'], all_results) # Show backtest results - show_backtest_results(self.config, data, all_results) + stats = generate_backtest_stats(self.config, data, all_results) + show_backtest_results(self.config, stats) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 5afdc6f77..c148f0f44 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -259,7 +259,7 @@ def generate_backtest_stats(config: Dict, btdata: Dict[str, DataFrame], all_results: Dict[str, DataFrame]): stake_currency = config['stake_currency'] max_open_trades = config['max_open_trades'] - result = {'strategy': {}} + result: Dict[str, Any] = {'strategy': {}} for strategy, results in all_results.items(): pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency, diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index ace82d28b..40c106975 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -333,6 +333,7 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None: mocker.patch('freqtrade.data.history.get_timerange', get_timerange) patch_exchange(mocker) mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest') + mocker.patch('freqtrade.optimize.backtesting.generate_backtest_stats') mocker.patch('freqtrade.optimize.backtesting.show_backtest_results') mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist', PropertyMock(return_value=['UNITTEST/BTC'])) @@ -612,8 +613,9 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir): patch_exchange(mocker) - mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock()) - mocker.patch('freqtrade.optimize.backtesting.show_backtest_results', MagicMock()) + mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest') + mocker.patch('freqtrade.optimize.backtesting.generate_backtest_stats') + mocker.patch('freqtrade.optimize.backtesting.show_backtest_results') mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist', PropertyMock(return_value=['UNITTEST/BTC'])) patched_configuration_load_config_file(mocker, default_conf) From adde1cfee2591820fb09db55b0957f57880be05b Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 10:53:02 +0200 Subject: [PATCH 10/17] Add stoplosss_ratio and initial_stoploss_ratio --- freqtrade/persistence.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 111ccfe2a..2fe93f440 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -249,32 +249,40 @@ class Trade(_DECL_BASE): 'trade_id': self.id, 'pair': self.pair, 'is_open': self.is_open, + 'exchange': self.exchange, + 'amount': round(self.amount, 8), + 'stake_amount': round(self.stake_amount, 8), + 'strategy': self.strategy, + 'ticker_interval': self.ticker_interval, + 'fee_open': self.fee_open, 'fee_open_cost': self.fee_open_cost, 'fee_open_currency': self.fee_open_currency, 'fee_close': self.fee_close, 'fee_close_cost': self.fee_close_cost, 'fee_close_currency': self.fee_close_currency, + 'open_date_hum': arrow.get(self.open_date).humanize(), 'open_date': self.open_date.strftime("%Y-%m-%d %H:%M:%S"), '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, + 'close_date_hum': (arrow.get(self.close_date).humanize() if self.close_date else None), 'close_date': (self.close_date.strftime("%Y-%m-%d %H:%M:%S") if self.close_date else None), 'close_timestamp': int(self.close_date.timestamp() * 1000) if self.close_date else None, - 'open_rate': self.open_rate, - 'open_rate_requested': self.open_rate_requested, - 'open_trade_price': self.open_trade_price, 'close_rate': self.close_rate, 'close_rate_requested': self.close_rate_requested, - 'amount': round(self.amount, 8), - 'stake_amount': round(self.stake_amount, 8), 'close_profit': self.close_profit, 'close_profit_abs': self.close_profit_abs, + 'sell_reason': self.sell_reason, 'sell_order_status': self.sell_order_status, 'stop_loss': self.stop_loss, + 'stop_loss_ratio': self.stop_loss_pct if self.stop_loss_pct else None, 'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None, 'stoploss_order_id': self.stoploss_order_id, 'stoploss_last_update': (self.stoploss_last_update.strftime("%Y-%m-%d %H:%M:%S") @@ -282,14 +290,14 @@ class Trade(_DECL_BASE): 'stoploss_last_update_timestamp': (int(self.stoploss_last_update.timestamp() * 1000) if self.stoploss_last_update else None), 'initial_stop_loss': self.initial_stop_loss, + 'initial_stop_loss_ratio': (self.initial_stop_loss_pct + if self.initial_stop_loss_pct else None), 'initial_stop_loss_pct': (self.initial_stop_loss_pct * 100 if self.initial_stop_loss_pct else None), 'min_rate': self.min_rate, 'max_rate': self.max_rate, - 'strategy': self.strategy, - 'ticker_interval': self.ticker_interval, + 'open_order_id': self.open_order_id, - 'exchange': self.exchange, } def adjust_min_max_rates(self, current_price: float) -> None: From 6dec508c5eb3a0106525f16428f27d4cc7b99b6f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 10:56:05 +0200 Subject: [PATCH 11/17] Add new fields to tests --- tests/rpc/test_rpc.py | 4 ++++ tests/rpc/test_rpc_apiserver.py | 14 +++++++++----- tests/test_persistence.py | 4 ++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 7b7824310..dcd525574 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -82,11 +82,13 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'current_profit_pct': -0.41, 'stop_loss': 0.0, 'stop_loss_pct': None, + 'stop_loss_ratio': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss': 0.0, 'initial_stop_loss_pct': None, + 'initial_stop_loss_ratio': None, 'open_order': '(limit buy rem=0.00000000)', 'exchange': 'bittrex', @@ -136,11 +138,13 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'current_profit_pct': ANY, 'stop_loss': 0.0, 'stop_loss_pct': None, + 'stop_loss_ratio': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss': 0.0, 'initial_stop_loss_pct': None, + 'initial_stop_loss_ratio': None, 'open_order': '(limit buy rem=0.00000000)', 'exchange': 'bittrex', } == results[0] diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index c7f937b0c..13914fab0 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -510,8 +510,6 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'current_profit': -0.00408133, 'current_profit_pct': -0.41, 'current_rate': 1.099e-05, - 'initial_stop_loss': 0.0, - 'initial_stop_loss_pct': None, 'open_date': ANY, 'open_date_hum': 'just now', 'open_timestamp': ANY, @@ -521,9 +519,13 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'stake_amount': 0.001, 'stop_loss': 0.0, 'stop_loss_pct': None, + 'stop_loss_ratio': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, + 'initial_stop_loss': 0.0, + 'initial_stop_loss_pct': None, + 'initial_stop_loss_ratio': None, 'trade_id': 1, 'close_rate_requested': None, 'current_rate': 1.099e-05, @@ -622,12 +624,11 @@ def test_api_forcebuy(botclient, mocker, fee): data='{"pair": "ETH/BTC"}') assert_response(rc) assert rc.json == {'amount': 1, + 'trade_id': None, 'close_date': None, 'close_date_hum': None, 'close_timestamp': None, 'close_rate': 0.265441, - 'initial_stop_loss': None, - 'initial_stop_loss_pct': None, 'open_date': ANY, 'open_date_hum': 'just now', 'open_timestamp': ANY, @@ -636,10 +637,13 @@ def test_api_forcebuy(botclient, mocker, fee): 'stake_amount': 1, 'stop_loss': None, 'stop_loss_pct': None, + 'stop_loss_ratio': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, - 'trade_id': None, + 'initial_stop_loss': None, + 'initial_stop_loss_pct': None, + 'initial_stop_loss_ratio': None, 'close_profit': None, 'close_profit_abs': None, 'close_rate_requested': None, diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 3b1041fd8..942e22a19 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -762,12 +762,14 @@ def test_to_json(default_conf, fee): 'sell_reason': None, 'sell_order_status': None, 'stop_loss': None, + 'stop_loss_ratio': None, 'stop_loss_pct': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss': None, 'initial_stop_loss_pct': None, + 'initial_stop_loss_ratio': None, 'min_rate': None, 'max_rate': None, 'strategy': None, @@ -805,11 +807,13 @@ def test_to_json(default_conf, fee): 'stake_amount': 0.001, 'stop_loss': None, 'stop_loss_pct': None, + 'stop_loss_ratio': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss': None, 'initial_stop_loss_pct': None, + 'initial_stop_loss_ratio': None, 'close_profit': None, 'close_profit_abs': None, 'close_rate_requested': None, From d2b7016dffe40d0581427f7cbdb00915838759b0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 11:05:37 +0200 Subject: [PATCH 12/17] Add stop_loss_abs ... --- freqtrade/persistence.py | 6 ++++-- tests/rpc/test_rpc.py | 4 ++++ tests/rpc/test_rpc_apiserver.py | 4 ++++ tests/test_persistence.py | 4 ++++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 2fe93f440..823bf6dc0 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -281,7 +281,8 @@ class Trade(_DECL_BASE): 'sell_reason': self.sell_reason, 'sell_order_status': self.sell_order_status, - 'stop_loss': self.stop_loss, + 'stop_loss': self.stop_loss, # Deprecated - should not be used + 'stop_loss_abs': self.stop_loss, 'stop_loss_ratio': self.stop_loss_pct if self.stop_loss_pct else None, 'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None, 'stoploss_order_id': self.stoploss_order_id, @@ -289,7 +290,8 @@ class Trade(_DECL_BASE): if self.stoploss_last_update else None), 'stoploss_last_update_timestamp': (int(self.stoploss_last_update.timestamp() * 1000) if self.stoploss_last_update else None), - 'initial_stop_loss': self.initial_stop_loss, + 'initial_stop_loss': self.initial_stop_loss, # Deprecated - should not be used + 'initial_stop_loss_abs': self.initial_stop_loss, 'initial_stop_loss_ratio': (self.initial_stop_loss_pct if self.initial_stop_loss_pct else None), 'initial_stop_loss_pct': (self.initial_stop_loss_pct * 100 diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index dcd525574..eb20e3903 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -81,12 +81,14 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'current_profit': -0.00408133, 'current_profit_pct': -0.41, 'stop_loss': 0.0, + 'stop_loss_abs': 0.0, 'stop_loss_pct': None, 'stop_loss_ratio': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss': 0.0, + 'initial_stop_loss_abs': 0.0, 'initial_stop_loss_pct': None, 'initial_stop_loss_ratio': None, 'open_order': '(limit buy rem=0.00000000)', @@ -137,12 +139,14 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'current_profit': ANY, 'current_profit_pct': ANY, 'stop_loss': 0.0, + 'stop_loss_abs': 0.0, 'stop_loss_pct': None, 'stop_loss_ratio': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss': 0.0, + 'initial_stop_loss_abs': 0.0, 'initial_stop_loss_pct': None, 'initial_stop_loss_ratio': None, 'open_order': '(limit buy rem=0.00000000)', diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 13914fab0..4e4dd7dfd 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -518,12 +518,14 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'pair': 'ETH/BTC', 'stake_amount': 0.001, 'stop_loss': 0.0, + 'stop_loss_abs': 0.0, 'stop_loss_pct': None, 'stop_loss_ratio': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss': 0.0, + 'initial_stop_loss_abs': 0.0, 'initial_stop_loss_pct': None, 'initial_stop_loss_ratio': None, 'trade_id': 1, @@ -636,12 +638,14 @@ def test_api_forcebuy(botclient, mocker, fee): 'pair': 'ETH/ETH', 'stake_amount': 1, 'stop_loss': None, + 'stop_loss_abs': None, 'stop_loss_pct': None, 'stop_loss_ratio': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss': None, + 'initial_stop_loss_abs': None, 'initial_stop_loss_pct': None, 'initial_stop_loss_ratio': None, 'close_profit': None, diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 942e22a19..ae639511b 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -762,12 +762,14 @@ def test_to_json(default_conf, fee): 'sell_reason': None, 'sell_order_status': None, 'stop_loss': None, + 'stop_loss_abs': None, 'stop_loss_ratio': None, 'stop_loss_pct': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss': None, + 'initial_stop_loss_abs': None, 'initial_stop_loss_pct': None, 'initial_stop_loss_ratio': None, 'min_rate': None, @@ -806,12 +808,14 @@ def test_to_json(default_conf, fee): 'amount': 100.0, 'stake_amount': 0.001, 'stop_loss': None, + 'stop_loss_abs': None, 'stop_loss_pct': None, 'stop_loss_ratio': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss': None, + 'initial_stop_loss_abs': None, 'initial_stop_loss_pct': None, 'initial_stop_loss_ratio': None, 'close_profit': None, From 65c5bba189ce8fc09f175a82ad52e13a0a1425ab Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 12:45:59 +0200 Subject: [PATCH 13/17] Fix typo in docs Co-authored-by: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 3a8262fab..c9953f0f1 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -103,7 +103,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details.
**Datatype:** Boolean | `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details.
**Datatype:** IPv4 | `api_server.listen_port` | Bind Port. See the [API Server documentation](rest-api.md) for more details.
**Datatype:** Integer between 1024 and 65535 -| `api_server.verbosity` | Loggging verbosity. `info` will print all RPC Calls, while "error" will only display errors.
**Datatype:** Enum, either `info` or `error`. Defaults to `info`. +| `api_server.verbosity` | Logging verbosity. `info` will print all RPC Calls, while "error" will only display errors.
**Datatype:** Enum, either `info` or `error`. Defaults to `info`. | `api_server.username` | Username for API server. See the [API Server documentation](rest-api.md) for more details.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String | `api_server.password` | Password for API server. See the [API Server documentation](rest-api.md) for more details.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String | `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite:///tradesv3.dryrun.sqlite` if `dry_run` is `true`, and to `sqlite:///tradesv3.sqlite` for production instances.
**Datatype:** String, SQLAlchemy connect string From c35f9f8d39967999807cab6a8a405976fa691b2d Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 19:45:37 +0200 Subject: [PATCH 14/17] Verify sell-rate got a value - otherwise downstream code does not work. Using PricingException here will cease operation for this pair for this iteration - postponing handling to the next iteration - where hopefully a price is again present. --- freqtrade/freqtradebot.py | 2 ++ tests/test_freqtradebot.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d4afa1d60..df0aad801 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -676,6 +676,8 @@ class FreqtradeBot: raise PricingError from e else: rate = self.exchange.fetch_ticker(pair)[ask_strategy['price_side']] + if rate is None: + raise PricingError(f"Sell-Rate for {pair} was empty.") self._sell_rate_cache[pair] = rate return rate diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 5e951b585..17b51dff5 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -3931,6 +3931,26 @@ def test_get_sell_rate_orderbook_exception(default_conf, mocker, caplog): assert log_has("Sell Price at location from orderbook could not be determined.", caplog) +def test_get_sell_rate_exception(default_conf, mocker, caplog): + # Ticker on one side can be empty in certain circumstances. + default_conf['ask_strategy']['price_side'] = 'ask' + pair = "ETH/BTC" + mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'ask': None, 'bid': 0.12}) + ft = get_patched_freqtradebot(mocker, default_conf) + with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."): + ft.get_sell_rate(pair, True) + + ft.config['ask_strategy']['price_side'] = 'bid' + assert ft.get_sell_rate(pair, True) == 0.12 + # Reverse sides + mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'ask': 0.13, 'bid': None}) + with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."): + ft.get_sell_rate(pair, True) + + ft.config['ask_strategy']['price_side'] = 'ask' + assert ft.get_sell_rate(pair, True) == 0.13 + + def test_startup_state(default_conf, mocker): default_conf['pairlist'] = {'method': 'VolumePairList', 'config': {'number_assets': 20} From f6f75072ba049815815e4b17e38acdcb8bf8d9fa Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 19:54:05 +0200 Subject: [PATCH 15/17] Fix linelength --- tests/test_freqtradebot.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 17b51dff5..487e3a60e 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -3935,7 +3935,8 @@ def test_get_sell_rate_exception(default_conf, mocker, caplog): # Ticker on one side can be empty in certain circumstances. default_conf['ask_strategy']['price_side'] = 'ask' pair = "ETH/BTC" - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'ask': None, 'bid': 0.12}) + mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', + return_value={'ask': None, 'bid': 0.12}) ft = get_patched_freqtradebot(mocker, default_conf) with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."): ft.get_sell_rate(pair, True) @@ -3943,7 +3944,8 @@ def test_get_sell_rate_exception(default_conf, mocker, caplog): ft.config['ask_strategy']['price_side'] = 'bid' assert ft.get_sell_rate(pair, True) == 0.12 # Reverse sides - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'ask': 0.13, 'bid': None}) + mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', + return_value={'ask': 0.13, 'bid': None}) with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."): ft.get_sell_rate(pair, True) From 3139343946889361c48442f684a7b661e4c1536b Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 19:58:28 +0200 Subject: [PATCH 16/17] Remove capital_available_percentage and raise instead --- docs/edge.md | 1 - freqtrade/configuration/deprecated_settings.py | 2 +- freqtrade/constants.py | 1 - freqtrade/edge/edge_positioning.py | 6 ++---- tests/config_test_comments.json | 1 - tests/test_configuration.py | 5 +++-- 6 files changed, 6 insertions(+), 10 deletions(-) diff --git a/docs/edge.md b/docs/edge.md index 029844c0b..bdcf6cde9 100644 --- a/docs/edge.md +++ b/docs/edge.md @@ -148,7 +148,6 @@ Edge module has following configuration options: | `enabled` | If true, then Edge will run periodically.
*Defaults to `false`.*
**Datatype:** Boolean | `process_throttle_secs` | How often should Edge run in seconds.
*Defaults to `3600` (once per hour).*
**Datatype:** Integer | `calculate_since_number_of_days` | Number of days of data against which Edge calculates Win Rate, Risk Reward and Expectancy.
**Note** that it downloads historical data so increasing this number would lead to slowing down the bot.
*Defaults to `7`.*
**Datatype:** Integer -| `capital_available_percentage` | **DEPRECATED - [replaced with `tradable_balance_ratio`](configuration.md#Available balance)** This is the percentage of the total capital on exchange in stake currency.
As an example if you have 10 ETH available in your wallet on the exchange and this value is 0.5 (which is 50%), then the bot will use a maximum amount of 5 ETH for trading and considers it as available capital.
*Defaults to `0.5`.*
**Datatype:** Float | `allowed_risk` | Ratio of allowed risk per trade.
*Defaults to `0.01` (1%)).*
**Datatype:** Float | `stoploss_range_min` | Minimum stoploss.
*Defaults to `-0.01`.*
**Datatype:** Float | `stoploss_range_max` | Maximum stoploss.
*Defaults to `-0.10`.*
**Datatype:** Float diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py index 3999ea422..ff4401c27 100644 --- a/freqtrade/configuration/deprecated_settings.py +++ b/freqtrade/configuration/deprecated_settings.py @@ -60,7 +60,7 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: if (config.get('edge', {}).get('enabled', False) and 'capital_available_percentage' in config.get('edge', {})): - logger.warning( + raise OperationalException( "DEPRECATED: " "Using 'edge.capital_available_percentage' has been deprecated in favor of " "'tradable_balance_ratio'. Please migrate your configuration to " diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 1984d4866..0e08db91f 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -286,7 +286,6 @@ CONF_SCHEMA = { 'process_throttle_secs': {'type': 'integer', 'minimum': 600}, 'calculate_since_number_of_days': {'type': 'integer'}, 'allowed_risk': {'type': 'number'}, - 'capital_available_percentage': {'type': 'number'}, 'stoploss_range_min': {'type': 'number'}, 'stoploss_range_max': {'type': 'number'}, 'stoploss_range_step': {'type': 'number'}, diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index c19d4552a..2550cf604 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -57,9 +57,7 @@ class Edge: if self.config['stake_amount'] != UNLIMITED_STAKE_AMOUNT: raise OperationalException('Edge works only with unlimited stake amount') - # Deprecated capital_available_percentage. Will use tradable_balance_ratio in the future. - self._capital_percentage: float = self.edge_config.get( - 'capital_available_percentage', self.config['tradable_balance_ratio']) + self._capital_ratio: float = self.config['tradable_balance_ratio'] self._allowed_risk: float = self.edge_config.get('allowed_risk') self._since_number_of_days: int = self.edge_config.get('calculate_since_number_of_days', 14) self._last_updated: int = 0 # Timestamp of pairs last updated time @@ -157,7 +155,7 @@ class Edge: def stake_amount(self, pair: str, free_capital: float, total_capital: float, capital_in_trade: float) -> float: stoploss = self.stoploss(pair) - available_capital = (total_capital + capital_in_trade) * self._capital_percentage + available_capital = (total_capital + capital_in_trade) * self._capital_ratio allowed_capital_at_risk = available_capital * self._allowed_risk max_position_size = abs(allowed_capital_at_risk / stoploss) position_size = min(max_position_size, free_capital) diff --git a/tests/config_test_comments.json b/tests/config_test_comments.json index 8f41b08fa..d9d4a86bc 100644 --- a/tests/config_test_comments.json +++ b/tests/config_test_comments.json @@ -92,7 +92,6 @@ "enabled": false, "process_throttle_secs": 3600, "calculate_since_number_of_days": 7, - "capital_available_percentage": 0.5, "allowed_risk": 0.01, "stoploss_range_min": -0.01, "stoploss_range_max": -0.1, diff --git a/tests/test_configuration.py b/tests/test_configuration.py index edcbe4516..2af89bea7 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1048,8 +1048,9 @@ def test_process_deprecated_setting_edge(mocker, edge_conf, caplog): 'capital_available_percentage': 0.5, }}) - process_temporary_deprecated_settings(edge_conf) - assert log_has_re(r"DEPRECATED.*Using 'edge.capital_available_percentage'*", caplog) + with pytest.raises(OperationalException, + match=r"DEPRECATED.*Using 'edge.capital_available_percentage'*"): + process_temporary_deprecated_settings(edge_conf) def test_check_conflicting_settings(mocker, default_conf, caplog): From 67a3c323730f4b4a882c1688c2cf53be3aa24f98 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 20:02:12 +0200 Subject: [PATCH 17/17] Remove some occurances of percentage --- docs/configuration.md | 6 +++--- docs/stoploss.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index a1cb45e0f..254c581fd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -53,8 +53,8 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in the Dry Run mode.
*Defaults to `1000`.*
**Datatype:** Float | `cancel_open_orders_on_exit` | Cancel open orders when the `/stop` RPC command is issued, `Ctrl+C` is pressed or the bot dies unexpectedly. When set to `true`, this allows you to use `/stop` to cancel unfilled and partially filled orders in the event of a market crash. It does not impact open positions.
*Defaults to `false`.*
**Datatype:** Boolean | `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean -| `minimal_roi` | **Required.** Set the threshold in percent the bot will use to sell a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict -| `stoploss` | **Required.** Value of the stoploss in percent used by the bot. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Float (as ratio) +| `minimal_roi` | **Required.** Set the threshold as ratio the bot will use to sell a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict +| `stoploss` | **Required.** Value as ratio of the stoploss used by the bot. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Float (as ratio) | `trailing_stop` | Enables trailing stoploss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Boolean | `trailing_stop_positive` | Changes stoploss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Float | `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
*Defaults to `0.0` (no offset).*
**Datatype:** Float @@ -217,7 +217,7 @@ To allow the bot to trade all the available `stake_currency` in your account (mi ### Understand minimal_roi The `minimal_roi` configuration parameter is a JSON object where the key is a duration -in minutes and the value is the minimum ROI in percent. +in minutes and the value is the minimum ROI as ratio. See the example below: ```json diff --git a/docs/stoploss.md b/docs/stoploss.md index f6d56fd41..0e43817ec 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -1,6 +1,6 @@ # Stop Loss -The `stoploss` configuration parameter is loss in percentage that should trigger a sale. +The `stoploss` configuration parameter is loss as ratio that should trigger a sale. For example, value `-0.10` will cause immediate sell if the profit dips below -10% for a given trade. This parameter is optional. Most of the strategy files already include the optimal `stoploss` value.