ccxt 4.5.1__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.
- ccxt/__init__.py +1 -5
- ccxt/ascendex.py +1 -1
- ccxt/async_support/__init__.py +1 -5
- ccxt/async_support/ascendex.py +1 -1
- ccxt/async_support/base/exchange.py +1 -1
- ccxt/async_support/binance.py +17 -12
- ccxt/async_support/bitget.py +1 -1
- ccxt/async_support/indodax.py +11 -12
- ccxt/async_support/okx.py +2 -2
- ccxt/async_support/poloniex.py +1 -1
- ccxt/async_support/zonda.py +12 -0
- ccxt/base/exchange.py +1 -1
- ccxt/binance.py +17 -12
- ccxt/bitget.py +1 -1
- ccxt/indodax.py +11 -12
- ccxt/okx.py +2 -2
- ccxt/poloniex.py +1 -1
- ccxt/pro/__init__.py +1 -3
- ccxt/pro/bitget.py +151 -26
- ccxt/pro/bitmart.py +1 -1
- ccxt/pro/gemini.py +6 -2
- ccxt/pro/hyperliquid.py +4 -0
- ccxt/pro/kraken.py +3 -6
- ccxt/test/tests_async.py +2 -25
- ccxt/test/tests_sync.py +2 -25
- ccxt/zonda.py +12 -0
- {ccxt-4.5.1.dist-info → ccxt-4.5.2.dist-info}/METADATA +111 -113
- {ccxt-4.5.1.dist-info → ccxt-4.5.2.dist-info}/RECORD +31 -38
- ccxt/abstract/ellipx.py +0 -25
- ccxt/abstract/vertex.py +0 -19
- ccxt/async_support/ellipx.py +0 -2029
- ccxt/async_support/vertex.py +0 -3050
- ccxt/ellipx.py +0 -2029
- ccxt/pro/vertex.py +0 -948
- ccxt/vertex.py +0 -3050
- {ccxt-4.5.1.dist-info → ccxt-4.5.2.dist-info}/LICENSE.txt +0 -0
- {ccxt-4.5.1.dist-info → ccxt-4.5.2.dist-info}/WHEEL +0 -0
- {ccxt-4.5.1.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)
|