ccxt 4.4.75__py2.py3-none-any.whl → 4.4.78__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ccxt/__init__.py +3 -3
- ccxt/abstract/apex.py +31 -0
- ccxt/abstract/myokx.py +4 -0
- ccxt/abstract/okx.py +4 -0
- ccxt/abstract/upbit.py +51 -37
- ccxt/abstract/xt.py +3 -0
- ccxt/apex.py +1884 -0
- ccxt/ascendex.py +2 -2
- ccxt/async_support/__init__.py +3 -3
- ccxt/async_support/apex.py +1884 -0
- ccxt/async_support/ascendex.py +2 -2
- ccxt/async_support/base/exchange.py +2 -2
- ccxt/async_support/binance.py +39 -217
- ccxt/async_support/bingx.py +1 -1
- ccxt/async_support/bitfinex.py +2 -2
- ccxt/async_support/bitflyer.py +2 -2
- ccxt/async_support/bitget.py +135 -65
- ccxt/async_support/bitmart.py +2 -2
- ccxt/async_support/bitmex.py +6 -6
- ccxt/async_support/bitrue.py +48 -0
- ccxt/async_support/cex.py +1 -1
- ccxt/async_support/coinbase.py +29 -4
- ccxt/async_support/coincatch.py +66 -0
- ccxt/async_support/coinex.py +3 -1
- ccxt/async_support/coinlist.py +85 -2
- ccxt/async_support/cryptocom.py +2 -2
- ccxt/async_support/defx.py +1 -1
- ccxt/async_support/delta.py +1 -1
- ccxt/async_support/deribit.py +2 -2
- ccxt/async_support/derive.py +2 -2
- ccxt/async_support/digifinex.py +2 -2
- ccxt/async_support/gate.py +1 -1
- ccxt/async_support/hitbtc.py +5 -2
- ccxt/async_support/htx.py +2 -2
- ccxt/async_support/hyperliquid.py +13 -6
- ccxt/async_support/kraken.py +2 -2
- ccxt/async_support/krakenfutures.py +2 -2
- ccxt/async_support/kucoinfutures.py +2 -2
- ccxt/async_support/mexc.py +50 -52
- ccxt/async_support/okx.py +21 -9
- ccxt/async_support/oxfun.py +2 -2
- ccxt/async_support/paradex.py +5 -10
- ccxt/async_support/phemex.py +4 -3
- ccxt/async_support/poloniex.py +3 -3
- ccxt/async_support/probit.py +1 -0
- ccxt/async_support/tradeogre.py +2 -1
- ccxt/async_support/upbit.py +265 -89
- ccxt/async_support/vertex.py +2 -2
- ccxt/async_support/whitebit.py +1 -0
- ccxt/async_support/woo.py +5 -3
- ccxt/async_support/woofipro.py +2 -2
- ccxt/async_support/xt.py +115 -5
- ccxt/base/exchange.py +76 -3
- ccxt/binance.py +39 -217
- ccxt/bingx.py +1 -1
- ccxt/bitfinex.py +2 -2
- ccxt/bitflyer.py +2 -2
- ccxt/bitget.py +135 -65
- ccxt/bitmart.py +2 -2
- ccxt/bitmex.py +6 -6
- ccxt/bitrue.py +48 -0
- ccxt/cex.py +1 -1
- ccxt/coinbase.py +29 -4
- ccxt/coincatch.py +66 -0
- ccxt/coinex.py +3 -1
- ccxt/coinlist.py +85 -2
- ccxt/cryptocom.py +2 -2
- ccxt/defx.py +1 -1
- ccxt/delta.py +1 -1
- ccxt/deribit.py +2 -2
- ccxt/derive.py +2 -2
- ccxt/digifinex.py +2 -2
- ccxt/gate.py +1 -1
- ccxt/hitbtc.py +5 -2
- ccxt/htx.py +2 -2
- ccxt/hyperliquid.py +13 -6
- ccxt/kraken.py +2 -2
- ccxt/krakenfutures.py +2 -2
- ccxt/kucoinfutures.py +2 -2
- ccxt/mexc.py +50 -52
- ccxt/okx.py +21 -9
- ccxt/oxfun.py +2 -2
- ccxt/paradex.py +5 -10
- ccxt/phemex.py +4 -3
- ccxt/poloniex.py +3 -3
- ccxt/pro/__init__.py +5 -1
- ccxt/pro/apex.py +984 -0
- ccxt/pro/coinbase.py +4 -6
- ccxt/pro/gate.py +22 -2
- ccxt/pro/hollaex.py +2 -2
- ccxt/pro/hyperliquid.py +1 -1
- ccxt/pro/p2b.py +2 -2
- ccxt/pro/tradeogre.py +272 -0
- ccxt/probit.py +1 -0
- ccxt/test/tests_async.py +27 -0
- ccxt/test/tests_sync.py +27 -0
- ccxt/tradeogre.py +2 -1
- ccxt/upbit.py +265 -89
- ccxt/vertex.py +2 -2
- ccxt/whitebit.py +1 -0
- ccxt/woo.py +5 -3
- ccxt/woofipro.py +2 -2
- ccxt/xt.py +115 -5
- {ccxt-4.4.75.dist-info → ccxt-4.4.78.dist-info}/METADATA +4 -4
- {ccxt-4.4.75.dist-info → ccxt-4.4.78.dist-info}/RECORD +108 -106
- ccxt/abstract/ace.py +0 -15
- ccxt/ace.py +0 -1152
- ccxt/async_support/ace.py +0 -1152
- {ccxt-4.4.75.dist-info → ccxt-4.4.78.dist-info}/LICENSE.txt +0 -0
- {ccxt-4.4.75.dist-info → ccxt-4.4.78.dist-info}/WHEEL +0 -0
- {ccxt-4.4.75.dist-info → ccxt-4.4.78.dist-info}/top_level.txt +0 -0
ccxt/pro/apex.py
ADDED
@@ -0,0 +1,984 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
|
4
|
+
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
|
5
|
+
|
6
|
+
import ccxt.async_support
|
7
|
+
from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheBySymbolBySide, ArrayCacheByTimestamp
|
8
|
+
import asyncio
|
9
|
+
import hashlib
|
10
|
+
import json
|
11
|
+
from ccxt.base.types import Any, Int, Order, OrderBook, Position, Str, Strings, Ticker, Tickers, Trade
|
12
|
+
from ccxt.async_support.base.ws.client import Client
|
13
|
+
from typing import List
|
14
|
+
from ccxt.base.errors import ExchangeError
|
15
|
+
from ccxt.base.errors import AuthenticationError
|
16
|
+
from ccxt.base.errors import ArgumentsRequired
|
17
|
+
|
18
|
+
|
19
|
+
class apex(ccxt.async_support.apex):
|
20
|
+
|
21
|
+
def describe(self) -> Any:
|
22
|
+
return self.deep_extend(super(apex, self).describe(), {
|
23
|
+
'has': {
|
24
|
+
'ws': True,
|
25
|
+
'watchTicker': True,
|
26
|
+
'watchTickers': True,
|
27
|
+
'watchOrderBook': True,
|
28
|
+
'watchOrders': True,
|
29
|
+
'watchTrades': True,
|
30
|
+
'watchTradesForSymbols': False,
|
31
|
+
'watchPositions': True,
|
32
|
+
'watchMyTrades': True,
|
33
|
+
'watchBalance': False,
|
34
|
+
'watchOHLCV': True,
|
35
|
+
},
|
36
|
+
'urls': {
|
37
|
+
'logo': 'https://omni.apex.exchange/assets/logo_content-CY9uyFbz.svg',
|
38
|
+
'api': {
|
39
|
+
'ws': {
|
40
|
+
'public': 'wss://quote.omni.apex.exchange/realtime_public?v=2',
|
41
|
+
'private': 'wss://quote.omni.apex.exchange/realtime_private?v=2',
|
42
|
+
},
|
43
|
+
},
|
44
|
+
'test': {
|
45
|
+
'ws': {
|
46
|
+
'public': 'wss://qa-quote.omni.apex.exchange/realtime_public?v=2',
|
47
|
+
'private': 'wss://qa-quote.omni.apex.exchange/realtime_private?v=2',
|
48
|
+
},
|
49
|
+
},
|
50
|
+
'www': 'https://apex.exchange/',
|
51
|
+
'doc': 'https://api-docs.pro.apex.exchange',
|
52
|
+
'fees': 'https://apex-pro.gitbook.io/apex-pro/apex-omni-live-now/trading-perpetual-contracts/trading-fees',
|
53
|
+
'referral': 'https://omni.apex.exchange/trade',
|
54
|
+
},
|
55
|
+
'options': {},
|
56
|
+
'streaming': {
|
57
|
+
'ping': self.ping,
|
58
|
+
'keepAlive': 18000,
|
59
|
+
},
|
60
|
+
})
|
61
|
+
|
62
|
+
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
63
|
+
"""
|
64
|
+
watches information on multiple trades made in a market
|
65
|
+
|
66
|
+
https://api-docs.pro.apex.exchange/#websocket-v3-for-omni-websocket-endpoint
|
67
|
+
|
68
|
+
:param str symbol: unified market symbol of the market trades were made in
|
69
|
+
:param int [since]: the earliest time in ms to fetch trades for
|
70
|
+
:param int [limit]: the maximum number of trade structures to retrieve
|
71
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
72
|
+
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
73
|
+
"""
|
74
|
+
return await self.watch_trades_for_symbols([symbol], since, limit, params)
|
75
|
+
|
76
|
+
async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
77
|
+
"""
|
78
|
+
get the list of most recent trades for a list of symbols
|
79
|
+
|
80
|
+
https://api-docs.pro.apex.exchange/#websocket-v3-for-omni-websocket-endpoint
|
81
|
+
|
82
|
+
:param str[] symbols: unified symbol of the market to fetch trades for
|
83
|
+
:param int [since]: timestamp in ms of the earliest trade to fetch
|
84
|
+
:param int [limit]: the maximum amount of trades to fetch
|
85
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
86
|
+
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
87
|
+
"""
|
88
|
+
await self.load_markets()
|
89
|
+
symbols = self.market_symbols(symbols)
|
90
|
+
symbolsLength = len(symbols)
|
91
|
+
if symbolsLength == 0:
|
92
|
+
raise ArgumentsRequired(self.id + ' watchTradesForSymbols() requires a non-empty array of symbols')
|
93
|
+
timeStamp = str(self.milliseconds())
|
94
|
+
url = self.urls['api']['ws']['public'] + '×tamp=' + timeStamp
|
95
|
+
topics = []
|
96
|
+
messageHashes = []
|
97
|
+
for i in range(0, len(symbols)):
|
98
|
+
symbol = symbols[i]
|
99
|
+
market = self.market(symbol)
|
100
|
+
topic = 'recentlyTrade.H.' + market['id2']
|
101
|
+
topics.append(topic)
|
102
|
+
messageHash = 'trade:' + symbol
|
103
|
+
messageHashes.append(messageHash)
|
104
|
+
trades = await self.watch_topics(url, messageHashes, topics, params)
|
105
|
+
if self.newUpdates:
|
106
|
+
first = self.safe_value(trades, 0)
|
107
|
+
tradeSymbol = self.safe_string(first, 'symbol')
|
108
|
+
limit = trades.getLimit(tradeSymbol, limit)
|
109
|
+
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
|
110
|
+
|
111
|
+
def handle_trades(self, client: Client, message):
|
112
|
+
#
|
113
|
+
# {
|
114
|
+
# "topic": "recentlyTrade.H.BTCUSDT",
|
115
|
+
# "type": "snapshot",
|
116
|
+
# "ts": 1672304486868,
|
117
|
+
# "data": [
|
118
|
+
# {
|
119
|
+
# "T": 1672304486865,
|
120
|
+
# "s": "BTCUSDT",
|
121
|
+
# "S": "Buy",
|
122
|
+
# "v": "0.001",
|
123
|
+
# "p": "16578.50",
|
124
|
+
# "L": "PlusTick",
|
125
|
+
# "i": "20f43950-d8dd-5b31-9112-a178eb6023af",
|
126
|
+
# "BT": False
|
127
|
+
# }
|
128
|
+
# ]
|
129
|
+
# }
|
130
|
+
#
|
131
|
+
data = self.safe_value(message, 'data', {})
|
132
|
+
topic = self.safe_string(message, 'topic')
|
133
|
+
trades = data
|
134
|
+
parts = topic.split('.')
|
135
|
+
marketId = self.safe_string(parts, 2)
|
136
|
+
market = self.safe_market(marketId, None, None)
|
137
|
+
symbol = market['symbol']
|
138
|
+
stored = self.safe_value(self.trades, symbol)
|
139
|
+
if stored is None:
|
140
|
+
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
|
141
|
+
stored = ArrayCache(limit)
|
142
|
+
self.trades[symbol] = stored
|
143
|
+
for j in range(0, len(trades)):
|
144
|
+
parsed = self.parse_ws_trade(trades[j], market)
|
145
|
+
stored.append(parsed)
|
146
|
+
messageHash = 'trade' + ':' + symbol
|
147
|
+
client.resolve(stored, messageHash)
|
148
|
+
|
149
|
+
def parse_ws_trade(self, trade, market=None):
|
150
|
+
#
|
151
|
+
# public
|
152
|
+
# {
|
153
|
+
# "T": 1672304486865,
|
154
|
+
# "s": "BTCUSDT",
|
155
|
+
# "S": "Buy",
|
156
|
+
# "v": "0.001",
|
157
|
+
# "p": "16578.50",
|
158
|
+
# "L": "PlusTick",
|
159
|
+
# "i": "20f43950-d8dd-5b31-9112-a178eb6023af",
|
160
|
+
# "BT": False
|
161
|
+
# }
|
162
|
+
#
|
163
|
+
id = self.safe_string_n(trade, ['i', 'id', 'v'])
|
164
|
+
marketId = self.safe_string_n(trade, ['s', 'symbol'])
|
165
|
+
market = self.safe_market(marketId, market, None)
|
166
|
+
symbol = market['symbol']
|
167
|
+
timestamp = self.safe_integer_n(trade, ['t', 'T', 'createdAt'])
|
168
|
+
side = self.safe_string_lower_n(trade, ['S', 'side'])
|
169
|
+
price = self.safe_string_n(trade, ['p', 'price'])
|
170
|
+
amount = self.safe_string_n(trade, ['q', 'v', 'size'])
|
171
|
+
return self.safe_trade({
|
172
|
+
'id': id,
|
173
|
+
'info': trade,
|
174
|
+
'timestamp': timestamp,
|
175
|
+
'datetime': self.iso8601(timestamp),
|
176
|
+
'symbol': symbol,
|
177
|
+
'order': None,
|
178
|
+
'type': None,
|
179
|
+
'side': side,
|
180
|
+
'takerOrMaker': None,
|
181
|
+
'price': price,
|
182
|
+
'amount': amount,
|
183
|
+
'cost': None,
|
184
|
+
'fee': None,
|
185
|
+
}, market)
|
186
|
+
|
187
|
+
async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
|
188
|
+
"""
|
189
|
+
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
190
|
+
|
191
|
+
https://api-docs.pro.apex.exchange/#websocket-v3-for-omni-websocket-endpoint
|
192
|
+
|
193
|
+
:param str symbol: unified symbol of the market to fetch the order book for
|
194
|
+
:param int [limit]: the maximum amount of order book entries to return.
|
195
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
196
|
+
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
197
|
+
"""
|
198
|
+
return await self.watch_order_book_for_symbols([symbol], limit, params)
|
199
|
+
|
200
|
+
async def watch_order_book_for_symbols(self, symbols: List[str], limit: Int = None, params={}) -> OrderBook:
|
201
|
+
"""
|
202
|
+
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
203
|
+
|
204
|
+
https://api-docs.pro.apex.exchange/#websocket-v3-for-omni-websocket-endpoint
|
205
|
+
|
206
|
+
:param str[] symbols: unified array of symbols
|
207
|
+
:param int [limit]: the maximum amount of order book entries to return.
|
208
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
209
|
+
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
210
|
+
"""
|
211
|
+
await self.load_markets()
|
212
|
+
symbolsLength = len(symbols)
|
213
|
+
if symbolsLength == 0:
|
214
|
+
raise ArgumentsRequired(self.id + ' watchOrderBookForSymbols() requires a non-empty array of symbols')
|
215
|
+
symbols = self.market_symbols(symbols)
|
216
|
+
timeStamp = str(self.milliseconds())
|
217
|
+
url = self.urls['api']['ws']['public'] + '×tamp=' + timeStamp
|
218
|
+
topics = []
|
219
|
+
messageHashes = []
|
220
|
+
for i in range(0, len(symbols)):
|
221
|
+
symbol = symbols[i]
|
222
|
+
market = self.market(symbol)
|
223
|
+
if limit is None:
|
224
|
+
limit = 25
|
225
|
+
topic = 'orderBook' + str(limit) + '.H.' + market['id2']
|
226
|
+
topics.append(topic)
|
227
|
+
messageHash = 'orderbook:' + symbol
|
228
|
+
messageHashes.append(messageHash)
|
229
|
+
orderbook = await self.watch_topics(url, messageHashes, topics, params)
|
230
|
+
return orderbook.limit()
|
231
|
+
|
232
|
+
async def watch_topics(self, url, messageHashes, topics, params={}):
|
233
|
+
request: dict = {
|
234
|
+
'op': 'subscribe',
|
235
|
+
'args': topics,
|
236
|
+
}
|
237
|
+
message = self.extend(request, params)
|
238
|
+
return await self.watch_multiple(url, messageHashes, message, messageHashes)
|
239
|
+
|
240
|
+
def handle_order_book(self, client: Client, message):
|
241
|
+
#
|
242
|
+
# {
|
243
|
+
# "topic": "orderbook25.H.BTCUSDT",
|
244
|
+
# "type": "snapshot",
|
245
|
+
# "ts": 1672304484978,
|
246
|
+
# "data": {
|
247
|
+
# "s": "BTCUSDT",
|
248
|
+
# "b": [
|
249
|
+
# ...,
|
250
|
+
# [
|
251
|
+
# "16493.50",
|
252
|
+
# "0.006"
|
253
|
+
# ],
|
254
|
+
# [
|
255
|
+
# "16493.00",
|
256
|
+
# "0.100"
|
257
|
+
# ]
|
258
|
+
# ],
|
259
|
+
# "a": [
|
260
|
+
# [
|
261
|
+
# "16611.00",
|
262
|
+
# "0.029"
|
263
|
+
# ],
|
264
|
+
# [
|
265
|
+
# "16612.00",
|
266
|
+
# "0.213"
|
267
|
+
# ],
|
268
|
+
# ],
|
269
|
+
# "u": 18521288,
|
270
|
+
# "seq": 7961638724
|
271
|
+
# }
|
272
|
+
# }
|
273
|
+
#
|
274
|
+
type = self.safe_string(message, 'type')
|
275
|
+
isSnapshot = (type == 'snapshot')
|
276
|
+
data = self.safe_dict(message, 'data', {})
|
277
|
+
marketId = self.safe_string(data, 's')
|
278
|
+
market = self.safe_market(marketId, None, None)
|
279
|
+
symbol = market['symbol']
|
280
|
+
timestamp = self.safe_integer_product(message, 'ts', 0.001)
|
281
|
+
if not (symbol in self.orderbooks):
|
282
|
+
self.orderbooks[symbol] = self.order_book()
|
283
|
+
orderbook = self.orderbooks[symbol]
|
284
|
+
if isSnapshot:
|
285
|
+
snapshot = self.parse_order_book(data, symbol, timestamp, 'b', 'a')
|
286
|
+
orderbook.reset(snapshot)
|
287
|
+
else:
|
288
|
+
asks = self.safe_list(data, 'a', [])
|
289
|
+
bids = self.safe_list(data, 'b', [])
|
290
|
+
self.handle_deltas(orderbook['asks'], asks)
|
291
|
+
self.handle_deltas(orderbook['bids'], bids)
|
292
|
+
orderbook['timestamp'] = timestamp
|
293
|
+
orderbook['datetime'] = self.iso8601(timestamp)
|
294
|
+
messageHash = 'orderbook' + ':' + symbol
|
295
|
+
self.orderbooks[symbol] = orderbook
|
296
|
+
client.resolve(orderbook, messageHash)
|
297
|
+
|
298
|
+
def handle_delta(self, bookside, delta):
|
299
|
+
bidAsk = self.parse_bid_ask(delta, 0, 1)
|
300
|
+
bookside.storeArray(bidAsk)
|
301
|
+
|
302
|
+
def handle_deltas(self, bookside, deltas):
|
303
|
+
for i in range(0, len(deltas)):
|
304
|
+
self.handle_delta(bookside, deltas[i])
|
305
|
+
|
306
|
+
async def watch_ticker(self, symbol: str, params={}) -> Ticker:
|
307
|
+
"""
|
308
|
+
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
309
|
+
|
310
|
+
https://api-docs.pro.apex.exchange/#websocket-v3-for-omni-websocket-endpoint
|
311
|
+
|
312
|
+
:param str symbol: unified symbol of the market to fetch the ticker for
|
313
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
314
|
+
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
315
|
+
"""
|
316
|
+
await self.load_markets()
|
317
|
+
market = self.market(symbol)
|
318
|
+
symbol = market['symbol']
|
319
|
+
timeStamp = str(self.milliseconds())
|
320
|
+
url = self.urls['api']['ws']['public'] + '×tamp=' + timeStamp
|
321
|
+
messageHash = 'ticker:' + symbol
|
322
|
+
topic = 'instrumentInfo' + '.H.' + market['id2']
|
323
|
+
topics = [topic]
|
324
|
+
return await self.watch_topics(url, [messageHash], topics, params)
|
325
|
+
|
326
|
+
async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
|
327
|
+
"""
|
328
|
+
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
|
329
|
+
|
330
|
+
https://api-docs.pro.apex.exchange/#websocket-v3-for-omni-websocket-endpoint
|
331
|
+
|
332
|
+
:param str[] symbols: unified symbol of the market to fetch the ticker for
|
333
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
334
|
+
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
335
|
+
"""
|
336
|
+
await self.load_markets()
|
337
|
+
symbols = self.market_symbols(symbols, None, False)
|
338
|
+
messageHashes = []
|
339
|
+
timeStamp = str(self.milliseconds())
|
340
|
+
url = self.urls['api']['ws']['public'] + '×tamp=' + timeStamp
|
341
|
+
topics = []
|
342
|
+
for i in range(0, len(symbols)):
|
343
|
+
symbol = symbols[i]
|
344
|
+
market = self.market(symbol)
|
345
|
+
topic = 'instrumentInfo' + '.H.' + market['id2']
|
346
|
+
topics.append(topic)
|
347
|
+
messageHash = 'ticker:' + symbol
|
348
|
+
messageHashes.append(messageHash)
|
349
|
+
ticker = await self.watch_topics(url, messageHashes, topics, params)
|
350
|
+
if self.newUpdates:
|
351
|
+
result: dict = {}
|
352
|
+
result[ticker['symbol']] = ticker
|
353
|
+
return result
|
354
|
+
return self.filter_by_array(self.tickers, 'symbol', symbols)
|
355
|
+
|
356
|
+
def handle_ticker(self, client: Client, message):
|
357
|
+
# "topic":"instrumentInfo.H.BTCUSDT",
|
358
|
+
# "type":"snapshot",
|
359
|
+
# "data":{
|
360
|
+
# "symbol":"BTCUSDT",
|
361
|
+
# "lastPrice":"21572.5",
|
362
|
+
# "price24hPcnt":"-0.0194318181818182",
|
363
|
+
# "highPrice24h":"25306.5",
|
364
|
+
# "lowPrice24h":"17001.5",
|
365
|
+
# "turnover24h":"1334891.4545",
|
366
|
+
# "volume24h":"64.896",
|
367
|
+
# "nextFundingTime":"2022-08-26T08:00:00Z",
|
368
|
+
# "oraclePrice":"21412.060000000002752512",
|
369
|
+
# "indexPrice":"21409.82",
|
370
|
+
# "openInterest":"49.598",
|
371
|
+
# "tradeCount":"0",
|
372
|
+
# "fundingRate":"0.0000125",
|
373
|
+
# "predictedFundingRate":"0.0000125"
|
374
|
+
# },
|
375
|
+
# "cs":44939063,
|
376
|
+
# "ts":1661500091955487
|
377
|
+
# }
|
378
|
+
topic = self.safe_string(message, 'topic', '')
|
379
|
+
updateType = self.safe_string(message, 'type', '')
|
380
|
+
data = self.safe_dict(message, 'data', {})
|
381
|
+
symbol = None
|
382
|
+
parsed = None
|
383
|
+
if (updateType == 'snapshot'):
|
384
|
+
parsed = self.parse_ticker(data)
|
385
|
+
symbol = parsed['symbol']
|
386
|
+
elif updateType == 'delta':
|
387
|
+
topicParts = topic.split('.')
|
388
|
+
topicLength = len(topicParts)
|
389
|
+
marketId = self.safe_string(topicParts, topicLength - 1)
|
390
|
+
market = self.safe_market(marketId, None, None)
|
391
|
+
symbol = market['symbol']
|
392
|
+
ticker = self.safe_dict(self.tickers, symbol, {})
|
393
|
+
rawTicker = self.safe_dict(ticker, 'info', {})
|
394
|
+
merged = self.extend(rawTicker, data)
|
395
|
+
parsed = self.parse_ticker(merged)
|
396
|
+
timestamp = self.safe_integer_product(message, 'ts', 0.001)
|
397
|
+
parsed['timestamp'] = timestamp
|
398
|
+
parsed['datetime'] = self.iso8601(timestamp)
|
399
|
+
self.tickers[symbol] = parsed
|
400
|
+
messageHash = 'ticker:' + symbol
|
401
|
+
client.resolve(self.tickers[symbol], messageHash)
|
402
|
+
|
403
|
+
async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
|
404
|
+
"""
|
405
|
+
watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
406
|
+
|
407
|
+
https://api-docs.pro.apex.exchange/#websocket-v3-for-omni-websocket-endpoint
|
408
|
+
|
409
|
+
:param str symbol: unified symbol of the market to fetch OHLCV data for
|
410
|
+
:param str timeframe: the length of time each candle represents
|
411
|
+
:param int [since]: timestamp in ms of the earliest candle to fetch
|
412
|
+
:param int [limit]: the maximum amount of candles to fetch
|
413
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
414
|
+
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
415
|
+
"""
|
416
|
+
params['callerMethodName'] = 'watchOHLCV'
|
417
|
+
result = await self.watch_ohlcv_for_symbols([[symbol, timeframe]], since, limit, params)
|
418
|
+
return result[symbol][timeframe]
|
419
|
+
|
420
|
+
async def watch_ohlcv_for_symbols(self, symbolsAndTimeframes: List[List[str]], since: Int = None, limit: Int = None, params={}):
|
421
|
+
"""
|
422
|
+
watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
423
|
+
|
424
|
+
https://api-docs.pro.apex.exchange/#websocket-v3-for-omni-websocket-endpoint
|
425
|
+
|
426
|
+
:param str[][] symbolsAndTimeframes: array of arrays containing unified symbols and timeframes to fetch OHLCV data for, example [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]
|
427
|
+
:param int [since]: timestamp in ms of the earliest candle to fetch
|
428
|
+
:param int [limit]: the maximum amount of candles to fetch
|
429
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
430
|
+
:returns dict: A list of candles ordered, open, high, low, close, volume
|
431
|
+
"""
|
432
|
+
await self.load_markets()
|
433
|
+
timeStamp = str(self.milliseconds())
|
434
|
+
url = self.urls['api']['ws']['public'] + '×tamp=' + timeStamp
|
435
|
+
rawHashes = []
|
436
|
+
messageHashes = []
|
437
|
+
for i in range(0, len(symbolsAndTimeframes)):
|
438
|
+
data = symbolsAndTimeframes[i]
|
439
|
+
symbolString = self.safe_string(data, 0)
|
440
|
+
market = self.market(symbolString)
|
441
|
+
symbolString = market['id2']
|
442
|
+
unfiedTimeframe = self.safe_string(data, 1, '1')
|
443
|
+
timeframeId = self.safe_string(self.timeframes, unfiedTimeframe, unfiedTimeframe)
|
444
|
+
rawHashes.append('candle.' + timeframeId + '.' + symbolString)
|
445
|
+
messageHashes.append('ohlcv::' + symbolString + '::' + unfiedTimeframe)
|
446
|
+
symbol, timeframe, stored = await self.watch_topics(url, messageHashes, rawHashes, params)
|
447
|
+
if self.newUpdates:
|
448
|
+
limit = stored.getLimit(symbol, limit)
|
449
|
+
filtered = self.filter_by_since_limit(stored, since, limit, 0, True)
|
450
|
+
return self.create_ohlcv_object(symbol, timeframe, filtered)
|
451
|
+
|
452
|
+
def handle_ohlcv(self, client: Client, message):
|
453
|
+
#
|
454
|
+
# {
|
455
|
+
# "topic": "candle.5.BTCUSDT",
|
456
|
+
# "data": [
|
457
|
+
# {
|
458
|
+
# "start": 1672324800000,
|
459
|
+
# "end": 1672325099999,
|
460
|
+
# "interval": "5",
|
461
|
+
# "open": "16649.5",
|
462
|
+
# "close": "16677",
|
463
|
+
# "high": "16677",
|
464
|
+
# "low": "16608",
|
465
|
+
# "volume": "2.081",
|
466
|
+
# "turnover": "34666.4005",
|
467
|
+
# "confirm": False,
|
468
|
+
# "timestamp": 1672324988882
|
469
|
+
# }
|
470
|
+
# ],
|
471
|
+
# "ts": 1672324988882,
|
472
|
+
# "type": "snapshot"
|
473
|
+
# }
|
474
|
+
#
|
475
|
+
data = self.safe_value(message, 'data', {})
|
476
|
+
topic = self.safe_string(message, 'topic')
|
477
|
+
topicParts = topic.split('.')
|
478
|
+
topicLength = len(topicParts)
|
479
|
+
timeframeId = self.safe_string(topicParts, 1)
|
480
|
+
timeframe = self.find_timeframe(timeframeId)
|
481
|
+
marketId = self.safe_string(topicParts, topicLength - 1)
|
482
|
+
isSpot = client.url.find('spot') > -1
|
483
|
+
marketType = 'spot' if isSpot else 'contract'
|
484
|
+
market = self.safe_market(marketId, None, None, marketType)
|
485
|
+
symbol = market['symbol']
|
486
|
+
ohlcvsByTimeframe = self.safe_value(self.ohlcvs, symbol)
|
487
|
+
if ohlcvsByTimeframe is None:
|
488
|
+
self.ohlcvs[symbol] = {}
|
489
|
+
if self.safe_value(ohlcvsByTimeframe, timeframe) is None:
|
490
|
+
limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
|
491
|
+
self.ohlcvs[symbol][timeframe] = ArrayCacheByTimestamp(limit)
|
492
|
+
stored = self.ohlcvs[symbol][timeframe]
|
493
|
+
for i in range(0, len(data)):
|
494
|
+
parsed = self.parse_ws_ohlcv(data[i])
|
495
|
+
stored.append(parsed)
|
496
|
+
messageHash = 'ohlcv::' + symbol + '::' + timeframe
|
497
|
+
resolveData = [symbol, timeframe, stored]
|
498
|
+
client.resolve(resolveData, messageHash)
|
499
|
+
|
500
|
+
def parse_ws_ohlcv(self, ohlcv, market=None) -> list:
|
501
|
+
#
|
502
|
+
# {
|
503
|
+
# "start": 1670363160000,
|
504
|
+
# "end": 1670363219999,
|
505
|
+
# "interval": "1",
|
506
|
+
# "open": "16987.5",
|
507
|
+
# "close": "16987.5",
|
508
|
+
# "high": "16988",
|
509
|
+
# "low": "16987.5",
|
510
|
+
# "volume": "23.511",
|
511
|
+
# "turnover": "399396.344",
|
512
|
+
# "confirm": False,
|
513
|
+
# "timestamp": 1670363219614
|
514
|
+
# }
|
515
|
+
#
|
516
|
+
return [
|
517
|
+
self.safe_integer(ohlcv, 'start'),
|
518
|
+
self.safe_number(ohlcv, 'open'),
|
519
|
+
self.safe_number(ohlcv, 'high'),
|
520
|
+
self.safe_number(ohlcv, 'low'),
|
521
|
+
self.safe_number(ohlcv, 'close'),
|
522
|
+
self.safe_number_2(ohlcv, 'volume', 'turnover'),
|
523
|
+
]
|
524
|
+
|
525
|
+
async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
526
|
+
"""
|
527
|
+
watches information on multiple trades made by the user
|
528
|
+
|
529
|
+
https://api-docs.pro.apex.exchange/#private-websocket
|
530
|
+
|
531
|
+
:param str symbol: unified market symbol of the market orders were made in
|
532
|
+
:param int [since]: the earliest time in ms to fetch orders for
|
533
|
+
:param int [limit]: the maximum number of order structures to retrieve
|
534
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
535
|
+
:param boolean [params.unifiedMargin]: use unified margin account
|
536
|
+
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
537
|
+
"""
|
538
|
+
messageHash = 'myTrades'
|
539
|
+
await self.load_markets()
|
540
|
+
if symbol is not None:
|
541
|
+
symbol = self.symbol(symbol)
|
542
|
+
messageHash += ':' + symbol
|
543
|
+
timeStamp = str(self.milliseconds())
|
544
|
+
url = self.urls['api']['ws']['private'] + '×tamp=' + timeStamp
|
545
|
+
await self.authenticate(url)
|
546
|
+
trades = await self.watch_topics(url, [messageHash], ['myTrades'], params)
|
547
|
+
if self.newUpdates:
|
548
|
+
limit = trades.getLimit(symbol, limit)
|
549
|
+
return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)
|
550
|
+
|
551
|
+
async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
|
552
|
+
"""
|
553
|
+
|
554
|
+
https://api-docs.pro.apex.exchange/#private-websocket
|
555
|
+
|
556
|
+
watch all open positions
|
557
|
+
:param str[] [symbols]: list of unified market symbols
|
558
|
+
:param int [since]: the earliest time in ms to fetch positions for
|
559
|
+
:param int [limit]: the maximum number of positions to retrieve
|
560
|
+
:param dict params: extra parameters specific to the exchange API endpoint
|
561
|
+
:returns dict[]: a list of `position structure <https://docs.ccxt.com/en/latest/manual.html#position-structure>`
|
562
|
+
"""
|
563
|
+
await self.load_markets()
|
564
|
+
messageHash = ''
|
565
|
+
if not self.is_empty(symbols):
|
566
|
+
symbols = self.market_symbols(symbols)
|
567
|
+
messageHash = '::' + ','.join(symbols)
|
568
|
+
timeStamp = str(self.milliseconds())
|
569
|
+
url = self.urls['api']['ws']['private'] + '×tamp=' + timeStamp
|
570
|
+
messageHash = 'positions' + messageHash
|
571
|
+
client = self.client(url)
|
572
|
+
await self.authenticate(url)
|
573
|
+
self.set_positions_cache(client, symbols)
|
574
|
+
cache = self.positions
|
575
|
+
if cache is None:
|
576
|
+
snapshot = await client.future('fetchPositionsSnapshot')
|
577
|
+
return self.filter_by_symbols_since_limit(snapshot, symbols, since, limit, True)
|
578
|
+
topics = ['positions']
|
579
|
+
newPositions = await self.watch_topics(url, [messageHash], topics, params)
|
580
|
+
if self.newUpdates:
|
581
|
+
return newPositions
|
582
|
+
return self.filter_by_symbols_since_limit(cache, symbols, since, limit, True)
|
583
|
+
|
584
|
+
async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
585
|
+
"""
|
586
|
+
watches information on multiple orders made by the user
|
587
|
+
|
588
|
+
https://api-docs.pro.apex.exchange/#private-websocket
|
589
|
+
|
590
|
+
:param str symbol: unified market symbol of the market orders were made in
|
591
|
+
:param int [since]: the earliest time in ms to fetch orders for
|
592
|
+
:param int [limit]: the maximum number of order structures to retrieve
|
593
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
594
|
+
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
595
|
+
"""
|
596
|
+
await self.load_markets()
|
597
|
+
messageHash = 'orders'
|
598
|
+
if symbol is not None:
|
599
|
+
symbol = self.symbol(symbol)
|
600
|
+
messageHash += ':' + symbol
|
601
|
+
timeStamp = str(self.milliseconds())
|
602
|
+
url = self.urls['api']['ws']['private'] + '×tamp=' + timeStamp
|
603
|
+
await self.authenticate(url)
|
604
|
+
topics = ['orders']
|
605
|
+
orders = await self.watch_topics(url, [messageHash], topics, params)
|
606
|
+
if self.newUpdates:
|
607
|
+
limit = orders.getLimit(symbol, limit)
|
608
|
+
return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
|
609
|
+
|
610
|
+
def handle_my_trades(self, client: Client, lists):
|
611
|
+
# [
|
612
|
+
# {
|
613
|
+
# "symbol":"ETH-USDT",
|
614
|
+
# "side":"BUY",
|
615
|
+
# "orderId":"2048046080",
|
616
|
+
# "fee":"0.625000",
|
617
|
+
# "liquidity":"TAKER",
|
618
|
+
# "accountId":"1024000",
|
619
|
+
# "createdAt":1652185521361,
|
620
|
+
# "isOpen":true,
|
621
|
+
# "size":"0.500",
|
622
|
+
# "price":"2500.0",
|
623
|
+
# "quoteAmount":"1250.0000",
|
624
|
+
# "id":"2048000182272",
|
625
|
+
# "updatedAt":1652185678345
|
626
|
+
# }
|
627
|
+
# ]
|
628
|
+
if self.myTrades is None:
|
629
|
+
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
|
630
|
+
self.myTrades = ArrayCacheBySymbolById(limit)
|
631
|
+
trades = self.myTrades
|
632
|
+
symbols: dict = {}
|
633
|
+
for i in range(0, len(lists)):
|
634
|
+
rawTrade = lists[i]
|
635
|
+
parsed = None
|
636
|
+
parsed = self.parse_ws_trade(rawTrade)
|
637
|
+
symbol = parsed['symbol']
|
638
|
+
symbols[symbol] = True
|
639
|
+
trades.append(parsed)
|
640
|
+
keys = list(symbols.keys())
|
641
|
+
for i in range(0, len(keys)):
|
642
|
+
currentMessageHash = 'myTrades:' + keys[i]
|
643
|
+
client.resolve(trades, currentMessageHash)
|
644
|
+
# non-symbol specific
|
645
|
+
messageHash = 'myTrades'
|
646
|
+
client.resolve(trades, messageHash)
|
647
|
+
|
648
|
+
def handle_order(self, client: Client, lists):
|
649
|
+
# [
|
650
|
+
# {
|
651
|
+
# "symbol":"ETH-USDT",
|
652
|
+
# "cumSuccessFillFee":"0.625000",
|
653
|
+
# "trailingPercent":"0",
|
654
|
+
# "type":"LIMIT",
|
655
|
+
# "unfillableAt":1654779600000,
|
656
|
+
# "isDeleverage":false,
|
657
|
+
# "createdAt":1652185521339,
|
658
|
+
# "price":"2500.0",
|
659
|
+
# "cumSuccessFillValue":"0",
|
660
|
+
# "id":"2048046080",
|
661
|
+
# "cancelReason":"",
|
662
|
+
# "timeInForce":1,
|
663
|
+
# "updatedAt":1652185521392,
|
664
|
+
# "limitFee":"0.625000",
|
665
|
+
# "side":"BUY",
|
666
|
+
# "clientOrderId":"522843990",
|
667
|
+
# "triggerPrice":"",
|
668
|
+
# "expiresAt":1654779600000,
|
669
|
+
# "cumSuccessFillSize":"0",
|
670
|
+
# "accountId":"1024000",
|
671
|
+
# "size":"0.500",
|
672
|
+
# "reduceOnly":false,
|
673
|
+
# "isLiquidate":false,
|
674
|
+
# "remainingSize":"0.000",
|
675
|
+
# "status":"PENDING"
|
676
|
+
# }
|
677
|
+
# ]
|
678
|
+
if self.orders is None:
|
679
|
+
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
|
680
|
+
self.orders = ArrayCacheBySymbolById(limit)
|
681
|
+
orders = self.orders
|
682
|
+
symbols: dict = {}
|
683
|
+
for i in range(0, len(lists)):
|
684
|
+
parsed = None
|
685
|
+
parsed = self.parse_order(lists[i])
|
686
|
+
symbol = parsed['symbol']
|
687
|
+
symbols[symbol] = True
|
688
|
+
orders.append(parsed)
|
689
|
+
symbolsArray = list(symbols.keys())
|
690
|
+
for i in range(0, len(symbolsArray)):
|
691
|
+
currentMessageHash = 'orders:' + symbolsArray[i]
|
692
|
+
client.resolve(orders, currentMessageHash)
|
693
|
+
messageHash = 'orders'
|
694
|
+
client.resolve(orders, messageHash)
|
695
|
+
|
696
|
+
def set_positions_cache(self, client: Client, symbols: Strings = None):
|
697
|
+
if self.positions is not None:
|
698
|
+
return
|
699
|
+
messageHash = 'fetchPositionsSnapshot'
|
700
|
+
if not (messageHash in client.futures):
|
701
|
+
client.future(messageHash)
|
702
|
+
self.spawn(self.load_positions_snapshot, client, messageHash)
|
703
|
+
|
704
|
+
async def load_positions_snapshot(self, client, messageHash):
|
705
|
+
# one ws channel gives positions for all types, for snapshot must load all positions
|
706
|
+
fetchFunctions = [
|
707
|
+
self.fetch_positions(None),
|
708
|
+
]
|
709
|
+
promises = await asyncio.gather(*fetchFunctions)
|
710
|
+
self.positions = ArrayCacheBySymbolBySide()
|
711
|
+
cache = self.positions
|
712
|
+
for i in range(0, len(promises)):
|
713
|
+
positions = promises[i]
|
714
|
+
for ii in range(0, len(positions)):
|
715
|
+
position = positions[ii]
|
716
|
+
cache.append(position)
|
717
|
+
# don't remove the future from the .futures cache
|
718
|
+
future = client.futures[messageHash]
|
719
|
+
future.resolve(cache)
|
720
|
+
client.resolve(cache, 'positions')
|
721
|
+
|
722
|
+
def handle_positions(self, client, lists):
|
723
|
+
#
|
724
|
+
# [
|
725
|
+
# {
|
726
|
+
# "symbol":"ETH-USDT",
|
727
|
+
# "exitPrice":"0",
|
728
|
+
# "side":"LONG",
|
729
|
+
# "maxSize":"2820.000",
|
730
|
+
# "sumOpen":"1.820",
|
731
|
+
# "sumClose":"0.000",
|
732
|
+
# "netFunding":"0.000000",
|
733
|
+
# "entryPrice":"2500.000000000000000000",
|
734
|
+
# "accountId":"1024000",
|
735
|
+
# "createdAt":1652179377769,
|
736
|
+
# "size":"1.820",
|
737
|
+
# "realizedPnl":"0",
|
738
|
+
# "closedAt":1652185521392,
|
739
|
+
# "updatedAt":1652185521392
|
740
|
+
# }
|
741
|
+
# ]
|
742
|
+
#
|
743
|
+
# each account is connected to a different endpoint
|
744
|
+
# and has exactly one subscriptionhash which is the account type
|
745
|
+
if self.positions is None:
|
746
|
+
self.positions = ArrayCacheBySymbolBySide()
|
747
|
+
cache = self.positions
|
748
|
+
newPositions = []
|
749
|
+
for i in range(0, len(lists)):
|
750
|
+
rawPosition = lists[i]
|
751
|
+
position = self.parse_position(rawPosition)
|
752
|
+
side = self.safe_string(position, 'side')
|
753
|
+
# hacky solution to handle closing positions
|
754
|
+
# without crashing, we should handle self properly later
|
755
|
+
newPositions.append(position)
|
756
|
+
if side is None or side == '':
|
757
|
+
# closing update, adding both sides to "reset" both sides
|
758
|
+
# since we don't know which side is being closed
|
759
|
+
position['side'] = 'long'
|
760
|
+
cache.append(position)
|
761
|
+
position['side'] = 'short'
|
762
|
+
cache.append(position)
|
763
|
+
position['side'] = None
|
764
|
+
else:
|
765
|
+
# regular update
|
766
|
+
cache.append(position)
|
767
|
+
messageHashes = self.find_message_hashes(client, 'positions::')
|
768
|
+
for i in range(0, len(messageHashes)):
|
769
|
+
messageHash = messageHashes[i]
|
770
|
+
parts = messageHash.split('::')
|
771
|
+
symbolsString = parts[1]
|
772
|
+
symbols = symbolsString.split(',')
|
773
|
+
positions = self.filter_by_array(newPositions, 'symbol', symbols, False)
|
774
|
+
if not self.is_empty(positions):
|
775
|
+
client.resolve(positions, messageHash)
|
776
|
+
client.resolve(newPositions, 'positions')
|
777
|
+
|
778
|
+
async def authenticate(self, url, params={}):
|
779
|
+
self.check_required_credentials()
|
780
|
+
timestamp = str(self.milliseconds())
|
781
|
+
request_path = '/ws/accounts'
|
782
|
+
http_method = 'GET'
|
783
|
+
messageString = (timestamp + http_method + request_path)
|
784
|
+
signature = self.hmac(self.encode(messageString), self.encode(self.string_to_base64(self.secret)), hashlib.sha256, 'base64')
|
785
|
+
messageHash = 'authenticated'
|
786
|
+
client = self.client(url)
|
787
|
+
future = client.future(messageHash)
|
788
|
+
authenticated = self.safe_value(client.subscriptions, messageHash)
|
789
|
+
if authenticated is None:
|
790
|
+
# auth sign
|
791
|
+
request = {
|
792
|
+
'type': 'login',
|
793
|
+
'topics': ['ws_zk_accounts_v3'],
|
794
|
+
'httpMethod': http_method,
|
795
|
+
'requestPath': request_path,
|
796
|
+
'apiKey': self.apiKey,
|
797
|
+
'passphrase': self.password,
|
798
|
+
'timestamp': timestamp,
|
799
|
+
'signature': signature,
|
800
|
+
}
|
801
|
+
message = {
|
802
|
+
'op': 'login',
|
803
|
+
'args': [json.dumps(request)],
|
804
|
+
}
|
805
|
+
self.watch(url, messageHash, message, messageHash)
|
806
|
+
return await future
|
807
|
+
|
808
|
+
def handle_error_message(self, client: Client, message):
|
809
|
+
#
|
810
|
+
# {
|
811
|
+
# "success": False,
|
812
|
+
# "ret_msg": "error:invalid op",
|
813
|
+
# "conn_id": "5e079fdd-9c7f-404d-9dbf-969d650838b5",
|
814
|
+
# "request": {op: '', args: null}
|
815
|
+
# }
|
816
|
+
#
|
817
|
+
# auth error
|
818
|
+
#
|
819
|
+
# {
|
820
|
+
# "success": False,
|
821
|
+
# "ret_msg": "error:USVC1111",
|
822
|
+
# "conn_id": "e73770fb-a0dc-45bd-8028-140e20958090",
|
823
|
+
# "request": {
|
824
|
+
# "op": "auth",
|
825
|
+
# "args": [
|
826
|
+
# "9rFT6uR4uz9Imkw4Wx",
|
827
|
+
# "1653405853543",
|
828
|
+
# "542e71bd85597b4db0290f0ce2d13ed1fd4bb5df3188716c1e9cc69a879f7889"
|
829
|
+
# ]
|
830
|
+
# }
|
831
|
+
#
|
832
|
+
# {code: '-10009', desc: "Invalid period!"}
|
833
|
+
#
|
834
|
+
# {
|
835
|
+
# "reqId":"1",
|
836
|
+
# "retCode":170131,
|
837
|
+
# "retMsg":"Insufficient balance.",
|
838
|
+
# "op":"order.create",
|
839
|
+
# "data":{
|
840
|
+
#
|
841
|
+
# },
|
842
|
+
# "header":{
|
843
|
+
# "X-Bapi-Limit":"20",
|
844
|
+
# "X-Bapi-Limit-Status":"19",
|
845
|
+
# "X-Bapi-Limit-Reset-Timestamp":"1714236608944",
|
846
|
+
# "Traceid":"3d7168a137bf32a947b7e5e6a575ac7f",
|
847
|
+
# "Timenow":"1714236608946"
|
848
|
+
# },
|
849
|
+
# "connId":"cojifin88smerbj9t560-406"
|
850
|
+
# }
|
851
|
+
#
|
852
|
+
code = self.safe_string_n(message, ['code', 'ret_code', 'retCode'])
|
853
|
+
try:
|
854
|
+
if code is not None and code != '0':
|
855
|
+
feedback = self.id + ' ' + self.json(message)
|
856
|
+
self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
|
857
|
+
msg = self.safe_string_2(message, 'retMsg', 'ret_msg')
|
858
|
+
self.throw_broadly_matched_exception(self.exceptions['broad'], msg, feedback)
|
859
|
+
raise ExchangeError(feedback)
|
860
|
+
success = self.safe_value(message, 'success')
|
861
|
+
if success is not None and not success:
|
862
|
+
ret_msg = self.safe_string(message, 'ret_msg')
|
863
|
+
request = self.safe_value(message, 'request', {})
|
864
|
+
op = self.safe_string(request, 'op')
|
865
|
+
if op == 'auth':
|
866
|
+
raise AuthenticationError('Authentication failed: ' + ret_msg)
|
867
|
+
else:
|
868
|
+
raise ExchangeError(self.id + ' ' + ret_msg)
|
869
|
+
return False
|
870
|
+
except Exception as error:
|
871
|
+
if isinstance(error, AuthenticationError):
|
872
|
+
messageHash = 'authenticated'
|
873
|
+
client.reject(error, messageHash)
|
874
|
+
if messageHash in client.subscriptions:
|
875
|
+
del client.subscriptions[messageHash]
|
876
|
+
else:
|
877
|
+
messageHash = self.safe_string(message, 'reqId')
|
878
|
+
client.reject(error, messageHash)
|
879
|
+
return True
|
880
|
+
|
881
|
+
def handle_message(self, client: Client, message):
|
882
|
+
if self.handle_error_message(client, message):
|
883
|
+
return
|
884
|
+
topic = self.safe_string_2(message, 'topic', 'op', '')
|
885
|
+
methods: dict = {
|
886
|
+
'ws_zk_accounts_v3': self.handle_account,
|
887
|
+
'orderBook': self.handle_order_book,
|
888
|
+
'depth': self.handle_order_book,
|
889
|
+
'candle': self.handle_ohlcv,
|
890
|
+
'kline': self.handle_ohlcv,
|
891
|
+
'ticker': self.handle_ticker,
|
892
|
+
'instrumentInfo': self.handle_ticker,
|
893
|
+
'trade': self.handle_trades,
|
894
|
+
'recentlyTrade': self.handle_trades,
|
895
|
+
'pong': self.handle_pong,
|
896
|
+
'auth': self.handle_authenticate,
|
897
|
+
}
|
898
|
+
exacMethod = self.safe_value(methods, topic)
|
899
|
+
if exacMethod is not None:
|
900
|
+
exacMethod(client, message)
|
901
|
+
return
|
902
|
+
keys = list(methods.keys())
|
903
|
+
for i in range(0, len(keys)):
|
904
|
+
key = keys[i]
|
905
|
+
if topic.find(keys[i]) >= 0:
|
906
|
+
method = methods[key]
|
907
|
+
method(client, message)
|
908
|
+
return
|
909
|
+
# unified auth acknowledgement
|
910
|
+
type = self.safe_string(message, 'type')
|
911
|
+
if type == 'AUTH_RESP':
|
912
|
+
self.handle_authenticate(client, message)
|
913
|
+
|
914
|
+
def ping(self, client: Client):
|
915
|
+
timeStamp = str(self.milliseconds())
|
916
|
+
return {
|
917
|
+
'args': [timeStamp],
|
918
|
+
'op': 'ping',
|
919
|
+
}
|
920
|
+
|
921
|
+
def handle_pong(self, client: Client, message):
|
922
|
+
#
|
923
|
+
# {
|
924
|
+
# "success": True,
|
925
|
+
# "ret_msg": "pong",
|
926
|
+
# "conn_id": "db3158a0-8960-44b9-a9de-ac350ee13158",
|
927
|
+
# "request": {op: "ping", args: null}
|
928
|
+
# }
|
929
|
+
#
|
930
|
+
# {pong: 1653296711335}
|
931
|
+
#
|
932
|
+
client.lastPong = self.safe_integer(message, 'pong')
|
933
|
+
return message
|
934
|
+
|
935
|
+
def handle_account(self, client: Client, message):
|
936
|
+
contents = self.safe_dict(message, 'contents', {})
|
937
|
+
fills = self.safe_list(contents, 'fills', [])
|
938
|
+
if fills is not None:
|
939
|
+
self.handle_my_trades(client, fills)
|
940
|
+
positions = self.safe_list(contents, 'positions', [])
|
941
|
+
if positions is not None:
|
942
|
+
self.handle_positions(client, positions)
|
943
|
+
orders = self.safe_list(contents, 'orders', [])
|
944
|
+
if orders is not None:
|
945
|
+
self.handle_order(client, orders)
|
946
|
+
|
947
|
+
def handle_authenticate(self, client: Client, message):
|
948
|
+
#
|
949
|
+
# {
|
950
|
+
# "success": True,
|
951
|
+
# "ret_msg": '',
|
952
|
+
# "op": "auth",
|
953
|
+
# "conn_id": "ce3dpomvha7dha97tvp0-2xh"
|
954
|
+
# }
|
955
|
+
#
|
956
|
+
success = self.safe_value(message, 'success')
|
957
|
+
code = self.safe_integer(message, 'retCode')
|
958
|
+
messageHash = 'authenticated'
|
959
|
+
if success or code == 0:
|
960
|
+
future = self.safe_value(client.futures, messageHash)
|
961
|
+
future.resolve(True)
|
962
|
+
else:
|
963
|
+
error = AuthenticationError(self.id + ' ' + self.json(message))
|
964
|
+
client.reject(error, messageHash)
|
965
|
+
if messageHash in client.subscriptions:
|
966
|
+
del client.subscriptions[messageHash]
|
967
|
+
return message
|
968
|
+
|
969
|
+
def handle_subscription_status(self, client: Client, message):
|
970
|
+
#
|
971
|
+
# {
|
972
|
+
# "topic": "kline",
|
973
|
+
# "event": "sub",
|
974
|
+
# "params": {
|
975
|
+
# "symbol": "LTCUSDT",
|
976
|
+
# "binary": "false",
|
977
|
+
# "klineType": "1m",
|
978
|
+
# "symbolName": "LTCUSDT"
|
979
|
+
# },
|
980
|
+
# "code": "0",
|
981
|
+
# "msg": "Success"
|
982
|
+
# }
|
983
|
+
#
|
984
|
+
return message
|