diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 0b0ad1a0f..b9de58911 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -242,27 +242,38 @@ class Binance(Exchange): - interest rate: 0.03% daily, BNBUSDT, LINKUSDT, and LTCUSDT are 0% - premium: varies by price difference between the perpetual contract and mark price """ - if premium_index is None: - raise OperationalException("Premium index cannot be None for Binance._get_funding_fee") + if mark_price is None: + raise OperationalException("Mark price cannot be None for Binance._get_funding_fee") nominal_value = mark_price * contract_size - funding_rate = self._calculate_funding_rate(pair, premium_index) if funding_rate is None: - raise OperationalException("Funding rate should never be none on Binance") + raise OperationalException( + "Funding rate should never be none on Binance._get_funding_fee") return nominal_value * funding_rate - async def _async_get_historic_ohlcv(self, pair: str, timeframe: str, - since_ms: int, is_new_pair: bool - ) -> List: + async def _async_get_historic_ohlcv( + self, + pair: str, + timeframe: str, + since_ms: int, + is_new_pair: bool, + price: Optional[str] + ) -> List: """ Overwrite to introduce "fast new pair" functionality by detecting the pair's listing date Does not work for other exchanges, which don't return the earliest data when called with "0" + :param price: "mark" if retrieving the mark price cnadles """ if is_new_pair: - x = await self._async_get_candle_history(pair, timeframe, 0) + x = await self._async_get_candle_history(pair, timeframe, 0, price) if x and x[2] and x[2][0] and x[2][0][0] > since_ms: # Set starting date to first available candle. since_ms = x[2][0][0] logger.info(f"Candle-data for {pair} available starting with " f"{arrow.get(since_ms // 1000).isoformat()}.") return await super()._async_get_historic_ohlcv( - pair=pair, timeframe=timeframe, since_ms=since_ms, is_new_pair=is_new_pair) + pair=pair, + timeframe=timeframe, + since_ms=since_ms, + is_new_pair=is_new_pair, + price=price + ) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 3bf5e7baf..2d6dc0030 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1264,8 +1264,14 @@ class Exchange: # Historic data - def get_historic_ohlcv(self, pair: str, timeframe: str, - since_ms: int, is_new_pair: bool = False) -> List: + def get_historic_ohlcv( + self, + pair: str, + timeframe: str, + since_ms: int, + is_new_pair: bool = False, + price: Optional[str] + ) -> List: """ Get candle history using asyncio and returns the list of candles. Handles all async work for this. @@ -1273,31 +1279,55 @@ class Exchange: :param pair: Pair to download :param timeframe: Timeframe to get data for :param since_ms: Timestamp in milliseconds to get history from + :param price: "mark" if retrieving the mark price cnadles, "index" for index price candles :return: List with candle (OHLCV) data """ return asyncio.get_event_loop().run_until_complete( - self._async_get_historic_ohlcv(pair=pair, timeframe=timeframe, - since_ms=since_ms, is_new_pair=is_new_pair)) + self._async_get_historic_ohlcv( + pair=pair, + timeframe=timeframe, + since_ms=since_ms, + is_new_pair=is_new_pair, + price=price + )) - def get_historic_ohlcv_as_df(self, pair: str, timeframe: str, - since_ms: int) -> DataFrame: + def get_historic_ohlcv_as_df( + self, + pair: str, + timeframe: str, + since_ms: int, + price: Optional[str] + ) -> DataFrame: """ Minimal wrapper around get_historic_ohlcv - converting the result into a dataframe :param pair: Pair to download :param timeframe: Timeframe to get data for :param since_ms: Timestamp in milliseconds to get history from + :param price: "mark" if retrieving the mark price cnadles, "index" for index price candles :return: OHLCV DataFrame """ ticks = self.get_historic_ohlcv(pair, timeframe, since_ms=since_ms) - return ohlcv_to_dataframe(ticks, timeframe, pair=pair, fill_missing=True, - drop_incomplete=self._ohlcv_partial_candle) + return ohlcv_to_dataframe( + ticks, + timeframe, + pair=pair, + fill_missing=True, + drop_incomplete=self._ohlcv_partial_candle, + price=price + ) - async def _async_get_historic_ohlcv(self, pair: str, timeframe: str, - since_ms: int, is_new_pair: bool - ) -> List: + async def _async_get_historic_ohlcv( + self, + pair: str, + timeframe: str, + since_ms: int, + is_new_pair: bool, + price: Optional[str] + ) -> List: """ Download historic ohlcv :param is_new_pair: used by binance subclass to allow "fast" new pair downloading + :param price: "mark" if retrieving the mark price cnadles, "index" for index price candles """ one_call = timeframe_to_msecs(timeframe) * self.ohlcv_candle_limit(timeframe) @@ -1306,8 +1336,13 @@ class Exchange: one_call, arrow.utcnow().shift(seconds=one_call // 1000).humanize(only_distance=True) ) - input_coroutines = [self._async_get_candle_history( - pair, timeframe, since) for since in + input_coroutines = [ + self._async_get_candle_history( + pair, + timeframe, + since, + price + ) for since in range(since_ms, arrow.utcnow().int_timestamp * 1000, one_call)] data: List = [] @@ -1328,9 +1363,13 @@ class Exchange: logger.info(f"Downloaded data for {pair} with length {len(data)}.") return data - def refresh_latest_ohlcv(self, pair_list: ListPairsWithTimeframes, *, - since_ms: Optional[int] = None, cache: bool = True - ) -> Dict[Tuple[str, str], DataFrame]: + def refresh_latest_ohlcv( + self, + pair_list: ListPairsWithTimeframes, *, + since_ms: Optional[int] = None, + cache: bool = True, + price: Optional[str] + ) -> Dict[Tuple[str, str], DataFrame]: """ Refresh in-memory OHLCV asynchronously and set `_klines` with the result Loops asynchronously over pair_list and downloads all pairs async (semi-parallel). @@ -1338,6 +1377,7 @@ class Exchange: :param pair_list: List of 2 element tuples containing pair, interval to refresh :param since_ms: time since when to download, in milliseconds :param cache: Assign result to _klines. Usefull for one-off downloads like for pairlists + :param price: "mark" if retrieving the mark price cnadles, "index" for index price candles :return: Dict of [{(pair, timeframe): Dataframe}] """ logger.debug("Refreshing candle (OHLCV) data for %d pairs", len(pair_list)) @@ -1348,8 +1388,9 @@ class Exchange: for pair, timeframe in set(pair_list): if (((pair, timeframe) not in self._klines) or self._now_is_time_to_refresh(pair, timeframe)): - input_coroutines.append(self._async_get_candle_history(pair, timeframe, - since_ms=since_ms)) + input_coroutines.append( + self._async_get_candle_history(pair, timeframe, since_ms=since_ms, price=price) + ) else: logger.debug( "Using cached candle (OHLCV) data for pair %s, timeframe %s ...", @@ -1392,10 +1433,16 @@ class Exchange: + interval_in_sec) >= arrow.utcnow().int_timestamp) @retrier_async - async def _async_get_candle_history(self, pair: str, timeframe: str, - since_ms: Optional[int] = None) -> Tuple[str, str, List]: + async def _async_get_candle_history( + self, + pair: str, + timeframe: str, + since_ms: Optional[int] = None, + price: Optional[str] = None + ) -> Tuple[str, str, List]: """ Asynchronously get candle history data using fetch_ohlcv + :param price: "mark" if retrieving the mark price cnadles, "index" for index price candles returns tuple: (pair, timeframe, ohlcv_list) """ try: @@ -1405,11 +1452,15 @@ class Exchange: "Fetching pair %s, interval %s, since %s %s...", pair, timeframe, since_ms, s ) - params = self._ft_has.get('ohlcv_params', {}) - data = await self._api_async.fetch_ohlcv(pair, timeframe=timeframe, - since=since_ms, - limit=self.ohlcv_candle_limit(timeframe), - params=params) + # TODO-lev: Does this put price into params correctly? + params = self._ft_has.get('ohlcv_params', {price: price}) + data = await self._api_async.fetch_ohlcv( + pair, + timeframe=timeframe, + since=since_ms, + limit=self.ohlcv_candle_limit(timeframe), + params=params + ) # Some exchanges sort OHLCV in ASC order and others in DESC. # Ex: Bittrex returns the list of OHLCV in ASC order (oldest first, newest last)