From 47317e0f0659aaf8c126076ee0eaef6319f3f765 Mon Sep 17 00:00:00 2001 From: Joe Schr Date: Fri, 11 Mar 2022 17:53:29 +0100 Subject: [PATCH 01/21] version: use 'contains' to check for "develop" instead of literal comparison --- freqtrade/__init__.py | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index 2747efc96..d57cc9852 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,27 +1,14 @@ """ Freqtrade bot """ -__version__ = 'develop' - -if __version__ == 'develop': +__version__ = 'dev' +if 'dev' in __version__: try: import subprocess - __version__ = 'develop-' + subprocess.check_output( + __version__ = __version__ + '-' + subprocess.check_output( ['git', 'log', '--format="%h"', '-n 1'], stderr=subprocess.DEVNULL).decode("utf-8").rstrip().strip('"') - # from datetime import datetime - # last_release = subprocess.check_output( - # ['git', 'tag'] - # ).decode('utf-8').split()[-1].split(".") - # # Releases are in the format "2020.1" - we increment the latest version for dev. - # prefix = f"{last_release[0]}.{int(last_release[1]) + 1}" - # dev_version = int(datetime.now().timestamp() // 1000) - # __version__ = f"{prefix}.dev{dev_version}" - - # subprocess.check_output( - # ['git', 'log', '--format="%h"', '-n 1'], - # stderr=subprocess.DEVNULL).decode("utf-8").rstrip().strip('"') except Exception: # pragma: no cover # git not available, ignore try: From 95f69b905aec70c2e13b9def10dae6c0275df8ab Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 09:00:53 +0100 Subject: [PATCH 02/21] Remove ticker_interval support --- docs/backtesting.md | 4 ++-- docs/configuration.md | 2 +- docs/edge.md | 2 +- docs/hyperopt.md | 2 +- docs/plotting.md | 4 ++-- docs/strategy-customization.md | 2 +- freqtrade/commands/cli_options.py | 4 ++-- freqtrade/optimize/backtesting.py | 2 +- freqtrade/optimize/hyperopt_interface.py | 7 ++----- freqtrade/resolvers/hyperopt_resolver.py | 1 - freqtrade/resolvers/strategy_resolver.py | 4 ---- freqtrade/strategy/interface.py | 3 +-- 12 files changed, 14 insertions(+), 23 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index e7846b1f8..11608aad9 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -26,7 +26,7 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH] optional arguments: -h, --help show this help message and exit - -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME + -i TIMEFRAME, --timeframe TIMEFRAME Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`). --timerange TIMERANGE Specify what timerange of data to use. @@ -63,7 +63,7 @@ optional arguments: `30m`, `1h`, `1d`). --strategy-list STRATEGY_LIST [STRATEGY_LIST ...] Provide a space-separated list of strategies to - backtest. Please note that ticker-interval needs to be + backtest. Please note that timeframe needs to be set either in config or via command line. When using this together with `--export trades`, the strategy- name is injected into the filename (so `backtest- diff --git a/docs/configuration.md b/docs/configuration.md index d702fe8f9..2cb5dfa93 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -86,7 +86,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade).
*Defaults to `false`.*
**Datatype:** Boolean | `last_stake_amount_min_ratio` | Defines minimum stake amount that has to be left and executed. Applies only to the last stake amount when it's amended to a reduced value (i.e. if `amend_last_stake_amount` is set to `true`). [More information below](#configuring-amount-per-trade).
*Defaults to `0.5`.*
**Datatype:** Float (as ratio) | `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals.
*Defaults to `0.05` (5%).*
**Datatype:** Positive Float as ratio. -| `timeframe` | The timeframe (former ticker interval) to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** String +| `timeframe` | The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** String | `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency).
**Datatype:** String | `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode.
*Defaults to `true`.*
**Datatype:** Boolean | `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in Dry Run mode.
*Defaults to `1000`.*
**Datatype:** Float diff --git a/docs/edge.md b/docs/edge.md index 4402d767f..e92abf40f 100644 --- a/docs/edge.md +++ b/docs/edge.md @@ -222,7 +222,7 @@ usage: freqtrade edge [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] optional arguments: -h, --help show this help message and exit - -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME + -i TIMEFRAME, --timeframe TIMEFRAME Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`). --timerange TIMERANGE Specify what timerange of data to use. diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 19d8cd692..c8cb118f7 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -55,7 +55,7 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] optional arguments: -h, --help show this help message and exit - -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME + -i TIMEFRAME, --timeframe TIMEFRAME Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`). --timerange TIMERANGE Specify what timerange of data to use. diff --git a/docs/plotting.md b/docs/plotting.md index ccfbb12cb..004dd7821 100644 --- a/docs/plotting.md +++ b/docs/plotting.md @@ -65,7 +65,7 @@ optional arguments: _today.json` --timerange TIMERANGE Specify what timerange of data to use. - -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME + -i TIMEFRAME, --timeframe TIMEFRAME Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`). --no-trades Skip using trades from backtesting file and DB. @@ -330,7 +330,7 @@ optional arguments: --trade-source {DB,file} Specify the source for trades (Can be DB or file (backtest file)) Default: file - -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME + -i TIMEFRAME, --timeframe TIMEFRAME Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`). --auto-open Automatically open generated plot. diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index e6eff2416..045a55c5b 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -325,7 +325,7 @@ stoploss = -0.10 For the full documentation on stoploss features, look at the dedicated [stoploss page](stoploss.md). -### Timeframe (formerly ticker interval) +### Timeframe This is the set of candles the bot should download and use for the analysis. Common values are `"1m"`, `"5m"`, `"15m"`, `"1h"`, however all values supported by your exchange should work. diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 11fcc6b81..f30c25ba1 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -117,7 +117,7 @@ AVAILABLE_CLI_OPTIONS = { ), # Optimize common "timeframe": Arg( - '-i', '--timeframe', '--ticker-interval', + '-i', '--timeframe', help='Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).', ), "timerange": Arg( @@ -169,7 +169,7 @@ AVAILABLE_CLI_OPTIONS = { "strategy_list": Arg( '--strategy-list', help='Provide a space-separated list of strategies to backtest. ' - 'Please note that ticker-interval needs to be set either in config ' + 'Please note that timeframe needs to be set either in config ' 'or via command line. When using this together with `--export trades`, ' 'the strategy-name is injected into the filename ' '(so `backtest-data.json` becomes `backtest-data-SampleStrategy.json`', diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index eca643732..76f0e68f4 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -87,7 +87,7 @@ class Backtesting: validate_config_consistency(self.config) if "timeframe" not in self.config: - raise OperationalException("Timeframe (ticker interval) needs to be set in either " + raise OperationalException("Timeframe needs to be set in either " "configuration or as cli argument `--timeframe 5m`") self.timeframe = str(self.config.get('timeframe')) self.timeframe_min = timeframe_to_minutes(self.timeframe) diff --git a/freqtrade/optimize/hyperopt_interface.py b/freqtrade/optimize/hyperopt_interface.py index 01ffd7844..b1c68caca 100644 --- a/freqtrade/optimize/hyperopt_interface.py +++ b/freqtrade/optimize/hyperopt_interface.py @@ -29,15 +29,13 @@ class IHyperOpt(ABC): Class attributes you can use: timeframe -> int: value of the timeframe to use for the strategy """ - ticker_interval: str # DEPRECATED timeframe: str strategy: IStrategy def __init__(self, config: dict) -> None: self.config = config - # Assign ticker_interval to be used in hyperopt - IHyperOpt.ticker_interval = str(config['timeframe']) # DEPRECATED + # Assign timeframe to be used in hyperopt IHyperOpt.timeframe = str(config['timeframe']) def generate_estimator(self, dimensions: List[Dimension], **kwargs) -> EstimatorType: @@ -192,7 +190,7 @@ class IHyperOpt(ABC): Categorical([True, False], name='trailing_only_offset_is_reached'), ] - # This is needed for proper unpickling the class attribute ticker_interval + # This is needed for proper unpickling the class attribute timeframe # which is set to the actual value by the resolver. # Why do I still need such shamanic mantras in modern python? def __getstate__(self): @@ -202,5 +200,4 @@ class IHyperOpt(ABC): def __setstate__(self, state): self.__dict__.update(state) - IHyperOpt.ticker_interval = state['timeframe'] IHyperOpt.timeframe = state['timeframe'] diff --git a/freqtrade/resolvers/hyperopt_resolver.py b/freqtrade/resolvers/hyperopt_resolver.py index 6f0263e93..e3c234f60 100644 --- a/freqtrade/resolvers/hyperopt_resolver.py +++ b/freqtrade/resolvers/hyperopt_resolver.py @@ -44,7 +44,6 @@ class HyperOptLossResolver(IResolver): extra_dir=config.get('hyperopt_path')) # Assign timeframe to be used in hyperopt - hyperoptloss.__class__.ticker_interval = str(config['timeframe']) hyperoptloss.__class__.timeframe = str(config['timeframe']) return hyperoptloss diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index e9fcc3496..48cbd03cf 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -145,10 +145,6 @@ class StrategyResolver(IResolver): """ Normalize attributes to have the correct type. """ - # Assign deprecated variable - to not break users code relying on this. - if hasattr(strategy, 'timeframe'): - strategy.ticker_interval = strategy.timeframe - # Sort and apply type conversions if hasattr(strategy, 'minimal_roi'): strategy.minimal_roi = dict(sorted( diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 2f3657059..1eea84676 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -55,7 +55,7 @@ class IStrategy(ABC, HyperStrategyMixin): Attributes you can use: minimal_roi -> Dict: Minimal ROI designed for the strategy stoploss -> float: optimal stoploss designed for the strategy - timeframe -> str: value of the timeframe (ticker interval) to use with the strategy + timeframe -> str: value of the timeframe to use with the strategy """ # Strategy interface version # Default to version 2 @@ -81,7 +81,6 @@ class IStrategy(ABC, HyperStrategyMixin): use_custom_stoploss: bool = False # associated timeframe - ticker_interval: str # DEPRECATED timeframe: str # Optional order types From eb08b921802a6db6c2faa53df36e501a4a64e4af Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 09:01:18 +0100 Subject: [PATCH 03/21] Raise exception when ticker_interval is set. --- freqtrade/configuration/deprecated_settings.py | 11 +++-------- tests/optimize/test_backtesting.py | 9 ++++----- tests/optimize/test_edge_cli.py | 4 +--- tests/optimize/test_hyperopt.py | 1 - tests/strategy/test_strategy_loading.py | 2 -- tests/test_arguments.py | 6 +++--- tests/test_configuration.py | 16 ++++------------ 7 files changed, 15 insertions(+), 34 deletions(-) diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py index 5efe26bd2..cafa8957b 100644 --- a/freqtrade/configuration/deprecated_settings.py +++ b/freqtrade/configuration/deprecated_settings.py @@ -100,16 +100,11 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: "from the edge configuration." ) if 'ticker_interval' in config: - logger.warning( - "DEPRECATED: " + + raise OperationalException( + "DEPRECATED: 'ticker_interval' detected. " "Please use 'timeframe' instead of 'ticker_interval." ) - if 'timeframe' in config: - raise OperationalException( - "Both 'timeframe' and 'ticker_interval' detected." - "Please remove 'ticker_interval' from your configuration to continue operating." - ) - config['timeframe'] = config['ticker_interval'] if 'protections' in config: logger.warning("DEPRECATED: Setting 'protections' in the configuration is deprecated.") diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index a8998eb63..7a72747c0 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -314,16 +314,15 @@ def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None: patch_exchange(mocker) del default_conf['timeframe'] default_conf['strategy_list'] = ['StrategyTestV2', - 'SampleStrategy'] + 'HyperoptableStrategy'] mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5)) - with pytest.raises(OperationalException): + with pytest.raises(OperationalException, + match=r"Timeframe needs to be set in either configuration"): Backtesting(default_conf) - log_has("Ticker-interval needs to be set in either configuration " - "or as cli argument `--ticker-interval 5m`", caplog) -def test_data_with_fee(default_conf, mocker, testdatadir) -> None: +def test_data_with_fee(default_conf, mocker) -> None: patch_exchange(mocker) default_conf['fee'] = 0.1234 diff --git a/tests/optimize/test_edge_cli.py b/tests/optimize/test_edge_cli.py index 18d5f1c76..466a5f1cd 100644 --- a/tests/optimize/test_edge_cli.py +++ b/tests/optimize/test_edge_cli.py @@ -6,8 +6,7 @@ from unittest.mock import MagicMock from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_edge from freqtrade.enums import RunMode from freqtrade.optimize.edge_cli import EdgeCli -from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, - patched_configuration_load_config_file) +from tests.conftest import get_args, log_has, patch_exchange, patched_configuration_load_config_file def test_setup_optimize_configuration_without_arguments(mocker, default_conf, caplog) -> None: @@ -30,7 +29,6 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca assert 'datadir' in config assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert 'timeframe' in config - assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog) assert 'timerange' not in config assert 'stoploss_range' not in config diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 2328585dd..cc551277a 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -63,7 +63,6 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca assert 'datadir' in config assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert 'timeframe' in config - assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog) assert 'position_stacking' not in config assert not log_has('Parameter --enable-position-stacking detected ...', caplog) diff --git a/tests/strategy/test_strategy_loading.py b/tests/strategy/test_strategy_loading.py index 3590c3e01..3fe14a8de 100644 --- a/tests/strategy/test_strategy_loading.py +++ b/tests/strategy/test_strategy_loading.py @@ -111,7 +111,6 @@ def test_strategy(result, default_conf): assert default_conf['stoploss'] == -0.10 assert strategy.timeframe == '5m' - assert strategy.ticker_interval == '5m' assert default_conf['timeframe'] == '5m' df_indicators = strategy.advise_indicators(result, metadata=metadata) @@ -376,7 +375,6 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog): assert strategy._sell_fun_len == 2 assert strategy.INTERFACE_VERSION == 1 assert strategy.timeframe == '5m' - assert strategy.ticker_interval == '5m' indicator_df = strategy.advise_indicators(result, metadata=metadata) assert isinstance(indicator_df, DataFrame) diff --git a/tests/test_arguments.py b/tests/test_arguments.py index fca5c6ab9..ba9d154e5 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -111,17 +111,17 @@ def test_parse_args_strategy_path_invalid() -> None: def test_parse_args_backtesting_invalid() -> None: with pytest.raises(SystemExit, match=r'2'): - Arguments(['backtesting --ticker-interval']).get_parsed_arg() + Arguments(['backtesting --timeframe']).get_parsed_arg() with pytest.raises(SystemExit, match=r'2'): - Arguments(['backtesting --ticker-interval', 'abc']).get_parsed_arg() + Arguments(['backtesting --timeframe', 'abc']).get_parsed_arg() def test_parse_args_backtesting_custom() -> None: args = [ 'backtesting', '-c', 'test_conf.json', - '--ticker-interval', '1m', + '--timeframe', '1m', '--strategy-list', 'StrategyTestV2', 'SampleStrategy' diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 0a6935649..1cd9b0ff7 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -443,7 +443,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non '--strategy', 'StrategyTestV2', '--datadir', '/foo/bar', '--userdir', "/tmp/freqtrade", - '--ticker-interval', '1m', + '--timeframe', '1m', '--enable-position-stacking', '--disable-max-market-positions', '--timerange', ':100', @@ -494,7 +494,7 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non arglist = [ 'backtesting', '--config', 'config.json', - '--ticker-interval', '1m', + '--timeframe', '1m', '--export', 'trades', '--strategy-list', 'StrategyTestV2', @@ -1320,22 +1320,14 @@ def test_process_removed_setting(mocker, default_conf, caplog): def test_process_deprecated_ticker_interval(default_conf, caplog): message = "DEPRECATED: Please use 'timeframe' instead of 'ticker_interval." config = deepcopy(default_conf) + process_temporary_deprecated_settings(config) assert not log_has(message, caplog) del config['timeframe'] config['ticker_interval'] = '15m' - process_temporary_deprecated_settings(config) - assert log_has(message, caplog) - assert config['ticker_interval'] == '15m' - - config = deepcopy(default_conf) - # Have both timeframe and ticker interval in config - # Can also happen when using ticker_interval in configuration, and --timeframe as cli argument - config['timeframe'] = '5m' - config['ticker_interval'] = '4h' with pytest.raises(OperationalException, - match=r"Both 'timeframe' and 'ticker_interval' detected."): + match=r"DEPRECATED: 'ticker_interval' detected. Please use.*"): process_temporary_deprecated_settings(config) From 0f76b2373380ff30772ffaa07d940cfc9d71c6df Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 09:03:43 +0100 Subject: [PATCH 04/21] update deprecation message for ticker_interval --- docs/deprecated.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/deprecated.md b/docs/deprecated.md index eaf85bfbf..87c8a2b38 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -24,6 +24,10 @@ Please refer to [pairlists](plugins.md#pairlists-and-pairlist-handlers) instead. Did only download the latest 500 candles, so was ineffective in getting good backtest data. Removed in 2019-7-dev (develop branch) and in freqtrade 2019.8. +### `ticker_interval` (now `timeframe`) + +Support for `ticker_interval` terminology was deprecated in 2020.6 in favor of `timeframe` - and compatibility code was removed in 2022.3. + ### Allow running multiple pairlists in sequence The former `"pairlist"` section in the configuration has been removed, and is replaced by `"pairlists"` - being a list to specify a sequence of pairlists. From aceaa3faec80bba3ec7de35bcc50e0aee6cff58e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 09:30:54 +0100 Subject: [PATCH 05/21] remove last ticker_interval compatibility shim --- freqtrade/resolvers/strategy_resolver.py | 8 -------- tests/strategy/strats/legacy_strategy_v1.py | 4 +--- tests/strategy/test_strategy_loading.py | 3 --- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 48cbd03cf..8ad7cdb59 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -45,14 +45,6 @@ class StrategyResolver(IResolver): strategy_name, config=config, extra_dir=config.get('strategy_path')) - if hasattr(strategy, 'ticker_interval') and not hasattr(strategy, 'timeframe'): - # Assign ticker_interval to timeframe to keep compatibility - if 'timeframe' not in config: - logger.warning( - "DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'." - ) - strategy.timeframe = strategy.ticker_interval - if strategy._ft_params_from_file: # Set parameters from Hyperopt results file params = strategy._ft_params_from_file diff --git a/tests/strategy/strats/legacy_strategy_v1.py b/tests/strategy/strats/legacy_strategy_v1.py index ebfce632b..d7ed2014b 100644 --- a/tests/strategy/strats/legacy_strategy_v1.py +++ b/tests/strategy/strats/legacy_strategy_v1.py @@ -31,9 +31,7 @@ class TestStrategyLegacyV1(IStrategy): # This attribute will be overridden if the config file contains "stoploss" stoploss = -0.10 - # Optimal timeframe for the strategy - # Keep the legacy value here to test compatibility - ticker_interval = '5m' + timeframe = '5m' def populate_indicators(self, dataframe: DataFrame) -> DataFrame: """ diff --git a/tests/strategy/test_strategy_loading.py b/tests/strategy/test_strategy_loading.py index 3fe14a8de..cc924d1c2 100644 --- a/tests/strategy/test_strategy_loading.py +++ b/tests/strategy/test_strategy_loading.py @@ -388,9 +388,6 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog): assert isinstance(selldf, DataFrame) assert 'sell' in selldf - assert log_has("DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'.", - caplog) - def test_strategy_interface_versioning(result, monkeypatch, default_conf): default_conf.update({'strategy': 'StrategyTestV2'}) From 8556e6a0534e0e1e1ab1aeee53e99f23c078fb03 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 09:30:11 +0100 Subject: [PATCH 06/21] Automatically assign buy-tag to force-buys closes #6544 --- docs/telegram-usage.md | 1 + freqtrade/rpc/api_server/api_v1.py | 2 +- freqtrade/rpc/rpc.py | 2 +- tests/rpc/test_rpc.py | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index c7f9c58f6..232885ed2 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -277,6 +277,7 @@ Starting capital is either taken from the `available_capital` setting, or calcul > **BITTREX:** Buying ETH/BTC with limit `0.03400000` (`1.000000 ETH`, `225.290 USD`) Omitting the pair will open a query asking for the pair to buy (based on the current whitelist). +Trades crated through `/forcebuy` will have the buy-tag of `forceentry`. ![Telegram force-buy screenshot](assets/telegram_forcebuy.png) diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 6379150ee..5a34385da 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -137,7 +137,7 @@ def show_config(rpc: Optional[RPC] = Depends(get_rpc_optional), config=Depends(g def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)): ordertype = payload.ordertype.value if payload.ordertype else None stake_amount = payload.stakeamount if payload.stakeamount else None - entry_tag = payload.entry_tag if payload.entry_tag else None + entry_tag = payload.entry_tag if payload.entry_tag else 'forceentry' trade = rpc._rpc_forcebuy(payload.pair, payload.price, ordertype, stake_amount, entry_tag) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 3d4fffbc9..7fd419a5b 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -713,7 +713,7 @@ class RPC: def _rpc_forcebuy(self, pair: str, price: Optional[float], order_type: Optional[str] = None, stake_amount: Optional[float] = None, - buy_tag: Optional[str] = None) -> Optional[Trade]: + buy_tag: Optional[str] = 'forceentry') -> Optional[Trade]: """ Handler for forcebuy Buys a pair trade at the given or current price diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index d738760be..003b43ad2 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -1147,6 +1147,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> pair = 'LTC/BTC' trade = rpc._rpc_forcebuy(pair, 0.0001, order_type='limit', stake_amount=0.05) assert trade.stake_amount == 0.05 + assert trade.buy_tag == 'forceentry' # Test not buying pair = 'XRP/BTC' From fcec071a0804574dc9e03d8a45ebf8dcc8fa665b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 09:42:34 +0100 Subject: [PATCH 07/21] Use order date to fetch trades using the trade open-date may fail in case of several trade-entries spread over a longer timeperiod. closes #6551 --- freqtrade/freqtradebot.py | 15 ++++++++------- tests/test_freqtradebot.py | 31 ++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 16864f814..1c2b7208f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1428,14 +1428,14 @@ class FreqtradeBot(LoggingMixin): def handle_order_fee(self, trade: Trade, order_obj: Order, order: Dict[str, Any]) -> None: # Try update amount (binance-fix) try: - new_amount = self.get_real_amount(trade, order) + new_amount = self.get_real_amount(trade, order, order_obj) if not isclose(safe_value_fallback(order, 'filled', 'amount'), new_amount, abs_tol=constants.MATH_CLOSE_PREC): order_obj.ft_fee_base = trade.amount - new_amount except DependencyException as exception: logger.warning("Could not update trade amount: %s", exception) - def get_real_amount(self, trade: Trade, order: Dict) -> float: + def get_real_amount(self, trade: Trade, order: Dict, order_obj: Order) -> float: """ Detect and update trade fee. Calls trade.update_fee() upon correct detection. @@ -1453,7 +1453,7 @@ class FreqtradeBot(LoggingMixin): # use fee from order-dict if possible if self.exchange.order_has_fee(order): fee_cost, fee_currency, fee_rate = self.exchange.extract_cost_curr_rate(order) - logger.info(f"Fee for Trade {trade} [{order.get('side')}]: " + logger.info(f"Fee for Trade {trade} [{order_obj.ft_order_side}]: " f"{fee_cost:.8g} {fee_currency} - rate: {fee_rate}") if fee_rate is None or fee_rate < 0.02: # Reject all fees that report as > 2%. @@ -1465,17 +1465,18 @@ class FreqtradeBot(LoggingMixin): return self.apply_fee_conditional(trade, trade_base_currency, amount=order_amount, fee_abs=fee_cost) return order_amount - return self.fee_detection_from_trades(trade, order, order_amount, order.get('trades', [])) + return self.fee_detection_from_trades( + trade, order, order_obj, order_amount, order.get('trades', [])) - def fee_detection_from_trades(self, trade: Trade, order: Dict, order_amount: float, - trades: List) -> float: + def fee_detection_from_trades(self, trade: Trade, order: Dict, order_obj: Order, + order_amount: float, trades: List) -> float: """ fee-detection fallback to Trades. Either uses provided trades list or the result of fetch_my_trades to get correct fee. """ if not trades: trades = self.exchange.get_trades_for_order( - self.exchange.get_order_id_conditional(order), trade.pair, trade.open_date) + self.exchange.get_order_id_conditional(order), trade.pair, order_obj.order_date) if len(trades) == 0: logger.info("Applying fee on amount for %s failed: myTrade-Dict empty found", trade) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 1aeb56cdd..bafed8488 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -3568,9 +3568,9 @@ def test_get_real_amount_quote(default_conf_usdt, trades_for_order, buy_order_fe open_order_id="123456" ) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') # Amount is reduced by "fee" - assert freqtrade.get_real_amount(trade, buy_order_fee) == amount - (amount * 0.001) + assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == 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).', caplog) @@ -3594,8 +3594,9 @@ def test_get_real_amount_quote_dust(default_conf_usdt, trades_for_order, buy_ord freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) walletmock.reset_mock() + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') # Amount is kept as is - assert freqtrade.get_real_amount(trade, buy_order_fee) == amount + assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == amount assert walletmock.call_count == 1 assert log_has_re(r'Fee amount for Trade.* was in base currency ' '- Eating Fee 0.008 into dust', caplog) @@ -3616,8 +3617,9 @@ def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mock ) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') # Amount is reduced by "fee" - assert freqtrade.get_real_amount(trade, buy_order_fee) == amount + assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == 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) @@ -3668,7 +3670,8 @@ def test_get_real_amount( mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', side_effect=ExchangeError) caplog.clear() - assert freqtrade.get_real_amount(trade, buy_order) == amount - fee_reduction_amount + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') + assert freqtrade.get_real_amount(trade, buy_order, order_obj) == amount - fee_reduction_amount if expected_log: assert log_has(expected_log, caplog) @@ -3715,7 +3718,8 @@ def test_get_real_amount_multi( # Amount is reduced by "fee" expected_amount = amount - (amount * fee_reduction_amount) - assert freqtrade.get_real_amount(trade, buy_order_fee) == expected_amount + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') + assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == expected_amount assert log_has( ( 'Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' @@ -3750,8 +3754,9 @@ def test_get_real_amount_invalid_order(default_conf_usdt, trades_for_order, buy_ ) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') # Amount does not change - assert freqtrade.get_real_amount(trade, limit_buy_order_usdt) == amount + assert freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj) == amount def test_get_real_amount_fees_order(default_conf_usdt, market_buy_order_usdt_doublefee, @@ -3773,7 +3778,8 @@ def test_get_real_amount_fees_order(default_conf_usdt, market_buy_order_usdt_dou # Amount does not change assert trade.fee_open == 0.0025 - assert freqtrade.get_real_amount(trade, market_buy_order_usdt_doublefee) == 30.0 + order_obj = Order.parse_from_ccxt_object(market_buy_order_usdt_doublefee, 'LTC/ETH', 'buy') + assert freqtrade.get_real_amount(trade, market_buy_order_usdt_doublefee, order_obj) == 30.0 assert tfo_mock.call_count == 0 # Fetch fees from trades dict if available to get "proper" values assert round(trade.fee_open, 4) == 0.001 @@ -3797,9 +3803,10 @@ def test_get_real_amount_wrong_amount(default_conf_usdt, trades_for_order, buy_o ) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') # Amount does not change with pytest.raises(DependencyException, match=r"Half bought\? Amounts don't match"): - freqtrade.get_real_amount(trade, limit_buy_order_usdt) + freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj) def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_order, buy_order_fee, @@ -3821,9 +3828,10 @@ def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_ord ) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') # Amount changes by fee amount. assert isclose( - freqtrade.get_real_amount(trade, limit_buy_order_usdt), + freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj), amount - (amount * 0.001), abs_tol=MATH_CLOSE_PREC, ) @@ -3847,7 +3855,8 @@ def test_get_real_amount_open_trade(default_conf_usdt, fee, mocker): 'side': 'buy', } freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - assert freqtrade.get_real_amount(trade, order) == amount + order_obj = Order.parse_from_ccxt_object(order, 'LTC/ETH', 'buy') + assert freqtrade.get_real_amount(trade, order, order_obj) == amount @pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [ From 49e087df5b79d90381ac9ee64d0eaac88e044f74 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 13:07:06 +0100 Subject: [PATCH 08/21] Allow Strategy subclassing in different files by enabling local imports --- docs/strategy-advanced.md | 9 ++-- freqtrade/resolvers/iresolver.py | 58 ++++++++++++++++------- tests/strategy/strats/strategy_test_v2.py | 2 +- 3 files changed, 45 insertions(+), 24 deletions(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index 3793abacf..fa1c09560 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -164,16 +164,15 @@ class MyAwesomeStrategy2(MyAwesomeStrategy): Both attributes and methods may be overridden, altering behavior of the original strategy in a way you need. !!! Note "Parent-strategy in different files" - If you have the parent-strategy in a different file, you'll need to add the following to the top of your "child"-file to ensure proper loading, otherwise freqtrade may not be able to load the parent strategy correctly. + If you have the parent-strategy in a different file, you can still import the strategy. + Assuming `myawesomestrategy.py` is the filename, and `MyAwesomeStrategy` the strategy you need to import: ``` python - import sys - from pathlib import Path - sys.path.append(str(Path(__file__).parent)) - from myawesomestrategy import MyAwesomeStrategy ``` + This is the recommended way to derive strategies to avoid problems with hyperopt parameter files. + ## Embedding Strategies Freqtrade provides you with an easy way to embed the strategy into your configuration file. diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index c6f97c976..8d132da70 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -6,6 +6,7 @@ This module load custom objects import importlib.util import inspect import logging +import sys from pathlib import Path from typing import Any, Dict, Iterator, List, Optional, Tuple, Type, Union @@ -15,6 +16,22 @@ from freqtrade.exceptions import OperationalException logger = logging.getLogger(__name__) +class PathModifier: + def __init__(self, path: Path): + self.path = path + + def __enter__(self): + """Inject path to allow importing with relative imports.""" + sys.path.insert(0, str(self.path)) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """Undo insertion of local path.""" + str_path = str(self.path) + if str_path in sys.path: + sys.path.remove(str_path) + + class IResolver: """ This class contains all the logic to load custom classes @@ -57,27 +74,32 @@ class IResolver: # Generate spec based on absolute path # Pass object_name as first argument to have logging print a reasonable name. - spec = importlib.util.spec_from_file_location(object_name or "", str(module_path)) - if not spec: - return iter([None]) + with PathModifier(module_path.parent): - module = importlib.util.module_from_spec(spec) - try: - spec.loader.exec_module(module) # type: ignore # importlib does not use typehints - except (ModuleNotFoundError, SyntaxError, ImportError, NameError) as err: - # Catch errors in case a specific module is not installed - logger.warning(f"Could not import {module_path} due to '{err}'") - if enum_failed: + spec = importlib.util.spec_from_file_location(module_path.stem or "", str(module_path)) + if not spec: return iter([None]) - valid_objects_gen = ( - (obj, inspect.getsource(module)) for - name, obj in inspect.getmembers( - module, inspect.isclass) if ((object_name is None or object_name == name) - and issubclass(obj, cls.object_type) - and obj is not cls.object_type) - ) - return valid_objects_gen + module = importlib.util.module_from_spec(spec) + try: + spec.loader.exec_module(module) # type: ignore # importlib does not use typehints + except (ModuleNotFoundError, SyntaxError, ImportError, NameError) as err: + # Catch errors in case a specific module is not installed + logger.warning(f"Could not import {module_path} due to '{err}'") + if enum_failed: + return iter([None]) + + valid_objects_gen = ( + (obj, inspect.getsource(module)) for + name, obj in inspect.getmembers( + module, inspect.isclass) if ((object_name is None or object_name == name) + and issubclass(obj, cls.object_type) + and obj is not cls.object_type + and obj.__module__ == module_path.stem or "" + ) + ) + # The __module__ check ensures we only use strategies that are defined in this folder. + return valid_objects_gen @classmethod def _search_object(cls, directory: Path, *, object_name: str, add_source: bool = False diff --git a/tests/strategy/strats/strategy_test_v2.py b/tests/strategy/strats/strategy_test_v2.py index c57becdad..59f1f569e 100644 --- a/tests/strategy/strats/strategy_test_v2.py +++ b/tests/strategy/strats/strategy_test_v2.py @@ -7,7 +7,7 @@ from pandas import DataFrame import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.persistence import Trade -from freqtrade.strategy.interface import IStrategy +from freqtrade.strategy import IStrategy class StrategyTestV2(IStrategy): From 6ec7b84b92ec24264d5f573f8289cd466322196e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 13:12:26 +0100 Subject: [PATCH 09/21] Modify hyperoptable strategy to use relative importing --- .../strategy/strats/hyperoptable_strategy.py | 88 +------------------ 1 file changed, 3 insertions(+), 85 deletions(-) diff --git a/tests/strategy/strats/hyperoptable_strategy.py b/tests/strategy/strats/hyperoptable_strategy.py index 88bdd078e..dc6b03a3e 100644 --- a/tests/strategy/strats/hyperoptable_strategy.py +++ b/tests/strategy/strats/hyperoptable_strategy.py @@ -1,14 +1,13 @@ # pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement -import talib.abstract as ta from pandas import DataFrame +from strategy_test_v2 import StrategyTestV2 import freqtrade.vendor.qtpylib.indicators as qtpylib -from freqtrade.strategy import (BooleanParameter, DecimalParameter, IntParameter, IStrategy, - RealParameter) +from freqtrade.strategy import BooleanParameter, DecimalParameter, IntParameter, RealParameter -class HyperoptableStrategy(IStrategy): +class HyperoptableStrategy(StrategyTestV2): """ Default Strategy provided by freqtrade bot. Please do not modify this strategy, it's intended for internal use only. @@ -16,38 +15,6 @@ class HyperoptableStrategy(IStrategy): or strategy repository https://github.com/freqtrade/freqtrade-strategies for samples and inspiration. """ - INTERFACE_VERSION = 2 - - # Minimal ROI designed for the strategy - minimal_roi = { - "40": 0.0, - "30": 0.01, - "20": 0.02, - "0": 0.04 - } - - # Optimal stoploss designed for the strategy - stoploss = -0.10 - - # Optimal ticker interval for the strategy - timeframe = '5m' - - # Optional order type mapping - order_types = { - 'buy': 'limit', - 'sell': 'limit', - 'stoploss': 'limit', - 'stoploss_on_exchange': False - } - - # Number of candles the strategy requires before producing valid signals - startup_candle_count: int = 20 - - # Optional time in force for orders - order_time_in_force = { - 'buy': 'gtc', - 'sell': 'gtc', - } buy_params = { 'buy_rsi': 35, @@ -91,55 +58,6 @@ class HyperoptableStrategy(IStrategy): """ return [] - def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Adds several different TA indicators to the given DataFrame - - Performance Note: For the best performance be frugal on the number of indicators - you are using. Let uncomment only the indicator you are using in your strategies - or your hyperopt configuration, otherwise you will waste your memory and CPU usage. - :param dataframe: Dataframe with data from the exchange - :param metadata: Additional information, like the currently traded pair - :return: a Dataframe with all mandatory indicators for the strategies - """ - - # Momentum Indicator - # ------------------------------------ - - # ADX - dataframe['adx'] = ta.ADX(dataframe) - - # MACD - macd = ta.MACD(dataframe) - dataframe['macd'] = macd['macd'] - dataframe['macdsignal'] = macd['macdsignal'] - dataframe['macdhist'] = macd['macdhist'] - - # Minus Directional Indicator / Movement - dataframe['minus_di'] = ta.MINUS_DI(dataframe) - - # Plus Directional Indicator / Movement - dataframe['plus_di'] = ta.PLUS_DI(dataframe) - - # RSI - dataframe['rsi'] = ta.RSI(dataframe) - - # Stoch fast - stoch_fast = ta.STOCHF(dataframe) - dataframe['fastd'] = stoch_fast['fastd'] - dataframe['fastk'] = stoch_fast['fastk'] - - # Bollinger bands - bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) - dataframe['bb_lowerband'] = bollinger['lower'] - dataframe['bb_middleband'] = bollinger['mid'] - dataframe['bb_upperband'] = bollinger['upper'] - - # EMA - Exponential Moving Average - dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) - - return dataframe - def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe From e9c4e6a69d9c173b30c6b4d2263ad658b1950b42 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 13:14:52 +0100 Subject: [PATCH 10/21] Update derived strategy documentation --- docs/strategy-advanced.md | 11 +++++++---- freqtrade/resolvers/iresolver.py | 6 +++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index fa1c09560..533402528 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -146,7 +146,7 @@ def version(self) -> str: The strategies can be derived from other strategies. This avoids duplication of your custom strategy code. You can use this technique to override small parts of your main strategy, leaving the rest untouched: -``` python +``` python title="user_data/strategies/myawesomestrategy.py" class MyAwesomeStrategy(IStrategy): ... stoploss = 0.13 @@ -155,6 +155,10 @@ class MyAwesomeStrategy(IStrategy): # should be in any custom strategy... ... +``` + +``` python title="user_data/strategies/MyAwesomeStrategy2.py" +from myawesomestrategy import MyAwesomeStrategy class MyAwesomeStrategy2(MyAwesomeStrategy): # Override something stoploss = 0.08 @@ -163,16 +167,15 @@ class MyAwesomeStrategy2(MyAwesomeStrategy): Both attributes and methods may be overridden, altering behavior of the original strategy in a way you need. +While keeping the subclass in the same file is technically possible, it can lead to some problems with hyperopt parameter files. + !!! Note "Parent-strategy in different files" If you have the parent-strategy in a different file, you can still import the strategy. Assuming `myawesomestrategy.py` is the filename, and `MyAwesomeStrategy` the strategy you need to import: ``` python - from myawesomestrategy import MyAwesomeStrategy ``` - This is the recommended way to derive strategies to avoid problems with hyperopt parameter files. - ## Embedding Strategies Freqtrade provides you with an easy way to embed the strategy into your configuration file. diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index 8d132da70..3ab461041 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -75,8 +75,8 @@ class IResolver: # Generate spec based on absolute path # Pass object_name as first argument to have logging print a reasonable name. with PathModifier(module_path.parent): - - spec = importlib.util.spec_from_file_location(module_path.stem or "", str(module_path)) + module_name = module_path.stem or "" + spec = importlib.util.spec_from_file_location(module_name, str(module_path)) if not spec: return iter([None]) @@ -95,7 +95,7 @@ class IResolver: module, inspect.isclass) if ((object_name is None or object_name == name) and issubclass(obj, cls.object_type) and obj is not cls.object_type - and obj.__module__ == module_path.stem or "" + and obj.__module__ == module_name ) ) # The __module__ check ensures we only use strategies that are defined in this folder. From f5e71a67fabdbb6b403e9ed87f94c2fa773effb9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 03:01:11 +0000 Subject: [PATCH 11/21] Bump pymdown-extensions from 9.2 to 9.3 Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 9.2 to 9.3. - [Release notes](https://github.com/facelessuser/pymdown-extensions/releases) - [Commits](https://github.com/facelessuser/pymdown-extensions/compare/9.2...9.3) --- updated-dependencies: - dependency-name: pymdown-extensions dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 0ca0e4b63..01ce559ff 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,4 +1,4 @@ mkdocs==1.2.3 mkdocs-material==8.2.5 mdx_truly_sane_lists==1.2 -pymdown-extensions==9.2 +pymdown-extensions==9.3 From 1bc5d449a205c34c1672b0e0684b2936e884f1e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 03:01:16 +0000 Subject: [PATCH 12/21] Bump cryptography from 36.0.1 to 36.0.2 Bumps [cryptography](https://github.com/pyca/cryptography) from 36.0.1 to 36.0.2. - [Release notes](https://github.com/pyca/cryptography/releases) - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/36.0.1...36.0.2) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f0f030e78..e1c052353 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ pandas-ta==0.3.14b ccxt==1.76.5 # Pin cryptography for now due to rust build errors with piwheels -cryptography==36.0.1 +cryptography==36.0.2 aiohttp==3.8.1 SQLAlchemy==1.4.32 python-telegram-bot==13.11 From 59c7403b1214cb21ba5a377bafe1b3191e825cac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 03:01:23 +0000 Subject: [PATCH 13/21] Bump urllib3 from 1.26.8 to 1.26.9 Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.8 to 1.26.9. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/1.26.9/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.26.8...1.26.9) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f0f030e78..c112a37b5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ python-telegram-bot==13.11 arrow==1.2.2 cachetools==4.2.2 requests==2.27.1 -urllib3==1.26.8 +urllib3==1.26.9 jsonschema==4.4.0 TA-Lib==0.4.24 technical==1.3.0 From 5a136f04dfcdc7f7e3b4a6534ecd6c83a0abf373 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 03:01:33 +0000 Subject: [PATCH 14/21] Bump ccxt from 1.76.5 to 1.76.65 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.76.5 to 1.76.65. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg) - [Commits](https://github.com/ccxt/ccxt/compare/1.76.5...1.76.65) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f0f030e78..00ff46088 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.22.3 pandas==1.4.1 pandas-ta==0.3.14b -ccxt==1.76.5 +ccxt==1.76.65 # Pin cryptography for now due to rust build errors with piwheels cryptography==36.0.1 aiohttp==3.8.1 From 1a37100bd45a11c6665749b5ff028e0adac14a1c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 03:01:41 +0000 Subject: [PATCH 15/21] Bump types-python-dateutil from 2.8.9 to 2.8.10 Bumps [types-python-dateutil](https://github.com/python/typeshed) from 2.8.9 to 2.8.10. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-python-dateutil dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index c2f3eae8a..d9ca8c6ae 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -26,4 +26,4 @@ types-requests==2.27.12 types-tabulate==0.8.5 # Extensions to datetime library -types-python-dateutil==2.8.9 \ No newline at end of file +types-python-dateutil==2.8.10 \ No newline at end of file From 03090d8f3ffc34dc32f6d957385e2a52b894ad49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 03:01:45 +0000 Subject: [PATCH 16/21] Bump pytest from 7.1.0 to 7.1.1 Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.1.0 to 7.1.1. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.1.0...7.1.1) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index c2f3eae8a..3d87763aa 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ coveralls==3.3.1 flake8==4.0.1 flake8-tidy-imports==4.6.0 mypy==0.940 -pytest==7.1.0 +pytest==7.1.1 pytest-asyncio==0.18.2 pytest-cov==3.0.0 pytest-mock==3.7.0 From 057db5aaabe4356e465986650469d6b1b953836e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 05:30:51 +0000 Subject: [PATCH 17/21] Bump types-requests from 2.27.12 to 2.27.14 Bumps [types-requests](https://github.com/python/typeshed) from 2.27.12 to 2.27.14. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index d9ca8c6ae..614921019 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -22,7 +22,7 @@ nbconvert==6.4.4 # mypy types types-cachetools==5.0.0 types-filelock==3.2.5 -types-requests==2.27.12 +types-requests==2.27.14 types-tabulate==0.8.5 # Extensions to datetime library From 7170b585f9a3c9e665bfe0c3fb5697e820a266d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 08:06:25 +0000 Subject: [PATCH 18/21] Bump mypy from 0.940 to 0.941 Bumps [mypy](https://github.com/python/mypy) from 0.940 to 0.941. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.940...v0.941) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index ca625238e..af19187fa 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,7 +6,7 @@ coveralls==3.3.1 flake8==4.0.1 flake8-tidy-imports==4.6.0 -mypy==0.940 +mypy==0.941 pytest==7.1.1 pytest-asyncio==0.18.2 pytest-cov==3.0.0 From c28e0b0d0cd8f252a93acd854701c658fdc2a3bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 08:06:41 +0000 Subject: [PATCH 19/21] Bump types-tabulate from 0.8.5 to 0.8.6 Bumps [types-tabulate](https://github.com/python/typeshed) from 0.8.5 to 0.8.6. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-tabulate dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index ca625238e..8bd1b68ba 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -23,7 +23,7 @@ nbconvert==6.4.4 types-cachetools==5.0.0 types-filelock==3.2.5 types-requests==2.27.14 -types-tabulate==0.8.5 +types-tabulate==0.8.6 # Extensions to datetime library types-python-dateutil==2.8.10 \ No newline at end of file From 487d3e891e454542e3f438986f080b2b852fae2f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 21 Mar 2022 19:41:34 +0100 Subject: [PATCH 20/21] Revert version to develop for now --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index d57cc9852..f8be8f66f 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ Freqtrade bot """ -__version__ = 'dev' +__version__ = 'develop' if 'dev' in __version__: try: From e7418cdcdb9cf7916ff0a5464113c20817fe7486 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 22 Mar 2022 19:03:22 +0100 Subject: [PATCH 21/21] Remove obsolete note box closes #6581 --- docs/strategy-advanced.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index 533402528..b1f154355 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -167,14 +167,7 @@ class MyAwesomeStrategy2(MyAwesomeStrategy): Both attributes and methods may be overridden, altering behavior of the original strategy in a way you need. -While keeping the subclass in the same file is technically possible, it can lead to some problems with hyperopt parameter files. - -!!! Note "Parent-strategy in different files" - If you have the parent-strategy in a different file, you can still import the strategy. - Assuming `myawesomestrategy.py` is the filename, and `MyAwesomeStrategy` the strategy you need to import: - - ``` python - ``` +While keeping the subclass in the same file is technically possible, it can lead to some problems with hyperopt parameter files, we therefore recommend to use separate strategy files, and import the parent strategy as shown above. ## Embedding Strategies