freqtrade_origin/freqtrade/exchange/exchange_ws.py

117 lines
4.7 KiB
Python
Raw Normal View History

2022-10-18 18:48:40 +00:00
import asyncio
import logging
import time
2022-10-29 17:44:27 +00:00
from datetime import datetime
2022-10-18 18:48:40 +00:00
from threading import Thread
from typing import Dict, List, Set, Tuple
2022-10-18 18:48:40 +00:00
2022-11-07 06:07:15 +00:00
import ccxt
2022-11-28 18:46:50 +00:00
from freqtrade.constants import Config, PairWithTimeframe
2022-10-18 18:48:40 +00:00
from freqtrade.enums.candletype import CandleType
2022-10-29 17:44:27 +00:00
from freqtrade.exchange.exchange import timeframe_to_seconds
2022-10-18 18:48:40 +00:00
logger = logging.getLogger(__name__)
class ExchangeWS():
2022-11-28 19:54:00 +00:00
def __init__(self, config: Config, ccxt_object: ccxt.Exchange) -> None:
2022-10-18 18:48:40 +00:00
self.config = config
self.ccxt_object = ccxt_object
2022-11-11 05:46:14 +00:00
self._thread = Thread(name="ccxt_ws", target=self.__start_forever)
self._background_tasks: Set[asyncio.Task] = set()
2022-10-28 05:22:53 +00:00
2022-11-28 18:46:50 +00:00
self._klines_watching: Set[PairWithTimeframe] = set()
self._klines_scheduled: Set[PairWithTimeframe] = set()
self.klines_last_refresh: Dict[PairWithTimeframe, float] = {}
self.klines_last_request: Dict[PairWithTimeframe, float] = {}
2022-10-18 18:48:40 +00:00
self._thread.start()
2022-11-11 05:46:14 +00:00
def __start_forever(self) -> None:
2022-10-18 18:48:40 +00:00
self._loop = asyncio.new_event_loop()
self._loop.run_forever()
def cleanup(self) -> None:
logger.debug("Cleanup called - stopping")
2022-11-11 05:46:14 +00:00
self._klines_watching.clear()
self._loop.stop()
self._thread.join()
logger.debug("Stopped")
2022-10-28 05:22:53 +00:00
def cleanup_expired(self) -> None:
"""
Remove pairs from watchlist if they've not been requested within
the last timeframe (+ offset)
"""
2022-11-11 05:46:14 +00:00
for p in list(self._klines_watching):
2022-10-28 05:22:53 +00:00
_, timeframe, _ = p
timeframe_s = timeframe_to_seconds(timeframe)
2022-11-11 05:46:14 +00:00
last_refresh = self.klines_last_request.get(p, 0)
2022-10-28 05:22:53 +00:00
if last_refresh > 0 and time.time() - last_refresh > timeframe_s + 20:
logger.info(f"Removing {p} from watchlist")
2022-11-11 05:46:14 +00:00
self._klines_watching.discard(p)
2022-10-18 18:48:40 +00:00
async def _schedule_while_true(self) -> None:
2022-10-18 18:48:40 +00:00
2022-11-11 05:46:14 +00:00
for p in self._klines_watching:
if p not in self._klines_scheduled:
self._klines_scheduled.add(p)
2022-10-18 18:48:40 +00:00
pair, timeframe, candle_type = p
task = asyncio.create_task(
self._continuously_async_watch_ohlcv(pair, timeframe, candle_type))
2022-10-18 18:48:40 +00:00
self._background_tasks.add(task)
task.add_done_callback(self._continuous_stopped)
2022-10-18 18:48:40 +00:00
def _continuous_stopped(self, task: asyncio.Task):
2022-10-18 18:48:40 +00:00
self._background_tasks.discard(task)
result = task.result()
logger.info(f"Task finished {result}")
# self._pairs_scheduled.discard(pair, timeframe, candle_type)
2022-10-18 18:48:40 +00:00
async def _continuously_async_watch_ohlcv(
self, pair: str, timeframe: str, candle_type: CandleType) -> None:
2022-11-07 06:07:15 +00:00
try:
2022-11-11 05:46:14 +00:00
while (pair, timeframe, candle_type) in self._klines_watching:
2022-11-07 06:07:15 +00:00
start = time.time()
data = await self.ccxt_object.watch_ohlcv(pair, timeframe)
2022-11-11 05:46:14 +00:00
self.klines_last_refresh[(pair, timeframe, candle_type)] = time.time()
2022-11-07 06:07:15 +00:00
# logger.info(
# f"watch done {pair}, {timeframe}, data {len(data)} "
# f"in {time.time() - start:.2f}s")
except ccxt.BaseError:
logger.exception("Exception in continuously_async_watch_ohlcv")
finally:
2022-11-11 05:46:14 +00:00
self._klines_watching.discard((pair, timeframe, candle_type))
2022-10-18 18:48:40 +00:00
def schedule_ohlcv(self, pair: str, timeframe: str, candle_type: CandleType) -> None:
2022-11-11 05:46:14 +00:00
"""
Schedule a pair/timeframe combination to be watched
"""
self._klines_watching.add((pair, timeframe, candle_type))
self.klines_last_request[(pair, timeframe, candle_type)] = time.time()
2022-10-18 18:48:40 +00:00
# asyncio.run_coroutine_threadsafe(self.schedule_schedule(), loop=self._loop)
asyncio.run_coroutine_threadsafe(self._schedule_while_true(), loop=self._loop)
2022-10-28 05:22:53 +00:00
self.cleanup_expired()
async def get_ohlcv(
2022-11-28 19:54:00 +00:00
self,
pair: str,
timeframe: str,
candle_type: CandleType
) -> Tuple[str, str, CandleType, List]:
"""
Returns cached klines from ccxt's "watch" cache.
"""
candles = self.ccxt_object.ohlcvs.get(pair, {}).get(timeframe)
# Fake 1 candle - which is then removed again
2022-10-29 17:44:27 +00:00
# TODO: is this really a good idea??
2022-11-11 05:46:14 +00:00
refresh_time = int(self.klines_last_refresh[(pair, timeframe, candle_type)] * 1000)
candles.append([refresh_time, 0, 0, 0, 0, 0])
logger.info(
f"watch result for {pair}, {timeframe} with length {len(candles)}, "
f"{datetime.fromtimestamp(candles[-1][0] // 1000)}, "
2022-11-11 05:46:14 +00:00
f"lref={datetime.fromtimestamp(self.klines_last_refresh[(pair, timeframe, candle_type)])}")
return pair, timeframe, candle_type, candles