ccxt 4.4.85__py2.py3-none-any.whl → 4.4.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.
Files changed (92) hide show
  1. ccxt/__init__.py +7 -5
  2. ccxt/abstract/modetrade.py +119 -0
  3. ccxt/abstract/myokx.py +2 -0
  4. ccxt/abstract/okx.py +2 -0
  5. ccxt/abstract/okxus.py +349 -0
  6. ccxt/ascendex.py +187 -151
  7. ccxt/async_support/__init__.py +7 -5
  8. ccxt/async_support/ascendex.py +187 -151
  9. ccxt/async_support/base/exchange.py +30 -26
  10. ccxt/async_support/bequant.py +1 -1
  11. ccxt/async_support/binance.py +1 -1
  12. ccxt/async_support/bitget.py +4 -4
  13. ccxt/async_support/bitmart.py +1 -1
  14. ccxt/async_support/bitteam.py +31 -0
  15. ccxt/async_support/{huobijp.py → bittrade.py} +11 -11
  16. ccxt/async_support/coinbase.py +2 -5
  17. ccxt/async_support/coinmetro.py +3 -0
  18. ccxt/async_support/deribit.py +4 -5
  19. ccxt/async_support/gate.py +91 -73
  20. ccxt/async_support/hollaex.py +106 -49
  21. ccxt/async_support/htx.py +30 -51
  22. ccxt/async_support/hyperliquid.py +36 -20
  23. ccxt/async_support/kraken.py +5 -8
  24. ccxt/async_support/mexc.py +2 -2
  25. ccxt/async_support/modetrade.py +2727 -0
  26. ccxt/async_support/ndax.py +25 -24
  27. ccxt/async_support/okcoin.py +12 -29
  28. ccxt/async_support/okx.py +99 -3
  29. ccxt/async_support/okxus.py +54 -0
  30. ccxt/async_support/onetrading.py +10 -7
  31. ccxt/async_support/oxfun.py +40 -110
  32. ccxt/async_support/paradex.py +6 -0
  33. ccxt/async_support/phemex.py +4 -6
  34. ccxt/async_support/poloniex.py +172 -159
  35. ccxt/async_support/probit.py +18 -47
  36. ccxt/async_support/timex.py +5 -10
  37. ccxt/async_support/vertex.py +3 -4
  38. ccxt/async_support/whitebit.py +41 -11
  39. ccxt/async_support/woo.py +101 -75
  40. ccxt/async_support/woofipro.py +25 -20
  41. ccxt/async_support/xt.py +31 -41
  42. ccxt/base/exchange.py +12 -9
  43. ccxt/bequant.py +1 -1
  44. ccxt/binance.py +1 -1
  45. ccxt/bitget.py +4 -4
  46. ccxt/bitmart.py +1 -1
  47. ccxt/bitteam.py +31 -0
  48. ccxt/{huobijp.py → bittrade.py} +11 -11
  49. ccxt/coinbase.py +2 -5
  50. ccxt/coinmetro.py +3 -0
  51. ccxt/deribit.py +4 -5
  52. ccxt/gate.py +91 -73
  53. ccxt/hollaex.py +106 -49
  54. ccxt/htx.py +30 -51
  55. ccxt/hyperliquid.py +36 -20
  56. ccxt/kraken.py +5 -8
  57. ccxt/mexc.py +2 -2
  58. ccxt/modetrade.py +2727 -0
  59. ccxt/ndax.py +25 -24
  60. ccxt/okcoin.py +12 -29
  61. ccxt/okx.py +99 -3
  62. ccxt/okxus.py +54 -0
  63. ccxt/onetrading.py +10 -7
  64. ccxt/oxfun.py +40 -110
  65. ccxt/paradex.py +6 -0
  66. ccxt/phemex.py +4 -6
  67. ccxt/poloniex.py +172 -159
  68. ccxt/pro/__init__.py +101 -3
  69. ccxt/pro/binance.py +1 -0
  70. ccxt/pro/{huobijp.py → bittrade.py} +3 -3
  71. ccxt/pro/luno.py +6 -5
  72. ccxt/pro/mexc.py +2 -0
  73. ccxt/pro/modetrade.py +1271 -0
  74. ccxt/pro/okxus.py +38 -0
  75. ccxt/probit.py +18 -47
  76. ccxt/test/tests_async.py +17 -1
  77. ccxt/test/tests_sync.py +17 -1
  78. ccxt/timex.py +5 -10
  79. ccxt/vertex.py +3 -4
  80. ccxt/whitebit.py +41 -11
  81. ccxt/woo.py +100 -75
  82. ccxt/woofipro.py +24 -20
  83. ccxt/xt.py +31 -41
  84. {ccxt-4.4.85.dist-info → ccxt-4.4.87.dist-info}/METADATA +19 -8
  85. {ccxt-4.4.85.dist-info → ccxt-4.4.87.dist-info}/RECORD +89 -84
  86. ccxt/abstract/kuna.py +0 -182
  87. ccxt/async_support/kuna.py +0 -1935
  88. ccxt/kuna.py +0 -1935
  89. /ccxt/abstract/{huobijp.py → bittrade.py} +0 -0
  90. {ccxt-4.4.85.dist-info → ccxt-4.4.87.dist-info}/LICENSE.txt +0 -0
  91. {ccxt-4.4.85.dist-info → ccxt-4.4.87.dist-info}/WHEEL +0 -0
  92. {ccxt-4.4.85.dist-info → ccxt-4.4.87.dist-info}/top_level.txt +0 -0
