ccxt 4.5.22 → 4.5.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +6 -5
  2. package/dist/ccxt.browser.min.js +2 -2
  3. package/dist/cjs/ccxt.js +6 -1
  4. package/dist/cjs/src/abstract/bullish.js +11 -0
  5. package/dist/cjs/src/base/Exchange.js +3 -2
  6. package/dist/cjs/src/base/ws/WsClient.js +15 -0
  7. package/dist/cjs/src/binance.js +159 -36
  8. package/dist/cjs/src/bingx.js +2 -1
  9. package/dist/cjs/src/bitmart.js +1 -0
  10. package/dist/cjs/src/bullish.js +2919 -0
  11. package/dist/cjs/src/bybit.js +34 -37
  12. package/dist/cjs/src/gate.js +2 -2
  13. package/dist/cjs/src/htx.js +4 -1
  14. package/dist/cjs/src/hyperliquid.js +115 -12
  15. package/dist/cjs/src/kucoin.js +22 -3
  16. package/dist/cjs/src/mexc.js +7 -0
  17. package/dist/cjs/src/okx.js +117 -63
  18. package/dist/cjs/src/paradex.js +78 -3
  19. package/dist/cjs/src/pro/binance.js +131 -29
  20. package/dist/cjs/src/pro/bullish.js +781 -0
  21. package/dist/cjs/src/pro/coinbase.js +2 -2
  22. package/dist/cjs/src/pro/hyperliquid.js +75 -15
  23. package/dist/cjs/src/pro/upbit.js +28 -82
  24. package/js/ccxt.d.ts +8 -2
  25. package/js/ccxt.js +6 -2
  26. package/js/src/abstract/binance.d.ts +1 -0
  27. package/js/src/abstract/binancecoinm.d.ts +1 -0
  28. package/js/src/abstract/binanceus.d.ts +1 -0
  29. package/js/src/abstract/binanceusdm.d.ts +1 -0
  30. package/js/src/abstract/bingx.d.ts +1 -0
  31. package/js/src/abstract/bullish.d.ts +65 -0
  32. package/js/src/abstract/bullish.js +5 -0
  33. package/js/src/abstract/kucoin.d.ts +15 -0
  34. package/js/src/abstract/kucoinfutures.d.ts +15 -0
  35. package/js/src/abstract/mexc.d.ts +7 -0
  36. package/js/src/abstract/myokx.d.ts +90 -39
  37. package/js/src/abstract/okx.d.ts +90 -39
  38. package/js/src/abstract/okxus.d.ts +90 -39
  39. package/js/src/base/Exchange.d.ts +1 -1
  40. package/js/src/base/Exchange.js +3 -2
  41. package/js/src/base/ws/Client.d.ts +1 -0
  42. package/js/src/base/ws/WsClient.js +15 -0
  43. package/js/src/binance.d.ts +14 -5
  44. package/js/src/binance.js +159 -36
  45. package/js/src/bingx.js +2 -1
  46. package/js/src/bitmart.js +1 -0
  47. package/js/src/bullish.d.ts +446 -0
  48. package/js/src/bullish.js +2912 -0
  49. package/js/src/bybit.js +34 -37
  50. package/js/src/gate.js +2 -2
  51. package/js/src/htx.js +4 -1
  52. package/js/src/hyperliquid.d.ts +24 -0
  53. package/js/src/hyperliquid.js +115 -12
  54. package/js/src/kucoin.js +22 -3
  55. package/js/src/mexc.js +7 -0
  56. package/js/src/okx.js +117 -63
  57. package/js/src/paradex.d.ts +15 -1
  58. package/js/src/paradex.js +78 -3
  59. package/js/src/pro/binance.d.ts +7 -0
  60. package/js/src/pro/binance.js +131 -29
  61. package/js/src/pro/bullish.d.ts +108 -0
  62. package/js/src/pro/bullish.js +774 -0
  63. package/js/src/pro/coinbase.js +2 -2
  64. package/js/src/pro/hyperliquid.d.ts +13 -1
  65. package/js/src/pro/hyperliquid.js +75 -15
  66. package/js/src/pro/upbit.d.ts +0 -1
  67. package/js/src/pro/upbit.js +28 -82
  68. package/package.json +2 -2
