From 614a99659762c2b5594fcca1ddfc1c1458a732ae Mon Sep 17 00:00:00 2001 From: hoeckxer Date: Mon, 4 Jan 2021 20:49:24 +0100 Subject: [PATCH 01/14] First commit about ignoring expired candle Signed-off-by: hoeckxer --- docs/configuration.md | 17 +++++++++++++++++ freqtrade/strategy/interface.py | 21 ++++++++++++++++++++- tests/strategy/test_interface.py | 17 +++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index b70a85c04..db078cba8 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -121,6 +121,8 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `user_data_dir` | Directory containing user data.
*Defaults to `./user_data/`*.
**Datatype:** String | `dataformat_ohlcv` | Data format to use to store historical candle (OHLCV) data.
*Defaults to `json`*.
**Datatype:** String | `dataformat_trades` | Data format to use to store historical trades data.
*Defaults to `jsongz`*.
**Datatype:** String +| `ignore_buying_expired_candle` | Enables usage of skipping buys on candles that are older than a specified period.
*Defaults to `False`*
**Datatype:** Boolean +| `ignore_buying_expired_candle_after` | Specifies the number of seconds until a buy signal is no longer used when setting `ignore_buying_expired_candle`.
**Datatype:** Integer ### Parameters in the strategy @@ -144,6 +146,8 @@ Values set in the configuration file always overwrite values set in the strategy * `use_sell_signal` (ask_strategy) * `sell_profit_only` (ask_strategy) * `ignore_roi_if_buy_signal` (ask_strategy) +* `ignore_buying_expired_candle` +* `ignore_buying_expired_candle_after` ### Configuring amount per trade @@ -671,6 +675,19 @@ export HTTPS_PROXY="http://addr:port" freqtrade ``` +## Ignoring expired candles + +When working with larger timeframes (for example 1h or more) and using a low `max_trades` value, the last candle can be processed as soon as a trade slot becomes available. When processing the last candle, this can lead to a situation where it may not be desirable to use the buy signal on that candle. For example, when using a condition in your strategy where you use a cross-over, that point may have passed too long ago for you to start a trade on it. + +In these situations, you can enable the functionality to ignore candles that are beyond a specified period by setting `ignore_buying_expired_candle` to `True`. After this, you can set `ignore_buying_expired_candle_after` to the number of seconds after which the candle becomes expired. + +For example, if your strategy is using a 1h timeframe, and you only want to buy within the first 5 minutes when a new candle comes in, you can add the following configuration to your strategy: + +``` json +ignore_buying_expired_candle = True +ignore_buying_expired_candle_after = 300 # 5 minutes +``` + ## Embedding Strategies Freqtrade provides you with with an easy way to embed the strategy into your configuration file. diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 1e4fc8b12..62ef4e91b 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -5,7 +5,7 @@ This module defines the interface to apply for strategies import logging import warnings from abc import ABC, abstractmethod -from datetime import datetime, timezone +from datetime import datetime, timezone, timedelta from enum import Enum from typing import Dict, List, NamedTuple, Optional, Tuple @@ -113,6 +113,11 @@ class IStrategy(ABC): # run "populate_indicators" only for new candle process_only_new_candles: bool = False + # Don't analyze too old candles + ignore_buying_expired_candle: bool = False + # Number of seconds after which the candle will no longer result in a buy + ignore_buying_expired_candle_after: int = 0 + # Disable checking the dataframe (converts the error into a warning message) disable_dataframe_checks: bool = False @@ -476,8 +481,21 @@ class IStrategy(ABC): (buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1 logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', latest['date'], pair, str(buy), str(sell)) + if self.ignore_expired_candle(dataframe=dataframe, buy=buy): + return False, sell return buy, sell + def ignore_expired_candle(self, dataframe: DataFrame, buy: bool): + if self.ignore_buying_expired_candle and buy: + current_time = datetime.now(timezone.utc) - timedelta(seconds=self.ignore_buying_expired_candle_after) + candle_time = dataframe['date'].tail(1).iat[0] + time_delta = current_time - candle_time + if time_delta.total_seconds() > self.ignore_buying_expired_candle_after: + logger.debug('ignoring buy signals because candle exceeded ignore_buying_expired_candle_after of %s seconds', self.ignore_buying_expired_candle_after) + return True + else: + return False + def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, sell: bool, low: float = None, high: float = None, force_stoploss: float = 0) -> SellCheckTuple: @@ -672,6 +690,7 @@ class IStrategy(ABC): :return: DataFrame with buy column """ logger.debug(f"Populating buy signals for pair {metadata.get('pair')}.") + if self._buy_fun_len == 2: warnings.warn("deprecated - check out the Sample strategy to see " "the current function headers!", DeprecationWarning) diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 7eed43302..330689039 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -106,6 +106,23 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history): assert log_has('Outdated history for pair xyz. Last tick is 16 minutes old', caplog) +def test_ignore_expired_candle(default_conf, ohlcv_history): + default_conf.update({'strategy': 'DefaultStrategy'}) + strategy = StrategyResolver.load_strategy(default_conf) + strategy.ignore_buying_expired_candle = True + strategy.ignore_buying_expired_candle_after = 60 + + ohlcv_history.loc[-1, 'date'] = arrow.utcnow().shift(minutes=-3) + # Take a copy to correctly modify the call + mocked_history = ohlcv_history.copy() + mocked_history['sell'] = 0 + mocked_history['buy'] = 0 + mocked_history.loc[1, 'buy'] = 1 + mocked_history.loc[1, 'sell'] = 1 + + assert strategy.ignore_expired_candle(mocked_history, True) == True + + def test_assert_df_raise(mocker, caplog, ohlcv_history): ohlcv_history.loc[1, 'date'] = arrow.utcnow().shift(minutes=-16) # Take a copy to correctly modify the call From 844df96ec77b7e9f28969627e26bc53253189f76 Mon Sep 17 00:00:00 2001 From: hoeckxer Date: Tue, 5 Jan 2021 07:06:53 +0100 Subject: [PATCH 02/14] Making changes so the build checks are satisified (imports & flake8) Signed-off-by: hoeckxer --- freqtrade/strategy/interface.py | 10 +++++++--- tests/strategy/test_interface.py | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 62ef4e91b..5f7ef8590 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -5,7 +5,7 @@ This module defines the interface to apply for strategies import logging import warnings from abc import ABC, abstractmethod -from datetime import datetime, timezone, timedelta +from datetime import datetime, timedelta, timezone from enum import Enum from typing import Dict, List, NamedTuple, Optional, Tuple @@ -487,11 +487,15 @@ class IStrategy(ABC): def ignore_expired_candle(self, dataframe: DataFrame, buy: bool): if self.ignore_buying_expired_candle and buy: - current_time = datetime.now(timezone.utc) - timedelta(seconds=self.ignore_buying_expired_candle_after) + current_time = datetime.now(timezone.utc) - timedelta( + seconds=self.ignore_buying_expired_candle_after) candle_time = dataframe['date'].tail(1).iat[0] time_delta = current_time - candle_time if time_delta.total_seconds() > self.ignore_buying_expired_candle_after: - logger.debug('ignoring buy signals because candle exceeded ignore_buying_expired_candle_after of %s seconds', self.ignore_buying_expired_candle_after) + logger.debug( + '''ignoring buy signals because candle exceeded + ignore_buying_expired_candle_after of %s seconds''', + self.ignore_buying_expired_candle_after) return True else: return False diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 330689039..f389be45b 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -120,7 +120,7 @@ def test_ignore_expired_candle(default_conf, ohlcv_history): mocked_history.loc[1, 'buy'] = 1 mocked_history.loc[1, 'sell'] = 1 - assert strategy.ignore_expired_candle(mocked_history, True) == True + assert strategy.ignore_expired_candle(mocked_history, True) is True def test_assert_df_raise(mocker, caplog, ohlcv_history): From 9a93a0876ab7080cfa4eb424fff8a1f7eeb92d29 Mon Sep 17 00:00:00 2001 From: Erwin Hoeckx Date: Tue, 5 Jan 2021 07:32:07 +0100 Subject: [PATCH 03/14] Update interface.py Adjusted comment --- freqtrade/strategy/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 5f7ef8590..4d6e327f3 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -113,7 +113,7 @@ class IStrategy(ABC): # run "populate_indicators" only for new candle process_only_new_candles: bool = False - # Don't analyze too old candles + # Don't buy on expired candles ignore_buying_expired_candle: bool = False # Number of seconds after which the candle will no longer result in a buy ignore_buying_expired_candle_after: int = 0 From 67306d943a0e7b0a86d799cc6022ab117358369b Mon Sep 17 00:00:00 2001 From: Erwin Hoeckx Date: Tue, 5 Jan 2021 07:33:34 +0100 Subject: [PATCH 04/14] Update interface.py Simplified return value, thereby including the situation where the time simply hasn't expired yet --- freqtrade/strategy/interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 4d6e327f3..41fa26ac4 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -497,8 +497,8 @@ class IStrategy(ABC): ignore_buying_expired_candle_after of %s seconds''', self.ignore_buying_expired_candle_after) return True - else: - return False + + return False def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, sell: bool, low: float = None, high: float = None, From c9ed2137bb3b324a40d70110e0d5b959744fac10 Mon Sep 17 00:00:00 2001 From: hoeckxer Date: Tue, 5 Jan 2021 09:07:46 +0100 Subject: [PATCH 05/14] Simplified return statements Signed-off-by: hoeckxer --- freqtrade/strategy/interface.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 41fa26ac4..a1da3681d 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -491,14 +491,9 @@ class IStrategy(ABC): seconds=self.ignore_buying_expired_candle_after) candle_time = dataframe['date'].tail(1).iat[0] time_delta = current_time - candle_time - if time_delta.total_seconds() > self.ignore_buying_expired_candle_after: - logger.debug( - '''ignoring buy signals because candle exceeded - ignore_buying_expired_candle_after of %s seconds''', - self.ignore_buying_expired_candle_after) - return True - - return False + return time_delta.total_seconds() > self.ignore_buying_expired_candle_after + else: + return False def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, sell: bool, low: float = None, high: float = None, From eaaaddac86cf1f480fc34c6a0d8d33f3bffbceea Mon Sep 17 00:00:00 2001 From: Erwin Hoeckx Date: Tue, 5 Jan 2021 11:10:00 +0100 Subject: [PATCH 06/14] Update docs/configuration.md Co-authored-by: Matthias --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index db078cba8..c1c2e65ec 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -677,7 +677,7 @@ freqtrade ## Ignoring expired candles -When working with larger timeframes (for example 1h or more) and using a low `max_trades` value, the last candle can be processed as soon as a trade slot becomes available. When processing the last candle, this can lead to a situation where it may not be desirable to use the buy signal on that candle. For example, when using a condition in your strategy where you use a cross-over, that point may have passed too long ago for you to start a trade on it. +When working with larger timeframes (for example 1h or more) and using a low `max_open_trades` value, the last candle can be processed as soon as a trade slot becomes available. When processing the last candle, this can lead to a situation where it may not be desirable to use the buy signal on that candle. For example, when using a condition in your strategy where you use a cross-over, that point may have passed too long ago for you to start a trade on it. In these situations, you can enable the functionality to ignore candles that are beyond a specified period by setting `ignore_buying_expired_candle` to `True`. After this, you can set `ignore_buying_expired_candle_after` to the number of seconds after which the candle becomes expired. From e3f3f3629828242167aac1d7cd65939fa60fbfd6 Mon Sep 17 00:00:00 2001 From: hoeckxer Date: Tue, 5 Jan 2021 14:49:35 +0100 Subject: [PATCH 07/14] Changes based on review comments --- freqtrade/resolvers/strategy_resolver.py | 2 ++ freqtrade/strategy/interface.py | 10 +++++----- tests/strategy/test_interface.py | 9 +++------ 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 73af00fee..5872d95a6 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -79,6 +79,8 @@ class StrategyResolver(IResolver): ("sell_profit_only", False, 'ask_strategy'), ("ignore_roi_if_buy_signal", False, 'ask_strategy'), ("disable_dataframe_checks", False, None), + ("ignore_buying_expired_candle", None, 'ask_strategy'), + ("ignore_buying_expired_candle_after", 0, 'ask_strategy') ] for attribute, default, subkey in attributes: if subkey: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index a1da3681d..8976b2fd5 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -15,7 +15,7 @@ from pandas import DataFrame from freqtrade.constants import ListPairsWithTimeframes from freqtrade.data.dataprovider import DataProvider from freqtrade.exceptions import OperationalException, StrategyError -from freqtrade.exchange import timeframe_to_minutes +from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds from freqtrade.exchange.exchange import timeframe_to_next_date from freqtrade.persistence import PairLocks, Trade from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper @@ -481,16 +481,16 @@ class IStrategy(ABC): (buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1 logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', latest['date'], pair, str(buy), str(sell)) - if self.ignore_expired_candle(dataframe=dataframe, buy=buy): + timeframe_seconds = timeframe_to_seconds(timeframe) + if self.ignore_expired_candle(latest_date=latest_date, timeframe_seconds=timeframe_seconds, buy=buy): return False, sell return buy, sell - def ignore_expired_candle(self, dataframe: DataFrame, buy: bool): + def ignore_expired_candle(self, latest_date: datetime, timeframe_seconds: int, buy: bool): if self.ignore_buying_expired_candle and buy: current_time = datetime.now(timezone.utc) - timedelta( seconds=self.ignore_buying_expired_candle_after) - candle_time = dataframe['date'].tail(1).iat[0] - time_delta = current_time - candle_time + time_delta = current_time - latest_date + timedelta(seconds=timeframe_seconds) return time_delta.total_seconds() > self.ignore_buying_expired_candle_after else: return False diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index f389be45b..af086b0da 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -112,15 +112,12 @@ def test_ignore_expired_candle(default_conf, ohlcv_history): strategy.ignore_buying_expired_candle = True strategy.ignore_buying_expired_candle_after = 60 - ohlcv_history.loc[-1, 'date'] = arrow.utcnow().shift(minutes=-3) + ohlcv_history.loc[-1, 'date'] = arrow.utcnow() # Take a copy to correctly modify the call mocked_history = ohlcv_history.copy() - mocked_history['sell'] = 0 - mocked_history['buy'] = 0 - mocked_history.loc[1, 'buy'] = 1 - mocked_history.loc[1, 'sell'] = 1 + latest_date = mocked_history['date'].max() - assert strategy.ignore_expired_candle(mocked_history, True) is True + assert strategy.ignore_expired_candle(latest_date=latest_date, timeframe_seconds=300, buy=True) is True def test_assert_df_raise(mocker, caplog, ohlcv_history): From 573de1cf08a323accee427bd168a8bbd0fec7430 Mon Sep 17 00:00:00 2001 From: hoeckxer Date: Tue, 5 Jan 2021 15:30:29 +0100 Subject: [PATCH 08/14] Fixed flake8 warnings --- freqtrade/strategy/interface.py | 5 +++-- tests/strategy/test_interface.py | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 8976b2fd5..2b4a8dd03 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -21,7 +21,6 @@ from freqtrade.persistence import PairLocks, Trade from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.wallets import Wallets - logger = logging.getLogger(__name__) @@ -482,7 +481,9 @@ class IStrategy(ABC): logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', latest['date'], pair, str(buy), str(sell)) timeframe_seconds = timeframe_to_seconds(timeframe) - if self.ignore_expired_candle(latest_date=latest_date, timeframe_seconds=timeframe_seconds, buy=buy): + if self.ignore_expired_candle(latest_date=latest_date, + timeframe_seconds=timeframe_seconds, + buy=buy): return False, sell return buy, sell diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index af086b0da..d7d113a4e 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -117,7 +117,9 @@ def test_ignore_expired_candle(default_conf, ohlcv_history): mocked_history = ohlcv_history.copy() latest_date = mocked_history['date'].max() - assert strategy.ignore_expired_candle(latest_date=latest_date, timeframe_seconds=300, buy=True) is True + assert strategy.ignore_expired_candle(latest_date=latest_date, + timeframe_seconds=300, + buy=True) is True def test_assert_df_raise(mocker, caplog, ohlcv_history): From 65d91a3a58c7807f2e9b18a85d780ba7ea4dc6a4 Mon Sep 17 00:00:00 2001 From: hoeckxer Date: Tue, 5 Jan 2021 15:36:34 +0100 Subject: [PATCH 09/14] isort fix --- freqtrade/strategy/interface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 2b4a8dd03..a18c6c915 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -21,6 +21,7 @@ from freqtrade.persistence import PairLocks, Trade from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.wallets import Wallets + logger = logging.getLogger(__name__) From 5c34140a191a96a82e8ca617bf10d4b16f909b90 Mon Sep 17 00:00:00 2001 From: hoeckxer Date: Tue, 5 Jan 2021 20:59:31 +0100 Subject: [PATCH 10/14] Adjusted documentation to reflect sub-key configuration --- docs/configuration.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index c1c2e65ec..6ae37e7b3 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -74,6 +74,8 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `ask_strategy.use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `true`.*
**Datatype:** Boolean | `ask_strategy.sell_profit_only` | Wait until the bot makes a positive profit before taking a sell decision. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean | `ask_strategy.ignore_roi_if_buy_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean +| `ask_strategy.ignore_buying_expired_candle` | Enables usage of skipping buys on candles that are older than a specified period.
*Defaults to `False`*
**Datatype:** Boolean +| `ask_strategy.ignore_buying_expired_candle_after` | Specifies the number of seconds until a buy signal is no longer used when setting `ignore_buying_expired_candle`.
**Datatype:** Integer | `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict | `order_time_in_force` | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict | `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename).
**Datatype:** String @@ -121,8 +123,6 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `user_data_dir` | Directory containing user data.
*Defaults to `./user_data/`*.
**Datatype:** String | `dataformat_ohlcv` | Data format to use to store historical candle (OHLCV) data.
*Defaults to `json`*.
**Datatype:** String | `dataformat_trades` | Data format to use to store historical trades data.
*Defaults to `jsongz`*.
**Datatype:** String -| `ignore_buying_expired_candle` | Enables usage of skipping buys on candles that are older than a specified period.
*Defaults to `False`*
**Datatype:** Boolean -| `ignore_buying_expired_candle_after` | Specifies the number of seconds until a buy signal is no longer used when setting `ignore_buying_expired_candle`.
**Datatype:** Integer ### Parameters in the strategy @@ -146,8 +146,8 @@ Values set in the configuration file always overwrite values set in the strategy * `use_sell_signal` (ask_strategy) * `sell_profit_only` (ask_strategy) * `ignore_roi_if_buy_signal` (ask_strategy) -* `ignore_buying_expired_candle` -* `ignore_buying_expired_candle_after` +* `ignore_buying_expired_candle` (ask_strategy) +* `ignore_buying_expired_candle_after` (ask_strategy) ### Configuring amount per trade @@ -679,13 +679,17 @@ freqtrade When working with larger timeframes (for example 1h or more) and using a low `max_open_trades` value, the last candle can be processed as soon as a trade slot becomes available. When processing the last candle, this can lead to a situation where it may not be desirable to use the buy signal on that candle. For example, when using a condition in your strategy where you use a cross-over, that point may have passed too long ago for you to start a trade on it. -In these situations, you can enable the functionality to ignore candles that are beyond a specified period by setting `ignore_buying_expired_candle` to `True`. After this, you can set `ignore_buying_expired_candle_after` to the number of seconds after which the candle becomes expired. +In these situations, you can enable the functionality to ignore candles that are beyond a specified period by setting `ask_strategy.ignore_buying_expired_candle` to `true`. After this, you can set `ask_strategy.ignore_buying_expired_candle_after` to the number of seconds after which the candle becomes expired. For example, if your strategy is using a 1h timeframe, and you only want to buy within the first 5 minutes when a new candle comes in, you can add the following configuration to your strategy: -``` json -ignore_buying_expired_candle = True -ignore_buying_expired_candle_after = 300 # 5 minutes +``` jsonc + "ask_strategy":{ + "ignore_buying_expired_candle" = true + "ignore_buying_expired_candle_after" = 300 # 5 minutes + "price_side": "bid", + // ... + }, ``` ## Embedding Strategies From b43ef474ad231c1ac9cf87c760107ba7a5292a04 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 7 Jan 2021 07:51:49 +0100 Subject: [PATCH 11/14] Fix expired candle implementation Improve and simplify test by passing the current time to the function --- freqtrade/strategy/interface.py | 8 ++++---- tests/strategy/test_interface.py | 17 ++++++++++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index a18c6c915..b9d05f64f 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -483,16 +483,16 @@ class IStrategy(ABC): latest['date'], pair, str(buy), str(sell)) timeframe_seconds = timeframe_to_seconds(timeframe) if self.ignore_expired_candle(latest_date=latest_date, + current_time=datetime.now(timezone.utc), timeframe_seconds=timeframe_seconds, buy=buy): return False, sell return buy, sell - def ignore_expired_candle(self, latest_date: datetime, timeframe_seconds: int, buy: bool): + def ignore_expired_candle(self, latest_date: datetime, current_time: datetime, + timeframe_seconds: int, buy: bool): if self.ignore_buying_expired_candle and buy: - current_time = datetime.now(timezone.utc) - timedelta( - seconds=self.ignore_buying_expired_candle_after) - time_delta = current_time - latest_date + timedelta(seconds=timeframe_seconds) + time_delta = current_time - (latest_date + timedelta(seconds=timeframe_seconds)) return time_delta.total_seconds() > self.ignore_buying_expired_candle_after else: return False diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index d7d113a4e..a3969d91b 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -106,21 +106,28 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history): assert log_has('Outdated history for pair xyz. Last tick is 16 minutes old', caplog) -def test_ignore_expired_candle(default_conf, ohlcv_history): +def test_ignore_expired_candle(default_conf): default_conf.update({'strategy': 'DefaultStrategy'}) strategy = StrategyResolver.load_strategy(default_conf) strategy.ignore_buying_expired_candle = True strategy.ignore_buying_expired_candle_after = 60 - ohlcv_history.loc[-1, 'date'] = arrow.utcnow() - # Take a copy to correctly modify the call - mocked_history = ohlcv_history.copy() - latest_date = mocked_history['date'].max() + latest_date = datetime(2020, 12, 30, 7, 0, 0, tzinfo=timezone.utc) + # Add 1 candle length as the "latest date" defines candle open. + current_time = latest_date + timedelta(seconds=80 + 300) assert strategy.ignore_expired_candle(latest_date=latest_date, + current_time=current_time, timeframe_seconds=300, buy=True) is True + current_time = latest_date + timedelta(seconds=30 + 300) + + assert not strategy.ignore_expired_candle(latest_date=latest_date, + current_time=current_time, + timeframe_seconds=300, + buy=True) is True + def test_assert_df_raise(mocker, caplog, ohlcv_history): ohlcv_history.loc[1, 'date'] = arrow.utcnow().shift(minutes=-16) From e328182bd7d3bd40f850bb26179f97e868786ada Mon Sep 17 00:00:00 2001 From: hoeckxer Date: Tue, 12 Jan 2021 07:30:39 +0100 Subject: [PATCH 12/14] Changed workings so it only needs to timing-parameter, instead of also requiring a boolean value --- docs/configuration.md | 7 ++----- freqtrade/resolvers/strategy_resolver.py | 1 - freqtrade/strategy/interface.py | 8 ++++---- tests/strategy/test_interface.py | 1 - 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 6ae37e7b3..93693c919 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -74,8 +74,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `ask_strategy.use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `true`.*
**Datatype:** Boolean | `ask_strategy.sell_profit_only` | Wait until the bot makes a positive profit before taking a sell decision. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean | `ask_strategy.ignore_roi_if_buy_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean -| `ask_strategy.ignore_buying_expired_candle` | Enables usage of skipping buys on candles that are older than a specified period.
*Defaults to `False`*
**Datatype:** Boolean -| `ask_strategy.ignore_buying_expired_candle_after` | Specifies the number of seconds until a buy signal is no longer used when setting `ignore_buying_expired_candle`.
**Datatype:** Integer +| `ask_strategy.ignore_buying_expired_candle_after` | Specifies the number of seconds until a buy signal is no longer used.
**Datatype:** Integer | `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict | `order_time_in_force` | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict | `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename).
**Datatype:** String @@ -146,7 +145,6 @@ Values set in the configuration file always overwrite values set in the strategy * `use_sell_signal` (ask_strategy) * `sell_profit_only` (ask_strategy) * `ignore_roi_if_buy_signal` (ask_strategy) -* `ignore_buying_expired_candle` (ask_strategy) * `ignore_buying_expired_candle_after` (ask_strategy) ### Configuring amount per trade @@ -679,13 +677,12 @@ freqtrade When working with larger timeframes (for example 1h or more) and using a low `max_open_trades` value, the last candle can be processed as soon as a trade slot becomes available. When processing the last candle, this can lead to a situation where it may not be desirable to use the buy signal on that candle. For example, when using a condition in your strategy where you use a cross-over, that point may have passed too long ago for you to start a trade on it. -In these situations, you can enable the functionality to ignore candles that are beyond a specified period by setting `ask_strategy.ignore_buying_expired_candle` to `true`. After this, you can set `ask_strategy.ignore_buying_expired_candle_after` to the number of seconds after which the candle becomes expired. +In these situations, you can enable the functionality to ignore candles that are beyond a specified period by setting `ask_strategy.ignore_buying_expired_candle_after` to a positive number, indicating the number of seconds after which the candle becomes expired. For example, if your strategy is using a 1h timeframe, and you only want to buy within the first 5 minutes when a new candle comes in, you can add the following configuration to your strategy: ``` jsonc "ask_strategy":{ - "ignore_buying_expired_candle" = true "ignore_buying_expired_candle_after" = 300 # 5 minutes "price_side": "bid", // ... diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 5872d95a6..3b7374326 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -79,7 +79,6 @@ class StrategyResolver(IResolver): ("sell_profit_only", False, 'ask_strategy'), ("ignore_roi_if_buy_signal", False, 'ask_strategy'), ("disable_dataframe_checks", False, None), - ("ignore_buying_expired_candle", None, 'ask_strategy'), ("ignore_buying_expired_candle_after", 0, 'ask_strategy') ] for attribute, default, subkey in attributes: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index b9d05f64f..57dcdeb3c 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -113,9 +113,7 @@ class IStrategy(ABC): # run "populate_indicators" only for new candle process_only_new_candles: bool = False - # Don't buy on expired candles - ignore_buying_expired_candle: bool = False - # Number of seconds after which the candle will no longer result in a buy + # Number of seconds after which the candle will no longer result in a buy on expired candles ignore_buying_expired_candle_after: int = 0 # Disable checking the dataframe (converts the error into a warning message) @@ -491,7 +489,9 @@ class IStrategy(ABC): def ignore_expired_candle(self, latest_date: datetime, current_time: datetime, timeframe_seconds: int, buy: bool): - if self.ignore_buying_expired_candle and buy: + if self.ignore_buying_expired_candle_after \ + and self.ignore_buying_expired_candle_after > 0\ + and buy: time_delta = current_time - (latest_date + timedelta(seconds=timeframe_seconds)) return time_delta.total_seconds() > self.ignore_buying_expired_candle_after else: diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index a3969d91b..f158a1518 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -109,7 +109,6 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history): def test_ignore_expired_candle(default_conf): default_conf.update({'strategy': 'DefaultStrategy'}) strategy = StrategyResolver.load_strategy(default_conf) - strategy.ignore_buying_expired_candle = True strategy.ignore_buying_expired_candle_after = 60 latest_date = datetime(2020, 12, 30, 7, 0, 0, tzinfo=timezone.utc) From 71f45021b9083862b6d06fe7adf425a7de73ff49 Mon Sep 17 00:00:00 2001 From: hoeckxer Date: Tue, 12 Jan 2021 07:35:30 +0100 Subject: [PATCH 13/14] Removed redundant statement --- freqtrade/strategy/interface.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 57dcdeb3c..40debe78f 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -490,7 +490,6 @@ class IStrategy(ABC): def ignore_expired_candle(self, latest_date: datetime, current_time: datetime, timeframe_seconds: int, buy: bool): if self.ignore_buying_expired_candle_after \ - and self.ignore_buying_expired_candle_after > 0\ and buy: time_delta = current_time - (latest_date + timedelta(seconds=timeframe_seconds)) return time_delta.total_seconds() > self.ignore_buying_expired_candle_after From 1f6a71fdd96609a78ee9e75e232581e1d85f3776 Mon Sep 17 00:00:00 2001 From: hoeckxer Date: Tue, 12 Jan 2021 08:24:11 +0100 Subject: [PATCH 14/14] Reformat code on new version --- freqtrade/strategy/interface.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 40debe78f..2f1326f48 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -489,8 +489,7 @@ class IStrategy(ABC): def ignore_expired_candle(self, latest_date: datetime, current_time: datetime, timeframe_seconds: int, buy: bool): - if self.ignore_buying_expired_candle_after \ - and buy: + if self.ignore_buying_expired_candle_after and buy: time_delta = current_time - (latest_date + timedelta(seconds=timeframe_seconds)) return time_delta.total_seconds() > self.ignore_buying_expired_candle_after else: