1250 lines
52 KiB
Python
1250 lines
52 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, Bool, 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 BadRequest
|
|
from ccxt.base.errors import InvalidOrder
|
|
from ccxt.base.precise import Precise
|
|
|
|
|
|
class poloniex(ccxt.async_support.poloniex):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(poloniex, self).describe(), {
|
|
'has': {
|
|
'ws': True,
|
|
'watchOHLCV': True,
|
|
'watchOrderBook': True,
|
|
'watchTicker': True,
|
|
'watchTickers': True,
|
|
'watchTrades': True,
|
|
'watchTradesForSymbols': True,
|
|
'watchBalance': True,
|
|
'watchStatus': False,
|
|
'watchOrders': True,
|
|
'watchMyTrades': True,
|
|
'createOrderWs': True,
|
|
'editOrderWs': False,
|
|
'fetchOpenOrdersWs': False,
|
|
'fetchOrderWs': False,
|
|
'cancelOrderWs': True,
|
|
'cancelOrdersWs': True,
|
|
'cancelAllOrdersWs': True,
|
|
'fetchTradesWs': False,
|
|
'fetchBalanceWs': False,
|
|
},
|
|
'urls': {
|
|
'api': {
|
|
'ws': {
|
|
'public': 'wss://ws.poloniex.com/ws/public',
|
|
'private': 'wss://ws.poloniex.com/ws/private',
|
|
},
|
|
},
|
|
},
|
|
'options': {
|
|
'createMarketBuyOrderRequiresPrice': True,
|
|
'tradesLimit': 1000,
|
|
'ordersLimit': 1000,
|
|
'OHLCVLimit': 1000,
|
|
'watchOrderBook': {
|
|
'name': 'book_lv2', # can also be 'book'
|
|
},
|
|
'connectionsLimit': 2000, # 2000 public, 2000 private, 4000 total, only for subscribe events, unsubscribe not restricted
|
|
'requestsLimit': 500, # per second, only for subscribe events, unsubscribe not restricted
|
|
'timeframes': {
|
|
'1m': 'candles_minute_1',
|
|
'5m': 'candles_minute_5',
|
|
'10m': 'candles_minute_10',
|
|
'15m': 'candles_minute_15',
|
|
'30m': 'candles_minute_30',
|
|
'1h': 'candles_hour_1',
|
|
'2h': 'candles_hour_2',
|
|
'4h': 'candles_hour_4',
|
|
'6h': 'candles_hour_6',
|
|
'12h': 'candles_hour_12',
|
|
'1d': 'candles_day_1',
|
|
'3d': 'candles_day_3',
|
|
'1w': 'candles_week_1',
|
|
'1M': 'candles_month_1',
|
|
},
|
|
},
|
|
'streaming': {
|
|
'keepAlive': 15000,
|
|
'ping': self.ping,
|
|
},
|
|
})
|
|
|
|
async def authenticate(self, params={}):
|
|
"""
|
|
@ignore
|
|
authenticates the user to access private web socket channels
|
|
|
|
https://api-docs.poloniex.com/spot/websocket/authentication
|
|
|
|
:returns dict: response from exchange
|
|
"""
|
|
self.check_required_credentials()
|
|
timestamp = self.number_to_string(self.milliseconds())
|
|
url = self.urls['api']['ws']['private']
|
|
messageHash = 'authenticated'
|
|
client = self.client(url)
|
|
future = self.safe_value(client.subscriptions, messageHash)
|
|
if future is None:
|
|
accessPath = '/ws'
|
|
requestString = 'GET\n' + accessPath + '\nsignTimestamp=' + timestamp
|
|
signature = self.hmac(self.encode(requestString), self.encode(self.secret), hashlib.sha256, 'base64')
|
|
request: dict = {
|
|
'event': 'subscribe',
|
|
'channel': ['auth'],
|
|
'params': {
|
|
'key': self.apiKey,
|
|
'signTimestamp': timestamp,
|
|
'signature': signature,
|
|
'signatureMethod': 'HmacSHA256', # optional
|
|
'signatureVersion': '2', # optional
|
|
},
|
|
}
|
|
message = self.extend(request, params)
|
|
future = await self.watch(url, messageHash, message, messageHash)
|
|
#
|
|
# {
|
|
# "data": {
|
|
# "success": True,
|
|
# "ts": 1645597033915
|
|
# },
|
|
# "channel": "auth"
|
|
# }
|
|
#
|
|
# # Failure to return results
|
|
#
|
|
# {
|
|
# "data": {
|
|
# "success": False,
|
|
# "message": "Authentication failed!",
|
|
# "ts": 1646276295075
|
|
# },
|
|
# "channel": "auth"
|
|
# }
|
|
#
|
|
client.subscriptions[messageHash] = future
|
|
return future
|
|
|
|
async def subscribe(self, name: str, messageHash: str, isPrivate: bool, symbols: Strings = None, params={}):
|
|
"""
|
|
@ignore
|
|
Connects to a websocket channel
|
|
:param str name: name of the channel
|
|
:param str messageHash: unique identifier for the message
|
|
:param boolean isPrivate: True for the authenticated url, False for the public url
|
|
:param str[] [symbols]: CCXT market symbols
|
|
:param dict [params]: extra parameters specific to the poloniex api
|
|
:returns dict: data from the websocket stream
|
|
"""
|
|
publicOrPrivate = 'private' if isPrivate else 'public'
|
|
url = self.urls['api']['ws'][publicOrPrivate]
|
|
subscribe: dict = {
|
|
'event': 'subscribe',
|
|
'channel': [
|
|
name,
|
|
],
|
|
}
|
|
marketIds = []
|
|
if self.is_empty(symbols):
|
|
marketIds.append('all')
|
|
else:
|
|
messageHash = messageHash + '::' + ','.join(symbols)
|
|
marketIds = self.market_ids(symbols)
|
|
if name != 'balances':
|
|
subscribe['symbols'] = marketIds
|
|
request = self.extend(subscribe, params)
|
|
return await self.watch(url, messageHash, request, messageHash)
|
|
|
|
async def trade_request(self, name: str, params={}):
|
|
"""
|
|
@ignore
|
|
Connects to a websocket channel
|
|
:param str name: name of the channel
|
|
:param dict [params]: extra parameters specific to the poloniex api
|
|
:returns dict: data from the websocket stream
|
|
"""
|
|
url = self.urls['api']['ws']['private']
|
|
messageHash = str(self.nonce())
|
|
subscribe: dict = {
|
|
'id': messageHash,
|
|
'event': name,
|
|
'params': params,
|
|
}
|
|
return await self.watch(url, messageHash, subscribe, messageHash)
|
|
|
|
async def create_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}) -> Order:
|
|
"""
|
|
|
|
https://api-docs.poloniex.com/spot/websocket/trade-request#create-order
|
|
|
|
create a trade 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 poloniex api endpoint
|
|
:param str [params.timeInForce]: GTC(default), IOC, FOK
|
|
:param str [params.clientOrderId]: Maximum 64-character length.*
|
|
:param float [params.cost]: *spot market buy only* the quote quantity that can be used alternative for the amount
|
|
|
|
EXCHANGE SPECIFIC PARAMETERS
|
|
:param str [params.amount]: quote units for the order
|
|
:param boolean [params.allowBorrow]: allow order to be placed by borrowing funds(Default: False)
|
|
:param str [params.stpMode]: self-trade prevention, defaults to expire_taker, none: enable self-trade; expire_taker: taker order will be canceled when self-trade happens
|
|
:param str [params.slippageTolerance]: used to control the maximum slippage ratio, the value range is greater than 0 and less than 1
|
|
:returns dict: an `order structure <https://github.com/ccxt/ccxt/wiki/Manual#order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
await self.authenticate()
|
|
market = self.market(symbol)
|
|
uppercaseType = type.upper()
|
|
uppercaseSide = side.upper()
|
|
isPostOnly = self.is_post_only(uppercaseType == 'MARKET', uppercaseType == 'LIMIT_MAKER', params)
|
|
if isPostOnly:
|
|
uppercaseType = 'LIMIT_MAKER'
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
'side': side.upper(),
|
|
'type': type.upper(),
|
|
}
|
|
if (uppercaseType == 'MARKET') and (uppercaseSide == 'BUY'):
|
|
quoteAmount = None
|
|
createMarketBuyOrderRequiresPrice = True
|
|
createMarketBuyOrderRequiresPrice, params = self.handle_option_and_params(params, 'createOrder', 'createMarketBuyOrderRequiresPrice', True)
|
|
cost = self.safe_number(params, 'cost')
|
|
params = self.omit(params, 'cost')
|
|
if cost is not None:
|
|
quoteAmount = self.cost_to_precision(symbol, cost)
|
|
elif createMarketBuyOrderRequiresPrice:
|
|
if price is None:
|
|
raise InvalidOrder(self.id + ' createOrder() requires the price argument for market buy orders to calculate the total cost to spend(amount * price), alternatively set the createMarketBuyOrderRequiresPrice option or param to False and pass the cost to spend(quote quantity) in the amount argument')
|
|
else:
|
|
amountString = self.number_to_string(amount)
|
|
priceString = self.number_to_string(price)
|
|
costRequest = Precise.string_mul(amountString, priceString)
|
|
quoteAmount = self.cost_to_precision(symbol, costRequest)
|
|
else:
|
|
quoteAmount = self.cost_to_precision(symbol, amount)
|
|
request['amount'] = quoteAmount
|
|
else:
|
|
request['quantity'] = self.amount_to_precision(market['symbol'], amount)
|
|
if price is not None:
|
|
request['price'] = self.price_to_precision(symbol, price)
|
|
orders = await self.trade_request('createOrder', self.extend(request, params))
|
|
order = self.safe_dict(orders, 0)
|
|
return order
|
|
|
|
async def cancel_order_ws(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
|
|
https://api-docs.poloniex.com/spot/websocket/trade-request#cancel-multiple-orders
|
|
|
|
cancel multiple orders
|
|
:param str id: order id
|
|
:param str [symbol]: unified market symbol
|
|
:param dict [params]: extra parameters specific to the poloniex api endpoint
|
|
:param str [params.clientOrderId]: client order id
|
|
:returns dict: an list of `order structures <https://github.com/ccxt/ccxt/wiki/Manual#order-structure>`
|
|
"""
|
|
clientOrderId = self.safe_string(params, 'clientOrderId')
|
|
if clientOrderId is not None:
|
|
clientOrderIds = self.safe_value(params, 'clientOrderId', [])
|
|
params['clientOrderIds'] = self.array_concat(clientOrderIds, [clientOrderId])
|
|
orders = await self.cancel_orders_ws([id], symbol, params)
|
|
order = self.safe_dict(orders, 0)
|
|
return order
|
|
|
|
async def cancel_orders_ws(self, ids: List[str], symbol: Str = None, params={}):
|
|
"""
|
|
|
|
https://api-docs.poloniex.com/spot/websocket/trade-request#cancel-multiple-orders
|
|
|
|
cancel multiple orders
|
|
:param str[] ids: order ids
|
|
:param str symbol: unified market symbol, default is None
|
|
:param dict [params]: extra parameters specific to the poloniex api endpoint
|
|
:param str[] [params.clientOrderIds]: client order ids
|
|
:returns dict: an list of `order structures <https://github.com/ccxt/ccxt/wiki/Manual#order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
await self.authenticate()
|
|
request: dict = {
|
|
'orderIds': ids,
|
|
}
|
|
return await self.trade_request('cancelOrders', self.extend(request, params))
|
|
|
|
async def cancel_all_orders_ws(self, symbol: Str = None, params={}) -> List[Order]:
|
|
"""
|
|
|
|
https://api-docs.poloniex.com/spot/websocket/trade-request#cancel-all-orders
|
|
|
|
cancel all open orders of a type. Only applicable to Option in Portfolio Margin mode, and MMP privilege is required.
|
|
: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 poloniex api endpoint
|
|
:returns dict[]: a list of `order structures <https://github.com/ccxt/ccxt/wiki/Manual#order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
await self.authenticate()
|
|
return await self.trade_request('cancelAllOrders', params)
|
|
|
|
def handle_order_request(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "id": "1234567",
|
|
# "data": [{
|
|
# "orderId": 205343650954092544,
|
|
# "clientOrderId": "",
|
|
# "message": "",
|
|
# "code": 200
|
|
# }]
|
|
# }
|
|
#
|
|
messageHash = self.safe_string(message, 'id')
|
|
data = self.safe_value(message, 'data', [])
|
|
orders = []
|
|
for i in range(0, len(data)):
|
|
order = data[i]
|
|
parsedOrder = self.parse_ws_order(order)
|
|
orders.append(parsedOrder)
|
|
client.resolve(orders, messageHash)
|
|
|
|
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-docs.poloniex.com/spot/websocket/market-data#candlesticks
|
|
|
|
: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]: timestamp in ms of the earliest candle to fetch
|
|
:param int [limit]: the maximum amount of candles to fetch
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
|
"""
|
|
await self.load_markets()
|
|
timeframes = self.safe_value(self.options, 'timeframes', {})
|
|
channel = self.safe_string(timeframes, timeframe, timeframe)
|
|
if channel is None:
|
|
raise BadRequest(self.id + ' watchOHLCV cannot take a timeframe of ' + timeframe)
|
|
ohlcv = await self.subscribe(channel, channel, False, [symbol], params)
|
|
if self.newUpdates:
|
|
limit = ohlcv.getLimit(symbol, limit)
|
|
return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
|
|
|
|
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-docs.poloniex.com/spot/websocket/market-data#ticker
|
|
|
|
:param str symbol: unified symbol of the market to fetch the ticker for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
symbol = self.symbol(symbol)
|
|
tickers = await self.watch_tickers([symbol], params)
|
|
return self.safe_value(tickers, 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
|
|
|
|
https://api-docs.poloniex.com/spot/websocket/market-data#ticker
|
|
|
|
:param str[] symbols:
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
name = 'ticker'
|
|
symbols = self.market_symbols(symbols)
|
|
newTickers = await self.subscribe(name, name, False, symbols, params)
|
|
if self.newUpdates:
|
|
return newTickers
|
|
return self.filter_by_array(self.tickers, 'symbol', symbols)
|
|
|
|
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-docs.poloniex.com/spot/websocket/market-data#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>`
|
|
"""
|
|
return await self.watch_trades_for_symbols([symbol], since, limit, params)
|
|
|
|
async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
"""
|
|
get the list of most recent trades for a list of symbols
|
|
|
|
https://api-docs.poloniex.com/spot/websocket/market-data#trades
|
|
|
|
:param str[] symbols: 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()
|
|
symbols = self.market_symbols(symbols, None, False, True, True)
|
|
name = 'trades'
|
|
url = self.urls['api']['ws']['public']
|
|
marketIds = self.market_ids(symbols)
|
|
subscribe: dict = {
|
|
'event': 'subscribe',
|
|
'channel': [
|
|
name,
|
|
],
|
|
'symbols': marketIds,
|
|
}
|
|
request = self.extend(subscribe, params)
|
|
messageHashes = []
|
|
if symbols is not None:
|
|
for i in range(0, len(symbols)):
|
|
messageHashes.append(name + '::' + symbols[i])
|
|
trades = await self.watch_multiple(url, messageHashes, request, messageHashes)
|
|
if self.newUpdates:
|
|
first = self.safe_value(trades, 0)
|
|
tradeSymbol = self.safe_string(first, 'symbol')
|
|
limit = trades.getLimit(tradeSymbol, limit)
|
|
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
|
|
|
|
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-docs.poloniex.com/spot/websocket/market-data#book-level-2
|
|
|
|
:param str symbol: unified symbol of the market to fetch the order book for
|
|
:param int [limit]: not used by poloniex watchOrderBook
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
|
"""
|
|
await self.load_markets()
|
|
watchOrderBookOptions = self.safe_value(self.options, 'watchOrderBook')
|
|
name = self.safe_string(watchOrderBookOptions, 'name', 'book_lv2')
|
|
name, params = self.handle_option_and_params(params, 'method', 'name', name)
|
|
orderbook = await self.subscribe(name, name, False, [symbol], params)
|
|
return orderbook.limit()
|
|
|
|
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-docs.poloniex.com/spot/websocket/order
|
|
|
|
:param str symbol: unified market symbol of the market orders were made in
|
|
:param int [since]: not used by poloniex watchOrders
|
|
:param int [limit]: not used by poloniex watchOrders
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
name = 'orders'
|
|
await self.authenticate()
|
|
if symbol is not None:
|
|
symbol = self.symbol(symbol)
|
|
symbols = None if (symbol is None) else [symbol]
|
|
orders = await self.subscribe(name, name, True, symbols, params)
|
|
if self.newUpdates:
|
|
limit = orders.getLimit(symbol, limit)
|
|
return self.filter_by_since_limit(orders, since, limit, 'timestamp', True)
|
|
|
|
async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
"""
|
|
watches information on multiple trades made by the user using orders stream
|
|
|
|
https://api-docs.poloniex.com/spot/websocket/order
|
|
|
|
:param str symbol: unified market symbol of the market orders were made in
|
|
:param int [since]: not used by poloniex watchMyTrades
|
|
:param int [limit]: not used by poloniex watchMyTrades
|
|
:param dict [params]: extra parameters specific to the poloniex strean
|
|
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
name = 'orders'
|
|
messageHash = 'myTrades'
|
|
await self.authenticate()
|
|
if symbol is not None:
|
|
symbol = self.symbol(symbol)
|
|
symbols = None if (symbol is None) else [symbol]
|
|
trades = await self.subscribe(name, messageHash, True, symbols, params)
|
|
if self.newUpdates:
|
|
limit = trades.getLimit(symbol, limit)
|
|
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
|
|
|
|
async def watch_balance(self, params={}) -> Balances:
|
|
"""
|
|
watch balance and get the amount of funds available for trading or funds locked in orders
|
|
|
|
https://api-docs.poloniex.com/spot/websocket/balance
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
name = 'balances'
|
|
await self.authenticate()
|
|
return await self.subscribe(name, name, True, None, params)
|
|
|
|
def parse_ws_ohlcv(self, ohlcv, market=None) -> list:
|
|
#
|
|
# {
|
|
# "symbol": "BTC_USDT",
|
|
# "amount": "840.7240416",
|
|
# "high": "24832.35",
|
|
# "quantity": "0.033856",
|
|
# "tradeCount": 1,
|
|
# "low": "24832.35",
|
|
# "closeTime": 1676942519999,
|
|
# "startTime": 1676942460000,
|
|
# "close": "24832.35",
|
|
# "open": "24832.35",
|
|
# "ts": 1676942492072
|
|
# }
|
|
#
|
|
return [
|
|
self.safe_integer(ohlcv, 'startTime'),
|
|
self.safe_number(ohlcv, 'open'),
|
|
self.safe_number(ohlcv, 'high'),
|
|
self.safe_number(ohlcv, 'low'),
|
|
self.safe_number(ohlcv, 'close'),
|
|
self.safe_number(ohlcv, 'quantity'),
|
|
]
|
|
|
|
def handle_ohlcv(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "channel": "candles_minute_1",
|
|
# "data": [
|
|
# {
|
|
# "symbol": "BTC_USDT",
|
|
# "amount": "840.7240416",
|
|
# "high": "24832.35",
|
|
# "quantity": "0.033856",
|
|
# "tradeCount": 1,
|
|
# "low": "24832.35",
|
|
# "closeTime": 1676942519999,
|
|
# "startTime": 1676942460000,
|
|
# "close": "24832.35",
|
|
# "open": "24832.35",
|
|
# "ts": 1676942492072
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_value(message, 'data')
|
|
data = self.safe_value(data, 0)
|
|
channel = self.safe_string(message, 'channel')
|
|
marketId = self.safe_string(data, 'symbol')
|
|
symbol = self.safe_symbol(marketId)
|
|
market = self.safe_market(symbol)
|
|
timeframes = self.safe_value(self.options, 'timeframes', {})
|
|
timeframe = self.find_timeframe(channel, timeframes)
|
|
messageHash = channel + '::' + symbol
|
|
parsed = self.parse_ws_ohlcv(data, market)
|
|
self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {})
|
|
stored = self.safe_value(self.ohlcvs[symbol], timeframe)
|
|
if symbol is not None:
|
|
if stored is None:
|
|
limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
|
|
stored = ArrayCacheByTimestamp(limit)
|
|
self.ohlcvs[symbol][timeframe] = stored
|
|
stored.append(parsed)
|
|
client.resolve(stored, messageHash)
|
|
return message
|
|
|
|
def handle_trade(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "channel": "trades",
|
|
# "data": [
|
|
# {
|
|
# "symbol": "BTC_USDT",
|
|
# "amount": "13.41634893",
|
|
# "quantity": "0.000537",
|
|
# "takerSide": "buy",
|
|
# "createTime": 1676950548834,
|
|
# "price": "24983.89",
|
|
# "id": "62486976",
|
|
# "ts": 1676950548839
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_value(message, 'data', [])
|
|
for i in range(0, len(data)):
|
|
item = data[i]
|
|
marketId = self.safe_string(item, 'symbol')
|
|
if marketId is not None:
|
|
trade = self.parse_ws_trade(item)
|
|
symbol = trade['symbol']
|
|
type = 'trades'
|
|
messageHash = type + '::' + symbol
|
|
tradesArray = self.safe_value(self.trades, symbol)
|
|
if tradesArray is None:
|
|
tradesLimit = self.safe_integer(self.options, 'tradesLimit', 1000)
|
|
tradesArray = ArrayCache(tradesLimit)
|
|
self.trades[symbol] = tradesArray
|
|
tradesArray.append(trade)
|
|
client.resolve(tradesArray, messageHash)
|
|
return message
|
|
|
|
def parse_ws_trade(self, trade, market=None):
|
|
#
|
|
# handleTrade
|
|
#
|
|
# {
|
|
# "symbol": "BTC_USDT",
|
|
# "amount": "13.41634893",
|
|
# "quantity": "0.000537",
|
|
# "takerSide": "buy",
|
|
# "createTime": 1676950548834,
|
|
# "price": "24983.89",
|
|
# "id": "62486976",
|
|
# "ts": 1676950548839
|
|
# }
|
|
#
|
|
# private trade
|
|
# {
|
|
# "orderId":"186250258089635840",
|
|
# "tradeId":"62036513",
|
|
# "clientOrderId":"",
|
|
# "accountType":"SPOT",
|
|
# "eventType":"trade",
|
|
# "symbol":"ADA_USDT",
|
|
# "side":"SELL",
|
|
# "type":"MARKET",
|
|
# "price":"0",
|
|
# "quantity":"3",
|
|
# "state":"FILLED",
|
|
# "createTime":1685371921891,
|
|
# "tradeTime":1685371921908,
|
|
# "tradePrice":"0.37694",
|
|
# "tradeQty":"3",
|
|
# "feeCurrency":"USDT",
|
|
# "tradeFee":"0.00226164",
|
|
# "tradeAmount":"1.13082",
|
|
# "filledQuantity":"3",
|
|
# "filledAmount":"1.13082",
|
|
# "ts":1685371921945,
|
|
# "source":"WEB",
|
|
# "orderAmount":"0",
|
|
# "matchRole":"TAKER"
|
|
# }
|
|
#
|
|
marketId = self.safe_string(trade, 'symbol')
|
|
market = self.safe_market(marketId, market)
|
|
timestamp = self.safe_integer(trade, 'createTime')
|
|
takerMaker = self.safe_string_lower_2(trade, 'matchRole', 'taker')
|
|
return self.safe_trade({
|
|
'info': trade,
|
|
'id': self.safe_string_2(trade, 'id', 'tradeId'),
|
|
'symbol': self.safe_string(market, 'symbol'),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'order': self.safe_string(trade, 'orderId'),
|
|
'type': self.safe_string_lower(trade, 'type'),
|
|
'side': self.safe_string_lower_2(trade, 'takerSide', 'side'),
|
|
'takerOrMaker': takerMaker,
|
|
'price': self.omit_zero(self.safe_string_2(trade, 'tradePrice', 'price')),
|
|
'amount': self.omit_zero(self.safe_string_2(trade, 'filledQuantity', 'quantity')),
|
|
'cost': self.safe_string_2(trade, 'amount', 'filledAmount'),
|
|
'fee': {
|
|
'rate': None,
|
|
'cost': self.safe_string(trade, 'tradeFee'),
|
|
'currency': self.safe_string(trade, 'feeCurrency'),
|
|
},
|
|
}, market)
|
|
|
|
def parse_status(self, status):
|
|
statuses: dict = {
|
|
'NEW': 'open',
|
|
'PARTIALLY_FILLED': 'open',
|
|
'FILLED': 'closed',
|
|
'PENDING_CANCEL': 'open',
|
|
'PARTIALLY_CANCELED': 'open',
|
|
'CANCELED': 'canceled',
|
|
# FAILED
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_ws_order_trade(self, trade, market=None):
|
|
#
|
|
# {
|
|
# "symbol": "BTC_USDT",
|
|
# "type": "LIMIT",
|
|
# "quantity": "1",
|
|
# "orderId": "32471407854219264",
|
|
# "tradeFee": "0",
|
|
# "clientOrderId": "",
|
|
# "accountType": "SPOT",
|
|
# "feeCurrency": "",
|
|
# "eventType": "place",
|
|
# "source": "API",
|
|
# "side": "BUY",
|
|
# "filledQuantity": "0",
|
|
# "filledAmount": "0",
|
|
# "matchRole": "MAKER",
|
|
# "state": "NEW",
|
|
# "tradeTime": 0,
|
|
# "tradeAmount": "0",
|
|
# "orderAmount": "0",
|
|
# "createTime": 1648708186922,
|
|
# "price": "47112.1",
|
|
# "tradeQty": "0",
|
|
# "tradePrice": "0",
|
|
# "tradeId": "0",
|
|
# "ts": 1648708187469
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer(trade, 'tradeTime')
|
|
marketId = self.safe_string(trade, 'symbol')
|
|
return self.safe_trade({
|
|
'info': trade,
|
|
'id': self.safe_string(trade, 'tradeId'),
|
|
'symbol': self.safe_symbol(marketId, market),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'order': self.safe_string(trade, 'orderId'),
|
|
'type': self.safe_string_lower(trade, 'type'),
|
|
'side': self.safe_string(trade, 'side'),
|
|
'takerOrMaker': self.safe_string_lower(trade, 'matchRole'),
|
|
'price': self.safe_string(trade, 'price'),
|
|
'amount': self.safe_string(trade, 'tradeAmount'),
|
|
'cost': None,
|
|
'fee': {
|
|
'rate': None,
|
|
'cost': self.safe_string(trade, 'tradeFee'),
|
|
'currency': self.safe_string(trade, 'feeCurrency'),
|
|
},
|
|
}, market)
|
|
|
|
def handle_order(self, client: Client, message):
|
|
#
|
|
# Order is created
|
|
#
|
|
# {
|
|
# "channel": "orders",
|
|
# "data": [
|
|
# {
|
|
# "symbol": "BTC_USDT",
|
|
# "type": "LIMIT",
|
|
# "quantity": "1",
|
|
# "orderId": "32471407854219264",
|
|
# "tradeFee": "0",
|
|
# "clientOrderId": "",
|
|
# "accountType": "SPOT",
|
|
# "feeCurrency": "",
|
|
# "eventType": "place",
|
|
# "source": "API",
|
|
# "side": "BUY",
|
|
# "filledQuantity": "0",
|
|
# "filledAmount": "0",
|
|
# "matchRole": "MAKER",
|
|
# "state": "NEW",
|
|
# "tradeTime": 0,
|
|
# "tradeAmount": "0",
|
|
# "orderAmount": "0",
|
|
# "createTime": 1648708186922,
|
|
# "price": "47112.1",
|
|
# "tradeQty": "0",
|
|
# "tradePrice": "0",
|
|
# "tradeId": "0",
|
|
# "ts": 1648708187469
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_value(message, 'data', [])
|
|
orders = self.orders
|
|
if orders is None:
|
|
limit = self.safe_integer(self.options, 'ordersLimit')
|
|
orders = ArrayCacheBySymbolById(limit)
|
|
self.orders = orders
|
|
marketIds = []
|
|
for i in range(0, len(data)):
|
|
order = self.safe_value(data, i)
|
|
marketId = self.safe_string(order, 'symbol')
|
|
eventType = self.safe_string(order, 'eventType')
|
|
if marketId is not None:
|
|
symbol = self.safe_symbol(marketId)
|
|
orderId = self.safe_string(order, 'orderId')
|
|
clientOrderId = self.safe_string(order, 'clientOrderId')
|
|
if eventType == 'place' or eventType == 'canceled':
|
|
parsed = self.parse_ws_order(order)
|
|
orders.append(parsed)
|
|
else:
|
|
previousOrders = self.safe_value(orders.hashmap, symbol, {})
|
|
previousOrder = self.safe_value_2(previousOrders, orderId, clientOrderId)
|
|
trade = self.parse_ws_trade(order)
|
|
self.handle_my_trades(client, trade)
|
|
if previousOrder['trades'] is None:
|
|
previousOrder['trades'] = []
|
|
previousOrder['trades'].append(trade)
|
|
previousOrder['lastTradeTimestamp'] = trade['timestamp']
|
|
totalCost = '0'
|
|
totalAmount = '0'
|
|
previousOrderTrades = previousOrder['trades']
|
|
for j in range(0, len(previousOrderTrades)):
|
|
previousOrderTrade = previousOrderTrades[j]
|
|
cost = self.number_to_string(previousOrderTrade['cost'])
|
|
amount = self.number_to_string(previousOrderTrade['amount'])
|
|
totalCost = Precise.string_add(totalCost, cost)
|
|
totalAmount = Precise.string_add(totalAmount, amount)
|
|
if Precise.string_gt(totalAmount, '0'):
|
|
previousOrder['average'] = self.parse_number(Precise.string_div(totalCost, totalAmount))
|
|
previousOrder['cost'] = self.parse_number(totalCost)
|
|
if previousOrder['filled'] is not None:
|
|
tradeAmount = self.number_to_string(trade['amount'])
|
|
previousOrderFilled = self.number_to_string(previousOrder['filled'])
|
|
previousOrderFilled = Precise.string_add(previousOrderFilled, tradeAmount)
|
|
previousOrder['filled'] = previousOrderFilled
|
|
if previousOrder['amount'] is not None:
|
|
previousOrderAmount = self.number_to_string(previousOrder['amount'])
|
|
previousOrder['remaining'] = self.parse_number(Precise.string_sub(previousOrderAmount, previousOrderFilled))
|
|
if previousOrder['fee'] is None:
|
|
previousOrder['fee'] = {
|
|
'rate': None,
|
|
'cost': 0,
|
|
'currency': trade['fee']['currency'],
|
|
}
|
|
if (previousOrder['fee']['cost'] is not None) and (trade['fee']['cost'] is not None):
|
|
stringOrderCost = self.number_to_string(previousOrder['fee']['cost'])
|
|
stringTradeCost = self.number_to_string(trade['fee']['cost'])
|
|
previousOrder['fee']['cost'] = Precise.string_add(stringOrderCost, stringTradeCost)
|
|
rawState = self.safe_string(order, 'state')
|
|
state = self.parse_status(rawState)
|
|
previousOrder['status'] = state
|
|
# update the newUpdates count
|
|
orders.append(previousOrder)
|
|
marketIds.append(marketId)
|
|
for i in range(0, len(marketIds)):
|
|
marketId = marketIds[i]
|
|
market = self.market(marketId)
|
|
symbol = market['symbol']
|
|
messageHash = 'orders::' + symbol
|
|
client.resolve(orders, messageHash)
|
|
client.resolve(orders, 'orders')
|
|
return message
|
|
|
|
def parse_ws_order(self, order, market=None):
|
|
#
|
|
# {
|
|
# "symbol": "BTC_USDT",
|
|
# "type": "LIMIT",
|
|
# "quantity": "1",
|
|
# "orderId": "32471407854219264",
|
|
# "tradeFee": "0",
|
|
# "clientOrderId": "",
|
|
# "accountType": "SPOT",
|
|
# "feeCurrency": "",
|
|
# "eventType": "place",
|
|
# "source": "API",
|
|
# "side": "BUY",
|
|
# "filledQuantity": "0",
|
|
# "filledAmount": "0",
|
|
# "matchRole": "MAKER",
|
|
# "state": "NEW",
|
|
# "tradeTime": 0,
|
|
# "tradeAmount": "0",
|
|
# "orderAmount": "0",
|
|
# "createTime": 1648708186922,
|
|
# "price": "47112.1",
|
|
# "tradeQty": "0",
|
|
# "tradePrice": "0",
|
|
# "tradeId": "0",
|
|
# "ts": 1648708187469
|
|
# }
|
|
#
|
|
id = self.safe_string(order, 'orderId')
|
|
clientOrderId = self.safe_string(order, 'clientOrderId')
|
|
marketId = self.safe_string(order, 'symbol')
|
|
timestamp = self.safe_string(order, 'ts')
|
|
filledAmount = self.safe_string(order, 'filledAmount')
|
|
status = self.safe_string(order, 'state')
|
|
trades = None
|
|
if not Precise.string_eq(filledAmount, '0'):
|
|
trades = []
|
|
trade = self.parse_ws_order_trade(order)
|
|
trades.append(trade)
|
|
return self.safe_order({
|
|
'info': order,
|
|
'symbol': self.safe_symbol(marketId, market),
|
|
'id': id,
|
|
'clientOrderId': clientOrderId,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'lastTradeTimestamp': None,
|
|
'type': self.safe_string(order, 'type'),
|
|
'timeInForce': None,
|
|
'postOnly': None,
|
|
'side': self.safe_string(order, 'side'),
|
|
'price': self.safe_string(order, 'price'),
|
|
'stopPrice': None,
|
|
'triggerPrice': None,
|
|
'amount': self.safe_string(order, 'quantity'),
|
|
'cost': None,
|
|
'average': None,
|
|
'filled': filledAmount,
|
|
'remaining': self.safe_string(order, 'remaining_size'),
|
|
'status': self.parse_status(status),
|
|
'fee': {
|
|
'rate': None,
|
|
'cost': self.safe_string(order, 'tradeFee'),
|
|
'currency': self.safe_string(order, 'feeCurrency'),
|
|
},
|
|
'trades': trades,
|
|
})
|
|
|
|
def handle_ticker(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "channel": "ticker",
|
|
# "data": [
|
|
# {
|
|
# "symbol": "BTC_USDT",
|
|
# "startTime": 1677280800000,
|
|
# "open": "23154.32",
|
|
# "high": "23212.21",
|
|
# "low": "22761.01",
|
|
# "close": "23148.86",
|
|
# "quantity": "105.179566",
|
|
# "amount": "2423161.17436702",
|
|
# "tradeCount": 17582,
|
|
# "dailyChange": "-0.0002",
|
|
# "markPrice": "23151.09",
|
|
# "closeTime": 1677367197924,
|
|
# "ts": 1677367251090
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_value(message, 'data', [])
|
|
newTickers: dict = {}
|
|
for i in range(0, len(data)):
|
|
item = data[i]
|
|
marketId = self.safe_string(item, 'symbol')
|
|
if marketId is not None:
|
|
ticker = self.parse_ticker(item)
|
|
symbol = ticker['symbol']
|
|
self.tickers[symbol] = ticker
|
|
newTickers[symbol] = ticker
|
|
messageHashes = self.find_message_hashes(client, 'ticker::')
|
|
for i in range(0, len(messageHashes)):
|
|
messageHash = messageHashes[i]
|
|
parts = messageHash.split('::')
|
|
symbolsString = parts[1]
|
|
symbols = symbolsString.split(',')
|
|
tickers = self.filter_by_array(newTickers, 'symbol', symbols)
|
|
if not self.is_empty(tickers):
|
|
client.resolve(tickers, messageHash)
|
|
client.resolve(newTickers, 'ticker')
|
|
return message
|
|
|
|
def handle_order_book(self, client: Client, message):
|
|
#
|
|
# snapshot
|
|
#
|
|
# {
|
|
# "channel": "book_lv2",
|
|
# "data": [
|
|
# {
|
|
# "symbol": "BTC_USDT",
|
|
# "createTime": 1677368876253,
|
|
# "asks": [
|
|
# ["5.65", "0.02"],
|
|
# ...
|
|
# ],
|
|
# "bids": [
|
|
# ["6.16", "0.6"],
|
|
# ...
|
|
# ],
|
|
# "lastId": 164148724,
|
|
# "id": 164148725,
|
|
# "ts": 1677368876316
|
|
# }
|
|
# ],
|
|
# "action": "snapshot"
|
|
# }
|
|
#
|
|
# update
|
|
#
|
|
# {
|
|
# "channel": "book_lv2",
|
|
# "data": [
|
|
# {
|
|
# "symbol": "BTC_USDT",
|
|
# "createTime": 1677368876882,
|
|
# "asks": [
|
|
# ["6.35", "3"]
|
|
# ],
|
|
# "bids": [
|
|
# ["5.65", "0.02"]
|
|
# ],
|
|
# "lastId": 164148725,
|
|
# "id": 164148726,
|
|
# "ts": 1677368876890
|
|
# }
|
|
# ],
|
|
# "action": "update"
|
|
# }
|
|
#
|
|
data = self.safe_value(message, 'data', [])
|
|
type = self.safe_string(message, 'action')
|
|
snapshot = type == 'snapshot'
|
|
update = type == 'update'
|
|
for i in range(0, len(data)):
|
|
item = data[i]
|
|
marketId = self.safe_string(item, 'symbol')
|
|
market = self.safe_market(marketId)
|
|
symbol = market['symbol']
|
|
name = 'book_lv2'
|
|
messageHash = name + '::' + symbol
|
|
subscription = self.safe_value(client.subscriptions, messageHash, {})
|
|
limit = self.safe_integer(subscription, 'limit')
|
|
timestamp = self.safe_integer(item, 'ts')
|
|
asks = self.safe_value(item, 'asks')
|
|
bids = self.safe_value(item, 'bids')
|
|
if snapshot or update:
|
|
if snapshot:
|
|
self.orderbooks[symbol] = self.order_book({}, limit)
|
|
orderbook = self.orderbooks[symbol]
|
|
if bids is not None:
|
|
for j in range(0, len(bids)):
|
|
bid = self.safe_value(bids, j)
|
|
price = self.safe_number(bid, 0)
|
|
amount = self.safe_number(bid, 1)
|
|
bidsSide = orderbook['bids']
|
|
bidsSide.store(price, amount)
|
|
if asks is not None:
|
|
for j in range(0, len(asks)):
|
|
ask = self.safe_value(asks, j)
|
|
price = self.safe_number(ask, 0)
|
|
amount = self.safe_number(ask, 1)
|
|
asksSide = orderbook['asks']
|
|
asksSide.store(price, amount)
|
|
orderbook['symbol'] = symbol
|
|
orderbook['timestamp'] = timestamp
|
|
orderbook['datetime'] = self.iso8601(timestamp)
|
|
client.resolve(orderbook, messageHash)
|
|
|
|
def handle_balance(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "channel": "balances",
|
|
# "data": [
|
|
# {
|
|
# "changeTime": 1657312008411,
|
|
# "accountId": "1234",
|
|
# "accountType": "SPOT",
|
|
# "eventType": "place_order",
|
|
# "available": "9999999983.668",
|
|
# "currency": "BTC",
|
|
# "id": 60018450912695040,
|
|
# "userId": 12345,
|
|
# "hold": "16.332",
|
|
# "ts": 1657312008443
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_value(message, 'data', [])
|
|
messageHash = 'balances'
|
|
self.balance = self.parse_ws_balance(data)
|
|
client.resolve(self.balance, messageHash)
|
|
|
|
def parse_ws_balance(self, response):
|
|
#
|
|
# [
|
|
# {
|
|
# "changeTime": 1657312008411,
|
|
# "accountId": "1234",
|
|
# "accountType": "SPOT",
|
|
# "eventType": "place_order",
|
|
# "available": "9999999983.668",
|
|
# "currency": "BTC",
|
|
# "id": 60018450912695040,
|
|
# "userId": 12345,
|
|
# "hold": "16.332",
|
|
# "ts": 1657312008443
|
|
# }
|
|
# ]
|
|
#
|
|
firstBalance = self.safe_value(response, 0, {})
|
|
timestamp = self.safe_integer(firstBalance, 'ts')
|
|
result: dict = {
|
|
'info': response,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
}
|
|
for i in range(0, len(response)):
|
|
balance = self.safe_value(response, i)
|
|
currencyId = self.safe_string(balance, 'currency')
|
|
code = self.safe_currency_code(currencyId)
|
|
newAccount = self.account()
|
|
newAccount['free'] = self.safe_string(balance, 'available')
|
|
newAccount['used'] = self.safe_string(balance, 'hold')
|
|
result[code] = newAccount
|
|
return self.safe_balance(result)
|
|
|
|
def handle_my_trades(self, client: Client, parsedTrade):
|
|
# emulated using the orders' stream
|
|
messageHash = 'myTrades'
|
|
symbol = parsedTrade['symbol']
|
|
if self.myTrades is None:
|
|
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
|
|
self.myTrades = ArrayCacheBySymbolById(limit)
|
|
trades = self.myTrades
|
|
trades.append(parsedTrade)
|
|
client.resolve(trades, messageHash)
|
|
symbolMessageHash = messageHash + ':' + symbol
|
|
client.resolve(trades, symbolMessageHash)
|
|
|
|
def handle_pong(self, client: Client):
|
|
client.lastPong = self.milliseconds()
|
|
|
|
def handle_message(self, client: Client, message):
|
|
if self.handle_error_message(client, message):
|
|
return
|
|
type = self.safe_string(message, 'channel')
|
|
event = self.safe_string(message, 'event')
|
|
if event == 'pong':
|
|
client.lastPong = self.milliseconds()
|
|
methods: dict = {
|
|
'candles_minute_1': self.handle_ohlcv,
|
|
'candles_minute_5': self.handle_ohlcv,
|
|
'candles_minute_10': self.handle_ohlcv,
|
|
'candles_minute_15': self.handle_ohlcv,
|
|
'candles_minute_30': self.handle_ohlcv,
|
|
'candles_hour_1': self.handle_ohlcv,
|
|
'candles_hour_2': self.handle_ohlcv,
|
|
'candles_hour_4': self.handle_ohlcv,
|
|
'candles_hour_6': self.handle_ohlcv,
|
|
'candles_hour_12': self.handle_ohlcv,
|
|
'candles_day_1': self.handle_ohlcv,
|
|
'candles_day_3': self.handle_ohlcv,
|
|
'candles_week_1': self.handle_ohlcv,
|
|
'candles_month_1': self.handle_ohlcv,
|
|
'book': self.handle_order_book,
|
|
'book_lv2': self.handle_order_book,
|
|
'ticker': self.handle_ticker,
|
|
'trades': self.handle_trade,
|
|
'orders': self.handle_order,
|
|
'balances': self.handle_balance,
|
|
'createOrder': self.handle_order_request,
|
|
'cancelOrder': self.handle_order_request,
|
|
'cancelAllOrders': self.handle_order_request,
|
|
'auth': self.handle_authenticate,
|
|
}
|
|
method = self.safe_value(methods, type)
|
|
if type == 'auth':
|
|
self.handle_authenticate(client, message)
|
|
elif type is None:
|
|
self.handle_order_request(client, message)
|
|
else:
|
|
data = self.safe_value(message, 'data', [])
|
|
dataLength = len(data)
|
|
if dataLength > 0:
|
|
method(client, message)
|
|
|
|
def handle_error_message(self, client: Client, message) -> Bool:
|
|
#
|
|
# {
|
|
# message: 'Invalid channel value ["ordersss"]',
|
|
# event: 'error'
|
|
# }
|
|
#
|
|
# {
|
|
# "orderId": 0,
|
|
# "clientOrderId": null,
|
|
# "message": "Currency trade disabled",
|
|
# "code": 21352
|
|
# }
|
|
#
|
|
# {
|
|
# "event": "error",
|
|
# "message": "Platform in maintenance mode"
|
|
# }
|
|
# {
|
|
# "id":"1722386782048",
|
|
# "data":[
|
|
# {
|
|
# "orderId":0,
|
|
# "clientOrderId":null,
|
|
# "message":"available insufficient",
|
|
# "code":21721
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
id = self.safe_string(message, 'id')
|
|
event = self.safe_string(message, 'event')
|
|
data = self.safe_list(message, 'data')
|
|
first = self.safe_dict(data, 0)
|
|
orderId = self.safe_string(first, 'orderId')
|
|
if (event == 'error') or (orderId == '0'):
|
|
try:
|
|
error = self.safe_string(first, 'message')
|
|
code = self.safe_string(first, 'code')
|
|
feedback = self.id + ' ' + self.json(message)
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
|
|
self.throw_broadly_matched_exception(self.exceptions['broad'], error, feedback)
|
|
raise ExchangeError(feedback)
|
|
except Exception as e:
|
|
if isinstance(e, AuthenticationError):
|
|
messageHash = 'authenticated'
|
|
client.reject(e, messageHash)
|
|
if messageHash in client.subscriptions:
|
|
del client.subscriptions[messageHash]
|
|
else:
|
|
client.reject(e, id)
|
|
return True
|
|
return False
|
|
|
|
def handle_authenticate(self, client: Client, message):
|
|
#
|
|
# {
|
|
# "success": True,
|
|
# "ret_msg": '',
|
|
# "op": "auth",
|
|
# "conn_id": "ce3dpomvha7dha97tvp0-2xh"
|
|
# }
|
|
#
|
|
data = self.safe_value(message, 'data')
|
|
success = self.safe_value(data, 'success')
|
|
messageHash = 'authenticated'
|
|
if success:
|
|
client.resolve(message, messageHash)
|
|
else:
|
|
error = AuthenticationError(self.id + ' ' + self.json(message))
|
|
client.reject(error, messageHash)
|
|
if messageHash in client.subscriptions:
|
|
del client.subscriptions[messageHash]
|
|
return message
|
|
|
|
def ping(self, client: Client):
|
|
return {
|
|
'event': 'ping',
|
|
}
|