First commit about ignoring expired candle

Signed-off-by: hoeckxer <hawkeyenl@yahoo.com>
This commit is contained in:
hoeckxer 2021-01-04 20:49:24 +01:00
parent cce4d7e42c
commit 614a996597
3 changed files with 54 additions and 1 deletions

View File

@ -121,6 +121,8 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `user_data_dir` | Directory containing user data. <br> *Defaults to `./user_data/`*. <br> **Datatype:** String | `user_data_dir` | Directory containing user data. <br> *Defaults to `./user_data/`*. <br> **Datatype:** String
| `dataformat_ohlcv` | Data format to use to store historical candle (OHLCV) data. <br> *Defaults to `json`*. <br> **Datatype:** String | `dataformat_ohlcv` | Data format to use to store historical candle (OHLCV) data. <br> *Defaults to `json`*. <br> **Datatype:** String
| `dataformat_trades` | Data format to use to store historical trades data. <br> *Defaults to `jsongz`*. <br> **Datatype:** String | `dataformat_trades` | Data format to use to store historical trades data. <br> *Defaults to `jsongz`*. <br> **Datatype:** String
| `ignore_buying_expired_candle` | Enables usage of skipping buys on candles that are older than a specified period. <br>*Defaults to `False`* <br> **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`. <br> **Datatype:** Integer
### Parameters in the strategy ### 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) * `use_sell_signal` (ask_strategy)
* `sell_profit_only` (ask_strategy) * `sell_profit_only` (ask_strategy)
* `ignore_roi_if_buy_signal` (ask_strategy) * `ignore_roi_if_buy_signal` (ask_strategy)
* `ignore_buying_expired_candle`
* `ignore_buying_expired_candle_after`
### Configuring amount per trade ### Configuring amount per trade
@ -671,6 +675,19 @@ export HTTPS_PROXY="http://addr:port"
freqtrade 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 ## Embedding Strategies
Freqtrade provides you with with an easy way to embed the strategy into your configuration file. Freqtrade provides you with with an easy way to embed the strategy into your configuration file.

View File

@ -5,7 +5,7 @@ This module defines the interface to apply for strategies
import logging import logging
import warnings import warnings
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from datetime import datetime, timezone from datetime import datetime, timezone, timedelta
from enum import Enum from enum import Enum
from typing import Dict, List, NamedTuple, Optional, Tuple from typing import Dict, List, NamedTuple, Optional, Tuple
@ -113,6 +113,11 @@ class IStrategy(ABC):
# run "populate_indicators" only for new candle # run "populate_indicators" only for new candle
process_only_new_candles: bool = False 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 checking the dataframe (converts the error into a warning message)
disable_dataframe_checks: bool = False disable_dataframe_checks: bool = False
@ -476,8 +481,21 @@ class IStrategy(ABC):
(buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1 (buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1
logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', logger.debug('trigger: %s (pair=%s) buy=%s sell=%s',
latest['date'], pair, str(buy), str(sell)) latest['date'], pair, str(buy), str(sell))
if self.ignore_expired_candle(dataframe=dataframe, buy=buy):
return False, sell
return buy, 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, def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool,
sell: bool, low: float = None, high: float = None, sell: bool, low: float = None, high: float = None,
force_stoploss: float = 0) -> SellCheckTuple: force_stoploss: float = 0) -> SellCheckTuple:
@ -672,6 +690,7 @@ class IStrategy(ABC):
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
logger.debug(f"Populating buy signals for pair {metadata.get('pair')}.") logger.debug(f"Populating buy signals for pair {metadata.get('pair')}.")
if self._buy_fun_len == 2: if self._buy_fun_len == 2:
warnings.warn("deprecated - check out the Sample strategy to see " warnings.warn("deprecated - check out the Sample strategy to see "
"the current function headers!", DeprecationWarning) "the current function headers!", DeprecationWarning)

View File

@ -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) 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): def test_assert_df_raise(mocker, caplog, ohlcv_history):
ohlcv_history.loc[1, 'date'] = arrow.utcnow().shift(minutes=-16) ohlcv_history.loc[1, 'date'] = arrow.utcnow().shift(minutes=-16)
# Take a copy to correctly modify the call # Take a copy to correctly modify the call