stable/freqtrade/data/dataprovider.py

198 lines
7.6 KiB
Python
Raw Normal View History

2018-11-30 19:42:16 +00:00
"""
Dataprovider
Responsible to provide data to the bot
including ticker and orderbook data, live and historical candle (OHLCV) data
2018-11-30 19:42:16 +00:00
Common Interface for bot and strategy to access data.
"""
import logging
2020-06-14 09:51:20 +00:00
from datetime import datetime, timezone
2020-06-12 12:02:21 +00:00
from typing import Any, Dict, List, Optional, Tuple
2018-11-30 19:42:16 +00:00
2018-12-02 08:16:35 +00:00
from pandas import DataFrame
2020-06-12 12:12:33 +00:00
from freqtrade.constants import ListPairsWithTimeframes, PairWithTimeframe
2018-12-17 05:52:13 +00:00
from freqtrade.data.history import load_pair_history
2020-06-28 14:01:40 +00:00
from freqtrade.exceptions import ExchangeError, OperationalException
from freqtrade.exchange import Exchange
from freqtrade.state import RunMode
2020-09-28 17:39:41 +00:00
2018-11-30 19:42:16 +00:00
logger = logging.getLogger(__name__)
2021-05-05 18:08:31 +00:00
NO_EXCHANGE_EXCEPTION = 'Exchange is not available to DataProvider.'
2018-11-30 19:42:16 +00:00
2019-09-12 09:13:20 +00:00
class DataProvider:
2018-11-30 19:42:16 +00:00
def __init__(self, config: dict, exchange: Optional[Exchange], pairlists=None) -> None:
2018-12-02 08:16:35 +00:00
self._config = config
self._exchange = exchange
self._pairlists = pairlists
2020-06-15 12:08:57 +00:00
self.__cached_pairs: Dict[PairWithTimeframe, Tuple[DataFrame, datetime]] = {}
2020-06-12 12:02:21 +00:00
def _set_cached_df(self, pair: str, timeframe: str, dataframe: DataFrame) -> None:
"""
Store cached Dataframe.
Using private method as this should never be used by a user
(but the class is exposed via `self.dp` to the strategy)
:param pair: pair to get the data for
:param timeframe: Timeframe to get data for
:param dataframe: analyzed dataframe
"""
self.__cached_pairs[(pair, timeframe)] = (dataframe, datetime.now(timezone.utc))
2018-11-30 19:42:16 +00:00
def add_pairlisthandler(self, pairlists) -> None:
"""
Allow adding pairlisthandler after initialization
"""
self._pairlists = pairlists
def historic_ohlcv(self, pair: str, timeframe: str = None) -> DataFrame:
2018-11-30 19:42:16 +00:00
"""
Get stored historical candle (OHLCV) data
:param pair: pair to get the data for
2019-11-13 10:28:26 +00:00
:param timeframe: timeframe to get data for
2018-11-30 19:42:16 +00:00
"""
2018-12-17 05:52:13 +00:00
return load_pair_history(pair=pair,
timeframe=timeframe or self._config['timeframe'],
datadir=self._config['datadir'],
data_format=self._config.get('dataformat_ohlcv', 'json')
2018-12-17 05:52:13 +00:00
)
2018-11-30 19:42:16 +00:00
def get_pair_dataframe(self, pair: str, timeframe: str = None) -> DataFrame:
2019-08-17 08:43:36 +00:00
"""
Return pair candle (OHLCV) data, either live or cached historical -- depending
2019-08-17 08:43:36 +00:00
on the runmode.
:param pair: pair to get the data for
2019-11-13 10:28:26 +00:00
:param timeframe: timeframe to get data for
:return: Dataframe for this pair
2019-08-17 08:43:36 +00:00
"""
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
# Get live OHLCV data.
data = self.ohlcv(pair=pair, timeframe=timeframe)
2019-08-17 08:43:36 +00:00
else:
# Get historical OHLCV data (cached on disk).
data = self.historic_ohlcv(pair=pair, timeframe=timeframe)
2019-08-17 08:43:36 +00:00
if len(data) == 0:
logger.warning(f"No data found for ({pair}, {timeframe}).")
2019-08-17 08:43:36 +00:00
return data
2020-06-15 12:08:57 +00:00
def get_analyzed_dataframe(self, pair: str, timeframe: str) -> Tuple[DataFrame, datetime]:
2020-06-12 12:02:21 +00:00
"""
:param pair: pair to get the data for
:param timeframe: timeframe to get data for
2020-06-12 12:12:33 +00:00
:return: Tuple of (Analyzed Dataframe, lastrefreshed) for the requested pair / timeframe
2020-06-14 09:51:20 +00:00
combination.
Returns empty dataframe and Epoch 0 (1970-01-01) if no dataframe was cached.
2020-06-12 12:02:21 +00:00
"""
2020-06-12 12:12:33 +00:00
if (pair, timeframe) in self.__cached_pairs:
return self.__cached_pairs[(pair, timeframe)]
2020-06-12 12:02:21 +00:00
else:
2020-06-14 09:51:20 +00:00
return (DataFrame(), datetime.fromtimestamp(0, tz=timezone.utc))
2020-06-12 12:02:21 +00:00
2021-05-03 06:47:58 +00:00
@property
def runmode(self) -> RunMode:
"""
Get runmode of the bot
can be "live", "dry-run", "backtest", "edgecli", "hyperopt" or "other".
"""
return RunMode(self._config.get('runmode', RunMode.OTHER))
def current_whitelist(self) -> List[str]:
"""
fetch latest available whitelist.
Useful when you have a large whitelist and need to call each pair as an informative pair.
As available pairs does not show whitelist until after informative pairs have been cached.
:return: list of pairs in whitelist
"""
if self._pairlists:
return self._pairlists.whitelist.copy()
else:
raise OperationalException("Dataprovider was not initialized with a pairlist provider.")
def clear_cache(self):
"""
Clear pair dataframe cache.
"""
self.__cached_pairs = {}
# Exchange functions
def refresh(self,
pairlist: ListPairsWithTimeframes,
helping_pairs: ListPairsWithTimeframes = None) -> None:
"""
Refresh data, called with each cycle
"""
if self._exchange is None:
2021-05-05 18:08:31 +00:00
raise OperationalException(NO_EXCHANGE_EXCEPTION)
2021-05-03 06:47:58 +00:00
if helping_pairs:
self._exchange.refresh_latest_ohlcv(pairlist + helping_pairs)
else:
self._exchange.refresh_latest_ohlcv(pairlist)
@property
def available_pairs(self) -> ListPairsWithTimeframes:
"""
Return a list of tuples containing (pair, timeframe) for which data is currently cached.
Should be whitelist + open trades.
"""
if self._exchange is None:
2021-05-05 18:08:31 +00:00
raise OperationalException(NO_EXCHANGE_EXCEPTION)
2021-05-03 06:47:58 +00:00
return list(self._exchange._klines.keys())
def ohlcv(self, pair: str, timeframe: str = None, copy: bool = True) -> DataFrame:
"""
Get candle (OHLCV) data for the given pair as DataFrame
Please use the `available_pairs` method to verify which pairs are currently cached.
:param pair: pair to get the data for
:param timeframe: Timeframe to get data for
:param copy: copy dataframe before returning if True.
Use False only for read-only operations (where the dataframe is not modified)
"""
2021-05-05 18:08:31 +00:00
if self._exchange is None:
raise OperationalException(NO_EXCHANGE_EXCEPTION)
2021-05-03 06:47:58 +00:00
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
return self._exchange.klines((pair, timeframe or self._config['timeframe']),
copy=copy)
else:
return DataFrame()
def market(self, pair: str) -> Optional[Dict[str, Any]]:
2019-10-02 23:58:45 +00:00
"""
Return market data for the pair
:param pair: Pair to get the data for
:return: Market data dict from ccxt or None if market info is not available for the pair
"""
2021-05-03 06:47:58 +00:00
if self._exchange is None:
2021-05-05 18:08:31 +00:00
raise OperationalException(NO_EXCHANGE_EXCEPTION)
2019-10-02 23:58:45 +00:00
return self._exchange.markets.get(pair)
2018-12-02 08:16:35 +00:00
def ticker(self, pair: str):
"""
Return last ticker data from exchange
:param pair: Pair to get the data for
:return: Ticker dict from exchange or empty dict if ticker is not available for the pair
2018-12-02 08:16:35 +00:00
"""
2021-05-03 06:47:58 +00:00
if self._exchange is None:
2021-05-05 18:08:31 +00:00
raise OperationalException(NO_EXCHANGE_EXCEPTION)
try:
return self._exchange.fetch_ticker(pair)
2020-06-28 14:01:40 +00:00
except ExchangeError:
return {}
2018-11-30 19:42:16 +00:00
def orderbook(self, pair: str, maximum: int) -> Dict[str, List]:
2018-12-02 08:16:35 +00:00
"""
Fetch latest l2 orderbook data
Warning: Does a network request - so use with common sense.
:param pair: pair to get the data for
:param maximum: Maximum number of orderbook entries to query
:return: dict including bids/asks with a total of `maximum` entries.
2018-12-02 08:16:35 +00:00
"""
2021-05-03 06:47:58 +00:00
if self._exchange is None:
2021-05-05 18:08:31 +00:00
raise OperationalException(NO_EXCHANGE_EXCEPTION)
return self._exchange.fetch_l2_order_book(pair, maximum)