Introduce .analyze() function for Strategy

Fixing a few tests along the way
This commit is contained in:
Matthias 2020-06-13 18:06:52 +02:00
parent 95f3ac08d4
commit 273aaaff12
4 changed files with 47 additions and 25 deletions

View File

@ -151,6 +151,8 @@ class FreqtradeBot:
self.dataprovider.refresh(self.pairlists.create_pair_list(self.active_pair_whitelist), self.dataprovider.refresh(self.pairlists.create_pair_list(self.active_pair_whitelist),
self.strategy.informative_pairs()) self.strategy.informative_pairs())
self.strategy.analyze(self.active_pair_whitelist)
with self._sell_lock: with self._sell_lock:
# Check and handle any timed out open orders # Check and handle any timed out open orders
self.check_handle_timedout() self.check_handle_timedout()
@ -420,9 +422,7 @@ class FreqtradeBot:
return False return False
# running get_signal on historical data fetched # running get_signal on historical data fetched
(buy, sell) = self.strategy.get_signal( (buy, sell) = self.strategy.get_signal(pair, self.strategy.timeframe)
pair, self.strategy.timeframe,
self.dataprovider.ohlcv(pair, self.strategy.timeframe))
if buy and not sell: if buy and not sell:
stake_amount = self.get_trade_stake_amount(pair) stake_amount = self.get_trade_stake_amount(pair)
@ -697,9 +697,7 @@ class FreqtradeBot:
if (config_ask_strategy.get('use_sell_signal', True) or if (config_ask_strategy.get('use_sell_signal', True) or
config_ask_strategy.get('ignore_roi_if_buy_signal', False)): config_ask_strategy.get('ignore_roi_if_buy_signal', False)):
(buy, sell) = self.strategy.get_signal( (buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.timeframe)
trade.pair, self.strategy.timeframe,
self.dataprovider.ohlcv(trade.pair, self.strategy.timeframe))
if config_ask_strategy.get('use_order_book', False): if config_ask_strategy.get('use_order_book', False):
order_book_min = config_ask_strategy.get('order_book_min', 1) order_book_min = config_ask_strategy.get('order_book_min', 1)

View File

