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