ccxt 4.5.0__py2.py3-none-any.whl → 4.5.2__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 (64) hide show
  1. ccxt/__init__.py +1 -5
  2. ccxt/ascendex.py +1 -1
  3. ccxt/async_support/__init__.py +1 -5
  4. ccxt/async_support/ascendex.py +1 -1
  5. ccxt/async_support/base/exchange.py +1 -1
  6. ccxt/async_support/binance.py +17 -12
  7. ccxt/async_support/bitget.py +1 -1
  8. ccxt/async_support/coinbase.py +46 -34
  9. ccxt/async_support/gate.py +31 -17
  10. ccxt/async_support/gemini.py +1 -1
  11. ccxt/async_support/hibachi.py +1 -1
  12. ccxt/async_support/hyperliquid.py +13 -2
  13. ccxt/async_support/indodax.py +11 -12
  14. ccxt/async_support/kraken.py +1 -8
  15. ccxt/async_support/krakenfutures.py +25 -25
  16. ccxt/async_support/mexc.py +2 -1
  17. ccxt/async_support/okx.py +2 -2
  18. ccxt/async_support/poloniex.py +1 -1
  19. ccxt/async_support/timex.py +35 -0
  20. ccxt/async_support/tradeogre.py +32 -0
  21. ccxt/async_support/wavesexchange.py +33 -0
  22. ccxt/async_support/zonda.py +12 -0
  23. ccxt/base/exchange.py +7 -1
  24. ccxt/binance.py +17 -12
  25. ccxt/bitget.py +1 -1
  26. ccxt/coinbase.py +46 -34
  27. ccxt/gate.py +31 -17
  28. ccxt/gemini.py +1 -1
  29. ccxt/hibachi.py +1 -1
  30. ccxt/hyperliquid.py +13 -2
  31. ccxt/indodax.py +11 -12
  32. ccxt/kraken.py +1 -8
  33. ccxt/krakenfutures.py +25 -25
  34. ccxt/mexc.py +2 -1
  35. ccxt/okx.py +2 -2
  36. ccxt/poloniex.py +1 -1
  37. ccxt/pro/__init__.py +1 -3
  38. ccxt/pro/bitget.py +328 -75
  39. ccxt/pro/bitmart.py +1 -1
  40. ccxt/pro/bybit.py +8 -10
  41. ccxt/pro/gate.py +8 -1
  42. ccxt/pro/gemini.py +6 -2
  43. ccxt/pro/hyperliquid.py +6 -0
  44. ccxt/pro/kraken.py +4 -6
  45. ccxt/pro/lbank.py +56 -2
  46. ccxt/pro/mexc.py +1 -1
  47. ccxt/test/tests_async.py +2 -25
  48. ccxt/test/tests_sync.py +2 -25
  49. ccxt/timex.py +35 -0
  50. ccxt/tradeogre.py +32 -0
  51. ccxt/wavesexchange.py +33 -0
  52. ccxt/zonda.py +12 -0
  53. {ccxt-4.5.0.dist-info → ccxt-4.5.2.dist-info}/METADATA +111 -113
  54. {ccxt-4.5.0.dist-info → ccxt-4.5.2.dist-info}/RECORD +57 -64
  55. ccxt/abstract/ellipx.py +0 -25
  56. ccxt/abstract/vertex.py +0 -19
  57. ccxt/async_support/ellipx.py +0 -2029
  58. ccxt/async_support/vertex.py +0 -3050
  59. ccxt/ellipx.py +0 -2029
  60. ccxt/pro/vertex.py +0 -948
  61. ccxt/vertex.py +0 -3050
  62. {ccxt-4.5.0.dist-info → ccxt-4.5.2.dist-info}/LICENSE.txt +0 -0
  63. {ccxt-4.5.0.dist-info → ccxt-4.5.2.dist-info}/WHEEL +0 -0
  64. {ccxt-4.5.0.dist-info → ccxt-4.5.2.dist-info}/top_level.txt +0 -0