@ -7,20 +7,19 @@ import warnings
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from datetime import datetime, timezone from datetime import datetime, timezone
from enum import Enum from enum import Enum
from typing import Dict, NamedTuple, Optional, Tuple from typing import Dict, List, NamedTuple, Optional, Tuple
import arrow import arrow
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import ListPairsWithTimeframes
from freqtrade.data.dataprovider import DataProvider from freqtrade.data.dataprovider import DataProvider
from freqtrade.exceptions import StrategyError from freqtrade.exceptions import StrategyError
from freqtrade.exchange import timeframe_to_minutes from freqtrade.exchange import timeframe_to_minutes
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.constants import ListPairsWithTimeframes
from freqtrade.wallets import Wallets from freqtrade.wallets import Wallets
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -289,6 +288,38 @@ class IStrategy(ABC):
return dataframe return dataframe
def analyze_pair(self, pair: str):
"""
Fetch data for this pair from dataprovider and analyze.
Stores the dataframe into the dataprovider.
The analyzed dataframe is then accessible via `dp.get_analyzed_dataframe()`.
:param pair: Pair to analyze.
"""
dataframe = self.dp.ohlcv(pair, self.timeframe)
if not isinstance(dataframe, DataFrame) or dataframe.empty:
logger.warning('Empty candle (OHLCV) data for pair %s', pair)
return
try:
df_len, df_close, df_date = self.preserve_df(dataframe)
dataframe = strategy_safe_wrapper(
self._analyze_ticker_internal, message=""
)(dataframe, {'pair': pair})
self.assert_df(dataframe, df_len, df_close, df_date)
except StrategyError as error:
logger.warning(f"Unable to analyze candle (OHLCV) data for pair {pair}: {error}")
return
if dataframe.empty:
logger.warning('Empty dataframe for pair %s', pair)
return
def analyze(self, pairs: List[str]):
for pair in pairs:
self.analyze_pair(pair)
@staticmethod @staticmethod
def preserve_df(dataframe: DataFrame) -> Tuple[int, float, datetime]: def preserve_df(dataframe: DataFrame) -> Tuple[int, float, datetime]:
""" keep some data for dataframes """ """ keep some data for dataframes """
@ -309,30 +340,22 @@ class IStrategy(ABC):
else: else:
raise StrategyError(f"Dataframe returned from strategy has mismatching {message}.") raise StrategyError(f"Dataframe returned from strategy has mismatching {message}.")
def get_signal(self, pair: str, interval: str, dataframe: DataFrame) -> Tuple[bool, bool]: def get_signal(self, pair: str, timeframe: str) -> Tuple[bool, bool]:
""" """
Calculates current signal based several technical analysis indicators Calculates current signal based several technical analysis indicators
Used by Bot to get the latest signal Used by Bot to get the latest signal
:param pair: pair in format ANT/BTC :param pair: pair in format ANT/BTC
:param interval: Interval to use (in min) :param timeframe: timeframe to use
:param dataframe: Dataframe to analyze :param dataframe: Dataframe to analyze
:return: (Buy, Sell) A bool-tuple indicating buy/sell signal :return: (Buy, Sell) A bool-tuple indicating buy/sell signal
""" """
dataframe, _ = self.dp.get_analyzed_dataframe(pair, timeframe)
if not isinstance(dataframe, DataFrame) or dataframe.empty: if not isinstance(dataframe, DataFrame) or dataframe.empty:
logger.warning('Empty candle (OHLCV) data for pair %s', pair) logger.warning('Empty candle (OHLCV) data for pair %s', pair)
return False, False return False, False
try:
df_len, df_close, df_date = self.preserve_df(dataframe)
dataframe = strategy_safe_wrapper(
self._analyze_ticker_internal, message=""
)(dataframe, {'pair': pair})
self.assert_df(dataframe, df_len, df_close, df_date)
except StrategyError as error:
logger.warning(f"Unable to analyze candle (OHLCV) data for pair {pair}: {error}")
return False, False
if dataframe.empty: if dataframe.empty:
logger.warning('Empty dataframe for pair %s', pair) logger.warning('Empty dataframe for pair %s', pair)
return False, False return False, False
@ -343,9 +366,9 @@ class IStrategy(ABC):
latest_date = arrow.get(latest_date) latest_date = arrow.get(latest_date)
# Check if dataframe is out of date # Check if dataframe is out of date
interval_minutes = timeframe_to_minutes(interval) timeframe_minutes = timeframe_to_minutes(timeframe)
offset = self.config.get('exchange', {}).get('outdated_offset', 5) offset = self.config.get('exchange', {}).get('outdated_offset', 5)
if latest_date < (arrow.utcnow().shift(minutes=-(interval_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, pair,

View File

@ -163,7 +163,7 @@ def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False)) -> None:
:param value: which value IStrategy.get_signal() must return :param value: which value IStrategy.get_signal() must return
:return: None :return: None
""" """
freqtrade.strategy.get_signal = lambda e, s, t: value freqtrade.strategy.get_signal = lambda e, s: value
freqtrade.exchange.refresh_latest_ohlcv = lambda p: None freqtrade.exchange.refresh_latest_ohlcv = lambda p: None

View File

@ -912,6 +912,7 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None:
refresh_latest_ohlcv=refresh_mock, refresh_latest_ohlcv=refresh_mock,
) )
inf_pairs = MagicMock(return_value=[("BTC/ETH", '1m'), ("ETH/USDT", "1h")]) inf_pairs = MagicMock(return_value=[("BTC/ETH", '1m'), ("ETH/USDT", "1h")])
mocker.patch('freqtrade.strategy.interface.IStrategy.get_signal', return_value=(False, False))
mocker.patch('time.sleep', return_value=None) mocker.patch('time.sleep', return_value=None)
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)