From b384ca8fd28bfc41226718451f9dfcc58a0ce70b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 28 Jan 2020 20:30:03 +0100 Subject: [PATCH 001/117] Create new-config command --- freqtrade/commands/__init__.py | 1 + freqtrade/commands/arguments.py | 13 +++++++++++-- freqtrade/commands/deploy_commands.py | 8 ++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/freqtrade/commands/__init__.py b/freqtrade/commands/__init__.py index 990c1107a..81467cf61 100644 --- a/freqtrade/commands/__init__.py +++ b/freqtrade/commands/__init__.py @@ -9,6 +9,7 @@ Note: Be careful with file-scoped imports in these subfiles. from freqtrade.commands.arguments import Arguments from freqtrade.commands.data_commands import start_download_data from freqtrade.commands.deploy_commands import (start_create_userdir, + start_new_config, start_new_hyperopt, start_new_strategy) from freqtrade.commands.hyperopt_commands import (start_hyperopt_list, diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 724814554..504c6b0b5 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -43,6 +43,8 @@ ARGS_TEST_PAIRLIST = ["config", "quote_currencies", "print_one_column", "list_pa ARGS_CREATE_USERDIR = ["user_data_dir", "reset"] +ARGS_BUILD_CONFIG = ["config"] + ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"] ARGS_BUILD_HYPEROPT = ["user_data_dir", "hyperopt", "template"] @@ -133,8 +135,9 @@ class Arguments: from freqtrade.commands import (start_create_userdir, start_download_data, start_hyperopt_list, start_hyperopt_show, start_list_exchanges, start_list_markets, - start_list_strategies, start_new_hyperopt, - start_new_strategy, start_list_timeframes, + start_list_strategies, start_list_timeframes, + start_new_config, + start_new_hyperopt, start_new_strategy, start_plot_dataframe, start_plot_profit, start_backtesting, start_hyperopt, start_edge, start_test_pairlist, start_trading) @@ -177,6 +180,12 @@ class Arguments: create_userdir_cmd.set_defaults(func=start_create_userdir) self._build_args(optionlist=ARGS_CREATE_USERDIR, parser=create_userdir_cmd) + # add new-config subcommand + build_config_cmd = subparsers.add_parser('new-config', + help="Create new config") + build_config_cmd.set_defaults(func=start_new_config) + self._build_args(optionlist=ARGS_BUILD_CONFIG, parser=build_config_cmd) + # add new-strategy subcommand build_strategy_cmd = subparsers.add_parser('new-strategy', help="Create new strategy") diff --git a/freqtrade/commands/deploy_commands.py b/freqtrade/commands/deploy_commands.py index 99ae63244..34755932c 100644 --- a/freqtrade/commands/deploy_commands.py +++ b/freqtrade/commands/deploy_commands.py @@ -110,3 +110,11 @@ def start_new_hyperopt(args: Dict[str, Any]) -> None: deploy_new_hyperopt(args['hyperopt'], new_path, args['template']) else: raise OperationalException("`new-hyperopt` requires --hyperopt to be set.") + + +def start_new_config(args: Dict[str, Any]) -> None: + """ + Create a new strategy from a template + Asking the user questions to fill out the templateaccordingly. + """ + pass From 9f291282056fde93c3cab74470764a9cdf80f89b Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Jan 2020 07:01:17 +0100 Subject: [PATCH 002/117] Fix small json formatting issue --- config.json.example | 2 +- config_binance.json.example | 2 +- config_full.json.example | 2 +- config_kraken.json.example | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config.json.example b/config.json.example index 8b85e71eb..a03ab6c2a 100644 --- a/config.json.example +++ b/config.json.example @@ -4,7 +4,7 @@ "stake_amount": 0.05, "tradable_balance_ratio": 0.99, "fiat_display_currency": "USD", - "ticker_interval" : "5m", + "ticker_interval": "5m", "dry_run": false, "trailing_stop": false, "unfilledtimeout": { diff --git a/config_binance.json.example b/config_binance.json.example index 0521a3a35..e2c9879b0 100644 --- a/config_binance.json.example +++ b/config_binance.json.example @@ -4,7 +4,7 @@ "stake_amount": 0.05, "tradable_balance_ratio": 0.99, "fiat_display_currency": "USD", - "ticker_interval" : "5m", + "ticker_interval": "5m", "dry_run": true, "trailing_stop": false, "unfilledtimeout": { diff --git a/config_full.json.example b/config_full.json.example index 82d8bd04a..f543604e7 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -4,7 +4,7 @@ "stake_amount": 0.05, "tradable_balance_ratio": 0.99, "fiat_display_currency": "USD", - "amount_reserve_percent" : 0.05, + "amount_reserve_percent": 0.05, "amend_last_stake_amount": false, "last_stake_amount_min_ratio": 0.5, "dry_run": false, diff --git a/config_kraken.json.example b/config_kraken.json.example index a527b569d..4f74d0b7d 100644 --- a/config_kraken.json.example +++ b/config_kraken.json.example @@ -4,7 +4,7 @@ "stake_amount": 10, "tradable_balance_ratio": 0.99, "fiat_display_currency": "EUR", - "ticker_interval" : "5m", + "ticker_interval": "5m", "dry_run": true, "trailing_stop": false, "unfilledtimeout": { From 122c9163566acfbbf2d2a18e86dfcfac3a772dab Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Jan 2020 07:01:32 +0100 Subject: [PATCH 003/117] Add first version of config_deploy --- freqtrade/commands/deploy_commands.py | 28 ++++++++- freqtrade/templates/base_config.json.j2 | 82 +++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 freqtrade/templates/base_config.json.j2 diff --git a/freqtrade/commands/deploy_commands.py b/freqtrade/commands/deploy_commands.py index 34755932c..4e114a6d8 100644 --- a/freqtrade/commands/deploy_commands.py +++ b/freqtrade/commands/deploy_commands.py @@ -112,9 +112,35 @@ def start_new_hyperopt(args: Dict[str, Any]) -> None: raise OperationalException("`new-hyperopt` requires --hyperopt to be set.") +def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None: + """ + Applies selections to the template and writes the result to config_path + :param config_path: Path object for new config file. Should not exist yet + :param selecions: Dict containing selections taken by the user. + """ + config_text = render_template(templatefile='base_config.json.j2', + arguments=selections) + + config_path.write_text(config_text) + + def start_new_config(args: Dict[str, Any]) -> None: """ Create a new strategy from a template Asking the user questions to fill out the templateaccordingly. """ - pass + sample_selections = { + 'stake_currency': 'USDT', + 'stake_amount': 100, + 'fiat_display_currency': 'EUR', + 'ticker_interval': '15m', + 'dry_run': True, + 'exchange': 'binance', + 'exchange_key': 'sampleKey', + 'exchange_secret': 'Samplesecret', + 'telegram': True, + 'telegram_token': 'asdf1244', + 'telegram_chat_id': '1144444', + } + config_path = Path(args['config'][0]) + deploy_new_config(config_path, sample_selections) diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 new file mode 100644 index 000000000..ad8a762ec --- /dev/null +++ b/freqtrade/templates/base_config.json.j2 @@ -0,0 +1,82 @@ +{ + "max_open_trades": 3, + "stake_currency": "{{ stake_currency }}", + "stake_amount": {{ stake_amount }}, + "tradable_balance_ratio": 0.99, + "fiat_display_currency": "{{ fiat_display_currency }}", + "ticker_interval": "{{ ticker_interval }}", + "dry_run": {{ dry_run | lower }}, + "unfilledtimeout": { + "buy": 10, + "sell": 30 + }, + "bid_strategy": { + "ask_last_balance": 0.0, + "use_order_book": false, + "order_book_top": 1, + "check_depth_of_market": { + "enabled": false, + "bids_to_ask_delta": 1 + } + }, + "ask_strategy":{ + "use_order_book": false, + "order_book_min": 1, + "order_book_max": 9, + "use_sell_signal": true, + "sell_profit_only": false, + "ignore_roi_if_buy_signal": false + }, + "exchange": { + "name": "bittrex", + "key": "your_exchange_key", + "secret": "your_exchange_secret", + "ccxt_config": {"enableRateLimit": true}, + "ccxt_async_config": { + "enableRateLimit": true, + "rateLimit": 500 + }, + "pair_whitelist": [ + "ETH/BTC", + "LTC/BTC", + "ETC/BTC", + "DASH/BTC", + "ZEC/BTC", + "XLM/BTC", + "NXT/BTC", + "TRX/BTC", + "ADA/BTC", + "XMR/BTC" + ], + "pair_blacklist": [ + "DOGE/BTC" + ] + }, + "pairlists": [ + {"method": "StaticPairList"} + ], + "edge": { + "enabled": false, + "process_throttle_secs": 3600, + "calculate_since_number_of_days": 7, + "allowed_risk": 0.01, + "stoploss_range_min": -0.01, + "stoploss_range_max": -0.1, + "stoploss_range_step": -0.01, + "minimum_winrate": 0.60, + "minimum_expectancy": 0.20, + "min_trade_number": 10, + "max_trade_duration_minute": 1440, + "remove_pumps": false + }, + "telegram": { + "enabled": {{ telegram | lower }}, + "token": "{{ telegram_token }}", + "chat_id": "{{ telegram_chat_id }}" + }, + "initial_state": "running", + "forcebuy_enable": false, + "internals": { + "process_throttle_secs": 5 + } +} From c80d8f432acc07665353947acae84a4d09cd3ced Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Jan 2020 07:13:38 +0100 Subject: [PATCH 004/117] Add exchange templates --- freqtrade/commands/deploy_commands.py | 19 +++++++++-- freqtrade/misc.py | 1 - freqtrade/templates/base_config.json.j2 | 24 +------------- .../subtemplates/exchange_binance.j2 | 28 ++++++++++++++++ .../subtemplates/exchange_generic.j2 | 13 ++++++++ .../templates/subtemplates/exchange_kraken.j2 | 33 +++++++++++++++++++ 6 files changed, 92 insertions(+), 26 deletions(-) create mode 100644 freqtrade/templates/subtemplates/exchange_binance.j2 create mode 100644 freqtrade/templates/subtemplates/exchange_generic.j2 create mode 100644 freqtrade/templates/subtemplates/exchange_kraken.j2 diff --git a/freqtrade/commands/deploy_commands.py b/freqtrade/commands/deploy_commands.py index 4e114a6d8..065703faa 100644 --- a/freqtrade/commands/deploy_commands.py +++ b/freqtrade/commands/deploy_commands.py @@ -118,9 +118,22 @@ def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None: :param config_path: Path object for new config file. Should not exist yet :param selecions: Dict containing selections taken by the user. """ + from jinja2.exceptions import TemplateNotFound + try: + selections['exchange'] = render_template( + templatefile=f"subtemplates/exchange_{selections['exchange_name']}.j2", + arguments=selections + ) + except TemplateNotFound: + selections['exchange'] = render_template( + templatefile=f"subtemplates/exchange_generic.j2", + arguments=selections + ) + config_text = render_template(templatefile='base_config.json.j2', arguments=selections) + logger.info(f"Writing config to `{config_path}`.") config_path.write_text(config_text) @@ -135,12 +148,14 @@ def start_new_config(args: Dict[str, Any]) -> None: 'fiat_display_currency': 'EUR', 'ticker_interval': '15m', 'dry_run': True, - 'exchange': 'binance', + 'exchange_name': 'binance', 'exchange_key': 'sampleKey', 'exchange_secret': 'Samplesecret', - 'telegram': True, + 'telegram': False, 'telegram_token': 'asdf1244', 'telegram_chat_id': '1144444', } config_path = Path(args['config'][0]) deploy_new_config(config_path, sample_selections) + + diff --git a/freqtrade/misc.py b/freqtrade/misc.py index bcba78cf0..40e1fdf17 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -138,5 +138,4 @@ def render_template(templatefile: str, arguments: dict = {}): autoescape=select_autoescape(['html', 'xml']) ) template = env.get_template(templatefile) - return template.render(**arguments) diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 index ad8a762ec..f2f919e1f 100644 --- a/freqtrade/templates/base_config.json.j2 +++ b/freqtrade/templates/base_config.json.j2 @@ -28,29 +28,7 @@ "ignore_roi_if_buy_signal": false }, "exchange": { - "name": "bittrex", - "key": "your_exchange_key", - "secret": "your_exchange_secret", - "ccxt_config": {"enableRateLimit": true}, - "ccxt_async_config": { - "enableRateLimit": true, - "rateLimit": 500 - }, - "pair_whitelist": [ - "ETH/BTC", - "LTC/BTC", - "ETC/BTC", - "DASH/BTC", - "ZEC/BTC", - "XLM/BTC", - "NXT/BTC", - "TRX/BTC", - "ADA/BTC", - "XMR/BTC" - ], - "pair_blacklist": [ - "DOGE/BTC" - ] + {{ exchange | indent(8) }} }, "pairlists": [ {"method": "StaticPairList"} diff --git a/freqtrade/templates/subtemplates/exchange_binance.j2 b/freqtrade/templates/subtemplates/exchange_binance.j2 new file mode 100644 index 000000000..082af45c4 --- /dev/null +++ b/freqtrade/templates/subtemplates/exchange_binance.j2 @@ -0,0 +1,28 @@ +"name": "{{ exchange_name | lower }}", +"key": "{{ exchange_key }}", +"secret": "{{ exchange_secret }}", +"ccxt_config": {"enableRateLimit": true}, +"ccxt_async_config": { + "enableRateLimit": true, + "rateLimit": 200 +}, +"pair_whitelist": [ + "ALGO/BTC", + "ATOM/BTC", + "BAT/BTC", + "BCH/BTC", + "BRD/BTC", + "EOS/BTC", + "ETH/BTC", + "IOTA/BTC", + "LINK/BTC", + "LTC/BTC", + "NEO/BTC", + "NXS/BTC", + "XMR/BTC", + "XRP/BTC", + "XTZ/BTC" +], +"pair_blacklist": [ + "BNB/BTC" +] diff --git a/freqtrade/templates/subtemplates/exchange_generic.j2 b/freqtrade/templates/subtemplates/exchange_generic.j2 new file mode 100644 index 000000000..5d5bee2b2 --- /dev/null +++ b/freqtrade/templates/subtemplates/exchange_generic.j2 @@ -0,0 +1,13 @@ +"name": "{{ exchange_name | lower }}", +"key": "{{ exchange_key }}", +"secret": "{{ exchange_secret }}", +"ccxt_config": {"enableRateLimit": true}, +"ccxt_async_config": { + "enableRateLimit": true, +}, +"pair_whitelist": [ + +], +"pair_blacklist": [ + +] diff --git a/freqtrade/templates/subtemplates/exchange_kraken.j2 b/freqtrade/templates/subtemplates/exchange_kraken.j2 new file mode 100644 index 000000000..690828887 --- /dev/null +++ b/freqtrade/templates/subtemplates/exchange_kraken.j2 @@ -0,0 +1,33 @@ +"name": "kraken", +"key": "{{ exchange_key }}", +"secret": "{{ exchange_secret }}", +"ccxt_config": {"enableRateLimit": true}, +"ccxt_async_config": { + "enableRateLimit": true, + "rateLimit": 1000 +}, +"pair_whitelist": [ + "ADA/EUR", + "ATOM/EUR", + "BAT/EUR", + "BCH/EUR", + "BTC/EUR", + "DAI/EUR", + "DASH/EUR", + "EOS/EUR", + "ETC/EUR", + "ETH/EUR", + "LINK/EUR", + "LTC/EUR", + "QTUM/EUR", + "REP/EUR", + "WAVES/EUR", + "XLM/EUR", + "XMR/EUR", + "XRP/EUR", + "XTZ/EUR", + "ZEC/EUR" +], +"pair_blacklist": [ + +] From dd83cb1b95fb2a0f3790d7c83a377ed72b659fd3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Jan 2020 20:27:38 +0100 Subject: [PATCH 005/117] Extract selection generation to a seperate method --- freqtrade/commands/deploy_commands.py | 38 ++++++++++++++++--------- freqtrade/templates/base_config.json.j2 | 2 +- tests/commands/test_commands.py | 21 ++++++++++++-- 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/freqtrade/commands/deploy_commands.py b/freqtrade/commands/deploy_commands.py index 065703faa..87aea7492 100644 --- a/freqtrade/commands/deploy_commands.py +++ b/freqtrade/commands/deploy_commands.py @@ -112,6 +112,28 @@ def start_new_hyperopt(args: Dict[str, Any]) -> None: raise OperationalException("`new-hyperopt` requires --hyperopt to be set.") +def ask_user_config() -> Dict[str, Any]: + """ + Ask user a few questions to build the configuration. + :returns: Dict with keys to put into template + """ + sample_selections = { + 'max_open_trades': 3, + 'stake_currency': 'USDT', + 'stake_amount': 100, + 'fiat_display_currency': 'EUR', + 'ticker_interval': '15m', + 'dry_run': True, + 'exchange_name': 'binance', + 'exchange_key': 'sampleKey', + 'exchange_secret': 'Samplesecret', + 'telegram': False, + 'telegram_token': 'asdf1244', + 'telegram_chat_id': '1144444', + } + return sample_selections + + def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None: """ Applies selections to the template and writes the result to config_path @@ -142,20 +164,8 @@ def start_new_config(args: Dict[str, Any]) -> None: Create a new strategy from a template Asking the user questions to fill out the templateaccordingly. """ - sample_selections = { - 'stake_currency': 'USDT', - 'stake_amount': 100, - 'fiat_display_currency': 'EUR', - 'ticker_interval': '15m', - 'dry_run': True, - 'exchange_name': 'binance', - 'exchange_key': 'sampleKey', - 'exchange_secret': 'Samplesecret', - 'telegram': False, - 'telegram_token': 'asdf1244', - 'telegram_chat_id': '1144444', - } + selections = ask_user_config() config_path = Path(args['config'][0]) - deploy_new_config(config_path, sample_selections) + deploy_new_config(config_path, selections) diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 index f2f919e1f..1370bfa80 100644 --- a/freqtrade/templates/base_config.json.j2 +++ b/freqtrade/templates/base_config.json.j2 @@ -1,5 +1,5 @@ { - "max_open_trades": 3, + "max_open_trades": {{ max_open_trades }}, "stake_currency": "{{ stake_currency }}", "stake_amount": {{ stake_amount }}, "tradable_balance_ratio": 0.99, diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 65d7f6eaf..f8efdfd3c 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -8,8 +8,9 @@ from freqtrade.commands import (start_create_userdir, start_download_data, start_hyperopt_list, start_hyperopt_show, start_list_exchanges, start_list_markets, start_list_strategies, start_list_timeframes, - start_new_hyperopt, start_new_strategy, - start_test_pairlist, start_trading) + start_new_config, start_new_hyperopt, + start_new_strategy, start_test_pairlist, + start_trading) from freqtrade.configuration import setup_utils_configuration from freqtrade.exceptions import OperationalException from freqtrade.state import RunMode @@ -537,6 +538,22 @@ def test_start_new_hyperopt_no_arg(mocker, caplog): start_new_hyperopt(get_args(args)) +def test_start_new_config(mocker, caplog): + wt_mock = mocker.patch.object(Path, "write_text", MagicMock()) + mocker.patch.object(Path, "exists", MagicMock(return_value=False)) + + args = [ + "new-config", + "--config", + "coolconfig.json" + ] + start_new_config(get_args(args)) + + assert wt_mock.call_count == 1 + assert "binance" in wt_mock.call_args_list[0][0][0] + assert log_has_re("Writing config to .*", caplog) + + def test_download_data_keyboardInterrupt(mocker, caplog, markets): dl_mock = mocker.patch('freqtrade.commands.data_commands.refresh_backtest_ohlcv_data', MagicMock(side_effect=KeyboardInterrupt)) From 49c9258a088e42db46656b24fc90d1227d604dc1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Jan 2020 20:32:27 +0100 Subject: [PATCH 006/117] enhance test --- tests/commands/test_commands.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index f8efdfd3c..19999f319 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -1,3 +1,4 @@ +import json import re from pathlib import Path from unittest.mock import MagicMock, PropertyMock @@ -538,10 +539,26 @@ def test_start_new_hyperopt_no_arg(mocker, caplog): start_new_hyperopt(get_args(args)) -def test_start_new_config(mocker, caplog): +@pytest.mark.parametrize('exchange', ['bittrex', 'binance', 'kraken', 'ftx']) +def test_start_new_config(mocker, caplog, exchange): wt_mock = mocker.patch.object(Path, "write_text", MagicMock()) mocker.patch.object(Path, "exists", MagicMock(return_value=False)) - + sample_selections = { + 'max_open_trades': 3, + 'stake_currency': 'USDT', + 'stake_amount': 100, + 'fiat_display_currency': 'EUR', + 'ticker_interval': '15m', + 'dry_run': True, + 'exchange_name': exchange, + 'exchange_key': 'sampleKey', + 'exchange_secret': 'Samplesecret', + 'telegram': False, + 'telegram_token': 'asdf1244', + 'telegram_chat_id': '1144444', + } + mocker.patch('freqtrade.commands.deploy_commands.ask_user_config', + return_value=sample_selections) args = [ "new-config", "--config", @@ -549,9 +566,11 @@ def test_start_new_config(mocker, caplog): ] start_new_config(get_args(args)) - assert wt_mock.call_count == 1 - assert "binance" in wt_mock.call_args_list[0][0][0] assert log_has_re("Writing config to .*", caplog) + assert wt_mock.call_count == 1 + result = json.loads(wt_mock.call_args_list[0][0][0]) + assert result['exchange']['name'] == exchange + assert result['ticker_interval'] == '15m' def test_download_data_keyboardInterrupt(mocker, caplog, markets): From e250c56829e74445e6177d4b61c17e57a0af21e0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Jan 2020 21:21:38 +0100 Subject: [PATCH 007/117] Add Questionaire workflow --- freqtrade/commands/deploy_commands.py | 98 ++++++++++++++++++++++++++- requirements-common.txt | 3 + setup.py | 2 + 3 files changed, 101 insertions(+), 2 deletions(-) diff --git a/freqtrade/commands/deploy_commands.py b/freqtrade/commands/deploy_commands.py index 87aea7492..670f272ce 100644 --- a/freqtrade/commands/deploy_commands.py +++ b/freqtrade/commands/deploy_commands.py @@ -3,11 +3,14 @@ import sys from pathlib import Path from typing import Any, Dict +from questionary import Separator, prompt + from freqtrade.configuration import setup_utils_configuration from freqtrade.configuration.directory_operations import (copy_sample_files, create_userdata_dir) from freqtrade.constants import USERPATH_HYPEROPTS, USERPATH_STRATEGY from freqtrade.exceptions import OperationalException +from freqtrade.exchange import available_exchanges from freqtrade.misc import render_template from freqtrade.state import RunMode @@ -117,6 +120,99 @@ def ask_user_config() -> Dict[str, Any]: Ask user a few questions to build the configuration. :returns: Dict with keys to put into template """ + questions = [ + { + "type": "confirm", + "name": "dry_run", + "message": "Do you want to enable Dry-run (simulated trades)?", + "default": True, + }, + { + "type": "text", + "name": "stake_currency", + "message": "Please insert your stake currency:", + "default": 'BTC', + }, + { + "type": "text", + "name": "stake_amount", + "message": "Please insert your stake amount:", + "default": "0.01", + }, + { + "type": "text", + "name": "max_open_trades", + "message": "Please insert max_open_trades:", + "default": "3", + }, + { + "type": "text", + "name": "ticker_interval", + "message": "Please insert your ticker interval:", + "default": "5m", + }, + { + "type": "text", + "name": "fiat_display_currency", + "message": "Please insert your display Currency (for reporting):", + "default": 'USD', + }, + { + "type": "select", + "name": "exchange_name", + "message": "Select exchange", + "choices": [ + "bittrex", + "binance", + "binanceje", + "binanceus", + "kraken", + Separator(), + "other", + ], + }, + { + "type": "autocomplete", + "name": "exchange_name", + "message": "Type your exchange name (Must be supported by ccxt)", + "choices": available_exchanges(), + "when": lambda x: x["exchange_name"] == 'other' + }, + { + "type": "password", + "name": "exchange_key", + "message": "Insert Exchange Key", + "when": lambda x: not x['dry_run'] + }, + { + "type": "password", + "name": "exchange_secret", + "message": "Insert Exchange Secret", + "when": lambda x: not x['dry_run'] + }, + { + "type": "confirm", + "name": "telegram", + "message": "Do you want to enable Telegram?", + "default": False, + }, + { + "type": "password", + "name": "telegram_token", + "message": "Insert Telegram token", + "when": lambda x: x['telegram'] + }, + { + "type": "text", + "name": "telegram_chat_id", + "message": "Insert Telegram chat id", + "when": lambda x: x['telegram'] + }, + ] + answers = prompt(questions) + + print(answers) + sample_selections = { 'max_open_trades': 3, 'stake_currency': 'USDT', @@ -167,5 +263,3 @@ def start_new_config(args: Dict[str, Any]) -> None: selections = ask_user_config() config_path = Path(args['config'][0]) deploy_new_config(config_path, selections) - - diff --git a/requirements-common.txt b/requirements-common.txt index e4fe54721..80f18892b 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -28,3 +28,6 @@ flask==1.1.1 # Support for colorized terminal output colorama==0.4.3 +# Building config files interactively +questionary==1.5.1 +prompt-toolkit==3.0.3 diff --git a/setup.py b/setup.py index 7d8d7b68d..63a595f32 100644 --- a/setup.py +++ b/setup.py @@ -79,6 +79,8 @@ setup(name='freqtrade', 'sdnotify', 'colorama', 'jinja2', + 'questionary', + 'prompt-toolkit', # from requirements.txt 'numpy', 'pandas', From 940bfbee96eef00e56d7f5097f1c59099b34bb37 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Jan 2020 21:28:01 +0100 Subject: [PATCH 008/117] Move start_config out of build_commands file --- freqtrade/commands/__init__.py | 2 +- freqtrade/commands/build_config_commands.py | 160 ++++++++++++++++++ freqtrade/commands/deploy_commands.py | 153 ----------------- .../subtemplates/exchange_generic.j2 | 2 +- tests/commands/test_commands.py | 2 +- 5 files changed, 163 insertions(+), 156 deletions(-) create mode 100644 freqtrade/commands/build_config_commands.py diff --git a/freqtrade/commands/__init__.py b/freqtrade/commands/__init__.py index 81467cf61..6ea325e63 100644 --- a/freqtrade/commands/__init__.py +++ b/freqtrade/commands/__init__.py @@ -7,9 +7,9 @@ Note: Be careful with file-scoped imports in these subfiles. as they are parsed on startup, nothing containing optional modules should be loaded. """ from freqtrade.commands.arguments import Arguments +from freqtrade.commands.build_config_commands import start_new_config from freqtrade.commands.data_commands import start_download_data from freqtrade.commands.deploy_commands import (start_create_userdir, - start_new_config, start_new_hyperopt, start_new_strategy) from freqtrade.commands.hyperopt_commands import (start_hyperopt_list, diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py new file mode 100644 index 000000000..393416f53 --- /dev/null +++ b/freqtrade/commands/build_config_commands.py @@ -0,0 +1,160 @@ +import logging +from pathlib import Path +from typing import Any, Dict + +from questionary import Separator, prompt + +from freqtrade.exchange import available_exchanges +from freqtrade.misc import render_template + +logger = logging.getLogger(__name__) + + +def ask_user_config() -> Dict[str, Any]: + """ + Ask user a few questions to build the configuration. + :returns: Dict with keys to put into template + """ + questions = [ + { + "type": "confirm", + "name": "dry_run", + "message": "Do you want to enable Dry-run (simulated trades)?", + "default": True, + }, + { + "type": "text", + "name": "stake_currency", + "message": "Please insert your stake currency:", + "default": 'BTC', + }, + { + "type": "text", + "name": "stake_amount", + "message": "Please insert your stake amount:", + "default": "0.01", + }, + { + "type": "text", + "name": "max_open_trades", + "message": "Please insert max_open_trades:", + "default": "3", + }, + { + "type": "text", + "name": "ticker_interval", + "message": "Please insert your ticker interval:", + "default": "5m", + }, + { + "type": "text", + "name": "fiat_display_currency", + "message": "Please insert your display Currency (for reporting):", + "default": 'USD', + }, + { + "type": "select", + "name": "exchange_name", + "message": "Select exchange", + "choices": [ + "bittrex", + "binance", + "binanceje", + "binanceus", + "kraken", + Separator(), + "other", + ], + }, + { + "type": "autocomplete", + "name": "exchange_name", + "message": "Type your exchange name (Must be supported by ccxt)", + "choices": available_exchanges(), + "when": lambda x: x["exchange_name"] == 'other' + }, + { + "type": "password", + "name": "exchange_key", + "message": "Insert Exchange Key", + "when": lambda x: not x['dry_run'] + }, + { + "type": "password", + "name": "exchange_secret", + "message": "Insert Exchange Secret", + "when": lambda x: not x['dry_run'] + }, + { + "type": "confirm", + "name": "telegram", + "message": "Do you want to enable Telegram?", + "default": False, + }, + { + "type": "password", + "name": "telegram_token", + "message": "Insert Telegram token", + "when": lambda x: x['telegram'] + }, + { + "type": "text", + "name": "telegram_chat_id", + "message": "Insert Telegram chat id", + "when": lambda x: x['telegram'] + }, + ] + answers = prompt(questions) + + print(answers) + + sample_selections = { + 'max_open_trades': 3, + 'stake_currency': 'USDT', + 'stake_amount': 100, + 'fiat_display_currency': 'EUR', + 'ticker_interval': '15m', + 'dry_run': True, + 'exchange_name': 'binance', + 'exchange_key': 'sampleKey', + 'exchange_secret': 'Samplesecret', + 'telegram': False, + 'telegram_token': 'asdf1244', + 'telegram_chat_id': '1144444', + } + return sample_selections + + +def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None: + """ + Applies selections to the template and writes the result to config_path + :param config_path: Path object for new config file. Should not exist yet + :param selecions: Dict containing selections taken by the user. + """ + from jinja2.exceptions import TemplateNotFound + try: + selections['exchange'] = render_template( + templatefile=f"subtemplates/exchange_{selections['exchange_name']}.j2", + arguments=selections + ) + except TemplateNotFound: + selections['exchange'] = render_template( + templatefile=f"subtemplates/exchange_generic.j2", + arguments=selections + ) + + config_text = render_template(templatefile='base_config.json.j2', + arguments=selections) + + logger.info(f"Writing config to `{config_path}`.") + config_path.write_text(config_text) + + +def start_new_config(args: Dict[str, Any]) -> None: + """ + Create a new strategy from a template + Asking the user questions to fill out the templateaccordingly. + """ + selections = ask_user_config() + config_path = Path(args['config'][0]) + deploy_new_config(config_path, selections) diff --git a/freqtrade/commands/deploy_commands.py b/freqtrade/commands/deploy_commands.py index 670f272ce..99ae63244 100644 --- a/freqtrade/commands/deploy_commands.py +++ b/freqtrade/commands/deploy_commands.py @@ -3,14 +3,11 @@ import sys from pathlib import Path from typing import Any, Dict -from questionary import Separator, prompt - from freqtrade.configuration import setup_utils_configuration from freqtrade.configuration.directory_operations import (copy_sample_files, create_userdata_dir) from freqtrade.constants import USERPATH_HYPEROPTS, USERPATH_STRATEGY from freqtrade.exceptions import OperationalException -from freqtrade.exchange import available_exchanges from freqtrade.misc import render_template from freqtrade.state import RunMode @@ -113,153 +110,3 @@ def start_new_hyperopt(args: Dict[str, Any]) -> None: deploy_new_hyperopt(args['hyperopt'], new_path, args['template']) else: raise OperationalException("`new-hyperopt` requires --hyperopt to be set.") - - -def ask_user_config() -> Dict[str, Any]: - """ - Ask user a few questions to build the configuration. - :returns: Dict with keys to put into template - """ - questions = [ - { - "type": "confirm", - "name": "dry_run", - "message": "Do you want to enable Dry-run (simulated trades)?", - "default": True, - }, - { - "type": "text", - "name": "stake_currency", - "message": "Please insert your stake currency:", - "default": 'BTC', - }, - { - "type": "text", - "name": "stake_amount", - "message": "Please insert your stake amount:", - "default": "0.01", - }, - { - "type": "text", - "name": "max_open_trades", - "message": "Please insert max_open_trades:", - "default": "3", - }, - { - "type": "text", - "name": "ticker_interval", - "message": "Please insert your ticker interval:", - "default": "5m", - }, - { - "type": "text", - "name": "fiat_display_currency", - "message": "Please insert your display Currency (for reporting):", - "default": 'USD', - }, - { - "type": "select", - "name": "exchange_name", - "message": "Select exchange", - "choices": [ - "bittrex", - "binance", - "binanceje", - "binanceus", - "kraken", - Separator(), - "other", - ], - }, - { - "type": "autocomplete", - "name": "exchange_name", - "message": "Type your exchange name (Must be supported by ccxt)", - "choices": available_exchanges(), - "when": lambda x: x["exchange_name"] == 'other' - }, - { - "type": "password", - "name": "exchange_key", - "message": "Insert Exchange Key", - "when": lambda x: not x['dry_run'] - }, - { - "type": "password", - "name": "exchange_secret", - "message": "Insert Exchange Secret", - "when": lambda x: not x['dry_run'] - }, - { - "type": "confirm", - "name": "telegram", - "message": "Do you want to enable Telegram?", - "default": False, - }, - { - "type": "password", - "name": "telegram_token", - "message": "Insert Telegram token", - "when": lambda x: x['telegram'] - }, - { - "type": "text", - "name": "telegram_chat_id", - "message": "Insert Telegram chat id", - "when": lambda x: x['telegram'] - }, - ] - answers = prompt(questions) - - print(answers) - - sample_selections = { - 'max_open_trades': 3, - 'stake_currency': 'USDT', - 'stake_amount': 100, - 'fiat_display_currency': 'EUR', - 'ticker_interval': '15m', - 'dry_run': True, - 'exchange_name': 'binance', - 'exchange_key': 'sampleKey', - 'exchange_secret': 'Samplesecret', - 'telegram': False, - 'telegram_token': 'asdf1244', - 'telegram_chat_id': '1144444', - } - return sample_selections - - -def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None: - """ - Applies selections to the template and writes the result to config_path - :param config_path: Path object for new config file. Should not exist yet - :param selecions: Dict containing selections taken by the user. - """ - from jinja2.exceptions import TemplateNotFound - try: - selections['exchange'] = render_template( - templatefile=f"subtemplates/exchange_{selections['exchange_name']}.j2", - arguments=selections - ) - except TemplateNotFound: - selections['exchange'] = render_template( - templatefile=f"subtemplates/exchange_generic.j2", - arguments=selections - ) - - config_text = render_template(templatefile='base_config.json.j2', - arguments=selections) - - logger.info(f"Writing config to `{config_path}`.") - config_path.write_text(config_text) - - -def start_new_config(args: Dict[str, Any]) -> None: - """ - Create a new strategy from a template - Asking the user questions to fill out the templateaccordingly. - """ - selections = ask_user_config() - config_path = Path(args['config'][0]) - deploy_new_config(config_path, selections) diff --git a/freqtrade/templates/subtemplates/exchange_generic.j2 b/freqtrade/templates/subtemplates/exchange_generic.j2 index 5d5bee2b2..33309de3b 100644 --- a/freqtrade/templates/subtemplates/exchange_generic.j2 +++ b/freqtrade/templates/subtemplates/exchange_generic.j2 @@ -3,7 +3,7 @@ "secret": "{{ exchange_secret }}", "ccxt_config": {"enableRateLimit": true}, "ccxt_async_config": { - "enableRateLimit": true, + "enableRateLimit": true }, "pair_whitelist": [ diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 19999f319..51b69449d 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -557,7 +557,7 @@ def test_start_new_config(mocker, caplog, exchange): 'telegram_token': 'asdf1244', 'telegram_chat_id': '1144444', } - mocker.patch('freqtrade.commands.deploy_commands.ask_user_config', + mocker.patch('freqtrade.commands.build_config_commands.ask_user_config', return_value=sample_selections) args = [ "new-config", From 2f0775fa1b6381b163ecd55ba1a0b684a9bbfea8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Jan 2020 21:30:29 +0100 Subject: [PATCH 009/117] Extract build-config tests to new file --- tests/commands/test_build_config.py | 42 +++++++++++++++++++++++++++++ tests/commands/test_commands.py | 40 ++------------------------- 2 files changed, 44 insertions(+), 38 deletions(-) create mode 100644 tests/commands/test_build_config.py diff --git a/tests/commands/test_build_config.py b/tests/commands/test_build_config.py new file mode 100644 index 000000000..4114ff489 --- /dev/null +++ b/tests/commands/test_build_config.py @@ -0,0 +1,42 @@ +import json +from pathlib import Path +from unittest.mock import MagicMock + +import pytest + +from freqtrade.commands import start_new_config +from tests.conftest import get_args, log_has_re + + +@pytest.mark.parametrize('exchange', ['bittrex', 'binance', 'kraken', 'ftx']) +def test_start_new_config(mocker, caplog, exchange): + wt_mock = mocker.patch.object(Path, "write_text", MagicMock()) + mocker.patch.object(Path, "exists", MagicMock(return_value=False)) + sample_selections = { + 'max_open_trades': 3, + 'stake_currency': 'USDT', + 'stake_amount': 100, + 'fiat_display_currency': 'EUR', + 'ticker_interval': '15m', + 'dry_run': True, + 'exchange_name': exchange, + 'exchange_key': 'sampleKey', + 'exchange_secret': 'Samplesecret', + 'telegram': False, + 'telegram_token': 'asdf1244', + 'telegram_chat_id': '1144444', + } + mocker.patch('freqtrade.commands.build_config_commands.ask_user_config', + return_value=sample_selections) + args = [ + "new-config", + "--config", + "coolconfig.json" + ] + start_new_config(get_args(args)) + + assert log_has_re("Writing config to .*", caplog) + assert wt_mock.call_count == 1 + result = json.loads(wt_mock.call_args_list[0][0][0]) + assert result['exchange']['name'] == exchange + assert result['ticker_interval'] == '15m' diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 51b69449d..65d7f6eaf 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -1,4 +1,3 @@ -import json import re from pathlib import Path from unittest.mock import MagicMock, PropertyMock @@ -9,9 +8,8 @@ from freqtrade.commands import (start_create_userdir, start_download_data, start_hyperopt_list, start_hyperopt_show, start_list_exchanges, start_list_markets, start_list_strategies, start_list_timeframes, - start_new_config, start_new_hyperopt, - start_new_strategy, start_test_pairlist, - start_trading) + start_new_hyperopt, start_new_strategy, + start_test_pairlist, start_trading) from freqtrade.configuration import setup_utils_configuration from freqtrade.exceptions import OperationalException from freqtrade.state import RunMode @@ -539,40 +537,6 @@ def test_start_new_hyperopt_no_arg(mocker, caplog): start_new_hyperopt(get_args(args)) -@pytest.mark.parametrize('exchange', ['bittrex', 'binance', 'kraken', 'ftx']) -def test_start_new_config(mocker, caplog, exchange): - wt_mock = mocker.patch.object(Path, "write_text", MagicMock()) - mocker.patch.object(Path, "exists", MagicMock(return_value=False)) - sample_selections = { - 'max_open_trades': 3, - 'stake_currency': 'USDT', - 'stake_amount': 100, - 'fiat_display_currency': 'EUR', - 'ticker_interval': '15m', - 'dry_run': True, - 'exchange_name': exchange, - 'exchange_key': 'sampleKey', - 'exchange_secret': 'Samplesecret', - 'telegram': False, - 'telegram_token': 'asdf1244', - 'telegram_chat_id': '1144444', - } - mocker.patch('freqtrade.commands.build_config_commands.ask_user_config', - return_value=sample_selections) - args = [ - "new-config", - "--config", - "coolconfig.json" - ] - start_new_config(get_args(args)) - - assert log_has_re("Writing config to .*", caplog) - assert wt_mock.call_count == 1 - result = json.loads(wt_mock.call_args_list[0][0][0]) - assert result['exchange']['name'] == exchange - assert result['ticker_interval'] == '15m' - - def test_download_data_keyboardInterrupt(mocker, caplog, markets): dl_mock = mocker.patch('freqtrade.commands.data_commands.refresh_backtest_ohlcv_data', MagicMock(side_effect=KeyboardInterrupt)) From acbf13e648425987d19654123d7e9d2ae83e8da7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Jan 2020 21:47:05 +0100 Subject: [PATCH 010/117] Fail gracefully if user interrupted question session --- freqtrade/commands/build_config_commands.py | 24 ++++++--------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index 393416f53..a6623c3cd 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -6,13 +6,14 @@ from questionary import Separator, prompt from freqtrade.exchange import available_exchanges from freqtrade.misc import render_template - +from freqtrade.exceptions import OperationalException logger = logging.getLogger(__name__) def ask_user_config() -> Dict[str, Any]: """ Ask user a few questions to build the configuration. + Interactive questions built using https://github.com/tmbo/questionary :returns: Dict with keys to put into template """ questions = [ @@ -106,23 +107,11 @@ def ask_user_config() -> Dict[str, Any]: ] answers = prompt(questions) - print(answers) + if not answers: + # Interrupted questionary sessions return an empty dict. + raise OperationalException("User interrupted interactive questions.") - sample_selections = { - 'max_open_trades': 3, - 'stake_currency': 'USDT', - 'stake_amount': 100, - 'fiat_display_currency': 'EUR', - 'ticker_interval': '15m', - 'dry_run': True, - 'exchange_name': 'binance', - 'exchange_key': 'sampleKey', - 'exchange_secret': 'Samplesecret', - 'telegram': False, - 'telegram_token': 'asdf1244', - 'telegram_chat_id': '1144444', - } - return sample_selections + return answers def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None: @@ -156,5 +145,6 @@ def start_new_config(args: Dict[str, Any]) -> None: Asking the user questions to fill out the templateaccordingly. """ selections = ask_user_config() + config_path = Path(args['config'][0]) deploy_new_config(config_path, selections) From cebf99b5d890cd2979d5e68816b43d4c999bda7e Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Jan 2020 21:59:24 +0100 Subject: [PATCH 011/117] Implement validation --- freqtrade/commands/build_config_commands.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index a6623c3cd..6abacd826 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -4,12 +4,29 @@ from typing import Any, Dict from questionary import Separator, prompt +from freqtrade.constants import UNLIMITED_STAKE_AMOUNT from freqtrade.exchange import available_exchanges from freqtrade.misc import render_template from freqtrade.exceptions import OperationalException logger = logging.getLogger(__name__) +def validate_is_int(val): + try: + _ = int(val) + return True + except Exception: + return False + + +def validate_is_float(val): + try: + _ = float(val) + return True + except Exception: + return False + + def ask_user_config() -> Dict[str, Any]: """ Ask user a few questions to build the configuration. @@ -34,12 +51,14 @@ def ask_user_config() -> Dict[str, Any]: "name": "stake_amount", "message": "Please insert your stake amount:", "default": "0.01", + "validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val), }, { "type": "text", "name": "max_open_trades", - "message": "Please insert max_open_trades:", + "message": f"Please insert max_open_trades (Integer or '{UNLIMITED_STAKE_AMOUNT}'):", "default": "3", + "validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_int(val) }, { "type": "text", From 83baa6ee2e2c59a4b97ecf76d091cca2c7389154 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Jan 2020 22:47:15 +0100 Subject: [PATCH 012/117] Add test stub --- tests/commands/test_build_config.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/commands/test_build_config.py b/tests/commands/test_build_config.py index 4114ff489..46e79b357 100644 --- a/tests/commands/test_build_config.py +++ b/tests/commands/test_build_config.py @@ -4,7 +4,8 @@ from unittest.mock import MagicMock import pytest -from freqtrade.commands import start_new_config +from freqtrade.commands.build_config_commands import (ask_user_config, + start_new_config) from tests.conftest import get_args, log_has_re @@ -40,3 +41,9 @@ def test_start_new_config(mocker, caplog, exchange): result = json.loads(wt_mock.call_args_list[0][0][0]) assert result['exchange']['name'] == exchange assert result['ticker_interval'] == '15m' + + +def test_ask_user_config(): + # TODO: Implement me + pass + # assert ask_user_config() From 4be3f053ca38fc5534a445ac08ff0766708f17df Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 30 Jan 2020 21:42:48 +0100 Subject: [PATCH 013/117] Exclude trading against BNB bases on binance --- .../templates/subtemplates/exchange_binance.j2 | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/freqtrade/templates/subtemplates/exchange_binance.j2 b/freqtrade/templates/subtemplates/exchange_binance.j2 index 082af45c4..c527d296b 100644 --- a/freqtrade/templates/subtemplates/exchange_binance.j2 +++ b/freqtrade/templates/subtemplates/exchange_binance.j2 @@ -24,5 +24,16 @@ "XTZ/BTC" ], "pair_blacklist": [ - "BNB/BTC" + "BNB/BTC", + "BNB/BUSD", + "BNB/ETH", + "BNB/EUR", + "BNB/NGN", + "BNB/PAX", + "BNB/RUB", + "BNB/TRY", + "BNB/TUSD", + "BNB/USDC", + "BNB/USDS", + "BNB/USDT", ] From d69ef4380b273468b20c652fb5e6f3d489fc4dd6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Feb 2020 13:44:04 +0100 Subject: [PATCH 014/117] Add basic documentation for new-config option --- docs/utils.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/utils.md b/docs/utils.md index 18deeac54..df2a1c3c3 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -36,6 +36,24 @@ optional arguments: └── sample_strategy.py ``` +## Create new config + +Creates a new configuration file, asking some questions which are important selections for a configuration. + + +``` +usage: freqtrade new-config [-h] [-c PATH] + +optional arguments: + -h, --help show this help message and exit + -c PATH, --config PATH + Specify configuration file (default: `config.json`). Multiple --config options may be used. Can be set to `-` + to read config from stdin. +``` + +!!! Warning + Only vital questions are asked. Freqtrade offers a lot more configuration possibilities, which are listed in the [Configuration documentation](configuration.md#configuration-parameters) + ## Create new strategy Creates a new strategy from a template similar to SampleStrategy. From c40a4d77f8972c80be8be311a9dfd183a1222a64 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Feb 2020 13:46:58 +0100 Subject: [PATCH 015/117] Use exchange_mapping to determine correct exchange-template --- freqtrade/commands/build_config_commands.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index 6abacd826..e0910e5b7 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -5,7 +5,7 @@ from typing import Any, Dict from questionary import Separator, prompt from freqtrade.constants import UNLIMITED_STAKE_AMOUNT -from freqtrade.exchange import available_exchanges +from freqtrade.exchange import available_exchanges, MAP_EXCHANGE_CHILDCLASS from freqtrade.misc import render_template from freqtrade.exceptions import OperationalException logger = logging.getLogger(__name__) @@ -141,8 +141,11 @@ def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None: """ from jinja2.exceptions import TemplateNotFound try: + exchange_template = MAP_EXCHANGE_CHILDCLASS.get( + selections['exchange_name'], selections['exchange_name']) + selections['exchange'] = render_template( - templatefile=f"subtemplates/exchange_{selections['exchange_name']}.j2", + templatefile=f"subtemplates/exchange_{exchange_template}.j2", arguments=selections ) except TemplateNotFound: From 54512a66ef187aedcddc048968f822b515fa510d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Feb 2020 13:52:25 +0100 Subject: [PATCH 016/117] Update help-strings for list-utils --- docs/utils.md | 51 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index df2a1c3c3..44cbc35d6 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -197,20 +197,31 @@ All exchanges supported by the ccxt library: _1btcxe, acx, adara, allcoin, anxpr Use the `list-timeframes` subcommand to see the list of ticker intervals (timeframes) available for the exchange. ``` -usage: freqtrade list-timeframes [-h] [--exchange EXCHANGE] [-1] +usage: freqtrade list-timeframes [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--exchange EXCHANGE] [-1] optional arguments: - -h, --help show this help message and exit - --exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no - config is provided. - -1, --one-column Print output in one column. + -h, --help show this help message and exit + --exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no config is provided. + -1, --one-column Print output in one column. + +Common arguments: + -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). + --logfile FILE Log to the file specified. Special values are: 'syslog', 'journald'. See the documentation for more details. + -V, --version show program's version number and exit + -c PATH, --config PATH + Specify configuration file (default: `config.json`). Multiple --config options may be used. Can be set to `-` + to read config from stdin. + -d PATH, --datadir PATH + Path to directory with historical backtesting data. + --userdir PATH, --user-data-dir PATH + Path to userdata directory. ``` * Example: see the timeframes for the 'binance' exchange, set in the configuration file: ``` -$ freqtrade -c config_binance.json list-timeframes +$ freqtrade list-timeframes -c config_binance.json ... Timeframes available for the exchange `binance`: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M ``` @@ -234,14 +245,16 @@ You can print info about any pair/market with these subcommands - and you can fi These subcommands have same usage and same set of available options: ``` -usage: freqtrade list-markets [-h] [--exchange EXCHANGE] [--print-list] - [--print-json] [-1] [--print-csv] +usage: freqtrade list-markets [-h] [-v] [--logfile FILE] [-V] [-c PATH] + [-d PATH] [--userdir PATH] [--exchange EXCHANGE] + [--print-list] [--print-json] [-1] [--print-csv] [--base BASE_CURRENCY [BASE_CURRENCY ...]] [--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]] [-a] -usage: freqtrade list-pairs [-h] [--exchange EXCHANGE] [--print-list] - [--print-json] [-1] [--print-csv] +usage: freqtrade list-pairs [-h] [-v] [--logfile FILE] [-V] [-c PATH] + [-d PATH] [--userdir PATH] [--exchange EXCHANGE] + [--print-list] [--print-json] [-1] [--print-csv] [--base BASE_CURRENCY [BASE_CURRENCY ...]] [--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]] [-a] @@ -260,6 +273,22 @@ optional arguments: Specify quote currency(-ies). Space-separated list. -a, --all Print all pairs or market symbols. By default only active ones are shown. + +Common arguments: + -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). + --logfile FILE Log to the file specified. Special values are: + 'syslog', 'journald'. See the documentation for more + details. + -V, --version show program's version number and exit + -c PATH, --config PATH + Specify configuration file (default: `config.json`). + Multiple --config options may be used. Can be set to + `-` to read config from stdin. + -d PATH, --datadir PATH + Path to directory with historical backtesting data. + --userdir PATH, --user-data-dir PATH + Path to userdata directory. + ``` By default, only active pairs/markets are shown. Active pairs/markets are those that can currently be traded @@ -281,7 +310,7 @@ $ freqtrade list-pairs --quote USD --print-json human-readable list with summary: ``` -$ freqtrade -c config_binance.json list-pairs --all --base BTC ETH --quote USDT USD --print-list +$ freqtrade list-pairs -c config_binance.json --all --base BTC ETH --quote USDT USD --print-list ``` * Print all markets on exchange "Kraken", in the tabular format: From 8796ecb2a9565e04c336c6e84ac606413c9c8c4c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Feb 2020 13:54:17 +0100 Subject: [PATCH 017/117] Ad example for new-config with answered questions --- docs/installation.md | 3 ++- docs/utils.md | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/installation.md b/docs/installation.md index cbe000da4..8d3d2c464 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -31,7 +31,7 @@ Freqtrade provides the Linux/MacOS Easy Installation script to install all depen !!! Note Windows installation is explained [here](#windows). -The easiest way to install and run Freqtrade is to clone the bot GitHub repository and then run the Easy Installation script, if it's available for your platform. +The easiest way to install and run Freqtrade is to clone the bot Github repository and then run the Easy Installation script, if it's available for your platform. !!! Note "Version considerations" When cloning the repository the default working branch has the name `develop`. This branch contains all last features (can be considered as relatively stable, thanks to automated tests). The `master` branch contains the code of the last release (done usually once per month on an approximately one week old snapshot of the `develop` branch to prevent packaging bugs, so potentially it's more stable). @@ -47,6 +47,7 @@ cd freqtrade git checkout master # Optional, see (1) ./setup.sh --install ``` + (1) This command switches the cloned repository to the use of the `master` branch. It's not needed if you wish to stay on the `develop` branch. You may later switch between branches at any time with the `git checkout master`/`git checkout develop` commands. ## Easy Installation Script (Linux/MacOS) diff --git a/docs/utils.md b/docs/utils.md index 44cbc35d6..f77d2c428 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -54,6 +54,21 @@ optional arguments: !!! Warning Only vital questions are asked. Freqtrade offers a lot more configuration possibilities, which are listed in the [Configuration documentation](configuration.md#configuration-parameters) +### Create config examples + +``` +$ freqtrade new-config --config config_binance.json + +? Do you want to enable Dry-run (simulated trades)? Yes +? Please insert your stake currency: BTC +? Please insert your stake amount: 0.05 +? Please insert max_open_trades (Integer or 'unlimited'): 5 +? Please insert your ticker interval: 15m +? Please insert your display Currency (for reporting): USD +? Select exchange binance +? Do you want to enable Telegram? No +``` + ## Create new strategy Creates a new strategy from a template similar to SampleStrategy. From 929bbe3058e5682793126781956095207d04e3c0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Feb 2020 14:01:19 +0100 Subject: [PATCH 018/117] Link to docker installation from index.md --- docs/index.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index c88c73619..f0ee831e3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -51,12 +51,15 @@ To run this bot we recommend you a cloud instance with a minimum of: ### Software requirements +- Docker (Recommended) + +Alternatively + - Python 3.6.x - pip (pip3) - git - TA-Lib - virtualenv (Recommended) -- Docker (Recommended) ## Support @@ -67,4 +70,4 @@ Click [here](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODc ## Ready to try? -Begin by reading our installation guide [here](installation). +Begin by reading our installation guide [for docker](docker.md), or for [installation without docker](installation.md). From c224c669784cdf0383fab61380e3b65d8189f95a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Feb 2020 14:06:31 +0100 Subject: [PATCH 019/117] Small edits to install.md --- docs/installation.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/installation.md b/docs/installation.md index 8d3d2c464..054cafe9b 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -130,6 +130,17 @@ bash setup.sh -i #### 1. Install TA-Lib +Use the provided ta-lib installation script + +```bash +sudo ./build_helpers/install_ta-lib.sh +``` + +!!! Note + This will use the ta-lib tar.gz included in this repository. + +##### TA-Lib manual installation + Official webpage: https://mrjbq7.github.io/ta-lib/install.html ```bash @@ -185,7 +196,8 @@ python3 -m pip install -e . # Initialize the user_directory freqtrade create-userdir --userdir user_data/ -cp config.json.example config.json +# Create a new configuration file +freqtrade new-config --config config.json ``` > *To edit the config please refer to [Bot Configuration](configuration.md).* From cfa6a3e3d32d5575299bb346367ec394ff8a1a7c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Feb 2020 14:12:21 +0100 Subject: [PATCH 020/117] Don't overwrite files --- freqtrade/commands/build_config_commands.py | 6 +++++- tests/commands/test_build_config.py | 12 ++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index e0910e5b7..6ba9cf0ac 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -166,7 +166,11 @@ def start_new_config(args: Dict[str, Any]) -> None: Create a new strategy from a template Asking the user questions to fill out the templateaccordingly. """ - selections = ask_user_config() config_path = Path(args['config'][0]) + if config_path.exists(): + raise OperationalException( + f"Configuration `{config_path}` already exists. " + "Please use another configuration name or delete the existing configuration.") + selections = ask_user_config() deploy_new_config(config_path, selections) diff --git a/tests/commands/test_build_config.py b/tests/commands/test_build_config.py index 46e79b357..b0a048c15 100644 --- a/tests/commands/test_build_config.py +++ b/tests/commands/test_build_config.py @@ -6,6 +6,7 @@ import pytest from freqtrade.commands.build_config_commands import (ask_user_config, start_new_config) +from freqtrade.exceptions import OperationalException from tests.conftest import get_args, log_has_re @@ -43,6 +44,17 @@ def test_start_new_config(mocker, caplog, exchange): assert result['ticker_interval'] == '15m' +def test_start_new_config_exists(mocker, caplog): + mocker.patch.object(Path, "exists", MagicMock(return_value=True)) + args = [ + "new-config", + "--config", + "coolconfig.json" + ] + with pytest.raises(OperationalException, match=r"Configuration .* already exists\."): + start_new_config(get_args(args)) + + def test_ask_user_config(): # TODO: Implement me pass From d1a3a2d000f1a2e5600265cc4fcf84287fd0122a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Feb 2020 14:22:40 +0100 Subject: [PATCH 021/117] Add tests for build_config --- freqtrade/commands/build_config_commands.py | 23 ++++++++++++++++++--- tests/commands/test_build_config.py | 12 ++++++++--- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index 6ba9cf0ac..838fd510a 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -27,6 +27,19 @@ def validate_is_float(val): return False +def ask_user_overwrite(config_path: Path) -> bool: + questions = [ + { + "type": "confirm", + "name": "overwrite", + "message": f"File {config_path} already exists. Overwrite?", + "default": False, + }, + ] + answers = prompt(questions) + return answers['overwrite'] + + def ask_user_config() -> Dict[str, Any]: """ Ask user a few questions to build the configuration. @@ -169,8 +182,12 @@ def start_new_config(args: Dict[str, Any]) -> None: config_path = Path(args['config'][0]) if config_path.exists(): - raise OperationalException( - f"Configuration `{config_path}` already exists. " - "Please use another configuration name or delete the existing configuration.") + overwrite = ask_user_overwrite(config_path) + if overwrite: + config_path.unlink() + else: + raise OperationalException( + f"Configuration `{config_path}` already exists. " + "Please use another configuration name or delete the existing configuration.") selections = ask_user_config() deploy_new_config(config_path, selections) diff --git a/tests/commands/test_build_config.py b/tests/commands/test_build_config.py index b0a048c15..8f71c2098 100644 --- a/tests/commands/test_build_config.py +++ b/tests/commands/test_build_config.py @@ -1,8 +1,8 @@ -import json from pathlib import Path from unittest.mock import MagicMock import pytest +import rapidjson from freqtrade.commands.build_config_commands import (ask_user_config, start_new_config) @@ -13,7 +13,10 @@ from tests.conftest import get_args, log_has_re @pytest.mark.parametrize('exchange', ['bittrex', 'binance', 'kraken', 'ftx']) def test_start_new_config(mocker, caplog, exchange): wt_mock = mocker.patch.object(Path, "write_text", MagicMock()) - mocker.patch.object(Path, "exists", MagicMock(return_value=False)) + mocker.patch.object(Path, "exists", MagicMock(return_value=True)) + unlink_mock = mocker.patch.object(Path, "unlink", MagicMock()) + mocker.patch('freqtrade.commands.build_config_commands.ask_user_overwrite', return_value=True) + sample_selections = { 'max_open_trades': 3, 'stake_currency': 'USDT', @@ -39,13 +42,16 @@ def test_start_new_config(mocker, caplog, exchange): assert log_has_re("Writing config to .*", caplog) assert wt_mock.call_count == 1 - result = json.loads(wt_mock.call_args_list[0][0][0]) + assert unlink_mock.call_count == 1 + result = rapidjson.loads(wt_mock.call_args_list[0][0][0], + parse_mode=rapidjson.PM_COMMENTS | rapidjson.PM_TRAILING_COMMAS) assert result['exchange']['name'] == exchange assert result['ticker_interval'] == '15m' def test_start_new_config_exists(mocker, caplog): mocker.patch.object(Path, "exists", MagicMock(return_value=True)) + mocker.patch('freqtrade.commands.build_config_commands.ask_user_overwrite', return_value=False) args = [ "new-config", "--config", From 12317b1c535a4ca6b82441896000527c4f69d8cb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Feb 2020 14:46:43 +0100 Subject: [PATCH 022/117] Add some rudimentary tests for questions --- tests/commands/test_build_config.py | 59 ++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/tests/commands/test_build_config.py b/tests/commands/test_build_config.py index 8f71c2098..d4ebe1de2 100644 --- a/tests/commands/test_build_config.py +++ b/tests/commands/test_build_config.py @@ -5,11 +5,33 @@ import pytest import rapidjson from freqtrade.commands.build_config_commands import (ask_user_config, - start_new_config) + ask_user_overwrite, + start_new_config, + validate_is_float, + validate_is_int) from freqtrade.exceptions import OperationalException from tests.conftest import get_args, log_has_re +def test_validate_is_float(): + assert validate_is_float('2.0') + assert validate_is_float('2.1') + assert validate_is_float('0.1') + assert validate_is_float('-0.5') + assert not validate_is_float('-0.5e') + + +def test_validate_is_int(): + assert validate_is_int('2') + assert validate_is_int('6') + assert validate_is_int('-1') + assert validate_is_int('500') + assert not validate_is_int('2.0') + assert not validate_is_int('2.1') + assert not validate_is_int('-2.1') + assert not validate_is_int('-ee') + + @pytest.mark.parametrize('exchange', ['bittrex', 'binance', 'kraken', 'ftx']) def test_start_new_config(mocker, caplog, exchange): wt_mock = mocker.patch.object(Path, "write_text", MagicMock()) @@ -61,7 +83,34 @@ def test_start_new_config_exists(mocker, caplog): start_new_config(get_args(args)) -def test_ask_user_config(): - # TODO: Implement me - pass - # assert ask_user_config() +def test_ask_user_overwrite(mocker): + """ + Once https://github.com/tmbo/questionary/issues/35 is implemented, improve this test. + """ + prompt_mock = mocker.patch('freqtrade.commands.build_config_commands.prompt', + return_value={'overwrite': False}) + assert not ask_user_overwrite(Path('test.json')) + assert prompt_mock.call_count == 1 + + prompt_mock.reset_mock() + prompt_mock = mocker.patch('freqtrade.commands.build_config_commands.prompt', + return_value={'overwrite': True}) + assert ask_user_overwrite(Path('test.json')) + assert prompt_mock.call_count == 1 + + +def test_ask_user_config(mocker): + """ + Once https://github.com/tmbo/questionary/issues/35 is implemented, improve this test. + """ + prompt_mock = mocker.patch('freqtrade.commands.build_config_commands.prompt', + return_value={'overwrite': False}) + answers = ask_user_config() + assert isinstance(answers, dict) + assert prompt_mock.call_count == 1 + + prompt_mock = mocker.patch('freqtrade.commands.build_config_commands.prompt', + return_value={}) + + with pytest.raises(OperationalException, match=r"User interrupted interactive questions\."): + ask_user_config() From 628b06927c08b043338faa11254305bbce948287 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Feb 2020 14:59:14 +0100 Subject: [PATCH 023/117] Support python3.8 virtualenvs and remove config generation via SED --- setup.sh | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/setup.sh b/setup.sh index fb5102e12..bce2e56cf 100755 --- a/setup.sh +++ b/setup.sh @@ -17,6 +17,14 @@ function check_installed_python() { exit 2 fi + which python3.8 + if [ $? -eq 0 ]; then + echo "using Python 3.8" + PYTHON=python3.8 + check_installed_pip + return + fi + which python3.7 if [ $? -eq 0 ]; then echo "using Python 3.7" @@ -215,27 +223,8 @@ function config_generator() { function config() { echo "-------------------------" - echo "Generating config file" + echo "Please use freqtrade new-config -c config.json to generate a new configuration file." echo "-------------------------" - if [ -f config.json ] - then - read -p "A config file already exist, do you want to override it [y/N]? " - if [[ $REPLY =~ ^[Yy]$ ]] - then - config_generator - else - echo "Configuration of config.json ignored." - fi - else - config_generator - fi - - echo - echo "-------------------------" - echo "Config file generated" - echo "-------------------------" - echo "Edit ./config.json to modify Pair and other configurations." - echo } function install() { From cbd2b265bbb2eea78ad6ffc6b727e1b733729ce5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 1 Feb 2020 15:16:44 +0100 Subject: [PATCH 024/117] Fix small error --- freqtrade/commands/arguments.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 504c6b0b5..c8a038328 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -182,7 +182,7 @@ class Arguments: # add new-config subcommand build_config_cmd = subparsers.add_parser('new-config', - help="Create new config") + help="Create new config") build_config_cmd.set_defaults(func=start_new_config) self._build_args(optionlist=ARGS_BUILD_CONFIG, parser=build_config_cmd) From a1fe3850e29d6cd04bbb4e2df182885dcca601c7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Feb 2020 13:34:04 +0100 Subject: [PATCH 025/117] Improve docker-compose file to be ready to use --- docker-compose.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index cae98c3ee..3a4c4c2db 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,18 @@ version: '3' services: freqtrade: image: freqtradeorg/freqtrade:master + # Build step - only needed when additional dependencies are needed + # build: + # context: . + # dockerfile: "./Dockerfile.technical" + restart: unless-stopped + container_name: freqtrade volumes: - "./user_data:/freqtrade/user_data" - - "./config.json:/freqtrade/config.json" + # Default command used when running `docker compose up` + command: > + trade + --logfile /freqtrade/user_data/freqtrade.log + --db-url sqlite:////freqtrade/user_data/tradesv3.sqlite + --config /freqtrade/user_data/config.json + --strategy SampleStrategy From f508324fc82641f5ccddc46940bff9baeda4f8ac Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Feb 2020 13:38:45 +0100 Subject: [PATCH 026/117] Update docker documentation to be easier to use --- docs/docker.md | 127 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 124 insertions(+), 3 deletions(-) diff --git a/docs/docker.md b/docs/docker.md index d1684abc5..b1eb0b298 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -1,4 +1,4 @@ -# Using FreqTrade with Docker +# Using Freqtrade with Docker ## Install Docker @@ -8,13 +8,134 @@ Start by downloading and installing Docker CE for your platform: * [Windows](https://docs.docker.com/docker-for-windows/install/) * [Linux](https://docs.docker.com/install/) +Optionally, [docker-compose](https://docs.docker.com/compose/install/) should be installed and available to follow the docker quick start guide. + Once you have Docker installed, simply prepare the config file (e.g. `config.json`) and run the image for `freqtrade` as explained below. -## Download the official FreqTrade docker image +## Freqtrade with docker-compose + +Freqtrade provides an official Docker image on [Dockerhub](https://hub.docker.com/r/freqtradeorg/freqtrade/), as well as a [docker-compose file](https://github.com/freqtrade/freqtrade/blob/develop/docker-compose.yml) ready for usage. + +!!! Note + The following section assumes that docker and docker-compose is installed and available to the logged in user. + +!!! Note + All below comands use relative directories and will have to be executed from the directory containing the `docker-compose.yml` file. + +### Docker quick start + +Create a new directory and place the [docker-compose file](https://github.com/freqtrade/freqtrade/blob/develop/docker-compose.yml) in this directory. + +``` bash +mkdir freqtrade +cd freqtrade/ +# Download the docker-compose file from the repository +curl https://raw.githubusercontent.com/freqtrade/freqtrade/develop/docker-compose.yml -o docker-compose.yml + +# Pull the freqtrade image +docker-compose pull + +# Create user directory structure +docker-compose run --rm freqtrade create-userdir --userdir user_data + +# Create configuration - Requires answering interactive questions +docker-compose run --rm freqtrade new-config --config user_data/config.json +``` + +The above snippet will create a directory called "freqtrade" - download the latest compose file and pull the freqtrade image. +The last 2 steps will create the user-directory, as well as a default configuration based on your selections. + +#### Adding your strategy + +The configuration is now available as `user_data/config.json`. +You should now copy your strategy to `user_data/strategies/` - and add the Strategy class name to the `docker-compose.yml` file, replacing `SampleStrategy`. + +Once this is done, you're ready to launch the bot in trading mode. + +``` bash +docker-compose up -d +``` + +#### Docker-compose logs + +Logs will be written to `user_data/freqtrade.log`. +Alternatively, you can check the latest logs using `docker-compose logs -f`. + +#### Database + +The database will be in the user_data directory as well, and will be called `user_data/tradesv3.sqlite`. + +#### Updating freqtrade with docker-compose + +To update freqtrade when using docker-compose is as simple as running the following 2 commands: + +``` bash +# Download the latest image +docker-compose pull +# Restart the image +docker-compose up -d +``` + +This will first pull the latest image, and will then restart the container with the just pulled version. + +!!! Note + You should always check the changelog for breaking changes / manual interventions required and make sure the bot starts correctly after the update. + +#### Going from here + +Advanced users may edit the docker-compose file further to include all possible options or arguments. + +All possible freqtrade arguments will be available by running `docker-compose run --rm freqtrade `. + +!!! Note "`docker-compose run --rm`" + Inluding `--rm` will clean up the container after completion, and is highly recommended for all modes except trading mode (`freqtrade trade`). + +##### Example: Download data with docker-compose + +Downloading backtest data for one pair from binance. The data will be stored in the host directory `user_data/data/`. + +``` bash +docker-compose run --rm freqtrade download-data --pairs ETH/BTC --exchange binance --days 5 -t 1h +``` + +Head over to the [Data Downloading Documentation](data-download.md) for more details on downloading data. + +##### Example: Backtest with docker-compose + +Backtesting in docker-containers: + +``` bash +docker-compose run --rm freqtrade backtesting --config user_data/config.json --strategy SampleStrategy --timerange 20190801-20191001 -i 5m +``` + +Head over to the [Backtesting Documentation](backtesting.md) to learn more. + +#### Additional dependencies with docker-compose + +If your strategy requires dependencies not included in the default image (like [technical](https://github.com/freqtrade/technical)) - it will be necessary to build the image on your host. +For this, please create a Dockerfile containing installation steps for the additional dependencies (have a look at [Dockerfile.technical](https://github.com/freqtrade/freqtrade/blob/develop/Dockerfile.technical) for an example). + +You'll then also need to modify the `docker-compose.yml` file and uncomment the build step, as well as rename the image to avoid naming collisions. + +``` yaml + image: freqtrade_custom + build: + context: . + dockerfile: "./Dockerfile." +``` + +You can then run `docker-compose build` to build the docker image, and run it using the commands described above. + +## Docker - without docker compose + +!!! Warning + The below documentation is provided for completeness and assumes that you are somewhat familiar with running docker containers. If you're just starting out with docker, we recommend to follow the [Freqtrade with docker-compose](#freqtrade-with-docker-compose) instructions. + +### Download the official Freqtrade docker image Pull the image from docker hub. -Branches / tags available can be checked out on [Dockerhub](https://hub.docker.com/r/freqtradeorg/freqtrade/tags/). +Branches / tags available can be checked out on [Dockerhub tags page](https://hub.docker.com/r/freqtradeorg/freqtrade/tags/). ```bash docker pull freqtradeorg/freqtrade:develop From 52f4187129566deeed3121752d34e5131410a87e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Feb 2020 13:51:55 +0100 Subject: [PATCH 027/117] Allow exchange templates to configure outside-options too --- freqtrade/commands/build_config_commands.py | 2 +- freqtrade/templates/base_config.json.j2 | 4 +- .../subtemplates/exchange_binance.j2 | 80 ++++++++++--------- .../subtemplates/exchange_generic.j2 | 24 +++--- .../templates/subtemplates/exchange_kraken.j2 | 67 ++++++++-------- 5 files changed, 91 insertions(+), 86 deletions(-) diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index 838fd510a..7dd1be607 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -90,10 +90,10 @@ def ask_user_config() -> Dict[str, Any]: "name": "exchange_name", "message": "Select exchange", "choices": [ - "bittrex", "binance", "binanceje", "binanceus", + "bittrex", "kraken", Separator(), "other", diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 index 1370bfa80..0a4f92c4b 100644 --- a/freqtrade/templates/base_config.json.j2 +++ b/freqtrade/templates/base_config.json.j2 @@ -27,9 +27,7 @@ "sell_profit_only": false, "ignore_roi_if_buy_signal": false }, - "exchange": { - {{ exchange | indent(8) }} - }, + {{ exchange | indent(4) }}, "pairlists": [ {"method": "StaticPairList"} ], diff --git a/freqtrade/templates/subtemplates/exchange_binance.j2 b/freqtrade/templates/subtemplates/exchange_binance.j2 index c527d296b..03aa0560c 100644 --- a/freqtrade/templates/subtemplates/exchange_binance.j2 +++ b/freqtrade/templates/subtemplates/exchange_binance.j2 @@ -1,39 +1,41 @@ -"name": "{{ exchange_name | lower }}", -"key": "{{ exchange_key }}", -"secret": "{{ exchange_secret }}", -"ccxt_config": {"enableRateLimit": true}, -"ccxt_async_config": { - "enableRateLimit": true, - "rateLimit": 200 -}, -"pair_whitelist": [ - "ALGO/BTC", - "ATOM/BTC", - "BAT/BTC", - "BCH/BTC", - "BRD/BTC", - "EOS/BTC", - "ETH/BTC", - "IOTA/BTC", - "LINK/BTC", - "LTC/BTC", - "NEO/BTC", - "NXS/BTC", - "XMR/BTC", - "XRP/BTC", - "XTZ/BTC" -], -"pair_blacklist": [ - "BNB/BTC", - "BNB/BUSD", - "BNB/ETH", - "BNB/EUR", - "BNB/NGN", - "BNB/PAX", - "BNB/RUB", - "BNB/TRY", - "BNB/TUSD", - "BNB/USDC", - "BNB/USDS", - "BNB/USDT", -] +"exchange": { + "name": "{{ exchange_name | lower }}", + "key": "{{ exchange_key }}", + "secret": "{{ exchange_secret }}", + "ccxt_config": {"enableRateLimit": true}, + "ccxt_async_config": { + "enableRateLimit": true, + "rateLimit": 200 + }, + "pair_whitelist": [ + "ALGO/BTC", + "ATOM/BTC", + "BAT/BTC", + "BCH/BTC", + "BRD/BTC", + "EOS/BTC", + "ETH/BTC", + "IOTA/BTC", + "LINK/BTC", + "LTC/BTC", + "NEO/BTC", + "NXS/BTC", + "XMR/BTC", + "XRP/BTC", + "XTZ/BTC" + ], + "pair_blacklist": [ + "BNB/BTC", + "BNB/BUSD", + "BNB/ETH", + "BNB/EUR", + "BNB/NGN", + "BNB/PAX", + "BNB/RUB", + "BNB/TRY", + "BNB/TUSD", + "BNB/USDC", + "BNB/USDS", + "BNB/USDT", + ] +} diff --git a/freqtrade/templates/subtemplates/exchange_generic.j2 b/freqtrade/templates/subtemplates/exchange_generic.j2 index 33309de3b..ade9c2f28 100644 --- a/freqtrade/templates/subtemplates/exchange_generic.j2 +++ b/freqtrade/templates/subtemplates/exchange_generic.j2 @@ -1,13 +1,15 @@ -"name": "{{ exchange_name | lower }}", -"key": "{{ exchange_key }}", -"secret": "{{ exchange_secret }}", -"ccxt_config": {"enableRateLimit": true}, -"ccxt_async_config": { - "enableRateLimit": true -}, -"pair_whitelist": [ +"exchange": { + "name": "{{ exchange_name | lower }}", + "key": "{{ exchange_key }}", + "secret": "{{ exchange_secret }}", + "ccxt_config": {"enableRateLimit": true}, + "ccxt_async_config": { + "enableRateLimit": true + }, + "pair_whitelist": [ -], -"pair_blacklist": [ + ], + "pair_blacklist": [ -] + ] +} diff --git a/freqtrade/templates/subtemplates/exchange_kraken.j2 b/freqtrade/templates/subtemplates/exchange_kraken.j2 index 690828887..7139a0830 100644 --- a/freqtrade/templates/subtemplates/exchange_kraken.j2 +++ b/freqtrade/templates/subtemplates/exchange_kraken.j2 @@ -1,33 +1,36 @@ -"name": "kraken", -"key": "{{ exchange_key }}", -"secret": "{{ exchange_secret }}", -"ccxt_config": {"enableRateLimit": true}, -"ccxt_async_config": { - "enableRateLimit": true, - "rateLimit": 1000 -}, -"pair_whitelist": [ - "ADA/EUR", - "ATOM/EUR", - "BAT/EUR", - "BCH/EUR", - "BTC/EUR", - "DAI/EUR", - "DASH/EUR", - "EOS/EUR", - "ETC/EUR", - "ETH/EUR", - "LINK/EUR", - "LTC/EUR", - "QTUM/EUR", - "REP/EUR", - "WAVES/EUR", - "XLM/EUR", - "XMR/EUR", - "XRP/EUR", - "XTZ/EUR", - "ZEC/EUR" -], -"pair_blacklist": [ +"download_trades": true, +"exchange": { + "name": "kraken", + "key": "{{ exchange_key }}", + "secret": "{{ exchange_secret }}", + "ccxt_config": {"enableRateLimit": true}, + "ccxt_async_config": { + "enableRateLimit": true, + "rateLimit": 1000 + }, + "pair_whitelist": [ + "ADA/EUR", + "ATOM/EUR", + "BAT/EUR", + "BCH/EUR", + "BTC/EUR", + "DAI/EUR", + "DASH/EUR", + "EOS/EUR", + "ETC/EUR", + "ETH/EUR", + "LINK/EUR", + "LTC/EUR", + "QTUM/EUR", + "REP/EUR", + "WAVES/EUR", + "XLM/EUR", + "XMR/EUR", + "XRP/EUR", + "XTZ/EUR", + "ZEC/EUR" + ], + "pair_blacklist": [ -] + ] +} From 34f04668c19f719f712b34bd556f13c5fb3af1bc Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Feb 2020 14:02:51 +0100 Subject: [PATCH 028/117] Add template for bittrex --- docs/utils.md | 1 - .../subtemplates/exchange_bittrex.j2 | 24 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 freqtrade/templates/subtemplates/exchange_bittrex.j2 diff --git a/docs/utils.md b/docs/utils.md index 66101d9bc..a986f040b 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -40,7 +40,6 @@ optional arguments: Creates a new configuration file, asking some questions which are important selections for a configuration. - ``` usage: freqtrade new-config [-h] [-c PATH] diff --git a/freqtrade/templates/subtemplates/exchange_bittrex.j2 b/freqtrade/templates/subtemplates/exchange_bittrex.j2 new file mode 100644 index 000000000..7a7e8e291 --- /dev/null +++ b/freqtrade/templates/subtemplates/exchange_bittrex.j2 @@ -0,0 +1,24 @@ +"exchange": { + "name": "{{ exchange_name | lower }}", + "key": "{{ exchange_key }}", + "secret": "{{ exchange_secret }}", + "ccxt_config": {"enableRateLimit": true}, + "ccxt_async_config": { + "enableRateLimit": true, + "rateLimit": 500 + }, + "pair_whitelist": [ + "ETH/BTC", + "LTC/BTC", + "ETC/BTC", + "DASH/BTC", + "ZEC/BTC", + "XLM/BTC", + "XRP/BTC", + "TRX/BTC", + "ADA/BTC", + "XMR/BTC" + ], + "pair_blacklist": [ + ] +} From f3b1161640507d91e7694f03e80095304bfdaf0a Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Sat, 8 Feb 2020 21:02:52 +0100 Subject: [PATCH 029/117] wide notifications fixes --- docs/configuration.md | 2 + docs/telegram-usage.md | 2 +- docs/webhook-config.md | 51 ++++++++++++ freqtrade/constants.py | 4 +- freqtrade/freqtradebot.py | 137 ++++++++++++++++++++++++--------- freqtrade/rpc/rpc.py | 14 ++-- freqtrade/rpc/telegram.py | 20 +++-- freqtrade/rpc/webhook.py | 4 + tests/rpc/test_rpc.py | 6 +- tests/rpc/test_rpc_telegram.py | 81 ++++++++++++++++--- tests/rpc/test_rpc_webhook.py | 46 ++++++----- tests/test_freqtradebot.py | 6 +- 12 files changed, 288 insertions(+), 85 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index c0404d647..53e554709 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -92,7 +92,9 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `webhook.enabled` | Enable usage of Webhook notifications
***Datatype:*** *Boolean* | `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
***Datatype:*** *String* | `webhook.webhookbuy` | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
***Datatype:*** *String* +| `webhook.webhookbuycancel` | Payload to send on buy order cancel. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
***Datatype:*** *String* | `webhook.webhooksell` | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
***Datatype:*** *String* +| `webhook.webhooksellcancel` | Payload to send on sell order cancel. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
***Datatype:*** *String* | `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
***Datatype:*** *String* | `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* diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index ed0c21a6e..ac9cea3d6 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -55,7 +55,7 @@ official commands. You can ask at any moment for help with `/help`. | `/reload_conf` | | 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 +| `/status table` | | List all open trades in a table format. Pending buy orders are marked with an asterisk(*) | `/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/docs/webhook-config.md b/docs/webhook-config.md index 9e0a34eae..878b18e8a 100644 --- a/docs/webhook-config.md +++ b/docs/webhook-config.md @@ -15,11 +15,21 @@ Sample configuration (tested using IFTTT). "value2": "limit {limit:8f}", "value3": "{stake_amount:8f} {stake_currency}" }, + "webhookbuycancel": { + "value1": "Cancelling Buy {pair}", + "value2": "limit {limit:8f}", + "value3": "{stake_amount:8f} {stake_currency}" + }, "webhooksell": { "value1": "Selling {pair}", "value2": "limit {limit:8f}", "value3": "profit: {profit_amount:8f} {stake_currency}" }, + "webhooksellcancel": { + "value1": "Cancelling Sell {pair}", + "value2": "limit {limit:8f}", + "value3": "profit: {profit_amount:8f} {stake_currency}" + }, "webhookstatus": { "value1": "Status: {status}", "value2": "", @@ -40,10 +50,29 @@ Possible parameters are: * `exchange` * `pair` * `limit` +* `amount` * `stake_amount` * `stake_currency` * `fiat_currency` * `order_type` +* `open_rate` +* `current_rate` + +### Webhookbuycancel + +The fields in `webhook.webhookbuycancel` are filled when the bot cancels a buy order. Parameters are filled using string.format. +Possible parameters are: + +* `exchange` +* `pair` +* `limit` +* `amount` +* `stake_amount` +* `stake_currency` +* `fiat_currency` +* `order_type` +* `open_rate` +* `current_rate` ### Webhooksell @@ -57,6 +86,7 @@ Possible parameters are: * `amount` * `open_rate` * `current_rate` +* `close_rate` * `profit_amount` * `profit_percent` * `stake_currency` @@ -66,6 +96,27 @@ Possible parameters are: * `open_date` * `close_date` +### Webhooksellcancel + +The fields in `webhook.webhooksellcancel` are filled when the bot cancels a sell order. Parameters are filled using string.format. +Possible parameters are: + +* `exchange` +* `pair` +* `gain` +* `limit` +* `amount` +* `open_rate` +* `current_rate` +* `close_rate` +* `profit_amount` +* `profit_percent` +* `stake_currency` +* `fiat_currency` +* `sell_reason` +* `order_type` +* `open_date` + ### Webhookstatus The fields in `webhook.webhookstatus` are used for regular status messages (Started / Stopped / ...). Parameters are filled using string.format. diff --git a/freqtrade/constants.py b/freqtrade/constants.py index e68e741af..b34805e94 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -78,7 +78,7 @@ CONF_SCHEMA = { 'amend_last_stake_amount': {'type': 'boolean', 'default': False}, 'last_stake_amount_min_ratio': { 'type': 'number', 'minimum': 0.0, 'maximum': 1.0, 'default': 0.5 - }, + }, 'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT}, 'dry_run': {'type': 'boolean'}, 'dry_run_wallet': {'type': 'number', 'default': DRY_RUN_WALLET}, @@ -191,7 +191,9 @@ CONF_SCHEMA = { 'properties': { 'enabled': {'type': 'boolean'}, 'webhookbuy': {'type': 'object'}, + 'webhookbuycancel': {'type': 'object'}, 'webhooksell': {'type': 'object'}, + 'webhooksellcancel': {'type': 'object'}, 'webhookstatus': {'type': 'object'}, }, }, diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index e51b3d550..2f57ca41b 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -234,7 +234,7 @@ class FreqtradeBot: return trades_created - def get_buy_rate(self, pair: str, tick: Dict = None) -> float: + def get_buy_rate(self, pair: str, refresh: bool = False, tick: Dict = None) -> float: """ Calculates bid target between current ask price and last price :return: float: Price @@ -253,7 +253,7 @@ class FreqtradeBot: else: if not tick: logger.info('Using Last Ask / Last Price') - ticker = self.exchange.fetch_ticker(pair) + ticker = self.exchange.fetch_ticker(pair, refresh) else: ticker = tick if ticker['ask'] < ticker['last']: @@ -404,7 +404,7 @@ class FreqtradeBot: stake_amount = self.get_trade_stake_amount(pair) if not stake_amount: - logger.debug("Stake amount is 0, ignoring possible trade for {pair}.") + logger.debug(f"Stake amount is 0, ignoring possible trade for {pair}.") return False logger.info(f"Buy signal found: about create a new trade with stake_amount: " @@ -414,10 +414,12 @@ class FreqtradeBot: if ((bid_check_dom.get('enabled', False)) and (bid_check_dom.get('bids_to_ask_delta', 0) > 0)): if self._check_depth_of_market_buy(pair, bid_check_dom): + logger.info(f'Executed Buy for {pair}.') return self.execute_buy(pair, stake_amount) else: return False + logger.info(f'Executed Buy for {pair}') return self.execute_buy(pair, stake_amount) else: return False @@ -450,7 +452,7 @@ class FreqtradeBot: """ Executes a limit buy for the given pair :param pair: pair for which we want to create a LIMIT_BUY - :return: None + :return: bool """ time_in_force = self.strategy.order_time_in_force['buy'] @@ -458,7 +460,7 @@ class FreqtradeBot: buy_limit_requested = price else: # Calculate price - buy_limit_requested = self.get_buy_rate(pair) + buy_limit_requested = self.get_buy_rate(pair, True) min_stake_amount = self._get_min_pair_stake_amount(pair, buy_limit_requested) if min_stake_amount is not None and min_stake_amount > stake_amount: @@ -547,11 +549,37 @@ class FreqtradeBot: 'type': RPCMessageType.BUY_NOTIFICATION, 'exchange': self.exchange.name.capitalize(), 'pair': trade.pair, - 'limit': trade.open_rate, + 'limit': trade.open_rate_requested, 'order_type': order_type, 'stake_amount': trade.stake_amount, 'stake_currency': self.config['stake_currency'], 'fiat_currency': self.config.get('fiat_display_currency', None), + 'amount': trade.amount, + 'open_date': trade.open_date or datetime.utcnow(), + 'current_rate': trade.open_rate_requested, + } + + # Send the message + self.rpc.send_msg(msg) + + def _notify_buy_cancel(self, trade: Trade, order_type: str) -> None: + """ + Sends rpc notification when a buy cancel occured. + """ + current_rate = self.get_buy_rate(trade.pair, False) + + msg = { + 'type': RPCMessageType.BUY_CANCEL_NOTIFICATION, + 'exchange': self.exchange.name.capitalize(), + 'pair': trade.pair, + 'limit': trade.open_rate_requested, + 'order_type': order_type, + 'stake_amount': trade.stake_amount, + 'stake_currency': self.config['stake_currency'], + 'fiat_currency': self.config.get('fiat_display_currency', None), + 'amount': trade.amount, + 'open_date': trade.open_date, + 'current_rate': current_rate, } # Send the message @@ -587,7 +615,7 @@ class FreqtradeBot: return trades_closed - def get_sell_rate(self, pair: str, refresh: bool) -> float: + def get_sell_rate(self, pair: str, refresh: bool = False) -> float: """ Get sell rate - either using get-ticker bid or first bid based on orderbook The orderbook portion is only used for rpc messaging, which would otherwise fail @@ -751,7 +779,7 @@ class FreqtradeBot: update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60) if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() >= update_beat: # cancelling the current stoploss on exchange first - logger.info('Trailing stoploss: cancelling current stoploss on exchange (id:{%s})' + logger.info('Trailing stoploss: cancelling current stoploss on exchange (id:{%s}) ' 'in order to add another one ...', order['id']) try: self.exchange.cancel_order(order['id'], trade.pair) @@ -777,7 +805,7 @@ class FreqtradeBot: if should_sell.sell_flag: self.execute_sell(trade, sell_rate, should_sell.sell_type) - logger.info('executed sell, reason: %s', should_sell.sell_type) + logger.info(f'Executed Sell for {trade.pair}. Reason: {should_sell.sell_type}') return True return False @@ -820,41 +848,41 @@ class FreqtradeBot: if ((order['side'] == 'buy' and order['status'] == 'canceled') or (self._check_timed_out('buy', order))): - self.handle_timedout_limit_buy(trade, order) self.wallets.update() + order_type = self.strategy.order_types['buy'] + self._notify_buy_cancel(trade, order_type) elif ((order['side'] == 'sell' and order['status'] == 'canceled') or (self._check_timed_out('sell', order))): self.handle_timedout_limit_sell(trade, order) self.wallets.update() + order_type = self.strategy.order_types['sell'] + self._notify_sell_cancel(trade, order_type) - def handle_buy_order_full_cancel(self, trade: Trade, reason: str) -> None: - """Close trade in database and send message""" + def delete_trade(self, trade: Trade) -> None: + """Delete trade in database""" Trade.session.delete(trade) Trade.session.flush() - logger.info('Buy order %s for %s.', reason, trade) - self.rpc.send_msg({ - 'type': RPCMessageType.STATUS_NOTIFICATION, - 'status': f'Unfilled buy order for {trade.pair} {reason}' - }) def handle_timedout_limit_buy(self, trade: Trade, order: Dict) -> bool: """ Buy timeout - cancel order :return: True if order was fully cancelled """ - reason = "cancelled due to timeout" if order['status'] != 'canceled': + reason = "cancelled due to timeout" corder = self.exchange.cancel_order(trade.open_order_id, trade.pair) + logger.info('Buy order %s for %s.', reason, trade) else: # Order was cancelled already, so we can reuse the existing dict corder = order - reason = "canceled on Exchange" + reason = "cancelled on exchange" + logger.info('Buy order %s for %s.', reason, trade) if corder.get('remaining', order['remaining']) == order['amount']: # if trade is not partially completed, just delete the trade - self.handle_buy_order_full_cancel(trade, reason) + self.delete_trade(trade) return True # if trade is partially complete, edit the stake details for the trade @@ -878,10 +906,6 @@ class FreqtradeBot: trade.open_order_id = None logger.info('Partial buy order timeout for %s.', trade) - self.rpc.send_msg({ - 'type': RPCMessageType.STATUS_NOTIFICATION, - 'status': f'Remaining buy order for {trade.pair} cancelled due to timeout' - }) return False def handle_timedout_limit_sell(self, trade: Trade, order: Dict) -> bool: @@ -889,24 +913,22 @@ class FreqtradeBot: Sell timeout - cancel order and update trade :return: True if order was fully cancelled """ + # if trade is not partially completed, just cancel the trade if order['remaining'] == order['amount']: - # if trade is not partially completed, just cancel the trade if order["status"] != "canceled": - reason = "due to timeout" + reason = "cancelled due to timeout" + # if trade is not partially completed, just delete the trade self.exchange.cancel_order(trade.open_order_id, trade.pair) - logger.info('Sell order timeout for %s.', trade) + logger.info('Sell order %s for %s.', reason, trade) else: - reason = "on exchange" - logger.info('Sell order canceled on exchange for %s.', trade) + reason = "cancelled on exchange" + logger.info('Sell order %s for %s.', reason, trade) + trade.close_rate = None trade.close_profit = None trade.close_date = None trade.is_open = True trade.open_order_id = None - self.rpc.send_msg({ - 'type': RPCMessageType.STATUS_NOTIFICATION, - 'status': f'Unfilled sell order for {trade.pair} cancelled {reason}' - }) return True @@ -938,13 +960,13 @@ class FreqtradeBot: raise DependencyException( f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}") - def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType) -> None: + def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType) -> bool: """ Executes a limit sell for the given trade and limit :param trade: Trade instance :param limit: limit rate for the sell order :param sellreason: Reason the sell was triggered - :return: None + :return: bool """ sell_type = 'sell' if sell_reason in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): @@ -965,7 +987,7 @@ class FreqtradeBot: order_type = self.strategy.order_types[sell_type] if sell_reason == SellType.EMERGENCY_SELL: - # Emergencysells (default to market!) + # Emergency sells (default to market!) order_type = self.strategy.order_types.get("emergencysell", "market") amount = self._safe_sell_amount(trade.pair, trade.amount) @@ -990,6 +1012,8 @@ class FreqtradeBot: self._notify_sell(trade, order_type) + return True + def _notify_sell(self, trade: Trade, order_type: str) -> None: """ Sends rpc notification when a sell occured. @@ -1006,7 +1030,7 @@ class FreqtradeBot: 'exchange': trade.exchange.capitalize(), 'pair': trade.pair, 'gain': gain, - 'limit': trade.close_rate_requested, + 'limit': profit_rate, 'order_type': order_type, 'amount': trade.amount, 'open_rate': trade.open_rate, @@ -1017,6 +1041,45 @@ class FreqtradeBot: 'open_date': trade.open_date, 'close_date': trade.close_date or datetime.utcnow(), 'stake_currency': self.config['stake_currency'], + 'fiat_currency': self.config.get('fiat_display_currency', None), + } + + if 'fiat_display_currency' in self.config: + msg.update({ + 'fiat_currency': self.config['fiat_display_currency'], + }) + + # Send the message + self.rpc.send_msg(msg) + + def _notify_sell_cancel(self, trade: Trade, order_type: str) -> None: + """ + Sends rpc notification when a sell cancel occured. + """ + profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested + profit_trade = trade.calc_profit(rate=profit_rate) + # Use cached ticker here - it was updated seconds ago. + current_rate = self.get_sell_rate(trade.pair, False) + profit_percent = trade.calc_profit_ratio(profit_rate) + gain = "profit" if profit_percent > 0 else "loss" + + msg = { + 'type': RPCMessageType.SELL_CANCEL_NOTIFICATION, + 'exchange': trade.exchange.capitalize(), + 'pair': trade.pair, + 'gain': gain, + 'limit': profit_rate, + 'order_type': order_type, + 'amount': trade.amount, + 'open_rate': trade.open_rate, + 'current_rate': current_rate, + 'profit_amount': profit_trade, + 'profit_percent': profit_percent, + 'sell_reason': trade.sell_reason, + 'open_date': trade.open_date, + 'close_date': trade.close_date, + 'stake_currency': self.config['stake_currency'], + 'fiat_currency': self.config.get('fiat_display_currency', None), } if 'fiat_display_currency' in self.config: diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 7f5cfc101..c1efea79e 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -26,7 +26,9 @@ class RPCMessageType(Enum): WARNING_NOTIFICATION = 'warning' CUSTOM_NOTIFICATION = 'custom' BUY_NOTIFICATION = 'buy' + BUY_CANCEL_NOTIFICATION = 'buy_cancel' SELL_NOTIFICATION = 'sell' + SELL_CANCEL_NOTIFICATION = 'sell_cancel' def __repr__(self): return self.value @@ -39,6 +41,7 @@ class RPCException(Exception): raise RPCException('*Status:* `no active trade`') """ + def __init__(self, message: str) -> None: super().__init__(self) self.message = message @@ -157,15 +160,16 @@ class RPC: profit_str = f'{trade_perc:.2f}%' if self._fiat_converter: fiat_profit = self._fiat_converter.convert_amount( - trade_profit, - stake_currency, - fiat_display_currency - ) + trade_profit, + stake_currency, + fiat_display_currency + ) if fiat_profit and not isnan(fiat_profit): profit_str += f" ({fiat_profit:.2f})" trades_list.append([ trade.id, - trade.pair, + trade.pair + ['', '*'][trade.open_order_id is not None + and trade.close_rate_requested is None], shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)), profit_str ]) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index e9ecdcff6..0dd7a8ffd 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -134,13 +134,18 @@ class Telegram(RPC): msg['stake_amount_fiat'] = 0 message = ("*{exchange}:* Buying {pair}\n" - "at rate `{limit:.8f}\n" - "({stake_amount:.6f} {stake_currency}").format(**msg) + "*Amount:* `{amount:.8f}`\n" + "*Open Rate:* `{limit:.8f}`\n" + "*Current Rate:* `{current_rate:.8f}`\n" + "*Total:* `({stake_amount:.6f} {stake_currency}").format(**msg) if msg.get('fiat_currency', None): - message += ",{stake_amount_fiat:.3f} {fiat_currency}".format(**msg) + message += ", {stake_amount_fiat:.3f} {fiat_currency}".format(**msg) message += ")`" + elif msg['type'] == RPCMessageType.BUY_CANCEL_NOTIFICATION: + message = "*{exchange}:* Cancelling Buy {pair}".format(**msg) + elif msg['type'] == RPCMessageType.SELL_NOTIFICATION: msg['amount'] = round(msg['amount'], 8) msg['profit_percent'] = round(msg['profit_percent'] * 100, 2) @@ -149,10 +154,10 @@ class Telegram(RPC): msg['duration_min'] = msg['duration'].total_seconds() / 60 message = ("*{exchange}:* Selling {pair}\n" - "*Rate:* `{limit:.8f}`\n" "*Amount:* `{amount:.8f}`\n" "*Open Rate:* `{open_rate:.8f}`\n" "*Current Rate:* `{current_rate:.8f}`\n" + "*Close Rate:* `{limit:.8f}`\n" "*Sell Reason:* `{sell_reason}`\n" "*Duration:* `{duration} ({duration_min:.1f} min)`\n" "*Profit:* `{profit_percent:.2f}%`").format(**msg) @@ -163,8 +168,11 @@ class Telegram(RPC): and self._fiat_converter): msg['profit_fiat'] = self._fiat_converter.convert_amount( msg['profit_amount'], msg['stake_currency'], msg['fiat_currency']) - message += ('` ({gain}: {profit_amount:.8f} {stake_currency}`' - '` / {profit_fiat:.3f} {fiat_currency})`').format(**msg) + message += (' `({gain}: {profit_amount:.8f} {stake_currency}' + ' / {profit_fiat:.3f} {fiat_currency})`').format(**msg) + + elif msg['type'] == RPCMessageType.SELL_CANCEL_NOTIFICATION: + message = "*{exchange}:* Cancelling Sell {pair}".format(**msg) elif msg['type'] == RPCMessageType.STATUS_NOTIFICATION: message = '*Status:* `{status}`'.format(**msg) diff --git a/freqtrade/rpc/webhook.py b/freqtrade/rpc/webhook.py index 37ca466de..1309663d4 100644 --- a/freqtrade/rpc/webhook.py +++ b/freqtrade/rpc/webhook.py @@ -41,8 +41,12 @@ class Webhook(RPC): if msg['type'] == RPCMessageType.BUY_NOTIFICATION: valuedict = self._config['webhook'].get('webhookbuy', None) + elif msg['type'] == RPCMessageType.BUY_CANCEL_NOTIFICATION: + valuedict = self._config['webhook'].get('webhookbuycancel', None) elif msg['type'] == RPCMessageType.SELL_NOTIFICATION: valuedict = self._config['webhook'].get('webhooksell', None) + elif msg['type'] == RPCMessageType.SELL_CANCEL_NOTIFICATION: + valuedict = self._config['webhook'].get('webhooksellcancel', None) elif msg['type'] in(RPCMessageType.STATUS_NOTIFICATION, RPCMessageType.CUSTOM_NOTIFICATION, RPCMessageType.WARNING_NOTIFICATION): diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 36fce1797..a35bfa0d6 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -122,7 +122,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: assert "Since" in headers assert "Pair" in headers assert 'instantly' == result[0][2] - assert 'ETH/BTC' == result[0][1] + assert 'ETH/BTC' in result[0][1] assert '-0.59%' == result[0][3] # Test with fiatconvert @@ -131,7 +131,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: assert "Since" in headers assert "Pair" in headers assert 'instantly' == result[0][2] - assert 'ETH/BTC' == result[0][1] + assert 'ETH/BTC' in result[0][1] assert '-0.59% (-0.09)' == result[0][3] mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', @@ -140,7 +140,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: rpc._freqtrade.exchange._cached_ticker = {} result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') assert 'instantly' == result[0][2] - assert 'ETH/BTC' == result[0][1] + assert 'ETH/BTC' in result[0][1] assert 'nan%' == result[0][3] diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index ffc29ee12..ae9c0c4dc 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -284,7 +284,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None: fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ') assert int(fields[0]) == 1 - assert fields[1] == 'ETH/BTC' + assert 'ETH/BTC' in fields[1] assert msg_mock.call_count == 1 @@ -1200,12 +1200,35 @@ def test_send_msg_buy_notification(default_conf, mocker) -> None: 'stake_amount': 0.001, 'stake_amount_fiat': 0.0, 'stake_currency': 'BTC', - 'fiat_currency': 'USD' + 'fiat_currency': 'USD', + 'current_rate': 1.099e-05, + 'amount': 1333.3333333333335, + 'open_date': arrow.utcnow().shift(hours=-1) }) assert msg_mock.call_args[0][0] \ == '*Bittrex:* Buying ETH/BTC\n' \ - 'at rate `0.00001099\n' \ - '(0.001000 BTC,0.000 USD)`' + '*Amount:* `1333.33333333`\n' \ + '*Open Rate:* `0.00001099`\n' \ + '*Current Rate:* `0.00001099`\n' \ + '*Total:* `(0.001000 BTC, 0.000 USD)`' + + +def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None: + 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) + telegram.send_msg({ + 'type': RPCMessageType.BUY_CANCEL_NOTIFICATION, + 'exchange': 'Bittrex', + 'pair': 'ETH/BTC', + }) + assert msg_mock.call_args[0][0] \ + == ('*Bittrex:* Cancelling Buy ETH/BTC') def test_send_msg_sell_notification(default_conf, mocker) -> None: @@ -1239,13 +1262,13 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: }) assert msg_mock.call_args[0][0] \ == ('*Binance:* Selling KEY/ETH\n' - '*Rate:* `0.00003201`\n' '*Amount:* `1333.33333333`\n' '*Open Rate:* `0.00007500`\n' '*Current Rate:* `0.00003201`\n' + '*Close Rate:* `0.00003201`\n' '*Sell Reason:* `stop_loss`\n' '*Duration:* `1:00:00 (60.0 min)`\n' - '*Profit:* `-57.41%`` (loss: -0.05746268 ETH`` / -24.812 USD)`') + '*Profit:* `-57.41%` `(loss: -0.05746268 ETH / -24.812 USD)`') msg_mock.reset_mock() telegram.send_msg({ @@ -1267,10 +1290,10 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: }) assert msg_mock.call_args[0][0] \ == ('*Binance:* Selling KEY/ETH\n' - '*Rate:* `0.00003201`\n' '*Amount:* `1333.33333333`\n' '*Open Rate:* `0.00007500`\n' '*Current Rate:* `0.00003201`\n' + '*Close Rate:* `0.00003201`\n' '*Sell Reason:* `stop_loss`\n' '*Duration:* `1 day, 2:30:00 (1590.0 min)`\n' '*Profit:* `-57.41%`') @@ -1278,6 +1301,37 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: telegram._fiat_converter.convert_amount = old_convamount +def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None: + 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) + old_convamount = telegram._fiat_converter.convert_amount + telegram._fiat_converter.convert_amount = lambda a, b, c: -24.812 + telegram.send_msg({ + 'type': RPCMessageType.SELL_CANCEL_NOTIFICATION, + 'exchange': 'Binance', + 'pair': 'KEY/ETH', + }) + assert msg_mock.call_args[0][0] \ + == ('*Binance:* Cancelling Sell KEY/ETH') + + msg_mock.reset_mock() + telegram.send_msg({ + 'type': RPCMessageType.SELL_CANCEL_NOTIFICATION, + 'exchange': 'Binance', + 'pair': 'KEY/ETH', + }) + assert msg_mock.call_args[0][0] \ + == ('*Binance:* Cancelling Sell KEY/ETH') + # Reset singleton function to avoid random breaks + telegram._fiat_converter.convert_amount = old_convamount + + def test_send_msg_status_notification(default_conf, mocker) -> None: msg_mock = MagicMock() mocker.patch.multiple( @@ -1360,12 +1414,17 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None: 'stake_amount': 0.001, 'stake_amount_fiat': 0.0, 'stake_currency': 'BTC', - 'fiat_currency': None + 'fiat_currency': None, + 'current_rate': 1.099e-05, + 'amount': 1333.3333333333335, + 'open_date': arrow.utcnow().shift(hours=-1) }) assert msg_mock.call_args[0][0] \ == '*Bittrex:* Buying ETH/BTC\n' \ - 'at rate `0.00001099\n' \ - '(0.001000 BTC)`' + '*Amount:* `1333.33333333`\n' \ + '*Open Rate:* `0.00001099`\n' \ + '*Current Rate:* `0.00001099`\n' \ + '*Total:* `(0.001000 BTC)`' def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: @@ -1398,10 +1457,10 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: }) assert msg_mock.call_args[0][0] \ == '*Binance:* Selling KEY/ETH\n' \ - '*Rate:* `0.00003201`\n' \ '*Amount:* `1333.33333333`\n' \ '*Open Rate:* `0.00007500`\n' \ '*Current Rate:* `0.00003201`\n' \ + '*Close Rate:* `0.00003201`\n' \ '*Sell Reason:* `stop_loss`\n' \ '*Duration:* `2:35:03 (155.1 min)`\n' \ '*Profit:* `-57.41%`' diff --git a/tests/rpc/test_rpc_webhook.py b/tests/rpc/test_rpc_webhook.py index c066aa8e7..3b9ce3f0d 100644 --- a/tests/rpc/test_rpc_webhook.py +++ b/tests/rpc/test_rpc_webhook.py @@ -13,24 +13,34 @@ from tests.conftest import get_patched_freqtradebot, log_has def get_webhook_dict() -> dict: return { - "enabled": True, - "url": "https://maker.ifttt.com/trigger/freqtrade_test/with/key/c764udvJ5jfSlswVRukZZ2/", - "webhookbuy": { - "value1": "Buying {pair}", - "value2": "limit {limit:8f}", - "value3": "{stake_amount:8f} {stake_currency}" - }, - "webhooksell": { - "value1": "Selling {pair}", - "value2": "limit {limit:8f}", - "value3": "profit: {profit_amount:8f} {stake_currency}" - }, - "webhookstatus": { - "value1": "Status: {status}", - "value2": "", - "value3": "" - } - } + "enabled": True, + "url": "https://maker.ifttt.com/trigger/freqtrade_test/with/key/c764udvJ5jfSlswVRukZZ2/", + "webhookbuy": { + "value1": "Buying {pair}", + "value2": "limit {limit:8f}", + "value3": "{stake_amount:8f} {stake_currency}" + }, + "webhookbuycancel": { + "value1": "Cancelling Buy {pair}", + "value2": "limit {limit:8f}", + "value3": "{stake_amount:8f} {stake_currency}" + }, + "webhooksell": { + "value1": "Selling {pair}", + "value2": "limit {limit:8f}", + "value3": "profit: {profit_amount:8f} {stake_currency}" + }, + "webhooksellcancel": { + "value1": "Cancelling Sell {pair}", + "value2": "limit {limit:8f}", + "value3": "profit: {profit_amount:8f} {stake_currency}" + }, + "webhookstatus": { + "value1": "Status: {status}", + "value2": "", + "value3": "" + } + } def test__init__(mocker, default_conf): diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index f334e4eb0..429d3599d 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -300,7 +300,7 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, caplog, mocker, edge_conf # stoploss shoud be hit assert freqtrade.handle_trade(trade) is True - assert log_has('executed sell, reason: SellType.STOP_LOSS', caplog) + assert log_has('Executed Sell for NEO/BTC. Reason: SellType.STOP_LOSS', caplog) assert trade.sell_reason == SellType.STOP_LOSS.value @@ -1964,7 +1964,7 @@ def test_check_handle_cancelled_buy(default_conf, ticker, limit_buy_order_old, o trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() nb_trades = len(trades) assert nb_trades == 0 - assert log_has_re("Buy order canceled on Exchange for Trade.*", caplog) + assert log_has_re("Buy order cancelled on exchange for Trade.*", caplog) def test_check_handle_timedout_buy_exception(default_conf, ticker, limit_buy_order_old, open_trade, @@ -2045,7 +2045,7 @@ def test_check_handle_cancelled_sell(default_conf, ticker, limit_sell_order_old, assert cancel_order_mock.call_count == 0 assert rpc_mock.call_count == 1 assert open_trade.is_open is True - assert log_has_re("Sell order canceled on exchange for Trade.*", caplog) + assert log_has_re("Sell order cancelled on exchange for Trade.*", caplog) def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old_partial, From 4fad7a462cc067c6c91aaa5b970de881025a9f07 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Sat, 8 Feb 2020 21:19:07 +0100 Subject: [PATCH 030/117] fixes in webhook-config docs --- docs/webhook-config.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/webhook-config.md b/docs/webhook-config.md index 878b18e8a..b287fa71e 100644 --- a/docs/webhook-config.md +++ b/docs/webhook-config.md @@ -51,11 +51,11 @@ Possible parameters are: * `pair` * `limit` * `amount` +* `open_date` * `stake_amount` * `stake_currency` * `fiat_currency` * `order_type` -* `open_rate` * `current_rate` ### Webhookbuycancel @@ -67,11 +67,11 @@ Possible parameters are: * `pair` * `limit` * `amount` +* `open_date` * `stake_amount` * `stake_currency` * `fiat_currency` * `order_type` -* `open_rate` * `current_rate` ### Webhooksell @@ -86,7 +86,6 @@ Possible parameters are: * `amount` * `open_rate` * `current_rate` -* `close_rate` * `profit_amount` * `profit_percent` * `stake_currency` @@ -108,7 +107,6 @@ Possible parameters are: * `amount` * `open_rate` * `current_rate` -* `close_rate` * `profit_amount` * `profit_percent` * `stake_currency` @@ -116,6 +114,7 @@ Possible parameters are: * `sell_reason` * `order_type` * `open_date` +* `close_date` ### Webhookstatus From 879b5138228cc2052ab6bff3fd913080686828e6 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Sat, 8 Feb 2020 21:31:36 +0100 Subject: [PATCH 031/117] enhanced method description --- freqtrade/freqtradebot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 2f57ca41b..5f1024f8c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -452,7 +452,7 @@ class FreqtradeBot: """ Executes a limit buy for the given pair :param pair: pair for which we want to create a LIMIT_BUY - :return: bool + :return: True if a buy order is created, false if it fails. """ time_in_force = self.strategy.order_time_in_force['buy'] @@ -966,7 +966,7 @@ class FreqtradeBot: :param trade: Trade instance :param limit: limit rate for the sell order :param sellreason: Reason the sell was triggered - :return: bool + :return: True if it succeeds (supported) False (not supported) """ sell_type = 'sell' if sell_reason in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): From 636bd5acb5e8b66dab60879c50343a26dc8e15c6 Mon Sep 17 00:00:00 2001 From: Fredrik Rydin Date: Sat, 8 Feb 2020 23:21:42 +0100 Subject: [PATCH 032/117] Added filter options to "hyperopt-list" in order to easier find epochs. --profitable Select only profitable epochs. --min-avg-time INT Select epochs on above average time. --max-avg-time INT Select epochs on under average time. --min-avg-profit FLOAT Select epochs on above average profit. --min-total-profit FLOAT Select epochs on above total profit. --- docs/utils.md | 24 +++++++++--- freqtrade/commands/arguments.py | 3 +- freqtrade/commands/cli_options.py | 24 ++++++++++++ freqtrade/commands/hyperopt_commands.py | 47 ++++++++++++++++++------ freqtrade/configuration/configuration.py | 12 ++++++ 5 files changed, 91 insertions(+), 19 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index b0559f9cc..71039f174 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -135,15 +135,27 @@ Common arguments: ``` ``` freqtrade list-hyperopts --help -usage: freqtrade list-hyperopts [-h] [-v] [--logfile FILE] [-V] [-c PATH] - [-d PATH] [--userdir PATH] - [--hyperopt-path PATH] [-1] +usage: freqtrade hyperopt-list [-h] [-v] [--logfile FILE] [-V] [-c PATH] + [-d PATH] [--userdir PATH] [--best] + [--profitable] [--min-avg-time INT] + [--max-avg-time INT] [--min-avg-profit FLOAT] + [--min-total-profit FLOAT] [--no-color] + [--print-json] [--no-details] optional arguments: -h, --help show this help message and exit - --hyperopt-path PATH Specify additional lookup path for Hyperopt and - Hyperopt Loss functions. - -1, --one-column Print output in one column. + --best Select only best epochs. + --profitable Select only profitable epochs. + --min-avg-time INT Select epochs on above average time. + --max-avg-time INT Select epochs on under average time. + --min-avg-profit FLOAT + Select epochs on above average profit. + --min-total-profit FLOAT + Select epochs on above total profit. + --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. + --no-details Do not print best epoch details. Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 1931a51be..2d02058f1 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -59,7 +59,8 @@ ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit", ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url", "trade_source", "ticker_interval"] -ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable", "print_colorized", +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_min_total_profit", "print_colorized", "print_json", "hyperopt_list_no_details"] ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index", diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 6d8d13129..0c6d64691 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -398,6 +398,30 @@ AVAILABLE_CLI_OPTIONS = { help='Select only best epochs.', action='store_true', ), + "hyperopt_list_min_avg_time": Arg( + '--min-avg-time', + help='Select epochs on above average time.', + type=check_int_nonzero, + metavar='INT', + ), + "hyperopt_list_max_avg_time": Arg( + '--max-avg-time', + help='Select epochs on under average time.', + type=check_int_nonzero, + metavar='INT', + ), + "hyperopt_list_min_avg_profit": Arg( + '--min-avg-profit', + help='Select epochs on above average profit.', + type=float, + metavar='FLOAT', + ), + "hyperopt_list_min_total_profit": Arg( + '--min-total-profit', + help='Select epochs on above total profit.', + 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 5c6f25848..8472fcfe1 100644 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -19,13 +19,20 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) - only_best = config.get('hyperopt_list_best', False) - only_profitable = config.get('hyperopt_list_profitable', False) print_colorized = config.get('print_colorized', False) print_json = config.get('print_json', False) no_details = config.get('hyperopt_list_no_details', False) no_header = False + filteroptions = { + 'only_best': config.get('hyperopt_list_best', False), + 'only_profitable': config.get('hyperopt_list_profitable', False), + 'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', 0), + 'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', 0), + 'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', 0.0), + 'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', 0.0) + } + trials_file = (config['user_data_dir'] / 'hyperopt_results' / 'hyperopt_results.pickle') @@ -33,7 +40,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: trials = Hyperopt.load_previous_results(trials_file) total_epochs = len(trials) - trials = _hyperopt_filter_trials(trials, only_best, only_profitable) + trials = _hyperopt_filter_trials(trials, filteroptions) # TODO: fetch the interval for epochs to print from the cli option epoch_start, epoch_stop = 0, None @@ -44,7 +51,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: try: # Human-friendly indexes used here (starting from 1) for val in trials[epoch_start:epoch_stop]: - Hyperopt.print_results_explanation(val, total_epochs, not only_best, print_colorized) + Hyperopt.print_results_explanation(val, total_epochs, not filteroptions['only_best'], print_colorized) except KeyboardInterrupt: print('User interrupted..') @@ -63,8 +70,14 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) - only_best = config.get('hyperopt_list_best', False) - only_profitable = config.get('hyperopt_list_profitable', False) + filteroptions = { + 'only_best': config.get('hyperopt_list_best', False), + 'only_profitable': config.get('hyperopt_list_profitable', False), + 'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', 0), + 'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', 0), + 'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', 0), + 'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', 0) + } no_header = config.get('hyperopt_show_no_header', False) trials_file = (config['user_data_dir'] / @@ -74,7 +87,7 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: trials = Hyperopt.load_previous_results(trials_file) total_epochs = len(trials) - trials = _hyperopt_filter_trials(trials, only_best, only_profitable) + trials = _hyperopt_filter_trials(trials, filteroptions) trials_epochs = len(trials) n = config.get('hyperopt_show_index', -1) @@ -97,18 +110,28 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: header_str="Epoch details") -def _hyperopt_filter_trials(trials: List, only_best: bool, only_profitable: bool) -> List: +def _hyperopt_filter_trials(trials: List, filteroptions: dict) -> List: """ Filter our items from the list of hyperopt results """ - if only_best: + if filteroptions['only_best']: trials = [x for x in trials if x['is_best']] - if only_profitable: + if filteroptions['only_profitable']: trials = [x for x in trials if x['results_metrics']['profit'] > 0] + if not filteroptions['only_best']: + if filteroptions['filter_min_avg_time'] > 0: + trials = [x for x in trials if x['results_metrics']['duration'] > filteroptions['filter_min_avg_time']] + if filteroptions['filter_max_avg_time'] > 0: + trials = [x for x in trials if x['results_metrics']['duration'] < filteroptions['filter_max_avg_time']] + if filteroptions['filter_min_avg_profit'] > 0: + trials = [x for x in trials if x['results_metrics']['avg_profit'] > filteroptions['filter_min_avg_profit']] + if filteroptions['filter_min_total_profit'] > 0: + trials = [x for x in trials if x['results_metrics']['profit'] > filteroptions['filter_min_total_profit']] + logger.info(f"{len(trials)} " + - ("best " if only_best else "") + - ("profitable " if only_profitable else "") + + ("best " if filteroptions['only_best'] else "") + + ("profitable " if filteroptions['only_profitable'] else "") + "epochs found.") return trials diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index a8b7638c8..f7e87f3a1 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -310,6 +310,18 @@ class Configuration: self._args_to_config(config, argname='hyperopt_list_profitable', logstring='Parameter --profitable detected: {}') + self._args_to_config(config, argname='hyperopt_list_min_avg_time', + logstring='Parameter --min-avg-time detected: {}') + + self._args_to_config(config, argname='hyperopt_list_max_avg_time', + logstring='Parameter --max-avg-time detected: {}') + + self._args_to_config(config, argname='hyperopt_list_min_avg_profit', + logstring='Parameter --min-avg-profit detected: {}') + + self._args_to_config(config, argname='hyperopt_list_min_total_profit', + logstring='Parameter --min-total-profit detected: {}') + self._args_to_config(config, argname='hyperopt_list_no_details', logstring='Parameter --no-details detected: {}') From 2796d3d8a0c1af2c297170f030cc5cfbb4b9c924 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Sun, 9 Feb 2020 00:11:58 +0100 Subject: [PATCH 033/117] added missing tests to increase coverage --- tests/rpc/test_rpc_webhook.py | 53 +++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/tests/rpc/test_rpc_webhook.py b/tests/rpc/test_rpc_webhook.py index 3b9ce3f0d..ab40047c0 100644 --- a/tests/rpc/test_rpc_webhook.py +++ b/tests/rpc/test_rpc_webhook.py @@ -54,6 +54,9 @@ def test_send_msg(default_conf, mocker): msg_mock = MagicMock() mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock) webhook = Webhook(get_patched_freqtradebot(mocker, default_conf)) + # Test buy + msg_mock = MagicMock() + mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock) msg = { 'type': RPCMessageType.BUY_NOTIFICATION, 'exchange': 'Bittrex', @@ -64,8 +67,6 @@ def test_send_msg(default_conf, mocker): 'stake_currency': 'BTC', 'fiat_currency': 'EUR' } - msg_mock = MagicMock() - mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock) webhook.send_msg(msg=msg) assert msg_mock.call_count == 1 assert (msg_mock.call_args[0][0]["value1"] == @@ -74,6 +75,27 @@ def test_send_msg(default_conf, mocker): default_conf["webhook"]["webhookbuy"]["value2"].format(**msg)) assert (msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["webhookbuy"]["value3"].format(**msg)) + # Test buy cancel + msg_mock = MagicMock() + mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock) + msg = { + 'type': RPCMessageType.BUY_CANCEL_NOTIFICATION, + 'exchange': 'Bittrex', + 'pair': 'ETH/BTC', + 'limit': 0.005, + 'stake_amount': 0.8, + 'stake_amount_fiat': 500, + 'stake_currency': 'BTC', + 'fiat_currency': 'EUR' + } + webhook.send_msg(msg=msg) + assert msg_mock.call_count == 1 + assert (msg_mock.call_args[0][0]["value1"] == + default_conf["webhook"]["webhookbuycancel"]["value1"].format(**msg)) + assert (msg_mock.call_args[0][0]["value2"] == + default_conf["webhook"]["webhookbuycancel"]["value2"].format(**msg)) + assert (msg_mock.call_args[0][0]["value3"] == + default_conf["webhook"]["webhookbuycancel"]["value3"].format(**msg)) # Test sell msg_mock = MagicMock() mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock) @@ -100,7 +122,32 @@ def test_send_msg(default_conf, mocker): default_conf["webhook"]["webhooksell"]["value2"].format(**msg)) assert (msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["webhooksell"]["value3"].format(**msg)) - + # Test sell cancel + msg_mock = MagicMock() + mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock) + msg = { + 'type': RPCMessageType.SELL_CANCEL_NOTIFICATION, + 'exchange': 'Bittrex', + 'pair': 'ETH/BTC', + 'gain': "profit", + 'limit': 0.005, + 'amount': 0.8, + 'order_type': 'limit', + 'open_rate': 0.004, + 'current_rate': 0.005, + 'profit_amount': 0.001, + 'profit_percent': 0.20, + 'stake_currency': 'BTC', + 'sell_reason': SellType.STOP_LOSS.value + } + webhook.send_msg(msg=msg) + assert msg_mock.call_count == 1 + assert (msg_mock.call_args[0][0]["value1"] == + default_conf["webhook"]["webhooksellcancel"]["value1"].format(**msg)) + assert (msg_mock.call_args[0][0]["value2"] == + default_conf["webhook"]["webhooksellcancel"]["value2"].format(**msg)) + assert (msg_mock.call_args[0][0]["value3"] == + default_conf["webhook"]["webhooksellcancel"]["value3"].format(**msg)) for msgtype in [RPCMessageType.STATUS_NOTIFICATION, RPCMessageType.WARNING_NOTIFICATION, RPCMessageType.CUSTOM_NOTIFICATION]: From c96acd6ca02b6e0dc5cd1e28c78ad0b3be648fe1 Mon Sep 17 00:00:00 2001 From: Fredrik Rydin Date: Sun, 9 Feb 2020 00:16:11 +0100 Subject: [PATCH 034/117] Fixed to pass PEP8 --- freqtrade/commands/arguments.py | 7 ++++--- freqtrade/commands/hyperopt_commands.py | 28 ++++++++++++++++++------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 2d02058f1..6d0c16d88 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -59,9 +59,10 @@ ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit", ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url", "trade_source", "ticker_interval"] -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_min_total_profit", "print_colorized", - "print_json", "hyperopt_list_no_details"] +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_min_total_profit", + "print_colorized", "print_json", "hyperopt_list_no_details"] ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index", "print_json", "hyperopt_show_no_header"] diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index 8472fcfe1..f5fcc971f 100644 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -32,7 +32,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: 'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', 0.0), 'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', 0.0) } - + trials_file = (config['user_data_dir'] / 'hyperopt_results' / 'hyperopt_results.pickle') @@ -51,7 +51,8 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: try: # Human-friendly indexes used here (starting from 1) for val in trials[epoch_start:epoch_stop]: - Hyperopt.print_results_explanation(val, total_epochs, not filteroptions['only_best'], print_colorized) + Hyperopt.print_results_explanation(val, total_epochs, + not filteroptions['only_best'], print_colorized) except KeyboardInterrupt: print('User interrupted..') @@ -121,14 +122,27 @@ def _hyperopt_filter_trials(trials: List, filteroptions: dict) -> List: if not filteroptions['only_best']: if filteroptions['filter_min_avg_time'] > 0: - trials = [x for x in trials if x['results_metrics']['duration'] > filteroptions['filter_min_avg_time']] + trials = [ + x for x in trials + if x['results_metrics']['duration'] > filteroptions['filter_min_avg_time'] + ] if filteroptions['filter_max_avg_time'] > 0: - trials = [x for x in trials if x['results_metrics']['duration'] < filteroptions['filter_max_avg_time']] + trials = [ + x for x in trials + if x['results_metrics']['duration'] < filteroptions['filter_max_avg_time'] + ] if filteroptions['filter_min_avg_profit'] > 0: - trials = [x for x in trials if x['results_metrics']['avg_profit'] > filteroptions['filter_min_avg_profit']] + trials = [ + x for x in trials + if x['results_metrics']['avg_profit'] + > filteroptions['filter_min_avg_profit'] + ] if filteroptions['filter_min_total_profit'] > 0: - trials = [x for x in trials if x['results_metrics']['profit'] > filteroptions['filter_min_total_profit']] - + trials = [ + x for x in trials + if x['results_metrics']['profit'] > filteroptions['filter_min_total_profit'] + ] + logger.info(f"{len(trials)} " + ("best " if filteroptions['only_best'] else "") + ("profitable " if filteroptions['only_profitable'] else "") + From b536d501945c502ecc6003f7fea8ffc781f45f02 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 9 Feb 2020 11:41:29 +0100 Subject: [PATCH 035/117] Address PR Review --- freqtrade/commands/build_config_commands.py | 4 ++-- freqtrade/templates/base_config.json.j2 | 2 +- setup.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index 7dd1be607..1598fa2ae 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -187,7 +187,7 @@ def start_new_config(args: Dict[str, Any]) -> None: config_path.unlink() else: raise OperationalException( - f"Configuration `{config_path}` already exists. " - "Please use another configuration name or delete the existing configuration.") + f"Configuration file `{config_path}` already exists. " + "Please delete it or use a different configuration file name.") selections = ask_user_config() deploy_new_config(config_path, selections) diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 index 0a4f92c4b..88edeb1e8 100644 --- a/freqtrade/templates/base_config.json.j2 +++ b/freqtrade/templates/base_config.json.j2 @@ -19,7 +19,7 @@ "bids_to_ask_delta": 1 } }, - "ask_strategy":{ + "ask_strategy": { "use_order_book": false, "order_book_min": 1, "order_book_max": 9, diff --git a/setup.sh b/setup.sh index bce2e56cf..e120190ce 100755 --- a/setup.sh +++ b/setup.sh @@ -223,7 +223,7 @@ function config_generator() { function config() { echo "-------------------------" - echo "Please use freqtrade new-config -c config.json to generate a new configuration file." + echo "Please use 'freqtrade new-config -c config.json' to generate a new configuration file." echo "-------------------------" } From c7ba85c2e6c244f861a9e914782d5d5d14f30837 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Sun, 9 Feb 2020 14:19:13 +0300 Subject: [PATCH 036/117] Add tip on running order types for Bittrex --- docs/faq.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index 2416beae4..390b35b9b 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -45,12 +45,24 @@ the tutorial [here|Testing-new-strategies-with-Hyperopt](bot-usage.md#hyperopt-c You can use the `/forcesell all` command from Telegram. -### I get the message "RESTRICTED_MARKET" +### I'm getting the "RESTRICTED_MARKET" message in the log Currently known to happen for US Bittrex users. Read [the Bittrex section about restricted markets](exchanges.md#restricted-markets) for more information. +### I'm getting the "Exchange Bittrex does not support market orders." message and cannot run my strategy + +As the message says, Bittrex does not support market orders and you have one of the [order types](configuration.md/#understand-order_types) set to "market". Probably your strategy was written for another exchanges in mind and sets "market" orders for "stoploss" orders, which is correct and preferable for most of other exchanges. + +To fix it for Bittrex, redefine order types in the configuration file (do this for all order types that are defined as "market" in your strategy): + +``` +"order_types": { + "stoploss": "limit", +} +``` + ### How do I search the bot logs for something? By default, the bot writes its log into stderr stream. This is implemented this way so that you can easily separate the bot's diagnostics messages from Backtesting, Edge and Hyperopt results, output from other various Freqtrade utility subcommands, as well as from the output of your custom `print()`'s you may have inserted into your strategy. So if you need to search the log messages with the grep utility, you need to redirect stderr to stdout and disregard stdout. From c648ec7c0c76e09d57bee0e52bf820bb0d9adf01 Mon Sep 17 00:00:00 2001 From: Fredrik Rydin Date: Sun, 9 Feb 2020 14:18:56 +0100 Subject: [PATCH 037/117] Added test cases and fixed a minor bug --- freqtrade/commands/hyperopt_commands.py | 6 +++ tests/commands/test_commands.py | 59 ++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index f5fcc971f..38e1fa429 100644 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -120,24 +120,30 @@ def _hyperopt_filter_trials(trials: List, filteroptions: dict) -> List: if filteroptions['only_profitable']: trials = [x for x in trials if x['results_metrics']['profit'] > 0] + print(trials[0]) + if not filteroptions['only_best']: if filteroptions['filter_min_avg_time'] > 0: + trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] trials = [ x for x in trials if x['results_metrics']['duration'] > filteroptions['filter_min_avg_time'] ] if filteroptions['filter_max_avg_time'] > 0: + trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] trials = [ x for x in trials if x['results_metrics']['duration'] < filteroptions['filter_max_avg_time'] ] if filteroptions['filter_min_avg_profit'] > 0: + trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] trials = [ x for x in trials if x['results_metrics']['avg_profit'] > filteroptions['filter_min_avg_profit'] ] if filteroptions['filter_min_total_profit'] > 0: + trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] trials = [ x for x in trials if x['results_metrics']['profit'] > filteroptions['filter_min_total_profit'] diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index c59799190..fb15c3d7f 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -778,7 +778,64 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results): assert all(x not in captured.out for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", " 11/12", " 12/12"]) - + args = [ + "hyperopt-list", + "--profitable", + "--no-details", + "--min-avg-profit", "0.11" + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 2/12"]) + assert all(x not in captured.out + for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", + " 10/12", " 11/12", " 12/12"]) + args = [ + "hyperopt-list", + "--no-details", + "--min-total-profit", "0.4" + ] + 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", + "--profitable", + "--no-details", + "--min-avg-time", "2000" + ] + 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-avg-time", "1500" + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 2/12", " 6/12"]) + assert all(x not in captured.out + for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 7/12", " 8/12" + " 9/12", " 10/12", " 11/12", " 12/12"]) def test_hyperopt_show(mocker, capsys, hyperopt_results): mocker.patch( From eb3783dc0095740ccf973cbec351a67951cfcda5 Mon Sep 17 00:00:00 2001 From: Fredrik Rydin Date: Sun, 9 Feb 2020 14:30:29 +0100 Subject: [PATCH 038/117] Fixed a blank line issue :-( --- tests/commands/test_commands.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index fb15c3d7f..db8a9289a 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -837,6 +837,7 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results): for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 7/12", " 8/12" " 9/12", " 10/12", " 11/12", " 12/12"]) + def test_hyperopt_show(mocker, capsys, hyperopt_results): mocker.patch( 'freqtrade.optimize.hyperopt.Hyperopt.load_previous_results', From c89a32224c2f77fa14aa2244dfcd23ab9f7ea56d Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Sun, 9 Feb 2020 18:40:19 +0300 Subject: [PATCH 039/117] Fix SharpeHyperOptLossDaily --- freqtrade/optimize/hyperopt_loss_sharpe_daily.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt_loss_sharpe_daily.py b/freqtrade/optimize/hyperopt_loss_sharpe_daily.py index d8ea3c5fe..5a8ebaa11 100644 --- a/freqtrade/optimize/hyperopt_loss_sharpe_daily.py +++ b/freqtrade/optimize/hyperopt_loss_sharpe_daily.py @@ -39,7 +39,8 @@ class SharpeHyperOptLossDaily(IHyperOptLoss): results['profit_percent'] - slippage_per_trade_ratio # create the index within the min_date and end max_date - t_index = date_range(start=min_date, end=max_date, freq=resample_freq) + t_index = date_range(start=min_date, end=max_date, freq=resample_freq, + normalize=True) sum_daily = ( results.resample(resample_freq, on='close_time').agg( From 40abdd26083f20e6fb05a1facae694b91724ba85 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Sun, 9 Feb 2020 18:54:04 +0300 Subject: [PATCH 040/117] Suggest changing strategy --- docs/faq.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 390b35b9b..81fd47561 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -55,14 +55,18 @@ Read [the Bittrex section about restricted markets](exchanges.md#restricted-mark As the message says, Bittrex does not support market orders and you have one of the [order types](configuration.md/#understand-order_types) set to "market". Probably your strategy was written for another exchanges in mind and sets "market" orders for "stoploss" orders, which is correct and preferable for most of other exchanges. -To fix it for Bittrex, redefine order types in the configuration file (do this for all order types that are defined as "market" in your strategy): +To fix it for Bittrex, redefine order types in the strategy to use "limit" instead of "market": ``` -"order_types": { - "stoploss": "limit", -} + order_types = { + ... + 'stoploss': 'limit', + ... + } ``` +Same fix should be done in the configuration file, if order types are defined in your custom config rather than in the strategy. + ### How do I search the bot logs for something? By default, the bot writes its log into stderr stream. This is implemented this way so that you can easily separate the bot's diagnostics messages from Backtesting, Edge and Hyperopt results, output from other various Freqtrade utility subcommands, as well as from the output of your custom `print()`'s you may have inserted into your strategy. So if you need to search the log messages with the grep utility, you need to redirect stderr to stdout and disregard stdout. From c83da7cadb4db3c0c7d59d4e787d80e7c876e79c Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Sun, 9 Feb 2020 19:11:06 +0300 Subject: [PATCH 041/117] Add section about order types into Bittrex Exchange-specific chapter --- docs/exchanges.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/exchanges.md b/docs/exchanges.md index 3c861ce44..06d33d562 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -32,6 +32,10 @@ To download data for the Kraken exchange, using `--dl-trades` is mandatory, othe ## Bittrex +### Order types + +Bittrex does not support market orders. If you have a message at the bot startup about this, you should change order type values set in your configuration and/or in the strategy from `"market"` to `"limit"`. See some more details on this [here in the FAQ](faw.md#im-getting-the-exchange-bittrex-does-not-support-market-orders-message-and-cannot-run-my-strategy). + ### Restricted markets Bittrex split its exchange into US and International versions. From cc3f65d069ddaa08ffa81e76a155f471adc37f9b Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Sun, 9 Feb 2020 19:45:04 +0300 Subject: [PATCH 042/117] Fix typo --- docs/exchanges.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index 06d33d562..f615bc61a 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -34,7 +34,7 @@ To download data for the Kraken exchange, using `--dl-trades` is mandatory, othe ### Order types -Bittrex does not support market orders. If you have a message at the bot startup about this, you should change order type values set in your configuration and/or in the strategy from `"market"` to `"limit"`. See some more details on this [here in the FAQ](faw.md#im-getting-the-exchange-bittrex-does-not-support-market-orders-message-and-cannot-run-my-strategy). +Bittrex does not support market orders. If you have a message at the bot startup about this, you should change order type values set in your configuration and/or in the strategy from `"market"` to `"limit"`. See some more details on this [here in the FAQ](faq.md#im-getting-the-exchange-bittrex-does-not-support-market-orders-message-and-cannot-run-my-strategy). ### Restricted markets From 5bf4c5869b81c39176efc7fe6705cebb02a7f489 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Sun, 9 Feb 2020 19:32:09 +0100 Subject: [PATCH 043/117] Update hyperopt_commands.py Missed a debug print --- freqtrade/commands/hyperopt_commands.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index 38e1fa429..cdfdb5ca6 100644 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -120,8 +120,6 @@ def _hyperopt_filter_trials(trials: List, filteroptions: dict) -> List: if filteroptions['only_profitable']: trials = [x for x in trials if x['results_metrics']['profit'] > 0] - print(trials[0]) - if not filteroptions['only_best']: if filteroptions['filter_min_avg_time'] > 0: trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] From f7c74e551fa4ff0674576de73a7892fdd32d1bfb Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Sun, 9 Feb 2020 21:56:59 +0300 Subject: [PATCH 044/117] Fix wording --- docs/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index 81fd47561..94818964b 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -53,7 +53,7 @@ Read [the Bittrex section about restricted markets](exchanges.md#restricted-mark ### I'm getting the "Exchange Bittrex does not support market orders." message and cannot run my strategy -As the message says, Bittrex does not support market orders and you have one of the [order types](configuration.md/#understand-order_types) set to "market". Probably your strategy was written for another exchanges in mind and sets "market" orders for "stoploss" orders, which is correct and preferable for most of other exchanges. +As the message says, Bittrex does not support market orders and you have one of the [order types](configuration.md/#understand-order_types) set to "market". Probably your strategy was written with other exchanges in mind and sets "market" orders for "stoploss" orders, which is correct and preferable for most of the exchanges supporting market orders (but not for Bittrex). To fix it for Bittrex, redefine order types in the strategy to use "limit" instead of "market": From 4af25ec315c95a9335285d9375b544f0f23b1a46 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Mon, 10 Feb 2020 05:52:07 +0300 Subject: [PATCH 045/117] Adjust mypy and flake commands --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a087103c6..a4a1a29f8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,7 +48,7 @@ pytest tests/test_.py::test_ #### Run Flake8 ```bash -flake8 freqtrade +flake8 freqtrade tests ``` We receive a lot of code that fails the `flake8` checks. @@ -61,7 +61,7 @@ Guide for installing them is [here](http://flake8.pycqa.org/en/latest/user/using #### Run mypy ``` bash -mypy freqtrade +mypy freqtrade tests ``` ## (Core)-Committer Guide From 90ee82ac437cbe1711d70d3663e986a8acce2fe8 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2020 08:01:42 +0000 Subject: [PATCH 046/117] Bump ccxt from 1.22.30 to 1.22.39 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.22.30 to 1.22.39. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/1.22.30...1.22.39) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 466880950..e1ae4a5bd 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.22.30 +ccxt==1.22.39 SQLAlchemy==1.3.13 python-telegram-bot==12.3.0 arrow==0.15.5 From 88f2ad1eae978f9e95e4b20c5c262dc5f97fb298 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2020 08:02:07 +0000 Subject: [PATCH 047/117] Bump pandas from 1.0.0 to 1.0.1 Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.0.0 to 1.0.1. - [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.0...v1.0.1) Signed-off-by: dependabot-preview[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 21be02a87..68024f587 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,4 @@ -r requirements-common.txt numpy==1.18.1 -pandas==1.0.0 +pandas==1.0.1 From 6b4094fd92866065ca0717f330e64dc29c5305b1 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2020 08:02:45 +0000 Subject: [PATCH 048/117] Bump mkdocs-material from 4.6.0 to 4.6.2 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 4.6.0 to 4.6.2. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/4.6.0...4.6.2) Signed-off-by: dependabot-preview[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 3e53c15e3..3980ecd64 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,2 +1,2 @@ -mkdocs-material==4.6.0 +mkdocs-material==4.6.2 mdx_truly_sane_lists==1.2 From 550f9fc8915841eef544bb51302d4c10553e2794 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2020 08:45:27 +0000 Subject: [PATCH 049/117] Bump python-telegram-bot from 12.3.0 to 12.4.1 Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 12.3.0 to 12.4.1. - [Release notes](https://github.com/python-telegram-bot/python-telegram-bot/releases) - [Changelog](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/CHANGES.rst) - [Commits](https://github.com/python-telegram-bot/python-telegram-bot/compare/v12.3.0...v12.4.1) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index e1ae4a5bd..f641dd2ad 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -2,7 +2,7 @@ # mainly used for Raspberry pi installs ccxt==1.22.39 SQLAlchemy==1.3.13 -python-telegram-bot==12.3.0 +python-telegram-bot==12.4.1 arrow==0.15.5 cachetools==4.0.0 requests==2.22.0 From 83644ce5d8502ddc99c5d24a46a33750cf7745bf Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 10 Feb 2020 10:35:48 +0100 Subject: [PATCH 050/117] Fix mypy type errors in tests --- tests/data/test_history.py | 6 +++--- tests/optimize/__init__.py | 2 +- tests/optimize/test_backtesting.py | 4 ++-- tests/optimize/test_edge_cli.py | 4 ++-- tests/optimize/test_hyperopt.py | 22 ++++++++++++---------- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/tests/data/test_history.py b/tests/data/test_history.py index 7b3143db9..da4c90191 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -370,7 +370,7 @@ def test_load_partial_missing(testdatadir, caplog) -> None: def test_init(default_conf, mocker) -> None: assert {} == load_data( - datadir='', + datadir=Path(''), pairs=[], timeframe=default_conf['ticker_interval'] ) @@ -379,13 +379,13 @@ def test_init(default_conf, mocker) -> None: def test_init_with_refresh(default_conf, mocker) -> None: exchange = get_patched_exchange(mocker, default_conf) refresh_data( - datadir='', + datadir=Path(''), pairs=[], timeframe=default_conf['ticker_interval'], exchange=exchange ) assert {} == load_data( - datadir='', + datadir=Path(''), pairs=[], timeframe=default_conf['ticker_interval'] ) diff --git a/tests/optimize/__init__.py b/tests/optimize/__init__.py index 8756143a0..524db093e 100644 --- a/tests/optimize/__init__.py +++ b/tests/optimize/__init__.py @@ -23,7 +23,7 @@ class BTContainer(NamedTuple): """ Minimal BacktestContainer defining Backtest inputs and results. """ - data: List[float] + data: List[List[float]] stop_loss: float roi: Dict[str, float] trades: List[BTrade] diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 07872da57..ec85c8030 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -287,8 +287,8 @@ def test_start(mocker, fee, default_conf, caplog) -> None: '--config', 'config.json', '--strategy', 'DefaultStrategy', ] - args = get_args(args) - start_backtesting(args) + pargs = get_args(args) + start_backtesting(pargs) assert log_has('Starting freqtrade in Backtesting mode', caplog) assert start_mock.call_count == 1 diff --git a/tests/optimize/test_edge_cli.py b/tests/optimize/test_edge_cli.py index 96dd0899d..a5e468542 100644 --- a/tests/optimize/test_edge_cli.py +++ b/tests/optimize/test_edge_cli.py @@ -82,8 +82,8 @@ def test_start(mocker, fee, edge_conf, caplog) -> None: '--config', 'config.json', '--strategy', 'DefaultStrategy', ] - args = get_args(args) - start_edge(args) + pargs = get_args(args) + start_edge(pargs) assert log_has('Starting freqtrade in Edge mode', caplog) assert start_mock.call_count == 1 diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index b3356bd6d..1780b5155 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -2,6 +2,7 @@ import locale from datetime import datetime from pathlib import Path +from typing import Dict, List from unittest.mock import MagicMock, PropertyMock import pandas as pd @@ -9,7 +10,8 @@ import pytest from arrow import Arrow from filelock import Timeout -from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_hyperopt +from freqtrade.commands.optimize_commands import (setup_optimize_configuration, + start_hyperopt) from freqtrade.data.converter import parse_ticker_dataframe from freqtrade.data.history import load_tickerdata_file from freqtrade.exceptions import OperationalException @@ -54,7 +56,7 @@ def hyperopt_results(): # Functions for recurrent object patching -def create_trials(mocker, hyperopt, testdatadir) -> None: +def create_trials(mocker, hyperopt, testdatadir) -> List[Dict]: """ When creating trials, mock the hyperopt Trials so that *by default* - we don't create any pickle'd files in the filesystem @@ -228,10 +230,10 @@ def test_start_not_installed(mocker, default_conf, caplog, import_fails) -> None '--hyperopt', 'DefaultHyperOpt', '--epochs', '5' ] - args = get_args(args) + pargs = get_args(args) with pytest.raises(OperationalException, match=r"Please ensure that the hyperopt dependencies"): - start_hyperopt(args) + start_hyperopt(pargs) def test_start(mocker, default_conf, caplog) -> None: @@ -246,8 +248,8 @@ def test_start(mocker, default_conf, caplog) -> None: '--hyperopt', 'DefaultHyperOpt', '--epochs', '5' ] - args = get_args(args) - start_hyperopt(args) + pargs = get_args(args) + start_hyperopt(pargs) assert log_has('Starting freqtrade in Hyperopt mode', caplog) assert start_mock.call_count == 1 @@ -269,9 +271,9 @@ def test_start_no_data(mocker, default_conf, caplog) -> None: '--hyperopt', 'DefaultHyperOpt', '--epochs', '5' ] - args = get_args(args) + pargs = get_args(args) with pytest.raises(OperationalException, match='No data found. Terminating.'): - start_hyperopt(args) + start_hyperopt(pargs) def test_start_filelock(mocker, default_conf, caplog) -> None: @@ -286,8 +288,8 @@ def test_start_filelock(mocker, default_conf, caplog) -> None: '--hyperopt', 'DefaultHyperOpt', '--epochs', '5' ] - args = get_args(args) - start_hyperopt(args) + pargs = get_args(args) + start_hyperopt(pargs) assert log_has("Another running instance of freqtrade Hyperopt detected.", caplog) From 7bb02d0cc60074fd668a8e498c81f167fa74198e Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 10 Feb 2020 11:01:33 +0100 Subject: [PATCH 051/117] Update docker-docs wording --- docs/docker.md | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/docs/docker.md b/docs/docker.md index b1eb0b298..6267c0cf2 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -27,8 +27,8 @@ Freqtrade provides an official Docker image on [Dockerhub](https://hub.docker.co Create a new directory and place the [docker-compose file](https://github.com/freqtrade/freqtrade/blob/develop/docker-compose.yml) in this directory. ``` bash -mkdir freqtrade -cd freqtrade/ +mkdir ft_userdata +cd ft_userdata/ # Download the docker-compose file from the repository curl https://raw.githubusercontent.com/freqtrade/freqtrade/develop/docker-compose.yml -o docker-compose.yml @@ -42,15 +42,22 @@ docker-compose run --rm freqtrade create-userdir --userdir user_data docker-compose run --rm freqtrade new-config --config user_data/config.json ``` -The above snippet will create a directory called "freqtrade" - download the latest compose file and pull the freqtrade image. -The last 2 steps will create the user-directory, as well as a default configuration based on your selections. +The above snippet creates a new directory called "ft_userdata", downloads the latest compose file and pulls the freqtrade image. +The last 2 steps in the snippet create the directory with user-data, as well as (interactively) the default configuration based on your selections. + +!!! Note + You can edit the configuration at any time, which is available as `user_data/config.json` (within the directory `ft_userdata`) when using the above configuration. #### Adding your strategy The configuration is now available as `user_data/config.json`. -You should now copy your strategy to `user_data/strategies/` - and add the Strategy class name to the `docker-compose.yml` file, replacing `SampleStrategy`. +You should now copy your strategy to `user_data/strategies/` - and add the Strategy class name to the `docker-compose.yml` file, replacing `SampleStrategy`. If you wish to run the bot with the SampleStrategy, just leave it as it is. -Once this is done, you're ready to launch the bot in trading mode. +!!! Warning + The `SampleStrategy` is there for your reference and give you ideas for your own strategy. + Please always backtest the strategy and use dry-run for some time before risking real money! + +Once this is done, you're ready to launch the bot in trading mode (Dry-run or Live-trading, depending on your answer to the corresponding question you made above). ``` bash docker-compose up -d @@ -88,11 +95,11 @@ Advanced users may edit the docker-compose file further to include all possible All possible freqtrade arguments will be available by running `docker-compose run --rm freqtrade `. !!! Note "`docker-compose run --rm`" - Inluding `--rm` will clean up the container after completion, and is highly recommended for all modes except trading mode (`freqtrade trade`). + Including `--rm` will clean up the container after completion, and is highly recommended for all modes except trading mode (running with `freqtrade trade` command). ##### Example: Download data with docker-compose -Downloading backtest data for one pair from binance. The data will be stored in the host directory `user_data/data/`. +Download backtesting data for 5 days for the pair ETH/BTC and 1h timeframe from Binance. The data will be stored in the directory `user_data/data/` on the host. ``` bash docker-compose run --rm freqtrade download-data --pairs ETH/BTC --exchange binance --days 5 -t 1h @@ -102,7 +109,7 @@ Head over to the [Data Downloading Documentation](data-download.md) for more det ##### Example: Backtest with docker-compose -Backtesting in docker-containers: +Run backtesting in docker-containers for SampleStrategy and specified timerange of historical data, on 5m timeframe: ``` bash docker-compose run --rm freqtrade backtesting --config user_data/config.json --strategy SampleStrategy --timerange 20190801-20191001 -i 5m @@ -126,7 +133,7 @@ You'll then also need to modify the `docker-compose.yml` file and uncomment the You can then run `docker-compose build` to build the docker image, and run it using the commands described above. -## Docker - without docker compose +## Freqtrade with docker without docker-compose !!! Warning The below documentation is provided for completeness and assumes that you are somewhat familiar with running docker containers. If you're just starting out with docker, we recommend to follow the [Freqtrade with docker-compose](#freqtrade-with-docker-compose) instructions. From d69ddd2ac37b18251b55f6f6e40995b1fac04402 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Mon, 10 Feb 2020 11:54:12 +0100 Subject: [PATCH 052/117] Apply suggestions from code review Committed 1 code suggestion in code review. Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> --- docs/telegram-usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index ac9cea3d6..c8ded4af5 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -55,7 +55,7 @@ official commands. You can ask at any moment for help with `/help`. | `/reload_conf` | | 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(*) +| `/status table` | | List all open trades in a table format. Pending buy orders are marked with an asterisk (*) | `/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`). From 0ac0ca74b5fe5db2e23d35bef0ad39174aaaabfd Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Mon, 10 Feb 2020 15:41:09 +0300 Subject: [PATCH 053/117] return back hint for running mypy --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a4a1a29f8..d84c743c9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,7 +61,7 @@ Guide for installing them is [here](http://flake8.pycqa.org/en/latest/user/using #### Run mypy ``` bash -mypy freqtrade tests +mypy freqtrade ``` ## (Core)-Committer Guide From d07c69809da128471d664f2e19cc7cb28504ca5b Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Mon, 10 Feb 2020 18:32:41 +0300 Subject: [PATCH 054/117] Fix tests for hyperopt_loss --- tests/optimize/test_hyperopt.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 1780b5155..a4704b793 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -295,9 +295,12 @@ def test_start_filelock(mocker, default_conf, caplog) -> None: def test_loss_calculation_prefer_correct_trade_count(default_conf, hyperopt_results) -> None: hl = HyperOptLossResolver.load_hyperoptloss(default_conf) - correct = hl.hyperopt_loss_function(hyperopt_results, 600) - over = hl.hyperopt_loss_function(hyperopt_results, 600 + 100) - under = hl.hyperopt_loss_function(hyperopt_results, 600 - 100) + correct = hl.hyperopt_loss_function(hyperopt_results, 600, + datetime(2019, 1, 1), datetime(2019, 5, 1)) + over = hl.hyperopt_loss_function(hyperopt_results, 600 + 100, + datetime(2019, 1, 1), datetime(2019, 5, 1)) + under = hl.hyperopt_loss_function(hyperopt_results, 600 - 100, + datetime(2019, 1, 1), datetime(2019, 5, 1)) assert over > correct assert under > correct @@ -307,8 +310,10 @@ def test_loss_calculation_prefer_shorter_trades(default_conf, hyperopt_results) resultsb.loc[1, 'trade_duration'] = 20 hl = HyperOptLossResolver.load_hyperoptloss(default_conf) - longer = hl.hyperopt_loss_function(hyperopt_results, 100) - shorter = hl.hyperopt_loss_function(resultsb, 100) + longer = hl.hyperopt_loss_function(hyperopt_results, 100, + datetime(2019, 1, 1), datetime(2019, 5, 1)) + shorter = hl.hyperopt_loss_function(resultsb, 100, + datetime(2019, 1, 1), datetime(2019, 5, 1)) assert shorter < longer @@ -319,9 +324,12 @@ def test_loss_calculation_has_limited_profit(default_conf, hyperopt_results) -> results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2 hl = HyperOptLossResolver.load_hyperoptloss(default_conf) - correct = hl.hyperopt_loss_function(hyperopt_results, 600) - over = hl.hyperopt_loss_function(results_over, 600) - under = hl.hyperopt_loss_function(results_under, 600) + correct = hl.hyperopt_loss_function(hyperopt_results, 600, + datetime(2019, 1, 1), datetime(2019, 5, 1)) + over = hl.hyperopt_loss_function(results_over, 600, + datetime(2019, 1, 1), datetime(2019, 5, 1)) + under = hl.hyperopt_loss_function(results_under, 600, + datetime(2019, 1, 1), datetime(2019, 5, 1)) assert over < correct assert under > correct From faf19eda86e3728905cf94644d47275af5cabbc5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 10 Feb 2020 17:27:47 +0100 Subject: [PATCH 055/117] Break the old binary file so users are forced to reinstall Note: This should not be relevant anymore - this binary has been deprecated and is not being used by new installations since July 2019. --- bin/freqtrade | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bin/freqtrade b/bin/freqtrade index 25c94fe98..eee7cbef4 100755 --- a/bin/freqtrade +++ b/bin/freqtrade @@ -1,11 +1,11 @@ #!/usr/bin/env python3 import sys -import warnings +import logging -from freqtrade.main import main +logger = logging.getLogger(__name__) -warnings.warn( - "Deprecated - To continue to run the bot like this, please run `pip install -e .` again.", - DeprecationWarning) -main(sys.argv[1:]) + +logger.error("DEPRECATED installation detected, please run `pip install -e .` again.") + +sys.exit(2) From 05128d21a8be65a71d1a8a30e973b3bb26bf3884 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Mon, 10 Feb 2020 20:48:49 +0300 Subject: [PATCH 056/117] Suggest to run flake for scripts --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d84c743c9..1c83437f6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,7 +48,7 @@ pytest tests/test_.py::test_ #### Run Flake8 ```bash -flake8 freqtrade tests +flake8 freqtrade tests scripts ``` We receive a lot of code that fails the `flake8` checks. From c924e4d519253a400218d95b7f17a64da27e5da6 Mon Sep 17 00:00:00 2001 From: Fredrik Rydin Date: Mon, 10 Feb 2020 20:54:31 +0100 Subject: [PATCH 057/117] Updated based on feedback: - Profit commands now use float - Compatible with --best - Corrected wrong information in docs --- docs/utils.md | 134 ++++++++++++++++++------ freqtrade/commands/cli_options.py | 8 +- freqtrade/commands/hyperopt_commands.py | 68 ++++++------ 3 files changed, 140 insertions(+), 70 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index 71039f174..5bb3a0e53 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -36,6 +36,38 @@ optional arguments: └── sample_strategy.py ``` +## Create new config + +Creates a new configuration file, asking some questions which are important selections for a configuration. + +``` +usage: freqtrade new-config [-h] [-c PATH] + +optional arguments: + -h, --help show this help message and exit + -c PATH, --config PATH + Specify configuration file (default: `config.json`). Multiple --config options may be used. Can be set to `-` + to read config from stdin. +``` + +!!! Warning + Only vital questions are asked. Freqtrade offers a lot more configuration possibilities, which are listed in the [Configuration documentation](configuration.md#configuration-parameters) + +### Create config examples + +``` +$ freqtrade new-config --config config_binance.json + +? Do you want to enable Dry-run (simulated trades)? Yes +? Please insert your stake currency: BTC +? Please insert your stake amount: 0.05 +? Please insert max_open_trades (Integer or 'unlimited'): 5 +? Please insert your ticker interval: 15m +? Please insert your display Currency (for reporting): USD +? Select exchange binance +? Do you want to enable Telegram? No +``` + ## Create new strategy Creates a new strategy from a template similar to SampleStrategy. @@ -135,27 +167,15 @@ Common arguments: ``` ``` freqtrade list-hyperopts --help -usage: freqtrade hyperopt-list [-h] [-v] [--logfile FILE] [-V] [-c PATH] - [-d PATH] [--userdir PATH] [--best] - [--profitable] [--min-avg-time INT] - [--max-avg-time INT] [--min-avg-profit FLOAT] - [--min-total-profit FLOAT] [--no-color] - [--print-json] [--no-details] +usage: freqtrade list-hyperopts [-h] [-v] [--logfile FILE] [-V] [-c PATH] + [-d PATH] [--userdir PATH] + [--hyperopt-path PATH] [-1] optional arguments: -h, --help show this help message and exit - --best Select only best epochs. - --profitable Select only profitable epochs. - --min-avg-time INT Select epochs on above average time. - --max-avg-time INT Select epochs on under average time. - --min-avg-profit FLOAT - Select epochs on above average profit. - --min-total-profit FLOAT - Select epochs on above total profit. - --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. - --no-details Do not print best epoch details. + --hyperopt-path PATH Specify additional lookup path for Hyperopt and + Hyperopt Loss functions. + -1, --one-column Print output in one column. Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). @@ -232,20 +252,31 @@ All exchanges supported by the ccxt library: _1btcxe, acx, adara, allcoin, anxpr Use the `list-timeframes` subcommand to see the list of ticker intervals (timeframes) available for the exchange. ``` -usage: freqtrade list-timeframes [-h] [--exchange EXCHANGE] [-1] +usage: freqtrade list-timeframes [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--exchange EXCHANGE] [-1] optional arguments: - -h, --help show this help message and exit - --exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no - config is provided. - -1, --one-column Print output in one column. + -h, --help show this help message and exit + --exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no config is provided. + -1, --one-column Print output in one column. + +Common arguments: + -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). + --logfile FILE Log to the file specified. Special values are: 'syslog', 'journald'. See the documentation for more details. + -V, --version show program's version number and exit + -c PATH, --config PATH + Specify configuration file (default: `config.json`). Multiple --config options may be used. Can be set to `-` + to read config from stdin. + -d PATH, --datadir PATH + Path to directory with historical backtesting data. + --userdir PATH, --user-data-dir PATH + Path to userdata directory. ``` * Example: see the timeframes for the 'binance' exchange, set in the configuration file: ``` -$ freqtrade -c config_binance.json list-timeframes +$ freqtrade list-timeframes -c config_binance.json ... Timeframes available for the exchange `binance`: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M ``` @@ -269,14 +300,16 @@ You can print info about any pair/market with these subcommands - and you can fi These subcommands have same usage and same set of available options: ``` -usage: freqtrade list-markets [-h] [--exchange EXCHANGE] [--print-list] - [--print-json] [-1] [--print-csv] +usage: freqtrade list-markets [-h] [-v] [--logfile FILE] [-V] [-c PATH] + [-d PATH] [--userdir PATH] [--exchange EXCHANGE] + [--print-list] [--print-json] [-1] [--print-csv] [--base BASE_CURRENCY [BASE_CURRENCY ...]] [--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]] [-a] -usage: freqtrade list-pairs [-h] [--exchange EXCHANGE] [--print-list] - [--print-json] [-1] [--print-csv] +usage: freqtrade list-pairs [-h] [-v] [--logfile FILE] [-V] [-c PATH] + [-d PATH] [--userdir PATH] [--exchange EXCHANGE] + [--print-list] [--print-json] [-1] [--print-csv] [--base BASE_CURRENCY [BASE_CURRENCY ...]] [--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]] [-a] @@ -295,6 +328,22 @@ optional arguments: Specify quote currency(-ies). Space-separated list. -a, --all Print all pairs or market symbols. By default only active ones are shown. + +Common arguments: + -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). + --logfile FILE Log to the file specified. Special values are: + 'syslog', 'journald'. See the documentation for more + details. + -V, --version show program's version number and exit + -c PATH, --config PATH + Specify configuration file (default: `config.json`). + Multiple --config options may be used. Can be set to + `-` to read config from stdin. + -d PATH, --datadir PATH + Path to directory with historical backtesting data. + --userdir PATH, --user-data-dir PATH + Path to userdata directory. + ``` By default, only active pairs/markets are shown. Active pairs/markets are those that can currently be traded @@ -316,7 +365,7 @@ $ freqtrade list-pairs --quote USD --print-json human-readable list with summary: ``` -$ freqtrade -c config_binance.json list-pairs --all --base BTC ETH --quote USDT USD --print-list +$ freqtrade list-pairs -c config_binance.json --all --base BTC ETH --quote USDT USD --print-list ``` * Print all markets on exchange "Kraken", in the tabular format: @@ -364,17 +413,40 @@ You can list the hyperoptimization epochs the Hyperopt module evaluated previous ``` usage: freqtrade hyperopt-list [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--best] - [--profitable] [--no-color] [--print-json] - [--no-details] + [--profitable] [--min-avg-time FLOAT] + [--max-avg-time FLOAT] [--min-avg-profit FLOAT] + [--min-total-profit FLOAT] [--no-color] + [--print-json] [--no-details] optional arguments: -h, --help show this help message and exit --best Select only best epochs. --profitable Select only profitable epochs. + --min-avg-time FLOAT Select epochs on above average time. + --max-avg-time FLOAT Select epochs on under average time. + --min-avg-profit FLOAT + Select epochs on above average profit. + --min-total-profit FLOAT + Select epochs on above total profit. --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. --no-details Do not print best epoch details. + +Common arguments: + -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). + --logfile FILE Log to the file specified. Special values are: + 'syslog', 'journald'. See the documentation for more + details. + -V, --version show program's version number and exit + -c PATH, --config PATH + Specify configuration file (default: `config.json`). + Multiple --config options may be used. Can be set to + `-` to read config from stdin. + -d PATH, --datadir PATH + Path to directory with historical backtesting data. + --userdir PATH, --user-data-dir PATH + Path to userdata directory. ``` ### Examples diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 0c6d64691..154404821 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -401,14 +401,14 @@ AVAILABLE_CLI_OPTIONS = { "hyperopt_list_min_avg_time": Arg( '--min-avg-time', help='Select epochs on above average time.', - type=check_int_nonzero, - metavar='INT', + type=float, + metavar='FLOAT', ), "hyperopt_list_max_avg_time": Arg( '--max-avg-time', help='Select epochs on under average time.', - type=check_int_nonzero, - metavar='INT', + type=float, + metavar='FLOAT', ), "hyperopt_list_min_avg_profit": Arg( '--min-avg-profit', diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index cdfdb5ca6..ed0728bf6 100644 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -27,10 +27,10 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: filteroptions = { 'only_best': config.get('hyperopt_list_best', False), 'only_profitable': config.get('hyperopt_list_profitable', False), - 'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', 0), - 'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', 0), - 'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', 0.0), - 'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', 0.0) + 'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', None), + 'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', None), + 'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None), + 'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', None) } trials_file = (config['user_data_dir'] / @@ -74,10 +74,10 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: filteroptions = { 'only_best': config.get('hyperopt_list_best', False), 'only_profitable': config.get('hyperopt_list_profitable', False), - 'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', 0), - 'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', 0), - 'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', 0), - 'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', 0) + 'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', None), + 'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', None), + 'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None), + 'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', None) } no_header = config.get('hyperopt_show_no_header', False) @@ -119,33 +119,31 @@ def _hyperopt_filter_trials(trials: List, filteroptions: dict) -> List: trials = [x for x in trials if x['is_best']] if filteroptions['only_profitable']: trials = [x for x in trials if x['results_metrics']['profit'] > 0] - - if not filteroptions['only_best']: - if filteroptions['filter_min_avg_time'] > 0: - trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] - trials = [ - x for x in trials - if x['results_metrics']['duration'] > filteroptions['filter_min_avg_time'] - ] - if filteroptions['filter_max_avg_time'] > 0: - trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] - trials = [ - x for x in trials - if x['results_metrics']['duration'] < filteroptions['filter_max_avg_time'] - ] - if filteroptions['filter_min_avg_profit'] > 0: - trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] - trials = [ - x for x in trials - if x['results_metrics']['avg_profit'] - > filteroptions['filter_min_avg_profit'] - ] - if filteroptions['filter_min_total_profit'] > 0: - trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] - trials = [ - x for x in trials - if x['results_metrics']['profit'] > filteroptions['filter_min_total_profit'] - ] + if filteroptions['filter_min_avg_time'] is not None: + trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] + trials = [ + x for x in trials + if x['results_metrics']['duration'] > filteroptions['filter_min_avg_time'] + ] + if filteroptions['filter_max_avg_time'] is not None: + trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] + trials = [ + x for x in trials + if x['results_metrics']['duration'] < filteroptions['filter_max_avg_time'] + ] + if filteroptions['filter_min_avg_profit'] is not None: + trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] + trials = [ + x for x in trials + if x['results_metrics']['avg_profit'] + > filteroptions['filter_min_avg_profit'] + ] + if filteroptions['filter_min_total_profit'] is not None: + trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] + trials = [ + x for x in trials + if x['results_metrics']['profit'] > filteroptions['filter_min_total_profit'] + ] logger.info(f"{len(trials)} " + ("best " if filteroptions['only_best'] else "") + From f2520c11e70c3f0717bdd842f7b7cdeb5482ab0a Mon Sep 17 00:00:00 2001 From: Fredrik Rydin Date: Mon, 10 Feb 2020 21:19:25 +0100 Subject: [PATCH 058/117] Used wrong utils.md as base --- docs/utils.md | 83 +++++++-------------------------------------------- 1 file changed, 11 insertions(+), 72 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index 5bb3a0e53..4bb2fdafb 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -36,38 +36,6 @@ optional arguments: └── sample_strategy.py ``` -## Create new config - -Creates a new configuration file, asking some questions which are important selections for a configuration. - -``` -usage: freqtrade new-config [-h] [-c PATH] - -optional arguments: - -h, --help show this help message and exit - -c PATH, --config PATH - Specify configuration file (default: `config.json`). Multiple --config options may be used. Can be set to `-` - to read config from stdin. -``` - -!!! Warning - Only vital questions are asked. Freqtrade offers a lot more configuration possibilities, which are listed in the [Configuration documentation](configuration.md#configuration-parameters) - -### Create config examples - -``` -$ freqtrade new-config --config config_binance.json - -? Do you want to enable Dry-run (simulated trades)? Yes -? Please insert your stake currency: BTC -? Please insert your stake amount: 0.05 -? Please insert max_open_trades (Integer or 'unlimited'): 5 -? Please insert your ticker interval: 15m -? Please insert your display Currency (for reporting): USD -? Select exchange binance -? Do you want to enable Telegram? No -``` - ## Create new strategy Creates a new strategy from a template similar to SampleStrategy. @@ -252,31 +220,20 @@ All exchanges supported by the ccxt library: _1btcxe, acx, adara, allcoin, anxpr Use the `list-timeframes` subcommand to see the list of ticker intervals (timeframes) available for the exchange. ``` -usage: freqtrade list-timeframes [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--exchange EXCHANGE] [-1] +usage: freqtrade list-timeframes [-h] [--exchange EXCHANGE] [-1] optional arguments: - -h, --help show this help message and exit - --exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no config is provided. - -1, --one-column Print output in one column. - -Common arguments: - -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). - --logfile FILE Log to the file specified. Special values are: 'syslog', 'journald'. See the documentation for more details. - -V, --version show program's version number and exit - -c PATH, --config PATH - Specify configuration file (default: `config.json`). Multiple --config options may be used. Can be set to `-` - to read config from stdin. - -d PATH, --datadir PATH - Path to directory with historical backtesting data. - --userdir PATH, --user-data-dir PATH - Path to userdata directory. + -h, --help show this help message and exit + --exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no + config is provided. + -1, --one-column Print output in one column. ``` * Example: see the timeframes for the 'binance' exchange, set in the configuration file: ``` -$ freqtrade list-timeframes -c config_binance.json +$ freqtrade -c config_binance.json list-timeframes ... Timeframes available for the exchange `binance`: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M ``` @@ -300,16 +257,14 @@ You can print info about any pair/market with these subcommands - and you can fi These subcommands have same usage and same set of available options: ``` -usage: freqtrade list-markets [-h] [-v] [--logfile FILE] [-V] [-c PATH] - [-d PATH] [--userdir PATH] [--exchange EXCHANGE] - [--print-list] [--print-json] [-1] [--print-csv] +usage: freqtrade list-markets [-h] [--exchange EXCHANGE] [--print-list] + [--print-json] [-1] [--print-csv] [--base BASE_CURRENCY [BASE_CURRENCY ...]] [--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]] [-a] -usage: freqtrade list-pairs [-h] [-v] [--logfile FILE] [-V] [-c PATH] - [-d PATH] [--userdir PATH] [--exchange EXCHANGE] - [--print-list] [--print-json] [-1] [--print-csv] +usage: freqtrade list-pairs [-h] [--exchange EXCHANGE] [--print-list] + [--print-json] [-1] [--print-csv] [--base BASE_CURRENCY [BASE_CURRENCY ...]] [--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]] [-a] @@ -328,22 +283,6 @@ optional arguments: Specify quote currency(-ies). Space-separated list. -a, --all Print all pairs or market symbols. By default only active ones are shown. - -Common arguments: - -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). - --logfile FILE Log to the file specified. Special values are: - 'syslog', 'journald'. See the documentation for more - details. - -V, --version show program's version number and exit - -c PATH, --config PATH - Specify configuration file (default: `config.json`). - Multiple --config options may be used. Can be set to - `-` to read config from stdin. - -d PATH, --datadir PATH - Path to directory with historical backtesting data. - --userdir PATH, --user-data-dir PATH - Path to userdata directory. - ``` By default, only active pairs/markets are shown. Active pairs/markets are those that can currently be traded @@ -365,7 +304,7 @@ $ freqtrade list-pairs --quote USD --print-json human-readable list with summary: ``` -$ freqtrade list-pairs -c config_binance.json --all --base BTC ETH --quote USDT USD --print-list +$ freqtrade -c config_binance.json list-pairs --all --base BTC ETH --quote USDT USD --print-list ``` * Print all markets on exchange "Kraken", in the tabular format: From 62bcb3d7660ad60f0b9c3f7374878df16117c253 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 11 Feb 2020 03:43:20 +0300 Subject: [PATCH 059/117] Fix tests in test_history.py --- tests/data/test_history.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/data/test_history.py b/tests/data/test_history.py index da4c90191..15f507b90 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -420,7 +420,7 @@ def test_trim_tickerlist(testdatadir) -> None: # Test the pattern ^(\d{8})-$ # This pattern extracts elements from the date to now - timerange = TimeRange('date', None, ticker_list[10][0] / 1000 - 1, None) + timerange = TimeRange('date', None, ticker_list[10][0] / 1000 - 1, 0) ticker = trim_tickerlist(ticker_list, timerange) ticker_len = len(ticker) @@ -430,14 +430,14 @@ def test_trim_tickerlist(testdatadir) -> None: # Test a wrong pattern # This pattern must return the list unchanged - timerange = TimeRange(None, None, None, 5) + timerange = TimeRange(None, None, 0, 5) ticker = trim_tickerlist(ticker_list, timerange) ticker_len = len(ticker) assert ticker_list_len == ticker_len # passing empty list - timerange = TimeRange(None, None, None, 5) + timerange = TimeRange(None, None, 0, 5) ticker = trim_tickerlist([], timerange) assert 0 == len(ticker) assert not ticker From 29f7c5071b2c99536f1499ba420f7c09a7eadcf2 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 11 Feb 2020 04:17:10 +0300 Subject: [PATCH 060/117] Fix usage of an item from BTContainer in tests --- tests/optimize/__init__.py | 4 ++-- tests/optimize/test_backtest_detail.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/optimize/__init__.py b/tests/optimize/__init__.py index 524db093e..13605a38c 100644 --- a/tests/optimize/__init__.py +++ b/tests/optimize/__init__.py @@ -1,4 +1,4 @@ -from typing import Dict, List, NamedTuple +from typing import Dict, List, NamedTuple, Optional import arrow from pandas import DataFrame @@ -30,7 +30,7 @@ class BTContainer(NamedTuple): profit_perc: float trailing_stop: bool = False trailing_only_offset_is_reached: bool = False - trailing_stop_positive: float = None + trailing_stop_positive: Optional[float] = None trailing_stop_positive_offset: float = 0.0 use_sell_signal: bool = False diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index bd2765430..e7bc76c1d 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -364,7 +364,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: default_conf["trailing_stop"] = data.trailing_stop default_conf["trailing_only_offset_is_reached"] = data.trailing_only_offset_is_reached # Only add this to configuration If it's necessary - if data.trailing_stop_positive: + if data.trailing_stop_positive is not None: default_conf["trailing_stop_positive"] = data.trailing_stop_positive default_conf["trailing_stop_positive_offset"] = data.trailing_stop_positive_offset default_conf["ask_strategy"] = {"use_sell_signal": data.use_sell_signal} From f99d1c38298b717a3e1d1299477338e41746c756 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Tue, 11 Feb 2020 15:44:47 +0100 Subject: [PATCH 061/117] fixed open_rate instead of open_rate_requested --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5f1024f8c..aa617a386 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -549,7 +549,7 @@ class FreqtradeBot: 'type': RPCMessageType.BUY_NOTIFICATION, 'exchange': self.exchange.name.capitalize(), 'pair': trade.pair, - 'limit': trade.open_rate_requested, + 'limit': trade.open_rate, 'order_type': order_type, 'stake_amount': trade.stake_amount, 'stake_currency': self.config['stake_currency'], From 7f4b90c68f9fd1a116ba3179b335add64c66c3cd Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Tue, 11 Feb 2020 15:45:35 +0100 Subject: [PATCH 062/117] fixed actual open_rate in notify_buy_cancel --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index aa617a386..6d1122aa1 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -572,7 +572,7 @@ class FreqtradeBot: 'type': RPCMessageType.BUY_CANCEL_NOTIFICATION, 'exchange': self.exchange.name.capitalize(), 'pair': trade.pair, - 'limit': trade.open_rate_requested, + 'limit': trade.open_rate, 'order_type': order_type, 'stake_amount': trade.stake_amount, 'stake_currency': self.config['stake_currency'], From 867b736b8477507f933ce8b9768a433de5721615 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Tue, 11 Feb 2020 15:49:57 +0100 Subject: [PATCH 063/117] Fixed to Executing Buys & Sells --- freqtrade/freqtradebot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 6d1122aa1..85ddb0da1 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -414,7 +414,7 @@ class FreqtradeBot: if ((bid_check_dom.get('enabled', False)) and (bid_check_dom.get('bids_to_ask_delta', 0) > 0)): if self._check_depth_of_market_buy(pair, bid_check_dom): - logger.info(f'Executed Buy for {pair}.') + logger.info(f'Executing Buy for {pair}.') return self.execute_buy(pair, stake_amount) else: return False @@ -804,8 +804,8 @@ class FreqtradeBot: ) if should_sell.sell_flag: + logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}') self.execute_sell(trade, sell_rate, should_sell.sell_type) - logger.info(f'Executed Sell for {trade.pair}. Reason: {should_sell.sell_type}') return True return False From fc29564974d77e54599bf759dd1d1ed4d2df884a Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Tue, 11 Feb 2020 15:58:40 +0100 Subject: [PATCH 064/117] Fixed messages and readability --- docs/webhook-config.md | 4 ++-- freqtrade/rpc/rpc.py | 4 ++-- freqtrade/rpc/telegram.py | 4 ++-- tests/rpc/test_rpc_telegram.py | 6 +++--- tests/rpc/test_rpc_webhook.py | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/webhook-config.md b/docs/webhook-config.md index b287fa71e..e53aa8af5 100644 --- a/docs/webhook-config.md +++ b/docs/webhook-config.md @@ -16,7 +16,7 @@ Sample configuration (tested using IFTTT). "value3": "{stake_amount:8f} {stake_currency}" }, "webhookbuycancel": { - "value1": "Cancelling Buy {pair}", + "value1": "Cancelling Open Buy Order for {pair}", "value2": "limit {limit:8f}", "value3": "{stake_amount:8f} {stake_currency}" }, @@ -26,7 +26,7 @@ Sample configuration (tested using IFTTT). "value3": "profit: {profit_amount:8f} {stake_currency}" }, "webhooksellcancel": { - "value1": "Cancelling Sell {pair}", + "value1": "Cancelling Open Sell Order for {pair}", "value2": "limit {limit:8f}", "value3": "profit: {profit_amount:8f} {stake_currency}" }, diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index c1efea79e..07631f258 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -168,8 +168,8 @@ class RPC: profit_str += f" ({fiat_profit:.2f})" trades_list.append([ trade.id, - trade.pair + ['', '*'][trade.open_order_id is not None - and trade.close_rate_requested is None], + trade.pair + '*' if (trade.open_order_id is not None + and trade.close_rate_requested is None) else '', shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)), profit_str ]) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 0dd7a8ffd..e3d4f54e7 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -144,7 +144,7 @@ class Telegram(RPC): message += ")`" elif msg['type'] == RPCMessageType.BUY_CANCEL_NOTIFICATION: - message = "*{exchange}:* Cancelling Buy {pair}".format(**msg) + message = "*{exchange}:* Cancelling Open Buy Order for {pair}".format(**msg) elif msg['type'] == RPCMessageType.SELL_NOTIFICATION: msg['amount'] = round(msg['amount'], 8) @@ -172,7 +172,7 @@ class Telegram(RPC): ' / {profit_fiat:.3f} {fiat_currency})`').format(**msg) elif msg['type'] == RPCMessageType.SELL_CANCEL_NOTIFICATION: - message = "*{exchange}:* Cancelling Sell {pair}".format(**msg) + message = "*{exchange}:* Cancelling Open Sell Order for {pair}".format(**msg) elif msg['type'] == RPCMessageType.STATUS_NOTIFICATION: message = '*Status:* `{status}`'.format(**msg) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index ae9c0c4dc..a8b8e0c5a 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1228,7 +1228,7 @@ def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None: 'pair': 'ETH/BTC', }) assert msg_mock.call_args[0][0] \ - == ('*Bittrex:* Cancelling Buy ETH/BTC') + == ('*Bittrex:* Cancelling Open Buy Order for ETH/BTC') def test_send_msg_sell_notification(default_conf, mocker) -> None: @@ -1318,7 +1318,7 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None: 'pair': 'KEY/ETH', }) assert msg_mock.call_args[0][0] \ - == ('*Binance:* Cancelling Sell KEY/ETH') + == ('*Binance:* Cancelling Open Sell Order for KEY/ETH') msg_mock.reset_mock() telegram.send_msg({ @@ -1327,7 +1327,7 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None: 'pair': 'KEY/ETH', }) assert msg_mock.call_args[0][0] \ - == ('*Binance:* Cancelling Sell KEY/ETH') + == ('*Binance:* Cancelling Open Sell Order for KEY/ETH') # Reset singleton function to avoid random breaks telegram._fiat_converter.convert_amount = old_convamount diff --git a/tests/rpc/test_rpc_webhook.py b/tests/rpc/test_rpc_webhook.py index ab40047c0..3f3f36766 100644 --- a/tests/rpc/test_rpc_webhook.py +++ b/tests/rpc/test_rpc_webhook.py @@ -21,7 +21,7 @@ def get_webhook_dict() -> dict: "value3": "{stake_amount:8f} {stake_currency}" }, "webhookbuycancel": { - "value1": "Cancelling Buy {pair}", + "value1": "Cancelling Open Buy Order for {pair}", "value2": "limit {limit:8f}", "value3": "{stake_amount:8f} {stake_currency}" }, @@ -31,7 +31,7 @@ def get_webhook_dict() -> dict: "value3": "profit: {profit_amount:8f} {stake_currency}" }, "webhooksellcancel": { - "value1": "Cancelling Sell {pair}", + "value1": "Cancelling Open Sell Order for {pair}", "value2": "limit {limit:8f}", "value3": "profit: {profit_amount:8f} {stake_currency}" }, From 5b4d8d69ef567ca074351186eebe97e3c9e2fe52 Mon Sep 17 00:00:00 2001 From: Fredrik Rydin Date: Tue, 11 Feb 2020 16:02:08 +0100 Subject: [PATCH 065/117] Adding --min-trades and --max-trades for hyperopt-list --- docs/utils.md | 5 +++- freqtrade/commands/arguments.py | 1 + freqtrade/commands/cli_options.py | 12 ++++++++++ freqtrade/commands/hyperopt_commands.py | 14 ++++++++++++ freqtrade/configuration/configuration.py | 6 +++++ tests/commands/test_commands.py | 29 ++++++++++++++++++++++++ 6 files changed, 66 insertions(+), 1 deletion(-) mode change 100644 => 100755 freqtrade/commands/hyperopt_commands.py diff --git a/docs/utils.md b/docs/utils.md index 5bb3a0e53..91dd6eae0 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -413,7 +413,8 @@ You can list the hyperoptimization epochs the Hyperopt module evaluated previous ``` usage: freqtrade hyperopt-list [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--best] - [--profitable] [--min-avg-time FLOAT] + [--profitable] [--min-trades INT] + [--max-trades INT] [--min-avg-time FLOAT] [--max-avg-time FLOAT] [--min-avg-profit FLOAT] [--min-total-profit FLOAT] [--no-color] [--print-json] [--no-details] @@ -422,6 +423,8 @@ optional arguments: -h, --help show this help message and exit --best Select only best epochs. --profitable Select only profitable epochs. + --min-trades INT Select epochs with more than INT trades. + --max-trades INT Select epochs with less than INT trades. --min-avg-time FLOAT Select epochs on above average time. --max-avg-time FLOAT Select epochs on under average time. --min-avg-profit FLOAT diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index e5a68389b..1b2c4482e 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -62,6 +62,7 @@ ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url", "trade_source", "ticker_interval"] ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable", + "hyperopt_list_min_trades", "hyperopt_list_max_trades", "hyperopt_list_min_avg_time", "hyperopt_list_max_avg_time", "hyperopt_list_min_avg_profit", "hyperopt_list_min_total_profit", "print_colorized", "print_json", "hyperopt_list_no_details"] diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 154404821..f9351c207 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -398,6 +398,18 @@ AVAILABLE_CLI_OPTIONS = { help='Select only best epochs.', action='store_true', ), + "hyperopt_list_min_trades": Arg( + '--min-trades', + help='Select epochs with more than INT trades.', + type=check_int_nonzero, + metavar='INT', + ), + "hyperopt_list_max_trades": Arg( + '--max-trades', + help='Select epochs with less than INT trades.', + type=check_int_nonzero, + metavar='INT', + ), "hyperopt_list_min_avg_time": Arg( '--min-avg-time', help='Select epochs on above average time.', diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py old mode 100644 new mode 100755 index ed0728bf6..c3baf2406 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -27,6 +27,8 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: filteroptions = { 'only_best': config.get('hyperopt_list_best', False), 'only_profitable': config.get('hyperopt_list_profitable', False), + 'filter_min_trades': config.get('hyperopt_list_min_trades', 0), + 'filter_max_trades': config.get('hyperopt_list_max_trades', 0), 'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', None), 'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', None), 'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None), @@ -74,6 +76,8 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: filteroptions = { 'only_best': config.get('hyperopt_list_best', False), 'only_profitable': config.get('hyperopt_list_profitable', False), + 'filter_min_trades': config.get('hyperopt_list_min_trades', 0), + 'filter_max_trades': config.get('hyperopt_list_max_trades', 0), 'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', None), 'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', None), 'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None), @@ -119,6 +123,16 @@ def _hyperopt_filter_trials(trials: List, filteroptions: dict) -> List: trials = [x for x in trials if x['is_best']] if filteroptions['only_profitable']: trials = [x for x in trials if x['results_metrics']['profit'] > 0] + if filteroptions['filter_min_trades'] > 0: + trials = [ + x for x in trials + if x['results_metrics']['trade_count'] > filteroptions['filter_min_trades'] + ] + if filteroptions['filter_max_trades'] > 0: + trials = [ + x for x in trials + if x['results_metrics']['trade_count'] < filteroptions['filter_max_trades'] + ] if filteroptions['filter_min_avg_time'] is not None: trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] trials = [ diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index f7e87f3a1..41f24e55c 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -310,6 +310,12 @@ class Configuration: self._args_to_config(config, argname='hyperopt_list_profitable', logstring='Parameter --profitable detected: {}') + self._args_to_config(config, argname='hyperopt_list_min_trades', + logstring='Parameter --min-trades detected: {}') + + self._args_to_config(config, argname='hyperopt_list_max_trades', + logstring='Parameter --max-trades detected: {}') + self._args_to_config(config, argname='hyperopt_list_min_avg_time', logstring='Parameter --min-avg-time detected: {}') diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index db8a9289a..e02a721a4 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -773,6 +773,35 @@ 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 [" 2/12", " 10/12"]) + assert all(x not in captured.out + for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", + " 11/12", " 12/12"]) + args = [ + "hyperopt-list", + "--no-details", + "--no-color", + "--min-trades", "20" + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 3/12", " 6/12", " 7/12", " 9/12", " 11/12"]) + assert all(x not in captured.out + for x in [" 1/12", " 2/12", " 4/12", " 5/12", " 8/12", " 10/12", " 12/12"]) + args = [ + "hyperopt-list", + "--profitable", + "--no-details", + "--max-trades", "20" + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() assert all(x in captured.out for x in [" 2/12", " 10/12"]) assert all(x not in captured.out From 4fedf1e564212b43b359a1bc533f0d571b9ce926 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Tue, 11 Feb 2020 16:05:44 +0100 Subject: [PATCH 066/117] default refresh TRUE on get_buy_rate and get_sell_Rate --- freqtrade/freqtradebot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 85ddb0da1..c04e3077e 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -234,7 +234,7 @@ class FreqtradeBot: return trades_created - def get_buy_rate(self, pair: str, refresh: bool = False, tick: Dict = None) -> float: + def get_buy_rate(self, pair: str, refresh: bool = True, tick: Dict = None) -> float: """ Calculates bid target between current ask price and last price :return: float: Price @@ -615,7 +615,7 @@ class FreqtradeBot: return trades_closed - def get_sell_rate(self, pair: str, refresh: bool = False) -> float: + def get_sell_rate(self, pair: str, refresh: bool = True) -> float: """ Get sell rate - either using get-ticker bid or first bid based on orderbook The orderbook portion is only used for rpc messaging, which would otherwise fail From 5f4c209fca844f6ea66f7302c22a64d7375812db Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Tue, 11 Feb 2020 16:14:49 +0100 Subject: [PATCH 067/117] fixed one more occurence of executed buy, and test --- freqtrade/freqtradebot.py | 2 +- tests/test_freqtradebot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index c04e3077e..0d1105b2f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -419,7 +419,7 @@ class FreqtradeBot: else: return False - logger.info(f'Executed Buy for {pair}') + logger.info(f'Executing Buy for {pair}') return self.execute_buy(pair, stake_amount) else: return False diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 429d3599d..18cd81aed 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -300,7 +300,7 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, caplog, mocker, edge_conf # stoploss shoud be hit assert freqtrade.handle_trade(trade) is True - assert log_has('Executed Sell for NEO/BTC. Reason: SellType.STOP_LOSS', caplog) + assert log_has('Executing Sell for NEO/BTC. Reason: SellType.STOP_LOSS', caplog) assert trade.sell_reason == SellType.STOP_LOSS.value From cde1b2b56c7fa6285af72ec3d1758968f4d9b88d Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Tue, 11 Feb 2020 16:28:48 +0100 Subject: [PATCH 068/117] readded rpc status message for partial buys --- freqtrade/freqtradebot.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 0d1105b2f..ffd951ee2 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -906,6 +906,10 @@ class FreqtradeBot: trade.open_order_id = None logger.info('Partial buy order timeout for %s.', trade) + self.rpc.send_msg({ + 'type': RPCMessageType.STATUS_NOTIFICATION, + 'status': f'Remaining buy order for {trade.pair} cancelled due to timeout' + }) return False def handle_timedout_limit_sell(self, trade: Trade, order: Dict) -> bool: From 899de8b27c3f35273f090534b0bb2a9d131270f6 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Tue, 11 Feb 2020 16:50:18 +0100 Subject: [PATCH 069/117] modified tests for double partial call --- tests/test_freqtradebot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 18cd81aed..c0af1f015 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2067,7 +2067,7 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old # note this is for a partially-complete buy order freqtrade.check_handle_timedout() assert cancel_order_mock.call_count == 1 - assert rpc_mock.call_count == 1 + assert rpc_mock.call_count == 2 trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() assert len(trades) == 1 assert trades[0].amount == 23.0 @@ -2101,7 +2101,7 @@ def test_check_handle_timedout_partial_fee(default_conf, ticker, open_trade, cap assert log_has_re(r"Applying fee on amount for Trade.* Order", caplog) assert cancel_order_mock.call_count == 1 - assert rpc_mock.call_count == 1 + assert rpc_mock.call_count == 2 trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() assert len(trades) == 1 # Verify that tradehas been updated @@ -2140,7 +2140,7 @@ def test_check_handle_timedout_partial_except(default_conf, ticker, open_trade, assert log_has_re(r"Could not update trade amount: .*", caplog) assert cancel_order_mock.call_count == 1 - assert rpc_mock.call_count == 1 + assert rpc_mock.call_count == 2 trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() assert len(trades) == 1 # Verify that tradehas been updated From d1c3eabb870fe5a5f2357086459b1a2ca06faaa9 Mon Sep 17 00:00:00 2001 From: Fredrik Rydin Date: Tue, 11 Feb 2020 18:08:30 +0100 Subject: [PATCH 070/117] Changed commands to use "check_int_positive" --- freqtrade/commands/cli_options.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index f9351c207..c3b79ae3a 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -401,13 +401,13 @@ AVAILABLE_CLI_OPTIONS = { "hyperopt_list_min_trades": Arg( '--min-trades', help='Select epochs with more than INT trades.', - type=check_int_nonzero, + type=check_int_positive, metavar='INT', ), "hyperopt_list_max_trades": Arg( '--max-trades', help='Select epochs with less than INT trades.', - type=check_int_nonzero, + type=check_int_positive, metavar='INT', ), "hyperopt_list_min_avg_time": Arg( From c35fe2c386ae84a128e817be79c901a5345538c9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 11 Feb 2020 19:29:43 +0100 Subject: [PATCH 071/117] Add link to quick-start-guide --- docs/docker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docker.md b/docs/docker.md index 6267c0cf2..cd24994bc 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -8,7 +8,7 @@ Start by downloading and installing Docker CE for your platform: * [Windows](https://docs.docker.com/docker-for-windows/install/) * [Linux](https://docs.docker.com/install/) -Optionally, [docker-compose](https://docs.docker.com/compose/install/) should be installed and available to follow the docker quick start guide. +Optionally, [docker-compose](https://docs.docker.com/compose/install/) should be installed and available to follow the [docker quick start guide](#docker-quick-start). Once you have Docker installed, simply prepare the config file (e.g. `config.json`) and run the image for `freqtrade` as explained below. From 7be9f0067e63f7a889e10c096b1f3fd36cbedf81 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 11 Feb 2020 20:45:53 +0100 Subject: [PATCH 072/117] Update data-analysis documentation to properly initialize configuration --- docs/strategy_analysis_example.md | 27 +++++++++---------- .../templates/strategy_analysis_example.ipynb | 27 +++++++++---------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md index cc6b9805f..2d77edaed 100644 --- a/docs/strategy_analysis_example.md +++ b/docs/strategy_analysis_example.md @@ -7,18 +7,19 @@ Debugging a strategy can be time-consuming. FreqTrade offers helper functions to ```python from pathlib import Path +from freqtrade.configuration import Configuration + # Customize these according to your needs. +# Initialize empty configuration object +config = Configuration.from_files([]) +# Optionally, Use existing configuration file +# config = Configuration.from_files(["config.json"]) + # Define some constants -timeframe = "5m" +config["ticker_interval"] = "5m" # Name of the strategy class -strategy_name = 'SampleStrategy' -# Path to user data -user_data_dir = Path('user_data') -# Location of the strategy -strategy_location = user_data_dir / 'strategies' -# Location of the data -data_location = Path(user_data_dir, 'data', 'binance') +config["strategy"] = "SampleStrategy" # Pair to analyze - Only use one pair here pair = "BTC_USDT" ``` @@ -28,8 +29,8 @@ pair = "BTC_USDT" # Load data using values set above from freqtrade.data.history import load_pair_history -candles = load_pair_history(datadir=data_location, - timeframe=timeframe, +candles = load_pair_history(datadir=config["data_dir"], + timeframe=config["ticker_interval"], pair=pair) # Confirm success @@ -44,9 +45,7 @@ candles.head() ```python # Load strategy using values set above from freqtrade.resolvers import StrategyResolver -strategy = StrategyResolver.load_strategy({'strategy': strategy_name, - 'user_data_dir': user_data_dir, - 'strategy_path': strategy_location}) +strategy = StrategyResolver.load_strategy(config) # Generate buy/sell signals using strategy df = strategy.analyze_ticker(candles, {'pair': pair}) @@ -86,7 +85,7 @@ Analyze a trades dataframe (also used below for plotting) from freqtrade.data.btanalysis import load_backtest_data # Load backtest results -trades = load_backtest_data(user_data_dir / "backtest_results/backtest-result.json") +trades = load_backtest_data(config["user_data_dir"] / "backtest_results/backtest-result.json") # Show value-counts per pair trades.groupby("pair")["sell_reason"].value_counts() diff --git a/freqtrade/templates/strategy_analysis_example.ipynb b/freqtrade/templates/strategy_analysis_example.ipynb index eea8fb85f..06fc3f557 100644 --- a/freqtrade/templates/strategy_analysis_example.ipynb +++ b/freqtrade/templates/strategy_analysis_example.ipynb @@ -23,18 +23,19 @@ "outputs": [], "source": [ "from pathlib import Path\n", + "from freqtrade.configuration import Configuration\n", + "\n", "# Customize these according to your needs.\n", "\n", + "# Initialize empty configuration object\n", + "config = Configuration.from_files([])\n", + "# Optionally, Use existing configuration file\n", + "# config = Configuration.from_files([\"config.json\"])\n", + "\n", "# Define some constants\n", - "timeframe = \"5m\"\n", + "config[\"ticker_interval\"] = \"5m\"\n", "# Name of the strategy class\n", - "strategy_name = 'SampleStrategy'\n", - "# Path to user data\n", - "user_data_dir = Path('user_data')\n", - "# Location of the strategy\n", - "strategy_location = user_data_dir / 'strategies'\n", - "# Location of the data\n", - "data_location = Path(user_data_dir, 'data', 'binance')\n", + "config[\"strategy\"] = \"SampleStrategy\"\n", "# Pair to analyze - Only use one pair here\n", "pair = \"BTC_USDT\"" ] @@ -48,8 +49,8 @@ "# Load data using values set above\n", "from freqtrade.data.history import load_pair_history\n", "\n", - "candles = load_pair_history(datadir=data_location,\n", - " timeframe=timeframe,\n", + "candles = load_pair_history(datadir=config[\"data_dir\"],\n", + " timeframe=config[\"ticker_interval\"],\n", " pair=pair)\n", "\n", "# Confirm success\n", @@ -73,9 +74,7 @@ "source": [ "# Load strategy using values set above\n", "from freqtrade.resolvers import StrategyResolver\n", - "strategy = StrategyResolver.load_strategy({'strategy': strategy_name,\n", - " 'user_data_dir': user_data_dir,\n", - " 'strategy_path': strategy_location})\n", + "strategy = StrategyResolver.load_strategy(config)\n", "\n", "# Generate buy/sell signals using strategy\n", "df = strategy.analyze_ticker(candles, {'pair': pair})\n", @@ -137,7 +136,7 @@ "from freqtrade.data.btanalysis import load_backtest_data\n", "\n", "# Load backtest results\n", - "trades = load_backtest_data(user_data_dir / \"backtest_results/backtest-result.json\")\n", + "trades = load_backtest_data(config[\"user_data_dir\"] / \"backtest_results/backtest-result.json\")\n", "\n", "# Show value-counts per pair\n", "trades.groupby(\"pair\")[\"sell_reason\"].value_counts()" From 539343b20d7304e3f746c3ac7868bb584f3e1539 Mon Sep 17 00:00:00 2001 From: Fredrik Rydin Date: Tue, 11 Feb 2020 21:29:55 +0100 Subject: [PATCH 073/117] Adding 2 more filter options for completeness --- docs/utils.md | 8 ++++++- freqtrade/commands/arguments.py | 3 ++- freqtrade/commands/cli_options.py | 12 ++++++++++ freqtrade/commands/hyperopt_commands.py | 21 ++++++++++++++++-- freqtrade/configuration/configuration.py | 6 +++++ tests/commands/test_commands.py | 28 ++++++++++++++++++++++++ 6 files changed, 74 insertions(+), 4 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index 91dd6eae0..abb7fd0db 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -416,7 +416,9 @@ usage: freqtrade hyperopt-list [-h] [-v] [--logfile FILE] [-V] [-c PATH] [--profitable] [--min-trades INT] [--max-trades INT] [--min-avg-time FLOAT] [--max-avg-time FLOAT] [--min-avg-profit FLOAT] - [--min-total-profit FLOAT] [--no-color] + [--max-avg-profit FLOAT] + [--min-total-profit FLOAT] + [--max-total-profit FLOAT] [--no-color] [--print-json] [--no-details] optional arguments: @@ -429,8 +431,12 @@ optional arguments: --max-avg-time FLOAT Select epochs on under average time. --min-avg-profit FLOAT Select epochs on above average profit. + --max-avg-profit FLOAT + Select epochs on below average profit. --min-total-profit FLOAT Select epochs on above total profit. + --max-total-profit FLOAT + Select epochs on below total profit. --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 1b2c4482e..fe6f49039 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -64,7 +64,8 @@ ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url", ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_list_min_trades", "hyperopt_list_max_trades", "hyperopt_list_min_avg_time", "hyperopt_list_max_avg_time", - "hyperopt_list_min_avg_profit", "hyperopt_list_min_total_profit", + "hyperopt_list_min_avg_profit", "hyperopt_list_max_avg_profit", + "hyperopt_list_min_total_profit", "hyperopt_list_max_total_profit", "print_colorized", "print_json", "hyperopt_list_no_details"] ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index", diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index c3b79ae3a..1776955b1 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -428,12 +428,24 @@ AVAILABLE_CLI_OPTIONS = { type=float, metavar='FLOAT', ), + "hyperopt_list_max_avg_profit": Arg( + '--max-avg-profit', + help='Select epochs on below average profit.', + type=float, + metavar='FLOAT', + ), "hyperopt_list_min_total_profit": Arg( '--min-total-profit', help='Select epochs on above total profit.', type=float, metavar='FLOAT', ), + "hyperopt_list_max_total_profit": Arg( + '--max-total-profit', + help='Select epochs on below total profit.', + 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 c3baf2406..8c1c80d98 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -32,7 +32,9 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: 'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', None), 'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', None), 'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None), - 'filter_min_total_profit': config.get('hyperopt_list_min_total_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) } trials_file = (config['user_data_dir'] / @@ -81,7 +83,9 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: 'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', None), 'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', None), 'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None), - 'filter_min_total_profit': config.get('hyperopt_list_min_total_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) } no_header = config.get('hyperopt_show_no_header', False) @@ -152,12 +156,25 @@ def _hyperopt_filter_trials(trials: List, filteroptions: dict) -> List: if x['results_metrics']['avg_profit'] > filteroptions['filter_min_avg_profit'] ] + if filteroptions['filter_max_avg_profit'] is not None: + trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] + trials = [ + x for x in trials + if x['results_metrics']['avg_profit'] + < filteroptions['filter_max_avg_profit'] + ] if filteroptions['filter_min_total_profit'] is not None: trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] trials = [ x for x in trials if x['results_metrics']['profit'] > filteroptions['filter_min_total_profit'] ] + if filteroptions['filter_max_total_profit'] is not None: + trials = [x for x in trials if x['results_metrics']['trade_count'] > 0] + trials = [ + x for x in trials + if x['results_metrics']['profit'] < filteroptions['filter_max_total_profit'] + ] logger.info(f"{len(trials)} " + ("best " if filteroptions['only_best'] else "") + diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 41f24e55c..c2613ba99 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -325,9 +325,15 @@ class Configuration: self._args_to_config(config, argname='hyperopt_list_min_avg_profit', logstring='Parameter --min-avg-profit detected: {}') + self._args_to_config(config, argname='hyperopt_list_max_avg_profit', + logstring='Parameter --max-avg-profit detected: {}') + self._args_to_config(config, argname='hyperopt_list_min_total_profit', logstring='Parameter --min-total-profit detected: {}') + 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_no_details', logstring='Parameter --no-details detected: {}') diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index e02a721a4..ee1db5db5 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -822,6 +822,20 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results): assert all(x not in captured.out for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", " 10/12", " 11/12", " 12/12"]) + args = [ + "hyperopt-list", + "--no-details", + "--max-avg-profit", "0.10" + ] + 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", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", + " 11/12"]) + assert all(x not in captured.out + for x in [" 2/12", " 4/12", " 10/12", " 12/12"]) args = [ "hyperopt-list", "--no-details", @@ -836,6 +850,20 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results): 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-total-profit", "0.4" + ] + 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"]) + assert all(x not in captured.out + for x in [" 4/12", " 10/12", " 12/12"]) args = [ "hyperopt-list", "--profitable", From 4f3376e2a189618f649e8f5d91d3ae753b1ae730 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 12 Feb 2020 01:39:15 +0300 Subject: [PATCH 074/117] Do not instantiate directly DefaultStrategy in tests --- tests/data/test_history.py | 14 ++++++++++---- tests/optimize/test_backtesting.py | 6 ++++-- tests/strategy/test_interface.py | 18 ++++++++++++------ tests/test_plotting.py | 14 +++++++++----- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/tests/data/test_history.py b/tests/data/test_history.py index 15f507b90..cf0901587 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -24,7 +24,7 @@ from freqtrade.data.history import (_download_pair_history, validate_backtest_data) from freqtrade.exchange import timeframe_to_minutes from freqtrade.misc import file_dump_json -from freqtrade.strategy.default_strategy import DefaultStrategy +from freqtrade.resolvers import StrategyResolver from tests.conftest import (get_patched_exchange, log_has, log_has_re, patch_exchange) @@ -509,7 +509,9 @@ def test_file_dump_json_tofile(testdatadir) -> None: def test_get_timerange(default_conf, mocker, testdatadir) -> None: patch_exchange(mocker) - strategy = DefaultStrategy(default_conf) + + default_conf.update({'strategy': 'DefaultStrategy'}) + strategy = StrategyResolver.load_strategy(default_conf) data = strategy.tickerdata_to_dataframe( load_data( @@ -525,7 +527,9 @@ def test_get_timerange(default_conf, mocker, testdatadir) -> None: def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) -> None: patch_exchange(mocker) - strategy = DefaultStrategy(default_conf) + + default_conf.update({'strategy': 'DefaultStrategy'}) + strategy = StrategyResolver.load_strategy(default_conf) data = strategy.tickerdata_to_dataframe( load_data( @@ -547,7 +551,9 @@ def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> None: patch_exchange(mocker) - strategy = DefaultStrategy(default_conf) + + default_conf.update({'strategy': 'DefaultStrategy'}) + strategy = StrategyResolver.load_strategy(default_conf) timerange = TimeRange('index', 'index', 200, 250) data = strategy.tickerdata_to_dataframe( diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index ec85c8030..bba15c156 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -20,8 +20,8 @@ from freqtrade.data.dataprovider import DataProvider from freqtrade.data.history import get_timerange from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.optimize.backtesting import Backtesting +from freqtrade.resolvers import StrategyResolver from freqtrade.state import RunMode -from freqtrade.strategy.default_strategy import DefaultStrategy from freqtrade.strategy.interface import SellType from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, patched_configuration_load_config_file) @@ -350,7 +350,9 @@ def test_tickerdata_to_dataframe_bt(default_conf, mocker, testdatadir) -> None: assert len(data['UNITTEST/BTC']) == 102 # Load strategy to compare the result between Backtesting function and strategy are the same - strategy = DefaultStrategy(default_conf) + default_conf.update({'strategy': 'DefaultStrategy'}) + strategy = StrategyResolver.load_strategy(default_conf) + data2 = strategy.tickerdata_to_dataframe(tickerlist) assert data['UNITTEST/BTC'].equals(data2['UNITTEST/BTC']) diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 89c38bda1..a28519383 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -10,8 +10,9 @@ from freqtrade.configuration import TimeRange from freqtrade.data.converter import parse_ticker_dataframe from freqtrade.data.history import load_tickerdata_file from freqtrade.persistence import Trade -from tests.conftest import get_patched_exchange, log_has +from freqtrade.resolvers import StrategyResolver from freqtrade.strategy.default_strategy import DefaultStrategy +from tests.conftest import get_patched_exchange, log_has # Avoid to reinit the same object again and again _STRATEGY = DefaultStrategy(config={}) @@ -104,7 +105,8 @@ def test_get_signal_handles_exceptions(mocker, default_conf): def test_tickerdata_to_dataframe(default_conf, testdatadir) -> None: - strategy = DefaultStrategy(default_conf) + default_conf.update({'strategy': 'DefaultStrategy'}) + strategy = StrategyResolver.load_strategy(default_conf) timerange = TimeRange.parse_timerange('1510694220-1510700340') tick = load_tickerdata_file(testdatadir, 'UNITTEST/BTC', '1m', timerange=timerange) @@ -120,7 +122,8 @@ def test_min_roi_reached(default_conf, fee) -> None: min_roi_list = [{20: 0.05, 55: 0.01, 0: 0.1}, {0: 0.1, 20: 0.05, 55: 0.01}] for roi in min_roi_list: - strategy = DefaultStrategy(default_conf) + default_conf.update({'strategy': 'DefaultStrategy'}) + strategy = StrategyResolver.load_strategy(default_conf) strategy.minimal_roi = roi trade = Trade( pair='ETH/BTC', @@ -158,7 +161,8 @@ def test_min_roi_reached2(default_conf, fee) -> None: }, ] for roi in min_roi_list: - strategy = DefaultStrategy(default_conf) + default_conf.update({'strategy': 'DefaultStrategy'}) + strategy = StrategyResolver.load_strategy(default_conf) strategy.minimal_roi = roi trade = Trade( pair='ETH/BTC', @@ -192,7 +196,8 @@ def test_min_roi_reached3(default_conf, fee) -> None: 30: 0.05, 55: 0.30, } - strategy = DefaultStrategy(default_conf) + default_conf.update({'strategy': 'DefaultStrategy'}) + strategy = StrategyResolver.load_strategy(default_conf) strategy.minimal_roi = min_roi trade = Trade( pair='ETH/BTC', @@ -292,7 +297,8 @@ def test__analyze_ticker_internal_skip_analyze(ticker_history, mocker, caplog) - def test_is_pair_locked(default_conf): - strategy = DefaultStrategy(default_conf) + default_conf.update({'strategy': 'DefaultStrategy'}) + strategy = StrategyResolver.load_strategy(default_conf) # dict should be empty assert not strategy._pair_locked_until diff --git a/tests/test_plotting.py b/tests/test_plotting.py index e7ec4ce46..34d1f2b0c 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -19,7 +19,7 @@ from freqtrade.plot.plotting import (add_indicators, add_profit, generate_profit_graph, init_plotscript, load_and_plot_trades, plot_profit, plot_trades, store_plot_file) -from freqtrade.strategy.default_strategy import DefaultStrategy +from freqtrade.resolvers import StrategyResolver from tests.conftest import get_args, log_has, log_has_re @@ -70,9 +70,11 @@ def test_add_indicators(default_conf, testdatadir, caplog): indicators1 = {"ema10": {}} indicators2 = {"macd": {"color": "red"}} + default_conf.update({'strategy': 'DefaultStrategy'}) + strategy = StrategyResolver.load_strategy(default_conf) + # Generate buy/sell signals and indicators - strat = DefaultStrategy(default_conf) - data = strat.analyze_ticker(data, {'pair': pair}) + data = strategy.analyze_ticker(data, {'pair': pair}) fig = generate_empty_figure() # Row 1 @@ -181,9 +183,11 @@ def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir) data = history.load_pair_history(pair=pair, timeframe='1m', datadir=testdatadir, timerange=timerange) + default_conf.update({'strategy': 'DefaultStrategy'}) + strategy = StrategyResolver.load_strategy(default_conf) + # Generate buy/sell signals and indicators - strat = DefaultStrategy(default_conf) - data = strat.analyze_ticker(data, {'pair': pair}) + data = strategy.analyze_ticker(data, {'pair': pair}) indicators1 = [] indicators2 = [] From d6b9397579c2d4542bf5742a9ac7aa7f68a56f08 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 12 Feb 2020 06:40:13 +0100 Subject: [PATCH 075/117] Fix typo in datadir key --- docs/strategy_analysis_example.md | 4 ++-- freqtrade/templates/strategy_analysis_example.ipynb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md index 2d77edaed..10ae73450 100644 --- a/docs/strategy_analysis_example.md +++ b/docs/strategy_analysis_example.md @@ -13,7 +13,7 @@ from freqtrade.configuration import Configuration # Initialize empty configuration object config = Configuration.from_files([]) -# Optionally, Use existing configuration file +# Optionally, use existing configuration file # config = Configuration.from_files(["config.json"]) # Define some constants @@ -29,7 +29,7 @@ pair = "BTC_USDT" # Load data using values set above from freqtrade.data.history import load_pair_history -candles = load_pair_history(datadir=config["data_dir"], +candles = load_pair_history(datadir=config["datadir"], timeframe=config["ticker_interval"], pair=pair) diff --git a/freqtrade/templates/strategy_analysis_example.ipynb b/freqtrade/templates/strategy_analysis_example.ipynb index 06fc3f557..4541f5ed5 100644 --- a/freqtrade/templates/strategy_analysis_example.ipynb +++ b/freqtrade/templates/strategy_analysis_example.ipynb @@ -29,7 +29,7 @@ "\n", "# Initialize empty configuration object\n", "config = Configuration.from_files([])\n", - "# Optionally, Use existing configuration file\n", + "# Optionally, use existing configuration file\n", "# config = Configuration.from_files([\"config.json\"])\n", "\n", "# Define some constants\n", @@ -49,7 +49,7 @@ "# Load data using values set above\n", "from freqtrade.data.history import load_pair_history\n", "\n", - "candles = load_pair_history(datadir=config[\"data_dir\"],\n", + "candles = load_pair_history(datadir=config[\"datadir\"],\n", " timeframe=config[\"ticker_interval\"],\n", " pair=pair)\n", "\n", From 483cba453a3f9961ed6905b780de1a72881bdd20 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 12 Feb 2020 19:58:23 +0100 Subject: [PATCH 076/117] Fix last occurence of data_location --- docs/strategy_analysis_example.md | 2 +- freqtrade/templates/strategy_analysis_example.ipynb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md index 10ae73450..97a555e9e 100644 --- a/docs/strategy_analysis_example.md +++ b/docs/strategy_analysis_example.md @@ -34,7 +34,7 @@ candles = load_pair_history(datadir=config["datadir"], pair=pair) # Confirm success -print("Loaded " + str(len(candles)) + f" rows of data for {pair} from {data_location}") +print("Loaded " + str(len(candles)) + f" rows of data for {pair} from {config['datadir']}") candles.head() ``` diff --git a/freqtrade/templates/strategy_analysis_example.ipynb b/freqtrade/templates/strategy_analysis_example.ipynb index 4541f5ed5..4b904e100 100644 --- a/freqtrade/templates/strategy_analysis_example.ipynb +++ b/freqtrade/templates/strategy_analysis_example.ipynb @@ -54,7 +54,7 @@ " pair=pair)\n", "\n", "# Confirm success\n", - "print(\"Loaded \" + str(len(candles)) + f\" rows of data for {pair} from {data_location}\")\n", + "print(\"Loaded \" + str(len(candles)) + f\" rows of data for {pair} from {config['datadir']}\")\n", "candles.head()" ] }, From 2efa1c164fa4c1b0f5bab1775fba7b0c496ef299 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 12 Feb 2020 21:43:43 +0100 Subject: [PATCH 077/117] Revert data-location section --- docs/strategy_analysis_example.md | 6 ++++-- freqtrade/templates/strategy_analysis_example.ipynb | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md index 97a555e9e..93e84122b 100644 --- a/docs/strategy_analysis_example.md +++ b/docs/strategy_analysis_example.md @@ -20,6 +20,8 @@ config = Configuration.from_files([]) config["ticker_interval"] = "5m" # Name of the strategy class config["strategy"] = "SampleStrategy" +# Location of the data +data_location = Path(config['user_data_dir'], 'data', 'binance') # Pair to analyze - Only use one pair here pair = "BTC_USDT" ``` @@ -29,12 +31,12 @@ pair = "BTC_USDT" # Load data using values set above from freqtrade.data.history import load_pair_history -candles = load_pair_history(datadir=config["datadir"], +candles = load_pair_history(datadir=data_location, timeframe=config["ticker_interval"], pair=pair) # Confirm success -print("Loaded " + str(len(candles)) + f" rows of data for {pair} from {config['datadir']}") +print("Loaded " + str(len(candles)) + f" rows of data for {pair} from {data_location}") candles.head() ``` diff --git a/freqtrade/templates/strategy_analysis_example.ipynb b/freqtrade/templates/strategy_analysis_example.ipynb index 4b904e100..91e132380 100644 --- a/freqtrade/templates/strategy_analysis_example.ipynb +++ b/freqtrade/templates/strategy_analysis_example.ipynb @@ -36,6 +36,8 @@ "config[\"ticker_interval\"] = \"5m\"\n", "# Name of the strategy class\n", "config[\"strategy\"] = \"SampleStrategy\"\n", + "# Location of the data\n", + "data_location = Path(config['user_data_dir'], 'data', 'binance')\n", "# Pair to analyze - Only use one pair here\n", "pair = \"BTC_USDT\"" ] @@ -49,12 +51,12 @@ "# Load data using values set above\n", "from freqtrade.data.history import load_pair_history\n", "\n", - "candles = load_pair_history(datadir=config[\"datadir\"],\n", + "candles = load_pair_history(datadir=data_location,\n", " timeframe=config[\"ticker_interval\"],\n", " pair=pair)\n", "\n", "# Confirm success\n", - "print(\"Loaded \" + str(len(candles)) + f\" rows of data for {pair} from {config['datadir']}\")\n", + "print(\"Loaded \" + str(len(candles)) + f\" rows of data for {pair} from {data_location}\")\n", "candles.head()" ] }, From 47874a452787360b610ab0f3d038de6e53cc5d53 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Wed, 12 Feb 2020 21:45:55 +0100 Subject: [PATCH 078/117] added logic to differentiate sell orders with double asterisk --- docs/telegram-usage.md | 2 +- freqtrade/rpc/rpc.py | 3 ++- freqtrade/rpc/telegram.py | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index c8ded4af5..f683ae8da 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -55,7 +55,7 @@ official commands. You can ask at any moment for help with `/help`. | `/reload_conf` | | 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 (*) +| `/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 (**) | `/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/rpc.py b/freqtrade/rpc/rpc.py index 07631f258..c182aad2b 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -169,7 +169,8 @@ class RPC: trades_list.append([ trade.id, trade.pair + '*' if (trade.open_order_id is not None - and trade.close_rate_requested is None) else '', + and trade.close_rate_requested is None) else '' + + '**' if (trade.close_rate_requested is not None) else '', shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)), profit_str ]) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index e3d4f54e7..d4ed5b189 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -561,6 +561,8 @@ class Telegram(RPC): "*/stop:* `Stops the trader`\n" \ "*/status [table]:* `Lists all open trades`\n" \ " *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" \ "*/profit:* `Lists cumulative profit from all finished trades`\n" \ "*/forcesell |all:* `Instantly sells the given trade or all trades, " \ "regardless of profit`\n" \ From f6db784a859b9a70a320e2c2da12dc47afce678e Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Wed, 12 Feb 2020 21:50:33 +0100 Subject: [PATCH 079/117] removed default to refresh argument in get_buy_rate and get_sell_rate --- freqtrade/freqtradebot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ffd951ee2..455396352 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -234,7 +234,7 @@ class FreqtradeBot: return trades_created - def get_buy_rate(self, pair: str, refresh: bool = True, tick: Dict = None) -> float: + def get_buy_rate(self, pair: str, refresh: bool, tick: Dict = None) -> float: """ Calculates bid target between current ask price and last price :return: float: Price @@ -615,7 +615,7 @@ class FreqtradeBot: return trades_closed - def get_sell_rate(self, pair: str, refresh: bool = True) -> float: + def get_sell_rate(self, pair: str, refresh: bool) -> float: """ Get sell rate - either using get-ticker bid or first bid based on orderbook The orderbook portion is only used for rpc messaging, which would otherwise fail From 2e3b8cdba758a33abef2ce5f349335ff8194aa8a Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Wed, 12 Feb 2020 21:51:58 +0100 Subject: [PATCH 080/117] fixed flake8 issues on /help output --- freqtrade/rpc/telegram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index d4ed5b189..5603ab03c 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -561,8 +561,8 @@ class Telegram(RPC): "*/stop:* `Stops the trader`\n" \ "*/status [table]:* `Lists all open trades`\n" \ " *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" \ + " pending buy orders are marked with an asterisk (*)\n" \ + " pending sell orders are marked with a double asterisk (**)\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 f09af888b153794ff135de9acedf493666124e9c Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Wed, 12 Feb 2020 21:55:38 +0100 Subject: [PATCH 081/117] modified get_buy/sell_rate refresh to true on notify_sell_cancel and notify_buy_cancel --- freqtrade/freqtradebot.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 455396352..158b631c1 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -566,7 +566,7 @@ class FreqtradeBot: """ Sends rpc notification when a buy cancel occured. """ - current_rate = self.get_buy_rate(trade.pair, False) + current_rate = self.get_buy_rate(trade.pair, True) msg = { 'type': RPCMessageType.BUY_CANCEL_NOTIFICATION, @@ -1062,8 +1062,7 @@ class FreqtradeBot: """ profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested profit_trade = trade.calc_profit(rate=profit_rate) - # Use cached ticker here - it was updated seconds ago. - current_rate = self.get_sell_rate(trade.pair, False) + current_rate = self.get_sell_rate(trade.pair, True) profit_percent = trade.calc_profit_ratio(profit_rate) gain = "profit" if profit_percent > 0 else "loss" From 007cc94474c5f4bc0970f61b4716b00d8a8b8c92 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Wed, 12 Feb 2020 22:03:56 +0100 Subject: [PATCH 082/117] fixed tests to send refresh, since its no longer defaulted --- tests/test_freqtradebot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index c0af1f015..5ed4d296c 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -921,7 +921,7 @@ def test_get_buy_rate(mocker, default_conf, ask, last, last_ab, expected) -> Non mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={'ask': ask, 'last': last})) - assert freqtrade.get_buy_rate('ETH/BTC') == expected + assert freqtrade.get_buy_rate('ETH/BTC', True) == expected def test_execute_buy(mocker, default_conf, fee, limit_buy_order) -> None: @@ -3524,7 +3524,7 @@ def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2) -> None: default_conf['telegram']['enabled'] = False freqtrade = FreqtradeBot(default_conf) - assert freqtrade.get_buy_rate('ETH/BTC') == 0.043935 + assert freqtrade.get_buy_rate('ETH/BTC', True) == 0.043935 assert ticker_mock.call_count == 0 @@ -3549,7 +3549,7 @@ def test_order_book_bid_strategy2(mocker, default_conf, order_book_l2) -> None: freqtrade = FreqtradeBot(default_conf) # orderbook shall be used even if tickers would be lower. - assert freqtrade.get_buy_rate('ETH/BTC') != 0.042 + assert freqtrade.get_buy_rate('ETH/BTC', True) != 0.042 assert ticker_mock.call_count == 0 From 634bf2b15cf5078a1dba2fbd1ce0915346b30be5 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Thu, 13 Feb 2020 01:44:46 +0300 Subject: [PATCH 083/117] Docs: Fix checking of runmode --- docs/strategy-customization.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 688647c2b..07833da34 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -346,7 +346,7 @@ if self.dp: ``` python if self.dp: - if self.dp.runmode in ('live', 'dry_run'): + if self.dp.runmode.value in ('live', 'dry_run'): ob = self.dp.orderbook(metadata['pair'], 1) dataframe['best_bid'] = ob['bids'][0][0] dataframe['best_ask'] = ob['asks'][0][0] @@ -422,7 +422,7 @@ from freqtrade.persistence import Trade The following example queries for the current pair and trades from today, however other filters can easily be added. ``` python -if self.config['runmode'] in ('live', 'dry_run'): +if self.config['runmode'].value in ('live', 'dry_run'): trades = Trade.get_trades([Trade.pair == metadata['pair'], Trade.open_date > datetime.utcnow() - timedelta(days=1), Trade.is_open == False, @@ -434,7 +434,7 @@ if self.config['runmode'] in ('live', 'dry_run'): Get amount of stake_currency currently invested in Trades: ``` python -if self.config['runmode'] in ('live', 'dry_run'): +if self.config['runmode'].value in ('live', 'dry_run'): total_stakes = Trade.total_open_trades_stakes() ``` @@ -442,7 +442,7 @@ Retrieve performance per pair. Returns a List of dicts per pair. ``` python -if self.config['runmode'] in ('live', 'dry_run'): +if self.config['runmode'].value in ('live', 'dry_run'): performance = Trade.get_overall_performance() ``` @@ -487,7 +487,7 @@ from datetime import timedelta, datetime, timezone # -------- # Within populate indicators (or populate_buy): -if self.config['runmode'] in ('live', 'dry_run'): +if self.config['runmode'].value in ('live', 'dry_run'): # fetch closed trades for the last 2 days trades = Trade.get_trades([Trade.pair == metadata['pair'], Trade.open_date > datetime.utcnow() - timedelta(days=2), From 81f849811fdcf4a2273f56ba2d7851aec66990a6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 13 Feb 2020 06:30:59 +0100 Subject: [PATCH 084/117] Initcap Freqtrade --- docs/strategy_analysis_example.md | 2 +- freqtrade/templates/strategy_analysis_example.ipynb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md index 93e84122b..f3f9b46c3 100644 --- a/docs/strategy_analysis_example.md +++ b/docs/strategy_analysis_example.md @@ -1,6 +1,6 @@ # Strategy analysis example -Debugging a strategy can be time-consuming. FreqTrade offers helper functions to visualize raw data. +Debugging a strategy can be time-consuming. Freqtrade offers helper functions to visualize raw data. ## Setup diff --git a/freqtrade/templates/strategy_analysis_example.ipynb b/freqtrade/templates/strategy_analysis_example.ipynb index 91e132380..021056df2 100644 --- a/freqtrade/templates/strategy_analysis_example.ipynb +++ b/freqtrade/templates/strategy_analysis_example.ipynb @@ -6,7 +6,7 @@ "source": [ "# Strategy analysis example\n", "\n", - "Debugging a strategy can be time-consuming. FreqTrade offers helper functions to visualize raw data." + "Debugging a strategy can be time-consuming. Freqtrade offers helper functions to visualize raw data." ] }, { From 86592c3ba1c0f4b626d7d4edb471a320d120dd13 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 13 Feb 2020 06:51:52 +0100 Subject: [PATCH 085/117] Fix /help from telegram --- freqtrade/rpc/telegram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 5603ab03c..abd322293 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -561,8 +561,8 @@ class Telegram(RPC): "*/stop:* `Stops the trader`\n" \ "*/status [table]:* `Lists all open trades`\n" \ " *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" \ + " `pending buy orders are marked with an asterisk (*)`\n" \ + " `pending sell orders are marked with a double asterisk (**)`\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 ccc923975138e47440e5ce48fd6fb38c1f0691c1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 13 Feb 2020 07:02:12 +0100 Subject: [PATCH 086/117] Reduce indentation of help --- freqtrade/rpc/telegram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index abd322293..e3958b31a 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -561,8 +561,8 @@ class Telegram(RPC): "*/stop:* `Stops the trader`\n" \ "*/status [table]:* `Lists all open trades`\n" \ " *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" \ + " `pending buy orders are marked with an asterisk (*)`\n" \ + " `pending sell orders are marked with a double asterisk (**)`\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 a93bc74eff01cebaed7e1e9838a53b526178913d Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 13 Feb 2020 07:04:37 +0100 Subject: [PATCH 087/117] Update documentation ... --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index c0404d647..98300c5fa 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -40,7 +40,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | Parameter | Description | |------------|-------------| -| `max_open_trades` | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades). [More information below](#configuring-amount-per-trade).
***Datatype:*** *Positive integer or -1.* +| `max_open_trades` | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades). [More information below](#configuring-amount-per-trade).
**Datatype:** Positive integer or -1. | `stake_currency` | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *String* | `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Set it to `"unlimited"` to allow the bot to use all available balance. [More information below](#configuring-amount-per-trade). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Positive float or `"unlimited"`.* | `tradable_balance_ratio` | Ratio of the total account balance the bot is allowed to trade. [More information below](#configuring-amount-per-trade).
*Defaults to `0.99` 99%).*
***Datatype:*** *Positive float between `0.1` and `1.0`.* From 02148a1df203d8512dc6987ce7ac4ad5111211a2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 13 Feb 2020 15:09:09 +0100 Subject: [PATCH 088/117] Fix datatype styling issues --- docs/configuration.md | 132 +++++++++++++++++++++--------------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 98300c5fa..fd686834f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -41,74 +41,74 @@ Mandatory parameters are marked as **Required**, which means that they are requi | Parameter | Description | |------------|-------------| | `max_open_trades` | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades). [More information below](#configuring-amount-per-trade).
**Datatype:** Positive integer or -1. -| `stake_currency` | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *String* -| `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Set it to `"unlimited"` to allow the bot to use all available balance. [More information below](#configuring-amount-per-trade). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Positive float or `"unlimited"`.* -| `tradable_balance_ratio` | Ratio of the total account balance the bot is allowed to trade. [More information below](#configuring-amount-per-trade).
*Defaults to `0.99` 99%).*
***Datatype:*** *Positive float between `0.1` and `1.0`.* -| `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade).
*Defaults to `false`.*
***Datatype:*** *Boolean* -| `last_stake_amount_min_ratio` | Defines minimum stake amount that has to be left and executed. Applies only to the last stake amount when it's amended to a reduced value (i.e. if `amend_last_stake_amount` is set to `true`). [More information below](#configuring-amount-per-trade).
*Defaults to `0.5`.*
***Datatype:*** *Float (as ratio)* -| `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals.
*Defaults to `0.05` (5%).*
***Datatype:*** *Positive Float as ratio.* -| `ticker_interval` | The ticker interval to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *String* -| `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency).
***Datatype:*** *String* -| `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode.
*Defaults to `true`.*
***Datatype:*** *Boolean* -| `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* -| `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)* -| `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* -| `trailing_only_offset_is_reached` | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
***Datatype:*** *Boolean* -| `unfilledtimeout.buy` | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Integer* -| `unfilledtimeout.sell` | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Integer* +| `stake_currency` | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy).
**Datatype:** String +| `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Set it to `"unlimited"` to allow the bot to use all available balance. [More information below](#configuring-amount-per-trade). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Positive float or `"unlimited"`. +| `tradable_balance_ratio` | Ratio of the total account balance the bot is allowed to trade. [More information below](#configuring-amount-per-trade).
*Defaults to `0.99` 99%).*
**Datatype:** Positive float between `0.1` and `1.0`. +| `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade).
*Defaults to `false`.*
**Datatype:** Boolean +| `last_stake_amount_min_ratio` | Defines minimum stake amount that has to be left and executed. Applies only to the last stake amount when it's amended to a reduced value (i.e. if `amend_last_stake_amount` is set to `true`). [More information below](#configuring-amount-per-trade).
*Defaults to `0.5`.*
**Datatype:** Float (as ratio) +| `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals.
*Defaults to `0.05` (5%).*
**Datatype:** Positive Float as ratio. +| `ticker_interval` | The ticker interval to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** String +| `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency).
**Datatype:** String +| `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode.
*Defaults to `true`.*
**Datatype:** Boolean +| `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 +| `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) +| `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 +| `trailing_only_offset_is_reached` | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean +| `unfilledtimeout.buy` | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Integer +| `unfilledtimeout.sell` | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Integer | `bid_strategy.ask_last_balance` | **Required.** Set the bidding price. More information [below](#buy-price-without-orderbook). -| `bid_strategy.use_order_book` | Enable buying using the rates in [Order Book Bids](#buy-price-with-orderbook-enabled).
***Datatype:*** *Boolean* -| `bid_strategy.order_book_top` | Bot will use the top N rate in Order Book Bids to buy. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in [Order Book Bids](#buy-price-with-orderbook-enabled).
*Defaults to `1`.*
***Datatype:*** *Positive Integer* -| `bid_strategy. check_depth_of_market.enabled` | Do not buy if the difference of buy orders and sell orders is met in Order Book. [Check market depth](#check-depth-of-market).
*Defaults to `false`.*
***Datatype:*** *Boolean* -| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | The difference ratio of buy orders and sell orders found in Order Book. A value below 1 means sell order size is greater, while value greater than 1 means buy order size is higher. [Check market depth](#check-depth-of-market)
*Defaults to `0`.*
***Datatype:*** *Float (as ratio)* -| `ask_strategy.use_order_book` | Enable selling of open trades using [Order Book Asks](#sell-price-with-orderbook-enabled).
***Datatype:*** *Boolean* -| `ask_strategy.order_book_min` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
*Defaults to `1`.*
***Datatype:*** *Positive Integer* -| `ask_strategy.order_book_max` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
*Defaults to `1`.*
***Datatype:*** *Positive Integer* -| `ask_strategy.use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `true`.*
***Datatype:*** *Boolean* -| `ask_strategy.sell_profit_only` | Wait until the bot makes a positive profit before taking a sell decision. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
***Datatype:*** *Boolean* -| `ask_strategy.ignore_roi_if_buy_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
***Datatype:*** *Boolean* -| `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Dict* -| `order_time_in_force` | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Dict* -| `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename).
***Datatype:*** *String* -| `exchange.sandbox` | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details.
***Datatype:*** *Boolean* -| `exchange.key` | API key to use for the exchange. Only required when you are in production mode.
**Keep it in secret, do not disclose publicly.**
***Datatype:*** *String* -| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode.
**Keep it in secret, do not disclose publicly.**
***Datatype:*** *String* -| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests.
**Keep it in secret, do not disclose publicly.**
***Datatype:*** *String* -| `exchange.pair_whitelist` | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Not used by VolumePairList (see [below](#dynamic-pairlists)).
***Datatype:*** *List* -| `exchange.pair_blacklist` | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#dynamic-pairlists)).
***Datatype:*** *List* -| `exchange.ccxt_config` | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation)
***Datatype:*** *Dict* -| `exchange.ccxt_async_config` | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation)
***Datatype:*** *Dict* -| `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded.
*Defaults to `60` minutes.*
***Datatype:*** *Positive Integer* +| `bid_strategy.use_order_book` | Enable buying using the rates in [Order Book Bids](#buy-price-with-orderbook-enabled).
**Datatype:** Boolean +| `bid_strategy.order_book_top` | Bot will use the top N rate in Order Book Bids to buy. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in [Order Book Bids](#buy-price-with-orderbook-enabled).
*Defaults to `1`.*
**Datatype:** Positive Integer +| `bid_strategy. check_depth_of_market.enabled` | Do not buy if the difference of buy orders and sell orders is met in Order Book. [Check market depth](#check-depth-of-market).
*Defaults to `false`.*
**Datatype:** Boolean +| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | The difference ratio of buy orders and sell orders found in Order Book. A value below 1 means sell order size is greater, while value greater than 1 means buy order size is higher. [Check market depth](#check-depth-of-market)
*Defaults to `0`.*
**Datatype:** Float (as ratio) +| `ask_strategy.use_order_book` | Enable selling of open trades using [Order Book Asks](#sell-price-with-orderbook-enabled).
**Datatype:** Boolean +| `ask_strategy.order_book_min` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
*Defaults to `1`.*
**Datatype:** Positive Integer +| `ask_strategy.order_book_max` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
*Defaults to `1`.*
**Datatype:** Positive Integer +| `ask_strategy.use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `true`.*
**Datatype:** Boolean +| `ask_strategy.sell_profit_only` | Wait until the bot makes a positive profit before taking a sell decision. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean +| `ask_strategy.ignore_roi_if_buy_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean +| `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict +| `order_time_in_force` | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict +| `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename).
**Datatype:** String +| `exchange.sandbox` | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details.
**Datatype:** Boolean +| `exchange.key` | API key to use for the exchange. Only required when you are in production mode.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String +| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String +| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String +| `exchange.pair_whitelist` | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Not used by VolumePairList (see [below](#dynamic-pairlists)).
**Datatype:** List +| `exchange.pair_blacklist` | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#dynamic-pairlists)).
**Datatype:** List +| `exchange.ccxt_config` | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation)
**Datatype:** Dict +| `exchange.ccxt_async_config` | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation)
**Datatype:** Dict +| `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded.
*Defaults to `60` minutes.*
**Datatype:** Positive Integer | `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation. -| `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now.
*Defaults to `true`.*
***Datatype:*** *Boolean* -| `pairlists` | Define one or more pairlists to be used. [More information below](#dynamic-pairlists).
*Defaults to `StaticPairList`.*
***Datatype:*** *List of Dicts* -| `telegram.enabled` | Enable the usage of Telegram.
***Datatype:*** *Boolean* -| `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`.
**Keep it in secret, do not disclose publicly.**
***Datatype:*** *String* -| `telegram.chat_id` | Your personal Telegram account id. Only required if `telegram.enabled` is `true`.
**Keep it in secret, do not disclose publicly.**
***Datatype:*** *String* -| `webhook.enabled` | Enable usage of Webhook notifications
***Datatype:*** *Boolean* -| `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
***Datatype:*** *String* -| `webhook.webhookbuy` | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
***Datatype:*** *String* -| `webhook.webhooksell` | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
***Datatype:*** *String* -| `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
***Datatype:*** *String* -| `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.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* -| `initial_state` | Defines the initial application state. More information below.
*Defaults to `stopped`.*
***Datatype:*** *Enum, either `stopped` or `running`* -| `forcebuy_enable` | Enables the RPC Commands to force a buy. More information below.
***Datatype:*** *Boolean* -| `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`.
***Datatype:*** *ClassName* -| `strategy_path` | Adds an additional strategy lookup path (must be a directory).
***Datatype:*** *String* -| `internals.process_throttle_secs` | Set the process throttle. Value in second.
*Defaults to `5` seconds.*
***Datatype:*** *Positive Integer* -| `internals.heartbeat_interval` | Print heartbeat message every N seconds. Set to 0 to disable heartbeat messages.
*Defaults to `60` seconds.*
***Datatype:*** *Positive Integer or 0* -| `internals.sd_notify` | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details.
***Datatype:*** *Boolean* -| `logfile` | Specifies logfile name. Uses a rolling strategy for log file rotation for 10 files with the 1MB limit per file.
***Datatype:*** *String* -| `user_data_dir` | Directory containing user data.
*Defaults to `./user_data/`*.
***Datatype:*** *String* +| `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now.
*Defaults to `true`.*
**Datatype:** Boolean +| `pairlists` | Define one or more pairlists to be used. [More information below](#dynamic-pairlists).
*Defaults to `StaticPairList`.*
**Datatype:** List of Dicts +| `telegram.enabled` | Enable the usage of Telegram.
**Datatype:** Boolean +| `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String +| `telegram.chat_id` | Your personal Telegram account id. Only required if `telegram.enabled` is `true`.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String +| `webhook.enabled` | Enable usage of Webhook notifications
**Datatype:** Boolean +| `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
**Datatype:** String +| `webhook.webhookbuy` | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
**Datatype:** String +| `webhook.webhooksell` | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
**Datatype:** String +| `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
**Datatype:** String +| `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.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 +| `initial_state` | Defines the initial application state. More information below.
*Defaults to `stopped`.*
**Datatype:** Enum, either `stopped` or `running` +| `forcebuy_enable` | Enables the RPC Commands to force a buy. More information below.
**Datatype:** Boolean +| `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`.
**Datatype:** ClassName +| `strategy_path` | Adds an additional strategy lookup path (must be a directory).
**Datatype:** String +| `internals.process_throttle_secs` | Set the process throttle. Value in second.
*Defaults to `5` seconds.*
**Datatype:** Positive Intege +| `internals.heartbeat_interval` | Print heartbeat message every N seconds. Set to 0 to disable heartbeat messages.
*Defaults to `60` seconds.*
**Datatype:** Positive Integer or 0 +| `internals.sd_notify` | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details.
**Datatype:** Boolean +| `logfile` | Specifies logfile name. Uses a rolling strategy for log file rotation for 10 files with the 1MB limit per file.
**Datatype:** String +| `user_data_dir` | Directory containing user data.
*Defaults to `./user_data/`*.
**Datatype:** String ### Parameters in the strategy From a0a14a107820df972d1e02b42267093f58748f1e Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 14 Feb 2020 01:08:17 +0300 Subject: [PATCH 089/117] freqtrade/templates/subtemplates/exchange_bittrex.j2 --- freqtrade/templates/subtemplates/exchange_bittrex.j2 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/freqtrade/templates/subtemplates/exchange_bittrex.j2 b/freqtrade/templates/subtemplates/exchange_bittrex.j2 index 7a7e8e291..7b27318ca 100644 --- a/freqtrade/templates/subtemplates/exchange_bittrex.j2 +++ b/freqtrade/templates/subtemplates/exchange_bittrex.j2 @@ -1,3 +1,10 @@ +"order_types": { + "buy": "limit", + "sell": "limit", + "emergencysell": "limit", + "stoploss": "limit", + "stoploss_on_exchange": false +}, "exchange": { "name": "{{ exchange_name | lower }}", "key": "{{ exchange_key }}", From 749463e4b7e9f9b1c475725a54b71d181b3a7a76 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 14 Feb 2020 03:05:07 +0300 Subject: [PATCH 090/117] Adjust message in main.py --- freqtrade/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/main.py b/freqtrade/main.py index a75eeebed..08bdc5e32 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -38,8 +38,8 @@ def main(sysargv: List[str] = None) -> None: # No subcommand was issued. raise OperationalException( "Usage of Freqtrade requires a subcommand to be specified.\n" - "To have the previous behavior (bot executing trades in live/dry-run modes, " - "depending on the value of the `dry_run` setting in the config), run freqtrade " + "To have the bot executing trades in live/dry-run modes, " + "depending on the value of the `dry_run` setting in the config, run Freqtrade " "as `freqtrade trade [options...]`.\n" "To see the full list of options available, please use " "`freqtrade --help` or `freqtrade --help`." From 36ef5c6bdff2562e158bbd659db7bc6c67c00560 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 14 Feb 2020 04:05:17 +0300 Subject: [PATCH 091/117] Get rid of delete_trades method in Freqtradebot --- freqtrade/freqtradebot.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 158b631c1..bf2697efa 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -860,11 +860,6 @@ class FreqtradeBot: order_type = self.strategy.order_types['sell'] self._notify_sell_cancel(trade, order_type) - def delete_trade(self, trade: Trade) -> None: - """Delete trade in database""" - Trade.session.delete(trade) - Trade.session.flush() - def handle_timedout_limit_buy(self, trade: Trade, order: Dict) -> bool: """ Buy timeout - cancel order @@ -882,7 +877,8 @@ class FreqtradeBot: if corder.get('remaining', order['remaining']) == order['amount']: # if trade is not partially completed, just delete the trade - self.delete_trade(trade) + Trade.session.delete(trade) + Trade.session.flush() return True # if trade is partially complete, edit the stake details for the trade From 20c21b42d50ce08fe4157c151c340a6be771b7f2 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 14 Feb 2020 06:23:03 +0300 Subject: [PATCH 092/117] Move rpc send to be after db session add/flash --- freqtrade/freqtradebot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 158b631c1..1284b2e9b 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -527,8 +527,6 @@ class FreqtradeBot: ticker_interval=timeframe_to_minutes(self.config['ticker_interval']) ) - self._notify_buy(trade, order_type) - # Update fees if order is closed if order_status == 'closed': self.update_trade_state(trade, order) @@ -539,6 +537,8 @@ class FreqtradeBot: # Updating wallets self.wallets.update() + self._notify_buy(trade, order_type) + return True def _notify_buy(self, trade: Trade, order_type: str) -> None: From 9cbf8c5f008520b99016a7a63fe299b4b0dcb821 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 14 Feb 2020 21:15:36 +0300 Subject: [PATCH 093/117] Add status for listed strategies --- freqtrade/commands/list_commands.py | 11 +++++++++-- freqtrade/resolvers/iresolver.py | 20 +++++++++++++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index f2b6bf995..b6ff682e6 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -43,10 +43,17 @@ def start_list_strategies(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) directory = Path(config.get('strategy_path', config['user_data_dir'] / USERPATH_STRATEGIES)) - strategies = StrategyResolver.search_all_objects(directory) + strategies = StrategyResolver.search_all_objects(directory, not args['print_one_column']) # Sort alphabetically strategies = sorted(strategies, key=lambda x: x['name']) - strats_to_print = [{'name': s['name'], 'location': s['location'].name} for s in strategies] + names = [s['name'] for s in strategies] + strats_to_print = [{ + 'name': s['name'] if s['name'] else "--", + 'location': s['location'].name, + 'status': ("LOAD FAILED" if s['class'] is None + else "OK" if names.count(s['name']) == 1 + else "DUPLICATED NAME") + } for s in strategies] if args['print_one_column']: print('\n'.join([s['name'] for s in strategies])) diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index a75c45933..8b5aa1dff 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -41,11 +41,15 @@ class IResolver: @classmethod def _get_valid_object(cls, module_path: Path, - object_name: Optional[str]) -> Generator[Any, None, None]: + object_name: Optional[str], + enum_failed: bool = False) -> Union[Generator[Any, None, None], + Tuple[None]]: """ Generator returning objects with matching object_type and object_name in the path given. :param module_path: absolute path to the module :param object_name: Class name of the object + :param enum_failed: If True, will return None for modules which fail. + Otherwise, failing modules are skipped. :return: generator containing matching objects """ @@ -58,6 +62,8 @@ class IResolver: except (ModuleNotFoundError, SyntaxError) as err: # Catch errors in case a specific module is not installed logger.warning(f"Could not import {module_path} due to '{err}'") + if enum_failed: + return (None, ) valid_objects_gen = ( obj for name, obj in inspect.getmembers(module, inspect.isclass) @@ -136,10 +142,13 @@ class IResolver: ) @classmethod - def search_all_objects(cls, directory: Path) -> List[Dict[str, Any]]: + def search_all_objects(cls, directory: Path, + enum_failed: bool) -> List[Dict[str, Any]]: """ Searches a directory for valid objects :param directory: Path to search + :param enum_failed: If True, will return None for modules which fail. + Otherwise, failing modules are skipped. :return: List of dicts containing 'name', 'class' and 'location' entires """ logger.debug(f"Searching for {cls.object_type.__name__} '{directory}'") @@ -151,10 +160,11 @@ class IResolver: continue module_path = entry.resolve() logger.debug(f"Path {module_path}") - for obj in cls._get_valid_object(module_path, object_name=None): + for obj in cls._get_valid_object(module_path, object_name=None, + enum_failed=enum_failed): objects.append( - {'name': obj.__name__, - 'class': obj, + {'name': obj.__name__ if obj is not None else '', + 'class': obj if obj is not None else None, 'location': entry, }) return objects From a2d7f8a70dc8e907c83ab4e958783708174ab07f Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 14 Feb 2020 21:24:30 +0300 Subject: [PATCH 094/117] Split tabular printing into sep. helper function --- freqtrade/commands/list_commands.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index b6ff682e6..782cd074e 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -3,7 +3,7 @@ import logging import sys from collections import OrderedDict from pathlib import Path -from typing import Any, Dict +from typing import Any, Dict, List import rapidjson from tabulate import tabulate @@ -36,6 +36,19 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: print(f"Exchanges available for Freqtrade: {', '.join(exchanges)}") +def _print_objs_tabular(objs: List) -> None: + names = [s['name'] for s in objs] + strats_to_print = [{ + 'name': s['name'] if s['name'] else "--", + 'location': s['location'].name, + 'status': ("LOAD FAILED" if s['class'] is None + else "OK" if names.count(s['name']) == 1 + else "DUPLICATED NAME") + } for s in objs] + + print(tabulate(strats_to_print, headers='keys', tablefmt='pipe')) + + def start_list_strategies(args: Dict[str, Any]) -> None: """ Print files with Strategy custom classes available in the directory @@ -46,19 +59,11 @@ def start_list_strategies(args: Dict[str, Any]) -> None: strategies = StrategyResolver.search_all_objects(directory, not args['print_one_column']) # Sort alphabetically strategies = sorted(strategies, key=lambda x: x['name']) - names = [s['name'] for s in strategies] - strats_to_print = [{ - 'name': s['name'] if s['name'] else "--", - 'location': s['location'].name, - 'status': ("LOAD FAILED" if s['class'] is None - else "OK" if names.count(s['name']) == 1 - else "DUPLICATED NAME") - } for s in strategies] if args['print_one_column']: print('\n'.join([s['name'] for s in strategies])) else: - print(tabulate(strats_to_print, headers='keys', tablefmt='pipe')) + _print_objs_tabular(strategies) def start_list_hyperopts(args: Dict[str, Any]) -> None: From 9dafc2f3c84819cf95bda82ddb0cc8ec380ede3d Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 11 Feb 2020 20:38:29 +0100 Subject: [PATCH 095/117] Load config.json from user_data first --- freqtrade/commands/arguments.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index fe6f49039..d37870ea0 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -6,8 +6,8 @@ from functools import partial from pathlib import Path from typing import Any, Dict, List, Optional -from freqtrade import constants from freqtrade.commands.cli_options import AVAILABLE_CLI_OPTIONS +from freqtrade.constants import DEFAULT_CONFIG ARGS_COMMON = ["verbosity", "logfile", "version", "config", "datadir", "user_data_dir"] @@ -107,10 +107,19 @@ class Arguments: # Workaround issue in argparse with action='append' and default value # (see https://bugs.python.org/issue16399) # Allow no-config for certain commands (like downloading / plotting) - if ('config' in parsed_arg and parsed_arg.config is None and - ((Path.cwd() / constants.DEFAULT_CONFIG).is_file() or - not ('command' in parsed_arg and parsed_arg.command in NO_CONF_REQURIED))): - parsed_arg.config = [constants.DEFAULT_CONFIG] + if ('config' in parsed_arg and parsed_arg.config is None): + conf_required = ('command' in parsed_arg and parsed_arg.command in NO_CONF_REQURIED) + + if 'user_data_dir' in parsed_arg and parsed_arg.user_data_dir is not None: + # Try loading from "user_data/config.json" + cfgfile = Path(parsed_arg.user_data_dir) / DEFAULT_CONFIG + if cfgfile.is_file() or not conf_required: + parsed_arg.config = [str(cfgfile)] + else: + # Else use "config.json". + cfgfile = Path.cwd() / DEFAULT_CONFIG + if cfgfile.is_file() or not conf_required: + parsed_arg.config = [DEFAULT_CONFIG] return parsed_arg From be4a9b5f4b0f9c4026ed043876bbcfb516a83a0d Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 14 Feb 2020 19:37:20 +0100 Subject: [PATCH 096/117] Lowercase freqtrade --- CONTRIBUTING.md | 4 ++-- docs/configuration.md | 2 +- docs/developer.md | 4 ++-- docs/hyperopt.md | 2 +- docs/rest-api.md | 2 +- freqtrade/commands/cli_options.py | 3 ++- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1c83437f6..90594866a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -109,11 +109,11 @@ Exceptions: Contributors may be given commit privileges. Preference will be given to those with: -1. Past contributions to FreqTrade and other related open-source projects. Contributions to FreqTrade include both code (both accepted and pending) and friendly participation in the issue tracker and Pull request reviews. Quantity and quality are considered. +1. Past contributions to Freqtrade and other related open-source projects. Contributions to Freqtrade include both code (both accepted and pending) and friendly participation in the issue tracker and Pull request reviews. Quantity and quality are considered. 1. A coding style that the other core committers find simple, minimal, and clean. 1. Access to resources for cross-platform development and testing. 1. Time to devote to the project regularly. -Being a Committer does not grant write permission on `develop` or `master` for security reasons (Users trust FreqTrade with their Exchange API keys). +Being a Committer does not grant write permission on `develop` or `master` for security reasons (Users trust Freqtrade with their Exchange API keys). After being Committer for some time, a Committer may be named Core Committer and given full repository access. diff --git a/docs/configuration.md b/docs/configuration.md index 211f7a04c..0f0279eb9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -672,7 +672,7 @@ freqtrade ## Embedding Strategies -FreqTrade provides you with with an easy way to embed the strategy into your configuration file. +Freqtrade provides you with with an easy way to embed the strategy into your configuration file. This is done by utilizing BASE64 encoding and providing this string at the strategy configuration field, in your chosen config file. diff --git a/docs/developer.md b/docs/developer.md index c679b8a49..b128ffd2b 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -1,6 +1,6 @@ # Development Help -This page is intended for developers of FreqTrade, people who want to contribute to the FreqTrade codebase or documentation, or people who want to understand the source code of the application they're running. +This page is intended for developers of Freqtrade, people who want to contribute to the Freqtrade codebase or documentation, or people who want to understand the source code of the application they're running. All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. We [track issues](https://github.com/freqtrade/freqtrade/issues) on [GitHub](https://github.com) and also have a dev channel in [slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE) where you can ask questions. @@ -153,7 +153,7 @@ In VolumePairList, this implements different methods of sorting, does early vali ## Implement a new Exchange (WIP) !!! Note - This section is a Work in Progress and is not a complete guide on how to test a new exchange with FreqTrade. + This section is a Work in Progress and is not a complete guide on how to test a new exchange with Freqtrade. Most exchanges supported by CCXT should work out of the box. diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 3e10f66da..401811a1b 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -182,7 +182,7 @@ add it to the `populate_indicators()` method in your custom hyperopt file. Each hyperparameter tuning requires a target. This is usually defined as a loss function (sometimes also called objective function), which should decrease for more desirable results, and increase for bad results. -By default, FreqTrade uses a loss function, which has been with freqtrade since the beginning and optimizes mostly for short trade duration and avoiding losses. +By default, Freqtrade uses a loss function, which has been with freqtrade since the beginning and optimizes mostly for short trade duration and avoiding losses. A different loss function can be specified by using the `--hyperopt-loss ` argument. This class should be in its own file within the `user_data/hyperopts/` directory. diff --git a/docs/rest-api.md b/docs/rest-api.md index 187a71c97..b68364f39 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -74,7 +74,7 @@ docker run -d \ ## Consuming the API You can consume the API by using the script `scripts/rest_client.py`. -The client script only requires the `requests` module, so FreqTrade does not need to be installed on the system. +The client script only requires the `requests` module, so Freqtrade does not need to be installed on the system. ``` bash python3 scripts/rest_client.py [optional parameters] diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 1776955b1..cdc8cb8f1 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -59,7 +59,8 @@ AVAILABLE_CLI_OPTIONS = { ), "config": Arg( '-c', '--config', - help=f'Specify configuration file (default: `{constants.DEFAULT_CONFIG}`). ' + help=f'Specify configuration file (default: `userdir/{constants.DEFAULT_CONFIG}` ' + f'or `config.json` whichever exists). ' f'Multiple --config options may be used. ' f'Can be set to `-` to read config from stdin.', action='append', From 1bc26fd07a11bf064098e544e4a42063d3294b90 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 14 Feb 2020 21:46:22 +0300 Subject: [PATCH 097/117] Add printing statuses for list-hyperopts --- freqtrade/commands/list_commands.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index 782cd074e..a2ac388b0 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -38,7 +38,7 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: def _print_objs_tabular(objs: List) -> None: names = [s['name'] for s in objs] - strats_to_print = [{ + objss_to_print = [{ 'name': s['name'] if s['name'] else "--", 'location': s['location'].name, 'status': ("LOAD FAILED" if s['class'] is None @@ -46,7 +46,7 @@ def _print_objs_tabular(objs: List) -> None: else "DUPLICATED NAME") } for s in objs] - print(tabulate(strats_to_print, headers='keys', tablefmt='pipe')) + print(tabulate(objss_to_print, headers='keys', tablefmt='pipe')) def start_list_strategies(args: Dict[str, Any]) -> None: @@ -56,14 +56,14 @@ def start_list_strategies(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) directory = Path(config.get('strategy_path', config['user_data_dir'] / USERPATH_STRATEGIES)) - strategies = StrategyResolver.search_all_objects(directory, not args['print_one_column']) + strategy_objs = StrategyResolver.search_all_objects(directory, not args['print_one_column']) # Sort alphabetically - strategies = sorted(strategies, key=lambda x: x['name']) + strategy_objs = sorted(strategy_objs, key=lambda x: x['name']) if args['print_one_column']: - print('\n'.join([s['name'] for s in strategies])) + print('\n'.join([s['name'] for s in strategy_objs])) else: - _print_objs_tabular(strategies) + _print_objs_tabular(strategy_objs) def start_list_hyperopts(args: Dict[str, Any]) -> None: @@ -75,15 +75,14 @@ def start_list_hyperopts(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) directory = Path(config.get('hyperopt_path', config['user_data_dir'] / USERPATH_HYPEROPTS)) - hyperopts = HyperOptResolver.search_all_objects(directory) + hyperopt_objs = HyperOptResolver.search_all_objects(directory, not args['print_one_column']) # Sort alphabetically - hyperopts = sorted(hyperopts, key=lambda x: x['name']) - hyperopts_to_print = [{'name': s['name'], 'location': s['location'].name} for s in hyperopts] + hyperopt_objs = sorted(hyperopt_objs, key=lambda x: x['name']) if args['print_one_column']: - print('\n'.join([s['name'] for s in hyperopts])) + print('\n'.join([s['name'] for s in hyperopt_objs])) else: - print(tabulate(hyperopts_to_print, headers='keys', tablefmt='pipe')) + _print_objs_tabular(hyperopt_objs) def start_list_timeframes(args: Dict[str, Any]) -> None: From c92e1d97d65b796a061d19081f701cac6aa59ce3 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 14 Feb 2020 21:52:02 +0300 Subject: [PATCH 098/117] Attempt to make mypy happy --- freqtrade/resolvers/iresolver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index 8b5aa1dff..d674daa9a 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -89,7 +89,7 @@ class IResolver: continue module_path = entry.resolve() - obj = next(cls._get_valid_object(module_path, object_name), None) + obj = next(cls._get_valid_object(module_path, object_name), None) # noqa if obj: return (obj, module_path) From 5efbdd25a7776c55eb8fbfb43a0e4b09213b4c41 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 14 Feb 2020 20:04:05 +0100 Subject: [PATCH 099/117] Properly default to user_data/config.json if it exists --- docs/bot-usage.md | 52 +++++++++++++++++++++------------ freqtrade/commands/arguments.py | 10 +++++-- tests/test_arguments.py | 24 +++++++++++++-- tests/test_main.py | 5 +++- 4 files changed, 66 insertions(+), 25 deletions(-) diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 56e6008a1..dbc111d44 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -58,9 +58,10 @@ Common arguments: details. -V, --version show program's version number and exit -c PATH, --config PATH - Specify configuration file (default: `config.json`). - Multiple --config options may be used. Can be set to - `-` to read config from stdin. + Specify configuration file (default: + `userdir/config.json` or `config.json` whichever + exists). Multiple --config options may be used. Can be + set to `-` to read config from stdin. -d PATH, --datadir PATH Path to directory with historical backtesting data. --userdir PATH, --user-data-dir PATH @@ -71,6 +72,7 @@ Strategy arguments: Specify strategy class name which will be used by the bot. --strategy-path PATH Specify additional strategy lookup path. +. ``` @@ -242,12 +244,15 @@ optional arguments: Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). - --logfile FILE Log to the file specified. + --logfile FILE Log to the file specified. Special values are: + 'syslog', 'journald'. See the documentation for more + details. -V, --version show program's version number and exit -c PATH, --config PATH - Specify configuration file (default: `config.json`). - Multiple --config options may be used. Can be set to - `-` to read config from stdin. + Specify configuration file (default: + `userdir/config.json` or `config.json` whichever + exists). Multiple --config options may be used. Can be + set to `-` to read config from stdin. -d PATH, --datadir PATH Path to directory with historical backtesting data. --userdir PATH, --user-data-dir PATH @@ -280,7 +285,7 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--stake-amount STAKE_AMOUNT] [--fee FLOAT] [--hyperopt NAME] [--hyperopt-path PATH] [--eps] [-e INT] - [--spaces {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]] + [--spaces {all,buy,sell,roi,stoploss,trailing,default} [{all,buy,sell,roi,stoploss,trailing,default} ...]] [--dmmp] [--print-all] [--no-color] [--print-json] [-j JOBS] [--random-state INT] [--min-trades INT] [--continue] [--hyperopt-loss NAME] @@ -308,9 +313,9 @@ optional arguments: Allow buying the same pair multiple times (position stacking). -e INT, --epochs INT Specify number of epochs (default: 100). - --spaces {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...] + --spaces {all,buy,sell,roi,stoploss,trailing,default} [{all,buy,sell,roi,stoploss,trailing,default} ...] Specify which parameters to hyperopt. Space-separated - list. Default: `all`. + list. --dmmp, --disable-max-market-positions Disable applying `max_open_trades` during backtest (same as setting `max_open_trades` to a very high @@ -338,16 +343,20 @@ optional arguments: target for optimization is different. Built-in Hyperopt-loss-functions are: DefaultHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss, - SharpeHyperOptLossDaily (default: `DefaultHyperOptLoss`). + SharpeHyperOptLossDaily.(default: + `DefaultHyperOptLoss`). Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). - --logfile FILE Log to the file specified. + --logfile FILE Log to the file specified. Special values are: + 'syslog', 'journald'. See the documentation for more + details. -V, --version show program's version number and exit -c PATH, --config PATH - Specify configuration file (default: `config.json`). - Multiple --config options may be used. Can be set to - `-` to read config from stdin. + Specify configuration file (default: + `userdir/config.json` or `config.json` whichever + exists). Multiple --config options may be used. Can be + set to `-` to read config from stdin. -d PATH, --datadir PATH Path to directory with historical backtesting data. --userdir PATH, --user-data-dir PATH @@ -358,6 +367,7 @@ Strategy arguments: Specify strategy class name which will be used by the bot. --strategy-path PATH Specify additional strategy lookup path. + ``` ## Edge commands @@ -394,12 +404,15 @@ optional arguments: Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). - --logfile FILE Log to the file specified. + --logfile FILE Log to the file specified. Special values are: + 'syslog', 'journald'. See the documentation for more + details. -V, --version show program's version number and exit -c PATH, --config PATH - Specify configuration file (default: `config.json`). - Multiple --config options may be used. Can be set to - `-` to read config from stdin. + Specify configuration file (default: + `userdir/config.json` or `config.json` whichever + exists). Multiple --config options may be used. Can be + set to `-` to read config from stdin. -d PATH, --datadir PATH Path to directory with historical backtesting data. --userdir PATH, --user-data-dir PATH @@ -410,6 +423,7 @@ Strategy arguments: Specify strategy class name which will be used by the bot. --strategy-path PATH Specify additional strategy lookup path. + ``` To understand edge and how to read the results, please read the [edge documentation](edge.md). diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index d37870ea0..580c9e298 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -111,10 +111,14 @@ class Arguments: conf_required = ('command' in parsed_arg and parsed_arg.command in NO_CONF_REQURIED) if 'user_data_dir' in parsed_arg and parsed_arg.user_data_dir is not None: + user_dir = parsed_arg.user_data_dir + else: + # Default case + user_dir = 'user_data' # Try loading from "user_data/config.json" - cfgfile = Path(parsed_arg.user_data_dir) / DEFAULT_CONFIG - if cfgfile.is_file() or not conf_required: - parsed_arg.config = [str(cfgfile)] + cfgfile = Path(user_dir) / DEFAULT_CONFIG + if cfgfile.is_file(): + parsed_arg.config = [str(cfgfile)] else: # Else use "config.json". cfgfile = Path.cwd() / DEFAULT_CONFIG diff --git a/tests/test_arguments.py b/tests/test_arguments.py index 60da0082a..61bca04a4 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -18,7 +18,8 @@ def test_parse_args_none() -> None: assert isinstance(arguments.parser, argparse.ArgumentParser) -def test_parse_args_defaults() -> None: +def test_parse_args_defaults(mocker) -> None: + mocker.patch.object(Path, "is_file", MagicMock(side_effect=[False, True])) args = Arguments(['trade']).get_parsed_arg() assert args["config"] == ['config.json'] assert args["strategy_path"] is None @@ -26,6 +27,25 @@ def test_parse_args_defaults() -> None: assert args["verbosity"] == 0 +def test_parse_args_default_userdatadir(mocker) -> None: + mocker.patch.object(Path, "is_file", MagicMock(return_value=True)) + args = Arguments(['trade']).get_parsed_arg() + # configuration defaults to user_data if that is available. + assert args["config"] == ['user_data/config.json'] + assert args["strategy_path"] is None + assert args["datadir"] is None + assert args["verbosity"] == 0 + + +def test_parse_args_userdatadir(mocker) -> None: + mocker.patch.object(Path, "is_file", MagicMock(return_value=True)) + args = Arguments(['trade', '--user-data-dir', 'user_data']).get_parsed_arg() + # configuration defaults to user_data if that is available. + assert args["config"] == ['user_data/config.json'] + assert args["strategy_path"] is None + assert args["datadir"] is None + assert args["verbosity"] == 0 + def test_parse_args_config() -> None: args = Arguments(['trade', '-c', '/dev/null']).get_parsed_arg() assert args["config"] == ['/dev/null'] @@ -208,7 +228,7 @@ def test_config_notrequired(mocker) -> None: assert pargs["config"] is None # When file exists: - mocker.patch.object(Path, "is_file", MagicMock(return_value=True)) + mocker.patch.object(Path, "is_file", MagicMock(side_effect=[False, True])) args = [ 'download-data', ] diff --git a/tests/test_main.py b/tests/test_main.py index 1229f748a..70b784002 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -5,8 +5,9 @@ from unittest.mock import MagicMock, PropertyMock import pytest +from pathlib import Path from freqtrade.commands import Arguments -from freqtrade.exceptions import OperationalException, FreqtradeException +from freqtrade.exceptions import FreqtradeException, OperationalException from freqtrade.freqtradebot import FreqtradeBot from freqtrade.main import main from freqtrade.state import State @@ -26,6 +27,7 @@ def test_parse_args_backtesting(mocker) -> None: Test that main() can start backtesting and also ensure we can pass some specific arguments further argument parsing is done in test_arguments.py """ + mocker.patch.object(Path, "is_file", MagicMock(side_effect=[False, True])) backtesting_mock = mocker.patch('freqtrade.commands.start_backtesting') backtesting_mock.__name__ = PropertyMock("start_backtesting") # it's sys.exit(0) at the end of backtesting @@ -42,6 +44,7 @@ def test_parse_args_backtesting(mocker) -> None: def test_main_start_hyperopt(mocker) -> None: + mocker.patch.object(Path, "is_file", MagicMock(side_effect=[False, True])) hyperopt_mock = mocker.patch('freqtrade.commands.start_hyperopt', MagicMock()) hyperopt_mock.__name__ = PropertyMock("start_hyperopt") # it's sys.exit(0) at the end of hyperopt From d5a298bbb7dd3db6be8f16c7ec1376fc7c677901 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 14 Feb 2020 20:12:26 +0100 Subject: [PATCH 100/117] Add sentence from suggestion --- docs/strategy_analysis_example.md | 1 + freqtrade/templates/strategy_analysis_example.ipynb | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md index f3f9b46c3..53b35ca09 100644 --- a/docs/strategy_analysis_example.md +++ b/docs/strategy_analysis_example.md @@ -1,6 +1,7 @@ # Strategy analysis example Debugging a strategy can be time-consuming. Freqtrade offers helper functions to visualize raw data. +The following assumes you work with SampleStrategy, data for 5m timeframe from Binance and have downloaded them into the data directory in the default location. ## Setup diff --git a/freqtrade/templates/strategy_analysis_example.ipynb b/freqtrade/templates/strategy_analysis_example.ipynb index 021056df2..399235cfe 100644 --- a/freqtrade/templates/strategy_analysis_example.ipynb +++ b/freqtrade/templates/strategy_analysis_example.ipynb @@ -6,7 +6,8 @@ "source": [ "# Strategy analysis example\n", "\n", - "Debugging a strategy can be time-consuming. Freqtrade offers helper functions to visualize raw data." + "Debugging a strategy can be time-consuming. Freqtrade offers helper functions to visualize raw data.\n", + "The following assumes you work with SampleStrategy, data for 5m timeframe from Binance and have downloaded them into the data directory in the default location." ] }, { From ecca7164d91a41ee252c93c2533e6e7737d0db71 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 14 Feb 2020 20:13:36 +0100 Subject: [PATCH 101/117] Fix small issue --- tests/test_arguments.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_arguments.py b/tests/test_arguments.py index 61bca04a4..22383661b 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -46,6 +46,7 @@ def test_parse_args_userdatadir(mocker) -> None: assert args["datadir"] is None assert args["verbosity"] == 0 + def test_parse_args_config() -> None: args = Arguments(['trade', '-c', '/dev/null']).get_parsed_arg() assert args["config"] == ['/dev/null'] From f024cc40d3808bfd75d55e75f8a8370b86cae57e Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 14 Feb 2020 20:21:09 +0100 Subject: [PATCH 102/117] Fix windows test failure --- tests/test_arguments.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_arguments.py b/tests/test_arguments.py index 22383661b..0052a61d0 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -31,7 +31,7 @@ def test_parse_args_default_userdatadir(mocker) -> None: mocker.patch.object(Path, "is_file", MagicMock(return_value=True)) args = Arguments(['trade']).get_parsed_arg() # configuration defaults to user_data if that is available. - assert args["config"] == ['user_data/config.json'] + assert args["config"] == [str(Path('user_data/config.json'))] assert args["strategy_path"] is None assert args["datadir"] is None assert args["verbosity"] == 0 @@ -41,7 +41,7 @@ def test_parse_args_userdatadir(mocker) -> None: mocker.patch.object(Path, "is_file", MagicMock(return_value=True)) args = Arguments(['trade', '--user-data-dir', 'user_data']).get_parsed_arg() # configuration defaults to user_data if that is available. - assert args["config"] == ['user_data/config.json'] + assert args["config"] == [str(Path('user_data/config.json'))] assert args["strategy_path"] is None assert args["datadir"] is None assert args["verbosity"] == 0 From e598c769d448db0a03c7c7df5d470d3a389d8cee Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 14 Feb 2020 22:28:49 +0300 Subject: [PATCH 103/117] Add colorization --- freqtrade/commands/arguments.py | 4 ++-- freqtrade/commands/list_commands.py | 23 +++++++++++++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index fe6f49039..063a152fe 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -30,9 +30,9 @@ ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"] -ARGS_LIST_STRATEGIES = ["strategy_path", "print_one_column"] +ARGS_LIST_STRATEGIES = ["strategy_path", "print_one_column", "print_colorized"] -ARGS_LIST_HYPEROPTS = ["hyperopt_path", "print_one_column"] +ARGS_LIST_HYPEROPTS = ["hyperopt_path", "print_one_column", "print_colorized"] ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all"] diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index a2ac388b0..6a2ccbfcf 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -5,6 +5,8 @@ from collections import OrderedDict from pathlib import Path from typing import Any, Dict, List +from colorama import init as colorama_init +from colorama import Fore, Style import rapidjson from tabulate import tabulate @@ -36,14 +38,23 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: print(f"Exchanges available for Freqtrade: {', '.join(exchanges)}") -def _print_objs_tabular(objs: List) -> None: +def _print_objs_tabular(objs: List, print_colorized: bool) -> None: + if print_colorized: + colorama_init(autoreset=True) + names = [s['name'] for s in objs] objss_to_print = [{ 'name': s['name'] if s['name'] else "--", 'location': s['location'].name, - 'status': ("LOAD FAILED" if s['class'] is None - else "OK" if names.count(s['name']) == 1 - else "DUPLICATED NAME") + 'status': (((Fore.RED if print_colorized else '') + + "LOAD FAILED" + (Style.RESET_ALL if print_colorized else '')) + if s['class'] is None + else ((Fore.GREEN if print_colorized else '') + + "OK" + (Style.RESET_ALL if print_colorized else '')) + if names.count(s['name']) == 1 + else ((Fore.YELLOW if print_colorized else '') + + "DUPLICATED NAME" + + (Style.RESET_ALL if print_colorized else ''))) } for s in objs] print(tabulate(objss_to_print, headers='keys', tablefmt='pipe')) @@ -63,7 +74,7 @@ def start_list_strategies(args: Dict[str, Any]) -> None: if args['print_one_column']: print('\n'.join([s['name'] for s in strategy_objs])) else: - _print_objs_tabular(strategy_objs) + _print_objs_tabular(strategy_objs, config.get('print_colorized', False)) def start_list_hyperopts(args: Dict[str, Any]) -> None: @@ -82,7 +93,7 @@ def start_list_hyperopts(args: Dict[str, Any]) -> None: if args['print_one_column']: print('\n'.join([s['name'] for s in hyperopt_objs])) else: - _print_objs_tabular(hyperopt_objs) + _print_objs_tabular(hyperopt_objs, config.get('print_colorized', False)) def start_list_timeframes(args: Dict[str, Any]) -> None: From 47a91c9d8ec0706add136b3b1713c586fadf4b5e Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 14 Feb 2020 22:32:46 +0300 Subject: [PATCH 104/117] Remove green color --- freqtrade/commands/list_commands.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index 6a2ccbfcf..67ee59375 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -49,9 +49,7 @@ def _print_objs_tabular(objs: List, print_colorized: bool) -> None: 'status': (((Fore.RED if print_colorized else '') + "LOAD FAILED" + (Style.RESET_ALL if print_colorized else '')) if s['class'] is None - else ((Fore.GREEN if print_colorized else '') + - "OK" + (Style.RESET_ALL if print_colorized else '')) - if names.count(s['name']) == 1 + else "OK" if names.count(s['name']) == 1 else ((Fore.YELLOW if print_colorized else '') + "DUPLICATED NAME" + (Style.RESET_ALL if print_colorized else ''))) From 06b84b4086d6807bb6ac861973028e799dcaea91 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 14 Feb 2020 23:13:49 +0300 Subject: [PATCH 105/117] Remove redundant code --- freqtrade/resolvers/iresolver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index d674daa9a..84dee85cd 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -164,7 +164,7 @@ class IResolver: enum_failed=enum_failed): objects.append( {'name': obj.__name__ if obj is not None else '', - 'class': obj if obj is not None else None, + 'class': obj, 'location': entry, }) return objects From 93f9ff1b636724ad729a88b0dc4cb35fa6612e35 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 15 Feb 2020 04:22:21 +0300 Subject: [PATCH 106/117] Fix existing test --- tests/strategy/test_strategy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/strategy/test_strategy.py b/tests/strategy/test_strategy.py index d3977ae44..1b4b64f89 100644 --- a/tests/strategy/test_strategy.py +++ b/tests/strategy/test_strategy.py @@ -32,7 +32,7 @@ def test_search_strategy(): def test_search_all_strategies(): directory = Path(__file__).parent - strategies = StrategyResolver.search_all_objects(directory) + strategies = StrategyResolver.search_all_objects(directory, enum_failed=False) assert isinstance(strategies, list) assert len(strategies) == 3 assert isinstance(strategies[0], dict) From 29d9b6a46a94ed222a97bb1e14081c1948f2cca1 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 15 Feb 2020 04:32:10 +0300 Subject: [PATCH 107/117] Add test for enum failed --- tests/strategy/test_strategy.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/strategy/test_strategy.py b/tests/strategy/test_strategy.py index 1b4b64f89..3d3f0f424 100644 --- a/tests/strategy/test_strategy.py +++ b/tests/strategy/test_strategy.py @@ -30,7 +30,7 @@ def test_search_strategy(): assert s is None -def test_search_all_strategies(): +def test_search_all_strategies_no_failed(): directory = Path(__file__).parent strategies = StrategyResolver.search_all_objects(directory, enum_failed=False) assert isinstance(strategies, list) @@ -38,6 +38,15 @@ def test_search_all_strategies(): assert isinstance(strategies[0], dict) +def test_search_all_strategies_with_failed(): + directory = Path(__file__).parent + strategies = StrategyResolver.search_all_objects(directory, enum_failed=True) + assert isinstance(strategies, list) + assert len(strategies) == 4 + assert isinstance(strategies[0], dict) + assert strategies[0]['class'] is None + + def test_load_strategy(default_conf, result): default_conf.update({'strategy': 'SampleStrategy', 'strategy_path': str(Path(__file__).parents[2] / 'freqtrade/templates') From 1cf19133f47237fa42e693e96bbdc42f072740ca Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 15 Feb 2020 05:17:34 +0300 Subject: [PATCH 108/117] Added missing failing strategy --- tests/strategy/failing_strategy.py | 87 ++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 tests/strategy/failing_strategy.py diff --git a/tests/strategy/failing_strategy.py b/tests/strategy/failing_strategy.py new file mode 100644 index 000000000..57a8cc1ae --- /dev/null +++ b/tests/strategy/failing_strategy.py @@ -0,0 +1,87 @@ + +# --- Do not remove these libs --- +from freqtrade.strategy.interface import IStrategy +from pandas import DataFrame +# -------------------------------- + +# Add your lib to import here +import talib.abstract as ta + +import nonexiting_module # noqa + + +# This class is a sample. Feel free to customize it. +class TestStrategyLegacy(IStrategy): + """ + This is a test strategy using the legacy function headers, which will be + removed in a future update. + Please do not use this as a template, but refer to user_data/strategy/sample_strategy.py + for a uptodate version of this template. + """ + + # Minimal ROI designed for the strategy. + # This attribute will be overridden if the config file contains "minimal_roi" + minimal_roi = { + "40": 0.0, + "30": 0.01, + "20": 0.02, + "0": 0.04 + } + + # Optimal stoploss designed for the strategy + # This attribute will be overridden if the config file contains "stoploss" + stoploss = -0.10 + + # Optimal ticker interval for the strategy + ticker_interval = '5m' + + def populate_indicators(self, dataframe: DataFrame) -> DataFrame: + """ + Adds several different TA indicators to the given DataFrame + + Performance Note: For the best performance be frugal on the number of indicators + you are using. Let uncomment only the indicator you are using in your strategies + or your hyperopt configuration, otherwise you will waste your memory and CPU usage. + """ + + # Momentum Indicator + # ------------------------------------ + + # ADX + dataframe['adx'] = ta.ADX(dataframe) + + # TEMA - Triple Exponential Moving Average + dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) + + return dataframe + + def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: + """ + Based on TA indicators, populates the buy signal for the given dataframe + :param dataframe: DataFrame + :return: DataFrame with buy column + """ + dataframe.loc[ + ( + (dataframe['adx'] > 30) & + (dataframe['tema'] > dataframe['tema'].shift(1)) & + (dataframe['volume'] > 0) + ), + 'buy'] = 1 + + return dataframe + + def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: + """ + Based on TA indicators, populates the sell signal for the given dataframe + :param dataframe: DataFrame + :return: DataFrame with buy column + """ + dataframe.loc[ + ( + (dataframe['adx'] > 70) & + (dataframe['tema'] < dataframe['tema'].shift(1)) & + (dataframe['volume'] > 0) + ), + 'sell'] = 1 + return dataframe From e8c0a0bcd3b242e49f02a620d489cf352b548aa3 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 15 Feb 2020 06:18:00 +0300 Subject: [PATCH 109/117] Make mypy happy --- freqtrade/resolvers/iresolver.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index 84dee85cd..34f3934b6 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -7,7 +7,7 @@ import importlib.util import inspect import logging from pathlib import Path -from typing import Any, Dict, Generator, List, Optional, Tuple, Type, Union +from typing import Any, Dict, Iterator, List, Optional, Tuple, Type, Union from freqtrade.exceptions import OperationalException @@ -40,10 +40,8 @@ class IResolver: return abs_paths @classmethod - def _get_valid_object(cls, module_path: Path, - object_name: Optional[str], - enum_failed: bool = False) -> Union[Generator[Any, None, None], - Tuple[None]]: + def _get_valid_object(cls, module_path: Path, object_name: Optional[str], + enum_failed: bool = False) -> Iterator[Any]: """ Generator returning objects with matching object_type and object_name in the path given. :param module_path: absolute path to the module @@ -63,7 +61,7 @@ class IResolver: # Catch errors in case a specific module is not installed logger.warning(f"Could not import {module_path} due to '{err}'") if enum_failed: - return (None, ) + return iter([None]) valid_objects_gen = ( obj for name, obj in inspect.getmembers(module, inspect.isclass) From ddea4b9300f12fcd099f0e4f5724bf290ec17881 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 15 Feb 2020 06:54:18 +0300 Subject: [PATCH 110/117] Fix test --- tests/strategy/test_strategy.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/strategy/test_strategy.py b/tests/strategy/test_strategy.py index 3d3f0f424..379260599 100644 --- a/tests/strategy/test_strategy.py +++ b/tests/strategy/test_strategy.py @@ -43,8 +43,10 @@ def test_search_all_strategies_with_failed(): strategies = StrategyResolver.search_all_objects(directory, enum_failed=True) assert isinstance(strategies, list) assert len(strategies) == 4 - assert isinstance(strategies[0], dict) - assert strategies[0]['class'] is None + # with enum_failed=True search_all_objects() shall find 3 good strategies + # and 1 which fails to load + assert len([x for x in strategies if x['class'] is not None]) == 3 + assert len([x for x in strategies if x['class'] is None]) == 1 def test_load_strategy(default_conf, result): From 42a5d78e607f226e4e65cf5157f65bdf28ec54f5 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 15 Feb 2020 07:19:24 +0300 Subject: [PATCH 111/117] Wording (duplicate, not duplicated) --- freqtrade/commands/list_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index 67ee59375..0cfc78596 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -51,7 +51,7 @@ def _print_objs_tabular(objs: List, print_colorized: bool) -> None: if s['class'] is None else "OK" if names.count(s['name']) == 1 else ((Fore.YELLOW if print_colorized else '') + - "DUPLICATED NAME" + + "DUPLICATE NAME" + (Style.RESET_ALL if print_colorized else ''))) } for s in objs] From fdd362299f5c7018f500b626f884f7dd27c17e62 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 15 Feb 2020 07:34:39 +0300 Subject: [PATCH 112/117] Docs adjusted --- docs/utils.md | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index abb7fd0db..cdf0c31af 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -144,38 +144,47 @@ freqtrade new-hyperopt --userdir ~/.freqtrade/ --hyperopt AwesomeHyperopt Use the `list-strategies` subcommand to see all strategies in one particular directory and the `list-hyperopts` subcommand to list custom Hyperopts. +These subcommands are useful for finding problems in your environment with loading strategies or hyperopt classes: modules with strategies or hyperopt classes that contain errors and failed to load are printed in red (LOAD FAILED), while strategies or hyperopt classes with duplicate names are printed in yellow (DUPLICATE NAME). + ``` -freqtrade list-strategies --help -usage: freqtrade list-strategies [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--strategy-path PATH] [-1] +usage: freqtrade list-strategies [-h] [-v] [--logfile FILE] [-V] [-c PATH] + [-d PATH] [--userdir PATH] + [--strategy-path PATH] [-1] [--no-color] optional arguments: -h, --help show this help message and exit --strategy-path PATH Specify additional strategy lookup path. -1, --one-column Print output in one column. + --no-color Disable colorization of hyperopt results. May be + useful if you are redirecting output to a file. Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). - --logfile FILE Log to the file specified. Special values are: 'syslog', 'journald'. See the documentation for more details. + --logfile FILE Log to the file specified. Special values are: + 'syslog', 'journald'. See the documentation for more + details. -V, --version show program's version number and exit -c PATH, --config PATH - Specify configuration file (default: `config.json`). Multiple --config options may be used. Can be set to `-` - to read config from stdin. + Specify configuration file (default: `config.json`). + Multiple --config options may be used. Can be set to + `-` to read config from stdin. -d PATH, --datadir PATH Path to directory with historical backtesting data. --userdir PATH, --user-data-dir PATH Path to userdata directory. ``` ``` -freqtrade list-hyperopts --help usage: freqtrade list-hyperopts [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] - [--hyperopt-path PATH] [-1] + [--hyperopt-path PATH] [-1] [--no-color] optional arguments: -h, --help show this help message and exit --hyperopt-path PATH Specify additional lookup path for Hyperopt and Hyperopt Loss functions. -1, --one-column Print output in one column. + --no-color Disable colorization of hyperopt results. May be + useful if you are redirecting output to a file. Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). From 87b506972f5b99f11e4f5c97a54f69c716283558 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 15 Feb 2020 13:12:29 +0100 Subject: [PATCH 113/117] Fix edge documentation rendering --- docs/edge.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/edge.md b/docs/edge.md index dcefe7451..6a301b044 100644 --- a/docs/edge.md +++ b/docs/edge.md @@ -145,19 +145,19 @@ Edge module has following configuration options: | Parameter | Description | |------------|-------------| -| `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* -| `stoploss_range_step` | As an example if this is set to -0.01 then Edge will test the strategy for `[-0.01, -0,02, -0,03 ..., -0.09, -0.10]` ranges.
**Note** than having a smaller step means having a bigger range which could lead to slow calculation.
If you set this parameter to -0.001, you then slow down the Edge calculation by a factor of 10.
*Defaults to `-0.001`.*
***Datatype:*** *Float* -| `minimum_winrate` | It filters out pairs which don't have at least minimum_winrate.
This comes handy if you want to be conservative and don't comprise win rate in favour of risk reward ratio.
*Defaults to `0.60`.*
***Datatype:*** *Float* -| `minimum_expectancy` | It filters out pairs which have the expectancy lower than this number.
Having an expectancy of 0.20 means if you put 10$ on a trade you expect a 12$ return.
*Defaults to `0.20`.*
***Datatype:*** *Float* -| `min_trade_number` | When calculating *W*, *R* and *E* (expectancy) against historical data, you always want to have a minimum number of trades. The more this number is the more Edge is reliable.
Having a win rate of 100% on a single trade doesn't mean anything at all. But having a win rate of 70% over past 100 trades means clearly something.
*Defaults to `10` (it is highly recommended not to decrease this number).*
***Datatype:*** *Integer* -| `max_trade_duration_minute` | Edge will filter out trades with long duration. If a trade is profitable after 1 month, it is hard to evaluate the strategy based on it. But if most of trades are profitable and they have maximum duration of 30 minutes, then it is clearly a good sign.
**NOTICE:** While configuring this value, you should take into consideration your ticker interval. As an example filtering out trades having duration less than one day for a strategy which has 4h interval does not make sense. Default value is set assuming your strategy interval is relatively small (1m or 5m, etc.).
*Defaults to `1440` (one day).*
***Datatype:*** *Integer* -| `remove_pumps` | Edge will remove sudden pumps in a given market while going through historical data. However, given that pumps happen very often in crypto markets, we recommend you keep this off.
*Defaults to `false`.*
***Datatype:*** *Boolean* +| `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 +| `stoploss_range_step` | As an example if this is set to -0.01 then Edge will test the strategy for `[-0.01, -0,02, -0,03 ..., -0.09, -0.10]` ranges.
**Note** than having a smaller step means having a bigger range which could lead to slow calculation.
If you set this parameter to -0.001, you then slow down the Edge calculation by a factor of 10.
*Defaults to `-0.001`.*
**Datatype:** Float +| `minimum_winrate` | It filters out pairs which don't have at least minimum_winrate.
This comes handy if you want to be conservative and don't comprise win rate in favour of risk reward ratio.
*Defaults to `0.60`.*
**Datatype:** Float +| `minimum_expectancy` | It filters out pairs which have the expectancy lower than this number.
Having an expectancy of 0.20 means if you put 10$ on a trade you expect a 12$ return.
*Defaults to `0.20`.*
**Datatype:** Float +| `min_trade_number` | When calculating *W*, *R* and *E* (expectancy) against historical data, you always want to have a minimum number of trades. The more this number is the more Edge is reliable.
Having a win rate of 100% on a single trade doesn't mean anything at all. But having a win rate of 70% over past 100 trades means clearly something.
*Defaults to `10` (it is highly recommended not to decrease this number).*
**Datatype:** Integer +| `max_trade_duration_minute` | Edge will filter out trades with long duration. If a trade is profitable after 1 month, it is hard to evaluate the strategy based on it. But if most of trades are profitable and they have maximum duration of 30 minutes, then it is clearly a good sign.
**NOTICE:** While configuring this value, you should take into consideration your ticker interval. As an example filtering out trades having duration less than one day for a strategy which has 4h interval does not make sense. Default value is set assuming your strategy interval is relatively small (1m or 5m, etc.).
*Defaults to `1440` (one day).*
**Datatype:** Integer +| `remove_pumps` | Edge will remove sudden pumps in a given market while going through historical data. However, given that pumps happen very often in crypto markets, we recommend you keep this off.
*Defaults to `false`.*
**Datatype:** Boolean ## Running Edge independently From 6139239b863b36522a7d96efdb859e830403bad9 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 15 Feb 2020 20:43:11 +0300 Subject: [PATCH 114/117] Address points stated in comments --- freqtrade/commands/list_commands.py | 15 +++-- freqtrade/resolvers/iresolver.py | 2 +- tests/strategy/failing_strategy.py | 86 ++--------------------------- 3 files changed, 14 insertions(+), 89 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index 0cfc78596..49674b81a 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -41,18 +41,21 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: def _print_objs_tabular(objs: List, print_colorized: bool) -> None: if print_colorized: colorama_init(autoreset=True) + red = Fore.RED + yellow = Fore.YELLOW + reset = Style.RESET_ALL + else: + red = '' + yellow = '' + reset = '' names = [s['name'] for s in objs] objss_to_print = [{ 'name': s['name'] if s['name'] else "--", 'location': s['location'].name, - 'status': (((Fore.RED if print_colorized else '') + - "LOAD FAILED" + (Style.RESET_ALL if print_colorized else '')) - if s['class'] is None + 'status': (red + "LOAD FAILED" + reset if s['class'] is None else "OK" if names.count(s['name']) == 1 - else ((Fore.YELLOW if print_colorized else '') + - "DUPLICATE NAME" + - (Style.RESET_ALL if print_colorized else ''))) + else yellow + "DUPLICATE NAME" + reset) } for s in objs] print(tabulate(objss_to_print, headers='keys', tablefmt='pipe')) diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index 34f3934b6..922a2700a 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -87,7 +87,7 @@ class IResolver: continue module_path = entry.resolve() - obj = next(cls._get_valid_object(module_path, object_name), None) # noqa + obj = next(cls._get_valid_object(module_path, object_name), None) if obj: return (obj, module_path) diff --git a/tests/strategy/failing_strategy.py b/tests/strategy/failing_strategy.py index 57a8cc1ae..f8eaac3c3 100644 --- a/tests/strategy/failing_strategy.py +++ b/tests/strategy/failing_strategy.py @@ -1,87 +1,9 @@ - -# --- Do not remove these libs --- -from freqtrade.strategy.interface import IStrategy -from pandas import DataFrame -# -------------------------------- - -# Add your lib to import here -import talib.abstract as ta +# The strategy which fails to load due to non-existent dependency import nonexiting_module # noqa +from freqtrade.strategy.interface import IStrategy + -# This class is a sample. Feel free to customize it. class TestStrategyLegacy(IStrategy): - """ - This is a test strategy using the legacy function headers, which will be - removed in a future update. - Please do not use this as a template, but refer to user_data/strategy/sample_strategy.py - for a uptodate version of this template. - """ - - # Minimal ROI designed for the strategy. - # This attribute will be overridden if the config file contains "minimal_roi" - minimal_roi = { - "40": 0.0, - "30": 0.01, - "20": 0.02, - "0": 0.04 - } - - # Optimal stoploss designed for the strategy - # This attribute will be overridden if the config file contains "stoploss" - stoploss = -0.10 - - # Optimal ticker interval for the strategy - ticker_interval = '5m' - - def populate_indicators(self, dataframe: DataFrame) -> DataFrame: - """ - Adds several different TA indicators to the given DataFrame - - Performance Note: For the best performance be frugal on the number of indicators - you are using. Let uncomment only the indicator you are using in your strategies - or your hyperopt configuration, otherwise you will waste your memory and CPU usage. - """ - - # Momentum Indicator - # ------------------------------------ - - # ADX - dataframe['adx'] = ta.ADX(dataframe) - - # TEMA - Triple Exponential Moving Average - dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) - - return dataframe - - def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: - """ - Based on TA indicators, populates the buy signal for the given dataframe - :param dataframe: DataFrame - :return: DataFrame with buy column - """ - dataframe.loc[ - ( - (dataframe['adx'] > 30) & - (dataframe['tema'] > dataframe['tema'].shift(1)) & - (dataframe['volume'] > 0) - ), - 'buy'] = 1 - - return dataframe - - def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: - """ - Based on TA indicators, populates the sell signal for the given dataframe - :param dataframe: DataFrame - :return: DataFrame with buy column - """ - dataframe.loc[ - ( - (dataframe['adx'] > 70) & - (dataframe['tema'] < dataframe['tema'].shift(1)) & - (dataframe['volume'] > 0) - ), - 'sell'] = 1 - return dataframe + pass From 6e71f2f1662246b848d8137223c8449068f2348e Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Sat, 15 Feb 2020 20:55:12 +0100 Subject: [PATCH 115/117] my fix --- freqtrade/rpc/rpc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index c182aad2b..f6ac999f7 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -168,9 +168,9 @@ class RPC: profit_str += f" ({fiat_profit:.2f})" trades_list.append([ trade.id, - trade.pair + '*' if (trade.open_order_id is not None - and trade.close_rate_requested is None) else '' - + '**' if (trade.close_rate_requested is not None) else '', + trade.pair + ['', '*'][trade.open_order_id is not None + and trade.close_rate_requested is None] + + ['', '**'][trade.close_rate_requested is not None], shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)), profit_str ]) From 180939a962e1214b744999c449753be26024edad Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Sat, 15 Feb 2020 21:01:45 +0100 Subject: [PATCH 116/117] winner, readability, with brackets as fix --- freqtrade/rpc/rpc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index f6ac999f7..3411318bb 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -168,9 +168,9 @@ class RPC: profit_str += f" ({fiat_profit:.2f})" trades_list.append([ trade.id, - trade.pair + ['', '*'][trade.open_order_id is not None - and trade.close_rate_requested is None] - + ['', '**'][trade.close_rate_requested is not None], + trade.pair + ('*' if (trade.open_order_id is not None + and trade.close_rate_requested is None) else '') + + ('**' if (trade.close_rate_requested is not None) else ''), shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)), profit_str ]) From 3787ac7b980e16d615cebe5a942a8d45490ba72c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 16 Feb 2020 13:20:11 +0100 Subject: [PATCH 117/117] increment limit to adjust to FTX defaults (1500 candles) --- freqtrade/exchange/__init__.py | 18 ++++++++++-------- freqtrade/exchange/ftx.py | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 freqtrade/exchange/ftx.py diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index df18bca02..a39f8f5df 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -1,18 +1,20 @@ -from freqtrade.exchange.common import MAP_EXCHANGE_CHILDCLASS # noqa: F401 -from freqtrade.exchange.exchange import Exchange # noqa: F401 -from freqtrade.exchange.exchange import (get_exchange_bad_reason, # noqa: F401 +# flake8: noqa: F401 +from freqtrade.exchange.common import MAP_EXCHANGE_CHILDCLASS +from freqtrade.exchange.exchange import Exchange +from freqtrade.exchange.exchange import (get_exchange_bad_reason, is_exchange_bad, is_exchange_known_ccxt, is_exchange_officially_supported, ccxt_exchanges, available_exchanges) -from freqtrade.exchange.exchange import (timeframe_to_seconds, # noqa: F401 +from freqtrade.exchange.exchange import (timeframe_to_seconds, timeframe_to_minutes, timeframe_to_msecs, timeframe_to_next_date, timeframe_to_prev_date) -from freqtrade.exchange.exchange import (market_is_active, # noqa: F401 +from freqtrade.exchange.exchange import (market_is_active, symbol_is_pair) -from freqtrade.exchange.kraken import Kraken # noqa: F401 -from freqtrade.exchange.binance import Binance # noqa: F401 -from freqtrade.exchange.bibox import Bibox # noqa: F401 +from freqtrade.exchange.kraken import Kraken +from freqtrade.exchange.binance import Binance +from freqtrade.exchange.bibox import Bibox +from freqtrade.exchange.ftx import Ftx diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py new file mode 100644 index 000000000..75915122b --- /dev/null +++ b/freqtrade/exchange/ftx.py @@ -0,0 +1,14 @@ +""" FTX exchange subclass """ +import logging +from typing import Dict + +from freqtrade.exchange import Exchange + +logger = logging.getLogger(__name__) + + +class Ftx(Exchange): + + _ft_has: Dict = { + "ohlcv_candle_limit": 1500, + }