Added candle_type to a lot of methods, wrote some tests

This commit is contained in:
Sam Germain
2021-11-21 01:43:05 -06:00
parent e2f98a8dab
commit 920151934a
27 changed files with 495 additions and 253 deletions

View File

@@ -204,7 +204,7 @@ class Binance(Exchange):
since_ms: int, is_new_pair: bool = False,
raise_: bool = False,
candle_type: str = ''
) -> Tuple[str, str, List]:
) -> Tuple[str, str, 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"

View File

@@ -92,7 +92,7 @@ class Exchange:
self._config.update(config)
# Holds last candle refreshed time of each pair
self._pairs_last_refresh_time: Dict[Tuple[str, str], int] = {}
self._pairs_last_refresh_time: Dict[Tuple[str, str, str], int] = {}
# Timestamp of last markets refresh
self._last_markets_refresh: int = 0
@@ -105,7 +105,7 @@ class Exchange:
self._buy_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
# Holds candles
self._klines: Dict[Tuple[str, str], DataFrame] = {}
self._klines: Dict[Tuple[str, str, str], DataFrame] = {}
# Holds all open sell orders for dry_run
self._dry_run_open_orders: Dict[str, Any] = {}
@@ -359,7 +359,7 @@ class Exchange:
or (self.trading_mode == TradingMode.FUTURES and self.market_is_future(market))
)
def klines(self, pair_interval: Tuple[str, str], copy: bool = True) -> DataFrame:
def klines(self, pair_interval: Tuple[str, str, str], copy: bool = True) -> DataFrame:
if pair_interval in self._klines:
return self._klines[pair_interval].copy() if copy else self._klines[pair_interval]
else:
@@ -1321,7 +1321,8 @@ class Exchange:
:param since_ms: Timestamp in milliseconds to get history from
:return: List with candle (OHLCV) data
"""
pair, timeframe, data = asyncio.get_event_loop().run_until_complete(
data: List
pair, timeframe, candle_type, data = 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,
candle_type=candle_type))
@@ -1337,15 +1338,15 @@ class Exchange:
:param since_ms: Timestamp in milliseconds to get history from
:return: OHLCV DataFrame
"""
ticks = self.get_historic_ohlcv(pair, timeframe, since_ms=since_ms)
ticks = self.get_historic_ohlcv(pair, timeframe, since_ms=since_ms, candle_type=candle_type)
return ohlcv_to_dataframe(ticks, timeframe, pair=pair, fill_missing=True,
drop_incomplete=self._ohlcv_partial_candle)
async def _async_get_historic_ohlcv(self, pair: str, timeframe: str,
since_ms: int, is_new_pair: bool,
since_ms: int, is_new_pair: bool = False,
raise_: bool = False,
candle_type: str = ''
) -> Tuple[str, str, List]:
) -> Tuple[str, str, str, List]:
"""
Download historic ohlcv
:param is_new_pair: used by binance subclass to allow "fast" new pair downloading
@@ -1374,12 +1375,12 @@ class Exchange:
continue
else:
# Deconstruct tuple if it's not an exception
p, _, new_data = res
if p == pair:
p, _, c, new_data = res
if p == pair and c == candle_type:
data.extend(new_data)
# Sort data again after extending the result - above calls return in "async order"
data = sorted(data, key=lambda x: x[0])
return pair, timeframe, data
return pair, timeframe, candle_type, data
def refresh_latest_ohlcv(self, pair_list: ListPairsWithTimeframes, *,
since_ms: Optional[int] = None, cache: bool = True,
@@ -1399,8 +1400,8 @@ class Exchange:
input_coroutines = []
cached_pairs = []
# Gather coroutines to run
for pair, timeframe in set(pair_list):
if ((pair, timeframe) not in self._klines
for pair, timeframe, candle_type in set(pair_list):
if ((pair, timeframe, candle_type) not in self._klines
or self._now_is_time_to_refresh(pair, timeframe)):
if not since_ms and self.required_candle_call_count > 1:
# Multiple calls for one pair - to get more history
@@ -1411,17 +1412,17 @@ class Exchange:
if since_ms:
input_coroutines.append(self._async_get_historic_ohlcv(
pair, timeframe, since_ms=since_ms, raise_=True))
pair, timeframe, since_ms=since_ms, raise_=True, candle_type=candle_type))
else:
# One call ... "regular" refresh
input_coroutines.append(self._async_get_candle_history(
pair, timeframe, since_ms=since_ms, candle_type=candle_type,))
pair, timeframe, since_ms=since_ms, candle_type=candle_type))
else:
logger.debug(
"Using cached candle (OHLCV) data for pair %s, timeframe %s ...",
pair, timeframe
pair, timeframe, candle_type
)
cached_pairs.append((pair, timeframe))
cached_pairs.append((pair, timeframe, candle_type))
results = asyncio.get_event_loop().run_until_complete(
asyncio.gather(*input_coroutines, return_exceptions=True))
@@ -1433,20 +1434,23 @@ class Exchange:
logger.warning("Async code raised an exception: %s", res.__class__.__name__)
continue
# Deconstruct tuple (has 3 elements)
pair, timeframe, ticks = res
pair, timeframe, c_type, ticks = res
# keeping last candle time as last refreshed time of the pair
if ticks:
self._pairs_last_refresh_time[(pair, timeframe)] = ticks[-1][0] // 1000
self._pairs_last_refresh_time[(pair, timeframe, c_type)] = ticks[-1][0] // 1000
# keeping parsed dataframe in cache
ohlcv_df = ohlcv_to_dataframe(
ticks, timeframe, pair=pair, fill_missing=True,
drop_incomplete=self._ohlcv_partial_candle)
results_df[(pair, timeframe)] = ohlcv_df
results_df[(pair, timeframe, c_type)] = ohlcv_df
if cache:
self._klines[(pair, timeframe)] = ohlcv_df
self._klines[(pair, timeframe, c_type)] = ohlcv_df
# Return cached klines
for pair, timeframe in cached_pairs:
results_df[(pair, timeframe)] = self.klines((pair, timeframe), copy=False)
for pair, timeframe, c_type in cached_pairs:
results_df[(pair, timeframe, c_type)] = self.klines(
(pair, timeframe, c_type),
copy=False
)
return results_df
@@ -1459,8 +1463,12 @@ class Exchange:
# Timeframe in seconds
interval_in_sec = timeframe_to_seconds(timeframe)
return not ((self._pairs_last_refresh_time.get((pair, timeframe), 0)
+ interval_in_sec) >= arrow.utcnow().int_timestamp)
return not (
(self._pairs_last_refresh_time.get(
(pair, timeframe, candle_type),
0
) + interval_in_sec) >= arrow.utcnow().int_timestamp
)
@retrier_async
async def _async_get_candle_history(
@@ -1501,9 +1509,9 @@ class Exchange:
data = sorted(data, key=lambda x: x[0])
except IndexError:
logger.exception("Error loading %s. Result was %s.", pair, data)
return pair, timeframe, []
return pair, timeframe, candle_type, []
logger.debug("Done fetching pair %s, interval %s ...", pair, timeframe)
return pair, timeframe, data
return pair, timeframe, candle_type, data
except ccxt.NotSupported as e:
raise OperationalException(