@@ -0,0 +1,774 @@
1
+ // ---------------------------------------------------------------------------
2
+ import bullishRest from '../bullish.js';
3
+ import { ArrayCache, ArrayCacheBySymbolById, ArrayCacheBySymbolBySide } from '../base/ws/Cache.js';
4
+ import { ExchangeError } from '../base/errors.js';
5
+ // ---------------------------------------------------------------------------
6
+ export default class bullish extends bullishRest {
7
+ describe() {
8
+ return this.deepExtend(super.describe(), {
9
+ 'has': {
10
+ 'ws': true,
11
+ 'watchTicker': true,
12
+ 'watchTickers': false,
13
+ 'watchOrderBook': true,
14
+ 'watchOrders': true,
15
+ 'watchTrades': true,
16
+ 'watchPositions': true,
17
+ 'watchMyTrades': true,
18
+ 'watchBalance': true,
19
+ 'watchOHLCV': false,
20
+ },
21
+ 'urls': {
22
+ 'api': {
23
+ 'ws': {
24
+ 'public': 'wss://api.exchange.bullish.com',
25
+ 'private': 'wss://api.exchange.bullish.com/trading-api/v1/private-data',
26
+ },
27
+ },
28
+ 'test': {
29
+ 'ws': {
30
+ 'public': 'wss://api.simnext.bullish-test.com',
31
+ 'private': 'wss://api.simnext.bullish-test.com/trading-api/v1/private-data',
32
+ },
33
+ },
34
+ },
35
+ 'options': {
36
+ 'ws': {
37
+ 'cookies': {},
38
+ },
39
+ },
40
+ 'streaming': {
41
+ 'ping': this.ping,
42
+ 'keepAlive': 99000, // disconnect after 100 seconds of inactivity
43
+ },
44
+ });
45
+ }
46
+ requestId() {
47
+ const requestId = this.sum(this.safeInteger(this.options, 'requestId', 0), 1);
48
+ this.options['requestId'] = requestId;
49
+ return requestId;
50
+ }
51
+ ping(client) {
52
+ // bullish does not support built-in ws protocol-level ping-pong
53
+ // https://api.exchange.bullish.com/docs/api/rest/trading-api/v2/#overview--keep-websocket-open
54
+ const id = this.requestId().toString();
55
+ return {
56
+ 'jsonrpc': '2.0',
57
+ 'type': 'command',
58
+ 'method': 'keepalivePing',
59
+ 'params': {},
60
+ 'id': id,
61
+ };
62
+ }
63
+ handlePong(client, message) {
64
+ //
65
+ // {
66
+ // "id": "7",
67
+ // "jsonrpc": "2.0",
68
+ // "result": {
69
+ // "responseCodeName": "OK",
70
+ // "responseCode": "200",
71
+ // "message": "Keep alive pong"
72
+ // }
73
+ // }
74
+ //
75
+ client.lastPong = this.milliseconds();
76
+ return message; // current line is for transpilation compatibility
77
+ }
78
+ async watchPublic(url, messageHash, request = {}, params = {}) {
79
+ const id = this.requestId().toString();
80
+ const message = {
81
+ 'jsonrpc': '2.0',
82
+ 'type': 'command',
83
+ 'method': 'subscribe',
84
+ 'params': request,
85
+ 'id': id,
86
+ };
87
+ const fullUrl = this.urls['api']['ws']['public'] + url;
88
+ return await this.watch(fullUrl, messageHash, this.deepExtend(message, params), messageHash);
89
+ }
90
+ async watchPrivate(messageHash, subscribeHash, request = {}, params = {}) {
91
+ const url = this.urls['api']['ws']['private'];
92
+ const token = await this.handleToken();
93
+ const cookies = {
94
+ 'JWT_COOKIE': token,
95
+ };
96
+ this.options['ws']['cookies'] = cookies;
97
+ const id = this.requestId().toString();
98
+ const message = {
99
+ 'jsonrpc': '2.0',
100
+ 'type': 'command',
101
+ 'method': 'subscribe',
102
+ 'params': request,
103
+ 'id': id,
104
+ };
105
+ const result = await this.watch(url, messageHash, this.deepExtend(message, params), subscribeHash);
106
+ return result;
107
+ }
108
+ /**
109
+ * @method
110
+ * @name bullish#watchTrades
111
+ * @description get the list of most recent trades for a particular symbol
112
+ * @see https://api.exchange.bullish.com/docs/api/rest/trading-api/v2/#overview--unified-anonymous-trades-websocket-unauthenticated
113
+ * @param {string} symbol unified symbol of the market to fetch trades for
114
+ * @param {int} [since] timestamp in ms of the earliest trade to fetch
115
+ * @param {int} [limit] the maximum amount of trades to fetch
116
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
117
+ * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades}
118
+ */
119
+ async watchTrades(symbol, since = undefined, limit = undefined, params = {}) {
120
+ await this.loadMarkets();
121
+ const market = this.market(symbol);
122
+ const messageHash = 'trades::' + market['symbol'];
123
+ const url = '/trading-api/v1/market-data/trades';
124
+ const request = {
125
+ 'topic': 'anonymousTrades',
126
+ 'symbol': market['id'],
127
+ };
128
+ const trades = await this.watchPublic(url, messageHash, request, params);
129
+ if (this.newUpdates) {
130
+ limit = trades.getLimit(symbol, limit);
131
+ }
132
+ return this.filterBySinceLimit(trades, since, limit, 'timestamp', true);
133
+ }
134
+ handleTrades(client, message) {
135
+ //
136
+ // {
137
+ // "type": "snapshot",
138
+ // "dataType": "V1TAAnonymousTradeUpdate",
139
+ // "data": {
140
+ // "trades": [
141
+ // {
142
+ // "tradeId": "100086000000609304",
143
+ // "isTaker": true,
144
+ // "price": "104889.2063",
145
+ // "createdAtTimestamp": "1749124509118",
146
+ // "quantity": "0.01000000",
147
+ // "publishedAtTimestamp": "1749124531466",
148
+ // "side": "BUY",
149
+ // "createdAtDatetime": "2025-06-05T11:55:09.118Z",
150
+ // "symbol": "BTCUSDC"
151
+ // }
152
+ // ],
153
+ // "createdAtTimestamp": "1749124509118",
154
+ // "publishedAtTimestamp": "1749124531466",
155
+ // "symbol": "BTCUSDC"
156
+ // }
157
+ // }
158
+ //
159
+ const data = this.safeDict(message, 'data', {});
160
+ const marketId = this.safeString(data, 'symbol');
161
+ const symbol = this.safeSymbol(marketId);
162
+ const market = this.market(symbol);
163
+ const rawTrades = this.safeList(data, 'trades', []);
164
+ const trades = this.parseTrades(rawTrades, market);
165
+ if (!(symbol in this.trades)) {
166
+ const limit = this.safeInteger(this.options, 'tradesLimit', 1000);
167
+ const tradesArrayCache = new ArrayCache(limit);
168
+ this.trades[symbol] = tradesArrayCache;
169
+ }
170
+ const tradesArray = this.trades[symbol];
171
+ for (let i = 0; i < trades.length; i++) {
172
+ tradesArray.append(trades[i]);
173
+ }
174
+ this.trades[symbol] = tradesArray;
175
+ const messageHash = 'trades::' + market['symbol'];
176
+ client.resolve(tradesArray, messageHash);
177
+ }
178
+ /**
179
+ * @method
180
+ * @name bullish#watchTicker
181
+ * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
182
+ * @see https://api.exchange.bullish.com/docs/api/rest/trading-api/v2/#overview--anonymous-market-data-price-tick-unauthenticated
183
+ * @param {string} symbol unified symbol of the market to fetch the ticker for
184
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
185
+ * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure}
186
+ */
187
+ async watchTicker(symbol, params = {}) {
188
+ await this.loadMarkets();
189
+ const market = this.market(symbol);
190
+ symbol = market['symbol'];
191
+ const url = this.urls['api']['ws']['public'] + '/trading-api/v1/market-data/tick/' + market['id'];
192
+ const messageHash = 'ticker::' + symbol;
193
+ return await this.watch(url, messageHash, params, messageHash); // no need to send a subscribe message, the server sends a ticker update on connect
194
+ }
195
+ handleTicker(client, message) {
196
+ //
197
+ // {
198
+ // "type": "update",
199
+ // "dataType": "V1TATickerResponse",
200
+ // "data": {
201
+ // "askVolume": "0.00100822",
202
+ // "average": "104423.1806",
203
+ // "baseVolume": "472.83799258",
204
+ // "bestAsk": "104324.6000",
205
+ // "bestBid": "104324.5000",
206
+ // "bidVolume": "0.00020146",
207
+ // "change": "-198.4864",
208
+ // "close": "104323.9374",
209
+ // "createdAtTimestamp": "1749132838951",
210
+ // "publishedAtTimestamp": "1749132838955",
211
+ // "high": "105966.6577",
212
+ // "last": "104323.9374",
213
+ // "lastTradeDatetime": "2025-06-05T14:13:56.111Z",
214
+ // "lastTradeSize": "0.02396100",
215
+ // "low": "104246.6662",
216
+ // "open": "104522.4238",
217
+ // "percentage": "-0.19",
218
+ // "quoteVolume": "49662592.6712",
219
+ // "symbol": "BTC-USDC-PERP",
220
+ // "type": "ticker",
221
+ // "vwap": "105030.6996",
222
+ // "currentPrice": "104324.7747",
223
+ // "ammData": [
224
+ // {
225
+ // "feeTierId": "1",
226
+ // "currentPrice": "104324.7747",
227
+ // "baseReservesQuantity": "8.27911366",
228
+ // "quoteReservesQuantity": "1067283.0234",
229
+ // "bidSpreadFee": "0.00000000",
230
+ // "askSpreadFee": "0.00000000"
231
+ // }
232
+ // ],
233
+ // "createdAtDatetime": "2025-06-05T14:13:58.951Z",
234
+ // "markPrice": "104289.6884",
235
+ // "fundingRate": "-0.000192",
236
+ // "openInterest": "92.24146651"
237
+ // }
238
+ // }
239
+ //
240
+ const updateType = this.safeString(message, 'type', '');
241
+ const data = this.safeDict(message, 'data', {});
242
+ const marketId = this.safeString(data, 'symbol');
243
+ const market = this.safeMarket(marketId);
244
+ const symbol = market['symbol'];
245
+ let parsed = undefined;
246
+ if ((updateType === 'snapshot')) {
247
+ parsed = this.parseTicker(data, market);
248
+ }
249
+ else if (updateType === 'update') {
250
+ const ticker = this.safeDict(this.tickers, symbol, {});
251
+ const rawTicker = this.safeDict(ticker, 'info', {});
252
+ const merged = this.extend(rawTicker, data);
253
+ parsed = this.parseTicker(merged, market);
254
+ }
255
+ this.tickers[symbol] = parsed;
256
+ const messageHash = 'ticker::' + symbol;
257
+ client.resolve(this.tickers[symbol], messageHash);
258
+ }
259
+ /**
260
+ * @method
261
+ * @name bullish#watchOrderBook
262
+ * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
263
+ * @see https://api.exchange.bullish.com/docs/api/rest/trading-api/v2/#overview--multi-orderbook-websocket-unauthenticated
264
+ * @param {string} symbol unified symbol of the market to fetch the order book for
265
+ * @param {int} [limit] the maximum amount of order book entries to return
266
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
267
+ * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
268
+ */
269
+ async watchOrderBook(symbol, limit = undefined, params = {}) {
270
+ await this.loadMarkets();
271
+ const market = this.market(symbol);
272
+ const url = '/trading-api/v1/market-data/orderbook';
273
+ const messageHash = 'orderbook::' + market['symbol'];
274
+ const request = {
275
+ 'topic': 'l2Orderbook',
276
+ 'symbol': market['id'],
277
+ };
278
+ const orderbook = await this.watchPublic(url, messageHash, request, params);
279
+ return orderbook.limit();
280
+ }
281
+ handleOrderBook(client, message) {
282
+ //
283
+ // {
284
+ // "type": "snapshot",
285
+ // "dataType": "V1TALevel2",
286
+ // "data": {
287
+ // "timestamp": "1749372632028",
288
+ // "bids": [
289
+ // "105523.3000",
290
+ // "0.00046045",
291
+ // ],
292
+ // "asks": [
293
+ // "105523.4000",
294
+ // "0.00117112",
295
+ // ],
296
+ // "publishedAtTimestamp": "1749372632073",
297
+ // "datetime": "2025-06-08T08:50:32.028Z",
298
+ // "sequenceNumberRange": [ 1967862061, 1967862062 ],
299
+ // "symbol": "BTCUSDC"
300
+ // }
301
+ // }
302
+ //
303
+ // current channel is 'l2Orderbook' which returns only snapshots
304
+ const data = this.safeDict(message, 'data', {});
305
+ const marketId = this.safeString(data, 'symbol');
306
+ const symbol = this.safeSymbol(marketId);
307
+ const messageHash = 'orderbook::' + symbol;
308
+ const timestamp = this.safeInteger(data, 'timestamp');
309
+ if (!(symbol in this.orderbooks)) {
310
+ this.orderbooks[symbol] = this.orderBook();
311
+ }
312
+ const orderbook = this.orderbooks[symbol];
313
+ const bids = this.separateBidsOrAsks(this.safeList(data, 'bids', []));
314
+ const asks = this.separateBidsOrAsks(this.safeList(data, 'asks', []));
315
+ const snapshot = {
316
+ 'bids': bids,
317
+ 'asks': asks,
318
+ };
319
+ const parsed = this.parseOrderBook(snapshot, symbol, timestamp);
320
+ const sequenceNumberRange = this.safeList(data, 'sequenceNumberRange', []);
321
+ if (sequenceNumberRange.length > 0) {
322
+ const lastIndex = sequenceNumberRange.length - 1;
323
+ parsed['nonce'] = this.safeInteger(sequenceNumberRange, lastIndex);
324
+ }
325
+ orderbook.reset(parsed);
326
+ this.orderbooks[symbol] = orderbook;
327
+ client.resolve(orderbook, messageHash);
328
+ }
329
+ separateBidsOrAsks(entry) {
330
+ const result = [];
331
+ // 300 = '54885.0000000'
332
+ // 301 = '0.06141566'
333
+ // 302 ='53714.0000000'
334
+ for (let i = 0; i < entry.length; i++) {
335
+ if (i % 2 !== 0) {
336
+ continue;
337
+ }
338
+ const price = this.safeString(entry, i);
339
+ const amount = this.safeString(entry, i + 1);
340
+ result.push([price, amount]);
341
+ }
342
+ return result;
343
+ }
344
+ /**
345
+ * @method
346
+ * @name bullish#watchOrders
347
+ * @description watches information on multiple orders made by the user
348
+ * @see https://api.exchange.bullish.com/docs/api/rest/trading-api/v2/#overview--private-data-websocket-authenticated
349
+ * @param {string} symbol unified market symbol of the market orders were made in
350
+ * @param {int} [since] the earliest time in ms to fetch orders for
351
+ * @param {int} [limit] the maximum number of order structures to retrieve
352
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
353
+ * @param {string} [params.tradingAccountId] the trading account id to fetch entries for
354
+ * @returns {object[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
355
+ */
356
+ async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
357
+ await this.loadMarkets();
358
+ const subscribeHash = 'orders';
359
+ let messageHash = subscribeHash;
360
+ if (symbol !== undefined) {
361
+ symbol = this.symbol(symbol);
362
+ messageHash = messageHash + '::' + symbol;
363
+ }
364
+ const request = {
365
+ 'topic': 'orders',
366
+ };
367
+ const tradingAccountId = this.safeString(params, 'tradingAccountId');
368
+ if (tradingAccountId !== undefined) {
369
+ request['tradingAccountId'] = tradingAccountId;
370
+ params = this.omit(params, 'tradingAccountId');
371
+ }
372
+ const orders = await this.watchPrivate(messageHash, subscribeHash, request, params);
373
+ if (this.newUpdates) {
374
+ limit = orders.getLimit(symbol, limit);
375
+ }
376
+ return this.filterBySymbolSinceLimit(orders, symbol, since, limit, true);
377
+ }
378
+ handleOrders(client, message) {
379
+ // snapshot
380
+ // {
381
+ // "type": "snapshot",
382
+ // "tradingAccountId": "111309424211255",
383
+ // "dataType": "V1TAOrder",
384
+ // "data": [ ... ] // could be an empty list or a list of orders
385
+ // }
386
+ //
387
+ // update
388
+ // {
389
+ // "type": "update",
390
+ // "tradingAccountId": "111309424211255",
391
+ // "dataType": "V1TAOrder",
392
+ // "data": {
393
+ // "status": "OPEN",
394
+ // "createdAtTimestamp": "1751893427971",
395
+ // "quoteFee": "0.000000",
396
+ // "stopPrice": null,
397
+ // "quantityFilled": "0.00000000",
398
+ // "handle": null,
399
+ // "clientOrderId": null,
400
+ // "quantity": "0.10000000",
401
+ // "margin": false,
402
+ // "side": "BUY",
403
+ // "createdAtDatetime": "2025-07-07T13:03:47.971Z",
404
+ // "isLiquidation": false,
405
+ // "borrowedQuoteQuantity": null,
406
+ // "borrowedBaseQuantity": null,
407
+ // "timeInForce": "GTC",
408
+ // "borrowedQuantity": null,
409
+ // "baseFee": "0.000000",
410
+ // "quoteAmount": "0.0000000",
411
+ // "price": "0.0000000",
412
+ // "statusReason": "Order accepted",
413
+ // "type": "MKT",
414
+ // "statusReasonCode": 6014,
415
+ // "allowBorrow": false,
416
+ // "orderId": "862317981870850049",
417
+ // "publishedAtTimestamp": "1751893427975",
418
+ // "symbol": "ETHUSDT",
419
+ // "averageFillPrice": null
420
+ // }
421
+ // }
422
+ //
423
+ const type = this.safeString(message, 'type');
424
+ let rawOrders = [];
425
+ if (type === 'update') {
426
+ const data = this.safeDict(message, 'data', {});
427
+ rawOrders.push(data); // update is a single order
428
+ }
429
+ else {
430
+ rawOrders = this.safeList(message, 'data', []); // snapshot is a list of orders
431
+ }
432
+ if (rawOrders.length > 0) {
433
+ if (this.orders === undefined) {
434
+ const limit = this.safeInteger(this.options, 'ordersLimit', 1000);
435
+ this.orders = new ArrayCacheBySymbolById(limit);
436
+ }
437
+ const orders = this.orders;
438
+ const symbols = {};
439
+ for (let i = 0; i < rawOrders.length; i++) {
440
+ const rawOrder = rawOrders[i];
441
+ const parsedOrder = this.parseOrder(rawOrder);
442
+ orders.append(parsedOrder);
443
+ const symbol = this.safeString(parsedOrder, 'symbol');
444
+ symbols[symbol] = true;
445
+ }
446
+ const messageHash = 'orders';
447
+ client.resolve(orders, messageHash);
448
+ const keys = Object.keys(symbols);
449
+ for (let i = 0; i < keys.length; i++) {
450
+ const hashSymbol = keys[i];
451
+ const symbolMessageHash = messageHash + '::' + hashSymbol;
452
+ client.resolve(this.orders, symbolMessageHash);
453
+ }
454
+ }
455
+ }
456
+ /**
457
+ * @method
458
+ * @name bullish#watchMyTrades
459
+ * @description watches information on multiple trades made by the user
460
+ * @see https://api.exchange.bullish.com/docs/api/rest/trading-api/v2/#overview--private-data-websocket-authenticated
461
+ * @param {string} symbol unified market symbol of the market trades were made in
462
+ * @param {int} [since] the earliest time in ms to fetch trades for
463
+ * @param {int} [limit] the maximum number of trade structures to retrieve
464
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
465
+ * @param {string} [params.tradingAccountId] the trading account id to fetch entries for
466
+ * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=trade-structure}
467
+ */
468
+ async watchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) {
469
+ await this.loadMarkets();
470
+ const subscribeHash = 'myTrades';
471
+ let messageHash = subscribeHash;
472
+ if (symbol !== undefined) {
473
+ symbol = this.symbol(symbol);
474
+ messageHash += '::' + symbol;
475
+ }
476
+ const request = {
477
+ 'topic': 'trades',
478
+ };
479
+ const tradingAccountId = this.safeString(params, 'tradingAccountId');
480
+ if (tradingAccountId !== undefined) {
481
+ request['tradingAccountId'] = tradingAccountId;
482
+ params = this.omit(params, 'tradingAccountId');
483
+ }
484
+ const trades = await this.watchPrivate(messageHash, subscribeHash, request, params);
485
+ if (this.newUpdates) {
486
+ limit = trades.getLimit(symbol, limit);
487
+ }
488
+ return this.filterBySinceLimit(trades, since, limit, 'timestamp', true);
489
+ }
490
+ handleMyTrades(client, message) {
491
+ //
492
+ // snapshot
493
+ // {
494
+ // "type": "snapshot",
495
+ // "tradingAccountId": "111309424211255",
496
+ // "dataType": "V1TATrade",
497
+ // "data": [ ... ] // could be an empty list or a list of trades
498
+ // }
499
+ //
500
+ // update
501
+ // {
502
+ // "type": "update",
503
+ // "tradingAccountId": "111309424211255",
504
+ // "dataType": "V1TATrade",
505
+ // "data": {
506
+ // "clientOtcTradeId": null,
507
+ // "tradeId": "100203000003940164",
508
+ // "baseFee": "0.00000000",
509
+ // "isTaker": true,
510
+ // "quoteAmount": "253.6012195",
511
+ // "price": "2536.0121950",
512
+ // "createdAtTimestamp": "1751914859840",
513
+ // "quoteFee": "0.0000000",
514
+ // "tradeRebateAmount": null,
515
+ // "tradeRebateAssetSymbol": null,
516
+ // "handle": null,
517
+ // "otcTradeId": null,
518
+ // "otcMatchId": null,
519
+ // "orderId": "862407873644725249",
520
+ // "quantity": "0.10000000",
521
+ // "publishedAtTimestamp": "1751914859843",
522
+ // "side": "SELL",
523
+ // "createdAtDatetime": "2025-07-07T19:00:59.840Z",
524
+ // "symbol": "ETHUSDT"
525
+ // }
526
+ // }
527
+ //
528
+ const type = this.safeString(message, 'type');
529
+ let rawTrades = [];
530
+ if (type === 'update') {
531
+ const data = this.safeDict(message, 'data', {});
532
+ rawTrades.push(data); // update is a single trade
533
+ }
534
+ else {
535
+ rawTrades = this.safeList(message, 'data', []); // snapshot is a list of trades
536
+ }
537
+ if (rawTrades.length > 0) {
538
+ if (this.myTrades === undefined) {
539
+ const limit = this.safeInteger(this.options, 'tradesLimit', 1000);
540
+ this.myTrades = new ArrayCacheBySymbolById(limit);
541
+ }
542
+ const trades = this.myTrades;
543
+ const symbols = {};
544
+ for (let i = 0; i < rawTrades.length; i++) {
545
+ const rawTrade = rawTrades[i];
546
+ const parsedTrade = this.parseTrade(rawTrade);
547
+ trades.append(parsedTrade);
548
+ const symbol = this.safeString(parsedTrade, 'symbol');
549
+ symbols[symbol] = true;
550
+ }
551
+ const messageHash = 'myTrades';
552
+ client.resolve(trades, messageHash);
553
+ const keys = Object.keys(symbols);
554
+ for (let i = 0; i < keys.length; i++) {
555
+ const hashSymbol = keys[i];
556
+ const symbolMessageHash = messageHash + '::' + hashSymbol;
557
+ client.resolve(this.myTrades, symbolMessageHash);
558
+ }
559
+ }
560
+ }
561
+ /**
562
+ * @method
563
+ * @name bullish#watchBalance
564
+ * @description watch balance and get the amount of funds available for trading or funds locked in orders
565
+ * @see https://api.exchange.bullish.com/docs/api/rest/trading-api/v2/#overview--private-data-websocket-authenticated
566
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
567
+ * @param {string} [params.tradingAccountId] the trading account id to fetch entries for
568
+ * @returns {object} a [balance structure]{@link https://docs.ccxt.com/#/?id=balance-structure}
569
+ */
570
+ async watchBalance(params = {}) {
571
+ await this.loadMarkets();
572
+ const request = {
573
+ 'topic': 'assetAccounts',
574
+ };
575
+ let messageHash = 'balance';
576
+ const tradingAccountId = this.safeString(params, 'tradingAccountId');
577
+ if (tradingAccountId !== undefined) {
578
+ params = this.omit(params, 'tradingAccountId');
579
+ request['tradingAccountId'] = tradingAccountId;
580
+ messageHash += '::' + tradingAccountId;
581
+ }
582
+ return await this.watchPrivate(messageHash, messageHash, request, params);
583
+ }
584
+ handleBalance(client, message) {
585
+ //
586
+ // snapshot
587
+ // {
588
+ // "type": "snapshot",
589
+ // "tradingAccountId": "111309424211255",
590
+ // "dataType": "V1TAAssetAccount",
591
+ // "data": [
592
+ // {
593
+ // "updatedAtTimestamp": "1751989627509",
594
+ // "borrowedQuantity": "0.0000",
595
+ // "tradingAccountId": "111309424211255",
596
+ // "loanedQuantity": "0.0000",
597
+ // "lockedQuantity": "0.0000",
598
+ // "assetId": "5",
599
+ // "assetSymbol": "USDC",
600
+ // "publishedAtTimestamp": "1751989627512",
601
+ // "availableQuantity": "999672939.8767",
602
+ // "updatedAtDatetime": "2025-07-08T15:47:07.509Z"
603
+ // }
604
+ // ]
605
+ // }
606
+ //
607
+ // update
608
+ // {
609
+ // "type": "update",
610
+ // "tradingAccountId": "111309424211255",
611
+ // "dataType": "V1TAAssetAccount",
612
+ // "data": {
613
+ // "updatedAtTimestamp": "1751989627509",
614
+ // "borrowedQuantity": "0.0000",
615
+ // "tradingAccountId": "111309424211255",
616
+ // "loanedQuantity": "0.0000",
617
+ // "lockedQuantity": "0.0000",
618
+ // "assetId": "5",
619
+ // "assetSymbol": "USDC",
620
+ // "publishedAtTimestamp": "1751989627512",
621
+ // "availableQuantity": "999672939.8767",
622
+ // "updatedAtDatetime": "2025-07-08T15:47:07.509Z"
623
+ // }
624
+ // }
625
+ //
626
+ const tradingAccountId = this.safeString(message, 'tradingAccountId');
627
+ if (!(tradingAccountId in this.balance)) {
628
+ this.balance[tradingAccountId] = {};
629
+ }
630
+ const messageType = this.safeString(message, 'type');
631
+ if (messageType === 'snapshot') {
632
+ const data = this.safeList(message, 'data', []);
633
+ this.balance[tradingAccountId] = this.parseBalance(data);
634
+ }
635
+ else {
636
+ const data = this.safeDict(message, 'data', {});
637
+ const assetId = this.safeString(data, 'assetSymbol');
638
+ const account = this.account();
639
+ account['total'] = this.safeString(data, 'availableQuantity');
640
+ account['used'] = this.safeString(data, 'lockedQuantity');
641
+ const code = this.safeCurrencyCode(assetId);
642
+ this.balance[tradingAccountId][code] = account;
643
+ this.balance[tradingAccountId]['info'] = message;
644
+ this.balance[tradingAccountId] = this.safeBalance(this.balance[tradingAccountId]);
645
+ }
646
+ const messageHash = 'balance';
647
+ const tradingAccountIdHash = '::' + tradingAccountId;
648
+ client.resolve(this.balance[tradingAccountId], messageHash);
649
+ client.resolve(this.balance[tradingAccountId], messageHash + tradingAccountIdHash);
650
+ }
651
+ /**
652
+ * @method
653
+ * @name bullish#watchPositions
654
+ * @see https://api.exchange.bullish.com/docs/api/rest/trading-api/v2/#overview--private-data-websocket-authenticated
655
+ * @description watch all open positions
656
+ * @param {string[]} [symbols] list of unified market symbols
657
+ * @param {int} [since] the earliest time in ms to fetch positions for
658
+ * @param {int} [limit] the maximum number of positions to retrieve
659
+ * @param {object} params extra parameters specific to the exchange API endpoint
660
+ * @returns {object[]} a list of [position structure]{@link https://docs.ccxt.com/en/latest/manual.html#position-structure}
661
+ */
662
+ async watchPositions(symbols = undefined, since = undefined, limit = undefined, params = {}) {
663
+ await this.loadMarkets();
664
+ const subscribeHash = 'positions';
665
+ let messageHash = subscribeHash;
666
+ if (!this.isEmpty(symbols)) {
667
+ symbols = this.marketSymbols(symbols);
668
+ messageHash += '::' + symbols.join(',');
669
+ }
670
+ const request = {
671
+ 'topic': 'derivativesPositionsV2',
672
+ };
673
+ const positions = await this.watchPrivate(messageHash, subscribeHash, request, params);
674
+ if (this.newUpdates) {
675
+ return positions;
676
+ }
677
+ return this.filterBySymbolsSinceLimit(positions, symbols, since, limit, true);
678
+ }
679
+ handlePositions(client, message) {
680
+ // exchange does not return messages for sandbox mode
681
+ // current method is implemented blindly
682
+ // todo: check if this works with not-sandbox mode
683
+ const messageType = this.safeString(message, 'type');
684
+ let rawPositions = [];
685
+ if (messageType === 'update') {
686
+ const data = this.safeDict(message, 'data', {});
687
+ rawPositions.push(data);
688
+ }
689
+ else {
690
+ rawPositions = this.safeList(message, 'data', []);
691
+ }
692
+ if (this.positions === undefined) {
693
+ this.positions = new ArrayCacheBySymbolBySide();
694
+ }
695
+ const positions = this.positions;
696
+ const newPositions = [];
697
+ for (let i = 0; i < rawPositions.length; i++) {
698
+ const rawPosition = rawPositions[i];
699
+ const position = this.parsePosition(rawPosition);
700
+ positions.append(position);
701
+ newPositions.push(position);
702
+ }
703
+ const messageHashes = this.findMessageHashes(client, 'positions::');
704
+ for (let i = 0; i < messageHashes.length; i++) {
705
+ const messageHash = messageHashes[i];
706
+ const parts = messageHash.split('::');
707
+ const symbolsString = parts[1];
708
+ const symbols = symbolsString.split(',');
709
+ const symbolPositions = this.filterByArray(newPositions, 'symbol', symbols, false);
710
+ if (!this.isEmpty(symbolPositions)) {
711
+ client.resolve(symbolPositions, messageHash);
712
+ }
713
+ }
714
+ client.resolve(positions, 'positions');
715
+ }
716
+ handleErrorMessage(client, message) {
717
+ //
718
+ // {
719
+ // "data": {
720
+ // "errorCode": 401,
721
+ // "errorCodeName": "UNAUTHORIZED",
722
+ // "message": "Unable to authenticate; JWT is missing/invalid or unauthorised to access account"
723
+ // },
724
+ // "dataType": "V1TAErrorResponse",
725
+ // "type": "error"
726
+ // }
727
+ //
728
+ const data = this.safeDict(message, 'data', {});
729
+ const feedback = this.id + ' ' + this.json(data);
730
+ try {
731
+ const errorCode = this.safeString(data, 'errorCode');
732
+ const errorCodeName = this.safeString(data, 'errorCodeName');
733
+ this.throwExactlyMatchedException(this.exceptions['exact'], errorCode, feedback);
734
+ this.throwBroadlyMatchedException(this.exceptions['broad'], errorCodeName, feedback);
735
+ throw new ExchangeError(feedback); // unknown message
736
+ }
737
+ catch (e) {
738
+ client.reject(e);
739
+ }
740
+ }
741
+ handleMessage(client, message) {
742
+ const dataType = this.safeString(message, 'dataType');
743
+ const result = this.safeDict(message, 'result');
744
+ if (result !== undefined) {
745
+ const response = this.safeString(result, 'message');
746
+ if (response === 'Keep alive pong') {
747
+ this.handlePong(client, message);
748
+ }
749
+ }
750
+ else if (dataType !== undefined) {
751
+ if (dataType === 'V1TAAnonymousTradeUpdate') {
752
+ this.handleTrades(client, message);
753
+ }
754
+ if (dataType === 'V1TATickerResponse') {
755
+ this.handleTicker(client, message);
756
+ }
757
+ if (dataType === 'V1TALevel2') {
758
+ this.handleOrderBook(client, message);
759
+ }
760
+ if (dataType === 'V1TAOrder') {
761
+ this.handleOrders(client, message);
762
+ }
763
+ if (dataType === 'V1TATrade') {
764
+ this.handleMyTrades(client, message);
765
+ }
766
+ if (dataType === 'V1TAAssetAccount') {
767
+ this.handleBalance(client, message);
768
+ }
769
+ if (dataType === 'V1TAErrorResponse') {
770
+ this.handleErrorMessage(client, message);
771
+ }
772
+ }
773
+ }
774
+ }