@icgio/icg-exchanges 1.40.42 → 1.40.43

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 (43) hide show
  1. package/API_DOCS.md +6 -6
  2. package/lib/exchanges/ascendex.js +7 -3
  3. package/lib/exchanges/biconomy.js +65 -2
  4. package/lib/exchanges/binance.js +65 -3
  5. package/lib/exchanges/bingx.js +73 -2
  6. package/lib/exchanges/bitfinex.js +76 -0
  7. package/lib/exchanges/bitget.js +86 -3
  8. package/lib/exchanges/bithumb.js +3 -0
  9. package/lib/exchanges/bitkub.js +3 -0
  10. package/lib/exchanges/bitmart.js +3 -2
  11. package/lib/exchanges/bitmex.js +71 -1
  12. package/lib/exchanges/bitrue.js +59 -2
  13. package/lib/exchanges/bitstamp.js +71 -0
  14. package/lib/exchanges/blofin.js +76 -3
  15. package/lib/exchanges/btse.js +6 -2
  16. package/lib/exchanges/bybit.js +3 -2
  17. package/lib/exchanges/coinbase.js +113 -8
  18. package/lib/exchanges/coinstore.js +6 -2
  19. package/lib/exchanges/cryptocom.js +69 -2
  20. package/lib/exchanges/deepcoin.js +64 -0
  21. package/lib/exchanges/digifinex.js +62 -0
  22. package/lib/exchanges/exchange-base.js +167 -1
  23. package/lib/exchanges/exchange-ws.js +2 -1
  24. package/lib/exchanges/fastex.js +3 -0
  25. package/lib/exchanges/gate.js +4 -2
  26. package/lib/exchanges/gemini.js +70 -0
  27. package/lib/exchanges/hashkey.js +70 -2
  28. package/lib/exchanges/hashkeyglobal.js +70 -2
  29. package/lib/exchanges/hitbtc.js +2 -1
  30. package/lib/exchanges/hkbitex.js +6 -2
  31. package/lib/exchanges/htx.js +78 -3
  32. package/lib/exchanges/indodax.js +3 -0
  33. package/lib/exchanges/kraken.js +63 -0
  34. package/lib/exchanges/kucoin.js +4 -3
  35. package/lib/exchanges/lbank.js +89 -53
  36. package/lib/exchanges/mexc.js +61 -2
  37. package/lib/exchanges/okx.js +4 -3
  38. package/lib/exchanges/phemex.js +4 -3
  39. package/lib/exchanges/swft.js +12 -9
  40. package/lib/exchanges/upbit.js +60 -2
  41. package/lib/exchanges/weex.js +3 -0
  42. package/lib/exchanges/xt.js +64 -2
  43. package/package.json +2 -2
package/API_DOCS.md CHANGED
@@ -8,9 +8,9 @@ Quick reference for official API documentation of all supported exchanges.
8
8
  |----------|-------------------|
9
9
  | Ascendex | https://ascendex.github.io/ascendex-pro-api/ |
10
10
  | Biconomy | https://github.com/BiconomyOfficial/APIDocs |
11
- | Binance | https://binance-docs.github.io/apidocs/spot/en/ |
11
+ | Binance | https://developers.binance.com/docs/binance-spot-api-docs/README |
12
12
  | Bingx | https://bingx-api.github.io/docs/ |
13
- | Bitfinex | https://docs.bitfinex.com/docs |
13
+ | Bitfinex | https://docs.bitfinex.com/reference |
14
14
  | Bitget | https://www.bitget.com/api-doc/spot/intro |
15
15
  | Bithumb | https://apidocs.bithumb.com/ |
16
16
  | Bitkub | https://github.com/bitkub/bitkub-official-api-docs |
@@ -31,13 +31,13 @@ Quick reference for official API documentation of all supported exchanges.
31
31
  | Digifinex | https://docs.digifinex.com/ |
32
32
  | Gate | https://www.gate.io/docs/developers/apiv4/ |
33
33
  | Gemini | https://docs.gemini.com/ |
34
- | Hashkey | https://hashkeypro-apidoc.readme.io/reference/introduction |
35
- | HashkeyGlobal | https://hashkeyglobal-apidoc.readme.io/reference/introduction |
34
+ | Hashkey | https://docs.hashkey.com/uae/en/ |
35
+ | HashkeyGlobal | https://docs.hashkey.com/glb |
36
36
  | Hitbtc | https://api.hitbtc.com/ |
37
37
  | Hkbitex | https://www.hkbitex.com.hk |
