ccxt 4.3.44__py2.py3-none-any.whl → 4.3.45__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/pro/oxfun.py ADDED
@@ -0,0 +1,950 @@
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
+ import hashlib
9
+ from ccxt.base.types import Balances, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, Trade
10
+ from ccxt.async_support.base.ws.client import Client
11
+ from typing import List
12
+ from ccxt.base.errors import AuthenticationError
13
+ from ccxt.base.errors import ArgumentsRequired
14
+ from ccxt.base.errors import BadRequest
15
+
16
+
17
+ class oxfun(ccxt.async_support.oxfun):
18
+
19
+ def describe(self):
20
+ return self.deep_extend(super(oxfun, self).describe(), {
21
+ 'has': {
22
+ 'ws': True,
23
+ 'watchTrades': True,
24
+ 'watchTradesForSymbols': True,
25
+ 'watchOrderBook': True,
26
+ 'watchOrderBookForSymbols': True,
27
+ 'watchOHLCV': True,
28
+ 'watchOHLCVForSymbols': True,
29
+ 'watchOrders': True,
30
+ 'watchMyTrades': False,
31
+ 'watchTicker': True,
32
+ 'watchTickers': True,
33
+ 'watchBalance': True,
34
+ 'createOrderWs': True,
35
+ 'editOrderWs': True,
36
+ 'cancelOrderWs': True,
37
+ 'cancelOrdersWs': True,
38
+ },
39
+ 'urls': {
40
+ 'api': {
41
+ 'ws': 'wss://api.ox.fun/v2/websocket',
42
+ 'test': 'wss://stgapi.ox.fun/v2/websocket',
43
+ },
44
+ },
45
+ 'options': {
46
+ 'timeframes': {
47
+ '1m': '60s',
48
+ '3m': '180s',
49
+ '5m': '300s',
50
+ '15m': '900s',
51
+ '30m': '1800s',
52
+ '1h': '3600s',
53
+ '2h': '7200s',
54
+ '4h': '14400s',
55
+ '6h': '21600s',
56
+ '12h': '43200s',
57
+ '1d': '86400s',
58
+ },
59
+ 'watchOrderBook': {
60
+ 'channel': 'depth', # depth, depthL5, depthL10, depthL25
61
+ },
62
+ },
63
+ 'streaming': {
64
+ 'ping': self.ping,
65
+ 'keepAlive': 50000,
66
+ },
67
+ })
68
+
69
+ async def subscribe_multiple(self, messageHashes, argsArray, params={}):
70
+ url = self.urls['api']['ws']
71
+ request: dict = {
72
+ 'op': 'subscribe',
73
+ 'args': argsArray,
74
+ }
75
+ return await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes)
76
+
77
+ async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
78
+ """
79
+ watches information on multiple trades made in a market
80
+ :see: https://docs.ox.fun/?json#trade
81
+ :param str symbol: unified market symbol of the market trades were made in
82
+ :param int [since]: the earliest time in ms to fetch orders for
83
+ :param int [limit]: the maximum number of trade structures to retrieve
84
+ :param dict [params]: extra parameters specific to the exchange API endpoint
85
+ :param int|str [params.tag]: If given it will be echoed in the reply and the max size of tag is 32
86
+ :returns dict[]: a list of [trade structures]{@link https://docs.ccxt.com/#/?id=trade-structure
87
+ """
88
+ return await self.watch_trades_for_symbols([symbol], since, limit, params)
89
+
90
+ async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
91
+ """
92
+ get the list of most recent trades for a particular symbol
93
+ :see: https://docs.ox.fun/?json#trade
94
+ :param str symbol: unified symbol of the market to fetch trades for
95
+ :param int [since]: timestamp in ms of the earliest trade to fetch
96
+ :param int [limit]: the maximum amount of trades to fetch
97
+ :param dict [params]: extra parameters specific to the exchange API endpoint
98
+ :param int|str [params.tag]: If given it will be echoed in the reply and the max size of tag is 32
99
+ :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
100
+ """
101
+ await self.load_markets()
102
+ symbols = self.market_symbols(symbols, None, False)
103
+ args = []
104
+ messageHashes = []
105
+ for i in range(0, len(symbols)):
106
+ symbol = symbols[i]
107
+ messageHash = 'trades' + ':' + symbol
108
+ messageHashes.append(messageHash)
109
+ marketId = self.market_id(symbol)
110
+ arg = 'trade:' + marketId
111
+ args.append(arg)
112
+ trades = await self.subscribe_multiple(messageHashes, args, params)
113
+ if self.newUpdates:
114
+ first = self.safe_dict(trades, 0, {})
115
+ tradeSymbol = self.safe_string(first, 'symbol')
116
+ limit = trades.getLimit(tradeSymbol, limit)
117
+ return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
118
+
119
+ def handle_trades(self, client: Client, message):
120
+ #
121
+ # {
122
+ # table: 'trade',
123
+ # data: [
124
+ # {
125
+ # side: 'SELL',
126
+ # quantity: '0.074',
127
+ # matchType: 'TAKER',
128
+ # price: '3079.5',
129
+ # marketCode: 'ETH-USD-SWAP-LIN',
130
+ # tradeId: '400017157974517783',
131
+ # timestamp: '1716124156643'
132
+ # }
133
+ # ]
134
+ # }
135
+ #
136
+ data = self.safe_list(message, 'data', [])
137
+ for i in range(0, len(data)):
138
+ trade = self.safe_dict(data, i, {})
139
+ parsedTrade = self.parse_ws_trade(trade)
140
+ symbol = self.safe_string(parsedTrade, 'symbol')
141
+ messageHash = 'trades:' + symbol
142
+ if not (symbol in self.trades):
143
+ tradesLimit = self.safe_integer(self.options, 'tradesLimit', 1000)
144
+ self.trades[symbol] = ArrayCache(tradesLimit)
145
+ stored = self.trades[symbol]
146
+ stored.append(parsedTrade)
147
+ client.resolve(stored, messageHash)
148
+
149
+ def parse_ws_trade(self, trade, market=None) -> Trade:
150
+ #
151
+ # {
152
+ # side: 'SELL',
153
+ # quantity: '0.074',
154
+ # matchType: 'TAKER',
155
+ # price: '3079.5',
156
+ # marketCode: 'ETH-USD-SWAP-LIN',
157
+ # tradeId: '400017157974517783',
158
+ # timestamp: '1716124156643'
159
+ # }
160
+ #
161
+ marketId = self.safe_string(trade, 'marketCode')
162
+ market = self.safe_market(marketId, market)
163
+ timestamp = self.safe_integer(trade, 'timestamp')
164
+ return self.safe_trade({
165
+ 'info': trade,
166
+ 'timestamp': timestamp,
167
+ 'datetime': self.iso8601(timestamp),
168
+ 'symbol': market['symbol'],
169
+ 'id': self.safe_string(trade, 'tradeId'),
170
+ 'order': None,
171
+ 'type': None,
172
+ 'takerOrMaker': self.safe_string_lower(trade, 'matchType'),
173
+ 'side': self.safe_string_lower(trade, 'side'),
174
+ 'price': self.safe_number(trade, 'price'),
175
+ 'amount': self.safe_number(trade, 'quantity'),
176
+ 'cost': None,
177
+ 'fee': None,
178
+ })
179
+
180
+ async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
181
+ """
182
+ watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
183
+ :see: https://docs.ox.fun/?json#candles
184
+ :param str symbol: unified symbol of the market to fetch OHLCV data for
185
+ :param str timeframe: the length of time each candle represents
186
+ :param int [since]: timestamp in ms of the earliest candle to fetch
187
+ :param int [limit]: the maximum amount of candles to fetch
188
+ :param dict [params]: extra parameters specific to the exchange API endpoint
189
+ :param int|str [params.tag]: If given it will be echoed in the reply and the max size of tag is 32
190
+ :returns int[][]: A list of candles ordered, open, high, low, close, volume
191
+ """
192
+ await self.load_markets()
193
+ market = self.market(symbol)
194
+ timeframes = self.safe_dict(self.options, 'timeframes', {})
195
+ interval = self.safe_string(timeframes, timeframe, timeframe)
196
+ args = 'candles' + interval + ':' + market['id']
197
+ messageHash = 'ohlcv:' + symbol + ':' + timeframe
198
+ url = self.urls['api']['ws']
199
+ request: dict = {
200
+ 'op': 'subscribe',
201
+ 'args': [args],
202
+ }
203
+ ohlcvs = await self.watch(url, messageHash, self.extend(request, params), messageHash)
204
+ if self.newUpdates:
205
+ limit = ohlcvs.getLimit(symbol, limit)
206
+ return self.filter_by_since_limit(ohlcvs, since, limit, 0, True)
207
+
208
+ async def watch_ohlcv_for_symbols(self, symbolsAndTimeframes: List[List[str]], since: Int = None, limit: Int = None, params={}):
209
+ """
210
+ watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
211
+ :see: https://docs.ox.fun/?json#candles
212
+ :param str[][] symbolsAndTimeframes: array of arrays containing unified symbols and timeframes to fetch OHLCV data for, example [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]
213
+ :param int [since]: timestamp in ms of the earliest candle to fetch
214
+ :param int [limit]: the maximum amount of candles to fetch
215
+ :param dict [params]: extra parameters specific to the exchange API endpoint
216
+ :param int|str [params.tag]: If given it will be echoed in the reply and the max size of tag is 32
217
+ :returns int[][]: A list of candles ordered, open, high, low, close, volume
218
+ """
219
+ symbolsLength = len(symbolsAndTimeframes)
220
+ if symbolsLength == 0 or not isinstance(symbolsAndTimeframes[0], list):
221
+ raise ArgumentsRequired(self.id + " watchOHLCVForSymbols() requires a an array of symbols and timeframes, like [['BTC/USDT:OX', '1m'], ['OX/USDT', '5m']]")
222
+ await self.load_markets()
223
+ args = []
224
+ messageHashes = []
225
+ timeframes = self.safe_dict(self.options, 'timeframes', {})
226
+ for i in range(0, len(symbolsAndTimeframes)):
227
+ symbolAndTimeframe = symbolsAndTimeframes[i]
228
+ sym = symbolAndTimeframe[0]
229
+ tf = symbolAndTimeframe[1]
230
+ marketId = self.market_id(sym)
231
+ interval = self.safe_string(timeframes, tf, tf)
232
+ arg = 'candles' + interval + ':' + marketId
233
+ args.append(arg)
234
+ messageHash = 'multi:ohlcv:' + sym + ':' + tf
235
+ messageHashes.append(messageHash)
236
+ symbol, timeframe, candles = await self.subscribe_multiple(messageHashes, args, params)
237
+ if self.newUpdates:
238
+ limit = candles.getLimit(symbol, limit)
239
+ filtered = self.filter_by_since_limit(candles, since, limit, 0, True)
240
+ return self.create_ohlcv_object(symbol, timeframe, filtered)
241
+
242
+ def handle_ohlcv(self, client: Client, message):
243
+ #
244
+ # {
245
+ # "table": "candles60s",
246
+ # "data": [
247
+ # {
248
+ # "marketCode": "BTC-USD-SWAP-LIN",
249
+ # "candle": [
250
+ # "1594313762698", #timestamp
251
+ # "9633.1", #open
252
+ # "9693.9", #high
253
+ # "9238.1", #low
254
+ # "9630.2", #close
255
+ # "45247", #volume in OX
256
+ # "5.3" #volume in Contracts
257
+ # ]
258
+ # }
259
+ # ]
260
+ # }
261
+ #
262
+ table = self.safe_string(message, 'table')
263
+ parts = table.split('candles')
264
+ timeframeId = self.safe_string(parts, 1, '')
265
+ timeframe = self.find_timeframe(timeframeId)
266
+ messageData = self.safe_list(message, 'data', [])
267
+ data = self.safe_dict(messageData, 0, {})
268
+ marketId = self.safe_string(data, 'marketCode')
269
+ market = self.safe_market(marketId)
270
+ symbol = self.safe_symbol(marketId, market)
271
+ if not (symbol in self.ohlcvs):
272
+ self.ohlcvs[symbol] = {}
273
+ if not (timeframe in self.ohlcvs[symbol]):
274
+ limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
275
+ self.ohlcvs[symbol][timeframe] = ArrayCacheByTimestamp(limit)
276
+ candle = self.safe_list(data, 'candle', [])
277
+ parsed = self.parse_ws_ohlcv(candle, market)
278
+ stored = self.ohlcvs[symbol][timeframe]
279
+ stored.append(parsed)
280
+ messageHash = 'ohlcv:' + symbol + ':' + timeframe
281
+ client.resolve(stored, messageHash)
282
+ # for multiOHLCV we need special object, to other "multi"
283
+ # methods, because OHLCV response item does not contain symbol
284
+ # or timeframe, thus otherwise it would be unrecognizable
285
+ messageHashForMulti = 'multi:' + messageHash
286
+ client.resolve([symbol, timeframe, stored], messageHashForMulti)
287
+
288
+ def parse_ws_ohlcv(self, ohlcv, market: Market = None) -> list:
289
+ #
290
+ # [
291
+ # "1594313762698", #timestamp
292
+ # "9633.1", #open
293
+ # "9693.9", #high
294
+ # "9238.1", #low
295
+ # "9630.2", #close
296
+ # "45247", #volume in OX
297
+ # "5.3" #volume in Contracts
298
+ # ]
299
+ #
300
+ return [
301
+ self.safe_integer(ohlcv, 0),
302
+ self.safe_number(ohlcv, 1),
303
+ self.safe_number(ohlcv, 2),
304
+ self.safe_number(ohlcv, 3),
305
+ self.safe_number(ohlcv, 4),
306
+ self.safe_number(ohlcv, 6),
307
+ ]
308
+
309
+ async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
310
+ """
311
+ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
312
+ :see: https://docs.ox.fun/?json#fixed-size-order-book
313
+ :see: https://docs.ox.fun/?json#full-order-book
314
+ :param str symbol: unified symbol of the market to fetch the order book for
315
+ :param int [limit]: the maximum amount of order book entries to return
316
+ :param dict [params]: extra parameters specific to the exchange API endpoint
317
+ :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
318
+ """
319
+ return await self.watch_order_book_for_symbols([symbol], limit, params)
320
+
321
+ async def watch_order_book_for_symbols(self, symbols: List[str], limit: Int = None, params={}) -> OrderBook:
322
+ """
323
+ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
324
+ :see: https://docs.ox.fun/?json#fixed-size-order-book
325
+ :see: https://docs.ox.fun/?json#full-order-book
326
+ :param str[] symbols: unified array of symbols
327
+ :param int [limit]: the maximum amount of order book entries to return
328
+ :param dict [params]: extra parameters specific to the exchange API endpoint
329
+ :param int|str [params.tag]: If given it will be echoed in the reply and the max size of tag is 32
330
+ :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
331
+ """
332
+ await self.load_markets()
333
+ symbols = self.market_symbols(symbols)
334
+ channel = 'depth'
335
+ options = self.safe_dict(self.options, 'watchOrderBook', {})
336
+ defaultChannel = self.safe_string(options, 'channel')
337
+ if defaultChannel is not None:
338
+ channel = defaultChannel
339
+ elif limit is not None:
340
+ if limit <= 5:
341
+ channel = 'depthL5'
342
+ elif limit <= 10:
343
+ channel = 'depthL10'
344
+ elif limit <= 25:
345
+ channel = 'depthL25'
346
+ args = []
347
+ messageHashes = []
348
+ for i in range(0, len(symbols)):
349
+ symbol = symbols[i]
350
+ messageHash = 'orderbook:' + symbol
351
+ messageHashes.append(messageHash)
352
+ marketId = self.market_id(symbol)
353
+ arg = channel + ':' + marketId
354
+ args.append(arg)
355
+ orderbook = await self.subscribe_multiple(messageHashes, args, params)
356
+ return orderbook.limit()
357
+
358
+ def handle_order_book(self, client: Client, message):
359
+ #
360
+ # {
361
+ # "table": "depth",
362
+ # "data": {
363
+ # "seqNum": "100170478917895032",
364
+ # "asks": [
365
+ # [0.01, 100500],
366
+ # ...
367
+ # ],
368
+ # "bids": [
369
+ # [69.69696, 69],
370
+ # ...
371
+ # ],
372
+ # "checksum": 261021645,
373
+ # "marketCode": "OX-USDT",
374
+ # "timestamp": 1716204786184
375
+ # },
376
+ # "action": "partial"
377
+ # }
378
+ #
379
+ data = self.safe_dict(message, 'data', {})
380
+ marketId = self.safe_string(data, 'marketCode')
381
+ symbol = self.safe_symbol(marketId)
382
+ timestamp = self.safe_integer(data, 'timestamp')
383
+ messageHash = 'orderbook:' + symbol
384
+ if not (symbol in self.orderbooks):
385
+ self.orderbooks[symbol] = self.order_book({})
386
+ orderbook = self.orderbooks[symbol]
387
+ snapshot = self.parse_order_book(data, symbol, timestamp, 'asks', 'bids')
388
+ orderbook.reset(snapshot)
389
+ orderbook['nonce'] = self.safe_integer(data, 'seqNum')
390
+ self.orderbooks[symbol] = orderbook
391
+ client.resolve(orderbook, messageHash)
392
+
393
+ async def watch_ticker(self, symbol: str, params={}) -> Ticker:
394
+ """
395
+ :see: https://docs.ox.fun/?json#ticker
396
+ watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
397
+ :param str symbol: unified symbol of the market to fetch the ticker for
398
+ :param dict [params]: extra parameters specific to the exchange API endpoint
399
+ :param int|str [params.tag]: If given it will be echoed in the reply and the max size of tag is 32
400
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
401
+ """
402
+ ticker = await self.watch_tickers([symbol], params)
403
+ return self.safe_value(ticker, symbol)
404
+
405
+ async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
406
+ """
407
+ :see: https://docs.ox.fun/?json#ticker
408
+ watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
409
+ :param str[] [symbols]: unified symbol of the market to fetch the ticker for
410
+ :param dict [params]: extra parameters specific to the exchange API endpoint
411
+ :param int|str [params.tag]: If given it will be echoed in the reply and the max size of tag is 32
412
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
413
+ """
414
+ await self.load_markets()
415
+ allSymbols = (symbols is None)
416
+ sym = symbols
417
+ args = []
418
+ if allSymbols:
419
+ sym = self.symbols
420
+ args.append('ticker:all')
421
+ messageHashes = []
422
+ for i in range(0, len(sym)):
423
+ symbol = sym[i]
424
+ messageHash = 'tickers' + ':' + symbol
425
+ messageHashes.append(messageHash)
426
+ marketId = self.market_id(symbol)
427
+ if not allSymbols:
428
+ args.append('ticker:' + marketId)
429
+ newTicker = await self.subscribe_multiple(messageHashes, args, params)
430
+ if self.newUpdates:
431
+ result = {}
432
+ result[newTicker['symbol']] = newTicker
433
+ return result
434
+ return self.filter_by_array(self.tickers, 'symbol', symbols)
435
+
436
+ def handle_ticker(self, client: Client, message):
437
+ #
438
+ # {
439
+ # "table": "ticker",
440
+ # "data": [
441
+ # {
442
+ # "last": "3088.6",
443
+ # "open24h": "3087.2",
444
+ # "high24h": "3142.0",
445
+ # "low24h": "3053.9",
446
+ # "volume24h": "450512672.1800",
447
+ # "currencyVolume24h": "1458.579",
448
+ # "openInterest": "3786.801",
449
+ # "marketCode": "ETH-USD-SWAP-LIN",
450
+ # "timestamp": "1716212747050",
451
+ # "lastQty": "0.813",
452
+ # "markPrice": "3088.6",
453
+ # "lastMarkPrice": "3088.6",
454
+ # "indexPrice": "3086.5"
455
+ # },
456
+ # ...
457
+ # ]
458
+ # }
459
+ #
460
+ data = self.safe_list(message, 'data', [])
461
+ for i in range(0, len(data)):
462
+ rawTicker = self.safe_dict(data, i, {})
463
+ ticker = self.parse_ticker(rawTicker)
464
+ symbol = ticker['symbol']
465
+ messageHash = 'tickers:' + symbol
466
+ self.tickers[symbol] = ticker
467
+ client.resolve(ticker, messageHash)
468
+
469
+ async def watch_balance(self, params={}) -> Balances:
470
+ """
471
+ :see: https://docs.ox.fun/?json#balance-channel
472
+ watch balance and get the amount of funds available for trading or funds locked in orders
473
+ :param dict [params]: extra parameters specific to the exchange API endpoint
474
+ :param int|str [params.tag]: If given it will be echoed in the reply and the max size of tag is 32
475
+ :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
476
+ """
477
+ await self.load_markets()
478
+ self.authenticate()
479
+ args = 'balance:all'
480
+ messageHash = 'balance'
481
+ url = self.urls['api']['ws']
482
+ request: dict = {
483
+ 'op': 'subscribe',
484
+ 'args': [args],
485
+ }
486
+ return await self.watch(url, messageHash, self.extend(request, params), messageHash)
487
+
488
+ def handle_balance(self, client, message):
489
+ #
490
+ # {
491
+ # "table": "balance",
492
+ # "accountId": "106464",
493
+ # "timestamp": "1716549132780",
494
+ # "tradeType": "PORTFOLIO",
495
+ # "data": [
496
+ # {
497
+ # "instrumentId": "xOX",
498
+ # "total": "23.375591220",
499
+ # "available": "23.375591220",
500
+ # "reserved": "0",
501
+ # "quantityLastUpdated": "1716509744262",
502
+ # "locked": "0"
503
+ # },
504
+ # ...
505
+ # ]
506
+ # }
507
+ #
508
+ balances = self.safe_list(message, 'data')
509
+ timestamp = self.safe_integer(message, 'timestamp')
510
+ self.balance['info'] = message
511
+ self.balance['timestamp'] = timestamp
512
+ self.balance['datetime'] = self.iso8601(timestamp)
513
+ for i in range(0, len(balances)):
514
+ balance = self.safe_dict(balances, i, {})
515
+ currencyId = self.safe_string(balance, 'instrumentId')
516
+ code = self.safe_currency_code(currencyId)
517
+ if not (code in self.balance):
518
+ self.balance[code] = self.account()
519
+ account = self.balance[code]
520
+ account['total'] = self.safe_string(balance, 'total')
521
+ account['used'] = self.safe_string(balance, 'reserved')
522
+ account['free'] = self.safe_string(balance, 'available')
523
+ self.balance[code] = account
524
+ self.balance = self.safe_balance(self.balance)
525
+ client.resolve(self.balance, 'balance')
526
+
527
+ async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
528
+ """
529
+ :see: https://docs.ox.fun/?json#position-channel
530
+ watch all open positions
531
+ :param str[]|None symbols: list of unified market symbols
532
+ :param dict params: extra parameters specific to the exchange API endpoint
533
+ :param int|str [params.tag]: If given it will be echoed in the reply and the max size of tag is 32
534
+ :returns dict[]: a list of `position structure <https://docs.ccxt.com/en/latest/manual.html#position-structure>`
535
+ """
536
+ await self.load_markets()
537
+ await self.authenticate()
538
+ allSymbols = (symbols is None)
539
+ sym = symbols
540
+ args = []
541
+ if allSymbols:
542
+ sym = self.symbols
543
+ args.append('position:all')
544
+ messageHashes = []
545
+ for i in range(0, len(sym)):
546
+ symbol = sym[i]
547
+ messageHash = 'positions' + ':' + symbol
548
+ messageHashes.append(messageHash)
549
+ marketId = self.market_id(symbol)
550
+ if not allSymbols:
551
+ args.append('position:' + marketId)
552
+ newPositions = await self.subscribe_multiple(messageHashes, args, params)
553
+ if self.newUpdates:
554
+ return newPositions
555
+ return self.filter_by_symbols_since_limit(self.positions, symbols, since, limit, True)
556
+
557
+ def handle_positions(self, client: Client, message):
558
+ #
559
+ # {
560
+ # "table": "position",
561
+ # "accountId": "106464",
562
+ # "timestamp": "1716550771582",
563
+ # "data": [
564
+ # {
565
+ # "instrumentId": "ETH-USD-SWAP-LIN",
566
+ # "quantity": "0.01",
567
+ # "lastUpdated": "1716550757299",
568
+ # "contractValCurrency": "ETH",
569
+ # "entryPrice": "3709.6",
570
+ # "positionPnl": "-5.000",
571
+ # "estLiquidationPrice": "743.4",
572
+ # "margin": "0",
573
+ # "leverage": "0"
574
+ # }
575
+ # ]
576
+ # }
577
+ #
578
+ if self.positions is None:
579
+ self.positions = ArrayCacheBySymbolBySide()
580
+ cache = self.positions
581
+ data = self.safe_list(message, 'data', [])
582
+ for i in range(0, len(data)):
583
+ rawPosition = self.safe_dict(data, i, {})
584
+ position = self.parse_ws_position(rawPosition)
585
+ symbol = position['symbol']
586
+ messageHash = 'positions:' + symbol
587
+ cache.append(position)
588
+ client.resolve(position, messageHash)
589
+
590
+ def parse_ws_position(self, position, market: Market = None):
591
+ #
592
+ # {
593
+ # "instrumentId": "ETH-USD-SWAP-LIN",
594
+ # "quantity": "0.01",
595
+ # "lastUpdated": "1716550757299",
596
+ # "contractValCurrency": "ETH",
597
+ # "entryPrice": "3709.6",
598
+ # "positionPnl": "-5.000",
599
+ # "estLiquidationPrice": "743.4",
600
+ # "margin": "0", # Currently always reports 0
601
+ # "leverage": "0" # Currently always reports 0
602
+ # }
603
+ #
604
+ marketId = self.safe_string(position, 'instrumentId')
605
+ market = self.safe_market(marketId, market)
606
+ return self.safe_position({
607
+ 'info': position,
608
+ 'id': None,
609
+ 'symbol': market['symbol'],
610
+ 'notional': None,
611
+ 'marginMode': 'cross',
612
+ 'liquidationPrice': self.safe_number(position, 'estLiquidationPrice'),
613
+ 'entryPrice': self.safe_number(position, 'entryPrice'),
614
+ 'unrealizedPnl': self.safe_number(position, 'positionPnl'),
615
+ 'realizedPnl': None,
616
+ 'percentage': None,
617
+ 'contracts': self.safe_number(position, 'quantity'),
618
+ 'contractSize': None,
619
+ 'markPrice': None,
620
+ 'lastPrice': None,
621
+ 'side': None,
622
+ 'hedged': None,
623
+ 'timestamp': None,
624
+ 'datetime': None,
625
+ 'lastUpdateTimestamp': self.safe_integer(position, 'lastUpdated'),
626
+ 'maintenanceMargin': None,
627
+ 'maintenanceMarginPercentage': None,
628
+ 'collateral': None,
629
+ 'initialMargin': None,
630
+ 'initialMarginPercentage': None,
631
+ 'leverage': None,
632
+ 'marginRatio': None,
633
+ 'stopLossPrice': None,
634
+ 'takeProfitPrice': None,
635
+ })
636
+
637
+ async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
638
+ """
639
+ watches information on multiple orders made by the user
640
+ :see: https://docs.ox.fun/?json#order-channel
641
+ :param str symbol: unified market symbol of the market orders were made in
642
+ :param int [since]: the earliest time in ms to fetch orders for
643
+ :param int [limit]: the maximum number of order structures to retrieve
644
+ :param dict [params]: extra parameters specific to the exchange API endpoint
645
+ :param int|str [params.tag]: If given it will be echoed in the reply and the max size of tag is 32
646
+ :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
647
+ """
648
+ await self.load_markets()
649
+ await self.authenticate()
650
+ messageHash = 'orders'
651
+ args = 'order:'
652
+ market = self.safe_market(symbol)
653
+ if symbol is None:
654
+ args += 'all'
655
+ else:
656
+ messageHash += ':' + symbol
657
+ args += ':' + market['id']
658
+ request: dict = {
659
+ 'op': 'subscribe',
660
+ 'args': [
661
+ args,
662
+ ],
663
+ }
664
+ url = self.urls['api']['ws']
665
+ orders = await self.watch(url, messageHash, request, messageHash)
666
+ if self.newUpdates:
667
+ limit = orders.getLimit(symbol, limit)
668
+ return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
669
+
670
+ def handle_orders(self, client: Client, message):
671
+ #
672
+ # {
673
+ # "table": "order",
674
+ # "data": [
675
+ # {
676
+ # "accountId": "106464",
677
+ # "clientOrderId": "1716713676233",
678
+ # "orderId": "1000116921319",
679
+ # "price": "1000.0",
680
+ # "quantity": "0.01",
681
+ # "amount": "0.0",
682
+ # "side": "BUY",
683
+ # "status": "OPEN",
684
+ # "marketCode": "ETH-USD-SWAP-LIN",
685
+ # "timeInForce": "MAKER_ONLY",
686
+ # "timestamp": "1716713677834",
687
+ # "remainQuantity": "0.01",
688
+ # "limitPrice": "1000.0",
689
+ # "notice": "OrderOpened",
690
+ # "orderType": "LIMIT",
691
+ # "isTriggered": "false",
692
+ # "displayQuantity": "0.01"
693
+ # }
694
+ # ]
695
+ # }
696
+ #
697
+ data = self.safe_list(message, 'data', [])
698
+ messageHash = 'orders'
699
+ if self.orders is None:
700
+ limit = self.safe_integer(self.options, 'ordersLimit', 1000)
701
+ self.orders = ArrayCacheBySymbolById(limit)
702
+ orders = self.orders
703
+ for i in range(0, len(data)):
704
+ order = self.safe_dict(data, i, {})
705
+ parsedOrder = self.parse_order(order)
706
+ orders.append(parsedOrder)
707
+ messageHash += ':' + parsedOrder['symbol']
708
+ client.resolve(self.orders, messageHash)
709
+
710
+ async def create_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}) -> Order:
711
+ """
712
+ :see: https://docs.ox.fun/?json#order-commands
713
+ create a trade order
714
+ :param str symbol: unified symbol of the market to create an order in
715
+ :param str type: 'market', 'limit', 'STOP_LIMIT' or 'STOP_MARKET'
716
+ :param str side: 'buy' or 'sell'
717
+ :param float amount: how much of currency you want to trade in units of base currency
718
+ :param float [price]: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
719
+ :param dict [params]: extra parameters specific to the exchange API endpoint
720
+ :param int [params.clientOrderId]: a unique id for the order
721
+ :param int [params.timestamp]: in milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected.
722
+ :param int [params.recvWindow]: in milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected. If timestamp is provided without recvWindow, then a default recvWindow of 1000ms is used.
723
+ :param float [params.cost]: the quote quantity that can be used alternative for the amount for market buy orders
724
+ :param float [params.triggerPrice]: The price at which a trigger order is triggered at
725
+ :param float [params.limitPrice]: Limit price for the STOP_LIMIT order
726
+ :param bool [params.postOnly]: if True, the order will only be posted if it will be a maker order
727
+ :param str [params.timeInForce]: GTC(default), IOC, FOK, PO, MAKER_ONLY or MAKER_ONLY_REPRICE(reprices order to the best maker only price if the specified price were to lead to a taker trade)
728
+ :param str [params.selfTradePreventionMode]: NONE, EXPIRE_MAKER, EXPIRE_TAKER or EXPIRE_BOTH for more info check here {@link https://docs.ox.fun/?json#self-trade-prevention-modes}
729
+ :param str [params.displayQuantity]: for an iceberg order, pass both quantity and displayQuantity fields in the order request
730
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
731
+ """
732
+ await self.load_markets()
733
+ await self.authenticate()
734
+ messageHash = str(self.nonce())
735
+ request: dict = {
736
+ 'op': 'placeorder',
737
+ 'tag': messageHash,
738
+ }
739
+ params = self.omit(params, 'tag')
740
+ orderRequest: dict = self.create_order_request(symbol, type, side, amount, price, params)
741
+ timestamp = self.safe_integer(orderRequest, 'timestamp')
742
+ if timestamp is None:
743
+ orderRequest['timestamp'] = self.milliseconds()
744
+ request['data'] = orderRequest
745
+ url = self.urls['api']['ws']
746
+ return await self.watch(url, messageHash, request, messageHash)
747
+
748
+ async def edit_order_ws(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}) -> Order:
749
+ """
750
+ edit a trade order
751
+ :see: https://docs.ox.fun/?json#modify-order
752
+ :param str id: order id
753
+ :param str symbol: unified symbol of the market to create an order in
754
+ :param str type: 'market' or 'limit'
755
+ :param str side: 'buy' or 'sell'
756
+ :param float amount: how much of the currency you want to trade in units of the base currency
757
+ :param float|None [price]: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
758
+ :param int [params.timestamp]: in milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected.
759
+ :param int [params.recvWindow]: in milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected. If timestamp is provided without recvWindow, then a default recvWindow of 1000ms is used.
760
+ :param dict [params]: extra parameters specific to the exchange API endpoint
761
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
762
+ """
763
+ await self.load_markets()
764
+ await self.authenticate()
765
+ messageHash = str(self.nonce())
766
+ request: dict = {
767
+ 'op': 'modifyorder',
768
+ 'tag': messageHash,
769
+ }
770
+ params = self.omit(params, 'tag')
771
+ orderRequest: dict = self.create_order_request(symbol, type, side, amount, price, params)
772
+ orderRequest = self.extend(orderRequest, {'orderId': id})
773
+ timestamp = self.safe_integer(orderRequest, 'timestamp')
774
+ if timestamp is None:
775
+ orderRequest['timestamp'] = self.milliseconds()
776
+ request['data'] = orderRequest
777
+ url = self.urls['api']['ws']
778
+ return await self.watch(url, messageHash, request, messageHash)
779
+
780
+ def handle_place_orders(self, client: Client, message):
781
+ #
782
+ # {
783
+ # "event": "placeorder",
784
+ # "submitted": True,
785
+ # "tag": "1716934577",
786
+ # "timestamp": "1716932973899",
787
+ # "data": {
788
+ # "marketCode": "ETH-USD-SWAP-LIN",
789
+ # "side": "BUY",
790
+ # "orderType": "LIMIT",
791
+ # "quantity": "0.010",
792
+ # "timeInForce": "GTC",
793
+ # "price": "400.0",
794
+ # "limitPrice": "400.0",
795
+ # "orderId": "1000117429736",
796
+ # "source": 13
797
+ # }
798
+ # }
799
+ #
800
+ #
801
+ # Failure response format
802
+ # {
803
+ # "event": "placeorder",
804
+ # "submitted": False,
805
+ # "message": "JSON data format is invalid",
806
+ # "code": "20009",
807
+ # "timestamp": "1716932877381"
808
+ # }
809
+ #
810
+ messageHash = self.safe_string(message, 'tag')
811
+ submitted = self.safe_bool(message, 'submitted')
812
+ # filter out partial errors
813
+ if not submitted:
814
+ method = self.safe_string(message, 'event')
815
+ stringMsg = self.json(message)
816
+ code = self.safe_integer(message, 'code')
817
+ self.handle_errors(code, None, client.url, method, None, stringMsg, message, None, None)
818
+ data = self.safe_value(message, 'data', {})
819
+ order = self.parse_order(data)
820
+ client.resolve(order, messageHash)
821
+
822
+ async def cancel_order_ws(self, id: str, symbol: Str = None, params={}) -> Order:
823
+ """
824
+ :see: https://docs.ox.fun/?json#cancel-order
825
+ cancels an open order
826
+ :param str id: order id
827
+ :param str symbol: unified market symbol, default is None
828
+ :param dict [params]: extra parameters specific to the exchange API endpoint
829
+ :returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
830
+ """
831
+ if symbol is None:
832
+ raise ArgumentsRequired(self.id + ' cancelOrderWs() requires a symbol argument')
833
+ await self.load_markets()
834
+ await self.authenticate()
835
+ messageHash = str(self.nonce())
836
+ data: dict = {
837
+ 'marketCode': self.market_id(symbol),
838
+ 'orderId': id,
839
+ }
840
+ request: dict = {
841
+ 'op': 'cancelorder',
842
+ 'tag': messageHash,
843
+ 'data': data,
844
+ }
845
+ url = self.urls['api']['ws']
846
+ return await self.watch(url, messageHash, request, messageHash)
847
+
848
+ async def cancel_orders_ws(self, ids: List[str], symbol: Str = None, params={}):
849
+ """
850
+ :see: https://www.okx.com/docs-v5/en/#order-book-trading-trade-ws-mass-cancel-order
851
+ cancel multiple orders
852
+ :param str[] ids: order ids
853
+ :param str symbol: unified market symbol, default is None
854
+ :param dict [params]: extra parameters specific to the exchange API endpoint
855
+ :returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
856
+ """
857
+ idsLength = len(ids)
858
+ if idsLength > 20:
859
+ raise BadRequest(self.id + ' cancelOrdersWs() accepts up to 20 ids at a time')
860
+ if symbol is None:
861
+ raise ArgumentsRequired(self.id + ' cancelOrdersWs() requires a symbol argument')
862
+ await self.load_markets()
863
+ await self.authenticate()
864
+ messageHash = str(self.nonce())
865
+ marketId = self.market_id(symbol)
866
+ dataArray = []
867
+ for i in range(0, idsLength):
868
+ data: dict = {
869
+ 'instId': marketId,
870
+ 'ordId': ids[i],
871
+ }
872
+ dataArray.append(data)
873
+ request: dict = {
874
+ 'op': 'cancelorders',
875
+ 'tag': messageHash,
876
+ 'dataArray': dataArray,
877
+ }
878
+ url = self.urls['api']['ws']
879
+ return await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)
880
+
881
+ async def authenticate(self, params={}):
882
+ url = self.urls['api']['ws']
883
+ client = self.client(url)
884
+ messageHash = 'authenticated'
885
+ future = client.future(messageHash)
886
+ authenticated = self.safe_dict(client.subscriptions, messageHash)
887
+ if authenticated is None:
888
+ self.check_required_credentials()
889
+ timestamp = self.milliseconds()
890
+ payload = str(timestamp) + 'GET/auth/self/verify'
891
+ signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256, 'base64')
892
+ request: dict = {
893
+ 'op': 'login',
894
+ 'data': {
895
+ 'apiKey': self.apiKey,
896
+ 'timestamp': timestamp,
897
+ 'signature': signature,
898
+ },
899
+ }
900
+ message = self.extend(request, params)
901
+ self.watch(url, messageHash, message, messageHash)
902
+ return await future
903
+
904
+ def handle_authentication_message(self, client: Client, message):
905
+ authenticated = self.safe_bool(message, 'success', False)
906
+ messageHash = 'authenticated'
907
+ if authenticated:
908
+ # we resolve the future here permanently so authentication only happens once
909
+ future = self.safe_dict(client.futures, messageHash)
910
+ future.resolve(True)
911
+ else:
912
+ error = AuthenticationError(self.json(message))
913
+ client.reject(error, messageHash)
914
+ if messageHash in client.subscriptions:
915
+ del client.subscriptions[messageHash]
916
+
917
+ def ping(self, client):
918
+ return 'ping'
919
+
920
+ def handle_pong(self, client: Client, message):
921
+ client.lastPong = self.milliseconds()
922
+ return message
923
+
924
+ def handle_message(self, client: Client, message):
925
+ if message == 'pong':
926
+ self.handle_pong(client, message)
927
+ return
928
+ table = self.safe_string(message, 'table')
929
+ data = self.safe_list(message, 'data', [])
930
+ event = self.safe_string(message, 'event')
931
+ if (table is not None) and (data is not None):
932
+ if table == 'trade':
933
+ self.handle_trades(client, message)
934
+ if table == 'ticker':
935
+ self.handle_ticker(client, message)
936
+ if table.find('candles') > -1:
937
+ self.handle_ohlcv(client, message)
938
+ if table.find('depth') > -1:
939
+ self.handle_order_book(client, message)
940
+ if table.find('balance') > -1:
941
+ self.handle_balance(client, message)
942
+ if table.find('position') > -1:
943
+ self.handle_positions(client, message)
944
+ if table.find('order') > -1:
945
+ self.handle_orders(client, message)
946
+ else:
947
+ if event == 'login':
948
+ self.handle_authentication_message(client, message)
949
+ if (event == 'placeorder') or (event == 'modifyorder') or (event == 'cancelorder'):
950
+ self.handle_place_orders(client, message)