ccxt 4.0.91 → 4.0.94

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.
@@ -6,7 +6,9 @@
6
6
 
7
7
  // ---------------------------------------------------------------------------
8
8
  import hitbtcRest from '../hitbtc.js';
9
- import { ArrayCache, ArrayCacheByTimestamp } from '../base/ws/Cache.js';
9
+ import { ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp } from '../base/ws/Cache.js';
10
+ import { sha256 } from '../static_dependencies/noble-hashes/sha256.js';
11
+ import { AuthenticationError } from '../base/errors.js';
10
12
  // ---------------------------------------------------------------------------
11
13
  export default class hitbtc extends hitbtcRest {
12
14
  describe() {
@@ -14,145 +16,245 @@ export default class hitbtc extends hitbtcRest {
14
16
  'has': {
15
17
  'ws': true,
16
18
  'watchTicker': true,
17
- 'watchTickers': false,
19
+ 'watchTickers': true,
18
20
  'watchTrades': true,
19
21
  'watchOrderBook': true,
20
- 'watchBalance': false,
22
+ 'watchBalance': true,
23
+ 'watchOrders': true,
21
24
  'watchOHLCV': true,
25
+ 'watchMyTrades': false,
22
26
  },
23
27
  'urls': {
24
28
  'api': {
25
- 'ws': 'wss://api.hitbtc.com/api/2/ws',
29
+ 'ws': {
30
+ 'public': 'wss://api.hitbtc.com/api/3/ws/public',
31
+ 'private': 'wss://api.hitbtc.com/api/3/ws/trading',
32
+ },
26
33
  },
27
34
  },
28
35
  'options': {
29
36
  'tradesLimit': 1000,
30
- 'methods': {
31
- 'orderbook': 'subscribeOrderbook',
32
- 'ticker': 'subscribeTicker',
33
- 'trades': 'subscribeTrades',
34
- 'ohlcv': 'subscribeCandles',
37
+ 'watchTicker': {
38
+ 'method': 'ticker/{speed}', // 'ticker/{speed}' or 'ticker/price/{speed}'
35
39
  },
40
+ 'watchTickers': {
41
+ 'method': 'ticker/{speed}', // 'ticker/{speed}','ticker/price/{speed}', 'ticker/{speed}/batch', or 'ticker/{speed}/price/batch''
42
+ },
43
+ 'watchOrderBook': {
44
+ 'method': 'orderbook/full', // 'orderbook/full', 'orderbook/{depth}/{speed}', 'orderbook/{depth}/{speed}/batch', 'orderbook/top/{speed}', or 'orderbook/top/{speed}/batch'
45
+ },
46
+ },
47
+ 'timeframes': {
48
+ '1m': 'M1',
49
+ '3m': 'M3',
50
+ '5m': 'M5',
51
+ '15m': 'M15',
52
+ '30m': 'M30',
53
+ '1h': 'H1',
54
+ '4h': 'H4',
55
+ '1d': 'D1',
56
+ '1w': 'D7',
57
+ '1M': '1M',
58
+ },
59
+ 'streaming': {
60
+ 'keepAlive': 4000,
36
61
  },
37
62
  });
38
63
  }
39
- async watchPublic(symbol, channel, timeframe = undefined, params = {}) {
64
+ async authenticate() {
65
+ /**
66
+ * @ignore
67
+ * @method
68
+ * @description authenticates the user to access private web socket channels
69
+ * @see https://api.hitbtc.com/#socket-authentication
70
+ * @returns {object} response from exchange
71
+ */
72
+ this.checkRequiredCredentials();
73
+ const url = this.urls['api']['ws']['private'];
74
+ const messageHash = 'authenticated';
75
+ const client = this.client(url);
76
+ const future = client.future(messageHash);
77
+ const authenticated = this.safeValue(client.subscriptions, messageHash);
78
+ if (authenticated === undefined) {
79
+ const timestamp = this.milliseconds();
80
+ const signature = this.hmac(this.encode(this.numberToString(timestamp)), this.encode(this.secret), sha256, 'hex');
81
+ const request = {
82
+ 'method': 'login',
83
+ 'params': {
84
+ 'type': 'HS256',
85
+ 'api_key': this.apiKey,
86
+ 'timestamp': timestamp,
87
+ 'signature': signature,
88
+ },
89
+ };
90
+ this.watch(url, messageHash, request, messageHash);
91
+ //
92
+ // {
93
+ // jsonrpc: '2.0',
94
+ // result: true
95
+ // }
96
+ //
97
+ // # Failure to return results
98
+ //
99
+ // {
100
+ // jsonrpc: '2.0',
101
+ // error: {
102
+ // code: 1002,
103
+ // message: 'Authorization is required or has been failed',
104
+ // description: 'invalid signature format'
105
+ // }
106
+ // }
107
+ //
108
+ }
109
+ return future;
110
+ }
111
+ async subscribePublic(name, symbols = undefined, params = {}) {
112
+ /**
113
+ * @ignore
114
+ * @method
115
+ * @param {string} name websocket endpoint name
116
+ * @param {[string]} [symbols] unified CCXT symbol(s)
117
+ * @param {object} [params] extra parameters specific to the hitbtc api
118
+ * @returns
119
+ */
40
120
  await this.loadMarkets();
41
- const marketId = this.marketId(symbol);
42
- const url = this.urls['api']['ws'];
43
- let messageHash = channel + ':' + marketId;
44
- if (timeframe !== undefined) {
45
- messageHash += ':' + timeframe;
121
+ const url = this.urls['api']['ws']['public'];
122
+ let messageHash = name;
123
+ if (symbols !== undefined) {
124
+ messageHash = messageHash + '::' + symbols.join(',');
46
125
  }
47
- const methods = this.safeValue(this.options, 'methods', {});
48
- const method = this.safeString(methods, channel, channel);
49
- const requestId = this.nonce();
50
126
  const subscribe = {
51
- 'method': method,
52
- 'params': {
53
- 'symbol': marketId,
54
- },
55
- 'id': requestId,
127
+ 'method': 'subscribe',
128
+ 'id': this.nonce(),
129
+ 'ch': name,
56
130
  };
57
- const request = this.deepExtend(subscribe, params);
131
+ const request = this.extend(subscribe, params);
58
132
  return await this.watch(url, messageHash, request, messageHash);
59
133
  }
134
+ async subscribePrivate(name, symbol = undefined, params = {}) {
135
+ /**
136
+ * @ignore
137
+ * @method
138
+ * @param {string} name websocket endpoint name
139
+ * @param {string} [symbol] unified CCXT symbol
140
+ * @param {object} [params] extra parameters specific to the hitbtc api
141
+ * @returns
142
+ */
143
+ await this.loadMarkets();
144
+ await this.authenticate();
145
+ const url = this.urls['api']['ws']['private'];
146
+ const splitName = name.split('_subscribe');
147
+ let messageHash = this.safeString(splitName, 0);
148
+ if (symbol !== undefined) {
149
+ messageHash = messageHash + '::' + symbol;
150
+ }
151
+ const subscribe = {
152
+ 'method': name,
153
+ 'params': params,
154
+ 'id': this.nonce(),
155
+ };
156
+ return await this.watch(url, messageHash, subscribe, messageHash);
157
+ }
60
158
  async watchOrderBook(symbol, limit = undefined, params = {}) {
61
159
  /**
62
160
  * @method
63
161
  * @name hitbtc#watchOrderBook
64
162
  * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
163
+ * @see https://api.hitbtc.com/#subscribe-to-full-order-book
164
+ * @see https://api.hitbtc.com/#subscribe-to-partial-order-book
165
+ * @see https://api.hitbtc.com/#subscribe-to-partial-order-book-in-batches
166
+ * @see https://api.hitbtc.com/#subscribe-to-top-of-book
167
+ * @see https://api.hitbtc.com/#subscribe-to-top-of-book-in-batches
65
168
  * @param {string} symbol unified symbol of the market to fetch the order book for
66
169
  * @param {int} [limit] the maximum amount of order book entries to return
67
170
  * @param {object} [params] extra parameters specific to the hitbtc api endpoint
68
- * @returns {object} A dictionary of [order book structures]{@link https://github.com/ccxt/ccxt/wiki/Manual#order-book-structure} indexed by market symbols
171
+ * @param {string} [params.method] 'orderbook/full', 'orderbook/{depth}/{speed}', 'orderbook/{depth}/{speed}/batch', 'orderbook/top/{speed}', or 'orderbook/top/{speed}/batch'
172
+ * @param {int} [params.depth] 5 , 10, or 20 (default)
173
+ * @param {int} [params.speed] 100 (default), 500, or 1000
174
+ * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
69
175
  */
70
- const orderbook = await this.watchPublic(symbol, 'orderbook', undefined, params);
71
- return orderbook.limit();
72
- }
73
- handleOrderBookSnapshot(client, message) {
74
- //
75
- // {
76
- // jsonrpc: "2.0",
77
- // method: "snapshotOrderbook",
78
- // params: {
79
- // ask: [
80
- // { price: "6927.75", size: "0.11991" },
81
- // { price: "6927.76", size: "0.06200" },
82
- // { price: "6927.85", size: "0.01000" },
83
- // ],
84
- // bid: [
85
- // { price: "6926.18", size: "0.16898" },
86
- // { price: "6926.17", size: "0.06200" },
87
- // { price: "6925.97", size: "0.00125" },
88
- // ],
89
- // symbol: "BTCUSD",
90
- // sequence: 494854,
91
- // timestamp: "2020-04-03T08:58:53.460Z"
92
- // }
93
- // }
94
- //
95
- const params = this.safeValue(message, 'params', {});
96
- const marketId = this.safeString(params, 'symbol');
97
- const market = this.safeMarket(marketId);
98
- const symbol = market['symbol'];
99
- const timestamp = this.parse8601(this.safeString(params, 'timestamp'));
100
- const nonce = this.safeInteger(params, 'sequence');
101
- if (symbol in this.orderbooks) {
102
- delete this.orderbooks[symbol];
176
+ const options = this.safeValue(this.options, 'watchOrderBook');
177
+ const defaultMethod = this.safeString(options, 'method', 'orderbook/full');
178
+ let name = this.safeString2(params, 'method', 'defaultMethod', defaultMethod);
179
+ const depth = this.safeString(params, 'depth', '20');
180
+ const speed = this.safeString(params, 'depth', '100');
181
+ if (name === 'orderbook/{depth}/{speed}') {
182
+ name = 'orderbook/D' + depth + '/' + speed + 'ms';
183
+ }
184
+ else if (name === 'orderbook/{depth}/{speed}/batch') {
185
+ name = 'orderbook/D' + depth + '/' + speed + 'ms/batch';
186
+ }
187
+ else if (name === 'orderbook/top/{speed}') {
188
+ name = 'orderbook/top/' + speed + 'ms';
103
189
  }
104
- const snapshot = this.parseOrderBook(params, symbol, timestamp, 'bid', 'ask', 'price', 'size');
105
- const orderbook = this.orderBook(snapshot);
106
- orderbook['nonce'] = nonce;
107
- this.orderbooks[symbol] = orderbook;
108
- const messageHash = 'orderbook:' + marketId;
109
- client.resolve(orderbook, messageHash);
190
+ else if (name === 'orderbook/top/{speed}/batch') {
191
+ name = 'orderbook/top/' + speed + 'ms/batch';
192
+ }
193
+ const market = this.market(symbol);
194
+ const request = {
195
+ 'params': {
196
+ 'symbols': [market['id']],
197
+ },
198
+ };
199
+ const orderbook = await this.subscribePublic(name, [symbol], this.deepExtend(request, params));
200
+ return orderbook.limit();
110
201
  }
111
- handleOrderBookUpdate(client, message) {
112
- //
113
- // {
114
- // jsonrpc: "2.0",
115
- // method: "updateOrderbook",
116
- // params: {
117
- // ask: [
118
- // { price: "6940.65", size: "0.00000" },
119
- // { price: "6940.66", size: "6.00000" },
120
- // { price: "6943.52", size: "0.04707" },
121
- // ],
122
- // bid: [
123
- // { price: "6938.40", size: "0.11991" },
124
- // { price: "6938.39", size: "0.00073" },
125
- // { price: "6936.65", size: "0.00000" },
126
- // ],
127
- // symbol: "BTCUSD",
128
- // sequence: 497872,
129
- // timestamp: "2020-04-03T09:03:56.685Z"
130
- // }
131
- // }
132
- //
133
- const params = this.safeValue(message, 'params', {});
134
- const marketId = this.safeString(params, 'symbol');
135
- const market = this.safeMarket(marketId);
136
- const symbol = market['symbol'];
137
- if (symbol in this.orderbooks) {
138
- const timestamp = this.parse8601(this.safeString(params, 'timestamp'));
139
- const nonce = this.safeInteger(params, 'sequence');
202
+ handleOrderBook(client, message) {
203
+ //
204
+ // {
205
+ // "ch": "orderbook/full", // Channel
206
+ // "snapshot": {
207
+ // "ETHBTC": {
208
+ // "t": 1626866578796, // Timestamp in milliseconds
209
+ // "s": 27617207, // Sequence number
210
+ // "a": [ // Asks
211
+ // ["0.060506", "0"],
212
+ // ["0.060549", "12.6431"],
213
+ // ["0.060570", "0"],
214
+ // ["0.060612", "0"]
215
+ // ],
216
+ // "b": [ // Bids
217
+ // ["0.060439", "4.4095"],
218
+ // ["0.060414", "0"],
219
+ // ["0.060407", "7.3349"],
220
+ // ["0.060390", "0"]
221
+ // ]
222
+ // }
223
+ // }
224
+ // }
225
+ //
226
+ const data = this.safeValue2(message, 'snapshot', 'update', {});
227
+ const marketIds = Object.keys(data);
228
+ const channel = this.safeString(message, 'ch');
229
+ for (let i = 0; i < marketIds.length; i++) {
230
+ const marketId = marketIds[i];
231
+ const market = this.safeMarket(marketId);
232
+ const symbol = market['symbol'];
233
+ const item = data[marketId];
234
+ const messageHash = channel + '::' + symbol;
235
+ if (!(symbol in this.orderbooks)) {
236
+ const subscription = this.safeValue(client.subscriptions, messageHash, {});
237
+ const limit = this.safeInteger(subscription, 'limit');
238
+ this.orderbooks[symbol] = this.orderBook({}, limit);
239
+ }
240
+ const timestamp = this.safeInteger(item, 't');
241
+ const nonce = this.safeInteger(item, 's');
140
242
  const orderbook = this.orderbooks[symbol];
141
- const asks = this.safeValue(params, 'ask', []);
142
- const bids = this.safeValue(params, 'bid', []);
243
+ const asks = this.safeValue(item, 'a', []);
244
+ const bids = this.safeValue(item, 'b', []);
143
245
  this.handleDeltas(orderbook['asks'], asks);
144
246
  this.handleDeltas(orderbook['bids'], bids);
145
247
  orderbook['timestamp'] = timestamp;
146
248
  orderbook['datetime'] = this.iso8601(timestamp);
147
249
  orderbook['nonce'] = nonce;
250
+ orderbook['symbol'] = symbol;
148
251
  this.orderbooks[symbol] = orderbook;
149
- const messageHash = 'orderbook:' + marketId;
150
252
  client.resolve(orderbook, messageHash);
151
253
  }
152
254
  }
153
255
  handleDelta(bookside, delta) {
154
- const price = this.safeFloat(delta, 'price');
155
- const amount = this.safeFloat(delta, 'size');
256
+ const price = this.safeNumber(delta, 0);
257
+ const amount = this.safeNumber(delta, 1);
156
258
  bookside.store(price, amount);
157
259
  }
158
260
  handleDeltas(bookside, deltas) {
@@ -165,180 +267,394 @@ export default class hitbtc extends hitbtcRest {
165
267
  * @method
166
268
  * @name hitbtc#watchTicker
167
269
  * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
270
+ * @see https://api.hitbtc.com/#subscribe-to-ticker
271
+ * @see https://api.hitbtc.com/#subscribe-to-ticker-in-batches
272
+ * @see https://api.hitbtc.com/#subscribe-to-mini-ticker
273
+ * @see https://api.hitbtc.com/#subscribe-to-mini-ticker-in-batches
168
274
  * @param {string} symbol unified symbol of the market to fetch the ticker for
169
275
  * @param {object} [params] extra parameters specific to the hitbtc api endpoint
170
- * @returns {object} a [ticker structure]{@link https://github.com/ccxt/ccxt/wiki/Manual#ticker-structure}
276
+ * @param {string} [params.method] 'ticker/{speed}' (default), or 'ticker/price/{speed}'
277
+ * @param {string} [params.speed] '1s' (default), or '3s'
278
+ * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure}
279
+ */
280
+ const options = this.safeValue(this.options, 'watchTicker');
281
+ const defaultMethod = this.safeString(options, 'method', 'ticker/{speed}');
282
+ const method = this.safeString2(params, 'method', 'defaultMethod', defaultMethod);
283
+ const speed = this.safeString(params, 'speed', '1s');
284
+ const name = this.implodeParams(method, { 'speed': speed });
285
+ params = this.omit(params, ['method', 'speed']);
286
+ const market = this.market(symbol);
287
+ const request = {
288
+ 'params': {
289
+ 'symbols': [market['id']],
290
+ },
291
+ };
292
+ return await this.subscribePublic(name, [symbol], this.deepExtend(request, params));
293
+ }
294
+ async watchTickers(symbols = undefined, params = {}) {
295
+ /**
296
+ * @method
297
+ * @name hitbtc#watchTicker
298
+ * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
299
+ * @param {string} symbol unified symbol of the market to fetch the ticker for
300
+ * @param {object} params extra parameters specific to the hitbtc api endpoint
301
+ * @param {string} params.method 'ticker/{speed}' (default),'ticker/price/{speed}', 'ticker/{speed}/batch', or 'ticker/{speed}/price/batch''
302
+ * @param {string} params.speed '1s' (default), or '3s'
303
+ * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure}
171
304
  */
172
- return await this.watchPublic(symbol, 'ticker', undefined, params);
305
+ await this.loadMarkets();
306
+ const options = this.safeValue(this.options, 'watchTicker');
307
+ const defaultMethod = this.safeString(options, 'method', 'ticker/{speed}');
308
+ const method = this.safeString2(params, 'method', 'defaultMethod', defaultMethod);
309
+ const speed = this.safeString(params, 'speed', '1s');
310
+ const name = this.implodeParams(method, { 'speed': speed });
311
+ params = this.omit(params, ['method', 'speed']);
312
+ const marketIds = [];
313
+ if (symbols === undefined) {
314
+ marketIds.push('*');
315
+ }
316
+ else {
317
+ for (let i = 0; i < symbols.length; i++) {
318
+ const marketId = this.marketId(symbols[i]);
319
+ marketIds.push(marketId);
320
+ }
321
+ }
322
+ const request = {
323
+ 'params': {
324
+ 'symbols': marketIds,
325
+ },
326
+ };
327
+ const tickers = await this.subscribePublic(name, symbols, this.deepExtend(request, params));
328
+ if (this.newUpdates) {
329
+ return tickers;
330
+ }
331
+ return this.filterByArray(this.tickers, 'symbol', symbols);
173
332
  }
174
333
  handleTicker(client, message) {
175
334
  //
176
- // {
177
- // jsonrpc: '2.0',
178
- // method: 'ticker',
179
- // params: {
180
- // ask: '6983.22',
181
- // bid: '6980.77',
182
- // last: '6980.77',
183
- // open: '6650.05',
184
- // low: '6606.45',
185
- // high: '7223.11',
186
- // volume: '79264.33941',
187
- // volumeQuote: '540183372.5134832',
188
- // timestamp: '2020-04-03T10:02:18.943Z',
189
- // symbol: 'BTCUSD'
190
- // }
191
- // }
335
+ // {
336
+ // "ch": "ticker/1s",
337
+ // "data": {
338
+ // "ETHBTC": {
339
+ // "t": 1614815872000, // Timestamp in milliseconds
340
+ // "a": "0.031175", // Best ask
341
+ // "A": "0.03329", // Best ask quantity
342
+ // "b": "0.031148", // Best bid
343
+ // "B": "0.10565", // Best bid quantity
344
+ // "c": "0.031210", // Last price
345
+ // "o": "0.030781", // Open price
346
+ // "h": "0.031788", // High price
347
+ // "l": "0.030733", // Low price
348
+ // "v": "62.587", // Base asset volume
349
+ // "q": "1.951420577", // Quote asset volume
350
+ // "p": "0.000429", // Price change
351
+ // "P": "1.39", // Price change percent
352
+ // "L": 1182694927 // Last trade identifier
353
+ // }
354
+ // }
355
+ // }
192
356
  //
193
- const params = this.safeValue(message, 'params');
194
- const marketId = this.safeValue(params, 'symbol');
195
- const market = this.safeMarket(marketId);
196
- const symbol = market['symbol'];
197
- const result = this.parseTicker(params, market);
198
- this.tickers[symbol] = result;
199
- const method = this.safeValue(message, 'method');
200
- const messageHash = method + ':' + marketId;
201
- client.resolve(result, messageHash);
357
+ // {
358
+ // "ch": "ticker/price/1s",
359
+ // "data": {
360
+ // "BTCUSDT": {
361
+ // "t": 1614815872030,
362
+ // "o": "32636.79",
363
+ // "c": "32085.51",
364
+ // "h": "33379.92",
365
+ // "l": "30683.28",
366
+ // "v": "11.90667",
367
+ // "q": "384081.1955629"
368
+ // }
369
+ // }
370
+ // }
371
+ //
372
+ const data = this.safeValue(message, 'data', {});
373
+ const marketIds = Object.keys(data);
374
+ const channel = this.safeString(message, 'ch');
375
+ const newTickers = [];
376
+ for (let i = 0; i < marketIds.length; i++) {
377
+ const marketId = marketIds[i];
378
+ const market = this.safeMarket(marketId);
379
+ const symbol = market['symbol'];
380
+ const ticker = this.parseWsTicker(data[marketId], market);
381
+ this.tickers[symbol] = ticker;
382
+ newTickers.push(ticker);
383
+ const messageHash = channel + '::' + symbol;
384
+ client.resolve(this.tickers[symbol], messageHash);
385
+ }
386
+ const messageHashes = this.findMessageHashes(client, channel + '::');
387
+ for (let i = 0; i < messageHashes.length; i++) {
388
+ const messageHash = messageHashes[i];
389
+ const parts = messageHash.split('::');
390
+ const symbolsString = parts[1];
391
+ const symbols = symbolsString.split(',');
392
+ const tickers = this.filterByArray(newTickers, 'symbol', symbols);
393
+ const tickersSymbols = Object.keys(tickers);
394
+ const numTickers = tickersSymbols.length;
395
+ if (numTickers > 0) {
396
+ client.resolve(tickers, messageHash);
397
+ }
398
+ }
399
+ client.resolve(this.tickers, channel);
400
+ return message;
401
+ }
402
+ parseWsTicker(ticker, market = undefined) {
403
+ //
404
+ // {
405
+ // "t": 1614815872000, // Timestamp in milliseconds
406
+ // "a": "0.031175", // Best ask
407
+ // "A": "0.03329", // Best ask quantity
408
+ // "b": "0.031148", // Best bid
409
+ // "B": "0.10565", // Best bid quantity
410
+ // "c": "0.031210", // Last price
411
+ // "o": "0.030781", // Open price
412
+ // "h": "0.031788", // High price
413
+ // "l": "0.030733", // Low price
414
+ // "v": "62.587", // Base asset volume
415
+ // "q": "1.951420577", // Quote asset volume
416
+ // "p": "0.000429", // Price change
417
+ // "P": "1.39", // Price change percent
418
+ // "L": 1182694927 // Last trade identifier
419
+ // }
420
+ //
421
+ // {
422
+ // "t": 1614815872030,
423
+ // "o": "32636.79",
424
+ // "c": "32085.51",
425
+ // "h": "33379.92",
426
+ // "l": "30683.28",
427
+ // "v": "11.90667",
428
+ // "q": "384081.1955629"
429
+ // }
430
+ //
431
+ const timestamp = this.safeInteger(ticker, 't');
432
+ const symbol = this.safeSymbol(undefined, market);
433
+ const last = this.safeString(ticker, 'c');
434
+ return this.safeTicker({
435
+ 'symbol': symbol,
436
+ 'timestamp': timestamp,
437
+ 'datetime': this.iso8601(timestamp),
438
+ 'high': this.safeString(ticker, 'h'),
439
+ 'low': this.safeString(ticker, 'l'),
440
+ 'bid': this.safeString(ticker, 'b'),
441
+ 'bidVolume': this.safeString(ticker, 'B'),
442
+ 'ask': this.safeString(ticker, 'a'),
443
+ 'askVolume': this.safeString(ticker, 'A'),
444
+ 'vwap': undefined,
445
+ 'open': this.safeString(ticker, 'o'),
446
+ 'close': last,
447
+ 'last': last,
448
+ 'previousClose': undefined,
449
+ 'change': undefined,
450
+ 'percentage': undefined,
451
+ 'average': undefined,
452
+ 'baseVolume': this.safeString(ticker, 'v'),
453
+ 'quoteVolume': this.safeString(ticker, 'q'),
454
+ 'info': ticker,
455
+ }, market);
202
456
  }
203
457
  async watchTrades(symbol, since = undefined, limit = undefined, params = {}) {
204
458
  /**
205
459
  * @method
206
460
  * @name hitbtc#watchTrades
207
461
  * @description get the list of most recent trades for a particular symbol
462
+ * @see https://api.hitbtc.com/#subscribe-to-trades
208
463
  * @param {string} symbol unified symbol of the market to fetch trades for
209
464
  * @param {int} [since] timestamp in ms of the earliest trade to fetch
210
465
  * @param {int} [limit] the maximum amount of trades to fetch
211
466
  * @param {object} [params] extra parameters specific to the hitbtc api endpoint
212
467
  * @returns {object[]} a list of [trade structures]{@link https://github.com/ccxt/ccxt/wiki/Manual#public-trades}
213
468
  */
214
- const trades = await this.watchPublic(symbol, 'trades', undefined, params);
469
+ await this.loadMarkets();
470
+ const market = this.market(symbol);
471
+ const request = {
472
+ 'params': {
473
+ 'symbols': [market['id']],
474
+ },
475
+ };
476
+ if (limit !== undefined) {
477
+ request['limit'] = limit;
478
+ }
479
+ const trades = await this.subscribePublic('trades', [symbol], this.deepExtend(request, params));
215
480
  if (this.newUpdates) {
216
481
  limit = trades.getLimit(symbol, limit);
217
482
  }
218
- return this.filterBySinceLimit(trades, since, limit, 'timestamp', true);
483
+ return this.filterBySinceLimit(trades, since, limit, 'timestamp');
219
484
  }
220
485
  handleTrades(client, message) {
221
486
  //
222
- // {
223
- // jsonrpc: '2.0',
224
- // method: 'snapshotTrades', // updateTrades
225
- // params: {
226
- // data: [
227
- // {
228
- // id: 814145791,
229
- // price: '6957.20',
230
- // quantity: '0.02779',
231
- // side: 'buy',
232
- // timestamp: '2020-04-03T10:28:20.032Z'
233
- // },
234
- // {
235
- // id: 814145792,
236
- // price: '6957.20',
237
- // quantity: '0.12918',
238
- // side: 'buy',
239
- // timestamp: '2020-04-03T10:28:20.039Z'
240
- // },
241
- // ],
242
- // symbol: 'BTCUSD'
243
- // }
244
- // }
245
- //
246
- const params = this.safeValue(message, 'params', {});
247
- const data = this.safeValue(params, 'data', []);
248
- const marketId = this.safeString(params, 'symbol');
249
- const market = this.safeMarket(marketId);
250
- const symbol = market['symbol'];
251
- const messageHash = 'trades:' + marketId;
252
- const tradesLimit = this.safeInteger(this.options, 'tradesLimit', 1000);
253
- let stored = this.safeValue(this.trades, symbol);
254
- if (stored === undefined) {
255
- stored = new ArrayCache(tradesLimit);
256
- this.trades[symbol] = stored;
257
- }
258
- if (Array.isArray(data)) {
259
- const trades = this.parseTrades(data, market);
487
+ // {
488
+ // "result": {
489
+ // "ch": "trades", // Channel
490
+ // "subscriptions": ["ETHBTC", "BTCUSDT"]
491
+ // },
492
+ // "id": 123
493
+ // }
494
+ //
495
+ // Notification snapshot
496
+ //
497
+ // {
498
+ // "ch": "trades", // Channel
499
+ // "snapshot": {
500
+ // "BTCUSDT": [{
501
+ // "t": 1626861109494, // Timestamp in milliseconds
502
+ // "i": 1555634969, // Trade identifier
503
+ // "p": "30881.96", // Price
504
+ // "q": "12.66828", // Quantity
505
+ // "s": "buy" // Side
506
+ // }]
507
+ // }
508
+ // }
509
+ //
510
+ // Notification update
511
+ //
512
+ // {
513
+ // "ch": "trades",
514
+ // "update": {
515
+ // "BTCUSDT": [{
516
+ // "t": 1626861123552,
517
+ // "i": 1555634969,
518
+ // "p": "30877.68",
519
+ // "q": "0.00006",
520
+ // "s": "sell"
521
+ // }]
522
+ // }
523
+ // }
524
+ //
525
+ const data = this.safeValue2(message, 'snapshot', 'update', {});
526
+ const marketIds = Object.keys(data);
527
+ for (let i = 0; i < marketIds.length; i++) {
528
+ const marketId = marketIds[i];
529
+ const market = this.safeMarket(marketId);
530
+ const tradesLimit = this.safeInteger(this.options, 'tradesLimit', 1000);
531
+ const symbol = market['symbol'];
532
+ let stored = this.safeValue(this.trades, symbol);
533
+ if (stored === undefined) {
534
+ stored = new ArrayCache(tradesLimit);
535
+ this.trades[symbol] = stored;
536
+ }
537
+ const trades = this.parseWsTrades(data[marketId], market);
260
538
  for (let i = 0; i < trades.length; i++) {
261
539
  stored.append(trades[i]);
262
540
  }
541
+ const messageHash = 'trades::' + symbol;
542
+ client.resolve(stored, messageHash);
263
543
  }
264
- else {
265
- const trade = this.parseTrade(message, market);
266
- stored.append(trade);
267
- }
268
- client.resolve(stored, messageHash);
269
544
  return message;
270
545
  }
546
+ parseWsTrades(trades, market = undefined, since = undefined, limit = undefined, params = {}) {
547
+ trades = this.toArray(trades);
548
+ let result = [];
549
+ for (let i = 0; i < trades.length; i++) {
550
+ const trade = this.extend(this.parseWsTrade(trades[i], market), params);
551
+ result.push(trade);
552
+ }
553
+ result = this.sortBy2(result, 'timestamp', 'id');
554
+ const symbol = this.safeString(market, 'symbol');
555
+ return this.filterBySymbolSinceLimit(result, symbol, since, limit);
556
+ }
557
+ parseWsTrade(trade, market = undefined) {
558
+ //
559
+ // {
560
+ // "t": 1626861123552, // Timestamp in milliseconds
561
+ // "i": 1555634969, // Trade identifier
562
+ // "p": "30877.68", // Price
563
+ // "q": "0.00006", // Quantity
564
+ // "s": "sell" // Side
565
+ // }
566
+ //
567
+ const timestamp = this.safeInteger(trade, 't');
568
+ return this.safeTrade({
569
+ 'info': trade,
570
+ 'id': this.safeString(trade, 'i'),
571
+ 'order': undefined,
572
+ 'timestamp': timestamp,
573
+ 'datetime': this.iso8601(timestamp),
574
+ 'symbol': this.safeString(market, 'symbol'),
575
+ 'type': undefined,
576
+ 'side': this.safeString(trade, 's'),
577
+ 'takerOrMaker': undefined,
578
+ 'price': this.safeString(trade, 'p'),
579
+ 'amount': this.safeString(trade, 'q'),
580
+ 'cost': undefined,
581
+ 'fee': undefined,
582
+ }, market);
583
+ }
271
584
  async watchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
272
585
  /**
273
586
  * @method
274
587
  * @name hitbtc#watchOHLCV
275
588
  * @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
589
+ * @see https://api.hitbtc.com/#subscribe-to-candles
276
590
  * @param {string} symbol unified symbol of the market to fetch OHLCV data for
277
- * @param {string} timeframe the length of time each candle represents
278
- * @param {int} [since] timestamp in ms of the earliest candle to fetch
279
- * @param {int} [limit] the maximum amount of candles to fetch
591
+ * @param {string} [timeframe] the length of time each candle represents
592
+ * @param {int} [since] not used by hitbtc watchOHLCV
593
+ * @param {int} [limit] 0 1000, default value = 0 (no history returned)
280
594
  * @param {object} [params] extra parameters specific to the hitbtc api endpoint
281
- * @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume
595
+ * @returns {[[int]]} A list of candles ordered as timestamp, open, high, low, close, volume
282
596
  */
283
- // if (limit === undefined) {
284
- // limit = 100;
285
- // }
286
597
  const period = this.safeString(this.timeframes, timeframe, timeframe);
598
+ const name = 'candles/' + period;
599
+ const market = this.market(symbol);
287
600
  const request = {
288
601
  'params': {
289
- 'period': period,
290
- // 'limit': limit,
602
+ 'symbols': [market['id']],
291
603
  },
292
604
  };
293
- const requestParams = this.deepExtend(request, params);
294
- const ohlcv = await this.watchPublic(symbol, 'ohlcv', period, requestParams);
605
+ if (limit !== undefined) {
606
+ request['params']['limit'] = limit;
607
+ }
608
+ const ohlcv = await this.subscribePublic(name, [symbol], this.deepExtend(request, params));
295
609
  if (this.newUpdates) {
296
610
  limit = ohlcv.getLimit(symbol, limit);
297
611
  }
298
- return this.filterBySinceLimit(ohlcv, since, limit, 0, true);
612
+ return this.filterBySinceLimit(ohlcv, since, limit, 0);
299
613
  }
300
614
  handleOHLCV(client, message) {
301
615
  //
302
- // {
303
- // jsonrpc: '2.0',
304
- // method: 'snapshotCandles', // updateCandles
305
- // params: {
306
- // data: [
307
- // {
308
- // timestamp: '2020-04-05T00:06:00.000Z',
309
- // open: '6869.40',
310
- // close: '6867.16',
311
- // min: '6863.17',
312
- // max: '6869.4',
313
- // volume: '0.08947',
314
- // volumeQuote: '614.4195442'
315
- // },
316
- // {
317
- // timestamp: '2020-04-05T00:07:00.000Z',
318
- // open: '6867.54',
319
- // close: '6859.26',
320
- // min: '6858.85',
321
- // max: '6867.54',
322
- // volume: '1.7766',
323
- // volumeQuote: '12191.5880395'
324
- // },
325
- // ],
326
- // symbol: 'BTCUSD',
327
- // period: 'M1'
328
- // }
329
- // }
330
- //
331
- const params = this.safeValue(message, 'params', {});
332
- const data = this.safeValue(params, 'data', []);
333
- const marketId = this.safeString(params, 'symbol');
334
- const market = this.safeMarket(marketId);
335
- const symbol = market['symbol'];
336
- const period = this.safeString(params, 'period');
616
+ // {
617
+ // "ch": "candles/M1", // Channel
618
+ // "snapshot": {
619
+ // "BTCUSDT": [{
620
+ // "t": 1626860340000, // Message timestamp
621
+ // "o": "30881.95", // Open price
622
+ // "c": "30890.96", // Last price
623
+ // "h": "30900.8", // High price
624
+ // "l": "30861.27", // Low price
625
+ // "v": "1.27852", // Base asset volume
626
+ // "q": "39493.9021811" // Quote asset volume
627
+ // }
628
+ // ...
629
+ // ]
630
+ // }
631
+ // }
632
+ //
633
+ // {
634
+ // "ch": "candles/M1",
635
+ // "update": {
636
+ // "ETHBTC": [{
637
+ // "t": 1626860880000,
638
+ // "o": "0.060711",
639
+ // "c": "0.060749",
640
+ // "h": "0.060749",
641
+ // "l": "0.060711",
642
+ // "v": "12.2800",
643
+ // "q": "0.7455339675"
644
+ // }]
645
+ // }
646
+ // }
647
+ //
648
+ const data = this.safeValue2(message, 'snapshot', 'update', {});
649
+ const marketIds = Object.keys(data);
650
+ const channel = this.safeString(message, 'ch');
651
+ const splitChannel = channel.split('/');
652
+ const period = this.safeString(splitChannel, 1);
337
653
  const timeframe = this.findTimeframe(period);
338
- const messageHash = 'ohlcv:' + marketId + ':' + period;
339
- for (let i = 0; i < data.length; i++) {
340
- const candle = data[i];
341
- const parsed = this.parseOHLCV(candle, market);
654
+ for (let i = 0; i < marketIds.length; i++) {
655
+ const marketId = marketIds[i];
656
+ const market = this.safeMarket(marketId);
657
+ const symbol = market['symbol'];
342
658
  this.ohlcvs[symbol] = this.safeValue(this.ohlcvs, symbol, {});
343
659
  let stored = this.safeValue(this.ohlcvs[symbol], timeframe);
344
660
  if (stored === undefined) {
@@ -346,11 +662,319 @@ export default class hitbtc extends hitbtcRest {
346
662
  stored = new ArrayCacheByTimestamp(limit);
347
663
  this.ohlcvs[symbol][timeframe] = stored;
348
664
  }
349
- stored.append(parsed);
665
+ const ohlcvs = this.parseWsOHLCVs(data[marketId], market);
666
+ for (let i = 0; i < ohlcvs.length; i++) {
667
+ stored.append(ohlcvs[i]);
668
+ }
669
+ const messageHash = channel + '::' + symbol;
350
670
  client.resolve(stored, messageHash);
351
671
  }
352
672
  return message;
353
673
  }
674
+ parseWsOHLCV(ohlcv, market = undefined) {
675
+ //
676
+ // {
677
+ // "t": 1626860340000, // Message timestamp
678
+ // "o": "30881.95", // Open price
679
+ // "c": "30890.96", // Last price
680
+ // "h": "30900.8", // High price
681
+ // "l": "30861.27", // Low price
682
+ // "v": "1.27852", // Base asset volume
683
+ // "q": "39493.9021811" // Quote asset volume
684
+ // }
685
+ //
686
+ return [
687
+ this.safeInteger(ohlcv, 't'),
688
+ this.safeNumber(ohlcv, 'o'),
689
+ this.safeNumber(ohlcv, 'h'),
690
+ this.safeNumber(ohlcv, 'l'),
691
+ this.safeNumber(ohlcv, 'c'),
692
+ this.safeNumber(ohlcv, 'v'),
693
+ ];
694
+ }
695
+ async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
696
+ /**
697
+ * @method
698
+ * @name hitbtc#watchOrders
699
+ * @description watches information on multiple orders made by the user
700
+ * @see https://api.hitbtc.com/#subscribe-to-reports
701
+ * @see https://api.hitbtc.com/#subscribe-to-reports-2
702
+ * @see https://api.hitbtc.com/#subscribe-to-reports-3
703
+ * @param {string} [symbol] unified CCXT market symbol
704
+ * @param {int} [since] timestamp in ms of the earliest order to fetch
705
+ * @param {int} [limit] the maximum amount of orders to fetch
706
+ * @param {object} [params] extra parameters specific to the hitbtc api endpoint
707
+ * @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
708
+ */
709
+ await this.loadMarkets();
710
+ let marketType = undefined;
711
+ let market = undefined;
712
+ if (symbol !== undefined) {
713
+ market = this.market(symbol);
714
+ }
715
+ [marketType, params] = this.handleMarketTypeAndParams('watchOrders', market, params);
716
+ const name = this.getSupportedMapping(marketType, {
717
+ 'spot': 'spot_subscribe',
718
+ 'margin': 'margin_subscribe',
719
+ 'swap': 'futures_subscribe',
720
+ 'future': 'futures_subscribe',
721
+ });
722
+ const orders = await this.subscribePrivate(name, symbol, params);
723
+ if (this.newUpdates) {
724
+ limit = orders.getLimit(symbol, limit);
725
+ }
726
+ return this.filterBySinceLimit(orders, since, limit, 'timestamp');
727
+ }
728
+ handleOrder(client, message) {
729
+ //
730
+ // {
731
+ // "jsonrpc": "2.0",
732
+ // "method": "spot_order", // "margin_order", "future_order"
733
+ // "params": {
734
+ // "id": 584244931496,
735
+ // "client_order_id": "b5acd79c0a854b01b558665bcf379456",
736
+ // "symbol": "BTCUSDT",
737
+ // "side": "buy",
738
+ // "status": "new",
739
+ // "type": "limit",
740
+ // "time_in_force": "GTC",
741
+ // "quantity": "0.01000",
742
+ // "quantity_cumulative": "0",
743
+ // "price": "0.01", // only updates and snapshots
744
+ // "post_only": false,
745
+ // "reduce_only": false, // only margin and contract
746
+ // "display_quantity": "0", // only updates and snapshot
747
+ // "created_at": "2021-07-02T22:52:32.864Z",
748
+ // "updated_at": "2021-07-02T22:52:32.864Z",
749
+ // "trade_id": 1361977606, // only trades
750
+ // "trade_quantity": "0.00001", // only trades
751
+ // "trade_price": "49595.04", // only trades
752
+ // "trade_fee": "0.001239876000", // only trades
753
+ // "trade_taker": true, // only trades, only spot
754
+ // "trade_position_id": 485308, // only trades, only margin
755
+ // "report_type": "new" // "trade", "status" (snapshot)
756
+ // }
757
+ // }
758
+ //
759
+ // {
760
+ // "jsonrpc": "2.0",
761
+ // "method": "spot_orders", // "margin_orders", "future_orders"
762
+ // "params": [
763
+ // {
764
+ // "id": 584244931496,
765
+ // "client_order_id": "b5acd79c0a854b01b558665bcf379456",
766
+ // "symbol": "BTCUSDT",
767
+ // "side": "buy",
768
+ // "status": "new",
769
+ // "type": "limit",
770
+ // "time_in_force": "GTC",
771
+ // "quantity": "0.01000",
772
+ // "quantity_cumulative": "0",
773
+ // "price": "0.01", // only updates and snapshots
774
+ // "post_only": false,
775
+ // "reduce_only": false, // only margin and contract
776
+ // "display_quantity": "0", // only updates and snapshot
777
+ // "created_at": "2021-07-02T22:52:32.864Z",
778
+ // "updated_at": "2021-07-02T22:52:32.864Z",
779
+ // "trade_id": 1361977606, // only trades
780
+ // "trade_quantity": "0.00001", // only trades
781
+ // "trade_price": "49595.04", // only trades
782
+ // "trade_fee": "0.001239876000", // only trades
783
+ // "trade_taker": true, // only trades, only spot
784
+ // "trade_position_id": 485308, // only trades, only margin
785
+ // "report_type": "new" // "trade", "status" (snapshot)
786
+ // }
787
+ // ]
788
+ // }
789
+ //
790
+ if (this.orders === undefined) {
791
+ const limit = this.safeInteger(this.options, 'ordersLimit');
792
+ this.orders = new ArrayCacheBySymbolById(limit);
793
+ }
794
+ const data = this.safeValue(message, 'params', []);
795
+ if (Array.isArray(data)) {
796
+ for (let i = 0; i < data.length; i++) {
797
+ const order = data[i];
798
+ this.handleOrderHelper(client, message, order);
799
+ }
800
+ }
801
+ else {
802
+ this.handleOrderHelper(client, message, data);
803
+ }
804
+ return message;
805
+ }
806
+ handleOrderHelper(client, message, order) {
807
+ const orders = this.orders;
808
+ const marketId = this.safeStringLower2(order, 'instrument', 'symbol');
809
+ const method = this.safeString(message, 'method');
810
+ const splitMethod = method.split('_order');
811
+ const messageHash = this.safeString(splitMethod, 0);
812
+ const symbol = this.safeSymbol(marketId);
813
+ const parsed = this.parseOrder(order);
814
+ orders.append(parsed);
815
+ client.resolve(orders, messageHash);
816
+ client.resolve(orders, messageHash + '::' + symbol);
817
+ }
818
+ parseWsOrderTrade(trade, market = undefined) {
819
+ //
820
+ // {
821
+ // "id": 584244931496,
822
+ // "client_order_id": "b5acd79c0a854b01b558665bcf379456",
823
+ // "symbol": "BTCUSDT",
824
+ // "side": "buy",
825
+ // "status": "new",
826
+ // "type": "limit",
827
+ // "time_in_force": "GTC",
828
+ // "quantity": "0.01000",
829
+ // "quantity_cumulative": "0",
830
+ // "price": "0.01", // only updates and snapshots
831
+ // "post_only": false,
832
+ // "reduce_only": false, // only margin and contract
833
+ // "display_quantity": "0", // only updates and snapshot
834
+ // "created_at": "2021-07-02T22:52:32.864Z",
835
+ // "updated_at": "2021-07-02T22:52:32.864Z",
836
+ // "trade_id": 1361977606, // only trades
837
+ // "trade_quantity": "0.00001", // only trades
838
+ // "trade_price": "49595.04", // only trades
839
+ // "trade_fee": "0.001239876000", // only trades
840
+ // "trade_taker": true, // only trades, only spot
841
+ // "trade_position_id": 485308, // only trades, only margin
842
+ // "report_type": "new" // "trade", "status" (snapshot)
843
+ // }
844
+ //
845
+ const timestamp = this.safeInteger(trade, 'created_at');
846
+ const marketId = this.safeString(trade, 'symbol');
847
+ return this.safeTrade({
848
+ 'info': trade,
849
+ 'id': this.safeString(trade, 'trade_id'),
850
+ 'order': this.safeString(trade, 'id'),
851
+ 'timestamp': timestamp,
852
+ 'datetime': this.iso8601(timestamp),
853
+ 'symbol': this.safeMarket(marketId, market),
854
+ 'type': undefined,
855
+ 'side': this.safeString(trade, 'side'),
856
+ 'takerOrMaker': this.safeString(trade, 'trade_taker'),
857
+ 'price': this.safeString(trade, 'trade_price'),
858
+ 'amount': this.safeString(trade, 'trade_quantity'),
859
+ 'cost': undefined,
860
+ 'fee': {
861
+ 'cost': this.safeString(trade, 'trade_fee'),
862
+ 'currency': undefined,
863
+ 'rate': undefined,
864
+ },
865
+ }, market);
866
+ }
867
+ parseWsOrder(order, market = undefined) {
868
+ //
869
+ // {
870
+ // "id": 584244931496,
871
+ // "client_order_id": "b5acd79c0a854b01b558665bcf379456",
872
+ // "symbol": "BTCUSDT",
873
+ // "side": "buy",
874
+ // "status": "new",
875
+ // "type": "limit",
876
+ // "time_in_force": "GTC",
877
+ // "quantity": "0.01000",
878
+ // "quantity_cumulative": "0",
879
+ // "price": "0.01", // only updates and snapshots
880
+ // "post_only": false,
881
+ // "reduce_only": false, // only margin and contract
882
+ // "display_quantity": "0", // only updates and snapshot
883
+ // "created_at": "2021-07-02T22:52:32.864Z",
884
+ // "updated_at": "2021-07-02T22:52:32.864Z",
885
+ // "trade_id": 1361977606, // only trades
886
+ // "trade_quantity": "0.00001", // only trades
887
+ // "trade_price": "49595.04", // only trades
888
+ // "trade_fee": "0.001239876000", // only trades
889
+ // "trade_taker": true, // only trades, only spot
890
+ // "trade_position_id": 485308, // only trades, only margin
891
+ // "report_type": "new" // "trade", "status" (snapshot)
892
+ // }
893
+ //
894
+ const timestamp = this.safeString(order, 'created_at');
895
+ const marketId = this.safeSymbol(order, 'symbol');
896
+ market = this.safeMarket(marketId, market);
897
+ const tradeId = this.safeString(order, 'trade_id');
898
+ let trades = undefined;
899
+ if (tradeId !== undefined) {
900
+ const trade = this.parseWsOrderTrade(order, market);
901
+ trades = [trade];
902
+ }
903
+ return this.safeOrder({
904
+ 'info': order,
905
+ 'id': this.safeString(order, 'id'),
906
+ 'clientOrderId': this.safeString(order, 'client_order_id'),
907
+ 'timestamp': timestamp,
908
+ 'datetime': this.iso8601(timestamp),
909
+ 'lastTradeTimestamp': undefined,
910
+ 'symbol': market['symbol'],
911
+ 'price': this.safeString(order, 'price'),
912
+ 'amount': this.safeString(order, 'quantity'),
913
+ 'type': this.safeString(order, 'type'),
914
+ 'side': this.safeStringUpper(order, 'side'),
915
+ 'timeInForce': this.safeString(order, 'time_in_force'),
916
+ 'postOnly': this.safeString(order, 'post_only'),
917
+ 'reduceOnly': this.safeValue(order, 'reduce_only'),
918
+ 'filled': undefined,
919
+ 'remaining': undefined,
920
+ 'cost': undefined,
921
+ 'status': this.parseOrderStatus(this.safeString(order, 'status')),
922
+ 'average': undefined,
923
+ 'trades': trades,
924
+ 'fee': undefined,
925
+ }, market);
926
+ }
927
+ async watchBalance(params = {}) {
928
+ /**
929
+ * @method
930
+ * @name hitbtc#watchBalance
931
+ * @description watches balance updates, cannot subscribe to margin account balances
932
+ * @see https://api.hitbtc.com/#subscribe-to-spot-balances
933
+ * @see https://api.hitbtc.com/#subscribe-to-futures-balances
934
+ * @param {object} [params] extra parameters specific to the hitbtc api endpoint
935
+ * @param {string} [params.type] 'spot', 'swap', or 'future'
936
+ *
937
+ * EXCHANGE SPECIFIC PARAMETERS
938
+ * @param {string} [params.mode] 'updates' or 'batches' (default), 'updates' = messages arrive after balance updates, 'batches' = messages arrive at equal intervals if there were any updates
939
+ * @returns {[object]} a list of [balance structures]{@link https://docs.ccxt.com/#/?id=balance-structure}
940
+ */
941
+ await this.loadMarkets();
942
+ let type = undefined;
943
+ [type, params] = this.handleMarketTypeAndParams('watchBalance', undefined, params);
944
+ const name = this.getSupportedMapping(type, {
945
+ 'spot': 'spot_balance_subscribe',
946
+ 'swap': 'futures_balance_subscribe',
947
+ 'future': 'futures_balance_subscribe',
948
+ });
949
+ const mode = this.safeString(params, 'mode', 'batches');
950
+ params = this.omit(params, 'mode');
951
+ const request = {
952
+ 'mode': mode,
953
+ };
954
+ return await this.subscribePrivate(name, undefined, this.extend(request, params));
955
+ }
956
+ handleBalance(client, message) {
957
+ //
958
+ // {
959
+ // "jsonrpc": "2.0",
960
+ // "method": "futures_balance",
961
+ // "params": [
962
+ // {
963
+ // "currency": "BCN",
964
+ // "available": "100.000000000000",
965
+ // "reserved": "0",
966
+ // "reserved_margin": "0"
967
+ // },
968
+ // ...
969
+ // ]
970
+ // }
971
+ //
972
+ const messageHash = this.safeString(message, 'method');
973
+ const params = this.safeValue(message, 'params');
974
+ const balance = this.parseBalance(params);
975
+ this.balance = this.deepExtend(this.balance, balance);
976
+ client.resolve(this.balance, messageHash);
977
+ }
354
978
  handleNotification(client, message) {
355
979
  //
356
980
  // { jsonrpc: '2.0', result: true, id: null }
@@ -358,22 +982,56 @@ export default class hitbtc extends hitbtcRest {
358
982
  return message;
359
983
  }
360
984
  handleMessage(client, message) {
361
- const methods = {
362
- 'snapshotOrderbook': this.handleOrderBookSnapshot,
363
- 'updateOrderbook': this.handleOrderBookUpdate,
364
- 'ticker': this.handleTicker,
365
- 'snapshotTrades': this.handleTrades,
366
- 'updateTrades': this.handleTrades,
367
- 'snapshotCandles': this.handleOHLCV,
368
- 'updateCandles': this.handleOHLCV,
369
- };
370
- const event = this.safeString(message, 'method');
371
- const method = this.safeValue(methods, event);
372
- if (method === undefined) {
373
- this.handleNotification(client, message);
985
+ let channel = this.safeString2(message, 'ch', 'method');
986
+ if (channel !== undefined) {
987
+ const splitChannel = channel.split('/');
988
+ channel = this.safeString(splitChannel, 0);
989
+ const methods = {
990
+ 'candles': this.handleOHLCV,
991
+ 'ticker': this.handleTicker,
992
+ 'trades': this.handleTrades,
993
+ 'orderbook': this.handleOrderBook,
994
+ 'spot_order': this.handleOrder,
995
+ 'spot_orders': this.handleOrder,
996
+ 'margin_order': this.handleOrder,
997
+ 'margin_orders': this.handleOrder,
998
+ 'futures_order': this.handleOrder,
999
+ 'futures_orders': this.handleOrder,
1000
+ 'spot_balance': this.handleBalance,
1001
+ 'futures_balance': this.handleBalance,
1002
+ };
1003
+ const method = this.safeValue(methods, channel);
1004
+ if (method !== undefined) {
1005
+ method.call(this, client, message);
1006
+ }
374
1007
  }
375
1008
  else {
376
- method.call(this, client, message);
1009
+ const success = this.safeValue(message, 'result');
1010
+ if ((success === true) && !('id' in message)) {
1011
+ this.handleAuthenticate(client, message);
1012
+ }
1013
+ }
1014
+ }
1015
+ handleAuthenticate(client, message) {
1016
+ //
1017
+ // {
1018
+ // jsonrpc: '2.0',
1019
+ // result: true
1020
+ // }
1021
+ //
1022
+ const success = this.safeValue(message, 'result');
1023
+ const messageHash = 'authenticated';
1024
+ if (success) {
1025
+ const future = this.safeValue(client.futures, messageHash);
1026
+ future.resolve(true);
377
1027
  }
1028
+ else {
1029
+ const error = new AuthenticationError(this.id + ' ' + this.json(message));
1030
+ client.reject(error, messageHash);
1031
+ if (messageHash in client.subscriptions) {
1032
+ delete client.subscriptions[messageHash];
1033
+ }
1034
+ }
1035
+ return message;
378
1036
  }
379
1037
  }