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:
commit
21f4aba4e3
@ -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:
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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'})
|
||||||
|
Loading…
Reference in New Issue
Block a user