ccxt/pro/modetrade.py ADDED
@@ -0,0 +1,1271 @@
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 Any, Balances, Int, Order, OrderBook, Position, Str, Strings, Ticker, Tickers, Trade
9
+ from ccxt.async_support.base.ws.client import Client
10
+ from typing import List
11
+ from ccxt.base.errors import AuthenticationError
12
+ from ccxt.base.errors import NotSupported
13
+ from ccxt.base.precise import Precise
14
+
15
+
16
+ class modetrade(ccxt.async_support.modetrade):
17
+
18
+ def describe(self) -> Any:
19
+ return self.deep_extend(super(modetrade, self).describe(), {
20
+ 'has': {
21
+ 'ws': True,
22
+ 'watchBalance': True,
23
+ 'watchMyTrades': True,
24
+ 'watchOHLCV': True,
25
+ 'watchOrderBook': True,
26
+ 'watchOrders': True,
27
+ 'watchTicker': True,
28
+ 'watchTickers': True,
29
+ 'watchBidsAsks': True,
30
+ 'watchTrades': True,
31
+ 'watchTradesForSymbols': False,
32
+ 'watchPositions': True,
33
+ },
34
+ 'urls': {
35
+ 'api': {
36
+ 'ws': {
37
+ 'public': 'wss://ws-evm.orderly.org/ws/stream',
38
+ 'private': 'wss://ws-private-evm.orderly.org/v2/ws/private/stream',
39
+ },
40
+ },
41
+ 'test': {
42
+ 'ws': {
43
+ 'public': 'wss://testnet-ws-evm.orderly.org/ws/stream',
44
+ 'private': 'wss://testnet-ws-private-evm.orderly.org/v2/ws/private/stream',
45
+ },
46
+ },
47
+ },
48
+ 'requiredCredentials': {
49
+ 'apiKey': True,
50
+ 'secret': True,
51
+ 'accountId': True,
52
+ },
53
+ 'options': {
54
+ 'tradesLimit': 1000,
55
+ 'ordersLimit': 1000,
56
+ 'requestId': {},
57
+ 'watchPositions': {
58
+ 'fetchPositionsSnapshot': True, # or False
59
+ 'awaitPositionsSnapshot': True, # whether to wait for the positions snapshot before providing updates
60
+ },
61
+ },
62
+ 'streaming': {
63
+ 'ping': self.ping,
64
+ 'keepAlive': 10000,
65
+ },
66
+ 'exceptions': {
67
+ 'ws': {
68
+ 'exact': {
69
+ 'Auth is needed.': AuthenticationError,
70
+ },
71
+ },
72
+ },
73
+ })
74
+
75
+ def request_id(self, url):
76
+ options = self.safe_dict(self.options, 'requestId', {})
77
+ previousValue = self.safe_integer(options, url, 0)
78
+ newValue = self.sum(previousValue, 1)
79
+ self.options['requestId'][url] = newValue
80
+ return newValue
81
+
82
+ async def watch_public(self, messageHash, message):
83
+ # the default id
84
+ id = 'OqdphuyCtYWxwzhxyLLjOWNdFP7sQt8RPWzmb5xY'
85
+ if self.accountId is not None:
86
+ id = self.accountId
87
+ url = self.urls['api']['ws']['public'] + '/' + id
88
+ requestId = self.request_id(url)
89
+ subscribe: dict = {
90
+ 'id': requestId,
91
+ }
92
+ request = self.extend(subscribe, message)
93
+ return await self.watch(url, messageHash, request, messageHash, subscribe)
94
+
95
+ async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
96
+ """
97
+
98
+ https://orderly.network/docs/build-on-evm/evm-api/websocket-api/public/orderbook
99
+
100
+ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
101
+ :param str symbol: unified symbol of the market to fetch the order book for
102
+ :param int [limit]: the maximum amount of order book entries to return.
103
+ :param dict [params]: extra parameters specific to the exchange API endpoint
104
+ :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
105
+ """
106
+ await self.load_markets()
107
+ name = 'orderbook'
108
+ market = self.market(symbol)
109
+ topic = market['id'] + '@' + name
110
+ request: dict = {
111
+ 'event': 'subscribe',
112
+ 'topic': topic,
113
+ }
114
+ message = self.extend(request, params)
115
+ orderbook = await self.watch_public(topic, message)
116
+ return orderbook.limit()
117
+
118
+ def handle_order_book(self, client: Client, message):
119
+ #
120
+ # {
121
+ # "topic": "PERP_BTC_USDC@orderbook",
122
+ # "ts": 1650121915308,
123
+ # "data": {
124
+ # "symbol": "PERP_BTC_USDC",
125
+ # "bids": [
126
+ # [
127
+ # 0.30891,
128
+ # 2469.98
129
+ # ]
130
+ # ],
131
+ # "asks": [
132
+ # [
133
+ # 0.31075,
134
+ # 2379.63
135
+ # ]
136
+ # ]
137
+ # }
138
+ # }
139
+ #
140
+ data = self.safe_dict(message, 'data', {})
141
+ marketId = self.safe_string(data, 'symbol')
142
+ market = self.safe_market(marketId)
143
+ symbol = market['symbol']
144
+ topic = self.safe_string(message, 'topic')
145
+ if not (symbol in self.orderbooks):
146
+ self.orderbooks[symbol] = self.order_book()
147
+ orderbook = self.orderbooks[symbol]
148
+ timestamp = self.safe_integer(message, 'ts')
149
+ snapshot = self.parse_order_book(data, symbol, timestamp, 'bids', 'asks')
150
+ orderbook.reset(snapshot)
151
+ client.resolve(orderbook, topic)
152
+
153
+ async def watch_ticker(self, symbol: str, params={}) -> Ticker:
154
+ """
155
+
156
+ https://orderly.network/docs/build-on-evm/evm-api/websocket-api/public/24-hour-ticker
157
+
158
+ watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
159
+ :param str symbol: unified symbol of the market to fetch the ticker for
160
+ :param dict [params]: extra parameters specific to the exchange API endpoint
161
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
162
+ """
163
+ await self.load_markets()
164
+ name = 'ticker'
165
+ market = self.market(symbol)
166
+ symbol = market['symbol']
167
+ topic = market['id'] + '@' + name
168
+ request: dict = {
169
+ 'event': 'subscribe',
170
+ 'topic': topic,
171
+ }
172
+ message = self.extend(request, params)
173
+ return await self.watch_public(topic, message)
174
+
175
+ def parse_ws_ticker(self, ticker, market=None):
176
+ #
177
+ # {
178
+ # "symbol": "PERP_BTC_USDC",
179
+ # "open": 19441.5,
180
+ # "close": 20147.07,
181
+ # "high": 20761.87,
182
+ # "low": 19320.54,
183
+ # "volume": 2481.103,
184
+ # "amount": 50037935.0286,
185
+ # "count": 3689
186
+ # }
187
+ #
188
+ return self.safe_ticker({
189
+ 'symbol': self.safe_symbol(None, market),
190
+ 'timestamp': None,
191
+ 'datetime': None,
192
+ 'high': self.safe_string(ticker, 'high'),
193
+ 'low': self.safe_string(ticker, 'low'),
194
+ 'bid': None,
195
+ 'bidVolume': None,
196
+ 'ask': None,
197
+ 'askVolume': None,
198
+ 'vwap': None,
199
+ 'open': self.safe_string(ticker, 'open'),
200
+ 'close': self.safe_string(ticker, 'close'),
201
+ 'last': None,
202
+ 'previousClose': None,
203
+ 'change': None,
204
+ 'percentage': None,
205
+ 'average': None,
206
+ 'baseVolume': self.safe_string(ticker, 'volume'),
207
+ 'quoteVolume': self.safe_string(ticker, 'amount'),
208
+ 'info': ticker,
209
+ }, market)
210
+
211
+ def handle_ticker(self, client: Client, message):
212
+ #
213
+ # {
214
+ # "topic": "PERP_BTC_USDC@ticker",
215
+ # "ts": 1657120017000,
216
+ # "data": {
217
+ # "symbol": "PERP_BTC_USDC",
218
+ # "open": 19441.5,
219
+ # "close": 20147.07,
220
+ # "high": 20761.87,
221
+ # "low": 19320.54,
222
+ # "volume": 2481.103,
223
+ # "amount": 50037935.0286,
224
+ # "count": 3689
225
+ # }
226
+ # }
227
+ #
228
+ data = self.safe_dict(message, 'data', {})
229
+ topic = self.safe_string(message, 'topic')
230
+ marketId = self.safe_string(data, 'symbol')
231
+ market = self.safe_market(marketId)
232
+ timestamp = self.safe_integer(message, 'ts')
233
+ data['date'] = timestamp
234
+ ticker = self.parse_ws_ticker(data, market)
235
+ ticker['symbol'] = market['symbol']
236
+ self.tickers[market['symbol']] = ticker
237
+ client.resolve(ticker, topic)
238
+ return message
239
+
240
+ async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
241
+ """
242
+
243
+ https://orderly.network/docs/build-on-evm/evm-api/websocket-api/public/24-hour-tickers
244
+
245
+ watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
246
+ :param str[] symbols: unified symbol of the market to fetch the ticker for
247
+ :param dict [params]: extra parameters specific to the exchange API endpoint
248
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
249
+ """
250
+ await self.load_markets()
251
+ symbols = self.market_symbols(symbols)
252
+ name = 'tickers'
253
+ topic = name
254
+ request: dict = {
255
+ 'event': 'subscribe',
256
+ 'topic': topic,
257
+ }
258
+ message = self.extend(request, params)
259
+ tickers = await self.watch_public(topic, message)
260
+ return self.filter_by_array(tickers, 'symbol', symbols)
261
+
262
+ def handle_tickers(self, client: Client, message):
263
+ #
264
+ # {
265
+ # "topic":"tickers",
266
+ # "ts":1618820615000,
267
+ # "data":[
268
+ # {
269
+ # "symbol":"PERP_NEAR_USDC",
270
+ # "open":16.297,
271
+ # "close":17.183,
272
+ # "high":24.707,
273
+ # "low":11.997,
274
+ # "volume":0,
275
+ # "amount":0,
276
+ # "count":0
277
+ # },
278
+ # ...
279
+ # ]
280
+ # }
281
+ #
282
+ topic = self.safe_string(message, 'topic')
283
+ data = self.safe_list(message, 'data', [])
284
+ timestamp = self.safe_integer(message, 'ts')
285
+ result = []
286
+ for i in range(0, len(data)):
287
+ marketId = self.safe_string(data[i], 'symbol')
288
+ market = self.safe_market(marketId)
289
+ ticker = self.parse_ws_ticker(self.extend(data[i], {'date': timestamp}), market)
290
+ self.tickers[market['symbol']] = ticker
291
+ result.append(ticker)
292
+ client.resolve(result, topic)
293
+
294
+ async def watch_bids_asks(self, symbols: Strings = None, params={}) -> Tickers:
295
+ """
296
+
297
+ https://orderly.network/docs/build-on-evm/evm-api/websocket-api/public/bbos
298
+
299
+ watches best bid & ask for symbols
300
+ :param str[] symbols: unified symbol of the market to fetch the ticker for
301
+ :param dict [params]: extra parameters specific to the exchange API endpoint
302
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
303
+ """
304
+ await self.load_markets()
305
+ symbols = self.market_symbols(symbols)
306
+ name = 'bbos'
307
+ topic = name
308
+ request: dict = {
309
+ 'event': 'subscribe',
310
+ 'topic': topic,
311
+ }
312
+ message = self.extend(request, params)
313
+ tickers = await self.watch_public(topic, message)
314
+ return self.filter_by_array(tickers, 'symbol', symbols)
315
+
316
+ def handle_bid_ask(self, client: Client, message):
317
+ #
318
+ # {
319
+ # "topic": "bbos",
320
+ # "ts": 1726212495000,
321
+ # "data": [
322
+ # {
323
+ # "symbol": "PERP_BTC_USDC",
324
+ # "ask": 0.16570,
325
+ # "askSize": 4224,
326
+ # "bid": 0.16553,
327
+ # "bidSize": 6645
328
+ # }
329
+ # ]
330
+ # }
331
+ #
332
+ topic = self.safe_string(message, 'topic')
333
+ data = self.safe_list(message, 'data', [])
334
+ timestamp = self.safe_integer(message, 'ts')
335
+ result = []
336
+ for i in range(0, len(data)):
337
+ ticker = self.parse_ws_bid_ask(self.extend(data[i], {'ts': timestamp}))
338
+ self.tickers[ticker['symbol']] = ticker
339
+ result.append(ticker)
340
+ client.resolve(result, topic)
341
+
342
+ def parse_ws_bid_ask(self, ticker, market=None):
343
+ marketId = self.safe_string(ticker, 'symbol')
344
+ market = self.safe_market(marketId, market)
345
+ symbol = self.safe_string(market, 'symbol')
346
+ timestamp = self.safe_integer(ticker, 'ts')
347
+ return self.safe_ticker({
348
+ 'symbol': symbol,
349
+ 'timestamp': timestamp,
350
+ 'datetime': self.iso8601(timestamp),
351
+ 'ask': self.safe_string(ticker, 'ask'),
352
+ 'askVolume': self.safe_string(ticker, 'askSize'),
353
+ 'bid': self.safe_string(ticker, 'bid'),
354
+ 'bidVolume': self.safe_string(ticker, 'bidSize'),
355
+ 'info': ticker,
356
+ }, market)
357
+
358
+ async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
359
+ """
360
+ watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
361
+
362
+ https://orderly.network/docs/build-on-evm/evm-api/websocket-api/public/k-line
363
+
364
+ :param str symbol: unified symbol of the market to fetch OHLCV data for
365
+ :param str timeframe: the length of time each candle represents
366
+ :param int [since]: timestamp in ms of the earliest candle to fetch
367
+ :param int [limit]: the maximum amount of candles to fetch
368
+ :param dict [params]: extra parameters specific to the exchange API endpoint
369
+ :returns int[][]: A list of candles ordered, open, high, low, close, volume
370
+ """
371
+ await self.load_markets()
372
+ if (timeframe != '1m') and (timeframe != '5m') and (timeframe != '15m') and (timeframe != '30m') and (timeframe != '1h') and (timeframe != '1d') and (timeframe != '1w') and (timeframe != '1M'):
373
+ raise NotSupported(self.id + ' watchOHLCV timeframe argument must be 1m, 5m, 15m, 30m, 1h, 1d, 1w, 1M')
374
+ market = self.market(symbol)
375
+ interval = self.safe_string(self.timeframes, timeframe, timeframe)
376
+ name = 'kline'
377
+ topic = market['id'] + '@' + name + '_' + interval
378
+ request: dict = {
379
+ 'event': 'subscribe',
380
+ 'topic': topic,
381
+ }
382
+ message = self.extend(request, params)
383
+ ohlcv = await self.watch_public(topic, message)
384
+ if self.newUpdates:
385
+ limit = ohlcv.getLimit(market['symbol'], limit)
386
+ return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
387
+
388
+ def handle_ohlcv(self, client: Client, message):
389
+ #
390
+ # {
391
+ # "topic":"PERP_BTC_USDC@kline_1m",
392
+ # "ts":1618822432146,
393
+ # "data":{
394
+ # "symbol":"PERP_BTC_USDC",
395
+ # "type":"1m",
396
+ # "open":56948.97,
397
+ # "close":56891.76,
398
+ # "high":56948.97,
399
+ # "low":56889.06,
400
+ # "volume":44.00947568,
401
+ # "amount":2504584.9,
402
+ # "startTime":1618822380000,
403
+ # "endTime":1618822440000
404
+ # }
405
+ # }
406
+ #
407
+ data = self.safe_dict(message, 'data', {})
408
+ topic = self.safe_string(message, 'topic')
409
+ marketId = self.safe_string(data, 'symbol')
410
+ market = self.safe_market(marketId)
411
+ symbol = market['symbol']
412
+ interval = self.safe_string(data, 'type')
413
+ timeframe = self.find_timeframe(interval)
414
+ parsed = [
415
+ self.safe_integer(data, 'startTime'),
416
+ self.safe_number(data, 'open'),
417
+ self.safe_number(data, 'high'),
418
+ self.safe_number(data, 'low'),
419
+ self.safe_number(data, 'close'),
420
+ self.safe_number(data, 'volume'),
421
+ ]
422
+ self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {})
423
+ stored = self.safe_value(self.ohlcvs[symbol], timeframe)
424
+ if stored is None:
425
+ limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
426
+ stored = ArrayCacheByTimestamp(limit)
427
+ self.ohlcvs[symbol][timeframe] = stored
428
+ ohlcvCache = self.ohlcvs[symbol][timeframe]
429
+ ohlcvCache.append(parsed)
430
+ client.resolve(ohlcvCache, topic)
431
+
432
+ async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
433
+ """
434
+ watches information on multiple trades made in a market
435
+
436
+ https://orderly.network/docs/build-on-evm/evm-api/websocket-api/public/trade
437
+
438
+ :param str symbol: unified market symbol of the market trades were made in
439
+ :param int [since]: the earliest time in ms to fetch trades for
440
+ :param int [limit]: the maximum number of trade structures to retrieve
441
+ :param dict [params]: extra parameters specific to the exchange API endpoint
442
+ :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
443
+ """
444
+ await self.load_markets()
445
+ market = self.market(symbol)
446
+ symbol = market['symbol']
447
+ topic = market['id'] + '@trade'
448
+ request: dict = {
449
+ 'event': 'subscribe',
450
+ 'topic': topic,
451
+ }
452
+ message = self.extend(request, params)
453
+ trades = await self.watch_public(topic, message)
454
+ if self.newUpdates:
455
+ limit = trades.getLimit(market['symbol'], limit)
456
+ return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)
457
+
458
+ def handle_trade(self, client: Client, message):
459
+ #
460
+ # {
461
+ # "topic":"PERP_ADA_USDC@trade",
462
+ # "ts":1618820361552,
463
+ # "data":{
464
+ # "symbol":"PERP_ADA_USDC",
465
+ # "price":1.27988,
466
+ # "size":300,
467
+ # "side":"BUY",
468
+ # }
469
+ # }
470
+ #
471
+ topic = self.safe_string(message, 'topic')
472
+ timestamp = self.safe_integer(message, 'ts')
473
+ data = self.safe_dict(message, 'data', {})
474
+ marketId = self.safe_string(data, 'symbol')
475
+ market = self.safe_market(marketId)
476
+ symbol = market['symbol']
477
+ trade = self.parse_ws_trade(self.extend(data, {'timestamp': timestamp}), market)
478
+ if not (symbol in self.trades):
479
+ limit = self.safe_integer(self.options, 'tradesLimit', 1000)
480
+ stored = ArrayCache(limit)
481
+ self.trades[symbol] = stored
482
+ trades = self.trades[symbol]
483
+ trades.append(trade)
484
+ self.trades[symbol] = trades
485
+ client.resolve(trades, topic)
486
+
487
+ def parse_ws_trade(self, trade, market=None):
488
+ #
489
+ # {
490
+ # "symbol":"PERP_ADA_USDC",
491
+ # "timestamp":1618820361552,
492
+ # "price":1.27988,
493
+ # "size":300,
494
+ # "side":"BUY",
495
+ # }
496
+ # private stream
497
+ # {
498
+ # symbol: 'PERP_XRP_USDC',
499
+ # clientOrderId: '',
500
+ # orderId: 1167632251,
501
+ # type: 'MARKET',
502
+ # side: 'BUY',
503
+ # quantity: 20,
504
+ # price: 0,
505
+ # tradeId: '1715179456664012',
506
+ # executedPrice: 0.5276,
507
+ # executedQuantity: 20,
508
+ # fee: 0.006332,
509
+ # feeAsset: 'USDC',
510
+ # totalExecutedQuantity: 20,
511
+ # avgPrice: 0.5276,
512
+ # averageExecutedPrice: 0.5276,
513
+ # status: 'FILLED',
514
+ # reason: '',
515
+ # totalFee: 0.006332,
516
+ # visible: 0,
517
+ # visibleQuantity: 0,
518
+ # timestamp: 1715179456660,
519
+ # orderTag: 'CCXT',
520
+ # createdTime: 1715179456656,
521
+ # maker: False
522
+ # }
523
+ #
524
+ marketId = self.safe_string(trade, 'symbol')
525
+ market = self.safe_market(marketId, market)
526
+ symbol = market['symbol']
527
+ price = self.safe_string_2(trade, 'executedPrice', 'price')
528
+ amount = self.safe_string_2(trade, 'executedQuantity', 'size')
529
+ cost = Precise.string_mul(price, amount)
530
+ side = self.safe_string_lower(trade, 'side')
531
+ timestamp = self.safe_integer(trade, 'timestamp')
532
+ takerOrMaker = None
533
+ maker = self.safe_bool(trade, 'maker')
534
+ if maker is not None:
535
+ takerOrMaker = 'maker' if maker else 'taker'
536
+ fee = None
537
+ feeValue = self.safe_string(trade, 'fee')
538
+ if feeValue is not None:
539
+ fee = {
540
+ 'cost': feeValue,
541
+ 'currency': self.safe_currency_code(self.safe_string(trade, 'feeAsset')),
542
+ }
543
+ return self.safe_trade({
544
+ 'id': self.safe_string(trade, 'tradeId'),
545
+ 'timestamp': timestamp,
546
+ 'datetime': self.iso8601(timestamp),
547
+ 'symbol': symbol,
548
+ 'side': side,
549
+ 'price': price,
550
+ 'amount': amount,
551
+ 'cost': cost,
552
+ 'order': self.safe_string(trade, 'orderId'),
553
+ 'takerOrMaker': takerOrMaker,
554
+ 'type': self.safe_string_lower(trade, 'type'),
555
+ 'fee': fee,
556
+ 'info': trade,
557
+ }, market)
558
+
559
+ def handle_auth(self, client: Client, message):
560
+ #
561
+ # {
562
+ # "event": "auth",
563
+ # "success": True,
564
+ # "ts": 1657463158812
565
+ # }
566
+ #
567
+ messageHash = 'authenticated'
568
+ success = self.safe_value(message, 'success')
569
+ if success:
570
+ # client.resolve(message, messageHash)
571
+ future = self.safe_value(client.futures, 'authenticated')
572
+ future.resolve(True)
573
+ else:
574
+ error = AuthenticationError(self.json(message))
575
+ client.reject(error, messageHash)
576
+ # allows further authentication attempts
577
+ if messageHash in client.subscriptions:
578
+ del client.subscriptions['authenticated']
579
+
580
+ async def authenticate(self, params={}):
581
+ self.check_required_credentials()
582
+ url = self.urls['api']['ws']['private'] + '/' + self.accountId
583
+ client = self.client(url)
584
+ messageHash = 'authenticated'
585
+ event = 'auth'
586
+ future = client.future(messageHash)
587
+ authenticated = self.safe_value(client.subscriptions, messageHash)
588
+ if authenticated is None:
589
+ ts = str(self.nonce())
590
+ auth = ts
591
+ secret = self.secret
592
+ if secret.find('ed25519:') >= 0:
593
+ parts = secret.split('ed25519:')
594
+ secret = parts[1]
595
+ signature = self.eddsa(self.encode(auth), self.base58_to_binary(secret), 'ed25519')
596
+ request: dict = {
597
+ 'event': event,
598
+ 'params': {
599
+ 'orderly_key': self.apiKey,
600
+ 'sign': signature,
601
+ 'timestamp': ts,
602
+ },
603
+ }
604
+ message = self.extend(request, params)
605
+ self.watch(url, messageHash, message, messageHash)
606
+ return await future
607
+
608
+ async def watch_private(self, messageHash, message, params={}):
609
+ await self.authenticate(params)
610
+ url = self.urls['api']['ws']['private'] + '/' + self.accountId
611
+ requestId = self.request_id(url)
612
+ subscribe: dict = {
613
+ 'id': requestId,
614
+ }
615
+ request = self.extend(subscribe, message)
616
+ return await self.watch(url, messageHash, request, messageHash, subscribe)
617
+
618
+ async def watch_private_multiple(self, messageHashes, message, params={}):
619
+ await self.authenticate(params)
620
+ url = self.urls['api']['ws']['private'] + '/' + self.accountId
621
+ requestId = self.request_id(url)
622
+ subscribe: dict = {
623
+ 'id': requestId,
624
+ }
625
+ request = self.extend(subscribe, message)
626
+ return await self.watch_multiple(url, messageHashes, request, messageHashes, subscribe)
627
+
628
+ async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
629
+ """
630
+ watches information on multiple orders made by the user
631
+
632
+ https://orderly.network/docs/build-on-evm/evm-api/websocket-api/private/execution-report
633
+ https://orderly.network/docs/build-on-evm/evm-api/websocket-api/private/algo-execution-report
634
+
635
+ :param str symbol: unified market symbol of the market orders were made in
636
+ :param int [since]: the earliest time in ms to fetch orders for
637
+ :param int [limit]: the maximum number of order structures to retrieve
638
+ :param dict [params]: extra parameters specific to the exchange API endpoint
639
+ :param bool [params.trigger]: True if trigger order
640
+ :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
641
+ """
642
+ await self.load_markets()
643
+ trigger = self.safe_bool_2(params, 'stop', 'trigger', False)
644
+ topic = 'algoexecutionreport' if (trigger) else 'executionreport'
645
+ params = self.omit(params, ['stop', 'trigger'])
646
+ messageHash = topic
647
+ if symbol is not None:
648
+ market = self.market(symbol)
649
+ symbol = market['symbol']
650
+ messageHash += ':' + symbol
651
+ request: dict = {
652
+ 'event': 'subscribe',
653
+ 'topic': topic,
654
+ }
655
+ message = self.extend(request, params)
656
+ orders = await self.watch_private(messageHash, message)
657
+ if self.newUpdates:
658
+ limit = orders.getLimit(symbol, limit)
659
+ return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
660
+
661
+ async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
662
+ """
663
+ watches information on multiple trades made by the user
664
+
665
+ https://orderly.network/docs/build-on-evm/evm-api/websocket-api/private/execution-report
666
+ https://orderly.network/docs/build-on-evm/evm-api/websocket-api/private/algo-execution-report
667
+
668
+ :param str symbol: unified market symbol of the market orders were made in
669
+ :param int [since]: the earliest time in ms to fetch orders for
670
+ :param int [limit]: the maximum number of order structures to retrieve
671
+ :param dict [params]: extra parameters specific to the exchange API endpoint
672
+ :param bool [params.trigger]: True if trigger order
673
+ :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
674
+ """
675
+ await self.load_markets()
676
+ trigger = self.safe_bool_2(params, 'stop', 'trigger', False)
677
+ topic = 'algoexecutionreport' if (trigger) else 'executionreport'
678
+ params = self.omit(params, 'stop')
679
+ messageHash = 'myTrades'
680
+ if symbol is not None:
681
+ market = self.market(symbol)
682
+ symbol = market['symbol']
683
+ messageHash += ':' + symbol
684
+ request: dict = {
685
+ 'event': 'subscribe',
686
+ 'topic': topic,
687
+ }
688
+ message = self.extend(request, params)
689
+ orders = await self.watch_private(messageHash, message)
690
+ if self.newUpdates:
691
+ limit = orders.getLimit(symbol, limit)
692
+ return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
693
+
694
+ def parse_ws_order(self, order, market=None):
695
+ #
696
+ # {
697
+ # "symbol": "PERP_BTC_USDT",
698
+ # "clientOrderId": 0,
699
+ # "orderId": 52952826,
700
+ # "type": "LIMIT",
701
+ # "side": "SELL",
702
+ # "quantity": 0.01,
703
+ # "price": 22000,
704
+ # "tradeId": 0,
705
+ # "executedPrice": 0,
706
+ # "executedQuantity": 0,
707
+ # "fee": 0,
708
+ # "feeAsset": "USDT",
709
+ # "totalExecutedQuantity": 0,
710
+ # "status": "NEW",
711
+ # "reason": '',
712
+ # "orderTag": "default",
713
+ # "totalFee": 0,
714
+ # "visible": 0.01,
715
+ # "timestamp": 1657515556799,
716
+ # "reduceOnly": False,
717
+ # "maker": False
718
+ # }
719
+ # algo order
720
+ # {
721
+ # "symbol":"PERP_MATIC_USDC",
722
+ # "rootAlgoOrderId":123,
723
+ # "parentAlgoOrderId":123,
724
+ # "algoOrderId":123,
725
+ # "orderTag":"some tags",
726
+ # "algoType": "STOP",
727
+ # "clientOrderId":"client_id",
728
+ # "type":"LIMIT",
729
+ # "side":"BUY",
730
+ # "quantity":7029.0,
731
+ # "price":0.7699,
732
+ # "tradeId":0,
733
+ # "triggerTradePrice":0,
734
+ # "triggerTime":1234567,
735
+ # "triggered": False,
736
+ # "activated": False,
737
+ # "executedPrice":0.0,
738
+ # "executedQuantity":0.0,
739
+ # "fee":0.0,
740
+ # "feeAsset":"USDC",
741
+ # "totalExecutedQuantity":0.0,
742
+ # "averageExecutedQuantity":0.0,
743
+ # "avgPrice":0,
744
+ # "triggerPrice":0.0,
745
+ # "triggerPriceType":"STOP",
746
+ # "isActivated": False,
747
+ # "status":"NEW",
748
+ # "rootAlgoStatus": "FILLED",
749
+ # "algoStatus": "FILLED",
750
+ # "reason":"",
751
+ # "totalFee":0.0,
752
+ # "visible": 7029.0,
753
+ # "visibleQuantity":7029.0,
754
+ # "timestamp":1704679472448,
755
+ # "maker":false,
756
+ # "isMaker":false,
757
+ # "createdTime":1704679472448
758
+ # }
759
+ #
760
+ orderId = self.safe_string(order, 'orderId')
761
+ marketId = self.safe_string(order, 'symbol')
762
+ market = self.market(marketId)
763
+ symbol = market['symbol']
764
+ timestamp = self.safe_integer(order, 'timestamp')
765
+ fee = {
766
+ 'cost': self.safe_string(order, 'totalFee'),
767
+ 'currency': self.safe_string(order, 'feeAsset'),
768
+ }
769
+ priceString = self.safe_string(order, 'price')
770
+ price = self.safe_number(order, 'price')
771
+ avgPrice = self.safe_number(order, 'avgPrice')
772
+ if Precise.string_eq(priceString, '0') and (avgPrice is not None):
773
+ price = avgPrice
774
+ amount = self.safe_string(order, 'quantity')
775
+ side = self.safe_string_lower(order, 'side')
776
+ type = self.safe_string_lower(order, 'type')
777
+ filled = self.safe_number(order, 'totalExecutedQuantity')
778
+ totalExecQuantity = self.safe_string(order, 'totalExecutedQuantity')
779
+ remaining = amount
780
+ if Precise.string_ge(amount, totalExecQuantity):
781
+ remaining = Precise.string_sub(remaining, totalExecQuantity)
782
+ rawStatus = self.safe_string(order, 'status')
783
+ status = self.parse_order_status(rawStatus)
784
+ trades = None
785
+ clientOrderId = self.safe_string(order, 'clientOrderId')
786
+ triggerPrice = self.safe_number(order, 'triggerPrice')
787
+ return self.safe_order({
788
+ 'info': order,
789
+ 'symbol': symbol,
790
+ 'id': orderId,
791
+ 'clientOrderId': clientOrderId,
792
+ 'timestamp': timestamp,
793
+ 'datetime': self.iso8601(timestamp),
794
+ 'lastTradeTimestamp': timestamp,
795
+ 'type': type,
796
+ 'timeInForce': None,
797
+ 'postOnly': None,
798
+ 'side': side,
799
+ 'price': price,
800
+ 'stopPrice': triggerPrice,
801
+ 'triggerPrice': triggerPrice,
802
+ 'amount': amount,
803
+ 'cost': None,
804
+ 'average': None,
805
+ 'filled': filled,
806
+ 'remaining': remaining,
807
+ 'status': status,
808
+ 'fee': fee,
809
+ 'trades': trades,
810
+ })
811
+
812
+ def handle_order_update(self, client: Client, message):
813
+ #
814
+ # {
815
+ # "topic": "executionreport",
816
+ # "ts": 1657515556799,
817
+ # "data": {
818
+ # "symbol": "PERP_BTC_USDT",
819
+ # "clientOrderId": 0,
820
+ # "orderId": 52952826,
821
+ # "type": "LIMIT",
822
+ # "side": "SELL",
823
+ # "quantity": 0.01,
824
+ # "price": 22000,
825
+ # "tradeId": 0,
826
+ # "executedPrice": 0,
827
+ # "executedQuantity": 0,
828
+ # "fee": 0,
829
+ # "feeAsset": "USDT",
830
+ # "totalExecutedQuantity": 0,
831
+ # "status": "NEW",
832
+ # "reason": '',
833
+ # "orderTag": "default",
834
+ # "totalFee": 0,
835
+ # "visible": 0.01,
836
+ # "timestamp": 1657515556799,
837
+ # "maker": False
838
+ # }
839
+ # }
840
+ #
841
+ topic = self.safe_string(message, 'topic')
842
+ data = self.safe_value(message, 'data')
843
+ if isinstance(data, list):
844
+ # algoexecutionreport
845
+ for i in range(0, len(data)):
846
+ order = data[i]
847
+ tradeId = self.omit_zero(self.safe_string(data, 'tradeId'))
848
+ if tradeId is not None:
849
+ self.handle_my_trade(client, order)
850
+ self.handle_order(client, order, topic)
851
+ else:
852
+ # executionreport
853
+ tradeId = self.omit_zero(self.safe_string(data, 'tradeId'))
854
+ if tradeId is not None:
855
+ self.handle_my_trade(client, data)
856
+ self.handle_order(client, data, topic)
857
+
858
+ def handle_order(self, client: Client, message, topic):
859
+ parsed = self.parse_ws_order(message)
860
+ symbol = self.safe_string(parsed, 'symbol')
861
+ orderId = self.safe_string(parsed, 'id')
862
+ if symbol is not None:
863
+ if self.orders is None:
864
+ limit = self.safe_integer(self.options, 'ordersLimit', 1000)
865
+ self.orders = ArrayCacheBySymbolById(limit)
866
+ cachedOrders = self.orders
867
+ orders = self.safe_dict(cachedOrders.hashmap, symbol, {})
868
+ order = self.safe_dict(orders, orderId)
869
+ if order is not None:
870
+ fee = self.safe_value(order, 'fee')
871
+ if fee is not None:
872
+ parsed['fee'] = fee
873
+ fees = self.safe_list(order, 'fees')
874
+ if fees is not None:
875
+ parsed['fees'] = fees
876
+ parsed['trades'] = self.safe_list(order, 'trades')
877
+ parsed['timestamp'] = self.safe_integer(order, 'timestamp')
878
+ parsed['datetime'] = self.safe_string(order, 'datetime')
879
+ cachedOrders.append(parsed)
880
+ client.resolve(self.orders, topic)
881
+ messageHashSymbol = topic + ':' + symbol
882
+ client.resolve(self.orders, messageHashSymbol)
883
+
884
+ def handle_my_trade(self, client: Client, message):
885
+ #
886
+ # {
887
+ # symbol: 'PERP_XRP_USDC',
888
+ # clientOrderId: '',
889
+ # orderId: 1167632251,
890
+ # type: 'MARKET',
891
+ # side: 'BUY',
892
+ # quantity: 20,
893
+ # price: 0,
894
+ # tradeId: '1715179456664012',
895
+ # executedPrice: 0.5276,
896
+ # executedQuantity: 20,
897
+ # fee: 0.006332,
898
+ # feeAsset: 'USDC',
899
+ # totalExecutedQuantity: 20,
900
+ # avgPrice: 0.5276,
901
+ # averageExecutedPrice: 0.5276,
902
+ # status: 'FILLED',
903
+ # reason: '',
904
+ # totalFee: 0.006332,
905
+ # visible: 0,
906
+ # visibleQuantity: 0,
907
+ # timestamp: 1715179456660,
908
+ # orderTag: 'CCXT',
909
+ # createdTime: 1715179456656,
910
+ # maker: False
911
+ # }
912
+ #
913
+ messageHash = 'myTrades'
914
+ marketId = self.safe_string(message, 'symbol')
915
+ market = self.safe_market(marketId)
916
+ symbol = market['symbol']
917
+ trade = self.parse_ws_trade(message, market)
918
+ trades = self.myTrades
919
+ if trades is None:
920
+ limit = self.safe_integer(self.options, 'tradesLimit', 1000)
921
+ trades = ArrayCacheBySymbolById(limit)
922
+ self.myTrades = trades
923
+ trades.append(trade)
924
+ client.resolve(trades, messageHash)
925
+ symbolSpecificMessageHash = messageHash + ':' + symbol
926
+ client.resolve(trades, symbolSpecificMessageHash)
927
+
928
+ async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
929
+ """
930
+
931
+ https://orderly.network/docs/build-on-evm/evm-api/websocket-api/private/position-push
932
+
933
+ watch all open positions
934
+ :param str[] [symbols]: list of unified market symbols
935
+ @param since timestamp in ms of the earliest position to fetch
936
+ @param limit the maximum number of positions to fetch
937
+ :param dict params: extra parameters specific to the exchange API endpoint
938
+ :returns dict[]: a list of `position structure <https://docs.ccxt.com/en/latest/manual.html#position-structure>`
939
+ """
940
+ await self.load_markets()
941
+ messageHashes = []
942
+ symbols = self.market_symbols(symbols)
943
+ if not self.is_empty(symbols):
944
+ for i in range(0, len(symbols)):
945
+ symbol = symbols[i]
946
+ messageHashes.append('positions::' + symbol)
947
+ else:
948
+ messageHashes.append('positions')
949
+ url = self.urls['api']['ws']['private'] + '/' + self.accountId
950
+ client = self.client(url)
951
+ self.set_positions_cache(client, symbols)
952
+ fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', True)
953
+ awaitPositionsSnapshot = self.handle_option('watchPositions', 'awaitPositionsSnapshot', True)
954
+ if fetchPositionsSnapshot and awaitPositionsSnapshot and self.positions is None:
955
+ snapshot = await client.future('fetchPositionsSnapshot')
956
+ return self.filter_by_symbols_since_limit(snapshot, symbols, since, limit, True)
957
+ request: dict = {
958
+ 'event': 'subscribe',
959
+ 'topic': 'position',
960
+ }
961
+ newPositions = await self.watch_private_multiple(messageHashes, request, params)
962
+ if self.newUpdates:
963
+ return newPositions
964
+ return self.filter_by_symbols_since_limit(self.positions, symbols, since, limit, True)
965
+
966
+ def set_positions_cache(self, client: Client, type, symbols: Strings = None):
967
+ fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', False)
968
+ if fetchPositionsSnapshot:
969
+ messageHash = 'fetchPositionsSnapshot'
970
+ if not (messageHash in client.futures):
971
+ client.future(messageHash)
972
+ self.spawn(self.load_positions_snapshot, client, messageHash)
973
+ else:
974
+ self.positions = ArrayCacheBySymbolBySide()
975
+
976
+ async def load_positions_snapshot(self, client, messageHash):
977
+ positions = await self.fetch_positions()
978
+ self.positions = ArrayCacheBySymbolBySide()
979
+ cache = self.positions
980
+ for i in range(0, len(positions)):
981
+ position = positions[i]
982
+ contracts = self.safe_string(position, 'contracts', '0')
983
+ if Precise.string_gt(contracts, '0'):
984
+ cache.append(position)
985
+ # don't remove the future from the .futures cache
986
+ future = client.futures[messageHash]
987
+ future.resolve(cache)
988
+ client.resolve(cache, 'positions')
989
+
990
+ def handle_positions(self, client, message):
991
+ #
992
+ # {
993
+ # "topic":"position",
994
+ # "ts":1705292345255,
995
+ # "data":{
996
+ # "positions":[
997
+ # {
998
+ # "symbol":"PERP_ETH_USDC",
999
+ # "positionQty":3.1408,
1000
+ # "costPosition":5706.51952,
1001
+ # "lastSumUnitaryFunding":0.804,
1002
+ # "sumUnitaryFundingVersion":0,
1003
+ # "pendingLongQty":0.0,
1004
+ # "pendingShortQty":-1.0,
1005
+ # "settlePrice":1816.9,
1006
+ # "averageOpenPrice":1804.51490427,
1007
+ # "unsettledPnl":-2.79856,
1008
+ # "pnl24H":-338.90179488,
1009
+ # "fee24H":4.242423,
1010
+ # "markPrice":1816.2,
1011
+ # "estLiqPrice":0.0,
1012
+ # "version":179967,
1013
+ # "imrwithOrders":0.1,
1014
+ # "mmrwithOrders":0.05,
1015
+ # "mmr":0.05,
1016
+ # "imr":0.1,
1017
+ # "timestamp":1685154032762
1018
+ # }
1019
+ # ]
1020
+ # }
1021
+ # }
1022
+ #
1023
+ data = self.safe_dict(message, 'data', {})
1024
+ rawPositions = self.safe_list(data, 'positions', [])
1025
+ if self.positions is None:
1026
+ self.positions = ArrayCacheBySymbolBySide()
1027
+ cache = self.positions
1028
+ newPositions = []
1029
+ for i in range(0, len(rawPositions)):
1030
+ rawPosition = rawPositions[i]
1031
+ marketId = self.safe_string(rawPosition, 'symbol')
1032
+ market = self.safe_market(marketId)
1033
+ position = self.parse_ws_position(rawPosition, market)
1034
+ newPositions.append(position)
1035
+ cache.append(position)
1036
+ messageHash = 'positions::' + market['symbol']
1037
+ client.resolve(position, messageHash)
1038
+ client.resolve(newPositions, 'positions')
1039
+
1040
+ def parse_ws_position(self, position, market=None):
1041
+ #
1042
+ # {
1043
+ # "symbol":"PERP_ETH_USDC",
1044
+ # "positionQty":3.1408,
1045
+ # "costPosition":5706.51952,
1046
+ # "lastSumUnitaryFunding":0.804,
1047
+ # "sumUnitaryFundingVersion":0,
1048
+ # "pendingLongQty":0.0,
1049
+ # "pendingShortQty":-1.0,
1050
+ # "settlePrice":1816.9,
1051
+ # "averageOpenPrice":1804.51490427,
1052
+ # "unsettledPnl":-2.79856,
1053
+ # "pnl24H":-338.90179488,
1054
+ # "fee24H":4.242423,
1055
+ # "markPrice":1816.2,
1056
+ # "estLiqPrice":0.0,
1057
+ # "version":179967,
1058
+ # "imrwithOrders":0.1,
1059
+ # "mmrwithOrders":0.05,
1060
+ # "mmr":0.05,
1061
+ # "imr":0.1,
1062
+ # "timestamp":1685154032762
1063
+ # }
1064
+ #
1065
+ contract = self.safe_string(position, 'symbol')
1066
+ market = self.safe_market(contract, market)
1067
+ size = self.safe_string(position, 'positionQty')
1068
+ side: Str = None
1069
+ if Precise.string_gt(size, '0'):
1070
+ side = 'long'
1071
+ else:
1072
+ side = 'short'
1073
+ contractSize = self.safe_string(market, 'contractSize')
1074
+ markPrice = self.safe_string(position, 'markPrice')
1075
+ timestamp = self.safe_integer(position, 'timestamp')
1076
+ entryPrice = self.safe_string(position, 'averageOpenPrice')
1077
+ unrealisedPnl = self.safe_string(position, 'unsettledPnl')
1078
+ size = Precise.string_abs(size)
1079
+ notional = Precise.string_mul(size, markPrice)
1080
+ return self.safe_position({
1081
+ 'info': position,
1082
+ 'id': None,
1083
+ 'symbol': self.safe_string(market, 'symbol'),
1084
+ 'timestamp': timestamp,
1085
+ 'datetime': self.iso8601(timestamp),
1086
+ 'lastUpdateTimestamp': None,
1087
+ 'initialMargin': None,
1088
+ 'initialMarginPercentage': None,
1089
+ 'maintenanceMargin': None,
1090
+ 'maintenanceMarginPercentage': None,
1091
+ 'entryPrice': self.parse_number(entryPrice),
1092
+ 'notional': self.parse_number(notional),
1093
+ 'leverage': None,
1094
+ 'unrealizedPnl': self.parse_number(unrealisedPnl),
1095
+ 'contracts': self.parse_number(size),
1096
+ 'contractSize': self.parse_number(contractSize),
1097
+ 'marginRatio': None,
1098
+ 'liquidationPrice': self.safe_number(position, 'estLiqPrice'),
1099
+ 'markPrice': self.parse_number(markPrice),
1100
+ 'lastPrice': None,
1101
+ 'collateral': None,
1102
+ 'marginMode': 'cross',
1103
+ 'marginType': None,
1104
+ 'side': side,
1105
+ 'percentage': None,
1106
+ 'hedged': None,
1107
+ 'stopLossPrice': None,
1108
+ 'takeProfitPrice': None,
1109
+ })
1110
+
1111
+ async def watch_balance(self, params={}) -> Balances:
1112
+ """
1113
+ watch balance and get the amount of funds available for trading or funds locked in orders
1114
+
1115
+ https://orderly.network/docs/build-on-evm/evm-api/websocket-api/private/balance
1116
+
1117
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1118
+ :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
1119
+ """
1120
+ await self.load_markets()
1121
+ topic = 'balance'
1122
+ messageHash = topic
1123
+ request: dict = {
1124
+ 'event': 'subscribe',
1125
+ 'topic': topic,
1126
+ }
1127
+ message = self.extend(request, params)
1128
+ return await self.watch_private(messageHash, message)
1129
+
1130
+ def handle_balance(self, client, message):
1131
+ #
1132
+ # {
1133
+ # "topic":"balance",
1134
+ # "ts":1651836695254,
1135
+ # "data":{
1136
+ # "balances":{
1137
+ # "USDC":{
1138
+ # "holding":5555815.47398272,
1139
+ # "frozen":0,
1140
+ # "interest":0,
1141
+ # "pendingShortQty":0,
1142
+ # "pendingExposure":0,
1143
+ # "pendingLongQty":0,
1144
+ # "pendingLongExposure":0,
1145
+ # "version":894,
1146
+ # "staked":51370692,
1147
+ # "unbonding":0,
1148
+ # "vault":0,
1149
+ # "averageOpenPrice":0.00000574,
1150
+ # "pnl24H":0,
1151
+ # "fee24H":0.01914,
1152
+ # "markPrice":0.31885
1153
+ # }
1154
+ # }
1155
+ # }
1156
+ # }
1157
+ #
1158
+ data = self.safe_dict(message, 'data', {})
1159
+ balances = self.safe_dict(data, 'balances', {})
1160
+ keys = list(balances.keys())
1161
+ ts = self.safe_integer(message, 'ts')
1162
+ self.balance['info'] = data
1163
+ self.balance['timestamp'] = ts
1164
+ self.balance['datetime'] = self.iso8601(ts)
1165
+ for i in range(0, len(keys)):
1166
+ key = keys[i]
1167
+ value = balances[key]
1168
+ code = self.safe_currency_code(key)
1169
+ account = self.balance[code] if (code in self.balance) else self.account()
1170
+ total = self.safe_string(value, 'holding')
1171
+ used = self.safe_string(value, 'frozen')
1172
+ account['total'] = total
1173
+ account['used'] = used
1174
+ account['free'] = Precise.string_sub(total, used)
1175
+ self.balance[code] = account
1176
+ self.balance = self.safe_balance(self.balance)
1177
+ client.resolve(self.balance, 'balance')
1178
+
1179
+ def handle_error_message(self, client: Client, message):
1180
+ #
1181
+ # {"id":"1","event":"subscribe","success":false,"ts":1710780997216,"errorMsg":"Auth is needed."}
1182
+ #
1183
+ if not ('success' in message):
1184
+ return False
1185
+ success = self.safe_bool(message, 'success')
1186
+ if success:
1187
+ return False
1188
+ errorMessage = self.safe_string(message, 'errorMsg')
1189
+ try:
1190
+ if errorMessage is not None:
1191
+ feedback = self.id + ' ' + self.json(message)
1192
+ self.throw_exactly_matched_exception(self.exceptions['exact'], errorMessage, feedback)
1193
+ return False
1194
+ except Exception as error:
1195
+ if isinstance(error, AuthenticationError):
1196
+ messageHash = 'authenticated'
1197
+ client.reject(error, messageHash)
1198
+ if messageHash in client.subscriptions:
1199
+ del client.subscriptions[messageHash]
1200
+ else:
1201
+ client.reject(error)
1202
+ return True
1203
+
1204
+ def handle_message(self, client: Client, message):
1205
+ if self.handle_error_message(client, message):
1206
+ return
1207
+ methods: dict = {
1208
+ 'ping': self.handle_ping,
1209
+ 'pong': self.handle_pong,
1210
+ 'subscribe': self.handle_subscribe,
1211
+ 'orderbook': self.handle_order_book,
1212
+ 'ticker': self.handle_ticker,
1213
+ 'tickers': self.handle_tickers,
1214
+ 'kline': self.handle_ohlcv,
1215
+ 'trade': self.handle_trade,
1216
+ 'auth': self.handle_auth,
1217
+ 'executionreport': self.handle_order_update,
1218
+ 'algoexecutionreport': self.handle_order_update,
1219
+ 'position': self.handle_positions,
1220
+ 'balance': self.handle_balance,
1221
+ 'bbos': self.handle_bid_ask,
1222
+ }
1223
+ event = self.safe_string(message, 'event')
1224
+ method = self.safe_value(methods, event)
1225
+ if method is not None:
1226
+ method(client, message)
1227
+ return
1228
+ topic = self.safe_string(message, 'topic')
1229
+ if topic is not None:
1230
+ method = self.safe_value(methods, topic)
1231
+ if method is not None:
1232
+ method(client, message)
1233
+ return
1234
+ splitTopic = topic.split('@')
1235
+ splitLength = len(splitTopic)
1236
+ if splitLength == 2:
1237
+ name = self.safe_string(splitTopic, 1)
1238
+ method = self.safe_value(methods, name)
1239
+ if method is not None:
1240
+ method(client, message)
1241
+ return
1242
+ splitName = name.split('_')
1243
+ splitNameLength = len(splitTopic)
1244
+ if splitNameLength == 2:
1245
+ method = self.safe_value(methods, self.safe_string(splitName, 0))
1246
+ if method is not None:
1247
+ method(client, message)
1248
+
1249
+ def ping(self, client: Client):
1250
+ return {'event': 'ping'}
1251
+
1252
+ def handle_ping(self, client: Client, message):
1253
+ return {'event': 'pong'}
1254
+
1255
+ def handle_pong(self, client: Client, message):
1256
+ #
1257
+ # {event: "pong", ts: 1614667590000}
1258
+ #
1259
+ client.lastPong = self.milliseconds()
1260
+ return message
1261
+
1262
+ def handle_subscribe(self, client: Client, message):
1263
+ #
1264
+ # {
1265
+ # "id": "666888",
1266
+ # "event": "subscribe",
1267
+ # "success": True,
1268
+ # "ts": 1657117712212
1269
+ # }
1270
+ #
1271
+ return message