38
38
  | Htx | https://huobiapi.github.io/docs/spot/v1/en/ |
39
39
  | Indodax | https://github.com/btcid/indodax-official-api-docs |
40
- | Kraken | https://docs.kraken.com/rest/ |
40
+ | Kraken | https://docs.kraken.com/api/ |
41
41
  | Kucoin | https://docs.kucoin.com/ |
42
42
  | Lbank | https://www.lbank.com/en-US/docs/index.html |
43
43
  | Mexc | https://mexcdevelop.github.io/apidocs/ |
@@ -46,6 +46,6 @@ Quick reference for official API documentation of all supported exchanges.
46
46
  | Phemex | https://phemex-docs.github.io/ |
47
47
  | Poloniex | https://docs.poloniex.com/ |
48
48
  | Swft | https://www.swft.pro |
49
- | Upbit | https://docs.upbit.com/ |
49
+ | Upbit | https://global-docs.upbit.com/ |
50
50
  | Weex | https://www.weex.com/api-doc |
51
51
  | Xt | https://doc.xt.com/ |
@@ -112,8 +112,9 @@ module.exports = class Ascendex extends ExchangeBase {
112
112
  }),
113
113
  )
114
114
  })
115
- this.ws.market.on_message((data) => {
115
+ this.ws.market.on_message((data, t_recv) => {
116
116
  data = JSON.parse(data)
117
+ const t_parsed = process.hrtime()
117
118
  if (data.op === 'pong') {
118
119
  this.ws.market.last_pong = new Date()
119
120
  } else {
@@ -126,7 +127,7 @@ module.exports = class Ascendex extends ExchangeBase {
126
127
  for (let bid of data.data.bids) {
127
128
  this.ws.market.bids_dict[pair].insert(bid[0], bid[1])
128
129
  }
129
- this.ws.market.ts_dict[pair] = { received_ts: Date.now() }
130
+ this.ws.market.ts_dict[pair] = { received_ts: Date.now(), t_recv, t_parsed }
130
131
  const asks_s = this.ws.market.asks_dict[pair].entries()
131
132
  const bids_s = this.ws.market.bids_dict[pair].entries()
132
133
  if (asks_s.length > 0 && bids_s.length > 0) {
@@ -152,7 +153,7 @@ module.exports = class Ascendex extends ExchangeBase {
152
153
  this.ws.market.bids_dict[pair].insert(parseFloat(bid[0]), parseFloat(bid[1]))
153
154
  }
154
155
  }
155
- this.ws.market.ts_dict[pair] = { received_ts: Date.now() }
156
+ this.ws.market.ts_dict[pair] = { received_ts: Date.now(), t_recv, t_parsed }
156
157
  const asks_u = this.ws.market.asks_dict[pair].entries()
157
158
  const bids_u = this.ws.market.bids_dict[pair].entries()
158
159
  if (asks_u.length > 0 && bids_u.length > 0) {
@@ -478,6 +479,9 @@ module.exports = class Ascendex extends ExchangeBase {
478
479
  }
479
480
  this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
480
481
  }
482
+ get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
483
+ this._get_history_trades_via_timeframe(pair, start_time_in_ms, end_time_in_ms, cb)
484
+ }
481
485
  static get_min_amount(cb) {
482
486
  let min_cur_amount = {}
483
487
  needle.get(base_url + '/api/pro/v1/cash/products', (err, res, body) => {
@@ -7,6 +7,7 @@ const needle = require('needle')
7
7
 
8
8
  const ExchangeBase = require('./exchange-base.js')
9
9
  const ExchangeWs = require('./exchange-ws.js')
10
+ const { filter_history_in_range, sleep, sort_history_rows } = ExchangeBase.history_utils
10
11
 
11
12
  const { error_codes } = require('../error_codes.json')
12
13
 
@@ -108,8 +109,9 @@ module.exports = class Biconomy extends ExchangeBase {
108
109
  }),
109
110
  )
110
111
  })
111
- this.ws.market.on_message((data) => {
112
+ this.ws.market.on_message((data, t_recv) => {
112
113
  data = JSON.parse(data)
114
+ const t_parsed = process.hrtime()
113
115
  if (data && data.method === 'depth.update') {
114
116
  let pair = post_process_pair(data.params[2])
115
117
  this.ws.market.pair_status[pair] = 'opened'
@@ -146,7 +148,7 @@ module.exports = class Biconomy extends ExchangeBase {
146
148
  }
147
149
  }
148
150
  }
149
- this.ws.market.ts_dict[pair] = { received_ts: Date.now() }
151
+ this.ws.market.ts_dict[pair] = { received_ts: Date.now(), t_recv, t_parsed }
150
152
  const asks_e = this.ws.market.asks_dict[pair].entries()
151
153
  const bids_e = this.ws.market.bids_dict[pair].entries()
152
154
  if (asks_e.length > 0 && bids_e.length > 0) {
@@ -551,6 +553,67 @@ module.exports = class Biconomy extends ExchangeBase {
551
553
  }
552
554
  this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
553
555
  }
556
+ get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
557
+ const get_history_trades_base = (pair, start_time_in_ms, end_time_in_ms, cb) => {
558
+ let [api_key, secret_key] = this.api_secret_key
559
+ ;(async () => {
560
+ const rows = []
561
+ const [, quote_cur] = utils.parse_pair(pair)
562
+ let offset = 0
563
+ const limit = 100
564
+ while (true) {
565
+ let params = {
566
+ api_key: api_key,
567
+ limit,
568
+ market: pre_process_pair(pair),
569
+ offset,
570
+ }
571
+ params.sign = get_signature_biconomy(secret_key, params)
572
+ const body = await new Promise((resolve, reject) => {
573
+ needle.post('https://api.biconomy.com/api/v1/private/order/finished', params, options, (err, res, body) => {
574
+ if (body && body.code === 0 && body.result && Array.isArray(body.result.records)) {
575
+ resolve(body)
576
+ } else if (body) {
577
+ reject({ error: error_codes.other, body })
578
+ } else {
579
+ reject({ error: error_codes.timeout })
580
+ }
581
+ })
582
+ })
583
+ const records = body.result.records
584
+ if (records.length === 0) break
585
+ for (const record of records) {
586
+ const close_time_ms = Math.floor(parseFloat(record.ftime) * 1000)
587
+ if (close_time_ms < start_time_in_ms) {
588
+ cb({ success: true, body: sort_history_rows(filter_history_in_range(rows, start_time_in_ms, end_time_in_ms)) })
589
+ return
590
+ }
591
+ if (close_time_ms >= end_time_in_ms || parseFloat(record.deal_stock) <= 0) continue
592
+ rows.push({
593
+ order_id: record.id != null ? record.id.toString() : '',
594
+ pair,
595
+ type: parseFloat(record.side) === 2 ? 'buy' : 'sell',
596
+ price: parseFloat(record.price),
597
+ amount: parseFloat(record.deal_stock),
598
+ total: parseFloat(record.deal_money),
599
+ close_time: new Date(close_time_ms),
600
+ fees: {
601
+ [quote_cur]: parseFloat(record.maker_fee) > 0 ? parseFloat(record.maker_fee) : parseFloat(record.taker_fee),
602
+ },
603
+ role: 'maker',
604
+ })
605
+ }
606
+ if (records.length < limit) break
607
+ offset += limit
608
+ await sleep(50)
609
+ }
610
+ cb({ success: true, body: sort_history_rows(filter_history_in_range(rows, start_time_in_ms, end_time_in_ms)) })
611
+ })().catch((error) => {
612
+ cb({ success: false, error: error?.error || error_codes.other, body: error?.body })
613
+ })
614
+ }
615
+ this.private_limiter.process(get_history_trades_base, pair, start_time_in_ms, end_time_in_ms, cb)
616
+ }
554
617
  // get_all_trades (timeframe_in_ms, cb) {
555
618
  //
556
619
  // }
@@ -73,8 +73,9 @@ module.exports = class Binance extends ExchangeBase {
73
73
  ;((this.rate_last_id = {}), (this.first_rate = {}))
74
74
  this.ws.market_socket = new websocket('wss://stream.binance.com:9443/stream?streams=' + streams.slice(0, -1))
75
75
  this.ws.market.on_open(() => {})
76
- this.ws.market.on_message((data) => {
76
+ this.ws.market.on_message((data, t_recv) => {
77
77
  data = JSON.parse(data).data
78
+ const t_parsed = process.hrtime()
78
79
  if (data.e === 'depthUpdate') {
79
80
  let pair = post_process_pair(data.s, true)
80
81
  this.ws.market.pair_status[pair] = 'opened'
@@ -103,7 +104,7 @@ module.exports = class Binance extends ExchangeBase {
103
104
  this.ws.market.bids_dict[pair].insert(parseFloat(bid[0]), parseFloat(bid[1]))
104
105
  }
105
106
  }
106
- this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: data.E }
107
+ this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: data.E, t_recv, t_parsed }
107
108
  } else if (this.first_rate[pair] && data.U > lastu[pair] + 1) {
108
109
  this.first_rate[pair] = false
109
110
  }
@@ -134,7 +135,7 @@ module.exports = class Binance extends ExchangeBase {
134
135
  if (options.depth === false) {
135
136
  this.ws.market.asks_dict[pair] = [[parseFloat(data.a), parseFloat(data.A)]]
136
137
  this.ws.market.bids_dict[pair] = [[parseFloat(data.b), parseFloat(data.B)]]
137
- this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: data.E || Date.now() }
138
+ this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: data.E || Date.now(), t_recv, t_parsed }
138
139
  this.ws.market.pair_status[pair] = 'opened'
139
140
  }
140
141
  this.ws.bbo_observable.notify()
@@ -656,6 +657,67 @@ module.exports = class Binance extends ExchangeBase {
656
657
  }
657
658
  this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
658
659
  }
660
+ get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
661
+ let [api_key, secret_key] = this.api_secret_key
662
+ let { fetch_split_windows, filter_history_in_range, sort_history_rows } = ExchangeBase.history_utils
663
+ let request_options = {
664
+ headers: {
665
+ ...options.headers,
666
+ 'X-MBX-APIKEY': api_key,
667
+ },
668
+ }
669
+ let path = '/api/v3/myTrades'
670
+ let fetch_window = (window_start_ms, window_stop_ms) =>
671
+ new Promise((resolve, reject) => {
672
+ let params = {
673
+ symbol: pre_process_pair(pair),
674
+ startTime: window_start_ms,
675
+ endTime: window_stop_ms - 1,
676
+ limit: 1000,
677
+ timestamp: Date.now(),
678
+ }
679
+ params.signature = get_signature_binance(secret_key, params)
680
+ let url = 'https://api.binance.com' + path + '?' + qs.stringify(params)
681
+ this.private_limiter.process(needle.get, url, request_options, (err, res, body) => {
682
+ if (body && Array.isArray(body)) {
683
+ resolve(
684
+ body.map((trade) => ({
685
+ order_id: trade.orderId.toString(),
686
+ trade_id: trade.id.toString(),
687
+ pair,
688
+ type: trade.isBuyer ? 'buy' : 'sell',
689
+ price: parseFloat(trade.price),
690
+ amount: parseFloat(trade.qty),
691
+ total: parseFloat(trade.quoteQty || parseFloat(trade.price) * parseFloat(trade.qty)),
692
+ close_time: new Date(trade.time),
693
+ fees: {
694
+ [post_process_cur(trade.commissionAsset)]: parseFloat(trade.commission),
695
+ },
696
+ role: trade.isMaker ? 'maker' : 'taker',
697
+ }))
698
+ )
699
+ } else if (body && body.code === -1021 && body.msg.startsWith('Timestamp for this request')) {
700
+ reject({ success: false, error: error_codes.request_error, body })
701
+ } else if (body) {
702
+ reject({ success: false, error: error_codes.other, body })
703
+ } else {
704
+ reject({ success: false, error: error_codes.timeout })
705
+ }
706
+ })
707
+ })
708
+ ;(async () => {
709
+ let trades = await fetch_split_windows({
710
+ start_ms: start_time_in_ms,
711
+ stop_ms: end_time_in_ms,
712
+ initial_window_ms: 7 * 24 * 60 * 60 * 1000,
713
+ page_limit: 1000,
714
+ fetch_window,
715
+ delay_ms: 250,
716
+ })
717
+ trades = sort_history_rows(filter_history_in_range(trades, start_time_in_ms, end_time_in_ms))
718
+ cb({ success: true, body: trades })
719
+ })().catch((error) => cb(error))
720
+ }
659
721
  // get_trades_ws (pair, timeframe_in_ms, cb) {
660
722
  // if (this.ws.trading.status === 'opened') {
661
723
  // if (!this.ws.trading.first_trades[pair]) {
@@ -8,6 +8,7 @@ const zlib = require('zlib')
8
8
 
9
9
  const ExchangeBase = require('./exchange-base.js')
10
10
  const ExchangeWs = require('./exchange-ws.js')
11
+ const { fetch_split_windows, sort_history_rows, with_retry } = ExchangeBase.history_utils
11
12
 
12
13
  const { error_codes } = require('../error_codes.json')
13
14
 
@@ -135,20 +136,21 @@ module.exports = class Bingx extends ExchangeBase {
135
136
  this.ws.market.bids_dict[pair] = utils.ordered_dict(false)
136
137
  })
137
138
  })
138
- this.ws.market.on_message((data) => {
139
+ this.ws.market.on_message((data, t_recv) => {
139
140
  zlib.gunzip(data, (err, decompressedData) => {
140
141
  if (err) {
141
142
  console.error('Error decompressing data:', err)
142
143
  } else {
143
144
  let data = decompressedData.toString('utf8')
144
145
  data = JSON.parse(data)
146
+ const t_parsed = process.hrtime()
145
147
  if (data && _.includes(data.dataType, 'depth')) {
146
148
  let parts = data.dataType.split('@')
147
149
  let pair = post_process_pair(parts[0])
148
150
  this.ws.market.pair_status[pair] = 'opened'
149
151
  this.ws.market.asks_dict[pair] = data.data.asks.map((ask) => [parseFloat(ask[0]), parseFloat(ask[1])]).reverse()
150
152
  this.ws.market.bids_dict[pair] = data.data.bids.map((bid) => [parseFloat(bid[0]), parseFloat(bid[1])])
151
- this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: data.timestamp }
153
+ this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: data.timestamp, t_recv, t_parsed }
152
154
  const asks = this.ws.market.asks_dict[pair]
153
155
  const bids = this.ws.market.bids_dict[pair]
154
156
  if (asks && asks.length > 0 && bids && bids.length > 0) {
@@ -527,6 +529,75 @@ module.exports = class Bingx extends ExchangeBase {
527
529
  }
528
530
  this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
529
531
  }
532
+ get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
533
+ const get_history_trades_base = (pair, start_time_in_ms, end_time_in_ms, cb) => {
534
+ let [api_key, secret_key] = this.api_secret_key
535
+ const options = get_options(api_key)
536
+ ;(async () => {
537
+ const rows = await fetch_split_windows({
538
+ start_ms: start_time_in_ms,
539
+ stop_ms: end_time_in_ms,
540
+ initial_window_ms: 60 * 60 * 1000,
541
+ page_limit: 1000,
542
+ fetch_window: async (window_start_ms, window_stop_ms) => {
543
+ const body = await with_retry(
544
+ async () =>
545
+ await new Promise((resolve, reject) => {
546
+ let params = {
547
+ limit: 1000,
548
+ startTime: window_start_ms,
549
+ endTime: window_stop_ms - 1,
550
+ symbol: pre_process_pair(pair),
551
+ timestamp: new Date().getTime(),
552
+ }
553
+ let signature = get_signature_bingx(secret_key, params)
554
+ let url = 'https://open-api.bingx.com/openApi/spot/v1/trade/myTrades?' + qs.stringify(params) + '&signature=' + signature
555
+ needle.get(url, options, (err, res, body) => {
556
+ body = parse_body(body)
557
+ if (body && body.code === 0 && body.data && Array.isArray(body.data.fills)) {
558
+ resolve(body)
559
+ } else {
560
+ const error = new Error(`bingx myTrades failed: ${JSON.stringify(body).slice(0, 300)}`)
561
+ error.body = body
562
+ error.code = body?.code
563
+ reject(error)
564
+ }
565
+ })
566
+ }),
567
+ {
568
+ retries: 5,
569
+ should_retry: (error) => error?.code === 100410 || /rate limited/i.test(String(error?.message || '')),
570
+ backoff_ms: (attempt, error) => {
571
+ const unblock_match = String(error?.message || '').match(/unblocked after (\d+)/i)
572
+ if (unblock_match) {
573
+ return Math.max(parseInt(unblock_match[1], 10) - Date.now(), 0) + 1000
574
+ }
575
+ return attempt * 3000
576
+ },
577
+ },
578
+ )
579
+ return body.data.fills.map((trade) => ({
580
+ order_id: trade.orderId != null ? trade.orderId.toString() : '',
581
+ pair,
582
+ type: trade.isBuyer ? 'buy' : 'sell',
583
+ price: parseFloat(trade.price),
584
+ amount: parseFloat(trade.qty),
585
+ total: parseFloat(trade.quoteQty || parseFloat(trade.price) * parseFloat(trade.qty)),
586
+ close_time: new Date(trade.time),
587
+ fees: {
588
+ [post_process_cur(trade.commissionAsset)]: parseFloat(trade.commission),
589
+ },
590
+ role: trade.isMaker ? 'maker' : 'taker',
591
+ }))
592
+ },
593
+ })
594
+ cb({ success: true, body: sort_history_rows(rows) })
595
+ })().catch((error) => {
596
+ cb({ success: false, error: error_codes.other, body: error?.body })
597
+ })
598
+ }
599
+ this.private_limiter.process(get_history_trades_base, pair, start_time_in_ms, end_time_in_ms, cb)
600
+ }
530
601
  get_all_deposits(timeframe_in_ms, cb) {
531
602
  let get_all_deposits_base = (timeframe_in_ms, cb) => {
532
603
  let [api_key, secret_key] = this.api_secret_key
@@ -80,6 +80,17 @@ function get_options(request, api_secret_key, url) {
80
80
  }
81
81
  }
82
82
 
83
+ function get_v2_headers_bitfinex(path, body, api_secret_key) {
84
+ let nonce = (Date.now() * 1000).toString()
85
+ let auth = '/api' + path + nonce + body
86
+ return {
87
+ 'bfx-nonce': nonce,
88
+ 'bfx-apikey': api_secret_key[0],
89
+ 'bfx-signature': get_signature_bitfinex(auth, api_secret_key[1]),
90
+ 'Content-Type': 'application/json',
91
+ }
92
+ }
93
+
83
94
  module.exports = class Bitfinex extends ExchangeBase {
84
95
  constructor(api_key, secret_key, settings) {
85
96
  super('bitfinex', settings.id, api_key, secret_key)
@@ -774,6 +785,71 @@ module.exports = class Bitfinex extends ExchangeBase {
774
785
  }
775
786
  this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
776
787
  }
788
+ get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
789
+ let { fetch_split_windows, filter_history_in_range, sort_history_rows } = ExchangeBase.history_utils
790
+ let path = '/v2/auth/r/trades/' + pre_process_pair(pair, true) + '/hist'
791
+ let api_secret_key = this.api_secret_key
792
+ let fetch_window = (window_start_ms, window_stop_ms) =>
793
+ new Promise((resolve, reject) => {
794
+ let params = {
795
+ start: window_start_ms,
796
+ end: window_stop_ms - 1,
797
+ limit: 1000,
798
+ sort: 1,
799
+ }
800
+ let body = JSON.stringify(params)
801
+ let options = {
802
+ headers: get_v2_headers_bitfinex(path, body, api_secret_key),
803
+ }
804
+ this.private_limiter.process(needle.post, 'https://api.bitfinex.com' + path, body, options, (err, res, response_body) => {
805
+ if (typeof response_body === 'string') {
806
+ try {
807
+ response_body = JSON.parse(response_body)
808
+ } catch (e) {}
809
+ }
810
+ if (Array.isArray(response_body)) {
811
+ resolve(
812
+ response_body.map((trade) => {
813
+ let amount = Math.abs(parseFloat(trade[4]))
814
+ let mapped_trade = {
815
+ order_id: (trade[3] || '').toString(),
816
+ trade_id: trade[0].toString(),
817
+ pair: post_process_pair(trade[1]),
818
+ type: parseFloat(trade[4]) >= 0 ? 'buy' : 'sell',
819
+ price: parseFloat(trade[5]),
820
+ amount,
821
+ total: amount * parseFloat(trade[5]),
822
+ close_time: new Date(trade[2]),
823
+ role: trade[8] ? 'maker' : 'taker',
824
+ }
825
+ if (trade[10] && trade[9] !== undefined) {
826
+ mapped_trade.fees = {
827
+ [post_process_cur(trade[10])]: Math.abs(parseFloat(trade[9])),
828
+ }
829
+ }
830
+ return mapped_trade
831
+ })
832
+ )
833
+ } else if (response_body) {
834
+ reject({ success: false, error: error_codes.other, body: response_body })
835
+ } else {
836
+ reject({ success: false, error: error_codes.timeout })
837
+ }
838
+ })
839
+ })
840
+ ;(async () => {
841
+ let trades = await fetch_split_windows({
842
+ start_ms: start_time_in_ms,
843
+ stop_ms: end_time_in_ms,
844
+ initial_window_ms: 30 * 24 * 60 * 60 * 1000,
845
+ page_limit: 1000,
846
+ fetch_window,
847
+ delay_ms: 250,
848
+ })
849
+ trades = sort_history_rows(filter_history_in_range(trades, start_time_in_ms, end_time_in_ms))
850
+ cb({ success: true, body: trades })
851
+ })().catch((error) => cb(error))
852
+ }
777
853
  // get_trades_ws (pair, timeframe_in_ms, cb) {
778
854
  // if (this.ws.trading.status === 'opened') {
779
855
  // if (this.ws.trading.trades_dict[pair]) {
@@ -56,6 +56,24 @@ function post_process_cur(cur) {
56
56
  return cur.toUpperCase()
57
57
  }
58
58
 
59
+ function get_trade_fees_bitget(fee_detail) {
60
+ let detail = fee_detail
61
+ if (typeof detail === 'string') {
62
+ try {
63
+ detail = JSON.parse(detail)
64
+ } catch (e) {
65
+ detail = null
66
+ }
67
+ }
68
+ if (!detail || typeof detail !== 'object') return {}
69
+ let fee_coin = detail.feeCoin || detail.feeCoinName || detail.coin
70
+ let fee = parseFloat(detail.totalFee ?? detail.fee ?? detail.feeAmount)
71
+ if (!fee_coin || !Number.isFinite(fee)) return {}
72
+ return {
73
+ [post_process_cur(fee_coin)]: Math.abs(fee),
74
+ }
75
+ }
76
+
59
77
  module.exports = class Bitget extends ExchangeBase {
60
78
  constructor(api_key, secret_key, settings) {
61
79
  super('bitget', settings.id, api_key, secret_key)
@@ -129,18 +147,19 @@ module.exports = class Bitget extends ExchangeBase {
129
147
  })
130
148
  this.ws.market.ping('ping')
131
149
  })
132
- this.ws.market.on_message((data) => {
150
+ this.ws.market.on_message((data, t_recv) => {
133
151
  if (data.toString() === 'pong') {
134
152
  this.ws.market.last_pong = new Date()
135
153
  } else {
136
154
  data = JSON.parse(data)
155
+ const t_parsed = process.hrtime()
137
156
  let book = data.data
138
157
  if (data && data.arg && data.arg.channel === depth_val && Array.isArray(book) && book) {
139
158
  let pair = data.arg.instId
140
159
  this.ws.market.pair_status[pair] = 'opened'
141
160
  this.ws.market.asks_dict[pair] = book[0].asks.map((ask) => [parseFloat(ask[0]), parseFloat(ask[1])])
142
161
  this.ws.market.bids_dict[pair] = book[0].bids.map((bid) => [parseFloat(bid[0]), parseFloat(bid[1])])
143
- this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: parseInt(book[0].ts) }
162
+ this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: parseInt(book[0].ts), t_recv, t_parsed }
144
163
  } else if (data && data.arg && data.arg.channel === ticker_val && Array.isArray(book) && book) {
145
164
  let pair = data.arg.instId
146
165
  let t = book[0]
@@ -157,7 +176,7 @@ module.exports = class Bitget extends ExchangeBase {
157
176
  if (options.depth === false) {
158
177
  this.ws.market.asks_dict[pair] = [[ask_1_price, ask_1_amount]]
159
178
  this.ws.market.bids_dict[pair] = [[bid_1_price, bid_1_amount]]
160
- this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: parseInt(t.ts || data.ts) || Date.now() }
179
+ this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: parseInt(t.ts || data.ts) || Date.now(), t_recv, t_parsed }
161
180
  this.ws.market.pair_status[pair] = 'opened'
162
181
  }
163
182
  this.ws.bbo_observable.notify()
@@ -491,6 +510,70 @@ module.exports = class Bitget extends ExchangeBase {
491
510
  }
492
511
  this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
493
512
  }
513
+ get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
514
+ let [api_key, secret_key] = this.api_secret_key
515
+ let { filter_history_in_range, sort_history_rows, split_windows, sleep } = ExchangeBase.history_utils
516
+ let path = '/api/v2/spot/trade/fills'
517
+ let fetch_page = (params) =>
518
+ new Promise((resolve, reject) => {
519
+ let options = get_options(api_key, secret_key, this.passphrase, 'GET', path, params)
520
+ let url = base_url + path + '?' + qs.stringify(params)
521
+ this.private_limiter.process(needle.get, url, options, (err, res, body) => {
522
+ if (body && body.code === '00000' && Array.isArray(body.data)) {
523
+ resolve(body.data)
524
+ } else if (body) {
525
+ reject({ success: false, error: error_codes.other, body })
526
+ } else {
527
+ reject({ success: false, error: error_codes.timeout })
528
+ }
529
+ })
530
+ })
531
+ ;(async () => {
532
+ let trades = []
533
+ let windows = split_windows(start_time_in_ms, end_time_in_ms, 90 * 24 * 60 * 60 * 1000)
534
+ for (let [window_start_ms, window_stop_ms] of windows) {
535
+ let cursor
536
+ while (true) {
537
+ let params = {
538
+ symbol: pre_process_pair(pair),
539
+ startTime: window_start_ms.toString(),
540
+ endTime: (window_stop_ms - 1).toString(),
541
+ limit: '100',
542
+ }
543
+ if (cursor) params.idLessThan = cursor
544
+ let rows = await fetch_page(params)
545
+ trades.push(
546
+ ...rows.map((trade) => {
547
+ let price = parseFloat(trade.priceAvg || trade.price)
548
+ let amount = parseFloat(trade.size)
549
+ let total = parseFloat(trade.amount)
550
+ let mapped_trade = {
551
+ order_id: trade.orderId.toString(),
552
+ trade_id: (trade.tradeId || trade.id || '').toString(),
553
+ pair: post_process_pair(trade.symbol),
554
+ type: trade.side,
555
+ price,
556
+ amount,
557
+ total: Number.isFinite(total) ? total : price * amount,
558
+ open_time: new Date(parseInt(trade.cTime)),
559
+ close_time: new Date(parseInt(trade.uTime || trade.cTime)),
560
+ role: trade.tradeScope,
561
+ }
562
+ let fees = get_trade_fees_bitget(trade.feeDetail)
563
+ if (_.keys(fees).length > 0) mapped_trade.fees = fees
564
+ return mapped_trade
565
+ })
566
+ )
567
+ if (rows.length < 100) break
568
+ cursor = rows[rows.length - 1].id || rows[rows.length - 1].tradeId
569
+ if (!cursor) break
570
+ await sleep(250)
571
+ }
572
+ }
573
+ trades = sort_history_rows(filter_history_in_range(trades, start_time_in_ms, end_time_in_ms))
574
+ cb({ success: true, body: trades })
575
+ })().catch((error) => cb(error))
576
+ }
494
577
  get_deposits(cur, timeframe_in_ms, cb) {
495
578
  let [api_key, secret_key] = this.api_secret_key
496
579
  let current = new Date().getTime()
@@ -425,6 +425,9 @@ module.exports = class Bithumb extends ExchangeBase {
425
425
  }
426
426
  this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
427
427
  }
428
+ get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
429
+ this._get_history_trades_via_timeframe(pair, start_time_in_ms, end_time_in_ms, cb)
430
+ }
428
431
  get_trades_id_type(pair, id, type, timeframe_in_ms, cb) {
429
432
  let [cur, quote_cur] = utils.parse_pair(pair)
430
433
  let get_trades_id_type_base = (pair, id, timeframe_in_ms, cb) => {
@@ -314,6 +314,9 @@ module.exports = class Bitkub extends ExchangeBase {
314
314
  }
315
315
  this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
316
316
  }
317
+ get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
318
+ this._get_history_trades_via_timeframe(pair, start_time_in_ms, end_time_in_ms, cb)
319
+ }
317
320
  _get_trades_page(pair, page, cb) {
318
321
  let get_trades_base = (pair, page, cb) => {
319
322
  let [api_key, secret_key] = this.api_secret_key
@@ -165,11 +165,12 @@ module.exports = class Bitmart extends ExchangeBase {
165
165
  })
166
166
  this.ws.market.ping('ping')
167
167
  })
168
- this.ws.market.on_message((data) => {
168
+ this.ws.market.on_message((data, t_recv) => {
169
169
  if (data.toString() === 'pong') {
170
170
  this.ws.market.last_pong = new Date()
171
171
  } else {
172
172
  data = JSON.parse(data)
173
+ const t_parsed = process.hrtime()
173
174
  let book = data.data
174
175
  // Get the pair from the message's symbol field
175
176
  const msg_symbol = data.symbol
@@ -178,7 +179,7 @@ module.exports = class Bitmart extends ExchangeBase {
178
179
  this.ws.market.pair_status[target_pair] = 'opened'
179
180
  this.ws.market.asks_dict[target_pair] = book.sells.map((ask) => [parseFloat(ask.price), parseFloat(ask.amount)])
180
181
  this.ws.market.bids_dict[target_pair] = book.buys.map((bid) => [parseFloat(bid.price), parseFloat(bid.amount)])
181
- this.ws.market.ts_dict[target_pair] = { received_ts: Date.now(), event_ts: parseInt(data.timeStamp) || undefined }
182
+ this.ws.market.ts_dict[target_pair] = { received_ts: Date.now(), event_ts: parseInt(data.timeStamp) || undefined, t_recv, t_parsed }
182
183
  const asks = this.ws.market.asks_dict[target_pair]
183
184
  const bids = this.ws.market.bids_dict[target_pair]
184
185
  if (asks && asks.length > 0 && bids && bids.length > 0) {