ccxt 4.3.85__py2.py3-none-any.whl → 4.3.87__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 +4 -1
- ccxt/abstract/hashkey.py +67 -0
- ccxt/async_support/__init__.py +4 -1
- ccxt/async_support/base/exchange.py +2 -2
- ccxt/async_support/binance.py +4 -2
- ccxt/async_support/bingx.py +5 -1
- ccxt/async_support/bitfinex.py +2 -2
- ccxt/async_support/hashkey.py +4061 -0
- ccxt/async_support/hyperliquid.py +80 -62
- ccxt/async_support/indodax.py +29 -8
- ccxt/async_support/kraken.py +32 -5
- ccxt/async_support/krakenfutures.py +10 -9
- ccxt/async_support/upbit.py +1 -1
- ccxt/base/errors.py +7 -1
- ccxt/base/exchange.py +2 -2
- ccxt/binance.py +4 -2
- ccxt/bingx.py +5 -1
- ccxt/bitfinex.py +2 -2
- ccxt/hashkey.py +4061 -0
- ccxt/hyperliquid.py +80 -62
- ccxt/indodax.py +29 -8
- ccxt/kraken.py +32 -5
- ccxt/krakenfutures.py +10 -9
- ccxt/pro/__init__.py +3 -1
- ccxt/pro/ascendex.py +41 -5
- ccxt/pro/bingx.py +13 -12
- ccxt/pro/bitget.py +143 -16
- ccxt/pro/hashkey.py +783 -0
- ccxt/pro/hyperliquid.py +118 -1
- ccxt/pro/mexc.py +13 -7
- ccxt/pro/p2b.py +30 -7
- ccxt/pro/poloniex.py +32 -3
- ccxt/pro/poloniexfutures.py +1 -0
- ccxt/pro/probit.py +2 -0
- ccxt/pro/upbit.py +44 -3
- ccxt/pro/vertex.py +1 -0
- ccxt/pro/wazirx.py +3 -0
- ccxt/pro/whitebit.py +9 -0
- ccxt/pro/woo.py +1 -0
- ccxt/pro/woofipro.py +1 -0
- ccxt/pro/xt.py +1 -0
- ccxt/test/tests_async.py +31 -31
- ccxt/test/tests_sync.py +31 -31
- ccxt/upbit.py +1 -1
- {ccxt-4.3.85.dist-info → ccxt-4.3.87.dist-info}/METADATA +9 -6
- {ccxt-4.3.85.dist-info → ccxt-4.3.87.dist-info}/RECORD +49 -45
- {ccxt-4.3.85.dist-info → ccxt-4.3.87.dist-info}/LICENSE.txt +0 -0
- {ccxt-4.3.85.dist-info → ccxt-4.3.87.dist-info}/WHEEL +0 -0
- {ccxt-4.3.85.dist-info → ccxt-4.3.87.dist-info}/top_level.txt +0 -0
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)
|