ccxt 4.3.85__py2.py3-none-any.whl → 4.3.86__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/pro/hashkey.py ADDED
@@ -0,0 +1,783 @@
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
+ from ccxt.base.types import Balances, Bool, Int, Market, Order, OrderBook, Position, Str, Strings, Ticker, Trade
9
+ from ccxt.async_support.base.ws.client import Client
10
+ from typing import List
11
+
12
+
13
+ class hashkey(ccxt.async_support.hashkey):
14
+
15
+ def describe(self):
16
+ return self.deep_extend(super(hashkey, self).describe(), {
17
+ 'has': {
18
+ 'ws': True,
19
+ 'watchBalance': True,
20
+ 'watchMyTrades': True,
21
+ 'watchOHLCV': True,
22
+ 'watchOrderBook': True,
23
+ 'watchOrders': True,
24
+ 'watchTicker': True,
25
+ 'watchTrades': True,
26
+ 'watchPositions': False,
27
+ },
28
+ 'urls': {
29
+ 'api': {
30
+ 'ws': {
31
+ 'public': 'wss://stream-glb.hashkey.com/quote/ws/v1',
32
+ 'private': 'wss://stream-glb.hashkey.com/api/v1/ws',
33
+ },
34
+ 'test': {
35
+ 'ws': {
36
+ 'public': 'wss://stream-glb.sim.hashkeydev.com/quote/ws/v1',
37
+ 'private': 'wss://stream-glb.sim.hashkeydev.com/api/v1/ws',
38
+ },
39
+ },
40
+ },
41
+ },
42
+ 'options': {
43
+ 'listenKeyRefreshRate': 3600000,
44
+ 'listenKey': None,
45
+ 'watchBalance': {
46
+ 'fetchBalanceSnapshot': True, # or False
47
+ 'awaitBalanceSnapshot': False, # whether to wait for the balance snapshot before providing updates
48
+ },
49
+ },
50
+ 'streaming': {
51
+ 'keepAlive': 10000,
52
+ },
53
+ })
54
+
55
+ async def wath_public(self, market: Market, topic: str, messageHash: str, params={}):
56
+ request: dict = {
57
+ 'symbol': market['id'],
58
+ 'topic': topic,
59
+ 'event': 'sub',
60
+ }
61
+ url = self.urls['api']['ws']['public']
62
+ return await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)
63
+
64
+ async def watch_private(self, messageHash):
65
+ listenKey = await self.authenticate()
66
+ url = self.get_private_url(listenKey)
67
+ return await self.watch(url, messageHash, None, messageHash)
68
+
69
+ def get_private_url(self, listenKey):
70
+ return self.urls['api']['ws']['private'] + '/' + listenKey
71
+
72
+ async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
73
+ """
74
+ watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
75
+ :see: https://hashkeyglobal-apidoc.readme.io/reference/websocket-api#public-stream
76
+ :param str symbol: unified symbol of the market to fetch OHLCV data for
77
+ :param str timeframe: the length of time each candle represents
78
+ :param int [since]: timestamp in ms of the earliest candle to fetch
79
+ :param int [limit]: the maximum amount of candles to fetch
80
+ :param dict [params]: extra parameters specific to the exchange API endpoint
81
+ :param bool [params.binary]: True or False - default False
82
+ :returns int[][]: A list of candles ordered, open, high, low, close, volume
83
+ """
84
+ await self.load_markets()
85
+ market = self.market(symbol)
86
+ symbol = market['symbol']
87
+ interval = self.safe_string(self.timeframes, timeframe, timeframe)
88
+ topic = 'kline_' + interval
89
+ messageHash = 'ohlcv:' + symbol + ':' + timeframe
90
+ ohlcv = await self.wath_public(market, topic, messageHash, params)
91
+ if self.newUpdates:
92
+ limit = ohlcv.getLimit(symbol, limit)
93
+ return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
94
+
95
+ def handle_ohlcv(self, client: Client, message):
96
+ #
97
+ # {
98
+ # "symbol": "DOGEUSDT",
99
+ # "symbolName": "DOGEUSDT",
100
+ # "topic": "kline",
101
+ # "params": {
102
+ # "realtimeInterval": "24h",
103
+ # "klineType": "1m"
104
+ # },
105
+ # "data": [
106
+ # {
107
+ # "t": 1722861660000,
108
+ # "s": "DOGEUSDT",
109
+ # "sn": "DOGEUSDT",
110
+ # "c": "0.08389",
111
+ # "h": "0.08389",
112
+ # "l": "0.08389",
113
+ # "o": "0.08389",
114
+ # "v": "0"
115
+ # }
116
+ # ],
117
+ # "f": True,
118
+ # "sendTime": 1722861664258,
119
+ # "shared": False
120
+ # }
121
+ #
122
+ marketId = self.safe_string(message, 'symbol')
123
+ market = self.safe_market(marketId)
124
+ symbol = self.safe_symbol(marketId, market)
125
+ if not (symbol in self.ohlcvs):
126
+ self.ohlcvs[symbol] = {}
127
+ params = self.safe_dict(message, 'params')
128
+ klineType = self.safe_string(params, 'klineType')
129
+ timeframe = self.find_timeframe(klineType)
130
+ if not (timeframe in self.ohlcvs[symbol]):
131
+ limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
132
+ self.ohlcvs[symbol][timeframe] = ArrayCacheByTimestamp(limit)
133
+ data = self.safe_list(message, 'data', [])
134
+ stored = self.ohlcvs[symbol][timeframe]
135
+ for i in range(0, len(data)):
136
+ candle = self.safe_dict(data, i, {})
137
+ parsed = self.parse_ws_ohlcv(candle, market)
138
+ stored.append(parsed)
139
+ messageHash = 'ohlcv:' + symbol + ':' + timeframe
140
+ client.resolve(stored, messageHash)
141
+
142
+ def parse_ws_ohlcv(self, ohlcv, market: Market = None) -> list:
143
+ #
144
+ # {
145
+ # "t": 1722861660000,
146
+ # "s": "DOGEUSDT",
147
+ # "sn": "DOGEUSDT",
148
+ # "c": "0.08389",
149
+ # "h": "0.08389",
150
+ # "l": "0.08389",
151
+ # "o": "0.08389",
152
+ # "v": "0"
153
+ # }
154
+ #
155
+ return [
156
+ self.safe_integer(ohlcv, 't'),
157
+ self.safe_number(ohlcv, 'o'),
158
+ self.safe_number(ohlcv, 'h'),
159
+ self.safe_number(ohlcv, 'l'),
160
+ self.safe_number(ohlcv, 'c'),
161
+ self.safe_number(ohlcv, 'v'),
162
+ ]
163
+
164
+ async def watch_ticker(self, symbol: str, params={}) -> Ticker:
165
+ """
166
+ watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
167
+ :see: https://hashkeyglobal-apidoc.readme.io/reference/websocket-api#public-stream
168
+ :param str symbol: unified symbol of the market to fetch the ticker for
169
+ :param dict [params]: extra parameters specific to the exchange API endpoint
170
+ :param bool [params.binary]: True or False - default False
171
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
172
+ """
173
+ await self.load_markets()
174
+ market = self.market(symbol)
175
+ symbol = market['symbol']
176
+ topic = 'realtimes'
177
+ messageHash = 'ticker:' + symbol
178
+ return await self.wath_public(market, topic, messageHash, params)
179
+
180
+ def handle_ticker(self, client: Client, message):
181
+ #
182
+ # {
183
+ # "symbol": "ETHUSDT",
184
+ # "symbolName": "ETHUSDT",
185
+ # "topic": "realtimes",
186
+ # "params": {
187
+ # "realtimeInterval": "24h"
188
+ # },
189
+ # "data": [
190
+ # {
191
+ # "t": 1722864411064,
192
+ # "s": "ETHUSDT",
193
+ # "sn": "ETHUSDT",
194
+ # "c": "2195",
195
+ # "h": "2918.85",
196
+ # "l": "2135.5",
197
+ # "o": "2915.78",
198
+ # "v": "666.5019",
199
+ # "qv": "1586902.757079",
200
+ # "m": "-0.2472",
201
+ # "e": 301
202
+ # }
203
+ # ],
204
+ # "f": False,
205
+ # "sendTime": 1722864411086,
206
+ # "shared": False
207
+ # }
208
+ #
209
+ data = self.safe_list(message, 'data', [])
210
+ ticker = self.parse_ticker(self.safe_dict(data, 0))
211
+ symbol = ticker['symbol']
212
+ messageHash = 'ticker:' + symbol
213
+ self.tickers[symbol] = ticker
214
+ client.resolve(self.tickers[symbol], messageHash)
215
+
216
+ async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
217
+ """
218
+ watches information on multiple trades made in a market
219
+ :see: https://hashkeyglobal-apidoc.readme.io/reference/websocket-api#public-stream
220
+ :param str symbol: unified market symbol of the market trades were made in
221
+ :param int [since]: the earliest time in ms to fetch orders for
222
+ :param int [limit]: the maximum number of trade structures to retrieve
223
+ :param dict [params]: extra parameters specific to the exchange API endpoint
224
+ :param bool [params.binary]: True or False - default False
225
+ :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
226
+ """
227
+ await self.load_markets()
228
+ market = self.market(symbol)
229
+ symbol = market['symbol']
230
+ topic = 'trade'
231
+ messageHash = 'trades:' + symbol
232
+ trades = await self.wath_public(market, topic, messageHash, params)
233
+ if self.newUpdates:
234
+ limit = trades.getLimit(symbol, limit)
235
+ return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
236
+
237
+ def handle_trades(self, client: Client, message):
238
+ #
239
+ # {
240
+ # "symbol": "ETHUSDT",
241
+ # "symbolName": "ETHUSDT",
242
+ # "topic": "trade",
243
+ # "params": {
244
+ # "realtimeInterval": "24h"
245
+ # },
246
+ # "data": [
247
+ # {
248
+ # "v": "1745922896272048129",
249
+ # "t": 1722866228075,
250
+ # "p": "2340.41",
251
+ # "q": "0.0132",
252
+ # "m": True
253
+ # },
254
+ # ...
255
+ # ],
256
+ # "f": True,
257
+ # "sendTime": 1722869464248,
258
+ # "channelId": "668498fffeba4108-00000001-00113184-562e27d215e43f9c-c188b319",
259
+ # "shared": False
260
+ # }
261
+ #
262
+ marketId = self.safe_string(message, 'symbol')
263
+ market = self.safe_market(marketId)
264
+ symbol = market['symbol']
265
+ if not (symbol in self.trades):
266
+ limit = self.safe_integer(self.options, 'tradesLimit', 1000)
267
+ self.trades[symbol] = ArrayCache(limit)
268
+ stored = self.trades[symbol]
269
+ data = self.safe_list(message, 'data')
270
+ if data is not None:
271
+ data = self.sort_by(data, 't')
272
+ for i in range(0, len(data)):
273
+ trade = self.safe_dict(data, i)
274
+ parsed = self.parse_ws_trade(trade, market)
275
+ stored.append(parsed)
276
+ messageHash = 'trades' + ':' + symbol
277
+ client.resolve(stored, messageHash)
278
+
279
+ async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
280
+ """
281
+ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
282
+ :see: https://hashkeyglobal-apidoc.readme.io/reference/websocket-api#public-stream
283
+ :param str symbol: unified symbol of the market to fetch the order book for
284
+ :param int [limit]: the maximum amount of order book entries to return.
285
+ :param dict [params]: extra parameters specific to the exchange API endpoint
286
+ :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
287
+ """
288
+ await self.load_markets()
289
+ market = self.market(symbol)
290
+ symbol = market['symbol']
291
+ topic = 'depth'
292
+ messageHash = 'orderbook:' + symbol
293
+ orderbook = await self.wath_public(market, topic, messageHash, params)
294
+ return orderbook.limit()
295
+
296
+ def handle_order_book(self, client: Client, message):
297
+ #
298
+ # {
299
+ # "symbol": "ETHUSDT",
300
+ # "symbolName": "ETHUSDT",
301
+ # "topic": "depth",
302
+ # "params": {"realtimeInterval": "24h"},
303
+ # "data": [
304
+ # {
305
+ # "e": 301,
306
+ # "s": "ETHUSDT",
307
+ # "t": 1722873144371,
308
+ # "v": "84661262_18",
309
+ # "b": [
310
+ # ["1650", "0.0864"],
311
+ # ...
312
+ # ],
313
+ # "a": [
314
+ # ["4085", "0.0074"],
315
+ # ...
316
+ # ],
317
+ # "o": 0
318
+ # }
319
+ # ],
320
+ # "f": False,
321
+ # "sendTime": 1722873144589,
322
+ # "channelId": "2265aafffe68b588-00000001-0011510c-9e9ca710b1500854-551830bd",
323
+ # "shared": False
324
+ # }
325
+ #
326
+ marketId = self.safe_string(message, 'symbol')
327
+ symbol = self.safe_symbol(marketId)
328
+ messageHash = 'orderbook:' + symbol
329
+ if not (symbol in self.orderbooks):
330
+ self.orderbooks[symbol] = self.order_book({})
331
+ orderbook = self.orderbooks[symbol]
332
+ data = self.safe_list(message, 'data', [])
333
+ dataEntry = self.safe_dict(data, 0)
334
+ timestamp = self.safe_integer(dataEntry, 't')
335
+ snapshot = self.parse_order_book(dataEntry, symbol, timestamp, 'b', 'a')
336
+ orderbook.reset(snapshot)
337
+ orderbook['nonce'] = self.safe_integer(message, 'id')
338
+ self.orderbooks[symbol] = orderbook
339
+ client.resolve(orderbook, messageHash)
340
+
341
+ async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
342
+ """
343
+ watches information on multiple orders made by the user
344
+ :see: https://hashkeyglobal-apidoc.readme.io/reference/websocket-api#private-stream
345
+ :param str symbol: unified market symbol of the market orders were made in
346
+ :param int [since]: the earliest time in ms to fetch orders for
347
+ :param int [limit]: the maximum number of order structures to retrieve
348
+ :param dict [params]: extra parameters specific to the exchange API endpoint
349
+ :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
350
+ """
351
+ await self.load_markets()
352
+ messageHash = 'orders'
353
+ if symbol is not None:
354
+ symbol = self.symbol(symbol)
355
+ messageHash = messageHash + ':' + symbol
356
+ orders = await self.watch_private(messageHash)
357
+ if self.newUpdates:
358
+ limit = orders.getLimit(symbol, limit)
359
+ return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
360
+
361
+ def handle_order(self, client: Client, message):
362
+ #
363
+ # swap
364
+ # {
365
+ # "e": "contractExecutionReport",
366
+ # "E": "1723037391181",
367
+ # "s": "ETHUSDT-PERPETUAL",
368
+ # "c": "1723037389677",
369
+ # "S": "BUY_OPEN",
370
+ # "o": "LIMIT",
371
+ # "f": "IOC",
372
+ # "q": "1",
373
+ # "p": "2561.75",
374
+ # "X": "FILLED",
375
+ # "i": "1747358716129257216",
376
+ # "l": "1",
377
+ # "z": "1",
378
+ # "L": "2463.36",
379
+ # "n": "0.001478016",
380
+ # "N": "USDT",
381
+ # "u": True,
382
+ # "w": True,
383
+ # "m": False,
384
+ # "O": "1723037391140",
385
+ # "Z": "2463.36",
386
+ # "C": False,
387
+ # "v": "5",
388
+ # "reqAmt": "0",
389
+ # "d": "1747358716255075840",
390
+ # "r": "0",
391
+ # "V": "2463.36",
392
+ # "P": "0",
393
+ # "lo": False,
394
+ # "lt": ""
395
+ # }
396
+ #
397
+ if self.orders is None:
398
+ limit = self.safe_integer(self.options, 'ordersLimit', 1000)
399
+ self.orders = ArrayCacheBySymbolById(limit)
400
+ parsed = self.parse_ws_order(message)
401
+ orders = self.orders
402
+ orders.append(parsed)
403
+ messageHash = 'orders'
404
+ client.resolve(orders, messageHash)
405
+ symbol = parsed['symbol']
406
+ symbolSpecificMessageHash = messageHash + ':' + symbol
407
+ client.resolve(orders, symbolSpecificMessageHash)
408
+
409
+ def parse_ws_order(self, order: dict, market: Market = None) -> Order:
410
+ marketId = self.safe_string(order, 's')
411
+ market = self.safe_market(marketId, market)
412
+ timestamp = self.safe_integer(order, 'O')
413
+ side = self.safe_string_lower(order, 'S')
414
+ reduceOnly: Bool = None
415
+ side, reduceOnly = self.parseOrderSideAndReduceOnly(side)
416
+ type = self.parseOrderType(self.safe_string(order, 'o'))
417
+ timeInForce = self.safe_string(order, 'f')
418
+ postOnly: Bool = None
419
+ type, timeInForce, postOnly = self.parseOrderTypeTimeInForceAndPostOnly(type, timeInForce)
420
+ if market['contract']: # swap orders are always have type 'LIMIT', thus we can not define the correct type
421
+ type = None
422
+ return self.safe_order({
423
+ 'id': self.safe_string(order, 'i'),
424
+ 'clientOrderId': self.safe_string(order, 'c'),
425
+ 'datetime': self.iso8601(timestamp),
426
+ 'timestamp': timestamp,
427
+ 'lastTradeTimestamp': None,
428
+ 'lastUpdateTimestamp': None,
429
+ 'status': self.parse_order_status(self.safe_string(order, 'X')),
430
+ 'symbol': market['symbol'],
431
+ 'type': type,
432
+ 'timeInForce': timeInForce,
433
+ 'side': side,
434
+ 'price': self.safe_string(order, 'p'),
435
+ 'average': self.safe_string(order, 'V'),
436
+ 'amount': self.omit_zero(self.safe_string(order, 'q')),
437
+ 'filled': self.safe_string(order, 'z'),
438
+ 'remaining': self.safe_string(order, 'r'),
439
+ 'stopPrice': None,
440
+ 'triggerPrice': None,
441
+ 'takeProfitPrice': None,
442
+ 'stopLossPrice': None,
443
+ 'cost': self.omit_zero(self.safe_string(order, 'Z')),
444
+ 'trades': None,
445
+ 'fee': {
446
+ 'currency': self.safe_currency_code(self.safe_string(order, 'N')),
447
+ 'amount': self.omit_zero(self.safe_string(order, 'n')),
448
+ },
449
+ 'reduceOnly': reduceOnly,
450
+ 'postOnly': postOnly,
451
+ 'info': order,
452
+ }, market)
453
+
454
+ async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
455
+ """
456
+ watches information on multiple trades made by the user
457
+ :see: https://hashkeyglobal-apidoc.readme.io/reference/websocket-api#private-stream
458
+ :param str symbol: unified market symbol of the market trades were made in
459
+ :param int [since]: the earliest time in ms to fetch trades for
460
+ :param int [limit]: the maximum number of trade structures to retrieve
461
+ :param dict [params]: extra parameters specific to the exchange API endpoint
462
+ :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
463
+ """
464
+ await self.load_markets()
465
+ messageHash = 'myTrades'
466
+ if symbol is not None:
467
+ symbol = self.symbol(symbol)
468
+ messageHash += ':' + symbol
469
+ trades = await self.watch_private(messageHash)
470
+ if self.newUpdates:
471
+ limit = trades.getLimit(symbol, limit)
472
+ return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
473
+
474
+ def handle_my_trade(self, client: Client, message, subscription={}):
475
+ #
476
+ # {
477
+ # "e": "ticketInfo",
478
+ # "E": "1723037391156",
479
+ # "s": "ETHUSDT-PERPETUAL",
480
+ # "q": "1.00",
481
+ # "t": "1723037391147",
482
+ # "p": "2463.36",
483
+ # "T": "1747358716187197441",
484
+ # "o": "1747358716129257216",
485
+ # "c": "1723037389677",
486
+ # "a": "1735619524953226496",
487
+ # "m": False,
488
+ # "S": "BUY"
489
+ # }
490
+ #
491
+ if self.myTrades is None:
492
+ limit = self.safe_integer(self.options, 'tradesLimit', 1000)
493
+ self.myTrades = ArrayCacheBySymbolById(limit)
494
+ tradesArray = self.myTrades
495
+ parsed = self.parse_ws_trade(message)
496
+ tradesArray.append(parsed)
497
+ self.myTrades = tradesArray
498
+ messageHash = 'myTrades'
499
+ client.resolve(tradesArray, messageHash)
500
+ symbol = parsed['symbol']
501
+ symbolSpecificMessageHash = messageHash + ':' + symbol
502
+ client.resolve(tradesArray, symbolSpecificMessageHash)
503
+
504
+ def parse_ws_trade(self, trade, market=None) -> Trade:
505
+ #
506
+ # watchTrades
507
+ # {
508
+ # "v": "1745922896272048129",
509
+ # "t": 1722866228075,
510
+ # "p": "2340.41",
511
+ # "q": "0.0132",
512
+ # "m": True
513
+ # }
514
+ #
515
+ # watchMyTrades
516
+ # {
517
+ # "e": "ticketInfo",
518
+ # "E": "1723037391156",
519
+ # "s": "ETHUSDT-PERPETUAL",
520
+ # "q": "1.00",
521
+ # "t": "1723037391147",
522
+ # "p": "2463.36",
523
+ # "T": "1747358716187197441",
524
+ # "o": "1747358716129257216",
525
+ # "c": "1723037389677",
526
+ # "a": "1735619524953226496",
527
+ # "m": False,
528
+ # "S": "BUY"
529
+ # }
530
+ #
531
+ marketId = self.safe_string(trade, 's')
532
+ market = self.safe_market(marketId, market)
533
+ timestamp = self.safe_integer(trade, 't')
534
+ isMaker = self.safe_bool(trade, 'm')
535
+ takerOrMaker: Str = None
536
+ if isMaker is not None:
537
+ if isMaker:
538
+ takerOrMaker = 'maker'
539
+ else:
540
+ takerOrMaker = 'taker'
541
+ return self.safe_trade({
542
+ 'id': self.safe_string_2(trade, 'v', 'T'),
543
+ 'timestamp': timestamp,
544
+ 'datetime': self.iso8601(timestamp),
545
+ 'symbol': market['symbol'],
546
+ 'side': self.safe_string_lower(trade, 'S'),
547
+ 'price': self.safe_string(trade, 'p'),
548
+ 'amount': self.safe_string(trade, 'q'),
549
+ 'cost': None,
550
+ 'takerOrMaker': takerOrMaker,
551
+ 'type': None,
552
+ 'order': self.safe_string(trade, 'o'),
553
+ 'fee': None,
554
+ 'info': trade,
555
+ }, market)
556
+
557
+ async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
558
+ """
559
+ :see: https://hashkeyglobal-apidoc.readme.io/reference/websocket-api#private-stream
560
+ watch all open positions
561
+ :param str[]|None symbols: list of unified market symbols
562
+ :param dict params: extra parameters specific to the exchange API endpoint
563
+ :returns dict[]: a list of `position structure <https://docs.ccxt.com/en/latest/manual.html#position-structure>`
564
+ """
565
+ await self.load_markets()
566
+ listenKey = await self.authenticate()
567
+ symbols = self.market_symbols(symbols)
568
+ messageHash = 'positions'
569
+ messageHashes = []
570
+ if symbols is None:
571
+ messageHashes.append(messageHash)
572
+ else:
573
+ for i in range(0, len(symbols)):
574
+ symbol = symbols[i]
575
+ messageHashes.append(messageHash + ':' + symbol)
576
+ url = self.get_private_url(listenKey)
577
+ positions = await self.watch_multiple(url, messageHashes, None, messageHashes)
578
+ if self.newUpdates:
579
+ return positions
580
+ return self.filter_by_symbols_since_limit(self.positions, symbols, since, limit, True)
581
+
582
+ def handle_position(self, client: Client, message):
583
+ #
584
+ # {
585
+ # "e": "outboundContractPositionInfo",
586
+ # "E": "1723084699801",
587
+ # "A": "1735619524953226496",
588
+ # "s": "ETHUSDT-PERPETUAL",
589
+ # "S": "LONG",
590
+ # "p": "2429.6",
591
+ # "P": "2",
592
+ # "a": "2",
593
+ # "f": "10760.14",
594
+ # "m": "1.0085",
595
+ # "r": "-0.0029",
596
+ # "up": "0.0478",
597
+ # "pr": "0.0492",
598
+ # "pv": "4.8592",
599
+ # "v": "5.00",
600
+ # "mt": "CROSS",
601
+ # "mm": "0.0367"
602
+ # }
603
+ #
604
+ if self.positions is None:
605
+ self.positions = ArrayCacheBySymbolBySide()
606
+ positions = self.positions
607
+ parsed = self.parse_ws_position(message)
608
+ positions.append(parsed)
609
+ messageHash = 'positions'
610
+ client.resolve(parsed, messageHash)
611
+ symbol = parsed['symbol']
612
+ client.resolve(parsed, messageHash + ':' + symbol)
613
+
614
+ def parse_ws_position(self, position, market: Market = None) -> Position:
615
+ marketId = self.safe_string(position, 's')
616
+ market = self.safe_market(marketId)
617
+ timestamp = self.safe_integer(position, 'E')
618
+ return self.safe_position({
619
+ 'symbol': market['symbol'],
620
+ 'id': None,
621
+ 'timestamp': timestamp,
622
+ 'datetime': self.iso8601(timestamp),
623
+ 'contracts': self.safe_number(position, 'P'),
624
+ 'contractSize': None,
625
+ 'side': self.safe_string_lower(position, 'S'),
626
+ 'notional': self.safe_number(position, 'pv'),
627
+ 'leverage': self.safe_integer(position, 'v'),
628
+ 'unrealizedPnl': self.safe_number(position, 'up'),
629
+ 'realizedPnl': self.safe_number(position, 'r'),
630
+ 'collateral': None,
631
+ 'entryPrice': self.safe_number(position, 'p'),
632
+ 'markPrice': None,
633
+ 'liquidationPrice': self.safe_number(position, 'f'),
634
+ 'marginMode': self.safe_string_lower(position, 'mt'),
635
+ 'hedged': True,
636
+ 'maintenanceMargin': self.safe_number(position, 'mm'),
637
+ 'maintenanceMarginPercentage': None,
638
+ 'initialMargin': self.safe_number(position, 'm'), # todo check
639
+ 'initialMarginPercentage': None,
640
+ 'marginRatio': None,
641
+ 'lastUpdateTimestamp': None,
642
+ 'lastPrice': None,
643
+ 'stopLossPrice': None,
644
+ 'takeProfitPrice': None,
645
+ 'percentage': None,
646
+ 'info': position,
647
+ })
648
+
649
+ async def watch_balance(self, params={}) -> Balances:
650
+ """
651
+ watch balance and get the amount of funds available for trading or funds locked in orders
652
+ :see: https://hashkeyglobal-apidoc.readme.io/reference/websocket-api#private-stream
653
+ :param dict [params]: extra parameters specific to the exchange API endpoint
654
+ :param str [params.type]: 'spot' or 'swap' - the type of the market to watch balance for(default 'spot')
655
+ :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
656
+ """
657
+ listenKey = await self.authenticate()
658
+ await self.load_markets()
659
+ type = 'spot'
660
+ type, params = self.handle_market_type_and_params('watchBalance', None, params, type)
661
+ messageHash = 'balance:' + type
662
+ url = self.get_private_url(listenKey)
663
+ client = self.client(url)
664
+ self.set_balance_cache(client, type, messageHash)
665
+ fetchBalanceSnapshot = None
666
+ awaitBalanceSnapshot = None
667
+ fetchBalanceSnapshot, params = self.handle_option_and_params(self.options, 'watchBalance', 'fetchBalanceSnapshot', True)
668
+ awaitBalanceSnapshot, params = self.handle_option_and_params(self.options, 'watchBalance', 'awaitBalanceSnapshot', False)
669
+ if fetchBalanceSnapshot and awaitBalanceSnapshot:
670
+ await client.future(type + ':fetchBalanceSnapshot')
671
+ return await self.watch(url, messageHash, None, messageHash)
672
+
673
+ def set_balance_cache(self, client: Client, type, subscribeHash):
674
+ if subscribeHash in client.subscriptions:
675
+ return
676
+ options = self.safe_dict(self.options, 'watchBalance')
677
+ snapshot = self.safe_bool(options, 'fetchBalanceSnapshot', True)
678
+ if snapshot:
679
+ messageHash = type + ':' + 'fetchBalanceSnapshot'
680
+ if not (messageHash in client.futures):
681
+ client.future(messageHash)
682
+ self.spawn(self.load_balance_snapshot, client, messageHash, type)
683
+ self.balance[type] = {}
684
+ # without self comment, transpilation breaks for some reason...
685
+
686
+ async def load_balance_snapshot(self, client, messageHash, type):
687
+ response = await self.fetch_balance({'type': type})
688
+ self.balance[type] = self.extend(response, self.safe_value(self.balance, type, {}))
689
+ # don't remove the future from the .futures cache
690
+ future = client.futures[messageHash]
691
+ future.resolve()
692
+ client.resolve(self.balance[type], 'balance:' + type)
693
+
694
+ def handle_balance(self, client: Client, message):
695
+ #
696
+ # {
697
+ # "e": "outboundContractAccountInfo", # event type
698
+ # # outboundContractAccountInfo
699
+ # "E": "1714717314118", # event time
700
+ # "T": True, # can trade
701
+ # "W": True, # can withdraw
702
+ # "D": True, # can deposit
703
+ # "B": [ # balances changed
704
+ # {
705
+ # "a": "USDT", # asset
706
+ # "f": "474960.65", # free amount
707
+ # "l": "24835.178056020383226869", # locked amount
708
+ # "r": "" # to be released
709
+ # }
710
+ # ]
711
+ # }
712
+ #
713
+ event = self.safe_string(message, 'e')
714
+ data = self.safe_list(message, 'B', [])
715
+ balanceUpdate = self.safe_dict(data, 0)
716
+ isSpot = event == 'outboundAccountInfo'
717
+ type = 'spot' if isSpot else 'swap'
718
+ if not (type in self.balance):
719
+ self.balance[type] = {}
720
+ self.balance[type]['info'] = message
721
+ currencyId = self.safe_string(balanceUpdate, 'a')
722
+ code = self.safe_currency_code(currencyId)
723
+ account = self.account()
724
+ account['free'] = self.safe_string(balanceUpdate, 'f')
725
+ account['used'] = self.safe_string(balanceUpdate, 'l')
726
+ self.balance[type][code] = account
727
+ self.balance[type] = self.safe_balance(self.balance[type])
728
+ messageHash = 'balance:' + type
729
+ client.resolve(self.balance[type], messageHash)
730
+
731
+ async def authenticate(self, params={}):
732
+ listenKey = self.safe_string(self.options, 'listenKey')
733
+ if listenKey is not None:
734
+ return listenKey
735
+ response = await self.privatePostApiV1UserDataStream(params)
736
+ #
737
+ # {
738
+ # "listenKey": "atbNEcWnBqnmgkfmYQeTuxKTpTStlZzgoPLJsZhzAOZTbAlxbHqGNWiYaUQzMtDz"
739
+ # }
740
+ #
741
+ listenKey = self.safe_string(response, 'listenKey')
742
+ self.options['listenKey'] = listenKey
743
+ listenKeyRefreshRate = self.safe_integer(self.options, 'listenKeyRefreshRate', 3600000)
744
+ self.delay(listenKeyRefreshRate, self.keep_alive_listen_key, listenKey, params)
745
+ return listenKey
746
+
747
+ async def keep_alive_listen_key(self, listenKey, params={}):
748
+ if listenKey is None:
749
+ return
750
+ request: dict = {
751
+ 'listenKey': listenKey,
752
+ }
753
+ try:
754
+ await self.privatePutApiV1UserDataStream(self.extend(request, params))
755
+ listenKeyRefreshRate = self.safe_integer(self.options, 'listenKeyRefreshRate', 1200000)
756
+ self.delay(listenKeyRefreshRate, self.keep_alive_listen_key, listenKey, params)
757
+ except Exception as error:
758
+ url = self.get_private_url(listenKey)
759
+ client = self.client(url)
760
+ self.options['listenKey'] = None
761
+ client.reject(error)
762
+ del self.clients[url]
763
+
764
+ def handle_message(self, client: Client, message):
765
+ if isinstance(message, list):
766
+ message = self.safe_dict(message, 0, {})
767
+ topic = self.safe_string_2(message, 'topic', 'e')
768
+ if topic == 'kline':
769
+ self.handle_ohlcv(client, message)
770
+ elif topic == 'realtimes':
771
+ self.handle_ticker(client, message)
772
+ elif topic == 'trade':
773
+ self.handle_trades(client, message)
774
+ elif topic == 'depth':
775
+ self.handle_order_book(client, message)
776
+ elif (topic == 'contractExecutionReport') or (topic == 'executionReport'):
777
+ self.handle_order(client, message)
778
+ elif topic == 'ticketInfo':
779
+ self.handle_my_trade(client, message)
780
+ elif topic == 'outboundContractPositionInfo':
781
+ self.handle_position(client, message)
782
+ elif (topic == 'outboundAccountInfo') or (topic == 'outboundContractAccountInfo'):
783
+ self.handle_balance(client, message)