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