ccxt 4.4.63__py2.py3-none-any.whl → 4.4.68__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 +5 -3
- ccxt/abstract/binance.py +1 -0
- ccxt/abstract/binancecoinm.py +1 -0
- ccxt/abstract/binanceus.py +1 -0
- ccxt/abstract/binanceusdm.py +1 -0
- ccxt/abstract/cryptomus.py +20 -0
- ccxt/abstract/derive.py +117 -0
- ccxt/abstract/tradeogre.py +1 -0
- ccxt/abstract/whitebit.py +16 -0
- ccxt/async_support/__init__.py +5 -3
- ccxt/async_support/base/exchange.py +6 -5
- ccxt/async_support/binance.py +8 -6
- ccxt/async_support/bitget.py +22 -12
- ccxt/async_support/bitrue.py +6 -3
- ccxt/async_support/bybit.py +1 -1
- ccxt/async_support/coinbase.py +73 -2
- ccxt/async_support/cryptocom.py +2 -0
- ccxt/async_support/cryptomus.py +1041 -0
- ccxt/async_support/derive.py +2530 -0
- ccxt/async_support/gate.py +5 -1
- ccxt/async_support/htx.py +19 -5
- ccxt/async_support/hyperliquid.py +108 -68
- ccxt/async_support/luno.py +113 -1
- ccxt/async_support/paradex.py +51 -12
- ccxt/async_support/tradeogre.py +132 -13
- ccxt/async_support/whitebit.py +276 -2
- ccxt/base/exchange.py +13 -4
- ccxt/binance.py +8 -6
- ccxt/bitget.py +22 -12
- ccxt/bitrue.py +6 -3
- ccxt/bybit.py +1 -1
- ccxt/coinbase.py +73 -2
- ccxt/cryptocom.py +2 -0
- ccxt/cryptomus.py +1041 -0
- ccxt/derive.py +2529 -0
- ccxt/gate.py +5 -1
- ccxt/htx.py +19 -5
- ccxt/hyperliquid.py +108 -68
- ccxt/luno.py +113 -1
- ccxt/paradex.py +51 -12
- ccxt/pro/__init__.py +3 -3
- ccxt/pro/bitopro.py +1 -1
- ccxt/pro/bybit.py +3 -2
- ccxt/pro/derive.py +704 -0
- ccxt/pro/gate.py +8 -1
- ccxt/pro/hyperliquid.py +3 -3
- ccxt/pro/vertex.py +5 -0
- ccxt/test/tests_async.py +36 -3
- ccxt/test/tests_sync.py +36 -3
- ccxt/tradeogre.py +132 -13
- ccxt/whitebit.py +276 -2
- {ccxt-4.4.63.dist-info → ccxt-4.4.68.dist-info}/METADATA +16 -12
- {ccxt-4.4.63.dist-info → ccxt-4.4.68.dist-info}/RECORD +56 -53
- ccxt/abstract/currencycom.py +0 -68
- ccxt/async_support/currencycom.py +0 -2070
- ccxt/currencycom.py +0 -2070
- ccxt/pro/currencycom.py +0 -536
- {ccxt-4.4.63.dist-info → ccxt-4.4.68.dist-info}/LICENSE.txt +0 -0
- {ccxt-4.4.63.dist-info → ccxt-4.4.68.dist-info}/WHEEL +0 -0
- {ccxt-4.4.63.dist-info → ccxt-4.4.68.dist-info}/top_level.txt +0 -0
ccxt/pro/derive.py
ADDED
@@ -0,0 +1,704 @@
|
|
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
|
8
|
+
from ccxt.base.types import Any, Int, Order, OrderBook, Str, Ticker, Trade
|
9
|
+
from ccxt.async_support.base.ws.client import Client
|
10
|
+
from typing import List
|
11
|
+
from ccxt.base.errors import ExchangeError
|
12
|
+
from ccxt.base.errors import AuthenticationError
|
13
|
+
from ccxt.base.errors import UnsubscribeError
|
14
|
+
|
15
|
+
|
16
|
+
class derive(ccxt.async_support.derive):
|
17
|
+
|
18
|
+
def describe(self) -> Any:
|
19
|
+
return self.deep_extend(super(derive, self).describe(), {
|
20
|
+
'has': {
|
21
|
+
'ws': False,
|
22
|
+
'watchBalance': False,
|
23
|
+
'watchMyTrades': True,
|
24
|
+
'watchOHLCV': False,
|
25
|
+
'watchOrderBook': True,
|
26
|
+
'watchOrders': True,
|
27
|
+
'watchTicker': True,
|
28
|
+
'watchTickers': False,
|
29
|
+
'watchBidsAsks': False,
|
30
|
+
'watchTrades': True,
|
31
|
+
'watchTradesForSymbols': False,
|
32
|
+
'watchPositions': False,
|
33
|
+
},
|
34
|
+
'urls': {
|
35
|
+
'api': {
|
36
|
+
'ws': 'wss://api.lyra.finance/ws',
|
37
|
+
},
|
38
|
+
'test': {
|
39
|
+
'ws': 'wss://api-demo.lyra.finance/ws',
|
40
|
+
},
|
41
|
+
},
|
42
|
+
'options': {
|
43
|
+
'tradesLimit': 1000,
|
44
|
+
'ordersLimit': 1000,
|
45
|
+
'requestId': {},
|
46
|
+
},
|
47
|
+
'streaming': {
|
48
|
+
'keepAlive': 9000,
|
49
|
+
},
|
50
|
+
'exceptions': {
|
51
|
+
'ws': {
|
52
|
+
'exact': {},
|
53
|
+
},
|
54
|
+
},
|
55
|
+
})
|
56
|
+
|
57
|
+
def request_id(self, url):
|
58
|
+
options = self.safe_value(self.options, 'requestId', {})
|
59
|
+
previousValue = self.safe_integer(options, url, 0)
|
60
|
+
newValue = self.sum(previousValue, 1)
|
61
|
+
self.options['requestId'][url] = newValue
|
62
|
+
return newValue
|
63
|
+
|
64
|
+
async def watch_public(self, messageHash, message, subscription):
|
65
|
+
url = self.urls['api']['ws']
|
66
|
+
requestId = self.request_id(url)
|
67
|
+
request = self.extend(message, {
|
68
|
+
'id': requestId,
|
69
|
+
})
|
70
|
+
subscription = self.extend(subscription, {
|
71
|
+
'id': requestId,
|
72
|
+
'method': 'subscribe',
|
73
|
+
})
|
74
|
+
return await self.watch(url, messageHash, request, messageHash, subscription)
|
75
|
+
|
76
|
+
async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
|
77
|
+
"""
|
78
|
+
|
79
|
+
https://docs.derive.xyz/reference/orderbook-instrument_name-group-depth
|
80
|
+
|
81
|
+
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
82
|
+
:param str symbol: unified symbol of the market to fetch the order book for
|
83
|
+
:param int [limit]: the maximum amount of order book entries to return.
|
84
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
85
|
+
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
86
|
+
"""
|
87
|
+
await self.load_markets()
|
88
|
+
if limit is None:
|
89
|
+
limit = 10
|
90
|
+
market = self.market(symbol)
|
91
|
+
topic = 'orderbook.' + market['id'] + '.10.' + self.number_to_string(limit)
|
92
|
+
request: dict = {
|
93
|
+
'method': 'subscribe',
|
94
|
+
'params': {
|
95
|
+
'channels': [
|
96
|
+
topic,
|
97
|
+
],
|
98
|
+
},
|
99
|
+
}
|
100
|
+
subscription: dict = {
|
101
|
+
'name': topic,
|
102
|
+
'symbol': symbol,
|
103
|
+
'limit': limit,
|
104
|
+
'params': params,
|
105
|
+
}
|
106
|
+
orderbook = await self.watch_public(topic, request, subscription)
|
107
|
+
return orderbook.limit()
|
108
|
+
|
109
|
+
def handle_order_book(self, client: Client, message):
|
110
|
+
#
|
111
|
+
# {
|
112
|
+
# method: 'subscription',
|
113
|
+
# params: {
|
114
|
+
# channel: 'orderbook.BTC-PERP.10.1',
|
115
|
+
# data: {
|
116
|
+
# timestamp: 1738331231506,
|
117
|
+
# instrument_name: 'BTC-PERP',
|
118
|
+
# publish_id: 628419,
|
119
|
+
# bids: [['104669', '40']],
|
120
|
+
# asks: [['104736', '40']]
|
121
|
+
# }
|
122
|
+
# }
|
123
|
+
# }
|
124
|
+
#
|
125
|
+
params = self.safe_dict(message, 'params')
|
126
|
+
data = self.safe_dict(params, 'data')
|
127
|
+
marketId = self.safe_string(data, 'instrument_name')
|
128
|
+
market = self.safe_market(marketId)
|
129
|
+
symbol = market['symbol']
|
130
|
+
topic = self.safe_string(params, 'channel')
|
131
|
+
if not (symbol in self.orderbooks):
|
132
|
+
defaultLimit = self.safe_integer(self.options, 'watchOrderBookLimit', 1000)
|
133
|
+
subscription = client.subscriptions[topic]
|
134
|
+
limit = self.safe_integer(subscription, 'limit', defaultLimit)
|
135
|
+
self.orderbooks[symbol] = self.order_book({}, limit)
|
136
|
+
orderbook = self.orderbooks[symbol]
|
137
|
+
timestamp = self.safe_integer(data, 'timestamp')
|
138
|
+
snapshot = self.parse_order_book(data, symbol, timestamp, 'bids', 'asks')
|
139
|
+
orderbook.reset(snapshot)
|
140
|
+
client.resolve(orderbook, topic)
|
141
|
+
|
142
|
+
async def watch_ticker(self, symbol: str, params={}) -> Ticker:
|
143
|
+
"""
|
144
|
+
|
145
|
+
https://docs.derive.xyz/reference/ticker-instrument_name-interval
|
146
|
+
|
147
|
+
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
148
|
+
:param str symbol: unified symbol of the market to fetch the ticker for
|
149
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
150
|
+
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
151
|
+
"""
|
152
|
+
await self.load_markets()
|
153
|
+
market = self.market(symbol)
|
154
|
+
topic = 'ticker.' + market['id'] + '.100'
|
155
|
+
request: dict = {
|
156
|
+
'method': 'subscribe',
|
157
|
+
'params': {
|
158
|
+
'channels': [
|
159
|
+
topic,
|
160
|
+
],
|
161
|
+
},
|
162
|
+
}
|
163
|
+
subscription: dict = {
|
164
|
+
'name': topic,
|
165
|
+
'symbol': symbol,
|
166
|
+
'params': params,
|
167
|
+
}
|
168
|
+
return await self.watch_public(topic, request, subscription)
|
169
|
+
|
170
|
+
def handle_ticker(self, client: Client, message):
|
171
|
+
#
|
172
|
+
# {
|
173
|
+
# method: 'subscription',
|
174
|
+
# params: {
|
175
|
+
# channel: 'ticker.BTC-PERP.100',
|
176
|
+
# data: {
|
177
|
+
# timestamp: 1738485104439,
|
178
|
+
# instrument_ticker: {
|
179
|
+
# instrument_type: 'perp',
|
180
|
+
# instrument_name: 'BTC-PERP',
|
181
|
+
# scheduled_activation: 1701840228,
|
182
|
+
# scheduled_deactivation: '9223372036854775807',
|
183
|
+
# is_active: True,
|
184
|
+
# tick_size: '0.1',
|
185
|
+
# minimum_amount: '0.01',
|
186
|
+
# maximum_amount: '10000',
|
187
|
+
# amount_step: '0.001',
|
188
|
+
# mark_price_fee_rate_cap: '0',
|
189
|
+
# maker_fee_rate: '0.0001',
|
190
|
+
# taker_fee_rate: '0.0003',
|
191
|
+
# base_fee: '0.1',
|
192
|
+
# base_currency: 'BTC',
|
193
|
+
# quote_currency: 'USD',
|
194
|
+
# option_details: null,
|
195
|
+
# perp_details: {
|
196
|
+
# index: 'BTC-USD',
|
197
|
+
# max_rate_per_hour: '0.004',
|
198
|
+
# min_rate_per_hour: '-0.004',
|
199
|
+
# static_interest_rate: '0.0000125',
|
200
|
+
# aggregate_funding: '10581.779418721074588722',
|
201
|
+
# funding_rate: '0.000024792239208858'
|
202
|
+
# },
|
203
|
+
# erc20_details: null,
|
204
|
+
# base_asset_address: '0xDBa83C0C654DB1cd914FA2710bA743e925B53086',
|
205
|
+
# base_asset_sub_id: '0',
|
206
|
+
# pro_rata_fraction: '0',
|
207
|
+
# fifo_min_allocation: '0',
|
208
|
+
# pro_rata_amount_step: '0.1',
|
209
|
+
# best_ask_amount: '0.131',
|
210
|
+
# best_ask_price: '99898.6',
|
211
|
+
# best_bid_amount: '0.056',
|
212
|
+
# best_bid_price: '99889.1',
|
213
|
+
# five_percent_bid_depth: '11.817',
|
214
|
+
# five_percent_ask_depth: '9.116',
|
215
|
+
# option_pricing: null,
|
216
|
+
# index_price: '99883.8',
|
217
|
+
# mark_price: '99897.52408421244763303548098',
|
218
|
+
# stats: {
|
219
|
+
# contract_volume: '92.395',
|
220
|
+
# num_trades: '2924',
|
221
|
+
# open_interest: '33.743468027373780786',
|
222
|
+
# high: '102320.4',
|
223
|
+
# low: '99064.3',
|
224
|
+
# percent_change: '-0.021356',
|
225
|
+
# usd_change: '-2178'
|
226
|
+
# },
|
227
|
+
# timestamp: 1738485165881,
|
228
|
+
# min_price: '97939.1',
|
229
|
+
# max_price: '101895.2'
|
230
|
+
# }
|
231
|
+
# }
|
232
|
+
# }
|
233
|
+
# }
|
234
|
+
#
|
235
|
+
params = self.safe_dict(message, 'params')
|
236
|
+
rawData = self.safe_dict(params, 'data')
|
237
|
+
data = self.safe_dict(rawData, 'instrument_ticker')
|
238
|
+
topic = self.safe_value(params, 'channel')
|
239
|
+
ticker = self.parse_ticker(data)
|
240
|
+
self.tickers[ticker['symbol']] = ticker
|
241
|
+
client.resolve(ticker, topic)
|
242
|
+
return message
|
243
|
+
|
244
|
+
async def un_watch_order_book(self, symbol: str, params={}) -> Any:
|
245
|
+
"""
|
246
|
+
unsubscribe from the orderbook channel
|
247
|
+
:param str symbol: unified symbol of the market to fetch the order book for
|
248
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
249
|
+
:param int [params.limit]: orderbook limit, default is None
|
250
|
+
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
251
|
+
"""
|
252
|
+
await self.load_markets()
|
253
|
+
limit = self.safe_integer(params, 'limit')
|
254
|
+
if limit is None:
|
255
|
+
limit = 10
|
256
|
+
market = self.market(symbol)
|
257
|
+
topic = 'orderbook.' + market['id'] + '.10.' + self.number_to_string(limit)
|
258
|
+
messageHash = 'unwatch' + topic
|
259
|
+
request: dict = {
|
260
|
+
'method': 'unsubscribe',
|
261
|
+
'params': {
|
262
|
+
'channels': [
|
263
|
+
topic,
|
264
|
+
],
|
265
|
+
},
|
266
|
+
}
|
267
|
+
subscription: dict = {
|
268
|
+
'name': topic,
|
269
|
+
}
|
270
|
+
return await self.un_watch_public(messageHash, request, subscription)
|
271
|
+
|
272
|
+
async def un_watch_trades(self, symbol: str, params={}) -> Any:
|
273
|
+
"""
|
274
|
+
unsubscribe from the trades channel
|
275
|
+
:param str symbol: unified symbol of the market to unwatch the trades for
|
276
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
277
|
+
:returns any: status of the unwatch request
|
278
|
+
"""
|
279
|
+
await self.load_markets()
|
280
|
+
market = self.market(symbol)
|
281
|
+
topic = 'trades.' + market['id']
|
282
|
+
messageHah = 'unwatch' + topic
|
283
|
+
request: dict = {
|
284
|
+
'method': 'unsubscribe',
|
285
|
+
'params': {
|
286
|
+
'channels': [
|
287
|
+
topic,
|
288
|
+
],
|
289
|
+
},
|
290
|
+
}
|
291
|
+
subscription: dict = {
|
292
|
+
'name': topic,
|
293
|
+
}
|
294
|
+
return await self.un_watch_public(messageHah, request, subscription)
|
295
|
+
|
296
|
+
async def un_watch_public(self, messageHash, message, subscription):
|
297
|
+
url = self.urls['api']['ws']
|
298
|
+
requestId = self.request_id(url)
|
299
|
+
request = self.extend(message, {
|
300
|
+
'id': requestId,
|
301
|
+
})
|
302
|
+
subscription = self.extend(subscription, {
|
303
|
+
'id': requestId,
|
304
|
+
'method': 'unsubscribe',
|
305
|
+
})
|
306
|
+
return await self.watch(url, messageHash, request, messageHash, subscription)
|
307
|
+
|
308
|
+
def handle_order_book_un_subscription(self, client: Client, topic):
|
309
|
+
parsedTopic = topic.split('.')
|
310
|
+
marketId = self.safe_string(parsedTopic, 1)
|
311
|
+
market = self.safe_market(marketId)
|
312
|
+
symbol = market['symbol']
|
313
|
+
if symbol in self.orderbooks:
|
314
|
+
del self.orderbooks[symbol]
|
315
|
+
if topic in client.subscriptions:
|
316
|
+
del client.subscriptions[topic]
|
317
|
+
error = UnsubscribeError(self.id + ' orderbook ' + symbol)
|
318
|
+
client.reject(error, topic)
|
319
|
+
client.resolve(error, 'unwatch' + topic)
|
320
|
+
|
321
|
+
def handle_trades_un_subscription(self, client: Client, topic):
|
322
|
+
parsedTopic = topic.split('.')
|
323
|
+
marketId = self.safe_string(parsedTopic, 1)
|
324
|
+
market = self.safe_market(marketId)
|
325
|
+
symbol = market['symbol']
|
326
|
+
if symbol in self.orderbooks:
|
327
|
+
del self.trades[symbol]
|
328
|
+
if topic in client.subscriptions:
|
329
|
+
del client.subscriptions[topic]
|
330
|
+
error = UnsubscribeError(self.id + ' trades ' + symbol)
|
331
|
+
client.reject(error, topic)
|
332
|
+
client.resolve(error, 'unwatch' + topic)
|
333
|
+
|
334
|
+
def handle_un_subscribe(self, client: Client, message):
|
335
|
+
#
|
336
|
+
# {
|
337
|
+
# id: 1,
|
338
|
+
# result: {
|
339
|
+
# status: {'orderbook.BTC-PERP.10.10': 'ok'},
|
340
|
+
# remaining_subscriptions: []
|
341
|
+
# }
|
342
|
+
# }
|
343
|
+
#
|
344
|
+
result = self.safe_dict(message, 'result')
|
345
|
+
status = self.safe_dict(result, 'status')
|
346
|
+
if status is not None:
|
347
|
+
topics = list(status.keys())
|
348
|
+
for i in range(0, len(topics)):
|
349
|
+
topic = topics[i]
|
350
|
+
if topic.find('orderbook') >= 0:
|
351
|
+
self.handle_order_book_un_subscription(client, topic)
|
352
|
+
elif topic.find('trades') >= 0:
|
353
|
+
self.handle_trades_un_subscription(client, topic)
|
354
|
+
return message
|
355
|
+
|
356
|
+
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
357
|
+
"""
|
358
|
+
watches information on multiple trades made in a market
|
359
|
+
|
360
|
+
https://docs.derive.xyz/reference/trades-instrument_name
|
361
|
+
|
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 <https://docs.ccxt.com/#/?id=trade-structure>`
|
367
|
+
"""
|
368
|
+
await self.load_markets()
|
369
|
+
market = self.market(symbol)
|
370
|
+
topic = 'trades.' + market['id']
|
371
|
+
request: dict = {
|
372
|
+
'method': 'subscribe',
|
373
|
+
'params': {
|
374
|
+
'channels': [
|
375
|
+
topic,
|
376
|
+
],
|
377
|
+
},
|
378
|
+
}
|
379
|
+
subscription: dict = {
|
380
|
+
'name': topic,
|
381
|
+
'symbol': symbol,
|
382
|
+
'params': params,
|
383
|
+
}
|
384
|
+
trades = await self.watch_public(topic, request, subscription)
|
385
|
+
if self.newUpdates:
|
386
|
+
limit = trades.getLimit(market['symbol'], limit)
|
387
|
+
return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)
|
388
|
+
|
389
|
+
def handle_trade(self, client: Client, message):
|
390
|
+
#
|
391
|
+
#
|
392
|
+
params = self.safe_dict(message, 'params')
|
393
|
+
data = self.safe_dict(params, 'data')
|
394
|
+
topic = self.safe_value(params, 'channel')
|
395
|
+
parsedTopic = topic.split('.')
|
396
|
+
marketId = self.safe_string(parsedTopic, 1)
|
397
|
+
market = self.safe_market(marketId)
|
398
|
+
symbol = market['symbol']
|
399
|
+
tradesArray = self.safe_value(self.trades, symbol)
|
400
|
+
if tradesArray is None:
|
401
|
+
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
|
402
|
+
tradesArray = ArrayCache(limit)
|
403
|
+
for i in range(0, len(data)):
|
404
|
+
trade = self.parse_trade(data[i])
|
405
|
+
tradesArray.append(trade)
|
406
|
+
self.trades[symbol] = tradesArray
|
407
|
+
client.resolve(tradesArray, topic)
|
408
|
+
|
409
|
+
async def authenticate(self, params={}):
|
410
|
+
self.check_required_credentials()
|
411
|
+
url = self.urls['api']['ws']
|
412
|
+
client = self.client(url)
|
413
|
+
messageHash = 'authenticated'
|
414
|
+
future = client.future(messageHash)
|
415
|
+
authenticated = self.safe_value(client.subscriptions, messageHash)
|
416
|
+
if authenticated is None:
|
417
|
+
requestId = self.request_id(url)
|
418
|
+
now = str(self.milliseconds())
|
419
|
+
signature = self.signMessage(now, self.privateKey)
|
420
|
+
contractWalletAddress = self.safe_string(self.options, 'contractWalletAddress')
|
421
|
+
request: dict = {
|
422
|
+
'id': requestId,
|
423
|
+
'method': 'public/login',
|
424
|
+
'params': {
|
425
|
+
'wallet': contractWalletAddress,
|
426
|
+
'timestamp': now,
|
427
|
+
'signature': signature,
|
428
|
+
},
|
429
|
+
}
|
430
|
+
# subscription: Dict = {
|
431
|
+
# 'name': topic,
|
432
|
+
# 'symbol': symbol,
|
433
|
+
# 'params': params,
|
434
|
+
# }
|
435
|
+
message = self.extend(request, params)
|
436
|
+
self.watch(url, messageHash, message, messageHash, message)
|
437
|
+
return await future
|
438
|
+
|
439
|
+
async def watch_private(self, messageHash, message, subscription):
|
440
|
+
await self.authenticate()
|
441
|
+
url = self.urls['api']['ws']
|
442
|
+
requestId = self.request_id(url)
|
443
|
+
request = self.extend(message, {
|
444
|
+
'id': requestId,
|
445
|
+
})
|
446
|
+
subscription = self.extend(subscription, {
|
447
|
+
'id': requestId,
|
448
|
+
'method': 'subscribe',
|
449
|
+
})
|
450
|
+
return await self.watch(url, messageHash, request, messageHash, subscription)
|
451
|
+
|
452
|
+
async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
453
|
+
"""
|
454
|
+
|
455
|
+
https://docs.derive.xyz/reference/subaccount_id-orders
|
456
|
+
|
457
|
+
watches information on multiple orders made by the user
|
458
|
+
:param str symbol: unified market symbol of the market orders were made in
|
459
|
+
:param int [since]: the earliest time in ms to fetch orders for
|
460
|
+
:param int [limit]: the maximum number of order structures to retrieve
|
461
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
462
|
+
:param str [params.subaccount_id]: *required* the subaccount id
|
463
|
+
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
464
|
+
"""
|
465
|
+
await self.load_markets()
|
466
|
+
subaccountId = None
|
467
|
+
subaccountId, params = self.handleDeriveSubaccountId('watchOrders', params)
|
468
|
+
topic = self.number_to_string(subaccountId) + '.orders'
|
469
|
+
messageHash = topic
|
470
|
+
if symbol is not None:
|
471
|
+
market = self.market(symbol)
|
472
|
+
symbol = market['symbol']
|
473
|
+
messageHash += ':' + symbol
|
474
|
+
request: dict = {
|
475
|
+
'method': 'subscribe',
|
476
|
+
'params': {
|
477
|
+
'channels': [
|
478
|
+
topic,
|
479
|
+
],
|
480
|
+
},
|
481
|
+
}
|
482
|
+
subscription: dict = {
|
483
|
+
'name': topic,
|
484
|
+
'params': params,
|
485
|
+
}
|
486
|
+
message = self.extend(request, params)
|
487
|
+
orders = await self.watch_private(messageHash, message, subscription)
|
488
|
+
if self.newUpdates:
|
489
|
+
limit = orders.getLimit(symbol, limit)
|
490
|
+
return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
|
491
|
+
|
492
|
+
def handle_order(self, client: Client, message):
|
493
|
+
#
|
494
|
+
# {
|
495
|
+
# method: 'subscription',
|
496
|
+
# params: {
|
497
|
+
# channel: '130837.orders',
|
498
|
+
# data: [
|
499
|
+
# {
|
500
|
+
# subaccount_id: 130837,
|
501
|
+
# order_id: '1f44c564-5658-4b69-b8c4-4019924207d5',
|
502
|
+
# instrument_name: 'BTC-PERP',
|
503
|
+
# direction: 'buy',
|
504
|
+
# label: 'test1234',
|
505
|
+
# quote_id: null,
|
506
|
+
# creation_timestamp: 1738578974146,
|
507
|
+
# last_update_timestamp: 1738578974146,
|
508
|
+
# limit_price: '10000',
|
509
|
+
# amount: '0.01',
|
510
|
+
# filled_amount: '0',
|
511
|
+
# average_price: '0',
|
512
|
+
# order_fee: '0',
|
513
|
+
# order_type: 'limit',
|
514
|
+
# time_in_force: 'post_only',
|
515
|
+
# order_status: 'untriggered',
|
516
|
+
# max_fee: '219',
|
517
|
+
# signature_expiry_sec: 1746354973,
|
518
|
+
# nonce: 1738578973570,
|
519
|
+
# signer: '0x30CB7B06AdD6749BbE146A6827502B8f2a79269A',
|
520
|
+
# signature: '0xc6927095f74a0d3b1aeef8c0579d120056530479f806e9d2e6616df742a8934c69046361beae833b32b25c0145e318438d7d1624bb835add956f63aa37192f571c',
|
521
|
+
# cancel_reason: '',
|
522
|
+
# mmp: False,
|
523
|
+
# is_transfer: False,
|
524
|
+
# replaced_order_id: null,
|
525
|
+
# trigger_type: 'stoploss',
|
526
|
+
# trigger_price_type: 'mark',
|
527
|
+
# trigger_price: '102800',
|
528
|
+
# trigger_reject_message: null
|
529
|
+
# }
|
530
|
+
# ]
|
531
|
+
# }
|
532
|
+
# }
|
533
|
+
#
|
534
|
+
params = self.safe_dict(message, 'params')
|
535
|
+
topic = self.safe_string(params, 'channel')
|
536
|
+
rawOrders = self.safe_list(params, 'data')
|
537
|
+
for i in range(0, len(rawOrders)):
|
538
|
+
data = rawOrders[i]
|
539
|
+
parsed = self.parse_order(data)
|
540
|
+
symbol = self.safe_string(parsed, 'symbol')
|
541
|
+
orderId = self.safe_string(parsed, 'id')
|
542
|
+
if symbol is not None:
|
543
|
+
if self.orders is None:
|
544
|
+
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
|
545
|
+
self.orders = ArrayCacheBySymbolById(limit)
|
546
|
+
cachedOrders = self.orders
|
547
|
+
orders = self.safe_value(cachedOrders.hashmap, symbol, {})
|
548
|
+
order = self.safe_value(orders, orderId)
|
549
|
+
if order is not None:
|
550
|
+
fee = self.safe_value(order, 'fee')
|
551
|
+
if fee is not None:
|
552
|
+
parsed['fee'] = fee
|
553
|
+
fees = self.safe_value(order, 'fees')
|
554
|
+
if fees is not None:
|
555
|
+
parsed['fees'] = fees
|
556
|
+
parsed['trades'] = self.safe_value(order, 'trades')
|
557
|
+
parsed['timestamp'] = self.safe_integer(order, 'timestamp')
|
558
|
+
parsed['datetime'] = self.safe_string(order, 'datetime')
|
559
|
+
cachedOrders.append(parsed)
|
560
|
+
messageHashSymbol = topic + ':' + symbol
|
561
|
+
client.resolve(self.orders, messageHashSymbol)
|
562
|
+
client.resolve(self.orders, topic)
|
563
|
+
|
564
|
+
async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
565
|
+
"""
|
566
|
+
|
567
|
+
https://docs.derive.xyz/reference/subaccount_id-trades
|
568
|
+
|
569
|
+
watches information on multiple trades made by the user
|
570
|
+
:param str symbol: unified market symbol of the market orders were made in
|
571
|
+
:param int [since]: the earliest time in ms to fetch orders for
|
572
|
+
:param int [limit]: the maximum number of order structures to retrieve
|
573
|
+
:param dict [params]: extra parameters specific to the exchange API endpoint
|
574
|
+
:param str [params.subaccount_id]: *required* the subaccount id
|
575
|
+
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
576
|
+
"""
|
577
|
+
await self.load_markets()
|
578
|
+
subaccountId = None
|
579
|
+
subaccountId, params = self.handleDeriveSubaccountId('watchMyTrades', params)
|
580
|
+
topic = self.number_to_string(subaccountId) + '.trades'
|
581
|
+
messageHash = topic
|
582
|
+
if symbol is not None:
|
583
|
+
market = self.market(symbol)
|
584
|
+
symbol = market['symbol']
|
585
|
+
messageHash += ':' + symbol
|
586
|
+
request: dict = {
|
587
|
+
'method': 'subscribe',
|
588
|
+
'params': {
|
589
|
+
'channels': [
|
590
|
+
topic,
|
591
|
+
],
|
592
|
+
},
|
593
|
+
}
|
594
|
+
subscription: dict = {
|
595
|
+
'name': topic,
|
596
|
+
'params': params,
|
597
|
+
}
|
598
|
+
message = self.extend(request, params)
|
599
|
+
trades = await self.watch_private(messageHash, message, subscription)
|
600
|
+
if self.newUpdates:
|
601
|
+
limit = trades.getLimit(symbol, limit)
|
602
|
+
return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)
|
603
|
+
|
604
|
+
def handle_my_trade(self, client: Client, message):
|
605
|
+
#
|
606
|
+
#
|
607
|
+
myTrades = self.myTrades
|
608
|
+
if myTrades is None:
|
609
|
+
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
|
610
|
+
myTrades = ArrayCacheBySymbolById(limit)
|
611
|
+
params = self.safe_dict(message, 'params')
|
612
|
+
topic = self.safe_string(params, 'channel')
|
613
|
+
rawTrades = self.safe_list(params, 'data')
|
614
|
+
for i in range(0, len(rawTrades)):
|
615
|
+
trade = self.parse_trade(message)
|
616
|
+
myTrades.append(trade)
|
617
|
+
client.resolve(myTrades, topic)
|
618
|
+
messageHash = topic + trade['symbol']
|
619
|
+
client.resolve(myTrades, messageHash)
|
620
|
+
|
621
|
+
def handle_error_message(self, client: Client, message):
|
622
|
+
#
|
623
|
+
# {
|
624
|
+
# id: '690c6276-0fc6-4121-aafa-f28bf5adedcb',
|
625
|
+
# error: {code: -32600, message: 'Invalid Request'}
|
626
|
+
# }
|
627
|
+
#
|
628
|
+
if not ('error' in message):
|
629
|
+
return False
|
630
|
+
errorMessage = self.safe_dict(message, 'error')
|
631
|
+
errorCode = self.safe_string(errorMessage, 'code')
|
632
|
+
try:
|
633
|
+
if errorCode is not None:
|
634
|
+
feedback = self.id + ' ' + self.json(message)
|
635
|
+
self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
|
636
|
+
raise ExchangeError(feedback)
|
637
|
+
return False
|
638
|
+
except Exception as error:
|
639
|
+
if isinstance(error, AuthenticationError):
|
640
|
+
messageHash = 'authenticated'
|
641
|
+
client.reject(error, messageHash)
|
642
|
+
if messageHash in client.subscriptions:
|
643
|
+
del client.subscriptions[messageHash]
|
644
|
+
else:
|
645
|
+
client.reject(error)
|
646
|
+
return True
|
647
|
+
|
648
|
+
def handle_message(self, client: Client, message):
|
649
|
+
if self.handle_error_message(client, message):
|
650
|
+
return
|
651
|
+
methods: dict = {
|
652
|
+
'orderbook': self.handle_order_book,
|
653
|
+
'ticker': self.handle_ticker,
|
654
|
+
'trades': self.handle_trade,
|
655
|
+
'orders': self.handle_order,
|
656
|
+
'mytrades': self.handle_my_trade,
|
657
|
+
}
|
658
|
+
event = None
|
659
|
+
params = self.safe_dict(message, 'params')
|
660
|
+
if params is not None:
|
661
|
+
channel = self.safe_string(params, 'channel')
|
662
|
+
if channel is not None:
|
663
|
+
parsedChannel = channel.split('.')
|
664
|
+
if (channel.find('orders') >= 0) or channel.find('trades') > 0:
|
665
|
+
event = self.safe_string(parsedChannel, 1)
|
666
|
+
# {subaccounr_id}.trades
|
667
|
+
if event == 'trades':
|
668
|
+
event = 'mytrades'
|
669
|
+
else:
|
670
|
+
event = self.safe_string(parsedChannel, 0)
|
671
|
+
method = self.safe_value(methods, event)
|
672
|
+
if method is not None:
|
673
|
+
method(client, message)
|
674
|
+
return
|
675
|
+
if 'id' in message:
|
676
|
+
id = self.safe_string(message, 'id')
|
677
|
+
subscriptionsById = self.index_by(client.subscriptions, 'id')
|
678
|
+
subscription = self.safe_value(subscriptionsById, id, {})
|
679
|
+
if 'method' in subscription:
|
680
|
+
if subscription['method'] == 'public/login':
|
681
|
+
self.handle_auth(client, message)
|
682
|
+
elif subscription['method'] == 'unsubscribe':
|
683
|
+
self.handle_un_subscribe(client, message)
|
684
|
+
# could handleSubscribe
|
685
|
+
|
686
|
+
def handle_auth(self, client: Client, message):
|
687
|
+
#
|
688
|
+
# {
|
689
|
+
# id: 1,
|
690
|
+
# result: [130837]
|
691
|
+
# }
|
692
|
+
#
|
693
|
+
messageHash = 'authenticated'
|
694
|
+
ids = self.safe_list(message, 'result')
|
695
|
+
if len(ids) > 0:
|
696
|
+
# client.resolve(message, messageHash)
|
697
|
+
future = self.safe_value(client.futures, 'authenticated')
|
698
|
+
future.resolve(True)
|
699
|
+
else:
|
700
|
+
error = AuthenticationError(self.json(message))
|
701
|
+
client.reject(error, messageHash)
|
702
|
+
# allows further authentication attempts
|
703
|
+
if messageHash in client.subscriptions:
|
704
|
+
del client.subscriptions['authenticated']
|