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