Merge pull request #3055 from yazeed/verify_date_on_new_candle_on_get_signal

Verify date on last candle before producing signal
This commit is contained in:
Matthias 2020-08-25 20:22:48 +02:00 committed by GitHub
commit 21f4aba4e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 55 additions and 15 deletions

View File

@ -433,7 +433,9 @@ class FreqtradeBot:
""" """
logger.debug(f"create_trade for pair {pair}") logger.debug(f"create_trade for pair {pair}")
if self.strategy.is_pair_locked(pair): analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(pair, self.strategy.timeframe)
if self.strategy.is_pair_locked(
pair, analyzed_df.iloc[-1]['date'] if len(analyzed_df) > 0 else None):
logger.info(f"Pair {pair} is currently locked.") logger.info(f"Pair {pair} is currently locked.")
return False return False
@ -444,7 +446,6 @@ class FreqtradeBot:
return False return False
# running get_signal on historical data fetched # running get_signal on historical data fetched
analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(pair, self.strategy.timeframe)
(buy, sell) = self.strategy.get_signal(pair, self.strategy.timeframe, analyzed_df) (buy, sell) = self.strategy.get_signal(pair, self.strategy.timeframe, analyzed_df)
if buy and not sell: if buy and not sell:

View File

@ -14,8 +14,9 @@ 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 StrategyError, OperationalException from freqtrade.exceptions import OperationalException, StrategyError
from freqtrade.exchange import timeframe_to_minutes from freqtrade.exchange import timeframe_to_minutes
from freqtrade.exchange.exchange import timeframe_to_next_date
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from freqtrade.wallets import Wallets from freqtrade.wallets import Wallets
@ -297,13 +298,25 @@ class IStrategy(ABC):
if pair in self._pair_locked_until: if pair in self._pair_locked_until:
del self._pair_locked_until[pair] del self._pair_locked_until[pair]
def is_pair_locked(self, pair: str) -> bool: def is_pair_locked(self, pair: str, candle_date: datetime = None) -> bool:
""" """
Checks if a pair is currently locked Checks if a pair is currently locked
The 2nd, optional parameter ensures that locks are applied until the new candle arrives,
and not stop at 14:00:00 - while the next candle arrives at 14:00:02 leaving a gap
of 2 seconds for a buy to happen on an old signal.
:param: pair: "Pair to check"
:param candle_date: Date of the last candle. Optional, defaults to current date
:returns: locking state of the pair in question.
""" """
if pair not in self._pair_locked_until: if pair not in self._pair_locked_until:
return False return False
return self._pair_locked_until[pair] >= datetime.now(timezone.utc) if not candle_date:
return self._pair_locked_until[pair] >= datetime.now(timezone.utc)
else:
# Locking should happen until a new candle arrives
lock_time = timeframe_to_next_date(self.timeframe, candle_date)
# lock_time = candle_date + timedelta(minutes=timeframe_to_minutes(self.timeframe))
return self._pair_locked_until[pair] > lock_time
def analyze_ticker(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def analyze_ticker(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
@ -434,7 +447,7 @@ class IStrategy(ABC):
if latest_date < (arrow.utcnow().shift(minutes=-(timeframe_minutes * 2 + offset))): if latest_date < (arrow.utcnow().shift(minutes=-(timeframe_minutes * 2 + offset))):
logger.warning( logger.warning(
'Outdated history for pair %s. Last tick is %s minutes old', 'Outdated history for pair %s. Last tick is %s minutes old',
pair, (arrow.utcnow() - latest_date).seconds // 60 pair, int((arrow.utcnow() - latest_date).total_seconds() // 60)
) )
return False, False return False, False

View File

@ -1,6 +1,7 @@
# pragma pylint: disable=missing-docstring, C0103 # pragma pylint: disable=missing-docstring, C0103
import logging import logging
from datetime import datetime, timedelta, timezone
from unittest.mock import MagicMock from unittest.mock import MagicMock
import arrow import arrow
@ -8,12 +9,12 @@ import pytest
from pandas import DataFrame from pandas import DataFrame
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.data.dataprovider import DataProvider
from freqtrade.data.history import load_data from freqtrade.data.history import load_data
from freqtrade.exceptions import StrategyError from freqtrade.exceptions import StrategyError
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.resolvers import StrategyResolver from freqtrade.resolvers import StrategyResolver
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from freqtrade.data.dataprovider import DataProvider
from tests.conftest import log_has, log_has_re from tests.conftest import log_has, log_has_re
from .strats.default_strategy import DefaultStrategy from .strats.default_strategy import DefaultStrategy
@ -261,14 +262,14 @@ def test_min_roi_reached3(default_conf, fee) -> None:
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
strategy.minimal_roi = min_roi strategy.minimal_roi = min_roi
trade = Trade( trade = Trade(
pair='ETH/BTC', pair='ETH/BTC',
stake_amount=0.001, stake_amount=0.001,
amount=5, amount=5,
open_date=arrow.utcnow().shift(hours=-1).datetime, open_date=arrow.utcnow().shift(hours=-1).datetime,
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
exchange='bittrex', exchange='bittrex',
open_rate=1, open_rate=1,
) )
assert not strategy.min_roi_reached(trade, 0.02, arrow.utcnow().shift(minutes=-56).datetime) assert not strategy.min_roi_reached(trade, 0.02, arrow.utcnow().shift(minutes=-56).datetime)
@ -387,6 +388,31 @@ def test_is_pair_locked(default_conf):
strategy.unlock_pair(pair) strategy.unlock_pair(pair)
assert not strategy.is_pair_locked(pair) assert not strategy.is_pair_locked(pair)
pair = 'BTC/USDT'
# Lock until 14:30
lock_time = datetime(2020, 5, 1, 14, 30, 0, tzinfo=timezone.utc)
strategy.lock_pair(pair, lock_time)
# Lock is in the past ...
assert not strategy.is_pair_locked(pair)
# latest candle is from 14:20, lock goes to 14:30
assert strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-10))
assert strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-50))
# latest candle is from 14:25 (lock should be lifted)
# Since this is the "new candle" available at 14:30
assert not strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-4))
# Should not be locked after time expired
assert not strategy.is_pair_locked(pair, lock_time + timedelta(minutes=10))
# Change timeframe to 15m
strategy.timeframe = '15m'
# Candle from 14:14 - lock goes until 14:30
assert strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-16))
assert strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-15, seconds=-2))
# Candle from 14:15 - lock goes until 14:30
assert not strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-15))
def test_is_informative_pairs_callback(default_conf): def test_is_informative_pairs_callback(default_conf):
default_conf.update({'strategy': 'TestStrategyLegacy'}) default_conf.update({'strategy': 'TestStrategyLegacy'})