ccxt 4.2.36 → 4.2.37

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.
@@ -7,7 +7,7 @@
7
7
  // ---------------------------------------------------------------------------
8
8
  import geminiRest from '../gemini.js';
9
9
  import { ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp } from '../base/ws/Cache.js';
10
- import { ExchangeError } from '../base/errors.js';
10
+ import { ExchangeError, NotSupported } from '../base/errors.js';
11
11
  import { sha384 } from '../static_dependencies/noble-hashes/sha512.js';
12
12
  // ---------------------------------------------------------------------------
13
13
  export default class gemini extends geminiRest {
@@ -19,9 +19,11 @@ export default class gemini extends geminiRest {
19
19
  'watchTicker': false,
20
20
  'watchTickers': false,
21
21
  'watchTrades': true,
22
+ 'watchTradesForSymbols': true,
22
23
  'watchMyTrades': false,
23
24
  'watchOrders': true,
24
25
  'watchOrderBook': true,
26
+ 'watchOrderBookForSymbols': true,
25
27
  'watchOHLCV': true,
26
28
  },
27
29
  'hostname': 'api.gemini.com',
@@ -70,7 +72,29 @@ export default class gemini extends geminiRest {
70
72
  }
71
73
  return this.filterBySinceLimit(trades, since, limit, 'timestamp', true);
72
74
  }