ccxt/pro/vertex.py DELETED
@@ -1,948 +0,0 @@
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
8
- from ccxt.base.types import Any, Bool, Int, Market, Order, OrderBook, Position, Str, Strings, Ticker, Trade
9
- from ccxt.async_support.base.ws.client import Client
10
- from typing import List
11
- from ccxt.base.errors import AuthenticationError
12
- from ccxt.base.errors import ArgumentsRequired
13
- from ccxt.base.errors import NotSupported
14
- from ccxt.base.precise import Precise
15
-
16
-
17
- class vertex(ccxt.async_support.vertex):
18
-
19
- def describe(self) -> Any:
20
- return self.deep_extend(super(vertex, self).describe(), {
21
- 'has': {
22
- 'ws': True,
23
- 'watchBalance': False,
24
- 'watchMyTrades': True,
25
- 'watchOHLCV': False,
26
- 'watchOrderBook': True,
27
- 'watchOrders': True,
28
- 'watchTicker': True,
29
- 'watchTickers': False,
30
- 'watchTrades': True,
31
- 'watchTradesForSymbols': False,
32
- 'watchPositions': True,
33
- },
34
- 'urls': {
35
- 'api': {
36
- 'ws': 'wss://gateway.prod.vertexprotocol.com/v1/subscribe',
37
- },
38
- 'test': {
39
- 'ws': 'wss://gateway.sepolia-test.vertexprotocol.com/v1/subscribe',
40
- },
41
- },
42
- 'requiredCredentials': {
43
- 'apiKey': False,
44
- 'secret': False,
45
- 'walletAddress': True,
46
- 'privateKey': True,
47
- },
48
- 'options': {
49
- 'tradesLimit': 1000,
50
- 'ordersLimit': 1000,
51
- 'requestId': {},
52
- 'watchPositions': {
53
- 'fetchPositionsSnapshot': True, # or False
54
- 'awaitPositionsSnapshot': True, # whether to wait for the positions snapshot before providing updates
55
- },
56
- 'ws': {
57
- 'inflate': True,
58
- 'options': {
59
- 'headers': {
60
- 'Sec-WebSocket-Extensions': 'permessage-deflate', # requires permessage-deflate extension, maybe we can set self in client implementation when self.inflateis True
61
- },
62
- },
63
- },
64
- },
65
- 'streaming': {
66
- # 'ping': self.ping,
67
- 'keepAlive': 30000,
68
- },
69
- 'exceptions': {
70
- 'ws': {
71
- 'exact': {
72
- 'Auth is needed.': AuthenticationError,
73
- },
74
- },
75
- },
76
- })
77
-
78
- def request_id(self, url):
79
- options = self.safe_dict(self.options, 'requestId', {})
80
- previousValue = self.safe_integer(options, url, 0)
81
- newValue = self.sum(previousValue, 1)
82
- self.options['requestId'][url] = newValue
83
- return newValue
84
-
85
- async def watch_public(self, messageHash, message):
86
- url = self.urls['api']['ws']
87
- requestId = self.request_id(url)
88
- subscribe = {
89
- 'id': requestId,
90
- }
91
- request = self.extend(subscribe, message)
92
- wsOptions = {
93
- 'headers': {
94
- 'Sec-WebSocket-Extensions': 'permessage-deflate',
95
- },
96
- }
97
- self.options['ws'] = {
98
- 'options': wsOptions,
99
- }
100
- return await self.watch(url, messageHash, request, messageHash, subscribe)
101
-
102
- async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
103
- """
104
- watches information on multiple trades made in a market
105
-
106
- https://docs.vertexprotocol.com/developer-resources/api/subscriptions/streams
107
-
108
- :param str symbol: unified market symbol of the market trades were made in
109
- :param int [since]: the earliest time in ms to fetch trades for
110
- :param int [limit]: the maximum number of trade structures to retrieve
111
- :param dict [params]: extra parameters specific to the exchange API endpoint
112
- :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
113
- """
114
- await self.load_markets()
115
- market = self.market(symbol)
116
- name = 'trade'
117
- topic = market['id'] + '@' + name
118
- request = {
119
- 'method': 'subscribe',
120
- 'stream': {
121
- 'type': name,
122
- 'product_id': self.parse_to_numeric(market['id']),
123
- },
124
- }
125
- message = self.extend(request, params)
126
- trades = await self.watch_public(topic, message)
127
- if self.newUpdates:
128
- limit = trades.getLimit(market['symbol'], limit)
129
- return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)
130
-
131
- def handle_trade(self, client: Client, message):
132
- #
133
- # {
134
- # "type": "trade",
135
- # "timestamp": "1676151190656903000", # timestamp of the event in nanoseconds
136
- # "product_id": 1,
137
- # "price": "1000", # price the trade happened at, multiplied by 1e18
138
- # # both taker_qty and maker_qty have the same value
139
- # # set to filled amount(min amount of taker and maker) when matching against book
140
- # # set to matched amm base amount when matching against amm
141
- # "taker_qty": "1000",
142
- # "maker_qty": "1000",
143
- # "is_taker_buyer": True,
144
- # "is_maker_amm": True # True when maker is amm
145
- # }
146
- #
147
- topic = self.safe_string(message, 'type')
148
- marketId = self.safe_string(message, 'product_id')
149
- trade = self.parse_ws_trade(message)
150
- symbol = trade['symbol']
151
- if not (symbol in self.trades):
152
- limit = self.safe_integer(self.options, 'tradesLimit', 1000)
153
- stored = ArrayCache(limit)
154
- self.trades[symbol] = stored
155
- trades = self.trades[symbol]
156
- trades.append(trade)
157
- self.trades[symbol] = trades
158
- client.resolve(trades, marketId + '@' + topic)
159
-
160
- async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
161
- """
162
- watches information on multiple trades made by the user
163
-
164
- https://docs.vertexprotocol.com/developer-resources/api/subscriptions/streams
165
-
166
- :param str symbol: unified market symbol of the market orders were made in
167
- :param int [since]: the earliest time in ms to fetch orders for
168
- :param int [limit]: the maximum number of order structures to retrieve
169
- :param dict [params]: extra parameters specific to the exchange API endpoint
170
- :param str [params.user]: user address, will default to self.walletAddress if not provided
171
- :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
172
- """
173
- if symbol is None:
174
- raise ArgumentsRequired(self.id + ' watchMyTrades requires a symbol.')
175
- await self.load_markets()
176
- userAddress = None
177
- userAddress, params = self.handlePublicAddress('watchMyTrades', params)
178
- market = self.market(symbol)
179
- name = 'fill'
180
- topic = market['id'] + '@' + name
181
- request = {
182
- 'method': 'subscribe',
183
- 'stream': {
184
- 'type': name,
185
- 'product_id': self.parse_to_numeric(market['id']),
186
- 'subaccount': self.convertAddressToSender(userAddress),
187
- },
188
- }
189
- message = self.extend(request, params)
190
- trades = await self.watch_public(topic, message)
191
- if self.newUpdates:
192
- limit = trades.getLimit(symbol, limit)
193
- return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)
194
-
195
- def handle_my_trades(self, client: Client, message):
196
- #
197
- # {
198
- # "type": "fill",
199
- # "timestamp": "1676151190656903000", # timestamp of the event in nanoseconds
200
- # "product_id": 1,
201
- # # the subaccount that placed self order
202
- # "subaccount": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
203
- # # hash of the order that uniquely identifies it
204
- # "order_digest": "0xf4f7a8767faf0c7f72251a1f9e5da590f708fd9842bf8fcdeacbaa0237958fff",
205
- # # the amount filled, multiplied by 1e18
206
- # "filled_qty": "1000",
207
- # # the amount outstanding unfilled, multiplied by 1e18
208
- # "remaining_qty": "2000",
209
- # # the original order amount, multiplied by 1e18
210
- # "original_qty": "3000",
211
- # # fill price
212
- # "price": "24991000000000000000000",
213
- # # True for `taker`, False for `maker`
214
- # "is_taker": True,
215
- # "is_bid": True,
216
- # # True when matching against amm
217
- # "is_against_amm": True,
218
- # # an optional `order id` that can be provided when placing an order
219
- # "id": 100
220
- # }
221
- #
222
- topic = self.safe_string(message, 'type')
223
- marketId = self.safe_string(message, 'product_id')
224
- if self.myTrades is None:
225
- limit = self.safe_integer(self.options, 'tradesLimit', 1000)
226
- self.myTrades = ArrayCacheBySymbolById(limit)
227
- trades = self.myTrades
228
- parsed = self.parse_ws_trade(message)
229
- trades.append(parsed)
230
- client.resolve(trades, marketId + '@' + topic)
231
-
232
- def parse_ws_trade(self, trade, market=None):
233
- #
234
- # watchTrades
235
- # {
236
- # "type": "trade",
237
- # "timestamp": "1676151190656903000", # timestamp of the event in nanoseconds
238
- # "product_id": 1,
239
- # "price": "1000", # price the trade happened at, multiplied by 1e18
240
- # # both taker_qty and maker_qty have the same value
241
- # # set to filled amount(min amount of taker and maker) when matching against book
242
- # # set to matched amm base amount when matching against amm
243
- # "taker_qty": "1000",
244
- # "maker_qty": "1000",
245
- # "is_taker_buyer": True,
246
- # "is_maker_amm": True # True when maker is amm
247
- # }
248
- # watchMyTrades
249
- # {
250
- # "type": "fill",
251
- # "timestamp": "1676151190656903000", # timestamp of the event in nanoseconds
252
- # "product_id": 1,
253
- # # the subaccount that placed self order
254
- # "subaccount": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
255
- # # hash of the order that uniquely identifies it
256
- # "order_digest": "0xf4f7a8767faf0c7f72251a1f9e5da590f708fd9842bf8fcdeacbaa0237958fff",
257
- # # the amount filled, multiplied by 1e18
258
- # "filled_qty": "1000",
259
- # # the amount outstanding unfilled, multiplied by 1e18
260
- # "remaining_qty": "2000",
261
- # # the original order amount, multiplied by 1e18
262
- # "original_qty": "3000",
263
- # # fill price
264
- # "price": "24991000000000000000000",
265
- # # True for `taker`, False for `maker`
266
- # "is_taker": True,
267
- # "is_bid": True,
268
- # # True when matching against amm
269
- # "is_against_amm": True,
270
- # # an optional `order id` that can be provided when placing an order
271
- # "id": 100
272
- # }
273
- #
274
- marketId = self.safe_string(trade, 'product_id')
275
- market = self.safe_market(marketId, market)
276
- symbol = market['symbol']
277
- price = self.convertFromX18(self.safe_string(trade, 'price'))
278
- amount = self.convertFromX18(self.safe_string_2(trade, 'taker_qty', 'filled_qty'))
279
- cost = Precise.string_mul(price, amount)
280
- timestamp = self.safe_integer_product(trade, 'timestamp', 0.000001)
281
- takerOrMaker = None
282
- isTaker = self.safe_bool(trade, 'is_taker')
283
- if isTaker is not None:
284
- takerOrMaker = 'taker' if (isTaker) else 'maker'
285
- side = None
286
- isBid = self.safe_bool(trade, 'is_bid')
287
- if isBid is not None:
288
- side = 'buy' if (isBid) else 'sell'
289
- return self.safe_trade({
290
- 'id': None,
291
- 'timestamp': timestamp,
292
- 'datetime': self.iso8601(timestamp),
293
- 'symbol': symbol,
294
- 'side': side,
295
- 'price': price,
296
- 'amount': amount,
297
- 'cost': cost,
298
- 'order': self.safe_string_2(trade, 'digest', 'id'),
299
- 'takerOrMaker': takerOrMaker,
300
- 'type': None,
301
- 'fee': None,
302
- 'info': trade,
303
- }, market)
304
-
305
- async def watch_ticker(self, symbol: str, params={}) -> Ticker:
306
- """
307
-
308
- https://docs.vertexprotocol.com/developer-resources/api/subscriptions/streams
309
-
310
- watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
311
- :param str symbol: unified symbol of the market to fetch the ticker for
312
- :param dict [params]: extra parameters specific to the exchange API endpoint
313
- :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
314
- """
315
- await self.load_markets()
316
- name = 'best_bid_offer'
317
- market = self.market(symbol)
318
- topic = market['id'] + '@' + name
319
- request = {
320
- 'method': 'subscribe',
321
- 'stream': {
322
- 'type': name,
323
- 'product_id': self.parse_to_numeric(market['id']),
324
- },
325
- }
326
- message = self.extend(request, params)
327
- return await self.watch_public(topic, message)
328
-
329
- def parse_ws_ticker(self, ticker, market=None):
330
- #
331
- # {
332
- # "type": "best_bid_offer",
333
- # "timestamp": "1676151190656903000", # timestamp of the event in nanoseconds
334
- # "product_id": 1,
335
- # "bid_price": "1000", # the highest bid price, multiplied by 1e18
336
- # "bid_qty": "1000", # quantity at the huighest bid, multiplied by 1e18.
337
- # # i.e. if self is USDC with 6 decimals, one USDC
338
- # # would be 1e12
339
- # "ask_price": "1000", # lowest ask price
340
- # "ask_qty": "1000" # quantity at the lowest ask
341
- # }
342
- #
343
- timestamp = self.safe_integer_product(ticker, 'timestamp', 0.000001)
344
- return self.safe_ticker({
345
- 'symbol': self.safe_symbol(None, market),
346
- 'timestamp': timestamp,
347
- 'datetime': self.iso8601(timestamp),
348
- 'high': self.safe_string(ticker, 'high'),
349
- 'low': self.safe_string(ticker, 'low'),
350
- 'bid': self.convertFromX18(self.safe_string(ticker, 'bid_price')),
351
- 'bidVolume': self.convertFromX18(self.safe_string(ticker, 'bid_qty')),
352
- 'ask': self.convertFromX18(self.safe_string(ticker, 'ask_price')),
353
- 'askVolume': self.convertFromX18(self.safe_string(ticker, 'ask_qty')),
354
- 'vwap': None,
355
- 'open': None,
356
- 'close': None,
357
- 'last': None,
358
- 'previousClose': None,
359
- 'change': None,
360
- 'percentage': None,
361
- 'average': None,
362
- 'baseVolume': None,
363
- 'quoteVolume': None,
364
- 'info': ticker,
365
- }, market)
366
-
367
- def handle_ticker(self, client: Client, message):
368
- #
369
- # {
370
- # "type": "best_bid_offer",
371
- # "timestamp": "1676151190656903000", # timestamp of the event in nanoseconds
372
- # "product_id": 1,
373
- # "bid_price": "1000", # the highest bid price, multiplied by 1e18
374
- # "bid_qty": "1000", # quantity at the huighest bid, multiplied by 1e18.
375
- # # i.e. if self is USDC with 6 decimals, one USDC
376
- # # would be 1e12
377
- # "ask_price": "1000", # lowest ask price
378
- # "ask_qty": "1000" # quantity at the lowest ask
379
- # }
380
- #
381
- marketId = self.safe_string(message, 'product_id')
382
- market = self.safe_market(marketId)
383
- ticker = self.parse_ws_ticker(message, market)
384
- ticker['symbol'] = market['symbol']
385
- self.tickers[market['symbol']] = ticker
386
- client.resolve(ticker, marketId + '@best_bid_offer')
387
- return message
388
-
389
- async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
390
- """
391
-
392
- https://docs.vertexprotocol.com/developer-resources/api/subscriptions/streams
393
-
394
- watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
395
- :param str symbol: unified symbol of the market to fetch the order book for
396
- :param int [limit]: the maximum amount of order book entries to return.
397
- :param dict [params]: extra parameters specific to the exchange API endpoint
398
- :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
399
- """
400
- await self.load_markets()
401
- name = 'book_depth'
402
- market = self.market(symbol)
403
- messageHash = market['id'] + '@' + name
404
- url = self.urls['api']['ws']
405
- requestId = self.request_id(url)
406
- request: dict = {
407
- 'id': requestId,
408
- 'method': 'subscribe',
409
- 'stream': {
410
- 'type': name,
411
- 'product_id': self.parse_to_numeric(market['id']),
412
- },
413
- }
414
- subscription: dict = {
415
- 'id': str(requestId),
416
- 'name': name,
417
- 'symbol': symbol,
418
- 'method': self.handle_order_book_subscription,
419
- 'limit': limit,
420
- 'params': params,
421
- }
422
- message = self.extend(request, params)
423
- orderbook = await self.watch(url, messageHash, message, messageHash, subscription)
424
- return orderbook.limit()
425
-
426
- def handle_order_book_subscription(self, client: Client, message, subscription):
427
- defaultLimit = self.safe_integer(self.options, 'watchOrderBookLimit', 1000)
428
- limit = self.safe_integer(subscription, 'limit', defaultLimit)
429
- symbol = self.safe_string(subscription, 'symbol') # watchOrderBook
430
- if symbol in self.orderbooks:
431
- del self.orderbooks[symbol]
432
- self.orderbooks[symbol] = self.order_book({}, limit)
433
- self.spawn(self.fetch_order_book_snapshot, client, message, subscription)
434
-
435
- async def fetch_order_book_snapshot(self, client, message, subscription):
436
- symbol = self.safe_string(subscription, 'symbol')
437
- market = self.market(symbol)
438
- messageHash = market['id'] + '@book_depth'
439
- try:
440
- defaultLimit = self.safe_integer(self.options, 'watchOrderBookLimit', 1000)
441
- limit = self.safe_integer(subscription, 'limit', defaultLimit)
442
- params = self.safe_value(subscription, 'params')
443
- snapshot = await self.fetch_rest_order_book_safe(symbol, limit, params)
444
- if self.safe_value(self.orderbooks, symbol) is None:
445
- # if the orderbook is dropped before the snapshot is received
446
- return
447
- orderbook = self.orderbooks[symbol]
448
- orderbook.reset(snapshot)
449
- messages = orderbook.cache
450
- for i in range(0, len(messages)):
451
- messageItem = messages[i]
452
- lastTimestamp = self.parse_to_int(Precise.string_div(self.safe_string(messageItem, 'last_max_timestamp'), '1000000'))
453
- if lastTimestamp < orderbook['timestamp']:
454
- continue
455
- else:
456
- self.handle_order_book_message(client, messageItem, orderbook)
457
- self.orderbooks[symbol] = orderbook
458
- client.resolve(orderbook, messageHash)
459
- except Exception as e:
460
- del client.subscriptions[messageHash]
461
- client.reject(e, messageHash)
462
-
463
- def handle_order_book(self, client: Client, message):
464
- #
465
- #
466
- # the feed does not include a snapshot, just the deltas
467
- #
468
- # {
469
- # "type":"book_depth",
470
- # # book depth aggregates a number of events once every 50ms
471
- # # these are the minimum and maximum timestamps from
472
- # # events that contributed to self response
473
- # "min_timestamp": "1683805381879572835",
474
- # "max_timestamp": "1683805381879572835",
475
- # # the max_timestamp of the last book_depth event for self product
476
- # "last_max_timestamp": "1683805381771464799",
477
- # "product_id":1,
478
- # # changes to the bid side of the book in the form of [[price, new_qty]]
479
- # "bids":[["21594490000000000000000","51007390115411548"]],
480
- # # changes to the ask side of the book in the form of [[price, new_qty]]
481
- # "asks":[["21694490000000000000000","0"],["21695050000000000000000","0"]]
482
- # }
483
- #
484
- marketId = self.safe_string(message, 'product_id')
485
- market = self.safe_market(marketId)
486
- symbol = market['symbol']
487
- if not (symbol in self.orderbooks):
488
- self.orderbooks[symbol] = self.order_book()
489
- orderbook = self.orderbooks[symbol]
490
- timestamp = self.safe_integer(orderbook, 'timestamp')
491
- if timestamp is None:
492
- # Buffer the events you receive from the stream.
493
- orderbook.cache.append(message)
494
- else:
495
- lastTimestamp = self.parse_to_int(Precise.string_div(self.safe_string(message, 'last_max_timestamp'), '1000000'))
496
- if lastTimestamp > timestamp:
497
- self.handle_order_book_message(client, message, orderbook)
498
- client.resolve(orderbook, marketId + '@book_depth')
499
-
500
- def handle_order_book_message(self, client: Client, message, orderbook):
501
- timestamp = self.parse_to_int(Precise.string_div(self.safe_string(message, 'last_max_timestamp'), '1000000'))
502
- # convert from X18
503
- data = {
504
- 'bids': [],
505
- 'asks': [],
506
- }
507
- bids = self.safe_list(message, 'bids', [])
508
- for i in range(0, len(bids)):
509
- bid = bids[i]
510
- data['bids'].append([
511
- self.convertFromX18(bid[0]),
512
- self.convertFromX18(bid[1]),
513
- ])
514
- asks = self.safe_list(message, 'asks', [])
515
- for i in range(0, len(asks)):
516
- ask = asks[i]
517
- data['asks'].append([
518
- self.convertFromX18(ask[0]),
519
- self.convertFromX18(ask[1]),
520
- ])
521
- self.handle_deltas(orderbook['asks'], self.safe_list(data, 'asks', []))
522
- self.handle_deltas(orderbook['bids'], self.safe_list(data, 'bids', []))
523
- orderbook['timestamp'] = timestamp
524
- orderbook['datetime'] = self.iso8601(timestamp)
525
- return orderbook
526
-
527
- def handle_delta(self, bookside, delta):
528
- price = self.safe_float(delta, 0)
529
- amount = self.safe_float(delta, 1)
530
- bookside.store(price, amount)
531
-
532
- def handle_deltas(self, bookside, deltas):
533
- for i in range(0, len(deltas)):
534
- self.handle_delta(bookside, deltas[i])
535
-
536
- def handle_subscription_status(self, client: Client, message):
537
- #
538
- # {
539
- # "result": null,
540
- # "id": 1574649734450
541
- # }
542
- #
543
- id = self.safe_string(message, 'id')
544
- subscriptionsById = self.index_by(client.subscriptions, 'id')
545
- subscription = self.safe_value(subscriptionsById, id, {})
546
- method = self.safe_value(subscription, 'method')
547
- if method is not None:
548
- method(client, message, subscription)
549
- return message
550
-
551
- async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
552
- """
553
-
554
- https://docs.vertexprotocol.com/developer-resources/api/subscriptions/streams
555
-
556
- watch all open positions
557
- :param str[]|None symbols: list of unified market symbols
558
- @param since
559
- @param limit
560
- :param dict params: extra parameters specific to the exchange API endpoint
561
- :param str [params.user]: user address, will default to self.walletAddress if not provided
562
- :returns dict[]: a list of `position structure <https://docs.ccxt.com/en/latest/manual.html#position-structure>`
563
- """
564
- await self.load_markets()
565
- symbols = self.market_symbols(symbols)
566
- if not self.is_empty(symbols):
567
- if len(symbols) > 1:
568
- raise NotSupported(self.id + ' watchPositions require only one symbol.')
569
- else:
570
- raise ArgumentsRequired(self.id + ' watchPositions require one symbol.')
571
- userAddress = None
572
- userAddress, params = self.handlePublicAddress('watchPositions', params)
573
- url = self.urls['api']['ws']
574
- client = self.client(url)
575
- self.set_positions_cache(client, symbols, params)
576
- fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', True)
577
- awaitPositionsSnapshot = self.handle_option('watchPositions', 'awaitPositionsSnapshot', True)
578
- if fetchPositionsSnapshot and awaitPositionsSnapshot and self.positions is None:
579
- snapshot = await client.future('fetchPositionsSnapshot')
580
- return self.filter_by_symbols_since_limit(snapshot, symbols, since, limit, True)
581
- name = 'position_change'
582
- market = self.market(symbols[0])
583
- topic = market['id'] + '@' + name
584
- request = {
585
- 'method': 'subscribe',
586
- 'stream': {
587
- 'type': name,
588
- 'product_id': self.parse_to_numeric(market['id']),
589
- 'subaccount': self.convertAddressToSender(userAddress),
590
- },
591
- }
592
- message = self.extend(request, params)
593
- newPositions = await self.watch_public(topic, message)
594
- if self.newUpdates:
595
- limit = newPositions.getLimit(symbols[0], limit)
596
- return self.filter_by_symbols_since_limit(self.positions, symbols, since, limit, True)
597
-
598
- def set_positions_cache(self, client: Client, symbols: Strings = None, params={}):
599
- fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', False)
600
- if fetchPositionsSnapshot:
601
- messageHash = 'fetchPositionsSnapshot'
602
- if not (messageHash in client.futures):
603
- client.future(messageHash)
604
- self.spawn(self.load_positions_snapshot, client, messageHash, symbols, params)
605
- else:
606
- self.positions = ArrayCacheBySymbolBySide()
607
-
608
- async def load_positions_snapshot(self, client, messageHash, symbols, params):
609
- positions = await self.fetch_positions(symbols, params)
610
- self.positions = ArrayCacheBySymbolBySide()
611
- cache = self.positions
612
- for i in range(0, len(positions)):
613
- position = positions[i]
614
- cache.append(position)
615
- # don't remove the future from the .futures cache
616
- future = client.futures[messageHash]
617
- future.resolve(cache)
618
- client.resolve(cache, 'positions')
619
-
620
- def handle_positions(self, client, message):
621
- #
622
- # {
623
- # "type":"position_change",
624
- # "timestamp": "1676151190656903000", # timestamp of event in nanoseconds
625
- # "product_id":1,
626
- # # whether self is a position change for the LP token for self product
627
- # "is_lp":false,
628
- # # subaccount who's position changed
629
- # "subaccount":"0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43706d00000000000000000000",
630
- # # new amount for self product
631
- # "amount":"51007390115411548",
632
- # # new quote balance for self product; zero for everything except non lp perps
633
- # # the negative of the entry cost of the perp
634
- # "v_quote_amount":"0"
635
- # }
636
- #
637
- if self.positions is None:
638
- self.positions = ArrayCacheBySymbolBySide()
639
- cache = self.positions
640
- topic = self.safe_string(message, 'type')
641
- marketId = self.safe_string(message, 'product_id')
642
- market = self.safe_market(marketId)
643
- position = self.parse_ws_position(message, market)
644
- cache.append(position)
645
- client.resolve(position, marketId + '@' + topic)
646
-
647
- def parse_ws_position(self, position, market=None):
648
- #
649
- # {
650
- # "type":"position_change",
651
- # "timestamp": "1676151190656903000", # timestamp of event in nanoseconds
652
- # "product_id":1,
653
- # # whether self is a position change for the LP token for self product
654
- # "is_lp":false,
655
- # # subaccount who's position changed
656
- # "subaccount":"0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43706d00000000000000000000",
657
- # # new amount for self product
658
- # "amount":"51007390115411548",
659
- # # new quote balance for self product; zero for everything except non lp perps
660
- # # the negative of the entry cost of the perp
661
- # "v_quote_amount":"0"
662
- # }
663
- #
664
- marketId = self.safe_string(position, 'product_id')
665
- market = self.safe_market(marketId)
666
- contractSize = self.convertFromX18(self.safe_string(position, 'amount'))
667
- side = 'buy'
668
- if Precise.string_lt(contractSize, '1'):
669
- side = 'sell'
670
- timestamp = self.parse_to_int(Precise.string_div(self.safe_string(position, 'timestamp'), '1000000'))
671
- return self.safe_position({
672
- 'info': position,
673
- 'id': None,
674
- 'symbol': self.safe_string(market, 'symbol'),
675
- 'timestamp': timestamp,
676
- 'datetime': self.iso8601(timestamp),
677
- 'lastUpdateTimestamp': None,
678
- 'initialMargin': None,
679
- 'initialMarginPercentage': None,
680
- 'maintenanceMargin': None,
681
- 'maintenanceMarginPercentage': None,
682
- 'entryPrice': None,
683
- 'notional': None,
684
- 'leverage': None,
685
- 'unrealizedPnl': None,
686
- 'contracts': None,
687
- 'contractSize': self.parse_number(contractSize),
688
- 'marginRatio': None,
689
- 'liquidationPrice': None,
690
- 'markPrice': None,
691
- 'lastPrice': None,
692
- 'collateral': None,
693
- 'marginMode': 'cross',
694
- 'marginType': None,
695
- 'side': side,
696
- 'percentage': None,
697
- 'hedged': None,
698
- 'stopLossPrice': None,
699
- 'takeProfitPrice': None,
700
- })
701
-
702
- def handle_auth(self, client: Client, message):
703
- #
704
- # {result: null, id: 1}
705
- #
706
- messageHash = 'authenticated'
707
- error = self.safe_string(message, 'error')
708
- if error is None:
709
- # client.resolve(message, messageHash)
710
- future = self.safe_value(client.futures, 'authenticated')
711
- future.resolve(True)
712
- else:
713
- authError = AuthenticationError(self.json(message))
714
- client.reject(authError, messageHash)
715
- # allows further authentication attempts
716
- if messageHash in client.subscriptions:
717
- del client.subscriptions['authenticated']
718
-
719
- def build_ws_authentication_sig(self, message, chainId, verifyingContractAddress):
720
- messageTypes = {
721
- 'StreamAuthentication': [
722
- {'name': 'sender', 'type': 'bytes32'},
723
- {'name': 'expiration', 'type': 'uint64'},
724
- ],
725
- }
726
- return self.buildSig(chainId, messageTypes, message, verifyingContractAddress)
727
-
728
- async def authenticate(self, params={}):
729
- self.check_required_credentials()
730
- url = self.urls['api']['ws']
731
- client = self.client(url)
732
- messageHash = 'authenticated'
733
- future = client.future(messageHash)
734
- authenticated = self.safe_value(client.subscriptions, messageHash)
735
- if authenticated is None:
736
- requestId = self.request_id(url)
737
- contracts = await self.queryContracts()
738
- chainId = self.safe_string(contracts, 'chain_id')
739
- verifyingContractAddress = self.safe_string(contracts, 'endpoint_addr')
740
- now = self.nonce()
741
- nonce = now + 90000
742
- authentication = {
743
- 'sender': self.convertAddressToSender(self.walletAddress),
744
- 'expiration': nonce,
745
- }
746
- request = {
747
- 'id': requestId,
748
- 'method': 'authenticate',
749
- 'tx': {
750
- 'sender': authentication['sender'],
751
- 'expiration': self.number_to_string(authentication['expiration']),
752
- },
753
- 'signature': self.build_ws_authentication_sig(authentication, chainId, verifyingContractAddress),
754
- }
755
- message = self.extend(request, params)
756
- self.watch(url, messageHash, message, messageHash)
757
- return await future
758
-
759
- async def watch_private(self, messageHash, message, params={}):
760
- await self.authenticate(params)
761
- url = self.urls['api']['ws']
762
- requestId = self.request_id(url)
763
- subscribe = {
764
- 'id': requestId,
765
- }
766
- request = self.extend(subscribe, message)
767
- return await self.watch(url, messageHash, request, messageHash, subscribe)
768
-
769
- async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
770
- """
771
- watches information on multiple orders made by the user
772
-
773
- https://docs.vertexprotocol.com/developer-resources/api/subscriptions/streams
774
-
775
- :param str symbol: unified market symbol of the market orders were made in
776
- :param int [since]: the earliest time in ms to fetch orders for
777
- :param int [limit]: the maximum number of order structures to retrieve
778
- :param dict [params]: extra parameters specific to the exchange API endpoint
779
- :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
780
- """
781
- if symbol is None:
782
- raise ArgumentsRequired(self.id + ' watchOrders requires a symbol.')
783
- self.check_required_credentials()
784
- await self.load_markets()
785
- name = 'order_update'
786
- market = self.market(symbol)
787
- topic = market['id'] + '@' + name
788
- request = {
789
- 'method': 'subscribe',
790
- 'stream': {
791
- 'type': name,
792
- 'subaccount': self.convertAddressToSender(self.walletAddress),
793
- 'product_id': self.parse_to_numeric(market['id']),
794
- },
795
- }
796
- message = self.extend(request, params)
797
- orders = await self.watch_private(topic, message)
798
- if self.newUpdates:
799
- limit = orders.getLimit(symbol, limit)
800
- return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
801
-
802
- def parse_ws_order_status(self, status):
803
- if status is not None:
804
- statuses = {
805
- 'filled': 'open',
806
- 'placed': 'open',
807
- 'cancelled': 'canceled',
808
- }
809
- return self.safe_string(statuses, status, status)
810
- return status
811
-
812
- def parse_ws_order(self, order, market: Market = None) -> Order:
813
- #
814
- # {
815
- # "type": "order_update",
816
- # # timestamp of the event in nanoseconds
817
- # "timestamp": "1695081920633151000",
818
- # "product_id": 1,
819
- # # order digest
820
- # "digest": "0xf7712b63ccf70358db8f201e9bf33977423e7a63f6a16f6dab180bdd580f7c6c",
821
- # # remaining amount to be filled.
822
- # # will be `0` if the order is either fully filled or cancelled.
823
- # "amount": "82000000000000000",
824
- # # any of: "filled", "cancelled", "placed"
825
- # "reason": "filled"
826
- # # an optional `order id` that can be provided when placing an order
827
- # "id": 100
828
- # }
829
- #
830
- marketId = self.safe_string(order, 'product_id')
831
- timestamp = self.parse_to_int(Precise.string_div(self.safe_string(order, 'timestamp'), '1000000'))
832
- remainingString = self.convertFromX18(self.safe_string(order, 'amount'))
833
- remaining = self.parse_to_numeric(remainingString)
834
- status = self.parse_ws_order_status(self.safe_string(order, 'reason'))
835
- if Precise.string_eq(remainingString, '0') and status == 'open':
836
- status = 'closed'
837
- market = self.safe_market(marketId, market)
838
- symbol = market['symbol']
839
- return self.safe_order({
840
- 'info': order,
841
- 'id': self.safe_string_2(order, 'digest', 'id'),
842
- 'clientOrderId': None,
843
- 'timestamp': timestamp,
844
- 'datetime': self.iso8601(timestamp),
845
- 'lastTradeTimestamp': None,
846
- 'lastUpdateTimestamp': None,
847
- 'symbol': symbol,
848
- 'type': None,
849
- 'timeInForce': None,
850
- 'postOnly': None,
851
- 'reduceOnly': None,
852
- 'side': None,
853
- 'price': None,
854
- 'triggerPrice': None,
855
- 'amount': None,
856
- 'cost': None,
857
- 'average': None,
858
- 'filled': None,
859
- 'remaining': remaining,
860
- 'status': status,
861
- 'fee': None,
862
- 'trades': None,
863
- }, market)
864
-
865
- def handle_order_update(self, client: Client, message):
866
- #
867
- # {
868
- # "type": "order_update",
869
- # # timestamp of the event in nanoseconds
870
- # "timestamp": "1695081920633151000",
871
- # "product_id": 1,
872
- # # order digest
873
- # "digest": "0xf7712b63ccf70358db8f201e9bf33977423e7a63f6a16f6dab180bdd580f7c6c",
874
- # # remaining amount to be filled.
875
- # # will be `0` if the order is either fully filled or cancelled.
876
- # "amount": "82000000000000000",
877
- # # any of: "filled", "cancelled", "placed"
878
- # "reason": "filled"
879
- # # an optional `order id` that can be provided when placing an order
880
- # "id": 100
881
- # }
882
- #
883
- topic = self.safe_string(message, 'type')
884
- marketId = self.safe_string(message, 'product_id')
885
- parsed = self.parse_ws_order(message)
886
- symbol = self.safe_string(parsed, 'symbol')
887
- orderId = self.safe_string(parsed, 'id')
888
- if symbol is not None:
889
- if self.orders is None:
890
- limit = self.safe_integer(self.options, 'ordersLimit', 1000)
891
- self.orders = ArrayCacheBySymbolById(limit)
892
- cachedOrders = self.orders
893
- orders = self.safe_dict(cachedOrders.hashmap, symbol, {})
894
- order = self.safe_dict(orders, orderId)
895
- if order is not None:
896
- parsed['timestamp'] = self.safe_integer(order, 'timestamp')
897
- parsed['datetime'] = self.safe_string(order, 'datetime')
898
- cachedOrders.append(parsed)
899
- client.resolve(self.orders, marketId + '@' + topic)
900
-
901
- def handle_error_message(self, client: Client, message) -> Bool:
902
- #
903
- # {
904
- # result: null,
905
- # error: 'error parsing request: missing field `expiration`',
906
- # id: 0
907
- # }
908
- #
909
- errorMessage = self.safe_string(message, 'error')
910
- try:
911
- if errorMessage is not None:
912
- feedback = self.id + ' ' + self.json(message)
913
- self.throw_exactly_matched_exception(self.exceptions['exact'], errorMessage, feedback)
914
- return False
915
- except Exception as error:
916
- if isinstance(error, AuthenticationError):
917
- messageHash = 'authenticated'
918
- client.reject(error, messageHash)
919
- if messageHash in client.subscriptions:
920
- del client.subscriptions[messageHash]
921
- else:
922
- client.reject(error)
923
- return True
924
-
925
- def handle_message(self, client: Client, message):
926
- if self.handle_error_message(client, message):
927
- return
928
- methods = {
929
- 'trade': self.handle_trade,
930
- 'best_bid_offer': self.handle_ticker,
931
- 'book_depth': self.handle_order_book,
932
- 'fill': self.handle_my_trades,
933
- 'position_change': self.handle_positions,
934
- 'order_update': self.handle_order_update,
935
- }
936
- event = self.safe_string(message, 'type')
937
- method = self.safe_value(methods, event)
938
- if method is not None:
939
- method(client, message)
940
- return
941
- requestId = self.safe_string(message, 'id')
942
- if requestId is not None:
943
- self.handle_subscription_status(client, message)
944
- return
945
- # check whether it's authentication
946
- auth = self.safe_value(client.futures, 'authenticated')
947
- if auth is not None:
948
- self.handle_auth(client, message)