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