75
+ async watchTradesForSymbols(symbols, since = undefined, limit = undefined, params = {}) {
76
+ /**
77
+ * @method
78
+ * @name gemini#watchTradesForSymbols
79
+ * @see https://docs.gemini.com/websocket-api/#multi-market-data
80
+ * @description get the list of most recent trades for a list of symbols
81
+ * @param {string[]} symbols unified symbol of the market to fetch trades for
82
+ * @param {int} [since] timestamp in ms of the earliest trade to fetch
83
+ * @param {int} [limit] the maximum amount of trades to fetch
84
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
85
+ * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades}
86
+ */
87
+ const trades = await this.helperForWatchMultipleConstruct('trades', symbols, params);
88
+ if (this.newUpdates) {
89
+ const first = this.safeList(trades, 0);
90
+ const tradeSymbol = this.safeString(first, 'symbol');
91
+ limit = trades.getLimit(tradeSymbol, limit);
92
+ }
93
+ return this.filterBySinceLimit(trades, since, limit, 'timestamp', true);
94
+ }
73
95
  parseWsTrade(trade, market = undefined) {
96
+ //
97
+ // regular v2 trade
74
98
  //
75
99
  // {
76
100
  // "type": "trade",
@@ -82,11 +106,31 @@ export default class gemini extends geminiRest {
82
106
  // "side": "buy"
83
107
  // }
84
108
  //
109
+ // multi data trade
110
+ //
111
+ // {
112
+ // "type": "trade",
113
+ // "symbol": "ETHUSD",
114
+ // "tid": "1683002242170204", // this is not TS, but somewhat ID
115
+ // "price": "2299.24",
116
+ // "amount": "0.002662",
117
+ // "makerSide": "bid"
118
+ // }
119
+ //
85
120
  const timestamp = this.safeInteger(trade, 'timestamp');
86
- const id = this.safeString(trade, 'event_id');
121
+ const id = this.safeString2(trade, 'event_id', 'tid');
87
122
  const priceString = this.safeString(trade, 'price');
88
- const amountString = this.safeString(trade, 'quantity');
89
- const side = this.safeStringLower(trade, 'side');
123
+ const amountString = this.safeString2(trade, 'quantity', 'amount');
124
+ let side = this.safeStringLower(trade, 'side');
125
+ if (side === undefined) {
126
+ const marketSide = this.safeStringLower(trade, 'makerSide');
127
+ if (marketSide === 'bid') {
128
+ side = 'sell';
129
+ }
130
+ else if (marketSide === 'ask') {
131
+ side = 'buy';
132
+ }
133
+ }
90
134
  const marketId = this.safeStringLower(trade, 'symbol');
91
135
  const symbol = this.safeSymbol(marketId, market);
92
136
  return this.safeTrade({
@@ -186,6 +230,34 @@ export default class gemini extends geminiRest {
186
230
  client.resolve(stored, messageHash);
187
231
  }
188
232
  }
233
+ handleTradesForMultidata(client, trades, timestamp) {
234
+ if (trades !== undefined) {
235
+ const tradesLimit = this.safeInteger(this.options, 'tradesLimit', 1000);
236
+ const storesForSymbols = {};
237
+ for (let i = 0; i < trades.length; i++) {
238
+ const marketId = trades[i]['symbol'];
239
+ const market = this.safeMarket(marketId.toLowerCase());
240
+ const symbol = market['symbol'];
241
+ const trade = this.parseWsTrade(trades[i], market);
242
+ trade['timestamp'] = timestamp;
243
+ trade['datetime'] = this.iso8601(timestamp);
244
+ let stored = this.safeValue(this.trades, symbol);
245
+ if (stored === undefined) {
246
+ stored = new ArrayCache(tradesLimit);
247
+ this.trades[symbol] = stored;
248
+ }
249
+ stored.append(trade);
250
+ storesForSymbols[symbol] = stored;
251
+ }
252
+ const symbols = Object.keys(storesForSymbols);
253
+ for (let i = 0; i < symbols.length; i++) {
254
+ const symbol = symbols[i];
255
+ const stored = storesForSymbols[symbol];
256
+ const messageHash = 'trades:' + symbol;
257
+ client.resolve(stored, messageHash);
258
+ }
259
+ }
260
+ }
189
261
  async watchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
190
262
  /**
191
263
  * @method
@@ -331,6 +403,93 @@ export default class gemini extends geminiRest {
331
403
  this.orderbooks[symbol] = orderbook;
332
404
  client.resolve(orderbook, messageHash);
333
405
  }
406
+ async watchOrderBookForSymbols(symbols, limit = undefined, params = {}) {
407
+ /**
408
+ * @method
409
+ * @name gemini#watchOrderBookForSymbols
410
+ * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
411
+ * @see https://docs.gemini.com/websocket-api/#multi-market-data
412
+ * @param {string[]} symbols unified array of symbols
413
+ * @param {int} [limit] the maximum amount of order book entries to return
414
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
415
+ * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
416
+ */
417
+ const orderbook = await this.helperForWatchMultipleConstruct('orderbook', symbols, params);
418
+ return orderbook.limit();
419
+ }
420
+ async helperForWatchMultipleConstruct(itemHashName, symbols, params = {}) {
421
+ await this.loadMarkets();
422
+ symbols = this.marketSymbols(symbols, undefined, false, true, true);
423
+ const firstMarket = this.market(symbols[0]);
424
+ if (!firstMarket['spot'] && !firstMarket['linear']) {
425
+ throw new NotSupported(this.id + ' watchMultiple supports only spot or linear-swap symbols');
426
+ }
427
+ const messageHashes = [];
428
+ const marketIds = [];
429
+ for (let i = 0; i < symbols.length; i++) {
430
+ const symbol = symbols[i];
431
+ const messageHash = itemHashName + ':' + symbol;
432
+ messageHashes.push(messageHash);
433
+ const market = this.market(symbol);
434
+ marketIds.push(market['id']);
435
+ }
436
+ const queryStr = marketIds.join(',');
437
+ let url = this.urls['api']['ws'] + '/v1/multimarketdata?symbols=' + queryStr + '&heartbeat=true&';
438
+ if (itemHashName === 'orderbook') {
439
+ url += 'trades=false&bids=true&offers=true';
440
+ }
441
+ else if (itemHashName === 'trades') {
442
+ url += 'trades=true&bids=false&offers=false';
443
+ }
444
+ return await this.watchMultiple(url, messageHashes, undefined);
445
+ }
446
+ handleOrderBookForMultidata(client, rawOrderBookChanges, timestamp, nonce) {
447
+ //
448
+ // rawOrderBookChanges
449
+ //
450
+ // [
451
+ // {
452
+ // delta: "4105123935484.817624",
453
+ // price: "0.000000001",
454
+ // reason: "initial", // initial|cancel|place
455
+ // remaining: "4105123935484.817624",
456
+ // side: "bid", // bid|ask
457
+ // symbol: "SHIBUSD",
458
+ // type: "change", // seems always change
459
+ // },
460
+ // ...
461
+ //
462
+ const marketId = rawOrderBookChanges[0]['symbol'];
463
+ const market = this.safeMarket(marketId.toLowerCase());
464
+ const symbol = market['symbol'];
465
+ const messageHash = 'orderbook:' + symbol;
466
+ let orderbook = this.safeDict(this.orderbooks, symbol);
467
+ if (orderbook === undefined) {
468
+ orderbook = this.orderBook();
469
+ }
470
+ const bids = orderbook['bids'];
471
+ const asks = orderbook['asks'];
472
+ for (let i = 0; i < rawOrderBookChanges.length; i++) {
473
+ const entry = rawOrderBookChanges[i];
474
+ const price = this.safeNumber(entry, 'price');
475
+ const size = this.safeNumber(entry, 'remaining');
476
+ const rawSide = this.safeString(entry, 'side');
477
+ if (rawSide === 'bid') {
478
+ bids.store(price, size);
479
+ }
480
+ else {
481
+ asks.store(price, size);
482
+ }
483
+ }
484
+ orderbook['bids'] = bids;
485
+ orderbook['asks'] = asks;
486
+ orderbook['symbol'] = symbol;
487
+ orderbook['nonce'] = nonce;
488
+ orderbook['timestamp'] = timestamp;
489
+ orderbook['datetime'] = this.iso8601(timestamp);
490
+ this.orderbooks[symbol] = orderbook;
491
+ client.resolve(orderbook, messageHash);
492
+ }
334
493
  handleL2Updates(client, message) {
335
494
  //
336
495
  // {
@@ -411,6 +570,7 @@ export default class gemini extends geminiRest {
411
570
  // "socket_sequence": 7
412
571
  // }
413
572
  //
573
+ client.lastPong = this.milliseconds();
414
574
  return message;
415
575
  }
416
576
  handleSubscription(client, message) {
@@ -613,6 +773,33 @@ export default class gemini extends geminiRest {
613
773
  if (method !== undefined) {
614
774
  method.call(this, client, message);
615
775
  }
776
+ // handle multimarketdata
777
+ if (type === 'update') {
778
+ const ts = this.safeInteger(message, 'timestampms', this.milliseconds());
779
+ const eventId = this.safeInteger(message, 'eventId');
780
+ const events = this.safeList(message, 'events');
781
+ const orderBookItems = [];
782
+ const collectedEventsOfTrades = [];
783
+ for (let i = 0; i < events.length; i++) {
784
+ const event = events[i];
785
+ const eventType = this.safeString(event, 'type');
786
+ const isOrderBook = (eventType === 'change') && ('side' in event) && this.inArray(event['side'], ['ask', 'bid']);
787
+ if (isOrderBook) {
788
+ orderBookItems.push(event);
789
+ }
790
+ else if (eventType === 'trade') {
791
+ collectedEventsOfTrades.push(events[i]);
792
+ }
793
+ }
794
+ const lengthOb = orderBookItems.length;
795
+ if (lengthOb > 0) {
796
+ this.handleOrderBookForMultidata(client, orderBookItems, ts, eventId);
797
+ }
798
+ const lengthTrades = collectedEventsOfTrades.length;
799
+ if (lengthTrades > 0) {
800
+ this.handleTradesForMultidata(client, collectedEventsOfTrades, ts);
801
+ }
802
+ }
616
803
  }
617
804
  async authenticate(params = {}) {
618
805
  const url = this.safeString(params, 'url');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccxt",
3
- "version": "4.2.36",
3
+ "version": "4.2.37",
4
4
  "description": "A JavaScript / TypeScript / Python / C# / PHP cryptocurrency trading library with support for 100+ exchanges",
5
5
  "unpkg": "dist/ccxt.browser.js",
6
6
  "type": "module",
@@ -144,7 +144,7 @@
144
144
  "as-table": "1.0.37",
145
145
  "asciichart": "^1.5.25",
146
146
  "assert": "^2.0.0",
147
- "ast-transpiler": "^0.0.41",
147
+ "ast-transpiler": "^0.0.43",
148
148
  "docsify": "^4.11.4",
149
149
  "eslint": "8.22.0",
150
150
  "eslint-config-airbnb-base": "15.0.0",