# -*- coding: utf-8 -*- # PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN: # https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code import ccxt.async_support from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp import hashlib from ccxt.base.types import Any, Balances, Int, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade from ccxt.async_support.base.ws.client import Client from typing import List from ccxt.base.errors import ExchangeError from ccxt.base.errors import AuthenticationError from ccxt.base.errors import NotSupported class hitbtc(ccxt.async_support.hitbtc): def describe(self) -> Any: return self.deep_extend(super(hitbtc, self).describe(), { 'has': { 'ws': True, 'watchTicker': True, 'watchTickers': True, 'watchBidsAsks': True, 'watchTrades': True, 'watchTradesForSymbols': False, 'watchOrderBook': True, 'watchBalance': True, 'watchOrders': True, 'watchOHLCV': True, 'watchMyTrades': False, 'createOrderWs': True, 'cancelOrderWs': True, 'fetchOpenOrdersWs': True, 'cancelAllOrdersWs': True, }, 'urls': { 'api': { 'ws': { 'public': 'wss://api.hitbtc.com/api/3/ws/public', 'private': 'wss://api.hitbtc.com/api/3/ws/trading', }, }, 'test': { 'ws': { 'public': 'wss://api.demo.hitbtc.com/api/3/ws/public', 'private': 'wss://api.demo.hitbtc.com/api/3/ws/trading', }, }, }, 'options': { 'tradesLimit': 1000, 'watchTicker': { 'method': 'ticker/{speed}', # 'ticker/{speed}' or 'ticker/price/{speed}' }, 'watchTickers': { 'method': 'ticker/{speed}', # 'ticker/{speed}','ticker/price/{speed}', 'ticker/{speed}/batch', or 'ticker/{speed}/price/batch'' }, 'watchBidsAsks': { 'method': 'orderbook/top/{speed}', # 'orderbook/top/{speed}', 'orderbook/top/{speed}/batch' }, 'watchOrderBook': { 'method': 'orderbook/full', # 'orderbook/full', 'orderbook/{depth}/{speed}', 'orderbook/{depth}/{speed}/batch' }, }, 'timeframes': { '1m': 'M1', '3m': 'M3', '5m': 'M5', '15m': 'M15', '30m': 'M30', '1h': 'H1', '4h': 'H4', '1d': 'D1', '1w': 'D7', '1M': '1M', }, 'streaming': { 'keepAlive': 4000, }, }) async def authenticate(self): """ @ignore authenticates the user to access private web socket channels https://api.hitbtc.com/#socket-authentication :returns dict: response from exchange """ self.check_required_credentials() url = self.urls['api']['ws']['private'] messageHash = 'authenticated' client = self.client(url) future = client.reusableFuture(messageHash) authenticated = self.safe_value(client.subscriptions, messageHash) if authenticated is None: timestamp = self.milliseconds() signature = self.hmac(self.encode(self.number_to_string(timestamp)), self.encode(self.secret), hashlib.sha256, 'hex') request: dict = { 'method': 'login', 'params': { 'type': 'HS256', 'api_key': self.apiKey, 'timestamp': timestamp, 'signature': signature, }, } self.watch(url, messageHash, request, messageHash) # # { # "jsonrpc": "2.0", # "result": True # } # # # Failure to return results # # { # "jsonrpc": "2.0", # "error": { # "code": 1002, # "message": "Authorization is required or has been failed", # "description": "invalid signature format" # } # } # return await future async def subscribe_public(self, name: str, messageHashPrefix: str, symbols: Strings = None, params={}): """ @ignore :param str name: websocket endpoint name :param str messageHashPrefix: prefix for the message hash :param str[] [symbols]: unified CCXT symbol(s) :param dict [params]: extra parameters specific to the hitbtc api """ await self.load_markets() symbols = self.market_symbols(symbols) isBatch = name.find('batch') >= 0 url = self.urls['api']['ws']['public'] messageHashes = [] if symbols is not None and not isBatch: for i in range(0, len(symbols)): messageHashes.append(messageHashPrefix + '::' + symbols[i]) else: messageHashes.append(messageHashPrefix) subscribe: dict = { 'method': 'subscribe', 'id': self.nonce(), 'ch': name, } request = self.extend(subscribe, params) return await self.watch_multiple(url, messageHashes, request, messageHashes) async def subscribe_private(self, name: str, symbol: Str = None, params={}): """ @ignore :param str name: websocket endpoint name :param str [symbol]: unified CCXT symbol :param dict [params]: extra parameters specific to the hitbtc api """ await self.load_markets() await self.authenticate() url = self.urls['api']['ws']['private'] splitName = name.split('_subscribe') messageHash = self.safe_string(splitName, 0) if symbol is not None: messageHash = messageHash + '::' + symbol subscribe: dict = { 'method': name, 'params': params, 'id': self.nonce(), } return await self.watch(url, messageHash, subscribe, messageHash) async def trade_request(self, name: str, params={}): """ @ignore :param str name: websocket endpoint name :param dict [params]: extra parameters specific to the hitbtc api """ await self.load_markets() await self.authenticate() url = self.urls['api']['ws']['private'] messageHash = str(self.nonce()) subscribe: dict = { 'method': name, 'params': params, 'id': messageHash, } return await self.watch(url, messageHash, subscribe, messageHash) async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook: """ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data https://api.hitbtc.com/#subscribe-to-full-order-book https://api.hitbtc.com/#subscribe-to-partial-order-book https://api.hitbtc.com/#subscribe-to-partial-order-book-in-batches https://api.hitbtc.com/#subscribe-to-top-of-book https://api.hitbtc.com/#subscribe-to-top-of-book-in-batches :param str symbol: unified symbol of the market to fetch the order book for :param int [limit]: the maximum amount of order book entries to return :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.method]: 'orderbook/full', 'orderbook/{depth}/{speed}', 'orderbook/{depth}/{speed}/batch' :param int [params.depth]: 5 , 10, or 20(default) :param int [params.speed]: 100(default), 500, or 1000 :returns dict: A dictionary of `order book structures ` indexed by market symbols """ options = self.safe_value(self.options, 'watchOrderBook') defaultMethod = self.safe_string(options, 'method', 'orderbook/full') name = self.safe_string_2(params, 'method', 'defaultMethod', defaultMethod) depth = self.safe_string(params, 'depth', '20') speed = self.safe_string(params, 'depth', '100') if name == 'orderbook/{depth}/{speed}': name = 'orderbook/D' + depth + '/' + speed + 'ms' elif name == 'orderbook/{depth}/{speed}/batch': name = 'orderbook/D' + depth + '/' + speed + 'ms/batch' market = self.market(symbol) request: dict = { 'params': { 'symbols': [market['id']], }, } orderbook = await self.subscribe_public(name, 'orderbooks', [symbol], self.deep_extend(request, params)) return orderbook.limit() def handle_order_book(self, client: Client, message): # # { # "ch": "orderbook/full", # Channel # "snapshot": { # "ETHBTC": { # "t": 1626866578796, # Timestamp in milliseconds # "s": 27617207, # Sequence number # "a": [ # Asks # ["0.060506", "0"], # ["0.060549", "12.6431"], # ["0.060570", "0"], # ["0.060612", "0"] # ], # "b": [ # Bids # ["0.060439", "4.4095"], # ["0.060414", "0"], # ["0.060407", "7.3349"], # ["0.060390", "0"] # ] # } # } # } # snapshot = self.safe_dict(message, 'snapshot') update = self.safe_dict(message, 'update') data = snapshot if snapshot else update type = 'snapshot' if snapshot else 'update' marketIds = list(data.keys()) for i in range(0, len(marketIds)): marketId = marketIds[i] market = self.safe_market(marketId) symbol = market['symbol'] item = data[marketId] messageHash = 'orderbooks::' + symbol if not (symbol in self.orderbooks): subscription = self.safe_dict(client.subscriptions, messageHash, {}) limit = self.safe_integer(subscription, 'limit') self.orderbooks[symbol] = self.order_book({}, limit) orderbook = self.orderbooks[symbol] timestamp = self.safe_integer(item, 't') nonce = self.safe_integer(item, 's') if type == 'snapshot': parsedSnapshot = self.parse_order_book(item, symbol, timestamp, 'b', 'a') orderbook.reset(parsedSnapshot) else: asks = self.safe_list(item, 'a', []) bids = self.safe_list(item, 'b', []) self.handle_deltas(orderbook['asks'], asks) self.handle_deltas(orderbook['bids'], bids) orderbook['timestamp'] = timestamp orderbook['datetime'] = self.iso8601(timestamp) orderbook['nonce'] = nonce orderbook['symbol'] = symbol self.orderbooks[symbol] = orderbook client.resolve(orderbook, messageHash) def handle_delta(self, bookside, delta): price = self.safe_number(delta, 0) amount = self.safe_number(delta, 1) bookside.store(price, amount) def handle_deltas(self, bookside, deltas): for i in range(0, len(deltas)): self.handle_delta(bookside, deltas[i]) async def watch_ticker(self, symbol: str, params={}) -> Ticker: """ watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market https://api.hitbtc.com/#subscribe-to-ticker https://api.hitbtc.com/#subscribe-to-ticker-in-batches https://api.hitbtc.com/#subscribe-to-mini-ticker https://api.hitbtc.com/#subscribe-to-mini-ticker-in-batches :param str symbol: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.method]: 'ticker/{speed}'(default), or 'ticker/price/{speed}' :param str [params.speed]: '1s'(default), or '3s' :returns dict: a `ticker structure ` """ ticker = await self.watch_tickers([symbol], params) return self.safe_value(ticker, symbol) async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers: """ watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market :param str[] [symbols]: :param dict params: extra parameters specific to the exchange API endpoint :param str params['method']: 'ticker/{speed}' ,'ticker/price/{speed}', 'ticker/{speed}/batch'(default), or 'ticker/{speed}/price/batch'' :param str params['speed']: '1s'(default), or '3s' :returns dict: a `ticker structure ` """ await self.load_markets() symbols = self.market_symbols(symbols) options = self.safe_value(self.options, 'watchTicker') defaultMethod = self.safe_string(options, 'method', 'ticker/{speed}/batch') method = self.safe_string_2(params, 'method', 'defaultMethod', defaultMethod) speed = self.safe_string(params, 'speed', '1s') name = self.implode_params(method, {'speed': speed}) params = self.omit(params, ['method', 'speed']) marketIds = [] if symbols is None: marketIds.append('*') else: for i in range(0, len(symbols)): marketId = self.market_id(symbols[i]) marketIds.append(marketId) request: dict = { 'params': { 'symbols': marketIds, }, } newTickers = await self.subscribe_public(name, 'tickers', symbols, self.deep_extend(request, params)) if self.newUpdates: if not isinstance(newTickers, list): tickers: dict = {} tickers[newTickers['symbol']] = newTickers return tickers return self.filter_by_array(newTickers, 'symbol', symbols) def handle_ticker(self, client: Client, message): # # { # "ch": "ticker/1s", # "data": { # "ETHBTC": { # "t": 1614815872000, # Timestamp in milliseconds # "a": "0.031175", # Best ask # "A": "0.03329", # Best ask quantity # "b": "0.031148", # Best bid # "B": "0.10565", # Best bid quantity # "c": "0.031210", # Last price # "o": "0.030781", # Open price # "h": "0.031788", # High price # "l": "0.030733", # Low price # "v": "62.587", # Base asset volume # "q": "1.951420577", # Quote asset volume # "p": "0.000429", # Price change # "P": "1.39", # Price change percent # "L": 1182694927 # Last trade identifier # } # } # } # # { # "ch": "ticker/price/1s", # "data": { # "BTCUSDT": { # "t": 1614815872030, # "o": "32636.79", # "c": "32085.51", # "h": "33379.92", # "l": "30683.28", # "v": "11.90667", # "q": "384081.1955629" # } # } # } # data = self.safe_value(message, 'data', {}) marketIds = list(data.keys()) result = [] topic = 'tickers' for i in range(0, len(marketIds)): marketId = marketIds[i] market = self.safe_market(marketId) symbol = market['symbol'] ticker = self.parse_ws_ticker(data[marketId], market) self.tickers[symbol] = ticker result.append(ticker) messageHash = topic + '::' + symbol client.resolve(ticker, messageHash) client.resolve(result, topic) def parse_ws_ticker(self, ticker, market=None): # # { # "t": 1614815872000, # Timestamp in milliseconds # "a": "0.031175", # Best ask # "A": "0.03329", # Best ask quantity # "b": "0.031148", # Best bid # "B": "0.10565", # Best bid quantity # "c": "0.031210", # Last price # "o": "0.030781", # Open price # "h": "0.031788", # High price # "l": "0.030733", # Low price # "v": "62.587", # Base asset volume # "q": "1.951420577", # Quote asset volume # "p": "0.000429", # Price change # "P": "1.39", # Price change percent # "L": 1182694927 # Last trade identifier # } # # { # "t": 1614815872030, # "o": "32636.79", # "c": "32085.51", # "h": "33379.92", # "l": "30683.28", # "v": "11.90667", # "q": "384081.1955629" # } # timestamp = self.safe_integer(ticker, 't') symbol = self.safe_symbol(None, market) last = self.safe_string(ticker, 'c') return self.safe_ticker({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'high': self.safe_string(ticker, 'h'), 'low': self.safe_string(ticker, 'l'), 'bid': self.safe_string(ticker, 'b'), 'bidVolume': self.safe_string(ticker, 'B'), 'ask': self.safe_string(ticker, 'a'), 'askVolume': self.safe_string(ticker, 'A'), 'vwap': None, 'open': self.safe_string(ticker, 'o'), 'close': last, 'last': last, 'previousClose': None, 'change': None, 'percentage': None, 'average': None, 'baseVolume': self.safe_string(ticker, 'v'), 'quoteVolume': self.safe_string(ticker, 'q'), 'info': ticker, }, market) async def watch_bids_asks(self, symbols: Strings = None, params={}) -> Tickers: """ watches best bid & ask for symbols https://api.hitbtc.com/#subscribe-to-top-of-book :param str[] symbols: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.method]: 'orderbook/top/{speed}' or 'orderbook/top/{speed}/batch(default)' :param str [params.speed]: '100ms'(default) or '500ms' or '1000ms' :returns dict: a `ticker structure ` """ await self.load_markets() symbols = self.market_symbols(symbols, None, False) options = self.safe_value(self.options, 'watchBidsAsks') defaultMethod = self.safe_string(options, 'method', 'orderbook/top/{speed}/batch') method = self.safe_string_2(params, 'method', 'defaultMethod', defaultMethod) speed = self.safe_string(params, 'speed', '100ms') name = self.implode_params(method, {'speed': speed}) params = self.omit(params, ['method', 'speed']) marketIds = self.market_ids(symbols) request: dict = { 'params': { 'symbols': marketIds, }, } newTickers = await self.subscribe_public(name, 'bidask', symbols, self.deep_extend(request, params)) if self.newUpdates: if not isinstance(newTickers, list): tickers: dict = {} tickers[newTickers['symbol']] = newTickers return tickers return self.filter_by_array(newTickers, 'symbol', symbols) def handle_bid_ask(self, client: Client, message): # # { # "ch": "orderbook/top/100ms", # or 'orderbook/top/100ms/batch' # "data": { # "BTCUSDT": { # "t": 1727276919771, # "a": "63931.45", # "A": "0.02879", # "b": "63926.97", # "B": "0.00100" # } # } # } # data = self.safe_dict(message, 'data', {}) marketIds = list(data.keys()) result = [] topic = 'bidask' for i in range(0, len(marketIds)): marketId = marketIds[i] market = self.safe_market(marketId) symbol = market['symbol'] ticker = self.parse_ws_bid_ask(data[marketId], market) self.bidsasks[symbol] = ticker result.append(ticker) messageHash = topic + '::' + symbol client.resolve(ticker, messageHash) client.resolve(result, topic) def parse_ws_bid_ask(self, ticker, market=None): timestamp = self.safe_integer(ticker, 't') return self.safe_ticker({ 'symbol': market['symbol'], 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'ask': self.safe_string(ticker, 'a'), 'askVolume': self.safe_string(ticker, 'A'), 'bid': self.safe_string(ticker, 'b'), 'bidVolume': self.safe_string(ticker, 'B'), 'info': ticker, }, market) async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ get the list of most recent trades for a particular symbol https://api.hitbtc.com/#subscribe-to-trades :param str symbol: unified symbol of the market to fetch trades for :param int [since]: timestamp in ms of the earliest trade to fetch :param int [limit]: the maximum amount of trades to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `trade structures ` """ await self.load_markets() market = self.market(symbol) request: dict = { 'params': { 'symbols': [market['id']], }, } if limit is not None: request['limit'] = limit name = 'trades' trades = await self.subscribe_public(name, 'trades', [symbol], self.deep_extend(request, params)) if self.newUpdates: limit = trades.getLimit(symbol, limit) return self.filter_by_since_limit(trades, since, limit, 'timestamp') def handle_trades(self, client: Client, message): # # { # "result": { # "ch": "trades", # Channel # "subscriptions": ["ETHBTC", "BTCUSDT"] # }, # "id": 123 # } # # Notification snapshot # # { # "ch": "trades", # Channel # "snapshot": { # "BTCUSDT": [{ # "t": 1626861109494, # Timestamp in milliseconds # "i": 1555634969, # Trade identifier # "p": "30881.96", # Price # "q": "12.66828", # Quantity # "s": "buy" # Side # }] # } # } # # Notification update # # { # "ch": "trades", # "update": { # "BTCUSDT": [{ # "t": 1626861123552, # "i": 1555634969, # "p": "30877.68", # "q": "0.00006", # "s": "sell" # }] # } # } # data = self.safe_value_2(message, 'snapshot', 'update', {}) marketIds = list(data.keys()) for i in range(0, len(marketIds)): marketId = marketIds[i] market = self.safe_market(marketId) tradesLimit = self.safe_integer(self.options, 'tradesLimit', 1000) symbol = market['symbol'] stored = self.safe_value(self.trades, symbol) if stored is None: stored = ArrayCache(tradesLimit) self.trades[symbol] = stored trades = self.parse_ws_trades(data[marketId], market) for j in range(0, len(trades)): stored.append(trades[j]) messageHash = 'trades::' + symbol client.resolve(stored, messageHash) return message def parse_ws_trades(self, trades, market: object = None, since: Int = None, limit: Int = None, params={}): trades = self.to_array(trades) result = [] for i in range(0, len(trades)): trade = self.extend(self.parse_ws_trade(trades[i], market), params) result.append(trade) result = self.sort_by_2(result, 'timestamp', 'id') symbol = self.safe_string(market, 'symbol') return self.filter_by_symbol_since_limit(result, symbol, since, limit) def parse_ws_trade(self, trade, market=None): # # { # "t": 1626861123552, # Timestamp in milliseconds # "i": 1555634969, # Trade identifier # "p": "30877.68", # Price # "q": "0.00006", # Quantity # "s": "sell" # Side # } # timestamp = self.safe_integer(trade, 't') return self.safe_trade({ 'info': trade, 'id': self.safe_string(trade, 'i'), 'order': None, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'symbol': self.safe_string(market, 'symbol'), 'type': None, 'side': self.safe_string(trade, 's'), 'takerOrMaker': None, 'price': self.safe_string(trade, 'p'), 'amount': self.safe_string(trade, 'q'), 'cost': None, 'fee': None, }, market) async def watch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]: """ watches historical candlestick data containing the open, high, low, and close price, and the volume of a market https://api.hitbtc.com/#subscribe-to-candles :param str symbol: unified symbol of the market to fetch OHLCV data for :param str [timeframe]: the length of time each candle represents :param int [since]: not used by hitbtc watchOHLCV :param int [limit]: 0 – 1000, default value = 0(no history returned) :param dict [params]: extra parameters specific to the exchange API endpoint :returns int[][]: A list of candles ordered, open, high, low, close, volume """ period = self.safe_string(self.timeframes, timeframe, timeframe) name = 'candles/' + period market = self.market(symbol) request: dict = { 'params': { 'symbols': [market['id']], }, } if limit is not None: request['params']['limit'] = limit ohlcv = await self.subscribe_public(name, 'candles', [symbol], self.deep_extend(request, params)) if self.newUpdates: limit = ohlcv.getLimit(symbol, limit) return self.filter_by_since_limit(ohlcv, since, limit, 0) def handle_ohlcv(self, client: Client, message): # # { # "ch": "candles/M1", # Channel # "snapshot": { # "BTCUSDT": [{ # "t": 1626860340000, # Message timestamp # "o": "30881.95", # Open price # "c": "30890.96", # Last price # "h": "30900.8", # High price # "l": "30861.27", # Low price # "v": "1.27852", # Base asset volume # "q": "39493.9021811" # Quote asset volume # } # ... # ] # } # } # # { # "ch": "candles/M1", # "update": { # "ETHBTC": [{ # "t": 1626860880000, # "o": "0.060711", # "c": "0.060749", # "h": "0.060749", # "l": "0.060711", # "v": "12.2800", # "q": "0.7455339675" # }] # } # } # data = self.safe_value_2(message, 'snapshot', 'update', {}) marketIds = list(data.keys()) channel = self.safe_string(message, 'ch') splitChannel = channel.split('/') period = self.safe_string(splitChannel, 1) timeframe = self.find_timeframe(period) for i in range(0, len(marketIds)): marketId = marketIds[i] market = self.safe_market(marketId) symbol = market['symbol'] self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {}) stored = self.safe_value(self.ohlcvs[symbol], timeframe) if stored is None: limit = self.safe_integer(self.options, 'OHLCVLimit', 1000) stored = ArrayCacheByTimestamp(limit) self.ohlcvs[symbol][timeframe] = stored ohlcvs = self.parse_ws_ohlcvs(data[marketId], market) for j in range(0, len(ohlcvs)): stored.append(ohlcvs[j]) messageHash = 'candles::' + symbol client.resolve(stored, messageHash) return message def parse_ws_ohlcv(self, ohlcv, market=None) -> list: # # { # "t": 1626860340000, # Message timestamp # "o": "30881.95", # Open price # "c": "30890.96", # Last price # "h": "30900.8", # High price # "l": "30861.27", # Low price # "v": "1.27852", # Base asset volume # "q": "39493.9021811" # Quote asset volume # } # return [ self.safe_integer(ohlcv, 't'), self.safe_number(ohlcv, 'o'), self.safe_number(ohlcv, 'h'), self.safe_number(ohlcv, 'l'), self.safe_number(ohlcv, 'c'), self.safe_number(ohlcv, 'v'), ] async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ watches information on multiple orders made by the user https://api.hitbtc.com/#subscribe-to-reports https://api.hitbtc.com/#subscribe-to-reports-2 https://api.hitbtc.com/#subscribe-to-reports-3 :param str [symbol]: unified CCXT market symbol :param int [since]: timestamp in ms of the earliest order to fetch :param int [limit]: the maximum amount of orders to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `order structures ` """ await self.load_markets() marketType = None market = None if symbol is not None: market = self.market(symbol) marketType, params = self.handle_market_type_and_params('watchOrders', market, params) name = self.get_supported_mapping(marketType, { 'spot': 'spot_subscribe', 'margin': 'margin_subscribe', 'swap': 'futures_subscribe', 'future': 'futures_subscribe', }) orders = await self.subscribe_private(name, symbol, params) if self.newUpdates: limit = orders.getLimit(symbol, limit) return self.filter_by_since_limit(orders, since, limit, 'timestamp') def handle_order(self, client: Client, message): # # { # "jsonrpc": "2.0", # "method": "spot_order", # "margin_order", "future_order" # "params": { # "id": 584244931496, # "client_order_id": "b5acd79c0a854b01b558665bcf379456", # "symbol": "BTCUSDT", # "side": "buy", # "status": "new", # "type": "limit", # "time_in_force": "GTC", # "quantity": "0.01000", # "quantity_cumulative": "0", # "price": "0.01", # only updates and snapshots # "post_only": False, # "reduce_only": False, # only margin and contract # "display_quantity": "0", # only updates and snapshot # "created_at": "2021-07-02T22:52:32.864Z", # "updated_at": "2021-07-02T22:52:32.864Z", # "trade_id": 1361977606, # only trades # "trade_quantity": "0.00001", # only trades # "trade_price": "49595.04", # only trades # "trade_fee": "0.001239876000", # only trades # "trade_taker": True, # only trades, only spot # "trade_position_id": 485308, # only trades, only margin # "report_type": "new" # "trade", "status"(snapshot) # } # } # # { # "jsonrpc": "2.0", # "method": "spot_orders", # "margin_orders", "future_orders" # "params": [ # { # "id": 584244931496, # "client_order_id": "b5acd79c0a854b01b558665bcf379456", # "symbol": "BTCUSDT", # "side": "buy", # "status": "new", # "type": "limit", # "time_in_force": "GTC", # "quantity": "0.01000", # "quantity_cumulative": "0", # "price": "0.01", # only updates and snapshots # "post_only": False, # "reduce_only": False, # only margin and contract # "display_quantity": "0", # only updates and snapshot # "created_at": "2021-07-02T22:52:32.864Z", # "updated_at": "2021-07-02T22:52:32.864Z", # "trade_id": 1361977606, # only trades # "trade_quantity": "0.00001", # only trades # "trade_price": "49595.04", # only trades # "trade_fee": "0.001239876000", # only trades # "trade_taker": True, # only trades, only spot # "trade_position_id": 485308, # only trades, only margin # "report_type": "new" # "trade", "status"(snapshot) # } # ] # } # if self.orders is None: limit = self.safe_integer(self.options, 'ordersLimit') self.orders = ArrayCacheBySymbolById(limit) data = self.safe_value(message, 'params', []) if isinstance(data, list): for i in range(0, len(data)): order = data[i] self.handle_order_helper(client, message, order) else: self.handle_order_helper(client, message, data) return message def handle_order_helper(self, client: Client, message, order): orders = self.orders marketId = self.safe_string_lower_2(order, 'instrument', 'symbol') method = self.safe_string(message, 'method') splitMethod = method.split('_order') messageHash = self.safe_string(splitMethod, 0) symbol = self.safe_symbol(marketId) parsed = self.parse_order(order) orders.append(parsed) client.resolve(orders, messageHash) client.resolve(orders, messageHash + '::' + symbol) def parse_ws_order_trade(self, trade, market=None): # # { # "id": 584244931496, # "client_order_id": "b5acd79c0a854b01b558665bcf379456", # "symbol": "BTCUSDT", # "side": "buy", # "status": "new", # "type": "limit", # "time_in_force": "GTC", # "quantity": "0.01000", # "quantity_cumulative": "0", # "price": "0.01", # only updates and snapshots # "post_only": False, # "reduce_only": False, # only margin and contract # "display_quantity": "0", # only updates and snapshot # "created_at": "2021-07-02T22:52:32.864Z", # "updated_at": "2021-07-02T22:52:32.864Z", # "trade_id": 1361977606, # only trades # "trade_quantity": "0.00001", # only trades # "trade_price": "49595.04", # only trades # "trade_fee": "0.001239876000", # only trades # "trade_taker": True, # only trades, only spot # "trade_position_id": 485308, # only trades, only margin # "report_type": "new" # "trade", "status"(snapshot) # } # timestamp = self.safe_integer(trade, 'created_at') marketId = self.safe_string(trade, 'symbol') return self.safe_trade({ 'info': trade, 'id': self.safe_string(trade, 'trade_id'), 'order': self.safe_string(trade, 'id'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'symbol': self.safe_market(marketId, market), 'type': None, 'side': self.safe_string(trade, 'side'), 'takerOrMaker': self.safe_string(trade, 'trade_taker'), 'price': self.safe_string(trade, 'trade_price'), 'amount': self.safe_string(trade, 'trade_quantity'), 'cost': None, 'fee': { 'cost': self.safe_string(trade, 'trade_fee'), 'currency': None, 'rate': None, }, }, market) def parse_ws_order(self, order, market=None): # # { # "id": 584244931496, # "client_order_id": "b5acd79c0a854b01b558665bcf379456", # "symbol": "BTCUSDT", # "side": "buy", # "status": "new", # "type": "limit", # "time_in_force": "GTC", # "quantity": "0.01000", # "quantity_cumulative": "0", # "price": "0.01", # only updates and snapshots # "post_only": False, # "reduce_only": False, # only margin and contract # "display_quantity": "0", # only updates and snapshot # "created_at": "2021-07-02T22:52:32.864Z", # "updated_at": "2021-07-02T22:52:32.864Z", # "trade_id": 1361977606, # only trades # "trade_quantity": "0.00001", # only trades # "trade_price": "49595.04", # only trades # "trade_fee": "0.001239876000", # only trades # "trade_taker": True, # only trades, only spot # "trade_position_id": 485308, # only trades, only margin # "report_type": "new" # "trade", "status"(snapshot) # } # timestamp = self.safe_string(order, 'created_at') marketId = self.safe_string(order, 'symbol') market = self.safe_market(marketId, market) tradeId = self.safe_string(order, 'trade_id') trades = None if tradeId is not None: trade = self.parse_ws_order_trade(order, market) trades = [trade] rawStatus = self.safe_string(order, 'status') report_type = self.safe_string(order, 'report_type') parsedStatus = None if report_type == 'canceled': parsedStatus = self.parse_order_status(report_type) else: parsedStatus = self.parse_order_status(rawStatus) return self.safe_order({ 'info': order, 'id': self.safe_string(order, 'id'), 'clientOrderId': self.safe_string(order, 'client_order_id'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'lastTradeTimestamp': None, 'symbol': market['symbol'], 'price': self.safe_string(order, 'price'), 'amount': self.safe_string(order, 'quantity'), 'type': self.safe_string(order, 'type'), 'side': self.safe_string_upper(order, 'side'), 'timeInForce': self.safe_string(order, 'time_in_force'), 'postOnly': self.safe_string(order, 'post_only'), 'reduceOnly': self.safe_value(order, 'reduce_only'), 'filled': None, 'remaining': None, 'cost': None, 'status': parsedStatus, 'average': None, 'trades': trades, 'fee': None, }, market) async def watch_balance(self, params={}) -> Balances: """ watches balance updates, cannot subscribe to margin account balances https://api.hitbtc.com/#subscribe-to-spot-balances https://api.hitbtc.com/#subscribe-to-futures-balances :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.type]: 'spot', 'swap', or 'future' EXCHANGE SPECIFIC PARAMETERS :param str [params.mode]: 'updates' or 'batches'(default), 'updates' = messages arrive after balance updates, 'batches' = messages arrive at equal intervals if there were any updates :returns dict[]: a list of `balance structures ` """ await self.load_markets() type = None type, params = self.handle_market_type_and_params('watchBalance', None, params) name = self.get_supported_mapping(type, { 'spot': 'spot_balance_subscribe', 'swap': 'futures_balance_subscribe', 'future': 'futures_balance_subscribe', }) mode = self.safe_string(params, 'mode', 'batches') params = self.omit(params, 'mode') request: dict = { 'mode': mode, } return await self.subscribe_private(name, None, self.extend(request, params)) async def create_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}) -> Order: """ create a trade order https://api.hitbtc.com/#create-new-spot-order https://api.hitbtc.com/#create-margin-order https://api.hitbtc.com/#create-futures-order :param str symbol: unified symbol of the market to create an order in :param str type: 'market' or 'limit' :param str side: 'buy' or 'sell' :param float amount: how much of currency you want to trade in units of base currency :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.marginMode]: 'cross' or 'isolated' only 'isolated' is supported for spot-margin, swap supports both, default is 'cross' :param bool [params.margin]: True for creating a margin order :param float [params.triggerPrice]: The price at which a trigger order is triggered at :param bool [params.postOnly]: if True, the order will only be posted to the order book and not executed immediately :param str [params.timeInForce]: "GTC", "IOC", "FOK", "Day", "GTD" :returns dict: an `order structure ` """ await self.load_markets() market = self.market(symbol) request = None marketType = None marketType, params = self.handle_market_type_and_params('createOrder', market, params) marginMode = None marginMode, params = self.handle_margin_mode_and_params('createOrder', params) request, params = self.create_order_request(market, marketType, type, side, amount, price, marginMode, params) request = self.extend(request, params) if marketType == 'swap': return await self.trade_request('futures_new_order', request) elif (marketType == 'margin') or (marginMode is not None): return await self.trade_request('margin_new_order', request) else: return await self.trade_request('spot_new_order', request) async def cancel_order_ws(self, id: str, symbol: Str = None, params={}) -> Order: """ https://api.hitbtc.com/#cancel-spot-order-2 https://api.hitbtc.com/#cancel-futures-order-2 https://api.hitbtc.com/#cancel-margin-order-2 cancels an open order :param str id: order id :param str symbol: unified symbol of the market the order was made in :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.marginMode]: 'cross' or 'isolated' only 'isolated' is supported :param bool [params.margin]: True for canceling a margin order :returns dict: An `order structure ` """ await self.load_markets() market = None request = { 'client_order_id': id, } if symbol is not None: market = self.market(symbol) marketType = None marketType, params = self.handle_market_type_and_params('cancelOrderWs', market, params) marginMode, query = self.handle_margin_mode_and_params('cancelOrderWs', params) request = self.extend(request, query) if marketType == 'swap': return await self.trade_request('futures_cancel_order', request) elif (marketType == 'margin') or (marginMode is not None): return await self.trade_request('margin_cancel_order', request) else: return await self.trade_request('spot_cancel_order', request) async def cancel_all_orders_ws(self, symbol: Str = None, params={}) -> List[Order]: """ https://api.hitbtc.com/#cancel-spot-orders https://api.hitbtc.com/#cancel-futures-order-3 cancel all open orders :param str symbol: unified market symbol, only orders in the market of self symbol are cancelled when symbol is not None :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.marginMode]: 'cross' or 'isolated' only 'isolated' is supported :param bool [params.margin]: True for canceling margin orders :returns dict[]: a list of `order structures ` """ await self.load_markets() market = None if symbol is not None: market = self.market(symbol) marketType = None marketType, params = self.handle_market_type_and_params('cancelAllOrdersWs', market, params) marginMode = None marginMode, params = self.handle_margin_mode_and_params('cancelAllOrdersWs', params) if marketType == 'swap': return await self.trade_request('futures_cancel_orders', params) elif (marketType == 'margin') or (marginMode is not None): raise NotSupported(self.id + ' cancelAllOrdersWs is not supported for margin orders') else: return await self.trade_request('spot_cancel_orders', params) async def fetch_open_orders_ws(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ https://api.hitbtc.com/#get-active-futures-orders-2 https://api.hitbtc.com/#get-margin-orders https://api.hitbtc.com/#get-active-spot-orders fetch all unfilled currently open orders :param str symbol: unified market symbol :param int [since]: the earliest time in ms to fetch open orders for :param int [limit]: the maximum number of open orders structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.marginMode]: 'cross' or 'isolated' only 'isolated' is supported :param bool [params.margin]: True for fetching open margin orders :returns Order[]: a list of `order structures ` """ await self.load_markets() market = None request: dict = {} if symbol is not None: market = self.market(symbol) request['symbol'] = market['id'] marketType = None marketType, params = self.handle_market_type_and_params('fetchOpenOrdersWs', market, params) marginMode = None marginMode, params = self.handle_margin_mode_and_params('fetchOpenOrdersWs', params) if marketType == 'swap': return await self.trade_request('futures_get_orders', request) elif (marketType == 'margin') or (marginMode is not None): return await self.trade_request('margin_get_orders', request) else: return await self.trade_request('spot_get_orders', request) def handle_balance(self, client: Client, message): # # { # "jsonrpc": "2.0", # "method": "futures_balance", # "params": [ # { # "currency": "BCN", # "available": "100.000000000000", # "reserved": "0", # "reserved_margin": "0" # }, # ... # ] # } # messageHash = self.safe_string(message, 'method') params = self.safe_value(message, 'params') balance = self.parse_balance(params) self.balance = self.deep_extend(self.balance, balance) client.resolve(self.balance, messageHash) def handle_notification(self, client: Client, message): # # {jsonrpc: "2.0", result: True, id: null} # return message def handle_order_request(self, client: Client, message): # # createOrderWs, cancelOrderWs # # { # "jsonrpc": "2.0", # "result": { # "id": 1130310696965, # "client_order_id": "OPC2oyHSkEBqIpPtniLqeW-597hUL3Yo", # "symbol": "ADAUSDT", # "side": "buy", # "status": "new", # "type": "limit", # "time_in_force": "GTC", # "quantity": "4", # "quantity_cumulative": "0", # "price": "0.3300000", # "post_only": False, # "created_at": "2023-11-17T14:58:15.903Z", # "updated_at": "2023-11-17T14:58:15.903Z", # "original_client_order_id": "d6b645556af740b1bd1683400fd9cbce", # spot_replace_order only # "report_type": "new" # "margin_mode": "isolated", # margin and future only # "reduce_only": False, # margin and future only # }, # "id": 1700233093414 # } # messageHash = self.safe_string(message, 'id') result = self.safe_value(message, 'result', {}) if isinstance(result, list): parsedOrders = [] for i in range(0, len(result)): parsedOrder = self.parse_ws_order(result[i]) parsedOrders.append(parsedOrder) client.resolve(parsedOrders, messageHash) else: parsedOrder = self.parse_ws_order(result) client.resolve(parsedOrder, messageHash) return message def handle_message(self, client: Client, message): if self.handle_error(client, message): return channel = self.safe_string_2(message, 'ch', 'method') if channel is not None: splitChannel = channel.split('/') channel = self.safe_string(splitChannel, 0) if channel == 'orderbook': channel2 = self.safe_string(splitChannel, 1) if channel2 is not None and channel2 == 'top': channel = 'orderbook/top' methods: dict = { 'candles': self.handle_ohlcv, 'ticker': self.handle_ticker, 'trades': self.handle_trades, 'orderbook': self.handle_order_book, 'orderbook/top': self.handle_bid_ask, 'spot_order': self.handle_order, 'spot_orders': self.handle_order, 'margin_order': self.handle_order, 'margin_orders': self.handle_order, 'futures_order': self.handle_order, 'futures_orders': self.handle_order, 'spot_balance': self.handle_balance, 'futures_balance': self.handle_balance, } method = self.safe_value(methods, channel) if method is not None: method(client, message) else: result = self.safe_value(message, 'result') clientOrderId = self.safe_string(result, 'client_order_id') if clientOrderId is not None: self.handle_order_request(client, message) if (result is True) and not ('id' in message): self.handle_authenticate(client, message) if isinstance(result, list): # to do improve self, not very reliable right now first = self.safe_value(result, 0, {}) arrayLength = len(result) if (arrayLength == 0) or ('client_order_id' in first): self.handle_order_request(client, message) def handle_authenticate(self, client: Client, message): # # { # "jsonrpc": "2.0", # "result": True # } # success = self.safe_value(message, 'result') messageHash = 'authenticated' if success: future = self.safe_value(client.futures, messageHash) future.resolve(True) else: error = AuthenticationError(self.id + ' ' + self.json(message)) client.reject(error, messageHash) if messageHash in client.subscriptions: del client.subscriptions[messageHash] return message def handle_error(self, client: Client, message): # # { # jsonrpc: '2.0', # error: { # code: 20001, # message: 'Insufficient funds', # description: 'Check that the funds are sufficient, given commissions' # }, # id: 1700228604325 # } # error = self.safe_value(message, 'error') if error is not None: try: code = self.safe_value(error, 'code') errorMessage = self.safe_string(error, 'message') description = self.safe_string(error, 'description') feedback = self.id + ' ' + description self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback) self.throw_broadly_matched_exception(self.exceptions['broad'], errorMessage, feedback) raise ExchangeError(feedback) # unknown message except Exception as e: if isinstance(e, AuthenticationError): messageHash = 'authenticated' client.reject(e, messageHash) if messageHash in client.subscriptions: del client.subscriptions[messageHash] else: id = self.safe_string(message, 'id') client.reject(e, id) return True return None