Merge pull request #4147 from hoeckxer/ignore_expired_candle
Ignoring candles that have expired within timeframe
This commit is contained in:
commit
6007d5182a
@ -75,6 +75,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
|||||||
| `ask_strategy.sell_profit_only` | Wait until the bot reaches `ask_strategy.sell_profit_offset` before taking a sell decision. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
| `ask_strategy.sell_profit_only` | Wait until the bot reaches `ask_strategy.sell_profit_offset` before taking a sell decision. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||||
| `ask_strategy.sell_profit_offset` | Sell-signal is only active above this value. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0`.* <br> **Datatype:** Float (as ratio)
|
| `ask_strategy.sell_profit_offset` | Sell-signal is only active above this value. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0`.* <br> **Datatype:** Float (as ratio)
|
||||||
| `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). <br>*Defaults to `false`.* <br> **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). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||||
|
| `ask_strategy.ignore_buying_expired_candle_after` | Specifies the number of seconds until a buy signal is no longer used. <br> **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).<br> **Datatype:** Dict
|
| `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).<br> **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). <br> **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). <br> **Datatype:** Dict
|
||||||
| `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). <br> **Datatype:** String
|
| `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). <br> **Datatype:** String
|
||||||
@ -146,6 +147,7 @@ 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_after` (ask_strategy)
|
||||||
|
|
||||||
### Configuring amount per trade
|
### Configuring amount per trade
|
||||||
|
|
||||||
@ -673,6 +675,22 @@ 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_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_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_after" = 300 # 5 minutes
|
||||||
|
"price_side": "bid",
|
||||||
|
// ...
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
## 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.
|
||||||
|
@ -81,6 +81,7 @@ class StrategyResolver(IResolver):
|
|||||||
("ignore_roi_if_buy_signal", False, 'ask_strategy'),
|
("ignore_roi_if_buy_signal", False, 'ask_strategy'),
|
||||||
("sell_profit_offset", 0.0, 'ask_strategy'),
|
("sell_profit_offset", 0.0, 'ask_strategy'),
|
||||||
("disable_dataframe_checks", False, None),
|
("disable_dataframe_checks", False, None),
|
||||||
|
("ignore_buying_expired_candle_after", 0, 'ask_strategy')
|
||||||
]
|
]
|
||||||
for attribute, default, subkey in attributes:
|
for attribute, default, subkey in attributes:
|
||||||
if subkey:
|
if subkey:
|
||||||
|
@ -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, timedelta, timezone
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Dict, List, NamedTuple, Optional, Tuple
|
from typing import Dict, List, NamedTuple, Optional, Tuple
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ from pandas import DataFrame
|
|||||||
from freqtrade.constants import ListPairsWithTimeframes
|
from freqtrade.constants import ListPairsWithTimeframes
|
||||||
from freqtrade.data.dataprovider import DataProvider
|
from freqtrade.data.dataprovider import DataProvider
|
||||||
from freqtrade.exceptions import OperationalException, StrategyError
|
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.exchange.exchange import timeframe_to_next_date
|
||||||
from freqtrade.persistence import PairLocks, Trade
|
from freqtrade.persistence import PairLocks, Trade
|
||||||
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
||||||
@ -113,6 +113,9 @@ 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
|
||||||
|
|
||||||
|
# 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)
|
# Disable checking the dataframe (converts the error into a warning message)
|
||||||
disable_dataframe_checks: bool = False
|
disable_dataframe_checks: bool = False
|
||||||
|
|
||||||
@ -479,8 +482,22 @@ 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))
|
||||||
|
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
|
return buy, sell
|
||||||
|
|
||||||
|
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:
|
||||||
|
time_delta = current_time - (latest_date + timedelta(seconds=timeframe_seconds))
|
||||||
|
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,
|
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:
|
||||||
@ -676,6 +693,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)
|
||||||
|
@ -106,6 +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)
|
assert log_has('Outdated history for pair xyz. Last tick is 16 minutes old', caplog)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ignore_expired_candle(default_conf):
|
||||||
|
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||||
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
strategy.ignore_buying_expired_candle_after = 60
|
||||||
|
|
||||||
|
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):
|
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
|
||||||
|
Loading…
Reference in New Issue
Block a user