async def _candle(self, msg: dict, timestamp: float): """ { 'kline': [ [1625332980, 60, 346285000, 346300000, 346390000, 346300000, 346390000, 49917, 144121225] ], 'sequence': 9048385626, 'symbol': 'BTCUSD', 'type': 'incremental' } """ symbol = self.exchange_symbol_to_std_symbol(msg['symbol']) for entry in msg['kline']: ts, _, _, open, high, low, close, _, volume = entry c = Candle( self.id, symbol, ts, ts + self.candle_interval_map[self.candle_interval], self.candle_interval, None, Decimal(open / self.price_scale[symbol]), Decimal(close / self.price_scale[symbol]), Decimal(high / self.price_scale[symbol]), Decimal(low / self.price_scale[symbol]), Decimal(volume), None, None ) await self.callback(CANDLES, c, timestamp)
async def _candle(self, msg: list, pair: str, timestamp: float): """ [327, ['1621988141.603324', start '1621988160.000000', end '38220.70000', open '38348.80000', high '38220.70000', low '38320.40000', close '38330.59222', vwap '3.23539643', volume 42 count ], 'ohlc-1', 'XBT/USD'] """ start, end, open, high, low, close, _, volume, count = msg[1] interval = int(msg[-2].split("-")[-1]) c = Candle(self.id, pair, float(end) - (interval * 60), float(end), self.normalize_candle_interval[interval], count, Decimal(open), Decimal(close), Decimal(high), Decimal(low), Decimal(volume), None, float(start), raw=msg) await self.callback(CANDLES, c, timestamp)
async def _candles(self, msg: dict, timestamp: float): ''' { 'candle_start_time': 1638134700000000, 'close': None, 'high': None, 'last_updated': 1638134700318213, 'low': None, 'open': None, 'resolution': '1m', 'symbol': 'BTC_USDT', 'timestamp': 1638134708903082, 'type': 'candlestick_1m', 'volume': 0 } ''' interval = self.normalize_candle_interval[msg['resolution']] c = Candle(self.id, self.exchange_symbol_to_std_symbol(msg['symbol']), self.timestamp_normalize(msg['candle_start_time']), self.timestamp_normalize(msg['candle_start_time']) + timedelta_str_to_sec(interval) - 1, interval, None, Decimal(msg['open'] if msg['open'] else 0), Decimal(msg['close'] if msg['close'] else 0), Decimal(msg['high'] if msg['high'] else 0), Decimal(msg['low'] if msg['low'] else 0), Decimal(msg['volume'] if msg['volume'] else 0), False, self.timestamp_normalize(msg['timestamp']), raw=msg) await self.callback(CANDLES, c, timestamp)
async def _candle(self, msg: dict, timestamp: float): ''' { 'action': 'update', 'arg': { 'instType': 'sp', 'channel': 'candle1m', 'instId': 'BTCUSDT' }, 'data': [['1649014920000', '46434.2', '46437.98', '46434.2', '46437.98', '0.9469']] } ''' for entry in msg['data']: t = Candle(self.id, self.exchange_symbol_to_std_symbol( msg['arg']['instId']), self.timestamp_normalize(int(entry[0])), self.timestamp_normalize(int(entry[0])) + timedelta_str_to_sec(self.candle_interval), self.candle_interval, None, Decimal(entry[1]), Decimal(entry[4]), Decimal(entry[2]), Decimal(entry[3]), Decimal(entry[5]), None, self.timestamp_normalize(int(entry[0])), raw=entry) await self.callback(CANDLES, t, timestamp)
async def _candles(self, msg: dict, symbol: str, timestamp: float): """ { 'data': { 'symbol': 'BTC-USDT', 'candles': ['1619196960', '49885.4', '49821', '49890.5', '49821', '2.60137567', '129722.909001802'], 'time': 1619196997007846442 }, 'subject': 'trade.candles.update', 'topic': '/market/candles:BTC-USDT_1min', 'type': 'message' } """ symbol, interval = symbol.split("_") interval = self.normalize_candle_interval[interval] start, open, close, high, low, vol, _ = msg['data']['candles'] end = int(start) + timedelta_str_to_sec(interval) - 1 c = Candle(self.id, symbol, int(start), end, interval, None, Decimal(open), Decimal(close), Decimal(high), Decimal(low), Decimal(vol), None, msg['data']['time'] / 1000000000, raw=msg) await self.callback(CANDLES, c, timestamp)
def test_candle_history_specific_time(self): expected = [ Candle( Coinbase.id, 'BTC-USD', 1578733200, 1578733200 + 3600, '1h', None, Decimal('8054.66'), Decimal('8109.53'), Decimal('8122'), Decimal('8054.64'), Decimal('78.91111363'), True, 1578733200 ), Candle( Coinbase.id, 'BTC-USD', 1578736800, 1578736800 + 3600, '1h', None, Decimal('8110.95'), Decimal('8050.94'), Decimal('8110.95'), Decimal('8045.67'), Decimal('71.11516828'), True, 1578736800 ) ] s = '2020-01-11 09:00:00' e = '2020-01-11 10:00:00' candle_history = [] for entry in public.candles_sync('BTC-USD', start=s, end=e, interval='1h'): candle_history.extend(entry) assert len(candle_history) == 2 assert candle_history == expected
def test_candle(): t = Candle( 'BINANCE_FUTURES', 'BTC-USD-PERP', time(), time() + 60, '1m', 54, Decimal(10), Decimal(100), Decimal(200), Decimal(10), Decimal(1234.5432), True, time(), ) d = t.to_dict(numeric_type=str) d = json.dumps(d) d = json.loads(d) t2 = Candle.from_dict(d) assert t == t2
def test_candles(self): expected = Candle(b.id, 'BTC-USDT', 1577836800.0, 1577836859.999, '1m', 493, Decimal('7195.24'), Decimal('7186.68'), Decimal('7196.25'), Decimal('7183.14'), Decimal('51.642812'), True, 1577836859.999) ret = [] for data in b.candles_sync('BTC-USDT', start='2020-01-01 00:00:00', end='2020-01-01 00:00:59'): ret.extend(data) assert len(ret) == 1 assert ret[0] == expected
def _candle_normalize(self, symbol: str, data: list, interval: str) -> dict: return Candle(self.id, symbol, data[0], data[0] + timedelta_str_to_sec(interval), interval, None, Decimal(data[3]), Decimal(data[4]), Decimal(data[2]), Decimal(data[1]), Decimal(data[5]), True, data[0], raw=data)
async def candles(self, symbol: str, start=None, end=None, interval='1m', retry_count=1, retry_delay=60): sym = self.std_symbol_to_exchange_symbol(symbol) ep = f'{self.api}klines?symbol={sym}&interval={interval}&limit=1000' start, end = self._interval_normalize(start, end) if start and end: start = int(start * 1000) end = int(end * 1000) while True: if start and end: endpoint = f'{ep}&startTime={start}&endTime={end}' else: endpoint = ep r = await self.http_conn.read(endpoint, retry_count=retry_count, retry_delay=retry_delay) data = json.loads(r, parse_float=Decimal) start = data[-1][6] data = [ Candle(self.id, symbol, self.timestamp_normalize(e[0]), self.timestamp_normalize(e[6]), interval, e[8], Decimal(e[1]), Decimal(e[4]), Decimal(e[2]), Decimal(e[3]), Decimal(e[5]), True, self.timestamp_normalize(e[6]), raw=e) for e in data ] yield data if len(data) < 1000 or end is None: break await asyncio.sleep(1 / self.request_limit)
async def candles(self, symbol: str, start=None, end=None, interval='1m', retry_count=1, retry_delay=60): _interval = self.candle_mappings[interval] sym = self.std_symbol_to_exchange_symbol(symbol) base_endpoint = f"{self.api}candles/trade:{_interval}:{sym}" start, end = self._interval_normalize(start, end) offset = timedelta_str_to_sec(interval) while True: if start and end: endpoint = f"{base_endpoint}/hist?limit=10000&start={int(start * 1000)}&end={int(end * 1000)}&sort=1" else: endpoint = f"{base_endpoint}/last" r = await self.http_conn.read(endpoint, retry_delay=retry_delay, retry_count=retry_count) data = json.loads(r, parse_float=Decimal) if not isinstance(data[0], list): data = [data] data = [ Candle(self.id, symbol, self.timestamp_normalize(e[0]), self.timestamp_normalize(e[0]) + offset, interval, None, Decimal(e[1]), Decimal(e[2]), Decimal(e[3]), Decimal(e[4]), Decimal(e[5]), True, self.timestamp_normalize(e[0]), raw=e) for e in data ] yield data if not end or len(data) < 10000: break start = data[-1].start + offset
async def candle(self, msg: dict, timestamp: float): """ { 'sequence': 134514, 'marketSymbol': 'BTC-USDT', 'interval': 'MINUTE_1', 'delta': { 'startsAt': datetime.datetime(2021, 6, 14, 1, 12, tzinfo=datetime.timezone.utc), 'open': '39023.31434847', 'high': '39023.31434847', 'low': '39023.31434847', 'close': '39023.31434847', 'volume': '0.05944473', 'quoteVolume': '2319.73038514' }, 'candleType': 'TRADE' } """ start = self.timestamp_normalize(msg['delta']['startsAt']) offset = 0 if self.candle_interval == '1m': offset = 60 elif self.candle_interval == '5m': offset = 300 elif self.candle_interval == '1h': offset = 3600 elif self.candle_interval == '1d': offset = 86400 end = start + offset c = Candle(self.id, self.exchange_symbol_to_std_symbol(msg['marketSymbol']), start, end, self.candle_interval, None, Decimal(msg['delta']['open']), Decimal(msg['delta']['close']), Decimal(msg['delta']['high']), Decimal(msg['delta']['low']), Decimal(msg['delta']['volume']), None, None, raw=msg) await self.callback(CANDLES, c, timestamp)
async def _candle(self, msg: dict, timestamp: float): """ { 'e': 'kline', 'E': 1615927655524, 's': 'BTCUSDT', 'k': { 't': 1615927620000, 'T': 1615927679999, 's': 'BTCUSDT', 'i': '1m', 'f': 710917276, 'L': 710917780, 'o': '56215.99000000', 'c': '56232.07000000', 'h': '56238.59000000', 'l': '56181.99000000', 'v': '13.80522200', 'n': 505, 'x': False, 'q': '775978.37383076', 'V': '7.19660600', 'Q': '404521.60814919', 'B': '0' } } """ if self.candle_closed_only and not msg['k']['x']: return c = Candle(self.id, self.exchange_symbol_to_std_symbol(msg['s']), msg['k']['t'] / 1000, msg['k']['T'] / 1000, msg['k']['i'], msg['k']['n'], Decimal(msg['k']['o']), Decimal(msg['k']['c']), Decimal(msg['k']['h']), Decimal(msg['k']['l']), Decimal(msg['k']['v']), msg['k']['x'], self.timestamp_normalize(msg['E']), raw=msg) await self.callback(CANDLES, c, timestamp)
async def _candle(self, msg: dict, timestamp: float): ''' { 'instrument_name': 'BTC_USDT', 'subscription': 'candlestick.14D.BTC_USDT', 'channel': 'candlestick', 'depth': 300, 'interval': '14D', 'data': [ { 't': 1636934400000, 'o': Decimal('65502.68'), 'h': Decimal('66336.25'), 'l': Decimal('55313.95'), 'c': Decimal('57582.1'), 'v': Decimal('366802.492134') } ] } ''' interval = msg['interval'] if interval == '14D': interval = '2w' elif interval == '7D': interval = '1w' elif interval == '1D': interval = '1d' for entry in msg['data']: c = Candle(self.id, self.exchange_symbol_to_std_symbol(msg['instrument_name']), entry['t'] / 1000, entry['t'] / 1000 + timedelta_str_to_sec(interval) - 1, interval, None, entry['o'], entry['c'], entry['h'], entry['l'], entry['v'], None, None, raw=entry) await self.callback(CANDLES, c, timestamp)
async def candles(self, symbol: str, start=None, end=None, interval='1m', retry_count=1, retry_delay=60): sym = self.std_symbol_to_exchange_symbol(symbol) interval_sec = timedelta_str_to_sec(interval) base = f'{self.api}/markets/{sym}/candles?resolution={interval_sec}' start, end = self._interval_normalize(start, end) while True: endpoint = base if start and end: endpoint = f'{base}&start_time={start}&end_time={end}' r = await self.http_conn.read(endpoint, retry_count=retry_count, retry_delay=retry_delay) data = json.loads(r, parse_float=Decimal)['result'] data = [ Candle(self.id, symbol, self.timestamp_normalize(e['startTime']), self.timestamp_normalize(e['startTime']) + interval_sec, interval, None, Decimal(e['open']), Decimal(e['close']), Decimal(e['high']), Decimal(e['low']), Decimal(e['volume']), True, self.timestamp_normalize(e['startTime']), raw=e) for e in data ] yield data end = data[0].start - interval_sec if not start or len(data) < 1501: break await asyncio.sleep(1 / self.request_limit)
async def candles(self, symbol: str, start=None, end=None, interval='1m', retry_count=1, retry_delay=60): sym = self.std_symbol_to_exchange_symbol(symbol) interval_sec = timedelta_str_to_sec(interval) base = f'{self.api}ohlc/{sym}/?step={interval_sec}&limit=1000' start, end = self._interval_normalize(start, end) while True: endpoint = base if start and end: endpoint = f'{base}&start={int(start)}&end={int(end)}' r = await self.http_conn.read(endpoint, retry_count=retry_count, retry_delay=retry_delay) data = json.loads(r, parse_float=Decimal)['data']['ohlc'] data = [ Candle(self.id, symbol, float(e['timestamp']), float(e['timestamp']) + interval_sec, interval, None, Decimal(e['open']), Decimal(e['close']), Decimal(e['high']), Decimal(e['low']), Decimal(e['volume']), True, float(e['timestamp']), raw=e) for e in data ] yield data end = data[0].start - interval_sec if not start or start >= end: break await asyncio.sleep(1 / self.request_limit)
async def _candles(self, msg: dict, timestamp: float): """ { "jsonrpc": "2.0", "method": "updateCandles", "params": { "data": [ { "timestamp": "2017-10-19T16:30:00.000Z", "open": "0.054614", "close": "0.054465", "min": "0.054339", "max": "0.054724", "volume": "141.268", "volumeQuote": "7.709353873" } ], "symbol": "ETHBTC", "period": "M30" } } """ interval = str(self.normalize_interval[msg['period']]) for candle in msg['data']: start = self.timestamp_normalize(candle['timestamp']) end = start + timedelta_str_to_sec(interval) - 1 c = Candle(self.id, self.exchange_symbol_to_std_symbol(msg['symbol']), start, end, interval, None, Decimal(candle['open']), Decimal(candle['close']), Decimal(candle['max']), Decimal(candle['min']), Decimal(candle['volume']), None, None, raw=candle) await self.callback(CANDLES, c, timestamp)
async def _candle(self, msg: dict, timestamp: float): ''' { "topic": "klineV2.1.BTCUSD", //topic name "data": [{ "start": 1572425640, //start time of the candle "end": 1572425700, //end time of the candle "open": 9200, //open price "close": 9202.5, //close price "high": 9202.5, //max price "low": 9196, //min price "volume": 81790, //volume "turnover": 8.889247899999999, //turnover "confirm": False, //snapshot flag (indicates if candle is closed or not) "cross_seq": 297503466, "timestamp": 1572425676958323 //cross time }], "timestamp_e6": 1572425677047994 //server time } ''' symbol = self.exchange_symbol_to_std_symbol( msg['topic'].split(".")[-1]) ts = msg['timestamp_e6'] / 1_000_000 for entry in msg['data']: if self.candle_closed_only and not entry['confirm']: continue c = Candle(self.id, symbol, entry['start'], entry['end'], self.candle_interval, entry['confirm'], Decimal(entry['open']), Decimal(entry['close']), Decimal(entry['high']), Decimal(entry['low']), Decimal(entry['volume']), None, ts, raw=entry) await self.callback(CANDLES, c, timestamp)
def test_candles(self): expected = Candle( o.id, 'BTC-USDT', 1609459200.0, 1609459260.0, '1m', None, Decimal('28914.8'), # open Decimal('28959.1'), # close Decimal('28959.1'), # high Decimal('28914.8'), # low Decimal('13.22459039'), # volume True, 1609459200.0 ) ret = [] for data in o.candles_sync('BTC-USDT', start='2021-01-01 00:00:00', end='2021-01-01 00:00:01', interval='1m'): ret.extend(data) assert len(ret) == 1 assert ret[0] == expected
async def _candle(self, msg: dict, ts: float): ''' { 'ch': 'candles/M1', 'update': { 'BTCUSDT': [{ 't': 1633805940000, 'o': '54849.03', 'c': '54849.03', 'h': '54849.03', 'l': '54849.03', 'v': '0.00766', 'q': '420.1435698' }] } } ''' interval = msg['ch'].split("/")[-1] for sym, updates in msg['update'].items(): symbol = self.exchange_symbol_to_std_symbol(sym) for u in updates: c = Candle( self.id, symbol, u['t'] / 1000, u['t'] / 1000 + timedelta_str_to_sec(self.normalize_interval[interval]) - 0.1, self.normalize_interval[interval], None, Decimal(u['o']), Decimal(u['c']), Decimal(u['h']), Decimal(u['l']), Decimal(u['v']), None, self.timestamp_normalize(u['t']), raw=msg) await self.callback(CANDLES, c, ts)
async def _candles(self, msg: dict, symbol: str, interval: str, timestamp: float): """ { 'ch': 'market.btcusdt.kline.1min', 'ts': 1618700872863, 'tick': { 'id': 1618700820, 'open': Decimal('60751.62'), 'close': Decimal('60724.73'), 'low': Decimal('60724.73'), 'high': Decimal('60751.62'), 'amount': Decimal('2.1990737759143966'), 'vol': Decimal('133570.944386'), 'count': 235} } } """ interval = self.normalize_candle_interval[interval] start = int(msg['tick']['id']) end = start + timedelta_str_to_sec(interval) - 1 c = Candle(self.id, self.exchange_symbol_to_std_symbol(symbol), start, end, interval, msg['tick']['count'], Decimal(msg['tick']['open']), Decimal(msg['tick']['close']), Decimal(msg['tick']['high']), Decimal(msg['tick']['low']), Decimal(msg['tick']['amount']), None, self.timestamp_normalize(msg['ts']), raw=msg) await self.callback(CANDLES, c, timestamp)
async def _candles(self, msg: dict, timestamp: float): """ { 'time': 1619092863, 'channel': 'spot.candlesticks', 'event': 'update', 'result': { 't': '1619092860', 'v': '1154.64627', 'c': '54992.64', 'h': '54992.64', 'l': '54976.29', 'o': '54976.29', 'n': '1m_BTC_USDT' } } """ interval, symbol = msg['result']['n'].split('_', 1) if interval == '7d': interval = '1w' c = Candle(self.id, self.exchange_symbol_to_std_symbol(symbol), float(msg['result']['t']), float(msg['result']['t']) + timedelta_str_to_sec(interval) - 0.1, interval, None, Decimal(msg['result']['o']), Decimal(msg['result']['c']), Decimal(msg['result']['h']), Decimal(msg['result']['l']), Decimal(msg['result']['v']), None, float(msg['time']), raw=msg) await self.callback(CANDLES, c, timestamp)
async def candles(self, symbol: str, start=None, end=None, interval='1m', retry_count=1, retry_delay=60): ''' [ { "market": "KRW-BTC", "candle_date_time_utc": "2021-09-11T00:32:00", "candle_date_time_kst": "2021-09-11T09:32:00", "opening_price": 55130000, "high_price": 55146000, "low_price": 55104000, "trade_price": 55145000, "timestamp": 1631320340367, "candle_acc_trade_price": 136592120.21198, "candle_acc_trade_volume": 2.47785284, "unit": 1 }, ... ] ''' sym = self.std_symbol_to_exchange_symbol(symbol) offset = timedelta_str_to_sec(interval) interval_mins = int(timedelta_str_to_sec(interval) / 60) if interval == '1d': base = f'{self.api}candles/days/?market={sym}&count=200' elif interval == '1w': base = f'{self.api}candles/weeks/?market={sym}&count=200' elif interval == '1M': base = f'{self.api}candles/months/?market={sym}&count=200' else: base = f'{self.api}candles/minutes/{interval_mins}?market={sym}&count=200' start, end = self._interval_normalize(start, end) def _ts_norm(timestamp: datetime) -> float: # Upbit sends timezone naïve datetimes, so need to force to UTC before converting to timestamp assert timestamp.tzinfo is None return timestamp.replace(tzinfo=timezone.utc).timestamp() def retain(c: Candle, _last: set): if start and end: return c.start <= end and c.start not in _last return True _last = set() while True: endpoint = base if start and end: end_timestamp = datetime.utcfromtimestamp(start + offset * 200) end_timestamp = end_timestamp.replace( microsecond=0).isoformat() + 'Z' endpoint = f'{base}&to={end_timestamp}' r = await self.http_conn.read(endpoint, retry_count=retry_count, retry_delay=retry_delay) data = json.loads(r, parse_float=Decimal) data = [ Candle(self.id, symbol, _ts_norm(e['candle_date_time_utc']), _ts_norm(e['candle_date_time_utc']) + interval_mins * 60, interval, None, Decimal(e['opening_price']), None, Decimal(e['high_price']), Decimal(e['low_price']), Decimal(e['candle_acc_trade_volume']), True, float(e['timestamp']) / 1000, raw=e) for e in data ] data = list( sorted([c for c in data if retain(c, _last)], key=lambda x: x.start)) yield data # exchange downtime can cause gaps in candles, and because of the way pagination works, there will be overlap in ranges that # cover the downtime. Solution: remove duplicates by storing last values returned to client. _last = set([c.start for c in data]) start = data[-1].start + offset if not end or start >= end: break await asyncio.sleep(1 / self.request_limit)