From f200f52a163b67348a1e41c4cb5026e59c21d5d2 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 3 Aug 2019 19:09:42 +0300 Subject: [PATCH 01/43] hyperopt print colorized results --- docs/hyperopt.md | 4 ++++ freqtrade/configuration/arguments.py | 3 ++- freqtrade/configuration/cli_options.py | 6 ++++++ freqtrade/configuration/configuration.py | 3 +++ freqtrade/misc.py | 12 ++++++++++++ freqtrade/optimize/hyperopt.py | 18 ++++++++++++++++-- 6 files changed, 43 insertions(+), 3 deletions(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 0b5d1a50e..2826dfc3b 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -343,6 +343,10 @@ def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: return dataframe ``` +You can use the `--print-all` command line option if you would like to see all results in the hyperopt output, not only the best ones. + +When the `--color/--print-colorized` command line option is used, the results are colorized -- bad results (with zero trades or limited by the `--min-trades` option) are red, currest bests -- in green, results with positive total profit are printed in bold. + ### Understand Hyperopt ROI results If you are optimizing ROI, you're result will look as follows and include a ROI table. diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index 4f0c3d31b..dd5a4290e 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -23,7 +23,8 @@ ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_pos ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", "position_stacking", "epochs", "spaces", - "use_max_market_positions", "print_all", "hyperopt_jobs", + "use_max_market_positions", "print_all", + "print_colorized", "hyperopt_jobs", "hyperopt_random_state", "hyperopt_min_trades", "hyperopt_continue", "hyperopt_loss"] diff --git a/freqtrade/configuration/cli_options.py b/freqtrade/configuration/cli_options.py index 04fde2051..aab3c12b3 100644 --- a/freqtrade/configuration/cli_options.py +++ b/freqtrade/configuration/cli_options.py @@ -191,6 +191,12 @@ AVAILABLE_CLI_OPTIONS = { action='store_true', default=False, ), + "print_colorized": Arg( + '--color', '--print-colorized', + help='Print colorized hyperopt results.', + action='store_true', + default=False + ), "hyperopt_jobs": Arg( '-j', '--job-workers', help='The number of concurrently running jobs for hyperoptimization ' diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 17ad37d6a..01732ca9e 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -254,6 +254,9 @@ class Configuration(object): self._args_to_config(config, argname='print_all', logstring='Parameter --print-all detected ...') + self._args_to_config(config, argname='print_colorized', + logstring='Parameter --color/--print-colorized detected ...') + self._args_to_config(config, argname='hyperopt_jobs', logstring='Parameter -j/--job-workers detected: {}') diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 05946e008..eadfc7490 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -113,3 +113,15 @@ def deep_merge_dicts(source, destination): destination[key] = value return destination + + +def green(s): + return '\033[92m' + s + '\033[0m' + + +def red(s): + return '\033[91m' + s + '\033[0m' + + +def bold(s): + return '\033[1m' + s + '\033[0m' diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 427b17cb8..e62cbe66b 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -20,6 +20,7 @@ from skopt.space import Dimension from freqtrade.configuration import Arguments from freqtrade.data.history import load_data, get_timeframe +from freqtrade.misc import green, red, bold from freqtrade.optimize.backtesting import Backtesting # Import IHyperOptLoss to allow users import from this file from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F4 @@ -153,8 +154,19 @@ class Hyperopt(Backtesting): Log results if it is better than any previous evaluation """ print_all = self.config.get('print_all', False) - if print_all or results['loss'] < self.current_best_loss: + is_best_loss = results['loss'] < self.current_best_loss + if print_all or is_best_loss: + if is_best_loss: + self.current_best_loss = results['loss'] log_str = self.format_results_logstring(results) + # Colorize output + if self.config.get('print_colorized', False): + if results['total_profit'] > 0: + log_str = bold(log_str) + if results['loss'] >= MAX_LOSS: + log_str = red(log_str) + elif is_best_loss: + log_str = green(log_str) if print_all: print(log_str) else: @@ -169,7 +181,6 @@ class Hyperopt(Backtesting): total = self.total_epochs res = results['results_explanation'] loss = results['loss'] - self.current_best_loss = results['loss'] log_str = f'{current:5d}/{total}: {res} Objective: {loss:.5f}' log_str = f'*{log_str}' if results['is_initial_point'] else f' {log_str}' return log_str @@ -237,6 +248,7 @@ class Hyperopt(Backtesting): results_explanation = self.format_results(results) trade_count = len(results.index) + total_profit = results.profit_abs.sum() # If this evaluation contains too short amount of trades to be # interesting -- consider it as 'bad' (assigned max. loss value) @@ -247,6 +259,7 @@ class Hyperopt(Backtesting): 'loss': MAX_LOSS, 'params': params, 'results_explanation': results_explanation, + 'total_profit': total_profit, } loss = self.calculate_loss(results=results, trade_count=trade_count, @@ -256,6 +269,7 @@ class Hyperopt(Backtesting): 'loss': loss, 'params': params, 'results_explanation': results_explanation, + 'total_profit': total_profit, } def format_results(self, results: DataFrame) -> str: From fe796c46c329d51c36761ef74ec24a53fc4dbf3e Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 3 Aug 2019 19:13:18 +0300 Subject: [PATCH 02/43] test adjusted --- freqtrade/tests/optimize/test_hyperopt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index e3b049c06..4fb08181a 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -605,7 +605,8 @@ def test_generate_optimizer(mocker, default_conf) -> None: 'loss': 1.9840569076926293, 'results_explanation': ' 1 trades. Avg profit 2.31%. Total profit 0.00023300 BTC ' '( 2.31Σ%). Avg duration 100.0 mins.', - 'params': optimizer_param + 'params': optimizer_param, + 'total_profit': 0.00023300 } hyperopt = Hyperopt(default_conf) From 3dd6fe2703a4884eaa43443980e523465b381435 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 3 Aug 2019 19:44:32 +0300 Subject: [PATCH 03/43] wording --- docs/hyperopt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 2826dfc3b..16e2212d5 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -345,7 +345,7 @@ def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: You can use the `--print-all` command line option if you would like to see all results in the hyperopt output, not only the best ones. -When the `--color/--print-colorized` command line option is used, the results are colorized -- bad results (with zero trades or limited by the `--min-trades` option) are red, currest bests -- in green, results with positive total profit are printed in bold. +When the `--color/--print-colorized` command line option is used, the results are colorized -- bad results (with zero trades or limited by the `--min-trades` option) are red, current bests -- in green, results with positive total profit are printed in bold. ### Understand Hyperopt ROI results From 9cbab35de01db7338563d4a029d4c62d7cbd9fe7 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sun, 4 Aug 2019 22:54:19 +0300 Subject: [PATCH 04/43] colorization by means of termcolor and colorama --- freqtrade/misc.py | 12 ------------ freqtrade/optimize/hyperopt.py | 12 ++++++++---- requirements-common.txt | 4 ++++ 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/freqtrade/misc.py b/freqtrade/misc.py index eadfc7490..05946e008 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -113,15 +113,3 @@ def deep_merge_dicts(source, destination): destination[key] = value return destination - - -def green(s): - return '\033[92m' + s + '\033[0m' - - -def red(s): - return '\033[91m' + s + '\033[0m' - - -def bold(s): - return '\033[1m' + s + '\033[0m' diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index e62cbe66b..fc0fb56b2 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -13,14 +13,15 @@ from pathlib import Path from pprint import pprint from typing import Any, Dict, List, Optional +from colorama import init as colorama_init from joblib import Parallel, delayed, dump, load, wrap_non_picklable_objects, cpu_count from pandas import DataFrame from skopt import Optimizer from skopt.space import Dimension +from termcolor import colored from freqtrade.configuration import Arguments from freqtrade.data.history import load_data, get_timeframe -from freqtrade.misc import green, red, bold from freqtrade.optimize.backtesting import Backtesting # Import IHyperOptLoss to allow users import from this file from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F4 @@ -162,11 +163,11 @@ class Hyperopt(Backtesting): # Colorize output if self.config.get('print_colorized', False): if results['total_profit'] > 0: - log_str = bold(log_str) + log_str = colored(log_str, attrs=['bold']) if results['loss'] >= MAX_LOSS: - log_str = red(log_str) + log_str = colored(log_str, 'red') elif is_best_loss: - log_str = green(log_str) + log_str = colored(log_str, 'green') if print_all: print(log_str) else: @@ -353,6 +354,9 @@ class Hyperopt(Backtesting): logger.info(f'Number of parallel jobs set as: {config_jobs}') opt = self.get_optimizer(config_jobs) + + colorama_init() + try: with Parallel(n_jobs=config_jobs) as parallel: jobs = parallel._effective_n_jobs() diff --git a/requirements-common.txt b/requirements-common.txt index 2e52b84ad..b0c4c6729 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -30,3 +30,7 @@ sdnotify==0.3.2 # Api server flask==1.1.1 + +# Support for colorized terminal output +termcolor==1.1.0 +colorama==0.4.1 From ae39f6fba510bf1d285433e18a91b75ecd996dc2 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 9 Aug 2019 14:48:57 +0300 Subject: [PATCH 05/43] use of termcolor eliminated --- freqtrade/optimize/hyperopt.py | 11 ++++++----- requirements-common.txt | 1 - 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index fc0fb56b2..802c848f1 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -14,11 +14,11 @@ from pprint import pprint from typing import Any, Dict, List, Optional from colorama import init as colorama_init +from colorama import Fore, Style from joblib import Parallel, delayed, dump, load, wrap_non_picklable_objects, cpu_count from pandas import DataFrame from skopt import Optimizer from skopt.space import Dimension -from termcolor import colored from freqtrade.configuration import Arguments from freqtrade.data.history import load_data, get_timeframe @@ -163,11 +163,11 @@ class Hyperopt(Backtesting): # Colorize output if self.config.get('print_colorized', False): if results['total_profit'] > 0: - log_str = colored(log_str, attrs=['bold']) + log_str = Style.BRIGHT + log_str if results['loss'] >= MAX_LOSS: - log_str = colored(log_str, 'red') + log_str = Fore.RED + log_str elif is_best_loss: - log_str = colored(log_str, 'green') + log_str = Fore.GREEN + log_str if print_all: print(log_str) else: @@ -355,7 +355,8 @@ class Hyperopt(Backtesting): opt = self.get_optimizer(config_jobs) - colorama_init() + if self.config.get('print_colorized', False): + colorama_init(autoreset=True) try: with Parallel(n_jobs=config_jobs) as parallel: diff --git a/requirements-common.txt b/requirements-common.txt index b0c4c6729..9b62cae5c 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -32,5 +32,4 @@ sdnotify==0.3.2 flask==1.1.1 # Support for colorized terminal output -termcolor==1.1.0 colorama==0.4.1 From 2d60e4b18b2c982c88578cdd6ff81f0c6b993396 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Mon, 12 Aug 2019 00:32:03 +0300 Subject: [PATCH 06/43] allow comments and trailing commas in config files --- freqtrade/configuration/load_config.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/freqtrade/configuration/load_config.py b/freqtrade/configuration/load_config.py index 25504144f..7a3ca1798 100644 --- a/freqtrade/configuration/load_config.py +++ b/freqtrade/configuration/load_config.py @@ -1,7 +1,7 @@ """ This module contain functions to load the configuration file """ -import json +import rapidjson import logging import sys from typing import Any, Dict @@ -12,6 +12,9 @@ from freqtrade import OperationalException logger = logging.getLogger(__name__) +CONFIG_PARSE_MODE = rapidjson.PM_COMMENTS | rapidjson.PM_TRAILING_COMMAS + + def load_config_file(path: str) -> Dict[str, Any]: """ Loads a config file from the given path @@ -21,7 +24,7 @@ def load_config_file(path: str) -> Dict[str, Any]: try: # Read config from stdin if requested in the options with open(path) if path != '-' else sys.stdin as file: - config = json.load(file) + config = rapidjson.load(file, parse_mode=CONFIG_PARSE_MODE) except FileNotFoundError: raise OperationalException( f'Config file "{path}" not found!' From 90b75afdb1373ee3c38eeeb495f053d4f3306abd Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Mon, 12 Aug 2019 00:33:34 +0300 Subject: [PATCH 07/43] test added to load config with comments and trailing commas --- freqtrade/tests/config_test_comments.json | 133 ++++++++++++++++++++++ freqtrade/tests/test_configuration.py | 11 ++ 2 files changed, 144 insertions(+) create mode 100644 freqtrade/tests/config_test_comments.json diff --git a/freqtrade/tests/config_test_comments.json b/freqtrade/tests/config_test_comments.json new file mode 100644 index 000000000..85becc3e8 --- /dev/null +++ b/freqtrade/tests/config_test_comments.json @@ -0,0 +1,133 @@ +{ + /* Single-line C-style comment */ + "max_open_trades": 3, + /* + * Multi-line C-style comment + */ + "stake_currency": "BTC", + "stake_amount": 0.05, + "fiat_display_currency": "USD", // C++-style comment + "amount_reserve_percent" : 0.05, // And more, tabs before this comment + "dry_run": false, + "ticker_interval": "5m", + "trailing_stop": false, + "trailing_stop_positive": 0.005, + "trailing_stop_positive_offset": 0.0051, + "trailing_only_offset_is_reached": false, + "minimal_roi": { + "40": 0.0, + "30": 0.01, + "20": 0.02, + "0": 0.04 + }, + "stoploss": -0.10, + "unfilledtimeout": { + "buy": 10, + "sell": 30, // Trailing comma should also be accepted now + }, + "bid_strategy": { + "use_order_book": false, + "ask_last_balance": 0.0, + "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 + }, + "order_types": { + "buy": "limit", + "sell": "limit", + "stoploss": "market", + "stoploss_on_exchange": false, + "stoploss_on_exchange_interval": 60 + }, + "order_time_in_force": { + "buy": "gtc", + "sell": "gtc" + }, + "pairlist": { + "method": "VolumePairList", + "config": { + "number_assets": 20, + "sort_key": "quoteVolume", + "precision_filter": false + } + }, + "exchange": { + "name": "bittrex", + "sandbox": false, + "key": "your_exchange_key", + "secret": "your_exchange_secret", + "password": "", + "ccxt_config": {"enableRateLimit": true}, + "ccxt_async_config": { + "enableRateLimit": false, + "rateLimit": 500, + "aiohttp_trust_env": false + }, + "pair_whitelist": [ + "ETH/BTC", + "LTC/BTC", + "ETC/BTC", + "DASH/BTC", + "ZEC/BTC", + "XLM/BTC", + "NXT/BTC", + "POWR/BTC", + "ADA/BTC", + "XMR/BTC" + ], + "pair_blacklist": [ + "DOGE/BTC" + ], + "outdated_offset": 5, + "markets_refresh_interval": 60 + }, + "edge": { + "enabled": false, + "process_throttle_secs": 3600, + "calculate_since_number_of_days": 7, + "capital_available_percentage": 0.5, + "allowed_risk": 0.01, + "stoploss_range_min": -0.01, + "stoploss_range_max": -0.1, + "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 + }, + "experimental": { + "use_sell_signal": false, + "sell_profit_only": false, + "ignore_roi_if_buy_signal": false + }, + "telegram": { +// We can now comment out some settings +// "enabled": true, + "enabled": false, + "token": "your_telegram_token", + "chat_id": "your_telegram_chat_id" + }, + "api_server": { + "enabled": false, + "listen_ip_address": "127.0.0.1", + "listen_port": 8080, + "username": "freqtrader", + "password": "SuperSecurePassword" + }, + "db_url": "sqlite:///tradesv3.sqlite", + "initial_state": "running", + "forcebuy_enable": false, + "internals": { + "process_throttle_secs": 5 + }, + "strategy": "DefaultStrategy", + "strategy_path": "user_data/strategies/" +} diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index e325a0de2..0a8381089 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -634,6 +634,17 @@ def test_validate_tsl(default_conf): configuration._validate_config_consistency(default_conf) +def test_load_config_test_comments() -> None: + """ + Load config with comments + """ + config_file = Path(__file__).parents[0] / "config_test_comments.json" + print(config_file) + conf = load_config_file(str(config_file)) + + assert conf + + def test_load_config_default_exchange(all_conf) -> None: """ config['exchange'] subtree has required options in it From 241d5100965ab0180a3393e06f37354c040ab2cf Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2019 16:34:55 +0200 Subject: [PATCH 08/43] Handle and update sell-orders immediately if they are closed --- freqtrade/freqtradebot.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 603b0631f..b384902b2 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -875,15 +875,18 @@ class FreqtradeBot(object): logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}") # Execute sell and update trade record - order_id = self.exchange.sell(pair=str(trade.pair), - ordertype=self.strategy.order_types[sell_type], - amount=trade.amount, rate=limit, - time_in_force=self.strategy.order_time_in_force['sell'] - )['id'] + order = self.exchange.sell(pair=str(trade.pair), + ordertype=self.strategy.order_types[sell_type], + amount=trade.amount, rate=limit, + time_in_force=self.strategy.order_time_in_force['sell'] + ) - trade.open_order_id = order_id + trade.open_order_id = order['id'] trade.close_rate_requested = limit trade.sell_reason = sell_reason.value + # In case of market sell orders the order can be closed immediately + if order.get('status', 'unknown') == 'closed': + trade.update(order) Trade.session.flush() self._notify_sell(trade) From bb0b1600016b87796d4949c1dae5ecd4d10c7e18 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2019 16:39:21 +0200 Subject: [PATCH 09/43] Remove duplicate test --- freqtrade/tests/test_freqtradebot.py | 47 ---------------------------- 1 file changed, 47 deletions(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 4e649250a..8c241197b 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -2374,53 +2374,6 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, assert rpc_mock.call_count == 2 -def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee, - ticker_sell_up, markets, mocker) -> None: - rpc_mock = patch_RPCManager(mocker) - mocker.patch.multiple( - 'freqtrade.exchange.Exchange', - _load_markets=MagicMock(return_value={}), - get_ticker=ticker, - get_fee=fee, - markets=PropertyMock(return_value=markets) - ) - freqtrade = FreqtradeBot(default_conf) - patch_get_signal(freqtrade) - - # Create some test data - freqtrade.create_trade() - - trade = Trade.query.first() - assert trade - - # Increase the price and sell it - mocker.patch.multiple( - 'freqtrade.exchange.Exchange', - get_ticker=ticker_sell_up - ) - freqtrade.config = {} - - freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sell_reason=SellType.ROI) - - assert rpc_mock.call_count == 2 - last_msg = rpc_mock.call_args_list[-1][0][0] - assert { - 'type': RPCMessageType.SELL_NOTIFICATION, - 'exchange': 'Bittrex', - 'pair': 'ETH/BTC', - 'gain': 'profit', - 'limit': 1.172e-05, - 'amount': 90.99181073703367, - 'order_type': 'limit', - 'open_rate': 1.099e-05, - 'current_rate': 1.172e-05, - 'profit_amount': 6.126e-05, - 'profit_percent': 0.0611052, - 'sell_reason': SellType.ROI.value - - } == last_msg - - def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee, ticker_sell_down, markets, mocker) -> None: rpc_mock = patch_RPCManager(mocker) From 444ee274d727cfe280d03f115b56226556a70d64 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2019 16:46:34 +0200 Subject: [PATCH 10/43] close dry-run orders in case of market orders --- freqtrade/exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 657f382d8..5bde4ce98 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -371,7 +371,7 @@ class Exchange(object): 'side': side, 'remaining': amount, 'datetime': arrow.utcnow().isoformat(), - 'status': "open", + 'status': "closed" if ordertype == "market" else "open", 'fee': None, "info": {} } From feced71a6d8fff0bf4fdd5d1a5db45b948d3182c Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Aug 2019 16:47:00 +0200 Subject: [PATCH 11/43] Test closing sell-orders immediately --- freqtrade/tests/test_freqtradebot.py | 52 ++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 8c241197b..9ced5c5a1 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -2374,6 +2374,58 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, assert rpc_mock.call_count == 2 +def test_execute_sell_market_order(default_conf, ticker, fee, + ticker_sell_up, markets, mocker) -> None: + rpc_mock = patch_RPCManager(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + _load_markets=MagicMock(return_value={}), + get_ticker=ticker, + get_fee=fee, + markets=PropertyMock(return_value=markets) + ) + freqtrade = FreqtradeBot(default_conf) + patch_get_signal(freqtrade) + + # Create some test data + freqtrade.create_trade() + + trade = Trade.query.first() + assert trade + + # Increase the price and sell it + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_ticker=ticker_sell_up + ) + freqtrade.config['order_types']['sell'] = 'market' + + freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sell_reason=SellType.ROI) + + assert not trade.is_open + assert trade.close_profit == 0.0611052 + + assert rpc_mock.call_count == 2 + last_msg = rpc_mock.call_args_list[-1][0][0] + assert { + 'type': RPCMessageType.SELL_NOTIFICATION, + 'exchange': 'Bittrex', + 'pair': 'ETH/BTC', + 'gain': 'profit', + 'limit': 1.172e-05, + 'amount': 90.99181073703367, + 'order_type': 'market', + 'open_rate': 1.099e-05, + 'current_rate': 1.172e-05, + 'profit_amount': 6.126e-05, + 'profit_percent': 0.0611052, + 'stake_currency': 'BTC', + 'fiat_currency': 'USD', + 'sell_reason': SellType.ROI.value + + } == last_msg + + def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee, ticker_sell_down, markets, mocker) -> None: rpc_mock = patch_RPCManager(mocker) From 0bd71db5df0c66762bd32b01ef9f3a4d3017140d Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 12 Aug 2019 15:25:05 +0000 Subject: [PATCH 12/43] Update scipy from 1.3.0 to 1.3.1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6420c7879..9d558b5b8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,4 @@ numpy==1.17.0 pandas==0.25.0 -scipy==1.3.0 +scipy==1.3.1 From c4cdd85e807aa763fbf9271d3035fa40e4a40e62 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 12 Aug 2019 15:25:06 +0000 Subject: [PATCH 13/43] Update ccxt from 1.18.1021 to 1.18.1043 --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 1413539c3..8eabfc5ea 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.18.1021 +ccxt==1.18.1043 SQLAlchemy==1.3.6 python-telegram-bot==11.1.0 arrow==0.14.4 From 6f42d6658fcc99b6f5f98ed6aee2778118c5ce0e Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 12 Aug 2019 15:25:08 +0000 Subject: [PATCH 14/43] Update arrow from 0.14.4 to 0.14.5 --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 8eabfc5ea..52b5cc590 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -3,7 +3,7 @@ ccxt==1.18.1043 SQLAlchemy==1.3.6 python-telegram-bot==11.1.0 -arrow==0.14.4 +arrow==0.14.5 cachetools==3.1.1 requests==2.22.0 urllib3==1.25.3 From dd30d746884fd805fc89520c90dba79c61cdd03c Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 12 Aug 2019 15:25:09 +0000 Subject: [PATCH 15/43] Update python-rapidjson from 0.7.2 to 0.8.0 --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 52b5cc590..19beec64b 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -23,7 +23,7 @@ filelock==3.0.12 py_find_1st==1.1.4 #Load ticker files 30% faster -python-rapidjson==0.7.2 +python-rapidjson==0.8.0 # Notify systemd sdnotify==0.3.2 From 8f9291285238037d5483807e479741cde9f3de33 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 10 Aug 2019 21:59:44 +0300 Subject: [PATCH 16/43] final colorization schema colorization schema-2: red, green, bright/dim colorization schema-3: red, green, bright only green bests colorization schema-4: no red, green for profit, bright for bests --- freqtrade/optimize/hyperopt.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 802c848f1..550f13e28 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -163,11 +163,9 @@ class Hyperopt(Backtesting): # Colorize output if self.config.get('print_colorized', False): if results['total_profit'] > 0: - log_str = Style.BRIGHT + log_str - if results['loss'] >= MAX_LOSS: - log_str = Fore.RED + log_str - elif is_best_loss: log_str = Fore.GREEN + log_str + if print_all and is_best_loss: + log_str = Style.BRIGHT + log_str if print_all: print(log_str) else: From 1a34b9b61ca481b5a7472723c8a780ce45c4a194 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Mon, 12 Aug 2019 21:07:29 +0300 Subject: [PATCH 17/43] --no-color option introduced --- freqtrade/configuration/cli_options.py | 9 +++++---- freqtrade/configuration/configuration.py | 5 +++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/freqtrade/configuration/cli_options.py b/freqtrade/configuration/cli_options.py index aab3c12b3..a5784b0c1 100644 --- a/freqtrade/configuration/cli_options.py +++ b/freqtrade/configuration/cli_options.py @@ -192,10 +192,11 @@ AVAILABLE_CLI_OPTIONS = { default=False, ), "print_colorized": Arg( - '--color', '--print-colorized', - help='Print colorized hyperopt results.', - action='store_true', - default=False + '--no-color', + help='Disable colorization of hyperopt results. May be useful if you are ' + 'redirecting output to a file.', + action='store_false', + default=True, ), "hyperopt_jobs": Arg( '-j', '--job-workers', diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 01732ca9e..4d85d5c28 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -254,8 +254,9 @@ class Configuration(object): self._args_to_config(config, argname='print_all', logstring='Parameter --print-all detected ...') - self._args_to_config(config, argname='print_colorized', - logstring='Parameter --color/--print-colorized detected ...') + if 'print_colorized' in self.args and not self.args.print_colorized: + logger.info('Parameter --no-color detected ...') + config.update({'print_colorized': getattr(self.args, 'print_colorized')}) self._args_to_config(config, argname='hyperopt_jobs', logstring='Parameter -j/--job-workers detected: {}') From 58d308fd05cef7339445fb91226ff6f052021ca0 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Mon, 12 Aug 2019 23:13:04 +0300 Subject: [PATCH 18/43] fix handling --no-color for edge and backtesting --- freqtrade/configuration/configuration.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 4d85d5c28..a2e16d5d9 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -256,7 +256,9 @@ class Configuration(object): if 'print_colorized' in self.args and not self.args.print_colorized: logger.info('Parameter --no-color detected ...') - config.update({'print_colorized': getattr(self.args, 'print_colorized')}) + config.update({'print_colorized': False}) + else: + config.update({'print_colorized': True}) self._args_to_config(config, argname='hyperopt_jobs', logstring='Parameter -j/--job-workers detected: {}') From 482847a99417a9c582492d685e5a225ad326150d Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 13 Aug 2019 00:10:33 +0300 Subject: [PATCH 19/43] docs adjusted; various fixes to bot-usage.md and configuration.md --- docs/bot-usage.md | 15 +++++++++------ docs/configuration.md | 19 ++++++++++++++----- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 0ca2f3cc5..f720bf554 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -2,7 +2,7 @@ This page explains the different parameters of the bot and how to run it. -!Note: +!!! Note: If you've used `setup.sh`, don't forget to activate your virtual environment (`source .env/bin/activate`) before running freqtrade commands. @@ -43,19 +43,22 @@ optional arguments: --sd-notify Notify systemd service manager. ``` -### How to use a different configuration file? +### How to specify which configuration file be used? -The bot allows you to select which configuration file you want to use. Per -default, the bot will load the file `./config.json` +The bot allows you to select which configuration file you want to use by means of +the `-c/--config` command line option: ```bash freqtrade -c path/far/far/away/config.json ``` +Per default, the bot loads the `config.json` configuration file from the current +working directory. + ### How to use multiple configuration files? The bot allows you to use multiple configuration files by specifying multiple -`-c/--config` configuration options in the command line. Configuration parameters +`-c/--config` options in the command line. Configuration parameters defined in the last configuration file override parameters with the same name defined in the previous configuration file specified in the command line. @@ -266,7 +269,7 @@ optional arguments: ## Edge commands -To know your trade expectacny and winrate against historical data, you can use Edge. +To know your trade expectancy and winrate against historical data, you can use Edge. ``` usage: freqtrade edge [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE] diff --git a/docs/configuration.md b/docs/configuration.md index f8dbbbbbb..b48e23eee 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,15 +1,24 @@ # Configure the bot -This page explains how to configure your `config.json` file. +This page explains how to configure your configuration file. -## Setup config.json +Per default, the bot loads configuration from the `config.json` file located in the current working directory. +You can change the configuration file used by the bot with the `-c/--config` option. -We recommend to copy and use the `config.json.example` as a template +If you used the [Quick start](installation.md/#quick-start) method for installing +the bot, the installation script should have already created the default configuration file (`config.json`) for you. + +We recommend you to copy and use the `config.json.example` as a template for your bot configuration. -The table below will list all configuration parameters. +The configuration file defines the set of configuration parameters for the bot written in the JSON format. +Additionally, you may use one-line `// ...` and multi-line `/* ... */` comments. -Mandatory Parameters are marked as **Required**. +## Configuration parameters + +The table below will list all configuration parameters available. + +Mandatory parameters are marked as **Required**. | Command | Default | Description | |----------|---------|-------------| From de802341651109b31884d3af3931123d16fe590b Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 13 Aug 2019 00:23:41 +0300 Subject: [PATCH 20/43] hyperopt options updated in bot-usage.md --- docs/bot-usage.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 8877ed010..e3db2ab33 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -207,7 +207,7 @@ usage: freqtrade hyperopt [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE] [--customhyperopt NAME] [--hyperopt-path PATH] [--eps] [-e INT] [-s {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]] - [--dmmp] [--print-all] [-j JOBS] + [--dmmp] [--print-all] [--no-color] [-j JOBS] [--random-state INT] [--min-trades INT] [--continue] [--hyperopt-loss NAME] @@ -243,6 +243,8 @@ optional arguments: (same as setting `max_open_trades` to a very high number). --print-all Print all results, not only the best ones. + --no-color Disable colorization of hyperopt results. May be + useful if you are redirecting output to a file. -j JOBS, --job-workers JOBS The number of concurrently running jobs for hyperoptimization (hyperopt worker processes). If -1 @@ -256,8 +258,7 @@ optional arguments: --continue Continue hyperopt from previous runs. By default, temporary files will be removed and hyperopt will start from scratch. - --hyperopt-loss NAME - Specify the class name of the hyperopt loss function + --hyperopt-loss NAME Specify the class name of the hyperopt loss function class (IHyperOptLoss). Different functions can generate completely different results, since the target for optimization is different. (default: From f960ea039e472e3b29d6b3bd69842f9d7e524097 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2019 08:05:51 +0200 Subject: [PATCH 21/43] Remove duplicate test --- freqtrade/tests/test_freqtradebot.py | 47 ---------------------------- 1 file changed, 47 deletions(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 9ced5c5a1..a1d5f691e 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -2426,53 +2426,6 @@ def test_execute_sell_market_order(default_conf, ticker, fee, } == last_msg -def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee, - ticker_sell_down, markets, mocker) -> None: - rpc_mock = patch_RPCManager(mocker) - mocker.patch.multiple( - 'freqtrade.exchange.Exchange', - _load_markets=MagicMock(return_value={}), - get_ticker=ticker, - get_fee=fee, - markets=PropertyMock(return_value=markets) - ) - freqtrade = FreqtradeBot(default_conf) - patch_get_signal(freqtrade) - - # Create some test data - freqtrade.create_trade() - - trade = Trade.query.first() - assert trade - - # Decrease the price and sell it - mocker.patch.multiple( - 'freqtrade.exchange.Exchange', - get_ticker=ticker_sell_down - ) - - freqtrade.config = {} - freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'], - sell_reason=SellType.STOP_LOSS) - - assert rpc_mock.call_count == 2 - last_msg = rpc_mock.call_args_list[-1][0][0] - assert { - 'type': RPCMessageType.SELL_NOTIFICATION, - 'exchange': 'Bittrex', - 'pair': 'ETH/BTC', - 'gain': 'loss', - 'limit': 1.044e-05, - 'amount': 90.99181073703367, - 'order_type': 'limit', - 'open_rate': 1.099e-05, - 'current_rate': 1.044e-05, - 'profit_amount': -5.492e-05, - 'profit_percent': -0.05478342, - 'sell_reason': SellType.STOP_LOSS.value - } == last_msg - - def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, fee, markets, mocker) -> None: patch_RPCManager(mocker) From d8dbea9d5b958f14f49677565363f8dde16fa3da Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2019 08:20:35 +0200 Subject: [PATCH 22/43] Add exchange_reasons to bad exchanges --- freqtrade/exchange/__init__.py | 3 ++- freqtrade/exchange/exchange.py | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 5c58320f6..cbf851fc2 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -1,5 +1,6 @@ from freqtrade.exchange.exchange import Exchange # noqa: F401 -from freqtrade.exchange.exchange import (is_exchange_bad, # noqa: F401 +from freqtrade.exchange.exchange import (get_exchange_bad_reason, # noqa: F401 + is_exchange_bad, is_exchange_available, is_exchange_officially_supported, available_exchanges) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 657f382d8..9f363c799 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -25,6 +25,10 @@ logger = logging.getLogger(__name__) API_RETRY_COUNT = 4 +BAD_EXCHANGES = { + "bitmex": "Various reasons", + "bitstamp": "Does not provide history. Details in https://github.com/freqtrade/freqtrade/issues/1983", + } def retrier_async(f): @@ -755,7 +759,11 @@ class Exchange(object): def is_exchange_bad(exchange: str) -> bool: - return exchange in ['bitmex', 'bitstamp'] + return exchange in BAD_EXCHANGES + + +def get_exchange_bad_reason(exchange: str) -> str: + return BAD_EXCHANGES.get(exchange) def is_exchange_available(exchange: str, ccxt_module=None) -> bool: From 3c589bb877c3c938cd0156dd231a1a6dc274a24d Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2019 08:26:10 +0200 Subject: [PATCH 23/43] fail if known bad exchanges are detcted --- docs/configuration.md | 1 + freqtrade/configuration/check_exchange.py | 11 +++++------ freqtrade/configuration/configuration.py | 2 +- freqtrade/exchange/exchange.py | 5 +++-- freqtrade/tests/test_configuration.py | 6 +++--- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index f8dbbbbbb..fc450bd82 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -53,6 +53,7 @@ Mandatory Parameters are marked as **Required**. | `experimental.use_sell_signal` | false | Use your sell strategy in addition of the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy). | `experimental.sell_profit_only` | false | Waits until you have made a positive profit before taking a sell decision. [Strategy Override](#parameters-in-the-strategy). | `experimental.ignore_roi_if_buy_signal` | false | Does not sell if the buy-signal is still active. Takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy). +| `experimental.block_bad_exchanges` | true | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now. | `pairlist.method` | StaticPairList | Use static or dynamic volume-based pairlist. [More information below](#dynamic-pairlists). | `pairlist.config` | None | Additional configuration for dynamic pairlists. [More information below](#dynamic-pairlists). | `telegram.enabled` | true | **Required.** Enable or not the usage of Telegram. diff --git a/freqtrade/configuration/check_exchange.py b/freqtrade/configuration/check_exchange.py index 8dae06f7a..70f4cfa33 100644 --- a/freqtrade/configuration/check_exchange.py +++ b/freqtrade/configuration/check_exchange.py @@ -2,9 +2,9 @@ import logging from typing import Any, Dict from freqtrade import OperationalException -from freqtrade.exchange import (is_exchange_bad, is_exchange_available, - is_exchange_officially_supported, available_exchanges) - +from freqtrade.exchange import (available_exchanges, get_exchange_bad_reason, + is_exchange_available, is_exchange_bad, + is_exchange_officially_supported) logger = logging.getLogger(__name__) @@ -31,9 +31,8 @@ def check_exchange(config: Dict[str, Any], check_for_bad: bool = True) -> bool: ) if check_for_bad and is_exchange_bad(exchange): - logger.warning(f'Exchange "{exchange}" is known to not work with the bot yet. ' - f'Use it only for development and testing purposes.') - return False + raise OperationalException(f'Exchange "{exchange}" is known to not work with the bot yet. ' + f'Reason: {get_exchange_bad_reason(exchange)}') if is_exchange_officially_supported(exchange): logger.info(f'Exchange "{exchange}" is officially supported ' diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 237346e37..4bbf4ddd6 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -148,7 +148,7 @@ class Configuration(object): config['internals'].update({'sd_notify': True}) # Check if the exchange set by the user is supported - check_exchange(config) + check_exchange(config, config.get('experimental', {}).get('block_bad_exchanges', True)) def _process_datadir_options(self, config: Dict[str, Any]) -> None: """ diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 9f363c799..0e23cf8b8 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -27,7 +27,8 @@ logger = logging.getLogger(__name__) API_RETRY_COUNT = 4 BAD_EXCHANGES = { "bitmex": "Various reasons", - "bitstamp": "Does not provide history. Details in https://github.com/freqtrade/freqtrade/issues/1983", + "bitstamp": "Does not provide history. " + "Details in https://github.com/freqtrade/freqtrade/issues/1983", } @@ -763,7 +764,7 @@ def is_exchange_bad(exchange: str) -> bool: def get_exchange_bad_reason(exchange: str) -> str: - return BAD_EXCHANGES.get(exchange) + return BAD_EXCHANGES.get(exchange, "") def is_exchange_available(exchange: str, ccxt_module=None) -> bool: diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index 132dd334e..2bc561b7d 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -499,9 +499,9 @@ def test_check_exchange(default_conf, caplog) -> None: # Test a 'bad' exchange, which known to have serious problems default_conf.get('exchange').update({'name': 'bitmex'}) - assert not check_exchange(default_conf) - assert log_has_re(r"Exchange .* is known to not work with the bot yet\. " - r"Use it only for development and testing purposes\.", caplog) + with pytest.raises(OperationalException, + match=r"Exchange .* is known to not work with the bot yet.*"): + check_exchange(default_conf) caplog.clear() # Test a 'bad' exchange with check_for_bad=False From 8d813fa728a4d454aaa8cf95a84e507513ca5aa6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2019 09:36:52 +0200 Subject: [PATCH 24/43] Remove return-value for _process --- freqtrade/tests/test_freqtradebot.py | 15 +++++++-------- freqtrade/worker.py | 9 ++------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 4e649250a..883e4f050 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -744,8 +744,7 @@ def test_process_exchange_failures(default_conf, ticker, markets, mocker) -> Non worker = Worker(args=None, config=default_conf) patch_get_signal(worker.freqtrade) - result = worker._process() - assert result is False + worker._process() assert sleep_mock.has_calls() @@ -763,8 +762,7 @@ def test_process_operational_exception(default_conf, ticker, markets, mocker) -> assert worker.state == State.RUNNING - result = worker._process() - assert result is False + worker._process() assert worker.state == State.STOPPED assert 'OperationalException' in msg_mock.call_args_list[-1][0][0]['status'] @@ -786,13 +784,14 @@ def test_process_trade_handling( trades = Trade.query.filter(Trade.is_open.is_(True)).all() assert not trades - result = freqtrade.process() - assert result is True + freqtrade.process() + trades = Trade.query.filter(Trade.is_open.is_(True)).all() assert len(trades) == 1 - result = freqtrade.process() - assert result is False + # Nothing happened ... + freqtrade.process() + assert len(trades) == 1 def test_process_trade_no_whitelist_pair( diff --git a/freqtrade/worker.py b/freqtrade/worker.py index db0dba0e8..df792e35e 100755 --- a/freqtrade/worker.py +++ b/freqtrade/worker.py @@ -127,11 +127,10 @@ class Worker(object): time.sleep(duration) return result - def _process(self) -> bool: + def _process(self) -> None: logger.debug("========================================") - state_changed = False try: - state_changed = self.freqtrade.process() + self.freqtrade.process() except TemporaryError as error: logger.warning(f"Error: {error}, retrying in {constants.RETRY_TIMEOUT} seconds...") time.sleep(constants.RETRY_TIMEOUT) @@ -144,10 +143,6 @@ class Worker(object): }) logger.exception('OperationalException. Stopping trader ...') self.freqtrade.state = State.STOPPED - # TODO: The return value of _process() is not used apart tests - # and should (could) be eliminated later. See PR #1689. -# state_changed = True - return state_changed def _reconfigure(self) -> None: """ From 4b8eaaf7aa97c6bf2a0cf339ca980978f34f84a0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2019 09:37:56 +0200 Subject: [PATCH 25/43] freqtradebot.process() does not need to return anything --- freqtrade/freqtradebot.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 603b0631f..c4792a275 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -105,13 +105,12 @@ class FreqtradeBot(object): # Adjust stoploss if it was changed Trade.stoploss_reinitialization(self.strategy.stoploss) - def process(self) -> bool: + def process(self) -> None: """ Queries the persistence layer for open trades and handles them, otherwise a new trade is created. :return: True if one or more trades has been created or closed, False otherwise """ - state_changed = False # Check whether markets have to be reloaded self.exchange._reload_markets() @@ -138,19 +137,17 @@ class FreqtradeBot(object): # First process current opened trades for trade in trades: - state_changed |= self.process_maybe_execute_sell(trade) + self.process_maybe_execute_sell(trade) # Then looking for buy opportunities if len(trades) < self.config['max_open_trades']: - state_changed = self.process_maybe_execute_buy() + self.process_maybe_execute_buy() if 'unfilledtimeout' in self.config: # Check and handle any timed out open orders self.check_handle_timedout() Trade.session.flush() - return state_changed - def _extend_whitelist_with_trades(self, whitelist: List[str], trades: List[Any]): """ Extend whitelist with pairs from open trades From c29389f5f397156819e79055e4bdf21b0b64d2d6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2019 09:38:21 +0200 Subject: [PATCH 26/43] Remove process() checks from tests --- freqtrade/tests/test_freqtradebot.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 883e4f050..7ad9cba7a 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -711,8 +711,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order, trades = Trade.query.filter(Trade.is_open.is_(True)).all() assert not trades - result = freqtrade.process() - assert result is True + freqtrade.process() trades = Trade.query.filter(Trade.is_open.is_(True)).all() assert len(trades) == 1 @@ -833,11 +832,10 @@ def test_process_trade_no_whitelist_pair( )) assert pair not in freqtrade.active_pair_whitelist - result = freqtrade.process() + freqtrade.process() assert pair in freqtrade.active_pair_whitelist # Make sure each pair is only in the list once assert len(freqtrade.active_pair_whitelist) == len(set(freqtrade.active_pair_whitelist)) - assert result is True def test_process_informative_pairs_added(default_conf, ticker, markets, mocker) -> None: From 8873e0072c81d27015d2e0e936f509c6c7690d83 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2019 09:42:22 +0200 Subject: [PATCH 27/43] process_maybe_execute_buy does not need to return bool --- freqtrade/freqtradebot.py | 10 +++------- freqtrade/tests/test_freqtradebot.py | 8 +++----- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index c4792a275..b18b8eac5 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -426,21 +426,17 @@ class FreqtradeBot(object): return True - def process_maybe_execute_buy(self) -> bool: + def process_maybe_execute_buy(self) -> None: """ Tries to execute a buy trade in a safe way :return: True if executed """ try: # Create entity and execute trade - if self.create_trade(): - return True - - logger.info('Found no buy signals for whitelisted currencies. Trying again..') - return False + if not self.create_trade(): + logger.info('Found no buy signals for whitelisted currencies. Trying again...') except DependencyException as exception: logger.warning('Unable to create trade: %s', exception) - return False def process_maybe_execute_sell(self, trade: Trade) -> bool: """ diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 7ad9cba7a..f70b5374e 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1385,14 +1385,12 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, stop_price=0.00002344 * 0.99) -def test_process_maybe_execute_buy(mocker, default_conf) -> None: +def test_process_maybe_execute_buy(mocker, default_conf, caplog) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) - mocker.patch('freqtrade.freqtradebot.FreqtradeBot.create_trade', MagicMock(return_value=True)) - assert freqtrade.process_maybe_execute_buy() - mocker.patch('freqtrade.freqtradebot.FreqtradeBot.create_trade', MagicMock(return_value=False)) - assert not freqtrade.process_maybe_execute_buy() + freqtrade.process_maybe_execute_buy() + assert log_has('Found no buy signals for whitelisted currencies. Trying again...', caplog) def test_process_maybe_execute_buy_exception(mocker, default_conf, caplog) -> None: From 997eb7574aba223f5d25e060b8bd3c488882efc6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2019 10:01:29 +0200 Subject: [PATCH 28/43] Support creating multiple trades in one iteration --- freqtrade/freqtradebot.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index b18b8eac5..f5ea131e4 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -279,15 +279,16 @@ class FreqtradeBot(object): logger.info("No currency pair in whitelist, but checking to sell open trades.") return False + buycount = 0 # running get_signal on historical data fetched for _pair in whitelist: (buy, sell) = self.strategy.get_signal( _pair, interval, self.dataprovider.ohlcv(_pair, self.strategy.ticker_interval)) - if buy and not sell: + if buy and not sell and len(Trade.get_open_trades()) < self.config['max_open_trades']: stake_amount = self._get_trade_stake_amount(_pair) if not stake_amount: - return False + continue logger.info(f"Buy signal found: about create a new trade with stake_amount: " f"{stake_amount} ...") @@ -297,12 +298,11 @@ class FreqtradeBot(object): if (bidstrat_check_depth_of_market.get('enabled', False)) and\ (bidstrat_check_depth_of_market.get('bids_to_ask_delta', 0) > 0): if self._check_depth_of_market_buy(_pair, bidstrat_check_depth_of_market): - return self.execute_buy(_pair, stake_amount) - else: - return False - return self.execute_buy(_pair, stake_amount) + buycount += self.execute_buy(_pair, stake_amount) - return False + buycount += self.execute_buy(_pair, stake_amount) + + return buycount > 0 def _check_depth_of_market_buy(self, pair: str, conf: Dict) -> bool: """ From a325f1ce2bb05df4d7e086c7b7fe0d8da21f9763 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2019 10:01:43 +0200 Subject: [PATCH 29/43] adapt some tests since create_trade() can now buy multiple times, we need to use execute_buy() to create a single trade --- freqtrade/tests/test_freqtradebot.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index f70b5374e..ba978e7ad 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -253,13 +253,14 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, assert result == default_conf['stake_amount'] / conf['max_open_trades'] # create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)' - freqtrade.create_trade() + # freqtrade.create_trade() + freqtrade.execute_buy('ETH/BTC', result) result = freqtrade._get_trade_stake_amount('LTC/BTC') assert result == default_conf['stake_amount'] / (conf['max_open_trades'] - 1) # create 2 trades, order amount should be None - freqtrade.create_trade() + freqtrade.execute_buy('LTC/BTC', result) result = freqtrade._get_trade_stake_amount('XRP/BTC') assert result is None From 6948e0ba847bd73eafb8e47a64369c9218815706 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2019 10:12:02 +0200 Subject: [PATCH 30/43] Handle orderbook_depth check correctly --- freqtrade/freqtradebot.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index f5ea131e4..699500628 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -299,6 +299,8 @@ class FreqtradeBot(object): (bidstrat_check_depth_of_market.get('bids_to_ask_delta', 0) > 0): if self._check_depth_of_market_buy(_pair, bidstrat_check_depth_of_market): buycount += self.execute_buy(_pair, stake_amount) + else: + continue buycount += self.execute_buy(_pair, stake_amount) From 974d899b337e83066ab4c52bae99d65a4f9b5543 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2019 10:12:12 +0200 Subject: [PATCH 31/43] Adapt some more tests --- freqtrade/tests/test_freqtradebot.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index ba978e7ad..43982fa5c 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -302,6 +302,7 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, markets, caplog, mocker, patch_RPCManager(mocker) patch_exchange(mocker) patch_edge(mocker) + edge_conf['max_open_trades'] = float('inf') # Strategy stoploss is -0.1 but Edge imposes a stoploss at -0.2 # Thus, if price falls 21%, stoploss should be triggered @@ -342,6 +343,7 @@ def test_edge_should_ignore_strategy_stoploss(limit_buy_order, fee, markets, patch_RPCManager(mocker) patch_exchange(mocker) patch_edge(mocker) + edge_conf['max_open_trades'] = float('inf') # Strategy stoploss is -0.1 but Edge imposes a stoploss at -0.2 # Thus, if price falls 15%, stoploss should not be triggered @@ -380,6 +382,7 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker, patch_RPCManager(mocker) patch_exchange(mocker) default_conf['stake_amount'] = 0.0000098751 + default_conf['max_open_trades'] = 2 mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_ticker=ticker, @@ -1284,7 +1287,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, patch_RPCManager(mocker) patch_exchange(mocker) patch_edge(mocker) - + edge_conf['max_open_trades'] = float('inf') mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_ticker=MagicMock(return_value={ From d69f7ae471ccb359aa41ba80a57c60d910b1be00 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2019 10:15:31 +0200 Subject: [PATCH 32/43] Adapt final tests to support multi-trade creation --- freqtrade/tests/rpc/test_rpc_telegram.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 3575520ad..e00903947 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -308,6 +308,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, limit_sell_order, markets, mocker) -> None: patch_exchange(mocker) + default_conf['max_open_trades'] = 1 mocker.patch( 'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0 @@ -357,9 +358,9 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, # Reset msg_mock msg_mock.reset_mock() + freqtradebot.config['max_open_trades'] = 2 # Add two other trades freqtradebot.create_trade() - freqtradebot.create_trade() trades = Trade.query.all() for trade in trades: @@ -832,14 +833,13 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker markets=PropertyMock(return_value=markets), validate_pairs=MagicMock(return_value={}) ) - + default_conf['max_open_trades'] = 4 freqtradebot = FreqtradeBot(default_conf) patch_get_signal(freqtradebot, (True, False)) telegram = Telegram(freqtradebot) # Create some test data - for _ in range(4): - freqtradebot.create_trade() + freqtradebot.create_trade() rpc_mock.reset_mock() update.message.text = '/forcesell all' From 0a07dfc5cf7bad17af06c1e873e363fe72dd2d46 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2019 10:20:32 +0200 Subject: [PATCH 33/43] Add test verifying that multiple trades are opened in one iteration --- freqtrade/tests/test_freqtradebot.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 43982fa5c..e8245d60b 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -697,6 +697,28 @@ def test_create_trade_no_signal(default_conf, fee, mocker) -> None: assert not freqtrade.create_trade() +@pytest.mark.parametrize("max_open", range(1, 5)) +def test_create_trade_multiple_trades(default_conf, ticker, + fee, markets, mocker, max_open) -> None: + patch_RPCManager(mocker) + patch_exchange(mocker) + default_conf['max_open_trades'] = max_open + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_ticker=ticker, + buy=MagicMock(return_value={'id': "12355555"}), + get_fee=fee, + markets=PropertyMock(return_value=markets) + ) + freqtrade = FreqtradeBot(default_conf) + patch_get_signal(freqtrade) + + freqtrade.create_trade() + + trades = Trade.get_open_trades() + assert len(trades) == max_open + + def test_process_trade_creation(default_conf, ticker, limit_buy_order, markets, fee, mocker, caplog) -> None: patch_RPCManager(mocker) From 9d476b5ab267f4bc829f00b3de05a4b97e54e249 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Aug 2019 10:34:27 +0200 Subject: [PATCH 34/43] Also check 0 open trades --- freqtrade/tests/test_freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index e8245d60b..acab74233 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -697,7 +697,7 @@ def test_create_trade_no_signal(default_conf, fee, mocker) -> None: assert not freqtrade.create_trade() -@pytest.mark.parametrize("max_open", range(1, 5)) +@pytest.mark.parametrize("max_open", range(0, 5)) def test_create_trade_multiple_trades(default_conf, ticker, fee, markets, mocker, max_open) -> None: patch_RPCManager(mocker) From 94196c84e9f2354163d47b57009793c7ea39e730 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 13 Aug 2019 14:25:56 +0300 Subject: [PATCH 35/43] docs: explanation for --no-color and colorization schema for results --- docs/hyperopt.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 16e2212d5..db6dbb343 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -343,9 +343,9 @@ def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: return dataframe ``` -You can use the `--print-all` command line option if you would like to see all results in the hyperopt output, not only the best ones. +By default, hyperopt prints colorized results -- epochs with positive profit are printed in the green color. This highlighting helps you find epochs that can be interesting for later analysis. Epochs with zero total profit or with negative profits (losses) are printed in the normal color. If you do not need colorization of results (for instance, when you are redirecting hyperopt output to a file) you can switch colorization off by specifying the `--no-color` option in the command line. -When the `--color/--print-colorized` command line option is used, the results are colorized -- bad results (with zero trades or limited by the `--min-trades` option) are red, current bests -- in green, results with positive total profit are printed in bold. +You can use the `--print-all` command line option if you would like to see all results in the hyperopt output, not only the best ones. When `--print-all` is used, current best results are also colorized by default -- they are printed in bold (bright) style. This can also be switched off with the `--no-color` command line option. ### Understand Hyperopt ROI results From 4c4ba08e854bd49b56fbc243f65534fa5ddc3dad Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 13 Aug 2019 19:47:38 +0300 Subject: [PATCH 36/43] colorama added to install_requires --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 202e3fd0d..572a32ccb 100644 --- a/setup.py +++ b/setup.py @@ -58,6 +58,7 @@ setup(name='freqtrade', 'py_find_1st', 'python-rapidjson', 'sdnotify', + 'colorama', # from requirements.txt 'numpy', 'pandas', From 3d36747b920ed70e366b4ab0143c445078e4ce5d Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 13 Aug 2019 21:52:50 +0300 Subject: [PATCH 37/43] preface in configuration.md reworked --- docs/configuration.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index b48e23eee..66b4b6da2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,18 +1,28 @@ # Configure the bot -This page explains how to configure your configuration file. +This page explains how to configure the bot. + +## The Freqtrade configuration file + +The bot uses a set of configuration parameters during its operation that all together conform the bot configuration. It normally reads its configuration from a file (Freqtrade configuration file). Per default, the bot loads configuration from the `config.json` file located in the current working directory. -You can change the configuration file used by the bot with the `-c/--config` option. + +You can change the name of the configuration file used by the bot with the `-c/--config` command line option. + +In some advanced use cases, multiple configuration files can be specified and used by the bot or the bot can read its configuration parameters from the process standard input stream. If you used the [Quick start](installation.md/#quick-start) method for installing the bot, the installation script should have already created the default configuration file (`config.json`) for you. -We recommend you to copy and use the `config.json.example` as a template +If default configuration file is not created we recommend you to copy and use the `config.json.example` as a template for your bot configuration. -The configuration file defines the set of configuration parameters for the bot written in the JSON format. -Additionally, you may use one-line `// ...` and multi-line `/* ... */` comments. +The Freqtrade configuration file is to be written in the JSON format. + +Additionally to the standard JSON syntax, you may use one-line `// ...` and multi-line `/* ... */` comments in your configuration files and trailing commas in the lists of parameters. + +Do not worry if you are not familiar with JSON format -- simply open the configuration file with an editor of your choice, make some changes to the parameters you need, save your changes and, finally, restart the bot or, if it was previously stopped, run it again with the changes you made to the configuration. The bot validates syntax of the configuration file at startup and will warn you if you made any errors editing it. ## Configuration parameters From a76136c0102cffbe3e1ca24c2b8ca88e81146558 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 14 Aug 2019 06:16:43 +0200 Subject: [PATCH 38/43] Rename create_trade to create_trades --- freqtrade/freqtradebot.py | 4 +- freqtrade/tests/rpc/test_rpc.py | 22 ++-- freqtrade/tests/rpc/test_rpc_apiserver.py | 8 +- freqtrade/tests/rpc/test_rpc_telegram.py | 22 ++-- freqtrade/tests/test_freqtradebot.py | 127 +++++++++++----------- 5 files changed, 91 insertions(+), 92 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 699500628..e67a8687f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -256,7 +256,7 @@ class FreqtradeBot(object): amount_reserve_percent = max(amount_reserve_percent, 0.5) return min(min_stake_amounts) / amount_reserve_percent - def create_trade(self) -> bool: + def create_trades(self) -> bool: """ Checks the implemented trading indicator(s) for a randomly picked pair, if one pair triggers the buy_signal a new trade record gets created @@ -435,7 +435,7 @@ class FreqtradeBot(object): """ try: # Create entity and execute trade - if not self.create_trade(): + if not self.create_trades(): logger.info('Found no buy signals for whitelisted currencies. Trying again...') except DependencyException as exception: logger.warning('Unable to create trade: %s', exception) diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index d273244b0..5d3fb7920 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -44,7 +44,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: with pytest.raises(RPCException, match=r'.*no active trade*'): rpc._rpc_trade_status() - freqtradebot.create_trade() + freqtradebot.create_trades() results = rpc._rpc_trade_status() assert { 'trade_id': 1, @@ -116,7 +116,7 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None: with pytest.raises(RPCException, match=r'.*no active order*'): rpc._rpc_status_table() - freqtradebot.create_trade() + freqtradebot.create_trades() result = rpc._rpc_status_table() assert 'instantly' in result['Since'].all() assert 'ETH/BTC' in result['Pair'].all() @@ -151,7 +151,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee, rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() # Create some test data - freqtradebot.create_trade() + freqtradebot.create_trades() trade = Trade.query.first() assert trade @@ -208,7 +208,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) # Create some test data - freqtradebot.create_trade() + freqtradebot.create_trades() trade = Trade.query.first() # Simulate fulfilled LIMIT_BUY order for trade trade.update(limit_buy_order) @@ -222,7 +222,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, trade.close_date = datetime.utcnow() trade.is_open = False - freqtradebot.create_trade() + freqtradebot.create_trades() trade = Trade.query.first() # Simulate fulfilled LIMIT_BUY order for trade trade.update(limit_buy_order) @@ -292,7 +292,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, markets, rpc = RPC(freqtradebot) # Create some test data - freqtradebot.create_trade() + freqtradebot.create_trades() trade = Trade.query.first() # Simulate fulfilled LIMIT_BUY order for trade trade.update(limit_buy_order) @@ -536,7 +536,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None: msg = rpc._rpc_forcesell('all') assert msg == {'result': 'Created sell orders for all open trades.'} - freqtradebot.create_trade() + freqtradebot.create_trades() msg = rpc._rpc_forcesell('all') assert msg == {'result': 'Created sell orders for all open trades.'} @@ -570,7 +570,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None: assert cancel_order_mock.call_count == 1 assert trade.amount == filled_amount - freqtradebot.create_trade() + freqtradebot.create_trades() trade = Trade.query.filter(Trade.id == '2').first() amount = trade.amount # make an limit-buy open trade, if there is no 'filled', don't sell it @@ -589,7 +589,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None: assert cancel_order_mock.call_count == 2 assert trade.amount == amount - freqtradebot.create_trade() + freqtradebot.create_trades() # make an limit-sell open trade mocker.patch( 'freqtrade.exchange.Exchange.get_order', @@ -622,7 +622,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, rpc = RPC(freqtradebot) # Create some test data - freqtradebot.create_trade() + freqtradebot.create_trades() trade = Trade.query.first() assert trade @@ -660,7 +660,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None: assert counts["current"] == 0 # Create some test data - freqtradebot.create_trade() + freqtradebot.create_trades() counts = rpc._rpc_count() assert counts["current"] == 1 diff --git a/freqtrade/tests/rpc/test_rpc_apiserver.py b/freqtrade/tests/rpc/test_rpc_apiserver.py index a218d5622..794343a98 100644 --- a/freqtrade/tests/rpc/test_rpc_apiserver.py +++ b/freqtrade/tests/rpc/test_rpc_apiserver.py @@ -275,7 +275,7 @@ def test_api_count(botclient, mocker, ticker, fee, markets): assert rc.json["max"] == 1.0 # Create some test data - ftbot.create_trade() + ftbot.create_trades() rc = client_get(client, f"{BASE_URI}/count") assert_response(rc) assert rc.json["current"] == 1.0 @@ -329,7 +329,7 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, li assert len(rc.json) == 1 assert rc.json == {"error": "Error querying _profit: no closed trade"} - ftbot.create_trade() + ftbot.create_trades() trade = Trade.query.first() # Simulate fulfilled LIMIT_BUY order for trade @@ -418,7 +418,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets): assert_response(rc, 502) assert rc.json == {'error': 'Error querying _status: no active trade'} - ftbot.create_trade() + ftbot.create_trades() rc = client_get(client, f"{BASE_URI}/status") assert_response(rc) assert len(rc.json) == 1 @@ -548,7 +548,7 @@ def test_api_forcesell(botclient, mocker, ticker, fee, markets): assert_response(rc, 502) assert rc.json == {"error": "Error querying _forcesell: invalid argument"} - ftbot.create_trade() + ftbot.create_trades() rc = client_post(client, f"{BASE_URI}/forcesell", data='{"tradeid": "1"}') diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index e00903947..2f469643f 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -192,7 +192,7 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None: # Create some test data for _ in range(3): - freqtradebot.create_trade() + freqtradebot.create_trades() telegram._status(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 @@ -240,7 +240,7 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No msg_mock.reset_mock() # Create some test data - freqtradebot.create_trade() + freqtradebot.create_trades() # Trigger status while we have a fulfilled order for the open trade telegram._status(bot=MagicMock(), update=update) @@ -292,7 +292,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) msg_mock.reset_mock() # Create some test data - freqtradebot.create_trade() + freqtradebot.create_trades() telegram._status_table(bot=MagicMock(), update=update) @@ -332,7 +332,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, telegram = Telegram(freqtradebot) # Create some test data - freqtradebot.create_trade() + freqtradebot.create_trades() trade = Trade.query.first() assert trade @@ -360,7 +360,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, msg_mock.reset_mock() freqtradebot.config['max_open_trades'] = 2 # Add two other trades - freqtradebot.create_trade() + freqtradebot.create_trades() trades = Trade.query.all() for trade in trades: @@ -439,7 +439,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, msg_mock.reset_mock() # Create some test data - freqtradebot.create_trade() + freqtradebot.create_trades() trade = Trade.query.first() # Simulate fulfilled LIMIT_BUY order for trade @@ -734,7 +734,7 @@ def test_forcesell_handle(default_conf, update, ticker, fee, telegram = Telegram(freqtradebot) # Create some test data - freqtradebot.create_trade() + freqtradebot.create_trades() trade = Trade.query.first() assert trade @@ -785,7 +785,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee, telegram = Telegram(freqtradebot) # Create some test data - freqtradebot.create_trade() + freqtradebot.create_trades() # Decrease the price and sell it mocker.patch.multiple( @@ -839,7 +839,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker telegram = Telegram(freqtradebot) # Create some test data - freqtradebot.create_trade() + freqtradebot.create_trades() rpc_mock.reset_mock() update.message.text = '/forcesell all' @@ -983,7 +983,7 @@ def test_performance_handle(default_conf, update, ticker, fee, telegram = Telegram(freqtradebot) # Create some test data - freqtradebot.create_trade() + freqtradebot.create_trades() trade = Trade.query.first() assert trade @@ -1028,7 +1028,7 @@ def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> Non freqtradebot.state = State.RUNNING # Create some test data - freqtradebot.create_trade() + freqtradebot.create_trades() msg_mock.reset_mock() telegram._count(bot=MagicMock(), update=update) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index acab74233..d74615d6b 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -253,7 +253,6 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, assert result == default_conf['stake_amount'] / conf['max_open_trades'] # create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)' - # freqtrade.create_trade() freqtrade.execute_buy('ETH/BTC', result) result = freqtrade._get_trade_stake_amount('LTC/BTC') @@ -327,7 +326,7 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, markets, caplog, mocker, freqtrade.active_pair_whitelist = ['NEO/BTC'] patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() trade.update(limit_buy_order) ############################################# @@ -368,7 +367,7 @@ def test_edge_should_ignore_strategy_stoploss(limit_buy_order, fee, markets, freqtrade.active_pair_whitelist = ['NEO/BTC'] patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() trade.update(limit_buy_order) ############################################# @@ -392,7 +391,7 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker, ) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() assert trade is not None @@ -400,7 +399,7 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker, assert trade.is_open assert trade.open_date is not None - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.order_by(Trade.id.desc()).first() assert trade is not None @@ -523,7 +522,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: assert result == min(8, 2 * 2) / 0.9 -def test_create_trade(default_conf, ticker, limit_buy_order, fee, markets, mocker) -> None: +def test_create_trades(default_conf, ticker, limit_buy_order, fee, markets, mocker) -> None: patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -538,7 +537,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, fee, markets, mocke whitelist = deepcopy(default_conf['exchange']['pair_whitelist']) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() assert trade is not None @@ -556,8 +555,8 @@ def test_create_trade(default_conf, ticker, limit_buy_order, fee, markets, mocke assert whitelist == default_conf['exchange']['pair_whitelist'] -def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, - fee, markets, mocker) -> None: +def test_create_trades_no_stake_amount(default_conf, ticker, limit_buy_order, + fee, markets, mocker) -> None: patch_RPCManager(mocker) patch_exchange(mocker) patch_wallet(mocker, free=default_conf['stake_amount'] * 0.5) @@ -572,11 +571,11 @@ def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, patch_get_signal(freqtrade) with pytest.raises(DependencyException, match=r'.*stake amount.*'): - freqtrade.create_trade() + freqtrade.create_trades() -def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, - fee, markets, mocker) -> None: +def test_create_trades_minimal_amount(default_conf, ticker, limit_buy_order, + fee, markets, mocker) -> None: patch_RPCManager(mocker) patch_exchange(mocker) buy_mock = MagicMock(return_value={'id': limit_buy_order['id']}) @@ -591,13 +590,13 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.create_trade() + freqtrade.create_trades() rate, amount = buy_mock.call_args[1]['rate'], buy_mock.call_args[1]['amount'] assert rate * amount >= default_conf['stake_amount'] -def test_create_trade_too_small_stake_amount(default_conf, ticker, limit_buy_order, - fee, markets, mocker) -> None: +def test_create_trades_too_small_stake_amount(default_conf, ticker, limit_buy_order, + fee, markets, mocker) -> None: patch_RPCManager(mocker) patch_exchange(mocker) buy_mock = MagicMock(return_value={'id': limit_buy_order['id']}) @@ -613,11 +612,11 @@ def test_create_trade_too_small_stake_amount(default_conf, ticker, limit_buy_ord freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - assert not freqtrade.create_trade() + assert not freqtrade.create_trades() -def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order, - fee, markets, mocker) -> None: +def test_create_trades_limit_reached(default_conf, ticker, limit_buy_order, + fee, markets, mocker) -> None: patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -634,12 +633,12 @@ def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order, freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - assert not freqtrade.create_trade() + assert not freqtrade.create_trades() assert freqtrade._get_trade_stake_amount('ETH/BTC') is None -def test_create_trade_no_pairs_let(default_conf, ticker, limit_buy_order, fee, - markets, mocker, caplog) -> None: +def test_create_trades_no_pairs_let(default_conf, ticker, limit_buy_order, fee, + markets, mocker, caplog) -> None: patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -654,13 +653,13 @@ def test_create_trade_no_pairs_let(default_conf, ticker, limit_buy_order, fee, freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - assert freqtrade.create_trade() - assert not freqtrade.create_trade() + assert freqtrade.create_trades() + assert not freqtrade.create_trades() assert log_has("No currency pair in whitelist, but checking to sell open trades.", caplog) -def test_create_trade_no_pairs_in_whitelist(default_conf, ticker, limit_buy_order, fee, - markets, mocker, caplog) -> None: +def test_create_trades_no_pairs_in_whitelist(default_conf, ticker, limit_buy_order, fee, + markets, mocker, caplog) -> None: patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -674,11 +673,11 @@ def test_create_trade_no_pairs_in_whitelist(default_conf, ticker, limit_buy_orde freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - assert not freqtrade.create_trade() + assert not freqtrade.create_trades() assert log_has("Whitelist is empty.", caplog) -def test_create_trade_no_signal(default_conf, fee, mocker) -> None: +def test_create_trades_no_signal(default_conf, fee, mocker) -> None: default_conf['dry_run'] = True patch_RPCManager(mocker) @@ -694,12 +693,12 @@ def test_create_trade_no_signal(default_conf, fee, mocker) -> None: Trade.query = MagicMock() Trade.query.filter = MagicMock() - assert not freqtrade.create_trade() + assert not freqtrade.create_trades() @pytest.mark.parametrize("max_open", range(0, 5)) -def test_create_trade_multiple_trades(default_conf, ticker, - fee, markets, mocker, max_open) -> None: +def test_create_trades_multiple_trades(default_conf, ticker, + fee, markets, mocker, max_open) -> None: patch_RPCManager(mocker) patch_exchange(mocker) default_conf['max_open_trades'] = max_open @@ -713,7 +712,7 @@ def test_create_trade_multiple_trades(default_conf, ticker, freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.create_trade() + freqtrade.create_trades() trades = Trade.get_open_trades() assert len(trades) == max_open @@ -1101,7 +1100,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog, # Fourth case: when stoploss is set and it is hit # should unset stoploss_order_id and return true # as a trade actually happened - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() trade.is_open = True trade.open_order_id = None @@ -1176,7 +1175,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, patch_get_signal(freqtrade) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() trade.is_open = True trade.open_order_id = None @@ -1266,7 +1265,7 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c # setting stoploss_on_exchange_interval to 60 seconds freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60 patch_get_signal(freqtrade) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() trade.is_open = True trade.open_order_id = None @@ -1347,7 +1346,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, freqtrade.active_pair_whitelist = freqtrade.edge.adjust(freqtrade.active_pair_whitelist) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() trade.is_open = True trade.open_order_id = None @@ -1414,7 +1413,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, def test_process_maybe_execute_buy(mocker, default_conf, caplog) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) - mocker.patch('freqtrade.freqtradebot.FreqtradeBot.create_trade', MagicMock(return_value=False)) + mocker.patch('freqtrade.freqtradebot.FreqtradeBot.create_trades', MagicMock(return_value=False)) freqtrade.process_maybe_execute_buy() assert log_has('Found no buy signals for whitelisted currencies. Trying again...', caplog) @@ -1423,7 +1422,7 @@ def test_process_maybe_execute_buy_exception(mocker, default_conf, caplog) -> No freqtrade = get_patched_freqtradebot(mocker, default_conf) mocker.patch( - 'freqtrade.freqtradebot.FreqtradeBot.create_trade', + 'freqtrade.freqtradebot.FreqtradeBot.create_trades', MagicMock(side_effect=DependencyException) ) freqtrade.process_maybe_execute_buy() @@ -1610,7 +1609,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() assert trade @@ -1650,7 +1649,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, patch_get_signal(freqtrade, value=(True, True)) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trade() + freqtrade.create_trades() # Buy and Sell triggering, so doing nothing ... trades = Trade.query.all() @@ -1659,7 +1658,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, # Buy is triggering, so buying ... patch_get_signal(freqtrade, value=(True, False)) - freqtrade.create_trade() + freqtrade.create_trades() trades = Trade.query.all() nb_trades = len(trades) assert nb_trades == 1 @@ -1706,7 +1705,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order, patch_get_signal(freqtrade, value=(True, False)) freqtrade.strategy.min_roi_reached = MagicMock(return_value=True) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() trade.is_open = True @@ -1738,7 +1737,7 @@ def test_handle_trade_experimental( freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() trade.is_open = True @@ -1766,7 +1765,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, patch_get_signal(freqtrade) # Create trade and sell it - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() assert trade @@ -2106,7 +2105,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, markets, moc patch_get_signal(freqtrade) # Create some test data - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() assert trade @@ -2152,7 +2151,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets, patch_get_signal(freqtrade) # Create some test data - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() assert trade @@ -2201,7 +2200,7 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe patch_get_signal(freqtrade) # Create some test data - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() assert trade @@ -2258,7 +2257,7 @@ def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, freqtrade.strategy.order_types['stoploss_on_exchange'] = True patch_get_signal(freqtrade) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() Trade.session = MagicMock() @@ -2305,7 +2304,7 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, patch_get_signal(freqtrade) # Create some test data - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() assert trade @@ -2357,7 +2356,7 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, patch_get_signal(freqtrade) # Create some test data - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() freqtrade.process_maybe_execute_sell(trade) assert trade @@ -2409,7 +2408,7 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee, patch_get_signal(freqtrade) # Create some test data - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() assert trade @@ -2456,7 +2455,7 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee, patch_get_signal(freqtrade) # Create some test data - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() assert trade @@ -2512,7 +2511,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() trade.update(limit_buy_order) @@ -2543,7 +2542,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() trade.update(limit_buy_order) @@ -2574,7 +2573,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, market patch_get_signal(freqtrade) freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple( sell_flag=False, sell_type=SellType.NONE)) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() trade.update(limit_buy_order) @@ -2605,7 +2604,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, marke patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() trade.update(limit_buy_order) @@ -2635,7 +2634,7 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, markets, m patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=True) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() trade.update(limit_buy_order) @@ -2667,7 +2666,7 @@ def test_trailing_stop_loss(default_conf, limit_buy_order, fee, markets, caplog, patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() assert freqtrade.handle_trade(trade) is False @@ -2720,7 +2719,7 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, markets freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() trade.update(limit_buy_order) @@ -2778,7 +2777,7 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() trade.update(limit_buy_order) @@ -2841,7 +2840,7 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, fee, freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() trade.update(limit_buy_order) @@ -2900,7 +2899,7 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=True) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() trade.update(limit_buy_order) @@ -3157,7 +3156,7 @@ def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order, fee, whitelist = deepcopy(default_conf['exchange']['pair_whitelist']) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() assert trade is not None @@ -3191,7 +3190,7 @@ def test_order_book_depth_of_market_high_delta(default_conf, ticker, limit_buy_o # Save state of current whitelist freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() assert trade is None @@ -3297,7 +3296,7 @@ def test_order_book_ask_strategy(default_conf, limit_buy_order, limit_sell_order freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.create_trade() + freqtrade.create_trades() trade = Trade.query.first() assert trade From a4ab42560f9494b69432addeaab5cf8eb4dc3b82 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 14 Aug 2019 06:16:59 +0200 Subject: [PATCH 39/43] improve docstring for create_trades --- freqtrade/freqtradebot.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index e67a8687f..e2c88376e 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -258,9 +258,10 @@ class FreqtradeBot(object): def create_trades(self) -> bool: """ - Checks the implemented trading indicator(s) for a randomly picked pair, - if one pair triggers the buy_signal a new trade record gets created - :return: True if a trade object has been created and persisted, False otherwise + Checks the implemented trading strategy for buy-signals, using the active pair whitelist. + If a pair triggers the buy_signal a new trade record gets created. + Checks pairs as long as the open trade count is below `max_open_trades`. + :return: True if at least one trade has been created. """ interval = self.strategy.ticker_interval whitelist = copy.deepcopy(self.active_pair_whitelist) From d6f5f6b7ba0577b65b22216cac558a565a68051b Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 14 Aug 2019 06:21:15 +0200 Subject: [PATCH 40/43] Add test with preexisting trades --- freqtrade/tests/test_freqtradebot.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index d74615d6b..868cda4df 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -718,6 +718,33 @@ def test_create_trades_multiple_trades(default_conf, ticker, assert len(trades) == max_open +def test_create_trades_preopen(default_conf, ticker, fee, markets, mocker) -> None: + patch_RPCManager(mocker) + patch_exchange(mocker) + default_conf['max_open_trades'] = 4 + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_ticker=ticker, + buy=MagicMock(return_value={'id': "12355555"}), + get_fee=fee, + markets=PropertyMock(return_value=markets) + ) + freqtrade = FreqtradeBot(default_conf) + patch_get_signal(freqtrade) + + # Create 2 existing trades + freqtrade.execute_buy('ETH/BTC', default_conf['stake_amount']) + freqtrade.execute_buy('NEO/BTC', default_conf['stake_amount']) + + assert len(Trade.get_open_trades()) == 2 + + # Create 2 new trades using create_trades + assert freqtrade.create_trades() + + trades = Trade.get_open_trades() + assert len(trades) == 4 + + def test_process_trade_creation(default_conf, ticker, limit_buy_order, markets, fee, mocker, caplog) -> None: patch_RPCManager(mocker) From 06fa07e73e998b208c8af7b2ea9d26f36d36fed6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 14 Aug 2019 10:07:14 +0200 Subject: [PATCH 41/43] Move parse_timerange to TimeRange class --- freqtrade/configuration/arguments.py | 58 +------------------------ freqtrade/configuration/timerange.py | 65 ++++++++++++++++++++++++++++ freqtrade/tests/test_arguments.py | 26 +---------- freqtrade/tests/test_timerange.py | 28 ++++++++++++ 4 files changed, 95 insertions(+), 82 deletions(-) create mode 100644 freqtrade/configuration/timerange.py create mode 100644 freqtrade/tests/test_timerange.py diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index dd5a4290e..c129a7e47 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -2,10 +2,8 @@ This module contains the argument manager class """ import argparse -import re -from typing import List, NamedTuple, Optional +from typing import List, Optional -import arrow from freqtrade.configuration.cli_options import AVAILABLE_CLI_OPTIONS from freqtrade import constants @@ -43,18 +41,6 @@ ARGS_PLOT_PROFIT = (ARGS_COMMON + ARGS_STRATEGY + ["pairs", "timerange", "export", "exportfilename", "db_url", "trade_source"]) -class TimeRange(NamedTuple): - """ - NamedTuple defining timerange inputs. - [start/stop]type defines if [start/stop]ts shall be used. - if *type is None, don't use corresponding startvalue. - """ - starttype: Optional[str] = None - stoptype: Optional[str] = None - startts: int = 0 - stopts: int = 0 - - class Arguments(object): """ Arguments Class. Manage the arguments received by the cli @@ -133,45 +119,3 @@ class Arguments(object): ) list_exchanges_cmd.set_defaults(func=start_list_exchanges) self._build_args(optionlist=ARGS_LIST_EXCHANGES, parser=list_exchanges_cmd) - - @staticmethod - def parse_timerange(text: Optional[str]) -> TimeRange: - """ - Parse the value of the argument --timerange to determine what is the range desired - :param text: value from --timerange - :return: Start and End range period - """ - if text is None: - return TimeRange(None, None, 0, 0) - syntax = [(r'^-(\d{8})$', (None, 'date')), - (r'^(\d{8})-$', ('date', None)), - (r'^(\d{8})-(\d{8})$', ('date', 'date')), - (r'^-(\d{10})$', (None, 'date')), - (r'^(\d{10})-$', ('date', None)), - (r'^(\d{10})-(\d{10})$', ('date', 'date')), - (r'^(-\d+)$', (None, 'line')), - (r'^(\d+)-$', ('line', None)), - (r'^(\d+)-(\d+)$', ('index', 'index'))] - for rex, stype in syntax: - # Apply the regular expression to text - match = re.match(rex, text) - if match: # Regex has matched - rvals = match.groups() - index = 0 - start: int = 0 - stop: int = 0 - if stype[0]: - starts = rvals[index] - if stype[0] == 'date' and len(starts) == 8: - start = arrow.get(starts, 'YYYYMMDD').timestamp - else: - start = int(starts) - index += 1 - if stype[1]: - stops = rvals[index] - if stype[1] == 'date' and len(stops) == 8: - stop = arrow.get(stops, 'YYYYMMDD').timestamp - else: - stop = int(stops) - return TimeRange(stype[0], stype[1], start, stop) - raise Exception('Incorrect syntax for timerange "%s"' % text) diff --git a/freqtrade/configuration/timerange.py b/freqtrade/configuration/timerange.py new file mode 100644 index 000000000..b44704682 --- /dev/null +++ b/freqtrade/configuration/timerange.py @@ -0,0 +1,65 @@ +""" +This module contains the argument manager class +""" +import re +from typing import Optional + +import arrow + + +class TimeRange(): + """ + object defining timerange inputs. + [start/stop]type defines if [start/stop]ts shall be used. + if *type is None, don't use corresponding startvalue. + """ + + def __init__(self, starttype: Optional[str], stoptype: Optional[str], + startts: int, stopts: int): + + self.starttype: Optional[str] = starttype + self.stoptype: Optional[str] = stoptype + self.startts: int = startts + self.stopts: int = stopts + + @staticmethod + def parse_timerange(text: Optional[str]): + """ + Parse the value of the argument --timerange to determine what is the range desired + :param text: value from --timerange + :return: Start and End range period + """ + if text is None: + return TimeRange(None, None, 0, 0) + syntax = [(r'^-(\d{8})$', (None, 'date')), + (r'^(\d{8})-$', ('date', None)), + (r'^(\d{8})-(\d{8})$', ('date', 'date')), + (r'^-(\d{10})$', (None, 'date')), + (r'^(\d{10})-$', ('date', None)), + (r'^(\d{10})-(\d{10})$', ('date', 'date')), + (r'^(-\d+)$', (None, 'line')), + (r'^(\d+)-$', ('line', None)), + (r'^(\d+)-(\d+)$', ('index', 'index'))] + for rex, stype in syntax: + # Apply the regular expression to text + match = re.match(rex, text) + if match: # Regex has matched + rvals = match.groups() + index = 0 + start: int = 0 + stop: int = 0 + if stype[0]: + starts = rvals[index] + if stype[0] == 'date' and len(starts) == 8: + start = arrow.get(starts, 'YYYYMMDD').timestamp + else: + start = int(starts) + index += 1 + if stype[1]: + stops = rvals[index] + if stype[1] == 'date' and len(stops) == 8: + stop = arrow.get(stops, 'YYYYMMDD').timestamp + else: + stop = int(stops) + return TimeRange(stype[0], stype[1], start, stop) + raise Exception('Incorrect syntax for timerange "%s"' % text) diff --git a/freqtrade/tests/test_arguments.py b/freqtrade/tests/test_arguments.py index bf744f72b..2cb7ff6d7 100644 --- a/freqtrade/tests/test_arguments.py +++ b/freqtrade/tests/test_arguments.py @@ -3,7 +3,7 @@ import argparse import pytest -from freqtrade.configuration import Arguments, TimeRange +from freqtrade.configuration import Arguments from freqtrade.configuration.arguments import ARGS_DOWNLOADER, ARGS_PLOT_DATAFRAME from freqtrade.configuration.cli_options import check_int_positive @@ -86,30 +86,6 @@ def test_parse_args_strategy_path_invalid() -> None: Arguments(['--strategy-path'], '').get_parsed_arg() -def test_parse_timerange_incorrect() -> None: - assert TimeRange(None, 'line', 0, -200) == Arguments.parse_timerange('-200') - assert TimeRange('line', None, 200, 0) == Arguments.parse_timerange('200-') - assert TimeRange('index', 'index', 200, 500) == Arguments.parse_timerange('200-500') - - assert TimeRange('date', None, 1274486400, 0) == Arguments.parse_timerange('20100522-') - assert TimeRange(None, 'date', 0, 1274486400) == Arguments.parse_timerange('-20100522') - timerange = Arguments.parse_timerange('20100522-20150730') - assert timerange == TimeRange('date', 'date', 1274486400, 1438214400) - - # Added test for unix timestamp - BTC genesis date - assert TimeRange('date', None, 1231006505, 0) == Arguments.parse_timerange('1231006505-') - assert TimeRange(None, 'date', 0, 1233360000) == Arguments.parse_timerange('-1233360000') - timerange = Arguments.parse_timerange('1231006505-1233360000') - assert TimeRange('date', 'date', 1231006505, 1233360000) == timerange - - # TODO: Find solution for the following case (passing timestamp in ms) - timerange = Arguments.parse_timerange('1231006505000-1233360000000') - assert TimeRange('date', 'date', 1231006505, 1233360000) != timerange - - with pytest.raises(Exception, match=r'Incorrect syntax.*'): - Arguments.parse_timerange('-') - - def test_parse_args_backtesting_invalid() -> None: with pytest.raises(SystemExit, match=r'2'): Arguments(['backtesting --ticker-interval'], '').get_parsed_arg() diff --git a/freqtrade/tests/test_timerange.py b/freqtrade/tests/test_timerange.py new file mode 100644 index 000000000..6599472fb --- /dev/null +++ b/freqtrade/tests/test_timerange.py @@ -0,0 +1,28 @@ +# pragma pylint: disable=missing-docstring, C0103 +import pytest + +from freqtrade.configuration import TimeRange + + +def test_parse_timerange_incorrect() -> None: + assert TimeRange(None, 'line', 0, -200) == TimeRange.parse_timerange('-200') + assert TimeRange('line', None, 200, 0) == TimeRange.parse_timerange('200-') + assert TimeRange('index', 'index', 200, 500) == TimeRange.parse_timerange('200-500') + + assert TimeRange('date', None, 1274486400, 0) == TimeRange.parse_timerange('20100522-') + assert TimeRange(None, 'date', 0, 1274486400) == TimeRange.parse_timerange('-20100522') + timerange = TimeRange.parse_timerange('20100522-20150730') + assert timerange == TimeRange('date', 'date', 1274486400, 1438214400) + + # Added test for unix timestamp - BTC genesis date + assert TimeRange('date', None, 1231006505, 0) == TimeRange.parse_timerange('1231006505-') + assert TimeRange(None, 'date', 0, 1233360000) == TimeRange.parse_timerange('-1233360000') + timerange = TimeRange.parse_timerange('1231006505-1233360000') + assert TimeRange('date', 'date', 1231006505, 1233360000) == timerange + + # TODO: Find solution for the following case (passing timestamp in ms) + timerange = TimeRange.parse_timerange('1231006505000-1233360000000') + assert TimeRange('date', 'date', 1231006505, 1233360000) != timerange + + with pytest.raises(Exception, match=r'Incorrect syntax.*'): + TimeRange.parse_timerange('-') From 51c3a31bb58c01696634360b5547fa09b3fc0341 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 14 Aug 2019 10:07:32 +0200 Subject: [PATCH 42/43] Correct imports and calls to parse_timerange --- freqtrade/configuration/__init__.py | 3 ++- freqtrade/edge/__init__.py | 4 ++-- freqtrade/optimize/backtesting.py | 4 ++-- freqtrade/optimize/edge_cli.py | 4 ++-- freqtrade/optimize/hyperopt.py | 4 ++-- freqtrade/plot/plotting.py | 4 ++-- freqtrade/tests/data/test_btanalysis.py | 4 ++-- freqtrade/tests/test_plotting.py | 6 +++--- scripts/download_backtest_data.py | 2 +- 9 files changed, 18 insertions(+), 17 deletions(-) diff --git a/freqtrade/configuration/__init__.py b/freqtrade/configuration/__init__.py index 548b508a7..7b476d173 100644 --- a/freqtrade/configuration/__init__.py +++ b/freqtrade/configuration/__init__.py @@ -1,2 +1,3 @@ -from freqtrade.configuration.arguments import Arguments, TimeRange # noqa: F401 +from freqtrade.configuration.arguments import Arguments # noqa: F401 +from freqtrade.configuration.timerange import TimeRange # noqa: F401 from freqtrade.configuration.configuration import Configuration # noqa: F401 diff --git a/freqtrade/edge/__init__.py b/freqtrade/edge/__init__.py index 7085663d6..2d3097ec4 100644 --- a/freqtrade/edge/__init__.py +++ b/freqtrade/edge/__init__.py @@ -10,7 +10,7 @@ import utils_find_1st as utf1st from pandas import DataFrame from freqtrade import constants, OperationalException -from freqtrade.configuration import Arguments, TimeRange +from freqtrade.configuration import TimeRange from freqtrade.data import history from freqtrade.strategy.interface import SellType @@ -75,7 +75,7 @@ class Edge(): self._stoploss_range_step ) - self._timerange: TimeRange = Arguments.parse_timerange("%s-" % arrow.now().shift( + self._timerange: TimeRange = TimeRange.parse_timerange("%s-" % arrow.now().shift( days=-1 * self._since_number_of_days).format('YYYYMMDD')) self.fee = self.exchange.get_fee() diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 252175269..8f40a6582 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -12,7 +12,7 @@ from typing import Any, Dict, List, NamedTuple, Optional from pandas import DataFrame from freqtrade import OperationalException -from freqtrade.configuration import Arguments +from freqtrade.configuration import TimeRange from freqtrade.data import history from freqtrade.data.dataprovider import DataProvider from freqtrade.exchange import timeframe_to_minutes @@ -404,7 +404,7 @@ class Backtesting(object): logger.info('Using stake_currency: %s ...', self.config['stake_currency']) logger.info('Using stake_amount: %s ...', self.config['stake_amount']) - timerange = Arguments.parse_timerange(None if self.config.get( + timerange = TimeRange.parse_timerange(None if self.config.get( 'timerange') is None else str(self.config.get('timerange'))) data = history.load_data( datadir=Path(self.config['datadir']) if self.config.get('datadir') else None, diff --git a/freqtrade/optimize/edge_cli.py b/freqtrade/optimize/edge_cli.py index 8d1fa381b..7e0d60843 100644 --- a/freqtrade/optimize/edge_cli.py +++ b/freqtrade/optimize/edge_cli.py @@ -9,7 +9,7 @@ from tabulate import tabulate from freqtrade import constants from freqtrade.edge import Edge -from freqtrade.configuration import Arguments +from freqtrade.configuration import TimeRange from freqtrade.exchange import Exchange from freqtrade.resolvers import StrategyResolver @@ -41,7 +41,7 @@ class EdgeCli(object): self.edge = Edge(config, self.exchange, self.strategy) self.edge._refresh_pairs = self.config.get('refresh_pairs', False) - self.timerange = Arguments.parse_timerange(None if self.config.get( + self.timerange = TimeRange.parse_timerange(None if self.config.get( 'timerange') is None else str(self.config.get('timerange'))) self.edge._timerange = self.timerange diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 550f13e28..772b4a10f 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -20,7 +20,7 @@ from pandas import DataFrame from skopt import Optimizer from skopt.space import Dimension -from freqtrade.configuration import Arguments +from freqtrade.configuration import TimeRange from freqtrade.data.history import load_data, get_timeframe from freqtrade.optimize.backtesting import Backtesting # Import IHyperOptLoss to allow users import from this file @@ -310,7 +310,7 @@ class Hyperopt(Backtesting): ) def start(self) -> None: - timerange = Arguments.parse_timerange(None if self.config.get( + timerange = TimeRange.parse_timerange(None if self.config.get( 'timerange') is None else str(self.config.get('timerange'))) data = load_data( datadir=Path(self.config['datadir']) if self.config.get('datadir') else None, diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index d03d3ae53..947b3003c 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -4,7 +4,7 @@ from typing import Dict, List, Optional import pandas as pd -from freqtrade.configuration import Arguments +from freqtrade.configuration import TimeRange from freqtrade.data import history from freqtrade.data.btanalysis import (combine_tickers_with_mean, create_cum_profit, load_trades) @@ -42,7 +42,7 @@ def init_plotscript(config): pairs = config["exchange"]["pair_whitelist"] # Set timerange to use - timerange = Arguments.parse_timerange(config.get("timerange")) + timerange = TimeRange.parse_timerange(config.get("timerange")) tickers = history.load_data( datadir=Path(str(config.get("datadir"))), diff --git a/freqtrade/tests/data/test_btanalysis.py b/freqtrade/tests/data/test_btanalysis.py index bad8db66f..cf8cae566 100644 --- a/freqtrade/tests/data/test_btanalysis.py +++ b/freqtrade/tests/data/test_btanalysis.py @@ -4,7 +4,7 @@ import pytest from arrow import Arrow from pandas import DataFrame, to_datetime -from freqtrade.configuration import Arguments, TimeRange +from freqtrade.configuration import TimeRange from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, combine_tickers_with_mean, create_cum_profit, @@ -121,7 +121,7 @@ def test_combine_tickers_with_mean(): def test_create_cum_profit(): filename = make_testdata_path(None) / "backtest-result_test.json" bt_data = load_backtest_data(filename) - timerange = Arguments.parse_timerange("20180110-20180112") + timerange = TimeRange.parse_timerange("20180110-20180112") df = load_pair_history(pair="POWR/BTC", ticker_interval='5m', datadir=None, timerange=timerange) diff --git a/freqtrade/tests/test_plotting.py b/freqtrade/tests/test_plotting.py index bfdd72215..cd72160f8 100644 --- a/freqtrade/tests/test_plotting.py +++ b/freqtrade/tests/test_plotting.py @@ -5,7 +5,7 @@ from unittest.mock import MagicMock import plotly.graph_objects as go from plotly.subplots import make_subplots -from freqtrade.configuration import Arguments, TimeRange +from freqtrade.configuration import TimeRange from freqtrade.data import history from freqtrade.data.btanalysis import create_cum_profit, load_backtest_data from freqtrade.plot.plotting import (add_indicators, add_profit, @@ -222,7 +222,7 @@ def test_generate_plot_file(mocker, caplog): def test_add_profit(): filename = history.make_testdata_path(None) / "backtest-result_test.json" bt_data = load_backtest_data(filename) - timerange = Arguments.parse_timerange("20180110-20180112") + timerange = TimeRange.parse_timerange("20180110-20180112") df = history.load_pair_history(pair="POWR/BTC", ticker_interval='5m', datadir=None, timerange=timerange) @@ -242,7 +242,7 @@ def test_add_profit(): def test_generate_profit_graph(): filename = history.make_testdata_path(None) / "backtest-result_test.json" trades = load_backtest_data(filename) - timerange = Arguments.parse_timerange("20180110-20180112") + timerange = TimeRange.parse_timerange("20180110-20180112") pairs = ["POWR/BTC", "XLM/BTC"] tickers = history.load_data(datadir=None, diff --git a/scripts/download_backtest_data.py b/scripts/download_backtest_data.py index 580592294..f77ad7422 100755 --- a/scripts/download_backtest_data.py +++ b/scripts/download_backtest_data.py @@ -105,7 +105,7 @@ if not pairs or args.pairs_file: timerange = TimeRange() if args.days: time_since = arrow.utcnow().shift(days=-args.days).strftime("%Y%m%d") - timerange = arguments.parse_timerange(f'{time_since}-') + timerange = TimeRange.parse_timerange(f'{time_since}-') logger.info(f'About to download pairs: {pairs}, intervals: {timeframes} to {dl_path}') From 096a6426dbf1142c5443371f9569e4f51870d148 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 14 Aug 2019 10:22:54 +0200 Subject: [PATCH 43/43] Override equality operator --- freqtrade/configuration/timerange.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/freqtrade/configuration/timerange.py b/freqtrade/configuration/timerange.py index b44704682..f980b71ea 100644 --- a/freqtrade/configuration/timerange.py +++ b/freqtrade/configuration/timerange.py @@ -14,14 +14,19 @@ class TimeRange(): if *type is None, don't use corresponding startvalue. """ - def __init__(self, starttype: Optional[str], stoptype: Optional[str], - startts: int, stopts: int): + def __init__(self, starttype: Optional[str] = None, stoptype: Optional[str] = None, + startts: int = 0, stopts: int = 0): self.starttype: Optional[str] = starttype self.stoptype: Optional[str] = stoptype self.startts: int = startts self.stopts: int = stopts + def __eq__(self, other): + """Override the default Equals behavior""" + return (self.starttype == other.starttype and self.stoptype == other.stoptype + and self.startts == other.startts and self.stopts == other.stopts) + @staticmethod def parse_timerange(text: Optional[str]): """