1322 lines
58 KiB
Python
1322 lines
58 KiB
Python
# -*- 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 <https://docs.ccxt.com/#/?id=order-book-structure>` 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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/en/latest/manual.html#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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=public-trades>`
|
||
"""
|
||
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 <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
|
||
"""
|
||
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 <https://docs.ccxt.com/#/?id=balance-structure>`
|
||
"""
|
||
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 <https://github.com/ccxt/ccxt/wiki/Manual#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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
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 <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
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
|