2018-11-30 19:42:16 +00:00
|
|
|
"""
|
|
|
|
Dataprovider
|
|
|
|
Responsible to provide data to the bot
|
2020-03-08 10:35:31 +00:00
|
|
|
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-12 12:12:33 +00:00
|
|
|
from datetime import datetime
|
2020-06-12 12:02:21 +00:00
|
|
|
from typing import Any, Dict, List, Optional, Tuple
|
2018-11-30 19:42:16 +00:00
|
|
|
|
2020-06-12 12:02:21 +00:00
|
|
|
from arrow import Arrow
|
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-05-14 10:36:48 +00:00
|
|
|
from freqtrade.exceptions import DependencyException, OperationalException
|
2018-12-25 13:23:59 +00:00
|
|
|
from freqtrade.exchange import Exchange
|
|
|
|
from freqtrade.state import RunMode
|
2020-05-16 08:09:50 +00:00
|
|
|
|
2018-11-30 19:42:16 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2019-09-12 09:13:20 +00:00
|
|
|
class DataProvider:
|
2018-11-30 19:42:16 +00:00
|
|
|
|
2020-05-11 15:32:28 +00:00
|
|
|
def __init__(self, config: dict, exchange: Exchange, pairlists=None) -> None:
|
2018-12-02 08:16:35 +00:00
|
|
|
self._config = config
|
|
|
|
self._exchange = exchange
|
2020-05-11 15:32:28 +00:00
|
|
|
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
|
|
|
|
"""
|
2020-06-12 12:12:33 +00:00
|
|
|
self.__cached_pairs[(pair, timeframe)] = (dataframe, Arrow.utcnow().datetime)
|
2018-11-30 19:42:16 +00:00
|
|
|
|
2019-01-22 05:55:40 +00:00
|
|
|
def refresh(self,
|
2020-05-16 08:09:50 +00:00
|
|
|
pairlist: ListPairsWithTimeframes,
|
|
|
|
helping_pairs: ListPairsWithTimeframes = None) -> None:
|
2018-11-30 19:42:16 +00:00
|
|
|
"""
|
|
|
|
Refresh data, called with each cycle
|
|
|
|
"""
|
2019-01-22 05:55:40 +00:00
|
|
|
if helping_pairs:
|
|
|
|
self._exchange.refresh_latest_ohlcv(pairlist + helping_pairs)
|
|
|
|
else:
|
|
|
|
self._exchange.refresh_latest_ohlcv(pairlist)
|
2018-11-30 19:42:16 +00:00
|
|
|
|
2018-12-26 13:23:21 +00:00
|
|
|
@property
|
2020-05-16 08:09:50 +00:00
|
|
|
def available_pairs(self) -> ListPairsWithTimeframes:
|
2018-12-26 13:23:21 +00:00
|
|
|
"""
|
2019-11-02 19:25:18 +00:00
|
|
|
Return a list of tuples containing (pair, timeframe) for which data is currently cached.
|
2018-12-26 13:23:21 +00:00
|
|
|
Should be whitelist + open trades.
|
|
|
|
"""
|
|
|
|
return list(self._exchange._klines.keys())
|
|
|
|
|
2019-11-02 19:19:13 +00:00
|
|
|
def ohlcv(self, pair: str, timeframe: str = None, copy: bool = True) -> DataFrame:
|
2018-11-30 19:42:16 +00:00
|
|
|
"""
|
2020-03-08 10:35:31 +00:00
|
|
|
Get candle (OHLCV) data for the given pair as DataFrame
|
2019-08-18 09:47:19 +00:00
|
|
|
Please use the `available_pairs` method to verify which pairs are currently cached.
|
2018-12-25 12:37:15 +00:00
|
|
|
:param pair: pair to get the data for
|
2020-03-08 10:35:31 +00:00
|
|
|
:param timeframe: Timeframe to get data for
|
2019-08-17 08:43:36 +00:00
|
|
|
:param copy: copy dataframe before returning if True.
|
|
|
|
Use False only for read-only operations (where the dataframe is not modified)
|
2018-11-30 19:42:16 +00:00
|
|
|
"""
|
2018-12-29 08:13:20 +00:00
|
|
|
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
|
2020-06-01 18:49:40 +00:00
|
|
|
return self._exchange.klines((pair, timeframe or self._config['timeframe']),
|
2019-08-18 10:00:37 +00:00
|
|
|
copy=copy)
|
|
|
|
else:
|
|
|
|
return DataFrame()
|
2018-11-30 19:42:16 +00:00
|
|
|
|
2019-11-02 19:19:13 +00:00
|
|
|
def historic_ohlcv(self, pair: str, timeframe: str = None) -> DataFrame:
|
2018-11-30 19:42:16 +00:00
|
|
|
"""
|
2020-03-08 10:35:31 +00:00
|
|
|
Get stored historical candle (OHLCV) data
|
2018-12-30 06:15:21 +00:00
|
|
|
: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,
|
2020-06-01 18:49:40 +00:00
|
|
|
timeframe=timeframe or self._config['timeframe'],
|
2019-12-23 14:09:17 +00:00
|
|
|
datadir=self._config['datadir']
|
2018-12-17 05:52:13 +00:00
|
|
|
)
|
2018-11-30 19:42:16 +00:00
|
|
|
|
2019-11-02 19:25:18 +00:00
|
|
|
def get_pair_dataframe(self, pair: str, timeframe: str = None) -> DataFrame:
|
2019-08-17 08:43:36 +00:00
|
|
|
"""
|
2020-03-08 10:35:31 +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
|
2019-11-02 19:25:18 +00:00
|
|
|
:return: Dataframe for this pair
|
2019-08-17 08:43:36 +00:00
|
|
|
"""
|
|
|
|
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
|
2020-03-08 10:35:31 +00:00
|
|
|
# Get live OHLCV data.
|
2019-11-02 19:25:18 +00:00
|
|
|
data = self.ohlcv(pair=pair, timeframe=timeframe)
|
2019-08-17 08:43:36 +00:00
|
|
|
else:
|
2020-03-08 10:35:31 +00:00
|
|
|
# Get historical OHLCV data (cached on disk).
|
2019-11-02 19:25:18 +00:00
|
|
|
data = self.historic_ohlcv(pair=pair, timeframe=timeframe)
|
2019-08-17 08:43:36 +00:00
|
|
|
if len(data) == 0:
|
2019-11-02 19:25:18 +00:00
|
|
|
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
|
|
|
|
combination
|
2020-06-12 12:02:21 +00:00
|
|
|
"""
|
|
|
|
# TODO: check updated time and don't return outdated data.
|
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:
|
|
|
|
# TODO: this is most likely wrong...
|
|
|
|
raise ValueError(f"No analyzed dataframe found for ({pair}, {timeframe})")
|
|
|
|
|
2019-10-10 15:03:52 +00:00
|
|
|
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
|
|
|
|
"""
|
|
|
|
return self._exchange.markets.get(pair)
|
|
|
|
|
2018-12-02 08:16:35 +00:00
|
|
|
def ticker(self, pair: str):
|
|
|
|
"""
|
2020-05-14 10:36:48 +00:00
|
|
|
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
|
|
|
"""
|
2020-05-14 10:36:48 +00:00
|
|
|
try:
|
|
|
|
return self._exchange.fetch_ticker(pair)
|
|
|
|
except DependencyException:
|
|
|
|
return {}
|
2018-11-30 19:42:16 +00:00
|
|
|
|
2019-10-10 15:03:52 +00:00
|
|
|
def orderbook(self, pair: str, maximum: int) -> Dict[str, List]:
|
2018-12-02 08:16:35 +00:00
|
|
|
"""
|
2020-05-26 18:27:35 +00:00
|
|
|
Fetch latest l2 orderbook data
|
|
|
|
Warning: Does a network request - so use with common sense.
|
2019-07-14 18:05:28 +00:00
|
|
|
: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
|
|
|
"""
|
2020-05-26 18:27:35 +00:00
|
|
|
return self._exchange.fetch_l2_order_book(pair, maximum)
|
2018-12-02 20:57:30 +00:00
|
|
|
|
|
|
|
@property
|
2018-12-25 13:23:59 +00:00
|
|
|
def runmode(self) -> RunMode:
|
2018-12-02 20:57:30 +00:00
|
|
|
"""
|
|
|
|
Get runmode of the bot
|
2018-12-29 08:13:20 +00:00
|
|
|
can be "live", "dry-run", "backtest", "edgecli", "hyperopt" or "other".
|
2018-12-02 20:57:30 +00:00
|
|
|
"""
|
2018-12-25 13:35:48 +00:00
|
|
|
return RunMode(self._config.get('runmode', RunMode.OTHER))
|
2020-05-06 15:48:57 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
"""
|
|
|
|
|
2020-05-11 15:32:28 +00:00
|
|
|
if self._pairlists:
|
|
|
|
return self._pairlists.whitelist
|
|
|
|
else:
|
|
|
|
raise OperationalException("Dataprovider was not initialized with a pairlist provider.")
|