diff --git a/docs/bot-usage.md b/docs/bot-usage.md index f720bf554..2a5907b30 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -59,8 +59,8 @@ working directory. The bot allows you to use multiple configuration files by specifying multiple `-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. +defined in the latter configuration files override parameters with the same name +defined in the previous configuration files specified in the command line earlier. For example, you can make a separate configuration file with your key and secrete for the Exchange you use for trading, specify default configuration file with @@ -259,12 +259,13 @@ 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: - `DefaultHyperOptLoss`). + target for optimization is different. Built-in + Hyperopt-loss-functions are: DefaultHyperOptLoss, + OnlyProfitHyperOptLoss, SharpeHyperOptLoss. + (default: `DefaultHyperOptLoss`). ``` ## Edge commands diff --git a/docs/data-analysis.md b/docs/data-analysis.md index ecd94445b..c89353cc8 100644 --- a/docs/data-analysis.md +++ b/docs/data-analysis.md @@ -31,6 +31,16 @@ df = load_trades_from_db("sqlite:///tradesv3.sqlite") df.groupby("pair")["sell_reason"].value_counts() ``` +### Load multiple configuration files + +This option can be usefull to inspect the results of passing in multiple configs in case of problems + +``` python +from freqtrade.configuration import Configuration +config = Configuration.from_files(["config1.json", "config2.json"]) +print(config) +``` + ## Strategy debugging example Debugging a strategy can be time-consuming. FreqTrade offers helper functions to visualize raw data. diff --git a/docs/developer.md b/docs/developer.md index 28369bb73..259bfafd8 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -17,6 +17,29 @@ Alternatively (if your system is not supported by the setup.sh script), follow t This will install all required tools for development, including `pytest`, `flake8`, `mypy`, and `coveralls`. +### Tests + +New code should be covered by basic unittests. Depending on the complexity of the feature, Reviewers may request more in-depth unittests. +If necessary, the Freqtrade team can assist and give guidance with writing good tests (however please don't expect anyone to write the tests for you). + +#### Checking log content in tests + +Freqtrade uses 2 main methods to check log content in tests, `log_has()` and `log_has_re()` (to check using regex, in case of dynamic log-messages). +These are available from `conftest.py` and can be imported in any test module. + +A sample check looks as follows: + +``` python +from freqtrade.tests.conftest import log_has, log_has_re + +def test_method_to_test(caplog): + method_to_test() + + assert log_has("This event happened", caplog) + # Check regex with trailing number ... + assert log_has_re(r"This dynamic event happened and produced \d+", caplog) +``` + ## Modules ### Dynamic Pairlist diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 6ef68d82f..918361c52 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -164,7 +164,11 @@ By default, FreqTrade uses a loss function, which has been with freqtrade since A different loss function can be specified by using the `--hyperopt-loss ` argument. This class should be in its own file within the `user_data/hyperopts/` directory. -Currently, the following loss functions are builtin: `DefaultHyperOptLoss` (default legacy Freqtrade hyperoptimization loss function), `SharpeHyperOptLoss` (optimizes Sharpe Ratio calculated on the trade returns) and `OnlyProfitHyperOptLoss` (which takes only amount of profit into consideration). +Currently, the following loss functions are builtin: + +* `DefaultHyperOptLoss` (default legacy Freqtrade hyperoptimization loss function) +* `OnlyProfitHyperOptLoss` (which takes only amount of profit into consideration) +* `SharpeHyperOptLoss` (optimizes Sharpe Ratio calculated on the trade returns) ### Creating and using a custom loss function diff --git a/freqtrade/configuration/cli_options.py b/freqtrade/configuration/cli_options.py index 04554c386..771cdfb80 100644 --- a/freqtrade/configuration/cli_options.py +++ b/freqtrade/configuration/cli_options.py @@ -226,7 +226,9 @@ AVAILABLE_CLI_OPTIONS = { '--hyperopt-loss', help='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: `%(default)s`).', + 'since the target for optimization is different. Built-in Hyperopt-loss-functions are: ' + 'DefaultHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss.' + '(default: `%(default)s`).', metavar='NAME', default=constants.DEFAULT_HYPEROPT_LOSS, ), diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index e564c79ce..237346e37 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -4,7 +4,7 @@ This module contains the configuration class import logging import warnings from argparse import Namespace -from typing import Any, Callable, Dict, Optional +from typing import Any, Callable, Dict, List, Optional from freqtrade import OperationalException, constants from freqtrade.configuration.check_exchange import check_exchange @@ -39,43 +39,43 @@ class Configuration(object): return self.config - def _load_config_files(self) -> Dict[str, Any]: + @staticmethod + def from_files(files: List[str]) -> Dict[str, Any]: """ - Iterate through the config files passed in the args, - loading all of them and merging their contents. + Iterate through the config files passed in, loading all of them + and merging their contents. + Files are loaded in sequence, parameters in later configuration files + override the same parameter from an earlier file (last definition wins). + :param files: List of file paths + :return: configuration dictionary """ + # Keep this method as staticmethod, so it can be used from interactive environments config: Dict[str, Any] = {} # We expect here a list of config filenames - for path in self.args.config: - logger.info('Using config: %s ...', path) + for path in files: + logger.info(f'Using config: {path} ...') # Merge config options, overwriting old values config = deep_merge_dicts(load_config_file(path), config) - return config - - def _normalize_config(self, config: Dict[str, Any]) -> None: - """ - Make config more canonical -- i.e. for example add missing parts that we expect - to be normally in it... - """ + # Normalize config if 'internals' not in config: config['internals'] = {} + # validate configuration before returning + logger.info('Validating configuration ...') + validate_config_schema(config) + + return config + def load_config(self) -> Dict[str, Any]: """ Extract information for sys.argv and load the bot configuration :return: Configuration dictionary """ # Load all configs - config: Dict[str, Any] = self._load_config_files() - - # Make resulting config more canonical - self._normalize_config(config) - - logger.info('Validating configuration ...') - validate_config_schema(config) + config: Dict[str, Any] = Configuration.from_files(self.args.config) self._validate_config_consistency(config) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d52165e0a..603b0631f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -20,7 +20,7 @@ from freqtrade.exchange import timeframe_to_minutes from freqtrade.persistence import Trade from freqtrade.rpc import RPCManager, RPCMessageType from freqtrade.resolvers import ExchangeResolver, StrategyResolver, PairListResolver -from freqtrade.state import State +from freqtrade.state import State, RunMode from freqtrade.strategy.interface import SellType, IStrategy from freqtrade.wallets import Wallets @@ -75,6 +75,12 @@ class FreqtradeBot(object): persistence.init(self.config.get('db_url', None), clean_open_orders=self.config.get('dry_run', False)) + # Stoploss on exchange does not make sense, therefore we need to disable that. + if (self.dataprovider.runmode == RunMode.DRY_RUN and + self.strategy.order_types.get('stoploss_on_exchange', False)): + logger.info("Disabling stoploss_on_exchange during dry-run.") + self.strategy.order_types['stoploss_on_exchange'] = False + config['order_types']['stoploss_on_exchange'] = False # Set initial bot state from config initial_state = self.config.get('initial_state') self.state = State[initial_state.upper()] if initial_state else State.STOPPED diff --git a/freqtrade/optimize/default_hyperopt.py b/freqtrade/optimize/default_hyperopt.py index e05dfc95c..2554982ad 100644 --- a/freqtrade/optimize/default_hyperopt.py +++ b/freqtrade/optimize/default_hyperopt.py @@ -14,36 +14,48 @@ from freqtrade.optimize.hyperopt_interface import IHyperOpt class DefaultHyperOpts(IHyperOpt): """ Default hyperopt provided by the Freqtrade bot. - You can override it with your own hyperopt + You can override it with your own Hyperopt """ @staticmethod def populate_indicators(dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Add several indicators needed for buy and sell strategies defined below. + """ + # ADX dataframe['adx'] = ta.ADX(dataframe) + # MACD macd = ta.MACD(dataframe) dataframe['macd'] = macd['macd'] dataframe['macdsignal'] = macd['macdsignal'] + # MFI dataframe['mfi'] = ta.MFI(dataframe) + # RSI dataframe['rsi'] = ta.RSI(dataframe) + # Stochastic Fast stoch_fast = ta.STOCHF(dataframe) dataframe['fastd'] = stoch_fast['fastd'] + # Minus-DI dataframe['minus_di'] = ta.MINUS_DI(dataframe) # Bollinger bands bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) dataframe['bb_lowerband'] = bollinger['lower'] dataframe['bb_upperband'] = bollinger['upper'] + # SAR dataframe['sar'] = ta.SAR(dataframe) + return dataframe @staticmethod def buy_strategy_generator(params: Dict[str, Any]) -> Callable: """ - Define the buy strategy parameters to be used by hyperopt + Define the buy strategy parameters to be used by Hyperopt. """ def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame: """ - Buy strategy Hyperopt will build and use + Buy strategy Hyperopt will build and use. """ conditions = [] + # GUARDS AND TRENDS if 'mfi-enabled' in params and params['mfi-enabled']: conditions.append(dataframe['mfi'] < params['mfi-value']) @@ -79,7 +91,7 @@ class DefaultHyperOpts(IHyperOpt): @staticmethod def indicator_space() -> List[Dimension]: """ - Define your Hyperopt space for searching strategy parameters + Define your Hyperopt space for searching buy strategy parameters. """ return [ Integer(10, 25, name='mfi-value'), @@ -96,14 +108,14 @@ class DefaultHyperOpts(IHyperOpt): @staticmethod def sell_strategy_generator(params: Dict[str, Any]) -> Callable: """ - Define the sell strategy parameters to be used by hyperopt + Define the sell strategy parameters to be used by Hyperopt. """ def populate_sell_trend(dataframe: DataFrame, metadata: dict) -> DataFrame: """ - Sell strategy Hyperopt will build and use + Sell strategy Hyperopt will build and use. """ - # print(params) conditions = [] + # GUARDS AND TRENDS if 'sell-mfi-enabled' in params and params['sell-mfi-enabled']: conditions.append(dataframe['mfi'] > params['sell-mfi-value']) @@ -139,7 +151,7 @@ class DefaultHyperOpts(IHyperOpt): @staticmethod def sell_indicator_space() -> List[Dimension]: """ - Define your Hyperopt space for searching sell strategy parameters + Define your Hyperopt space for searching sell strategy parameters. """ return [ Integer(75, 100, name='sell-mfi-value'), @@ -157,9 +169,9 @@ class DefaultHyperOpts(IHyperOpt): def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ - Based on TA indicators. Should be a copy of from strategy - must align to populate_indicators in this file - Only used when --spaces does not include buy + Based on TA indicators. Should be a copy of same method from strategy. + Must align to populate_indicators in this file. + Only used when --spaces does not include buy space. """ dataframe.loc[ ( @@ -174,9 +186,9 @@ class DefaultHyperOpts(IHyperOpt): def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ - Based on TA indicators. Should be a copy of from strategy - must align to populate_indicators in this file - Only used when --spaces does not include sell + Based on TA indicators. Should be a copy of same method from strategy. + Must align to populate_indicators in this file. + Only used when --spaces does not include sell space. """ dataframe.loc[ ( @@ -186,4 +198,5 @@ class DefaultHyperOpts(IHyperOpt): (dataframe['fastd'] > 54) ), 'sell'] = 1 + return dataframe diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 8c995b7c0..df9513cdb 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -34,13 +34,13 @@ def log_has(line, logs): # caplog mocker returns log as a tuple: ('freqtrade.something', logging.WARNING, 'foobar') # and we want to match line against foobar in the tuple return reduce(lambda a, b: a or b, - filter(lambda x: x[2] == line, logs), + filter(lambda x: x[2] == line, logs.record_tuples), False) def log_has_re(line, logs): return reduce(lambda a, b: a or b, - filter(lambda x: re.match(line, x[2]), logs), + filter(lambda x: re.match(line, x[2]), logs.record_tuples), False) diff --git a/freqtrade/tests/data/test_converter.py b/freqtrade/tests/data/test_converter.py index f68224e0e..39462bdd8 100644 --- a/freqtrade/tests/data/test_converter.py +++ b/freqtrade/tests/data/test_converter.py @@ -18,7 +18,7 @@ def test_parse_ticker_dataframe(ticker_history_list, caplog): dataframe = parse_ticker_dataframe(ticker_history_list, '5m', pair="UNITTEST/BTC", fill_missing=True) assert dataframe.columns.tolist() == columns - assert log_has('Parsing tickerlist to dataframe', caplog.record_tuples) + assert log_has('Parsing tickerlist to dataframe', caplog) def test_ohlcv_fill_up_missing_data(caplog): @@ -34,8 +34,7 @@ def test_ohlcv_fill_up_missing_data(caplog): assert (data.columns == data2.columns).all() assert log_has(f"Missing data fillup for UNITTEST/BTC: before: " - f"{len(data)} - after: {len(data2)}", - caplog.record_tuples) + f"{len(data)} - after: {len(data2)}", caplog) # Test fillup actually fixes invalid backtest data min_date, max_date = get_timeframe({'UNITTEST/BTC': data}) @@ -97,8 +96,7 @@ def test_ohlcv_fill_up_missing_data2(caplog): assert (data.columns == data2.columns).all() assert log_has(f"Missing data fillup for UNITTEST/BTC: before: " - f"{len(data)} - after: {len(data2)}", - caplog.record_tuples) + f"{len(data)} - after: {len(data2)}", caplog) def test_ohlcv_drop_incomplete(caplog): @@ -140,11 +138,11 @@ def test_ohlcv_drop_incomplete(caplog): data = parse_ticker_dataframe(ticks, ticker_interval, pair="UNITTEST/BTC", fill_missing=False, drop_incomplete=False) assert len(data) == 4 - assert not log_has("Dropping last candle", caplog.record_tuples) + assert not log_has("Dropping last candle", caplog) # Drop last candle data = parse_ticker_dataframe(ticks, ticker_interval, pair="UNITTEST/BTC", fill_missing=False, drop_incomplete=True) assert len(data) == 3 - assert log_has("Dropping last candle", caplog.record_tuples) + assert log_has("Dropping last candle", caplog) diff --git a/freqtrade/tests/data/test_history.py b/freqtrade/tests/data/test_history.py index 424333e99..00f4738f7 100644 --- a/freqtrade/tests/data/test_history.py +++ b/freqtrade/tests/data/test_history.py @@ -64,8 +64,7 @@ def test_load_data_30min_ticker(mocker, caplog, default_conf) -> None: assert isinstance(ld, DataFrame) assert not log_has( 'Download history data for pair: "UNITTEST/BTC", interval: 30m ' - 'and store in None.', - caplog.record_tuples + 'and store in None.', caplog ) @@ -76,8 +75,7 @@ def test_load_data_7min_ticker(mocker, caplog, default_conf) -> None: assert log_has( 'No history data for pair: "UNITTEST/BTC", interval: 7m. ' 'Use --refresh-pairs-cached option or download_backtest_data.py ' - 'script to download the data', - caplog.record_tuples + 'script to download the data', caplog ) @@ -89,8 +87,7 @@ def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None: assert os.path.isfile(file) is True assert not log_has( 'Download history data for pair: "UNITTEST/BTC", interval: 1m ' - 'and store in None.', - caplog.record_tuples + 'and store in None.', caplog ) _clean_test_file(file) @@ -113,8 +110,7 @@ def test_load_data_with_new_pair_1min(ticker_history_list, mocker, caplog, defau assert log_has( 'No history data for pair: "MEME/BTC", interval: 1m. ' 'Use --refresh-pairs-cached option or download_backtest_data.py ' - 'script to download the data', - caplog.record_tuples + 'script to download the data', caplog ) # download a new pair if refresh_pairs is set @@ -126,8 +122,7 @@ def test_load_data_with_new_pair_1min(ticker_history_list, mocker, caplog, defau assert os.path.isfile(file) is True assert log_has( 'Download history data for pair: "MEME/BTC", interval: 1m ' - 'and store in None.', - caplog.record_tuples + 'and store in None.', caplog ) with pytest.raises(OperationalException, match=r'Exchange needs to be initialized when.*'): history.load_pair_history(datadir=None, @@ -149,7 +144,7 @@ def test_load_data_live(default_conf, mocker, caplog) -> None: exchange=exchange) assert refresh_mock.call_count == 1 assert len(refresh_mock.call_args_list[0][0][0]) == 2 - assert log_has('Live: Downloading data for all defined pairs ...', caplog.record_tuples) + assert log_has('Live: Downloading data for all defined pairs ...', caplog) def test_load_data_live_noexchange(default_conf, mocker, caplog) -> None: @@ -350,8 +345,7 @@ def test_download_backtesting_data_exception(ticker_history, mocker, caplog, def _clean_test_file(file1_5) assert log_has( 'Failed to download history data for pair: "MEME/BTC", interval: 1m. ' - 'Error: File Error', - caplog.record_tuples + 'Error: File Error', caplog ) @@ -380,7 +374,7 @@ def test_load_partial_missing(caplog) -> None: start_real = tickerdata['UNITTEST/BTC'].iloc[0, 0] assert log_has(f'Missing data at start for pair ' f'UNITTEST/BTC, data starts at {start_real.strftime("%Y-%m-%d %H:%M:%S")}', - caplog.record_tuples) + caplog) # Make sure we start fresh - test missing data at end caplog.clear() start = arrow.get('2018-01-10T00:00:00') @@ -396,7 +390,7 @@ def test_load_partial_missing(caplog) -> None: end_real = arrow.get(tickerdata['UNITTEST/BTC'].iloc[-1, 0]).shift(minutes=5) assert log_has(f'Missing data at end for pair ' f'UNITTEST/BTC, data ends at {end_real.strftime("%Y-%m-%d %H:%M:%S")}', - caplog.record_tuples) + caplog) def test_init(default_conf, mocker) -> None: @@ -560,7 +554,7 @@ def test_validate_backtest_data_warn(default_conf, mocker, caplog) -> None: assert len(caplog.record_tuples) == 1 assert log_has( "UNITTEST/BTC has missing frames: expected 14396, got 13680, that's 716 missing values", - caplog.record_tuples) + caplog) def test_validate_backtest_data(default_conf, mocker, caplog) -> None: diff --git a/freqtrade/tests/edge/test_edge.py b/freqtrade/tests/edge/test_edge.py index 45b8e609e..09fa1d93e 100644 --- a/freqtrade/tests/edge/test_edge.py +++ b/freqtrade/tests/edge/test_edge.py @@ -311,7 +311,7 @@ def test_edge_process_no_data(mocker, edge_conf, caplog): assert not edge.calculate() assert len(edge._cached_pairs) == 0 - assert log_has("No data found. Edge is stopped ...", caplog.record_tuples) + assert log_has("No data found. Edge is stopped ...", caplog) assert edge._last_updated == 0 @@ -326,7 +326,7 @@ def test_edge_process_no_trades(mocker, edge_conf, caplog): assert not edge.calculate() assert len(edge._cached_pairs) == 0 - assert log_has("No trades found.", caplog.record_tuples) + assert log_has("No trades found.", caplog) def test_edge_init_error(mocker, edge_conf,): diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index ebe5ad9df..e8a7201f1 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -62,7 +62,7 @@ async def async_ccxt_exception(mocker, default_conf, api_mock, fun, mock_ccxt_fu def test_init(default_conf, mocker, caplog): caplog.set_level(logging.INFO) get_patched_exchange(mocker, default_conf) - assert log_has('Instance is running with dry_run enabled', caplog.record_tuples) + assert log_has('Instance is running with dry_run enabled', caplog) def test_init_ccxt_kwargs(default_conf, mocker, caplog): @@ -71,8 +71,7 @@ def test_init_ccxt_kwargs(default_conf, mocker, caplog): conf = copy.deepcopy(default_conf) conf['exchange']['ccxt_async_config'] = {'aiohttp_trust_env': True} ex = Exchange(conf) - assert log_has("Applying additional ccxt config: {'aiohttp_trust_env': True}", - caplog.record_tuples) + assert log_has("Applying additional ccxt config: {'aiohttp_trust_env': True}", caplog) assert ex._api_async.aiohttp_trust_env assert not ex._api.aiohttp_trust_env @@ -81,20 +80,18 @@ def test_init_ccxt_kwargs(default_conf, mocker, caplog): conf = copy.deepcopy(default_conf) conf['exchange']['ccxt_config'] = {'TestKWARG': 11} ex = Exchange(conf) - assert not log_has("Applying additional ccxt config: {'aiohttp_trust_env': True}", - caplog.record_tuples) + assert not log_has("Applying additional ccxt config: {'aiohttp_trust_env': True}", caplog) assert not ex._api_async.aiohttp_trust_env assert hasattr(ex._api, 'TestKWARG') assert ex._api.TestKWARG == 11 assert not hasattr(ex._api_async, 'TestKWARG') - assert log_has("Applying additional ccxt config: {'TestKWARG': 11}", - caplog.record_tuples) + assert log_has("Applying additional ccxt config: {'TestKWARG': 11}", caplog) def test_destroy(default_conf, mocker, caplog): caplog.set_level(logging.DEBUG) get_patched_exchange(mocker, default_conf) - assert log_has('Exchange object destroyed, closing async loop', caplog.record_tuples) + assert log_has('Exchange object destroyed, closing async loop', caplog) def test_init_exception(default_conf, mocker): @@ -120,8 +117,7 @@ def test_exchange_resolver(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) exchange = ExchangeResolver('Bittrex', default_conf).exchange assert isinstance(exchange, Exchange) - assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", - caplog.record_tuples) + assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog) caplog.clear() exchange = ExchangeResolver('kraken', default_conf).exchange @@ -129,7 +125,7 @@ def test_exchange_resolver(default_conf, mocker, caplog): assert isinstance(exchange, Kraken) assert not isinstance(exchange, Binance) assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.", - caplog.record_tuples) + caplog) exchange = ExchangeResolver('binance', default_conf).exchange assert isinstance(exchange, Exchange) @@ -137,7 +133,7 @@ def test_exchange_resolver(default_conf, mocker, caplog): assert not isinstance(exchange, Kraken) assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.", - caplog.record_tuples) + caplog) def test_validate_order_time_in_force(default_conf, mocker, caplog): @@ -249,8 +245,7 @@ def test__load_async_markets(default_conf, mocker, caplog): exchange._api_async.load_markets = Mock(side_effect=ccxt.BaseError("deadbeef")) exchange._load_async_markets() - assert log_has('Could not load async markets. Reason: deadbeef', - caplog.record_tuples) + assert log_has('Could not load async markets. Reason: deadbeef', caplog) def test__load_markets(default_conf, mocker, caplog): @@ -262,7 +257,7 @@ def test__load_markets(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock()) Exchange(default_conf) - assert log_has('Unable to initialize markets. Reason: SomeError', caplog.record_tuples) + assert log_has('Unable to initialize markets. Reason: SomeError', caplog) expected_return = {'ETH/BTC': 'available'} api_mock = MagicMock() @@ -298,7 +293,7 @@ def test__reload_markets(default_conf, mocker, caplog): exchange._last_markets_refresh = arrow.utcnow().timestamp - 15 * 60 exchange._reload_markets() assert exchange.markets == updated_markets - assert log_has('Performing scheduled market reload..', caplog.record_tuples) + assert log_has('Performing scheduled market reload..', caplog) def test__reload_markets_exception(default_conf, mocker, caplog): @@ -312,7 +307,7 @@ def test__reload_markets_exception(default_conf, mocker, caplog): # less than 10 minutes have passed, no reload exchange._reload_markets() assert exchange._last_markets_refresh == 0 - assert log_has_re(r"Could not reload markets.*", caplog.record_tuples) + assert log_has_re(r"Could not reload markets.*", caplog) def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly @@ -357,8 +352,7 @@ def test_validate_pairs_exception(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value={})) Exchange(default_conf) - assert log_has('Unable to validate pairs (assuming they are correct).', - caplog.record_tuples) + assert log_has('Unable to validate pairs (assuming they are correct).', caplog) def test_validate_pairs_restricted(default_conf, mocker, caplog): @@ -374,8 +368,7 @@ def test_validate_pairs_restricted(default_conf, mocker, caplog): Exchange(default_conf) assert log_has(f"Pair XRP/BTC is restricted for some users on this exchange." f"Please check if you are impacted by this restriction " - f"on the exchange and eventually remove XRP/BTC from your whitelist.", - caplog.record_tuples) + f"on the exchange and eventually remove XRP/BTC from your whitelist.", caplog) def test_validate_timeframes(default_conf, mocker): @@ -1060,7 +1053,7 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: assert not exchange._klines exchange.refresh_latest_ohlcv(pairs) - assert log_has(f'Refreshing ohlcv data for {len(pairs)} pairs', caplog.record_tuples) + assert log_has(f'Refreshing ohlcv data for {len(pairs)} pairs', caplog) assert exchange._klines assert exchange._api_async.fetch_ohlcv.call_count == 2 for pair in pairs: @@ -1079,7 +1072,7 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: assert exchange._api_async.fetch_ohlcv.call_count == 2 assert log_has(f"Using cached ohlcv data for pair {pairs[0][0]}, interval {pairs[0][1]} ...", - caplog.record_tuples) + caplog) @pytest.mark.asyncio @@ -1109,7 +1102,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_ assert res[1] == "5m" assert res[2] == tick assert exchange._api_async.fetch_ohlcv.call_count == 1 - assert not log_has(f"Using cached ohlcv data for {pair} ...", caplog.record_tuples) + assert not log_has(f"Using cached ohlcv data for {pair} ...", caplog) # exchange = Exchange(default_conf) await async_ccxt_exception(mocker, default_conf, MagicMock(), @@ -1168,8 +1161,8 @@ def test_refresh_latest_ohlcv_inv_result(default_conf, mocker, caplog): # Test that each is in list at least once as order is not guaranteed assert type(res[0]) is tuple or type(res[1]) is tuple assert type(res[0]) is TypeError or type(res[1]) is TypeError - assert log_has("Error loading ETH/BTC. Result was [[]].", caplog.record_tuples) - assert log_has("Async code raised an exception: TypeError", caplog.record_tuples) + assert log_has("Error loading ETH/BTC. Result was [[]].", caplog) + assert log_has("Async code raised an exception: TypeError", caplog) @pytest.mark.parametrize("exchange_name", EXCHANGES) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 71d460621..9ed7e7296 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -181,21 +181,18 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> assert 'exchange' in config assert 'pair_whitelist' in config['exchange'] assert 'datadir' in config - assert log_has( - 'Using data directory: {} ...'.format(config['datadir']), - caplog.record_tuples - ) + assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert 'ticker_interval' in config - assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog.record_tuples) + assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog) assert 'live' not in config - assert not log_has('Parameter -l/--live detected ...', caplog.record_tuples) + assert not log_has('Parameter -l/--live detected ...', caplog) assert 'position_stacking' not in config - assert not log_has('Parameter --enable-position-stacking detected ...', caplog.record_tuples) + assert not log_has('Parameter --enable-position-stacking detected ...', caplog) assert 'refresh_pairs' not in config - assert not log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples) + assert not log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog) assert 'timerange' not in config assert 'export' not in config @@ -235,43 +232,31 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) -> assert 'datadir' in config assert config['runmode'] == RunMode.BACKTEST - assert log_has( - 'Using data directory: {} ...'.format(config['datadir']), - caplog.record_tuples - ) + assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert 'ticker_interval' in config assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', - caplog.record_tuples) + caplog) assert 'live' in config - assert log_has('Parameter -l/--live detected ...', caplog.record_tuples) + assert log_has('Parameter -l/--live detected ...', caplog) assert 'position_stacking' in config - assert log_has('Parameter --enable-position-stacking detected ...', caplog.record_tuples) + assert log_has('Parameter --enable-position-stacking detected ...', caplog) assert 'use_max_market_positions' in config - assert log_has('Parameter --disable-max-market-positions detected ...', caplog.record_tuples) - assert log_has('max_open_trades set to unlimited ...', caplog.record_tuples) + assert log_has('Parameter --disable-max-market-positions detected ...', caplog) + assert log_has('max_open_trades set to unlimited ...', caplog) assert 'refresh_pairs' in config - assert log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples) + assert log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog) assert 'timerange' in config - assert log_has( - 'Parameter --timerange detected: {} ...'.format(config['timerange']), - caplog.record_tuples - ) + assert log_has('Parameter --timerange detected: {} ...'.format(config['timerange']), caplog) assert 'export' in config - assert log_has( - 'Parameter --export detected: {} ...'.format(config['export']), - caplog.record_tuples - ) + assert log_has('Parameter --export detected: {} ...'.format(config['export']), caplog) assert 'exportfilename' in config - assert log_has( - 'Storing backtest results to {} ...'.format(config['exportfilename']), - caplog.record_tuples - ) + assert log_has('Storing backtest results to {} ...'.format(config['exportfilename']), caplog) def test_setup_configuration_unlimited_stake_amount(mocker, default_conf, caplog) -> None: @@ -303,10 +288,7 @@ def test_start(mocker, fee, default_conf, caplog) -> None: ] args = get_args(args) start_backtesting(args) - assert log_has( - 'Starting freqtrade in Backtesting mode', - caplog.record_tuples - ) + assert log_has('Starting freqtrade in Backtesting mode', caplog) assert start_mock.call_count == 1 @@ -360,7 +342,7 @@ def test_backtesting_init_no_ticker_interval(mocker, default_conf, caplog) -> No with pytest.raises(OperationalException): Backtesting(default_conf) log_has("Ticker-interval needs to be set in either configuration " - "or as cli argument `--ticker-interval 5m`", caplog.record_tuples) + "or as cli argument `--ticker-interval 5m`", caplog) def test_tickerdata_to_dataframe_bt(default_conf, mocker) -> None: @@ -511,7 +493,7 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None: 'up to 2017-11-14T22:59:00+00:00 (0 days)..' ] for line in exists: - assert log_has(line, caplog.record_tuples) + assert log_has(line, caplog) def test_backtesting_start_no_data(default_conf, mocker, caplog) -> None: @@ -539,7 +521,7 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog) -> None: backtesting.start() # check the logs, that will contain the backtest result - assert log_has('No data found. Terminating.', caplog.record_tuples) + assert log_has('No data found. Terminating.', caplog) def test_backtest(default_conf, fee, mocker) -> None: @@ -876,7 +858,7 @@ def test_backtest_start_live(default_conf, mocker, caplog): ] for line in exists: - assert log_has(line, caplog.record_tuples) + assert log_has(line, caplog) @pytest.mark.filterwarnings("ignore:DEPRECATED") @@ -936,4 +918,4 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog): ] for line in exists: - assert log_has(line, caplog.record_tuples) + assert log_has(line, caplog) diff --git a/freqtrade/tests/optimize/test_edge_cli.py b/freqtrade/tests/optimize/test_edge_cli.py index badaa5c45..cdc724db2 100644 --- a/freqtrade/tests/optimize/test_edge_cli.py +++ b/freqtrade/tests/optimize/test_edge_cli.py @@ -29,15 +29,12 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> assert 'exchange' in config assert 'pair_whitelist' in config['exchange'] assert 'datadir' in config - assert log_has( - 'Using data directory: {} ...'.format(config['datadir']), - caplog.record_tuples - ) + assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert 'ticker_interval' in config - assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog.record_tuples) + assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog) assert 'refresh_pairs' not in config - assert not log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples) + assert not log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog) assert 'timerange' not in config assert 'stoploss_range' not in config @@ -69,21 +66,15 @@ def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> N assert 'pair_whitelist' in config['exchange'] assert 'datadir' in config assert config['runmode'] == RunMode.EDGE - assert log_has( - 'Using data directory: {} ...'.format(config['datadir']), - caplog.record_tuples - ) + assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert 'ticker_interval' in config assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', - caplog.record_tuples) + caplog) assert 'refresh_pairs' in config - assert log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples) + assert log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog) assert 'timerange' in config - assert log_has( - 'Parameter --timerange detected: {} ...'.format(config['timerange']), - caplog.record_tuples - ) + assert log_has('Parameter --timerange detected: {} ...'.format(config['timerange']), caplog) def test_start(mocker, fee, edge_conf, caplog) -> None: @@ -100,10 +91,7 @@ def test_start(mocker, fee, edge_conf, caplog) -> None: ] args = get_args(args) start_edge(args) - assert log_has( - 'Starting freqtrade in Edge mode', - caplog.record_tuples - ) + assert log_has('Starting freqtrade in Edge mode', caplog) assert start_mock.call_count == 1 diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index e3b049c06..cff1315a0 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -79,21 +79,18 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca assert 'exchange' in config assert 'pair_whitelist' in config['exchange'] assert 'datadir' in config - assert log_has( - 'Using data directory: {} ...'.format(config['datadir']), - caplog.record_tuples - ) + assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert 'ticker_interval' in config - assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog.record_tuples) + assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog) assert 'live' not in config - assert not log_has('Parameter -l/--live detected ...', caplog.record_tuples) + assert not log_has('Parameter -l/--live detected ...', caplog) assert 'position_stacking' not in config - assert not log_has('Parameter --enable-position-stacking detected ...', caplog.record_tuples) + assert not log_has('Parameter --enable-position-stacking detected ...', caplog) assert 'refresh_pairs' not in config - assert not log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples) + assert not log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog) assert 'timerange' not in config assert 'runmode' in config @@ -130,41 +127,32 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo assert 'datadir' in config assert config['runmode'] == RunMode.HYPEROPT - assert log_has( - 'Using data directory: {} ...'.format(config['datadir']), - caplog.record_tuples - ) + assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert 'ticker_interval' in config assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', - caplog.record_tuples) + caplog) assert 'position_stacking' in config - assert log_has('Parameter --enable-position-stacking detected ...', caplog.record_tuples) + assert log_has('Parameter --enable-position-stacking detected ...', caplog) assert 'use_max_market_positions' in config - assert log_has('Parameter --disable-max-market-positions detected ...', caplog.record_tuples) - assert log_has('max_open_trades set to unlimited ...', caplog.record_tuples) + assert log_has('Parameter --disable-max-market-positions detected ...', caplog) + assert log_has('max_open_trades set to unlimited ...', caplog) assert 'refresh_pairs' in config - assert log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples) + assert log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog) assert 'timerange' in config - assert log_has( - 'Parameter --timerange detected: {} ...'.format(config['timerange']), - caplog.record_tuples - ) + assert log_has('Parameter --timerange detected: {} ...'.format(config['timerange']), caplog) assert 'epochs' in config assert log_has('Parameter --epochs detected ... Will run Hyperopt with for 1000 epochs ...', - caplog.record_tuples) + caplog) assert 'spaces' in config - assert log_has( - 'Parameter -s/--spaces detected: {}'.format(config['spaces']), - caplog.record_tuples - ) + assert log_has('Parameter -s/--spaces detected: {}'.format(config['spaces']), caplog) assert 'print_all' in config - assert log_has('Parameter --print-all detected ...', caplog.record_tuples) + assert log_has('Parameter --print-all detected ...', caplog) def test_hyperoptresolver(mocker, default_conf, caplog) -> None: @@ -181,9 +169,9 @@ def test_hyperoptresolver(mocker, default_conf, caplog) -> None: assert not hasattr(x, 'populate_buy_trend') assert not hasattr(x, 'populate_sell_trend') assert log_has("Custom Hyperopt does not provide populate_sell_trend. " - "Using populate_sell_trend from DefaultStrategy.", caplog.record_tuples) + "Using populate_sell_trend from DefaultStrategy.", caplog) assert log_has("Custom Hyperopt does not provide populate_buy_trend. " - "Using populate_buy_trend from DefaultStrategy.", caplog.record_tuples) + "Using populate_buy_trend from DefaultStrategy.", caplog) assert hasattr(x, "ticker_interval") @@ -229,10 +217,7 @@ def test_start(mocker, default_conf, caplog) -> None: import pprint pprint.pprint(caplog.record_tuples) - assert log_has( - 'Starting freqtrade in Hyperopt mode', - caplog.record_tuples - ) + assert log_has('Starting freqtrade in Hyperopt mode', caplog) assert start_mock.call_count == 1 @@ -257,7 +242,7 @@ def test_start_no_data(mocker, default_conf, caplog) -> None: import pprint pprint.pprint(caplog.record_tuples) - assert log_has('No data found. Terminating.', caplog.record_tuples) + assert log_has('No data found. Terminating.', caplog) def test_start_failure(mocker, default_conf, caplog) -> None: @@ -275,10 +260,7 @@ def test_start_failure(mocker, default_conf, caplog) -> None: args = get_args(args) with pytest.raises(DependencyException): start_hyperopt(args) - assert log_has( - "Please don't use --strategy for hyperopt.", - caplog.record_tuples - ) + assert log_has("Please don't use --strategy for hyperopt.", caplog) def test_start_filelock(mocker, default_conf, caplog) -> None: @@ -294,10 +276,7 @@ def test_start_filelock(mocker, default_conf, caplog) -> None: ] args = get_args(args) start_hyperopt(args) - assert log_has( - "Another running instance of freqtrade Hyperopt detected.", - caplog.record_tuples - ) + assert log_has("Another running instance of freqtrade Hyperopt detected.", caplog) def test_loss_calculation_prefer_correct_trade_count(default_conf, hyperopt_results) -> None: @@ -401,10 +380,7 @@ def test_save_trials_saves_trials(mocker, hyperopt, caplog) -> None: hyperopt.save_trials() trials_file = os.path.join('freqtrade', 'tests', 'optimize', 'ut_trials.pickle') - assert log_has( - 'Saving 1 evaluations to \'{}\''.format(trials_file), - caplog.record_tuples - ) + assert log_has('Saving 1 evaluations to \'{}\''.format(trials_file), caplog) mock_dump.assert_called_once() @@ -413,10 +389,7 @@ def test_read_trials_returns_trials_file(mocker, hyperopt, caplog) -> None: mock_load = mocker.patch('freqtrade.optimize.hyperopt.load', return_value=trials) hyperopt_trial = hyperopt.read_trials() trials_file = os.path.join('freqtrade', 'tests', 'optimize', 'ut_trials.pickle') - assert log_has( - 'Reading Trials from \'{}\''.format(trials_file), - caplog.record_tuples - ) + assert log_has('Reading Trials from \'{}\''.format(trials_file), caplog) assert hyperopt_trial == trials mock_load.assert_called_once() @@ -626,7 +599,7 @@ def test_clean_hyperopt(mocker, default_conf, caplog): Hyperopt(default_conf) assert unlinkmock.call_count == 2 - assert log_has(f"Removing `{TICKERDATA_PICKLE}`.", caplog.record_tuples) + assert log_has(f"Removing `{TICKERDATA_PICKLE}`.", caplog) def test_continue_hyperopt(mocker, default_conf, caplog): @@ -643,4 +616,4 @@ def test_continue_hyperopt(mocker, default_conf, caplog): Hyperopt(default_conf) assert unlinkmock.call_count == 0 - assert log_has(f"Continuing on previous hyperopt results.", caplog.record_tuples) + assert log_has(f"Continuing on previous hyperopt results.", caplog) diff --git a/freqtrade/tests/rpc/test_fiat_convert.py b/freqtrade/tests/rpc/test_fiat_convert.py index 66870efcc..1689ecac6 100644 --- a/freqtrade/tests/rpc/test_fiat_convert.py +++ b/freqtrade/tests/rpc/test_fiat_convert.py @@ -91,7 +91,7 @@ def test_fiat_convert_unsupported_crypto(mocker, caplog): mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[]) fiat_convert = CryptoToFiatConverter() assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0 - assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog.record_tuples) + assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog) def test_fiat_convert_get_price(mocker): @@ -190,7 +190,7 @@ def test_fiat_invalid_response(mocker, caplog): length_cryptomap = len(fiat_convert._cryptomap) assert length_cryptomap == 0 assert log_has('Could not load FIAT Cryptocurrency map for the following problem: TypeError', - caplog.record_tuples) + caplog) def test_convert_amount(mocker): diff --git a/freqtrade/tests/rpc/test_rpc_apiserver.py b/freqtrade/tests/rpc/test_rpc_apiserver.py index bd420ada6..a218d5622 100644 --- a/freqtrade/tests/rpc/test_rpc_apiserver.py +++ b/freqtrade/tests/rpc/test_rpc_apiserver.py @@ -148,8 +148,8 @@ def test_api_run(default_conf, mocker, caplog): assert isinstance(server_mock.call_args_list[0][0][2], Flask) assert hasattr(apiserver, "srv") - assert log_has("Starting HTTP Server at 127.0.0.1:8080", caplog.record_tuples) - assert log_has("Starting Local Rest Server.", caplog.record_tuples) + assert log_has("Starting HTTP Server at 127.0.0.1:8080", caplog) + assert log_has("Starting Local Rest Server.", caplog) # Test binding to public caplog.clear() @@ -165,22 +165,20 @@ def test_api_run(default_conf, mocker, caplog): assert server_mock.call_args_list[0][0][0] == "0.0.0.0" assert server_mock.call_args_list[0][0][1] == "8089" assert isinstance(server_mock.call_args_list[0][0][2], Flask) - assert log_has("Starting HTTP Server at 0.0.0.0:8089", caplog.record_tuples) - assert log_has("Starting Local Rest Server.", caplog.record_tuples) + assert log_has("Starting HTTP Server at 0.0.0.0:8089", caplog) + assert log_has("Starting Local Rest Server.", caplog) assert log_has("SECURITY WARNING - Local Rest Server listening to external connections", - caplog.record_tuples) + caplog) assert log_has("SECURITY WARNING - This is insecure please set to your loopback," - "e.g 127.0.0.1 in config.json", - caplog.record_tuples) + "e.g 127.0.0.1 in config.json", caplog) assert log_has("SECURITY WARNING - No password for local REST Server defined. " - "Please make sure that this is intentional!", - caplog.record_tuples) + "Please make sure that this is intentional!", caplog) # Test crashing flask caplog.clear() mocker.patch('freqtrade.rpc.api_server.make_server', MagicMock(side_effect=Exception)) apiserver.run() - assert log_has("Api server failed to start.", caplog.record_tuples) + assert log_has("Api server failed to start.", caplog) def test_api_cleanup(default_conf, mocker, caplog): @@ -199,7 +197,7 @@ def test_api_cleanup(default_conf, mocker, caplog): apiserver.cleanup() assert stop_mock.shutdown.call_count == 1 - assert log_has("Stopping API Server", caplog.record_tuples) + assert log_has("Stopping API Server", caplog) def test_api_reloadconf(botclient): diff --git a/freqtrade/tests/rpc/test_rpc_manager.py b/freqtrade/tests/rpc/test_rpc_manager.py index 91fd2297f..468e3e8e5 100644 --- a/freqtrade/tests/rpc/test_rpc_manager.py +++ b/freqtrade/tests/rpc/test_rpc_manager.py @@ -19,7 +19,7 @@ def test_init_telegram_disabled(mocker, default_conf, caplog) -> None: default_conf['telegram']['enabled'] = False rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) - assert not log_has('Enabling rpc.telegram ...', caplog.record_tuples) + assert not log_has('Enabling rpc.telegram ...', caplog) assert rpc_manager.registered_modules == [] @@ -28,7 +28,7 @@ def test_init_telegram_enabled(mocker, default_conf, caplog) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) - assert log_has('Enabling rpc.telegram ...', caplog.record_tuples) + assert log_has('Enabling rpc.telegram ...', caplog) len_modules = len(rpc_manager.registered_modules) assert len_modules == 1 assert 'telegram' in [mod.name for mod in rpc_manager.registered_modules] @@ -43,7 +43,7 @@ def test_cleanup_telegram_disabled(mocker, default_conf, caplog) -> None: rpc_manager = RPCManager(freqtradebot) rpc_manager.cleanup() - assert not log_has('Cleaning up rpc.telegram ...', caplog.record_tuples) + assert not log_has('Cleaning up rpc.telegram ...', caplog) assert telegram_mock.call_count == 0 @@ -59,7 +59,7 @@ def test_cleanup_telegram_enabled(mocker, default_conf, caplog) -> None: assert 'telegram' in [mod.name for mod in rpc_manager.registered_modules] rpc_manager.cleanup() - assert log_has('Cleaning up rpc.telegram ...', caplog.record_tuples) + assert log_has('Cleaning up rpc.telegram ...', caplog) assert 'telegram' not in [mod.name for mod in rpc_manager.registered_modules] assert telegram_mock.call_count == 1 @@ -75,7 +75,7 @@ def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None: 'status': 'test' }) - assert log_has("Sending rpc message: {'type': status, 'status': 'test'}", caplog.record_tuples) + assert log_has("Sending rpc message: {'type': status, 'status': 'test'}", caplog) assert telegram_mock.call_count == 0 @@ -90,7 +90,7 @@ def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None: 'status': 'test' }) - assert log_has("Sending rpc message: {'type': status, 'status': 'test'}", caplog.record_tuples) + assert log_has("Sending rpc message: {'type': status, 'status': 'test'}", caplog) assert telegram_mock.call_count == 1 @@ -100,7 +100,7 @@ def test_init_webhook_disabled(mocker, default_conf, caplog) -> None: default_conf['webhook'] = {'enabled': False} rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) - assert not log_has('Enabling rpc.webhook ...', caplog.record_tuples) + assert not log_has('Enabling rpc.webhook ...', caplog) assert rpc_manager.registered_modules == [] @@ -110,7 +110,7 @@ def test_init_webhook_enabled(mocker, default_conf, caplog) -> None: default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"} rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) - assert log_has('Enabling rpc.webhook ...', caplog.record_tuples) + assert log_has('Enabling rpc.webhook ...', caplog) assert len(rpc_manager.registered_modules) == 1 assert 'webhook' in [mod.name for mod in rpc_manager.registered_modules] @@ -144,7 +144,7 @@ def test_init_apiserver_disabled(mocker, default_conf, caplog) -> None: default_conf['telegram']['enabled'] = False rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) - assert not log_has('Enabling rpc.api_server', caplog.record_tuples) + assert not log_has('Enabling rpc.api_server', caplog) assert rpc_manager.registered_modules == [] assert run_mock.call_count == 0 @@ -160,7 +160,7 @@ def test_init_apiserver_enabled(mocker, default_conf, caplog) -> None: "listen_port": "8080"} rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) - assert log_has('Enabling rpc.api_server', caplog.record_tuples) + assert log_has('Enabling rpc.api_server', caplog) assert len(rpc_manager.registered_modules) == 1 assert 'apiserver' in [mod.name for mod in rpc_manager.registered_modules] assert run_mock.call_count == 1 diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 1bee5bff3..3575520ad 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -76,7 +76,7 @@ def test_init(default_conf, mocker, caplog) -> None: "['performance'], ['daily'], ['count'], ['reload_conf'], " \ "['stopbuy'], ['whitelist'], ['blacklist'], ['edge'], ['help'], ['version']]" - assert log_has(message_str, caplog.record_tuples) + assert log_has(message_str, caplog) def test_cleanup(default_conf, mocker) -> None: @@ -102,18 +102,9 @@ def test_authorized_only(default_conf, mocker, caplog) -> None: dummy = DummyCls(bot) dummy.dummy_handler(bot=MagicMock(), update=update) assert dummy.state['called'] is True - assert log_has( - 'Executing handler: dummy_handler for chat_id: 0', - caplog.record_tuples - ) - assert not log_has( - 'Rejected unauthorized message from: 0', - caplog.record_tuples - ) - assert not log_has( - 'Exception occurred within Telegram module', - caplog.record_tuples - ) + assert log_has('Executing handler: dummy_handler for chat_id: 0', caplog) + assert not log_has('Rejected unauthorized message from: 0', caplog) + assert not log_has('Exception occurred within Telegram module', caplog) def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None: @@ -128,18 +119,9 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None: dummy = DummyCls(bot) dummy.dummy_handler(bot=MagicMock(), update=update) assert dummy.state['called'] is False - assert not log_has( - 'Executing handler: dummy_handler for chat_id: 3735928559', - caplog.record_tuples - ) - assert log_has( - 'Rejected unauthorized message from: 3735928559', - caplog.record_tuples - ) - assert not log_has( - 'Exception occurred within Telegram module', - caplog.record_tuples - ) + assert not log_has('Executing handler: dummy_handler for chat_id: 3735928559', caplog) + assert log_has('Rejected unauthorized message from: 3735928559', caplog) + assert not log_has('Exception occurred within Telegram module', caplog) def test_authorized_only_exception(default_conf, mocker, caplog) -> None: @@ -156,18 +138,9 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None: dummy.dummy_exception(bot=MagicMock(), update=update) assert dummy.state['called'] is False - assert not log_has( - 'Executing handler: dummy_handler for chat_id: 0', - caplog.record_tuples - ) - assert not log_has( - 'Rejected unauthorized message from: 0', - caplog.record_tuples - ) - assert log_has( - 'Exception occurred within Telegram module', - caplog.record_tuples - ) + assert not log_has('Executing handler: dummy_handler for chat_id: 0', caplog) + assert not log_has('Rejected unauthorized message from: 0', caplog) + assert log_has('Exception occurred within Telegram module', caplog) def test_status(default_conf, update, mocker, fee, ticker, markets) -> None: @@ -1440,7 +1413,4 @@ def test__send_msg_network_error(default_conf, mocker, caplog) -> None: # Bot should've tried to send it twice assert len(bot.method_calls) == 2 - assert log_has( - 'Telegram NetworkError: Oh snap! Trying one more time.', - caplog.record_tuples - ) + assert log_has('Telegram NetworkError: Oh snap! Trying one more time.', caplog) diff --git a/freqtrade/tests/rpc/test_rpc_webhook.py b/freqtrade/tests/rpc/test_rpc_webhook.py index a2dcd9b31..cc491d4dd 100644 --- a/freqtrade/tests/rpc/test_rpc_webhook.py +++ b/freqtrade/tests/rpc/test_rpc_webhook.py @@ -115,7 +115,7 @@ def test_exception_send_msg(default_conf, mocker, caplog): webhook = Webhook(get_patched_freqtradebot(mocker, default_conf)) webhook.send_msg({'type': RPCMessageType.BUY_NOTIFICATION}) assert log_has(f"Message type {RPCMessageType.BUY_NOTIFICATION} not configured for webhooks", - caplog.record_tuples) + caplog) default_conf["webhook"] = get_webhook_dict() default_conf["webhook"]["webhookbuy"]["value1"] = "{DEADBEEF:8f}" @@ -135,7 +135,7 @@ def test_exception_send_msg(default_conf, mocker, caplog): } webhook.send_msg(msg) assert log_has("Problem calling Webhook. Please check your webhook configuration. " - "Exception: 'DEADBEEF'", caplog.record_tuples) + "Exception: 'DEADBEEF'", caplog) msg_mock = MagicMock() mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock) @@ -164,4 +164,4 @@ def test__send_msg(default_conf, mocker, caplog): post = MagicMock(side_effect=RequestException) mocker.patch("freqtrade.rpc.webhook.post", post) webhook._send_msg(msg) - assert log_has('Could not call webhook url. Exception: ', caplog.record_tuples) + assert log_has('Could not call webhook url. Exception: ', caplog) diff --git a/freqtrade/tests/strategy/test_interface.py b/freqtrade/tests/strategy/test_interface.py index 9f5ab70e3..0eb7630a1 100644 --- a/freqtrade/tests/strategy/test_interface.py +++ b/freqtrade/tests/strategy/test_interface.py @@ -49,12 +49,12 @@ def test_returns_latest_sell_signal(mocker, default_conf, ticker_history): def test_get_signal_empty(default_conf, mocker, caplog): assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'], DataFrame()) - assert log_has('Empty ticker history for pair foo', caplog.record_tuples) + assert log_has('Empty ticker history for pair foo', caplog) caplog.clear() assert (False, False) == _STRATEGY.get_signal('bar', default_conf['ticker_interval'], []) - assert log_has('Empty ticker history for pair bar', caplog.record_tuples) + assert log_has('Empty ticker history for pair bar', caplog) def test_get_signal_exception_valueerror(default_conf, mocker, caplog, ticker_history): @@ -65,7 +65,7 @@ def test_get_signal_exception_valueerror(default_conf, mocker, caplog, ticker_hi ) assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'], ticker_history) - assert log_has('Unable to analyze ticker for pair foo: xyz', caplog.record_tuples) + assert log_has('Unable to analyze ticker for pair foo: xyz', caplog) def test_get_signal_empty_dataframe(default_conf, mocker, caplog, ticker_history): @@ -76,7 +76,7 @@ def test_get_signal_empty_dataframe(default_conf, mocker, caplog, ticker_history ) assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], ticker_history) - assert log_has('Empty dataframe for pair xyz', caplog.record_tuples) + assert log_has('Empty dataframe for pair xyz', caplog) def test_get_signal_old_dataframe(default_conf, mocker, caplog, ticker_history): @@ -91,10 +91,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ticker_history): ) assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], ticker_history) - assert log_has( - 'Outdated history for pair xyz. Last tick is 16 minutes old', - caplog.record_tuples - ) + assert log_has('Outdated history for pair xyz. Last tick is 16 minutes old', caplog) def test_get_signal_handles_exceptions(mocker, default_conf): @@ -237,9 +234,8 @@ def test_analyze_ticker_default(ticker_history, mocker, caplog) -> None: assert buy_mock.call_count == 1 assert buy_mock.call_count == 1 - assert log_has('TA Analysis Launched', caplog.record_tuples) - assert not log_has('Skipping TA Analysis for already analyzed candle', - caplog.record_tuples) + assert log_has('TA Analysis Launched', caplog) + assert not log_has('Skipping TA Analysis for already analyzed candle', caplog) caplog.clear() strategy.analyze_ticker(ticker_history, {'pair': 'ETH/BTC'}) @@ -247,9 +243,8 @@ def test_analyze_ticker_default(ticker_history, mocker, caplog) -> None: assert ind_mock.call_count == 2 assert buy_mock.call_count == 2 assert buy_mock.call_count == 2 - assert log_has('TA Analysis Launched', caplog.record_tuples) - assert not log_has('Skipping TA Analysis for already analyzed candle', - caplog.record_tuples) + assert log_has('TA Analysis Launched', caplog) + assert not log_has('Skipping TA Analysis for already analyzed candle', caplog) def test__analyze_ticker_internal_skip_analyze(ticker_history, mocker, caplog) -> None: @@ -275,9 +270,8 @@ def test__analyze_ticker_internal_skip_analyze(ticker_history, mocker, caplog) - assert ind_mock.call_count == 1 assert buy_mock.call_count == 1 assert buy_mock.call_count == 1 - assert log_has('TA Analysis Launched', caplog.record_tuples) - assert not log_has('Skipping TA Analysis for already analyzed candle', - caplog.record_tuples) + assert log_has('TA Analysis Launched', caplog) + assert not log_has('Skipping TA Analysis for already analyzed candle', caplog) caplog.clear() ret = strategy._analyze_ticker_internal(ticker_history, {'pair': 'ETH/BTC'}) @@ -290,6 +284,5 @@ def test__analyze_ticker_internal_skip_analyze(ticker_history, mocker, caplog) - assert 'sell' in ret.columns assert ret['buy'].sum() == 0 assert ret['sell'].sum() == 0 - assert not log_has('TA Analysis Launched', caplog.record_tuples) - assert log_has('Skipping TA Analysis for already analyzed candle', - caplog.record_tuples) + assert not log_has('TA Analysis Launched', caplog) + assert log_has('Skipping TA Analysis for already analyzed candle', caplog) diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index df8c0f126..6c2f6f78b 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -15,7 +15,7 @@ from freqtrade.resolvers import StrategyResolver from freqtrade.strategy import import_strategy from freqtrade.strategy.default_strategy import DefaultStrategy from freqtrade.strategy.interface import IStrategy -from freqtrade.tests.conftest import log_has_re +from freqtrade.tests.conftest import log_has, log_has_re def test_import_strategy(caplog): @@ -35,12 +35,8 @@ def test_import_strategy(caplog): assert imported_strategy.__module__ == 'freqtrade.strategy' assert imported_strategy.some_method() == 42 - assert ( - 'freqtrade.strategy', - logging.DEBUG, - 'Imported strategy freqtrade.strategy.default_strategy.DefaultStrategy ' - 'as freqtrade.strategy.DefaultStrategy', - ) in caplog.record_tuples + assert log_has('Imported strategy freqtrade.strategy.default_strategy.DefaultStrategy ' + 'as freqtrade.strategy.DefaultStrategy', caplog) def test_search_strategy(): @@ -76,8 +72,7 @@ def test_load_strategy_base64(result, caplog): assert 'adx' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'}) # Make sure strategy was loaded from base64 (using temp directory)!! assert log_has_re(r"Using resolved strategy TestStrategy from '" - + tempfile.gettempdir() + r"/.*/TestStrategy\.py'\.\.\.", - caplog.record_tuples) + + tempfile.gettempdir() + r"/.*/TestStrategy\.py'\.\.\.", caplog) def test_load_strategy_invalid_directory(result, caplog): @@ -85,7 +80,7 @@ def test_load_strategy_invalid_directory(result, caplog): extra_dir = Path.cwd() / 'some/path' resolver._load_strategy('TestStrategy', config={}, extra_dir=extra_dir) - assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog.record_tuples) + assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog) assert 'adx' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'}) @@ -105,7 +100,7 @@ def test_load_staticmethod_importerror(mocker, caplog): match=r"Impossible to load Strategy 'DefaultStrategy'. " r"This class does not exist or contains Python code errors."): StrategyResolver() - assert log_has_re(r".*Error: can't pickle staticmethod objects", caplog.record_tuples) + assert log_has_re(r".*Error: can't pickle staticmethod objects", caplog) def test_strategy(result): @@ -143,10 +138,7 @@ def test_strategy_override_minimal_roi(caplog): resolver = StrategyResolver(config) assert resolver.strategy.minimal_roi[0] == 0.5 - assert ('freqtrade.resolvers.strategy_resolver', - logging.INFO, - "Override strategy 'minimal_roi' with value in config file: {'0': 0.5}." - ) in caplog.record_tuples + assert log_has("Override strategy 'minimal_roi' with value in config file: {'0': 0.5}.", caplog) def test_strategy_override_stoploss(caplog): @@ -158,10 +150,7 @@ def test_strategy_override_stoploss(caplog): resolver = StrategyResolver(config) assert resolver.strategy.stoploss == -0.5 - assert ('freqtrade.resolvers.strategy_resolver', - logging.INFO, - "Override strategy 'stoploss' with value in config file: -0.5." - ) in caplog.record_tuples + assert log_has("Override strategy 'stoploss' with value in config file: -0.5.", caplog) def test_strategy_override_trailing_stop(caplog): @@ -174,10 +163,7 @@ def test_strategy_override_trailing_stop(caplog): assert resolver.strategy.trailing_stop assert isinstance(resolver.strategy.trailing_stop, bool) - assert ('freqtrade.resolvers.strategy_resolver', - logging.INFO, - "Override strategy 'trailing_stop' with value in config file: True." - ) in caplog.record_tuples + assert log_has("Override strategy 'trailing_stop' with value in config file: True.", caplog) def test_strategy_override_trailing_stop_positive(caplog): @@ -191,16 +177,12 @@ def test_strategy_override_trailing_stop_positive(caplog): resolver = StrategyResolver(config) assert resolver.strategy.trailing_stop_positive == -0.1 - assert ('freqtrade.resolvers.strategy_resolver', - logging.INFO, - "Override strategy 'trailing_stop_positive' with value in config file: -0.1." - ) in caplog.record_tuples + assert log_has("Override strategy 'trailing_stop_positive' with value in config file: -0.1.", + caplog) assert resolver.strategy.trailing_stop_positive_offset == -0.2 - assert ('freqtrade.resolvers.strategy_resolver', - logging.INFO, - "Override strategy 'trailing_stop_positive' with value in config file: -0.1." - ) in caplog.record_tuples + assert log_has("Override strategy 'trailing_stop_positive' with value in config file: -0.1.", + caplog) def test_strategy_override_ticker_interval(caplog): @@ -215,10 +197,8 @@ def test_strategy_override_ticker_interval(caplog): assert resolver.strategy.ticker_interval == 60 assert resolver.strategy.stake_currency == 'ETH' - assert ('freqtrade.resolvers.strategy_resolver', - logging.INFO, - "Override strategy 'ticker_interval' with value in config file: 60." - ) in caplog.record_tuples + assert log_has("Override strategy 'ticker_interval' with value in config file: 60.", + caplog) def test_strategy_override_process_only_new_candles(caplog): @@ -231,10 +211,8 @@ def test_strategy_override_process_only_new_candles(caplog): resolver = StrategyResolver(config) assert resolver.strategy.process_only_new_candles - assert ('freqtrade.resolvers.strategy_resolver', - logging.INFO, - "Override strategy 'process_only_new_candles' with value in config file: True." - ) in caplog.record_tuples + assert log_has("Override strategy 'process_only_new_candles' with value in config file: True.", + caplog) def test_strategy_override_order_types(caplog): @@ -257,12 +235,9 @@ def test_strategy_override_order_types(caplog): for method in ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']: assert resolver.strategy.order_types[method] == order_types[method] - assert ('freqtrade.resolvers.strategy_resolver', - logging.INFO, - "Override strategy 'order_types' with value in config file:" - " {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit'," - " 'stoploss_on_exchange': True}." - ) in caplog.record_tuples + assert log_has("Override strategy 'order_types' with value in config file:" + " {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit'," + " 'stoploss_on_exchange': True}.", caplog) config = { 'strategy': 'DefaultStrategy', @@ -293,11 +268,8 @@ def test_strategy_override_order_tif(caplog): for method in ['buy', 'sell']: assert resolver.strategy.order_time_in_force[method] == order_time_in_force[method] - assert ('freqtrade.resolvers.strategy_resolver', - logging.INFO, - "Override strategy 'order_time_in_force' with value in config file:" - " {'buy': 'fok', 'sell': 'gtc'}." - ) in caplog.record_tuples + assert log_has("Override strategy 'order_time_in_force' with value in config file:" + " {'buy': 'fok', 'sell': 'gtc'}.", caplog) config = { 'strategy': 'DefaultStrategy', @@ -332,10 +304,7 @@ def test_strategy_override_use_sell_signal(caplog): assert resolver.strategy.use_sell_signal assert isinstance(resolver.strategy.use_sell_signal, bool) - assert ('freqtrade.resolvers.strategy_resolver', - logging.INFO, - "Override strategy 'use_sell_signal' with value in config file: True." - ) in caplog.record_tuples + assert log_has("Override strategy 'use_sell_signal' with value in config file: True.", caplog) def test_strategy_override_use_sell_profit_only(caplog): @@ -360,10 +329,7 @@ def test_strategy_override_use_sell_profit_only(caplog): assert resolver.strategy.sell_profit_only assert isinstance(resolver.strategy.sell_profit_only, bool) - assert ('freqtrade.resolvers.strategy_resolver', - logging.INFO, - "Override strategy 'sell_profit_only' with value in config file: True." - ) in caplog.record_tuples + assert log_has("Override strategy 'sell_profit_only' with value in config file: True.", caplog) @pytest.mark.filterwarnings("ignore:deprecated") diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index 0a8381089..9ed3f4ebb 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -72,7 +72,7 @@ def test__args_to_config(caplog): # No warnings ... configuration._args_to_config(config, argname="strategy_path", logstring="DeadBeef") assert len(w) == 0 - assert log_has("DeadBeef", caplog.record_tuples) + assert log_has("DeadBeef", caplog) assert config['strategy_path'] == "TestTest" configuration = Configuration(args) @@ -84,7 +84,7 @@ def test__args_to_config(caplog): assert len(w) == 1 assert issubclass(w[-1].category, DeprecationWarning) assert "DEPRECATED: Going away soon!" in str(w[-1].message) - assert log_has("DeadBeef", caplog.record_tuples) + assert log_has("DeadBeef", caplog) assert config['strategy_path'] == "TestTest" @@ -98,7 +98,7 @@ def test_load_config_max_open_trades_zero(default_conf, mocker, caplog) -> None: assert validated_conf['max_open_trades'] == 0 assert 'internals' in validated_conf - assert log_has('Validating configuration ...', caplog.record_tuples) + assert log_has('Validating configuration ...', caplog) def test_load_config_combine_dicts(default_conf, mocker, caplog) -> None: @@ -130,7 +130,36 @@ def test_load_config_combine_dicts(default_conf, mocker, caplog) -> None: assert validated_conf['exchange']['pair_whitelist'] == conf2['exchange']['pair_whitelist'] assert 'internals' in validated_conf - assert log_has('Validating configuration ...', caplog.record_tuples) + assert log_has('Validating configuration ...', caplog) + + +def test_from_config(default_conf, mocker, caplog) -> None: + conf1 = deepcopy(default_conf) + conf2 = deepcopy(default_conf) + del conf1['exchange']['key'] + del conf1['exchange']['secret'] + del conf2['exchange']['name'] + conf2['exchange']['pair_whitelist'] += ['NANO/BTC'] + conf2['fiat_display_currency'] = "EUR" + config_files = [conf1, conf2] + + configsmock = MagicMock(side_effect=config_files) + mocker.patch( + 'freqtrade.configuration.configuration.load_config_file', + configsmock + ) + + validated_conf = Configuration.from_files(['test_conf.json', 'test2_conf.json']) + + exchange_conf = default_conf['exchange'] + assert validated_conf['exchange']['name'] == exchange_conf['name'] + assert validated_conf['exchange']['key'] == exchange_conf['key'] + assert validated_conf['exchange']['secret'] == exchange_conf['secret'] + assert validated_conf['exchange']['pair_whitelist'] != conf1['exchange']['pair_whitelist'] + assert validated_conf['exchange']['pair_whitelist'] == conf2['exchange']['pair_whitelist'] + assert validated_conf['fiat_display_currency'] == "EUR" + assert 'internals' in validated_conf + assert log_has('Validating configuration ...', caplog) def test_load_config_max_open_trades_minus_one(default_conf, mocker, caplog) -> None: @@ -143,7 +172,7 @@ def test_load_config_max_open_trades_minus_one(default_conf, mocker, caplog) -> assert validated_conf['max_open_trades'] > 999999999 assert validated_conf['max_open_trades'] == float('inf') - assert log_has('Validating configuration ...', caplog.record_tuples) + assert log_has('Validating configuration ...', caplog) assert "runmode" in validated_conf assert validated_conf['runmode'] == RunMode.DRY_RUN @@ -280,8 +309,8 @@ def test_show_info(default_conf, mocker, caplog) -> None: configuration = Configuration(args) configuration.get_config() - assert log_has('Using DB: "sqlite:///tmp/testdb"', caplog.record_tuples) - assert log_has('Dry run is enabled', caplog.record_tuples) + assert log_has('Using DB: "sqlite:///tmp/testdb"', caplog) + assert log_has('Dry run is enabled', caplog) def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None: @@ -303,21 +332,18 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> assert 'exchange' in config assert 'pair_whitelist' in config['exchange'] assert 'datadir' in config - assert log_has( - 'Using data directory: {} ...'.format(config['datadir']), - caplog.record_tuples - ) + assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert 'ticker_interval' in config - assert not log_has('Parameter -i/--ticker-interval detected ...', caplog.record_tuples) + assert not log_has('Parameter -i/--ticker-interval detected ...', caplog) assert 'live' not in config - assert not log_has('Parameter -l/--live detected ...', caplog.record_tuples) + assert not log_has('Parameter -l/--live detected ...', caplog) assert 'position_stacking' not in config - assert not log_has('Parameter --enable-position-stacking detected ...', caplog.record_tuples) + assert not log_has('Parameter --enable-position-stacking detected ...', caplog) assert 'refresh_pairs' not in config - assert not log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples) + assert not log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog) assert 'timerange' not in config assert 'export' not in config @@ -355,37 +381,28 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non assert 'exchange' in config assert 'pair_whitelist' in config['exchange'] assert 'datadir' in config - assert log_has( - 'Using data directory: {} ...'.format(config['datadir']), - caplog.record_tuples - ) + assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert 'ticker_interval' in config assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', - caplog.record_tuples) + caplog) assert 'live' in config - assert log_has('Parameter -l/--live detected ...', caplog.record_tuples) + assert log_has('Parameter -l/--live detected ...', caplog) assert 'position_stacking'in config - assert log_has('Parameter --enable-position-stacking detected ...', caplog.record_tuples) + assert log_has('Parameter --enable-position-stacking detected ...', caplog) assert 'use_max_market_positions' in config - assert log_has('Parameter --disable-max-market-positions detected ...', caplog.record_tuples) - assert log_has('max_open_trades set to unlimited ...', caplog.record_tuples) + assert log_has('Parameter --disable-max-market-positions detected ...', caplog) + assert log_has('max_open_trades set to unlimited ...', caplog) assert 'refresh_pairs'in config - assert log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples) + assert log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog) assert 'timerange' in config - assert log_has( - 'Parameter --timerange detected: {} ...'.format(config['timerange']), - caplog.record_tuples - ) + assert log_has('Parameter --timerange detected: {} ...'.format(config['timerange']), caplog) assert 'export' in config - assert log_has( - 'Parameter --export detected: {} ...'.format(config['export']), - caplog.record_tuples - ) + assert log_has('Parameter --export detected: {} ...'.format(config['export']), caplog) def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> None: @@ -415,16 +432,13 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non assert 'exchange' in config assert 'pair_whitelist' in config['exchange'] assert 'datadir' in config - assert log_has( - 'Using data directory: {} ...'.format(config['datadir']), - caplog.record_tuples - ) + assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert 'ticker_interval' in config assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', - caplog.record_tuples) + caplog) assert 'strategy_list' in config - assert log_has('Using strategy list of 2 Strategies', caplog.record_tuples) + assert log_has('Using strategy list of 2 Strategies', caplog) assert 'position_stacking' not in config @@ -433,10 +447,7 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non assert 'timerange' not in config assert 'export' in config - assert log_has( - 'Parameter --export detected: {} ...'.format(config['export']), - caplog.record_tuples - ) + assert log_has('Parameter --export detected: {} ...'.format(config['export']), caplog) def test_hyperopt_with_arguments(mocker, default_conf, caplog) -> None: @@ -455,11 +466,11 @@ def test_hyperopt_with_arguments(mocker, default_conf, caplog) -> None: assert 'epochs' in config assert int(config['epochs']) == 10 assert log_has('Parameter --epochs detected ... Will run Hyperopt with for 10 epochs ...', - caplog.record_tuples) + caplog) assert 'spaces' in config assert config['spaces'] == ['all'] - assert log_has('Parameter -s/--spaces detected: [\'all\']', caplog.record_tuples) + assert log_has('Parameter -s/--spaces detected: [\'all\']', caplog) assert "runmode" in config assert config['runmode'] == RunMode.HYPEROPT @@ -469,38 +480,35 @@ def test_check_exchange(default_conf, caplog) -> None: default_conf.get('exchange').update({'name': 'BITTREX'}) assert check_exchange(default_conf) assert log_has_re(r"Exchange .* is officially supported by the Freqtrade development team\.", - caplog.record_tuples) + caplog) caplog.clear() # Test an officially supported by Freqtrade team exchange default_conf.get('exchange').update({'name': 'binance'}) assert check_exchange(default_conf) assert log_has_re(r"Exchange .* is officially supported by the Freqtrade development team\.", - caplog.record_tuples) + caplog) caplog.clear() # Test an available exchange, supported by ccxt default_conf.get('exchange').update({'name': 'kraken'}) assert check_exchange(default_conf) assert log_has_re(r"Exchange .* is supported by ccxt and .* not officially supported " - r"by the Freqtrade development team\. .*", - caplog.record_tuples) + r"by the Freqtrade development team\. .*", caplog) caplog.clear() # 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.record_tuples) + r"Use it only for development and testing purposes\.", caplog) caplog.clear() # Test a 'bad' exchange with check_for_bad=False default_conf.get('exchange').update({'name': 'bitmex'}) assert check_exchange(default_conf, False) assert log_has_re(r"Exchange .* is supported by ccxt and .* not officially supported " - r"by the Freqtrade development team\. .*", - caplog.record_tuples) + r"by the Freqtrade development team\. .*", caplog) caplog.clear() # Test an invalid exchange @@ -526,7 +534,7 @@ def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None: validated_conf = configuration.load_config() assert validated_conf.get('verbosity') == 3 - assert log_has('Verbosity set to 3', caplog.record_tuples) + assert log_has('Verbosity set to 3', caplog) def test_set_loggers() -> None: @@ -592,7 +600,7 @@ def test_load_config_warn_forcebuy(default_conf, mocker, caplog) -> None: validated_conf = configuration.load_config() assert validated_conf.get('forcebuy_enable') - assert log_has('`forcebuy` RPC message enabled.', caplog.record_tuples) + assert log_has('`forcebuy` RPC message enabled.', caplog) def test_validate_default_conf(default_conf) -> None: @@ -605,7 +613,7 @@ def test_create_datadir(mocker, default_conf, caplog) -> None: create_datadir(default_conf, '/foo/bar') assert md.call_args[1]['parents'] is True - assert log_has('Created data directory: /foo/bar', caplog.record_tuples) + assert log_has('Created data directory: /foo/bar', caplog) def test_validate_tsl(default_conf): diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 1a4c5159c..4e649250a 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -16,7 +16,7 @@ from freqtrade.data.dataprovider import DataProvider from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade from freqtrade.rpc import RPCMessageType -from freqtrade.state import State +from freqtrade.state import State, RunMode from freqtrade.strategy.interface import SellCheckTuple, SellType from freqtrade.tests.conftest import (get_patched_freqtradebot, get_patched_worker, log_has, log_has_re, @@ -63,7 +63,7 @@ def test_cleanup(mocker, default_conf, caplog) -> None: mocker.patch('freqtrade.persistence.cleanup', mock_cleanup) freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade.cleanup() - assert log_has('Cleaning up modules ...', caplog.record_tuples) + assert log_has('Cleaning up modules ...', caplog) assert mock_cleanup.call_count == 1 @@ -76,7 +76,7 @@ def test_worker_running(mocker, default_conf, caplog) -> None: state = worker._worker(old_state=None) assert state is State.RUNNING - assert log_has('Changing state to: RUNNING', caplog.record_tuples) + assert log_has('Changing state to: RUNNING', caplog) assert mock_throttle.call_count == 1 # Check strategy is loaded, and received a dataprovider object assert worker.freqtrade.strategy @@ -93,7 +93,7 @@ def test_worker_stopped(mocker, default_conf, caplog) -> None: worker.state = State.STOPPED state = worker._worker(old_state=State.RUNNING) assert state is State.STOPPED - assert log_has('Changing state to: STOPPED', caplog.record_tuples) + assert log_has('Changing state to: STOPPED', caplog) assert mock_throttle.call_count == 0 assert mock_sleep.call_count == 1 @@ -111,7 +111,7 @@ def test_throttle(mocker, default_conf, caplog) -> None: assert result == 42 assert end - start > 0.1 - assert log_has('Throttling throttled_func for 0.10 seconds', caplog.record_tuples) + assert log_has('Throttling throttled_func for 0.10 seconds', caplog) result = worker._throttle(throttled_func, min_secs=-1) assert result == 42 @@ -130,7 +130,77 @@ def test_throttle_with_assets(mocker, default_conf) -> None: assert result == -1 -def test_get_trade_stake_amount(default_conf, ticker, limit_buy_order, fee, mocker) -> None: +def test_order_dict_dry_run(default_conf, mocker, caplog) -> None: + patch_RPCManager(mocker) + patch_exchange(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_balance=MagicMock(return_value=default_conf['stake_amount'] * 2) + ) + conf = default_conf.copy() + conf['runmode'] = RunMode.DRY_RUN + conf['order_types'] = { + 'buy': 'market', + 'sell': 'limit', + 'stoploss': 'limit', + 'stoploss_on_exchange': True, + } + + freqtrade = FreqtradeBot(conf) + assert log_has("Disabling stoploss_on_exchange during dry-run.", caplog) + assert not freqtrade.strategy.order_types['stoploss_on_exchange'] + + caplog.clear() + # is left untouched + conf = default_conf.copy() + conf['runmode'] = RunMode.DRY_RUN + conf['order_types'] = { + 'buy': 'market', + 'sell': 'limit', + 'stoploss': 'limit', + 'stoploss_on_exchange': False, + } + freqtrade = FreqtradeBot(conf) + assert not freqtrade.strategy.order_types['stoploss_on_exchange'] + assert not log_has_re(".*stoploss_on_exchange .* dry-run", caplog) + + +def test_order_dict_live(default_conf, mocker, caplog) -> None: + patch_RPCManager(mocker) + patch_exchange(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_balance=MagicMock(return_value=default_conf['stake_amount'] * 2) + ) + conf = default_conf.copy() + conf['runmode'] = RunMode.LIVE + conf['order_types'] = { + 'buy': 'market', + 'sell': 'limit', + 'stoploss': 'limit', + 'stoploss_on_exchange': True, + } + + freqtrade = FreqtradeBot(conf) + assert not log_has_re(".*stoploss_on_exchange .* dry-run", caplog) + assert freqtrade.strategy.order_types['stoploss_on_exchange'] + + caplog.clear() + # is left untouched + conf = default_conf.copy() + conf['runmode'] = RunMode.LIVE + conf['order_types'] = { + 'buy': 'market', + 'sell': 'limit', + 'stoploss': 'limit', + 'stoploss_on_exchange': False, + } + freqtrade = FreqtradeBot(conf) + assert not freqtrade.strategy.order_types['stoploss_on_exchange'] + assert not log_has_re(".*stoploss_on_exchange .* dry-run", caplog) + + +def test_get_trade_stake_amount(default_conf, ticker, mocker) -> None: patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -262,7 +332,7 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, markets, caplog, mocker, # stoploss shoud be hit assert freqtrade.handle_trade(trade) is True - assert log_has('executed sell, reason: SellType.STOP_LOSS', caplog.record_tuples) + assert log_has('executed sell, reason: SellType.STOP_LOSS', caplog) assert trade.sell_reason == SellType.STOP_LOSS.value @@ -582,8 +652,7 @@ def test_create_trade_no_pairs_let(default_conf, ticker, limit_buy_order, fee, assert freqtrade.create_trade() assert not freqtrade.create_trade() - assert log_has("No currency pair in whitelist, but checking to sell open trades.", - caplog.record_tuples) + 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, @@ -602,7 +671,7 @@ def test_create_trade_no_pairs_in_whitelist(default_conf, ticker, limit_buy_orde patch_get_signal(freqtrade) assert not freqtrade.create_trade() - assert log_has("Whitelist is empty.", caplog.record_tuples) + assert log_has("Whitelist is empty.", caplog) def test_create_trade_no_signal(default_conf, fee, mocker) -> None: @@ -657,8 +726,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order, assert trade.amount == 90.99181073703367 assert log_has( - 'Buy signal found: about create a new trade with stake_amount: 0.001 ...', - caplog.record_tuples + 'Buy signal found: about create a new trade with stake_amount: 0.001 ...', caplog ) @@ -1025,7 +1093,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog, }) mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hit) assert freqtrade.handle_stoploss_on_exchange(trade) is True - assert log_has('STOP_LOSS_LIMIT is hit for {}.'.format(trade), caplog.record_tuples) + assert log_has('STOP_LOSS_LIMIT is hit for {}.'.format(trade), caplog) assert trade.stoploss_order_id is None assert trade.is_open is False @@ -1034,7 +1102,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog, side_effect=DependencyException() ) freqtrade.handle_stoploss_on_exchange(trade) - assert log_has('Unable to place a stoploss order on exchange: ', caplog.record_tuples) + assert log_has('Unable to place a stoploss order on exchange: ', caplog) # Fifth case: get_order returns InvalidOrder # It should try to add stoploss order @@ -1196,8 +1264,7 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c mocker.patch('freqtrade.exchange.Exchange.cancel_order', side_effect=InvalidOrderException()) mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hanging) freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) - assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/BTC.*", - caplog.record_tuples) + assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/BTC.*", caplog) # Still try to create order assert stoploss_limit.call_count == 1 @@ -1208,8 +1275,7 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c mocker.patch("freqtrade.exchange.Exchange.stoploss_limit", side_effect=DependencyException()) freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) assert cancel_mock.call_count == 1 - assert log_has_re(r"Could create trailing stoploss order for pair ETH/BTC\..*", - caplog.record_tuples) + assert log_has_re(r"Could create trailing stoploss order for pair ETH/BTC\..*", caplog) def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, @@ -1340,7 +1406,7 @@ def test_process_maybe_execute_buy_exception(mocker, default_conf, caplog) -> No MagicMock(side_effect=DependencyException) ) freqtrade.process_maybe_execute_buy() - log_has('Unable to create trade:', caplog.record_tuples) + assert log_has('Unable to create trade: ', caplog) def test_process_maybe_execute_sell(mocker, default_conf, limit_buy_order, caplog) -> None: @@ -1358,8 +1424,7 @@ def test_process_maybe_execute_sell(mocker, default_conf, limit_buy_order, caplo assert not freqtrade.process_maybe_execute_sell(trade) # Test amount not modified by fee-logic assert not log_has( - 'Applying fee to amount for Trade {} from 90.99181073 to 90.81'.format(trade), - caplog.record_tuples + 'Applying fee to amount for Trade {} from 90.99181073 to 90.81'.format(trade), caplog ) mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81) @@ -1382,7 +1447,7 @@ def test_process_maybe_execute_sell_exception(mocker, default_conf, side_effect=DependencyException() ) freqtrade.process_maybe_execute_sell(trade) - assert log_has('Unable to sell trade: ', caplog.record_tuples) + assert log_has('Unable to sell trade: ', caplog) def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> None: @@ -1401,7 +1466,7 @@ def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> No trade.open_fee = 0.001 freqtrade.update_trade_state(trade) # Test amount not modified by fee-logic - assert not log_has_re(r'Applying fee to .*', caplog.record_tuples) + assert not log_has_re(r'Applying fee to .*', caplog) assert trade.open_order_id is None assert trade.amount == limit_buy_order['amount'] @@ -1418,7 +1483,7 @@ def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> No # Assert we call handle_trade() if trade is feasible for execution freqtrade.update_trade_state(trade) - assert log_has_re('Found open order for.*', caplog.record_tuples) + assert log_has_re('Found open order for.*', caplog) def test_update_trade_state_withorderdict(default_conf, trades_for_order, limit_buy_order, mocker): @@ -1457,7 +1522,7 @@ def test_update_trade_state_exception(mocker, default_conf, side_effect=OperationalException() ) freqtrade.update_trade_state(trade) - assert log_has('Could not update trade amount: ', caplog.record_tuples) + assert log_has('Could not update trade amount: ', caplog) def test_update_trade_state_orderexception(mocker, default_conf, caplog) -> None: @@ -1473,7 +1538,7 @@ def test_update_trade_state_orderexception(mocker, default_conf, caplog) -> None grm_mock = mocker.patch("freqtrade.freqtradebot.FreqtradeBot.get_real_amount", MagicMock()) freqtrade.update_trade_state(trade) assert grm_mock.call_count == 0 - assert log_has(f'Unable to fetch order {trade.open_order_id}: ', caplog.record_tuples) + assert log_has(f'Unable to fetch order {trade.open_order_id}: ', caplog) def test_update_trade_state_sell(default_conf, trades_for_order, limit_sell_order, mocker): @@ -1632,7 +1697,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order, # if ROI is reached we must sell patch_get_signal(freqtrade, value=(False, True)) assert freqtrade.handle_trade(trade) - assert log_has('Required profit reached. Selling..', caplog.record_tuples) + assert log_has('Required profit reached. Selling..', caplog) def test_handle_trade_experimental( @@ -1662,7 +1727,7 @@ def test_handle_trade_experimental( patch_get_signal(freqtrade, value=(False, True)) assert freqtrade.handle_trade(trade) - assert log_has('Sell signal received. Selling..', caplog.record_tuples) + assert log_has('Sell signal received. Selling..', caplog) def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, @@ -1768,7 +1833,7 @@ def test_check_handle_cancelled_buy(default_conf, ticker, limit_buy_order_old, trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all() nb_trades = len(trades) assert nb_trades == 0 - assert log_has_re("Buy order canceled on Exchange for Trade.*", caplog.record_tuples) + assert log_has_re("Buy order canceled on Exchange for Trade.*", caplog) def test_check_handle_timedout_buy_exception(default_conf, ticker, limit_buy_order_old, @@ -1881,7 +1946,7 @@ def test_check_handle_cancelled_sell(default_conf, ticker, limit_sell_order_old, assert cancel_order_mock.call_count == 0 assert rpc_mock.call_count == 1 assert trade_sell.is_open is True - assert log_has_re("Sell order canceled on exchange for Trade.*", caplog.record_tuples) + assert log_has_re("Sell order canceled on exchange for Trade.*", caplog) def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old_partial, @@ -1959,7 +2024,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) - freqtrade.check_handle_timedout() assert log_has_re(r'Cannot query order for Trade\(id=1, pair=ETH/BTC, amount=90.99181073, ' r'open_rate=0.00001099, open_since=10 hours ago\) due to Traceback \(most ' - r'recent call last\):\n.*', caplog.record_tuples) + r'recent call last\):\n.*', caplog) def test_handle_timedout_limit_buy(mocker, default_conf) -> None: @@ -2183,7 +2248,7 @@ def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, freqtrade.execute_sell(trade=trade, limit=1234, sell_reason=SellType.STOP_LOSS) assert sellmock.call_count == 1 - assert log_has('Could not cancel stoploss order abcd', caplog.record_tuples) + assert log_has('Could not cancel stoploss order abcd', caplog) def test_execute_sell_with_stoploss_on_exchange(default_conf, @@ -2609,7 +2674,7 @@ def test_trailing_stop_loss(default_conf, limit_buy_order, fee, markets, caplog, assert freqtrade.handle_trade(trade) is True assert log_has( f'HIT STOP: current price at 0.000012, stop loss is 0.000015, ' - f'initial stop loss was at 0.000010, trade opened at 0.000011', caplog.record_tuples) + f'initial stop loss was at 0.000010, trade opened at 0.000011', caplog) assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value @@ -2651,9 +2716,8 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, markets })) # stop-loss not reached, adjusted stoploss assert freqtrade.handle_trade(trade) is False - assert log_has(f'using positive stop loss: 0.01 offset: 0 profit: 0.2666%', - caplog.record_tuples) - assert log_has(f'adjusted stop loss', caplog.record_tuples) + assert log_has(f'using positive stop loss: 0.01 offset: 0 profit: 0.2666%', caplog) + assert log_has(f'adjusted stop loss', caplog) assert trade.stop_loss == 0.0000138501 mocker.patch('freqtrade.exchange.Exchange.get_ticker', @@ -2667,7 +2731,7 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, markets assert log_has( f'HIT STOP: current price at {buy_price + 0.000002:.6f}, ' f'stop loss is {trade.stop_loss:.6f}, ' - f'initial stop loss was at 0.000010, trade opened at 0.000011', caplog.record_tuples) + f'initial stop loss was at 0.000010, trade opened at 0.000011', caplog) def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, @@ -2710,9 +2774,8 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, })) # stop-loss not reached, adjusted stoploss assert freqtrade.handle_trade(trade) is False - assert log_has(f'using positive stop loss: 0.01 offset: 0.011 profit: 0.2666%', - caplog.record_tuples) - assert log_has(f'adjusted stop loss', caplog.record_tuples) + assert log_has(f'using positive stop loss: 0.01 offset: 0.011 profit: 0.2666%', caplog) + assert log_has(f'adjusted stop loss', caplog) assert trade.stop_loss == 0.0000138501 mocker.patch('freqtrade.exchange.Exchange.get_ticker', @@ -2726,7 +2789,7 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, assert log_has( f'HIT STOP: current price at {buy_price + 0.000002:.6f}, ' f'stop loss is {trade.stop_loss:.6f}, ' - f'initial stop loss was at 0.000010, trade opened at 0.000011', caplog.record_tuples) + f'initial stop loss was at 0.000010, trade opened at 0.000011', caplog) assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value @@ -2777,7 +2840,7 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, fee, # stop-loss should not be adjusted as offset is not reached yet assert freqtrade.handle_trade(trade) is False - assert not log_has(f'adjusted stop loss', caplog.record_tuples) + assert not log_has(f'adjusted stop loss', caplog) assert trade.stop_loss == 0.0000098910 # price rises above the offset (rises 12% when the offset is 5.5%) @@ -2789,9 +2852,8 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, fee, })) assert freqtrade.handle_trade(trade) is False - assert log_has(f'using positive stop loss: 0.05 offset: 0.055 profit: 0.1218%', - caplog.record_tuples) - assert log_has(f'adjusted stop loss', caplog.record_tuples) + assert log_has(f'using positive stop loss: 0.05 offset: 0.055 profit: 0.1218%', caplog) + assert log_has(f'adjusted stop loss', caplog) assert trade.stop_loss == 0.0000117705 @@ -2850,7 +2912,7 @@ def test_get_real_amount_quote(default_conf, trades_for_order, buy_order_fee, ca assert freqtrade.get_real_amount(trade, buy_order_fee) == amount - (amount * 0.001) assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' 'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992) from Trades', - caplog.record_tuples) + caplog) def test_get_real_amount_no_trade(default_conf, buy_order_fee, caplog, mocker): @@ -2873,7 +2935,7 @@ def test_get_real_amount_no_trade(default_conf, buy_order_fee, caplog, mocker): assert freqtrade.get_real_amount(trade, buy_order_fee) == amount assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' 'open_rate=0.24544100, open_since=closed) failed: myTrade-Dict empty found', - caplog.record_tuples) + caplog) def test_get_real_amount_stake(default_conf, trades_for_order, buy_order_fee, mocker): @@ -2962,7 +3024,7 @@ def test_get_real_amount_multi(default_conf, trades_for_order2, buy_order_fee, c assert freqtrade.get_real_amount(trade, buy_order_fee) == amount - (amount * 0.001) assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' 'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992) from Trades', - caplog.record_tuples) + caplog) def test_get_real_amount_fromorder(default_conf, trades_for_order, buy_order_fee, caplog, mocker): @@ -2988,7 +3050,7 @@ def test_get_real_amount_fromorder(default_conf, trades_for_order, buy_order_fee assert freqtrade.get_real_amount(trade, limit_buy_order) == amount - 0.004 assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' 'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.996) from Order', - caplog.record_tuples) + caplog) def test_get_real_amount_invalid_order(default_conf, trades_for_order, buy_order_fee, mocker): diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index bcaad4d7d..d8ec532b0 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -60,8 +60,8 @@ def test_main_fatal_exception(mocker, default_conf, caplog) -> None: # Test Main + the KeyboardInterrupt exception with pytest.raises(SystemExit): main(args) - assert log_has('Using config: config.json.example ...', caplog.record_tuples) - assert log_has('Fatal exception!', caplog.record_tuples) + assert log_has('Using config: config.json.example ...', caplog) + assert log_has('Fatal exception!', caplog) def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None: @@ -77,8 +77,8 @@ def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None: # Test Main + the KeyboardInterrupt exception with pytest.raises(SystemExit): main(args) - assert log_has('Using config: config.json.example ...', caplog.record_tuples) - assert log_has('SIGINT received, aborting ...', caplog.record_tuples) + assert log_has('Using config: config.json.example ...', caplog) + assert log_has('SIGINT received, aborting ...', caplog) def test_main_operational_exception(mocker, default_conf, caplog) -> None: @@ -97,8 +97,8 @@ def test_main_operational_exception(mocker, default_conf, caplog) -> None: # Test Main + the KeyboardInterrupt exception with pytest.raises(SystemExit): main(args) - assert log_has('Using config: config.json.example ...', caplog.record_tuples) - assert log_has('Oh snap!', caplog.record_tuples) + assert log_has('Using config: config.json.example ...', caplog) + assert log_has('Oh snap!', caplog) def test_main_reload_conf(mocker, default_conf, caplog) -> None: @@ -121,7 +121,7 @@ def test_main_reload_conf(mocker, default_conf, caplog) -> None: with pytest.raises(SystemExit): main(['-c', 'config.json.example']) - assert log_has('Using config: config.json.example ...', caplog.record_tuples) + assert log_has('Using config: config.json.example ...', caplog) assert worker_mock.call_count == 4 assert reconfigure_mock.call_count == 1 assert isinstance(worker.freqtrade, FreqtradeBot) diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index 32425ef7b..c3ab7c128 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -151,7 +151,7 @@ def test_update_with_bittrex(limit_buy_order, limit_sell_order, fee, caplog): assert trade.close_date is None assert log_has("LIMIT_BUY has been fulfilled for Trade(id=2, " "pair=ETH/BTC, amount=90.99181073, open_rate=0.00001099, open_since=closed).", - caplog.record_tuples) + caplog) caplog.clear() trade.open_order_id = 'something' @@ -162,7 +162,7 @@ def test_update_with_bittrex(limit_buy_order, limit_sell_order, fee, caplog): assert trade.close_date is not None assert log_has("LIMIT_SELL has been fulfilled for Trade(id=2, " "pair=ETH/BTC, amount=90.99181073, open_rate=0.00001099, open_since=closed).", - caplog.record_tuples) + caplog) @pytest.mark.usefixtures("init_persistence") @@ -184,7 +184,7 @@ def test_update_market_order(market_buy_order, market_sell_order, fee, caplog): assert trade.close_date is None assert log_has("MARKET_BUY has been fulfilled for Trade(id=1, " "pair=ETH/BTC, amount=91.99181073, open_rate=0.00004099, open_since=closed).", - caplog.record_tuples) + caplog) caplog.clear() trade.open_order_id = 'something' @@ -195,7 +195,7 @@ def test_update_market_order(market_buy_order, market_sell_order, fee, caplog): assert trade.close_date is not None assert log_has("MARKET_SELL has been fulfilled for Trade(id=1, " "pair=ETH/BTC, amount=91.99181073, open_rate=0.00004099, open_since=closed).", - caplog.record_tuples) + caplog) @pytest.mark.usefixtures("init_persistence") @@ -558,10 +558,9 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert trade.ticker_interval is None assert trade.stoploss_order_id is None assert trade.stoploss_last_update is None - assert log_has("trying trades_bak1", caplog.record_tuples) - assert log_has("trying trades_bak2", caplog.record_tuples) - assert log_has("Running database migration - backup available as trades_bak2", - caplog.record_tuples) + assert log_has("trying trades_bak1", caplog) + assert log_has("trying trades_bak2", caplog) + assert log_has("Running database migration - backup available as trades_bak2", caplog) def test_migrate_mid_state(mocker, default_conf, fee, caplog): @@ -621,9 +620,8 @@ def test_migrate_mid_state(mocker, default_conf, fee, caplog): assert trade.max_rate == 0.0 assert trade.stop_loss == 0.0 assert trade.initial_stop_loss == 0.0 - assert log_has("trying trades_bak0", caplog.record_tuples) - assert log_has("Running database migration - backup available as trades_bak0", - caplog.record_tuples) + assert log_has("trying trades_bak0", caplog) + assert log_has("Running database migration - backup available as trades_bak0", caplog) def test_adjust_stop_loss(fee): diff --git a/freqtrade/tests/test_plotting.py b/freqtrade/tests/test_plotting.py index f9da773fe..bfdd72215 100644 --- a/freqtrade/tests/test_plotting.py +++ b/freqtrade/tests/test_plotting.py @@ -87,7 +87,7 @@ def test_add_indicators(default_conf, caplog): # No indicator found fig3 = add_indicators(fig=deepcopy(fig), row=3, indicators=['no_indicator'], data=data) assert fig == fig3 - assert log_has_re(r'Indicator "no_indicator" ignored\..*', caplog.record_tuples) + assert log_has_re(r'Indicator "no_indicator" ignored\..*', caplog) def test_plot_trades(caplog): @@ -95,7 +95,7 @@ def test_plot_trades(caplog): # nothing happens when no trades are available fig = plot_trades(fig1, None) assert fig == fig1 - assert log_has("No trades found.", caplog.record_tuples) + assert log_has("No trades found.", caplog) pair = "ADA/BTC" filename = history.make_testdata_path(None) / "backtest-result_test.json" trades = load_backtest_data(filename) @@ -150,8 +150,8 @@ def test_generate_candlestick_graph_no_signals_no_trades(default_conf, mocker, c assert row_mock.call_count == 2 assert trades_mock.call_count == 1 - assert log_has("No buy-signals found.", caplog.record_tuples) - assert log_has("No sell-signals found.", caplog.record_tuples) + assert log_has("No buy-signals found.", caplog) + assert log_has("No sell-signals found.", caplog) def test_generate_candlestick_graph_no_trades(default_conf, mocker): @@ -216,7 +216,7 @@ def test_generate_plot_file(mocker, caplog): assert (plot_mock.call_args_list[0][1]['filename'] == "user_data/plots/freqtrade-plot-UNITTEST_BTC-5m.html") assert log_has("Stored plot as user_data/plots/freqtrade-plot-UNITTEST_BTC-5m.html", - caplog.record_tuples) + caplog) def test_add_profit(): diff --git a/requirements-common.txt b/requirements-common.txt index 1413539c3..19beec64b 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -1,9 +1,9 @@ # 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 +arrow==0.14.5 cachetools==3.1.1 requests==2.22.0 urllib3==1.25.3 @@ -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 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 diff --git a/user_data/hyperopts/sample_hyperopt.py b/user_data/hyperopts/sample_hyperopt.py index 1a3823afa..fabfdb23e 100644 --- a/user_data/hyperopts/sample_hyperopt.py +++ b/user_data/hyperopts/sample_hyperopt.py @@ -1,11 +1,10 @@ # pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement from functools import reduce -from math import exp from typing import Any, Callable, Dict, List from datetime import datetime -import numpy as np# noqa F401 +import numpy as np import talib.abstract as ta from pandas import DataFrame from skopt.space import Categorical, Dimension, Integer, Real @@ -16,7 +15,7 @@ from freqtrade.optimize.hyperopt_interface import IHyperOpt class SampleHyperOpts(IHyperOpt): """ - This is a sample hyperopt to inspire you. + This is a sample Hyperopt to inspire you. Feel free to customize it. More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md @@ -37,32 +36,44 @@ class SampleHyperOpts(IHyperOpt): """ @staticmethod def populate_indicators(dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Add several indicators needed for buy and sell strategies defined below. + """ + # ADX dataframe['adx'] = ta.ADX(dataframe) + # MACD macd = ta.MACD(dataframe) dataframe['macd'] = macd['macd'] dataframe['macdsignal'] = macd['macdsignal'] + # MFI dataframe['mfi'] = ta.MFI(dataframe) + # RSI dataframe['rsi'] = ta.RSI(dataframe) + # Stochastic Fast stoch_fast = ta.STOCHF(dataframe) dataframe['fastd'] = stoch_fast['fastd'] + # Minus-DI dataframe['minus_di'] = ta.MINUS_DI(dataframe) # Bollinger bands bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) dataframe['bb_lowerband'] = bollinger['lower'] dataframe['bb_upperband'] = bollinger['upper'] + # SAR dataframe['sar'] = ta.SAR(dataframe) + return dataframe @staticmethod def buy_strategy_generator(params: Dict[str, Any]) -> Callable: """ - Define the buy strategy parameters to be used by hyperopt + Define the buy strategy parameters to be used by Hyperopt. """ def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame: """ - Buy strategy Hyperopt will build and use + Buy strategy Hyperopt will build and use. """ conditions = [] + # GUARDS AND TRENDS if 'mfi-enabled' in params and params['mfi-enabled']: conditions.append(dataframe['mfi'] < params['mfi-value']) @@ -98,7 +109,7 @@ class SampleHyperOpts(IHyperOpt): @staticmethod def indicator_space() -> List[Dimension]: """ - Define your Hyperopt space for searching strategy parameters + Define your Hyperopt space for searching buy strategy parameters. """ return [ Integer(10, 25, name='mfi-value'), @@ -115,14 +126,14 @@ class SampleHyperOpts(IHyperOpt): @staticmethod def sell_strategy_generator(params: Dict[str, Any]) -> Callable: """ - Define the sell strategy parameters to be used by hyperopt + Define the sell strategy parameters to be used by Hyperopt. """ def populate_sell_trend(dataframe: DataFrame, metadata: dict) -> DataFrame: """ - Sell strategy Hyperopt will build and use + Sell strategy Hyperopt will build and use. """ - # print(params) conditions = [] + # GUARDS AND TRENDS if 'sell-mfi-enabled' in params and params['sell-mfi-enabled']: conditions.append(dataframe['mfi'] > params['sell-mfi-value']) @@ -158,7 +169,7 @@ class SampleHyperOpts(IHyperOpt): @staticmethod def sell_indicator_space() -> List[Dimension]: """ - Define your Hyperopt space for searching sell strategy parameters + Define your Hyperopt space for searching sell strategy parameters. """ return [ Integer(75, 100, name='sell-mfi-value'), @@ -176,9 +187,9 @@ class SampleHyperOpts(IHyperOpt): def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ - Based on TA indicators. Should be a copy of from strategy - must align to populate_indicators in this file - Only used when --spaces does not include buy + Based on TA indicators. Should be a copy of same method from strategy. + Must align to populate_indicators in this file. + Only used when --spaces does not include buy space. """ dataframe.loc[ ( @@ -193,9 +204,9 @@ class SampleHyperOpts(IHyperOpt): def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ - Based on TA indicators. Should be a copy of from strategy - must align to populate_indicators in this file - Only used when --spaces does not include sell + Based on TA indicators. Should be a copy of same method from strategy. + Must align to populate_indicators in this file. + Only used when --spaces does not include sell space. """ dataframe.loc[ ( @@ -205,4 +216,5 @@ class SampleHyperOpts(IHyperOpt): (dataframe['fastd'] > 54) ), 'sell'] = 1 + return dataframe