Introduce .analyze() function for Strategy
Fixing a few tests along the way
This commit is contained in:
parent
95f3ac08d4
commit
273aaaff12
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user