@icgio/icg-exchanges 1.40.42 → 1.40.44

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.
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/ |
@@ -478,6 +478,9 @@ module.exports = class Ascendex extends ExchangeBase {
478
478
  }
479
479
  this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
480
480
  }
481
+ get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
482
+ this._get_history_trades_via_timeframe(pair, start_time_in_ms, end_time_in_ms, cb)
483
+ }
481
484
  static get_min_amount(cb) {
482
485
  let min_cur_amount = {}
483
486
  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
 
@@ -551,6 +552,67 @@ module.exports = class Biconomy extends ExchangeBase {
551
552
  }
552
553
  this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
553
554
  }
555
+ get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
556
+ const get_history_trades_base = (pair, start_time_in_ms, end_time_in_ms, cb) => {
557
+ let [api_key, secret_key] = this.api_secret_key
558
+ ;(async () => {
559
+ const rows = []
560
+ const [, quote_cur] = utils.parse_pair(pair)
561
+ let offset = 0
562
+ const limit = 100
563
+ while (true) {
564
+ let params = {
565
+ api_key: api_key,
566
+ limit,
567
+ market: pre_process_pair(pair),
568
+ offset,
569
+ }
570
+ params.sign = get_signature_biconomy(secret_key, params)
571
+ const body = await new Promise((resolve, reject) => {
572
+ needle.post('https://api.biconomy.com/api/v1/private/order/finished', params, options, (err, res, body) => {
573
+ if (body && body.code === 0 && body.result && Array.isArray(body.result.records)) {
574
+ resolve(body)
575
+ } else if (body) {
576
+ reject({ error: error_codes.other, body })
577
+ } else {
578
+ reject({ error: error_codes.timeout })
579
+ }
580
+ })
581
+ })
582
+ const records = body.result.records
583
+ if (records.length === 0) break
584
+ for (const record of records) {
585
+ const close_time_ms = Math.floor(parseFloat(record.ftime) * 1000)
586
+ if (close_time_ms < start_time_in_ms) {
587
+ cb({ success: true, body: sort_history_rows(filter_history_in_range(rows, start_time_in_ms, end_time_in_ms)) })
588
+ return
589
+ }
590
+ if (close_time_ms >= end_time_in_ms || parseFloat(record.deal_stock) <= 0) continue
591
+ rows.push({
592
+ order_id: record.id != null ? record.id.toString() : '',
593
+ pair,
594
+ type: parseFloat(record.side) === 2 ? 'buy' : 'sell',
595
+ price: parseFloat(record.price),
596
+ amount: parseFloat(record.deal_stock),
597
+ total: parseFloat(record.deal_money),
598
+ close_time: new Date(close_time_ms),
599
+ fees: {
600
+ [quote_cur]: parseFloat(record.maker_fee) > 0 ? parseFloat(record.maker_fee) : parseFloat(record.taker_fee),
601
+ },
602
+ role: 'maker',
603
+ })
604
+ }
605
+ if (records.length < limit) break
606
+ offset += limit
607
+ await sleep(50)
608
+ }
609
+ cb({ success: true, body: sort_history_rows(filter_history_in_range(rows, start_time_in_ms, end_time_in_ms)) })
610
+ })().catch((error) => {
611
+ cb({ success: false, error: error?.error || error_codes.other, body: error?.body })
612
+ })
613
+ }
614
+ this.private_limiter.process(get_history_trades_base, pair, start_time_in_ms, end_time_in_ms, cb)
615
+ }
554
616
  // get_all_trades (timeframe_in_ms, cb) {
555
617
  //
556
618
  // }
@@ -656,6 +656,67 @@ module.exports = class Binance extends ExchangeBase {
656
656
  }
657
657
  this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
658
658
  }
659
+ get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
660
+ let [api_key, secret_key] = this.api_secret_key
661
+ let { fetch_split_windows, filter_history_in_range, sort_history_rows } = ExchangeBase.history_utils
662
+ let request_options = {
663
+ headers: {
664
+ ...options.headers,
665
+ 'X-MBX-APIKEY': api_key,
666
+ },
667
+ }
668
+ let path = '/api/v3/myTrades'
669
+ let fetch_window = (window_start_ms, window_stop_ms) =>
670
+ new Promise((resolve, reject) => {
671
+ let params = {
672
+ symbol: pre_process_pair(pair),
673
+ startTime: window_start_ms,
674
+ endTime: window_stop_ms - 1,
675
+ limit: 1000,
676
+ timestamp: Date.now(),
677
+ }
678
+ params.signature = get_signature_binance(secret_key, params)
679
+ let url = 'https://api.binance.com' + path + '?' + qs.stringify(params)
680
+ this.private_limiter.process(needle.get, url, request_options, (err, res, body) => {
681
+ if (body && Array.isArray(body)) {
682
+ resolve(
683
+ body.map((trade) => ({
684
+ order_id: trade.orderId.toString(),
685
+ trade_id: trade.id.toString(),
686
+ pair,
687
+ type: trade.isBuyer ? 'buy' : 'sell',
688
+ price: parseFloat(trade.price),
689
+ amount: parseFloat(trade.qty),
690
+ total: parseFloat(trade.quoteQty || parseFloat(trade.price) * parseFloat(trade.qty)),
691
+ close_time: new Date(trade.time),
692
+ fees: {
693
+ [post_process_cur(trade.commissionAsset)]: parseFloat(trade.commission),
694
+ },
695
+ role: trade.isMaker ? 'maker' : 'taker',
696
+ }))
697
+ )
698
+ } else if (body && body.code === -1021 && body.msg.startsWith('Timestamp for this request')) {
699
+ reject({ success: false, error: error_codes.request_error, body })
700
+ } else if (body) {
701
+ reject({ success: false, error: error_codes.other, body })
702
+ } else {
703
+ reject({ success: false, error: error_codes.timeout })
704
+ }
705
+ })
706
+ })
707
+ ;(async () => {
708
+ let trades = await fetch_split_windows({
709
+ start_ms: start_time_in_ms,
710
+ stop_ms: end_time_in_ms,
711
+ initial_window_ms: 7 * 24 * 60 * 60 * 1000,
712
+ page_limit: 1000,
713
+ fetch_window,
714
+ delay_ms: 250,
715
+ })
716
+ trades = sort_history_rows(filter_history_in_range(trades, start_time_in_ms, end_time_in_ms))
717
+ cb({ success: true, body: trades })
718
+ })().catch((error) => cb(error))
719
+ }
659
720
  // get_trades_ws (pair, timeframe_in_ms, cb) {
660
721
  // if (this.ws.trading.status === 'opened') {
661
722
  // 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
 
@@ -527,6 +528,75 @@ module.exports = class Bingx extends ExchangeBase {
527
528
  }
528
529
  this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
529
530
  }
531
+ get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
532
+ const get_history_trades_base = (pair, start_time_in_ms, end_time_in_ms, cb) => {
533
+ let [api_key, secret_key] = this.api_secret_key
534
+ const options = get_options(api_key)
535
+ ;(async () => {
536
+ const rows = await fetch_split_windows({
537
+ start_ms: start_time_in_ms,
538
+ stop_ms: end_time_in_ms,
539
+ initial_window_ms: 60 * 60 * 1000,
540
+ page_limit: 1000,
541
+ fetch_window: async (window_start_ms, window_stop_ms) => {
542
+ const body = await with_retry(
543
+ async () =>
544
+ await new Promise((resolve, reject) => {
545
+ let params = {
546
+ limit: 1000,
547
+ startTime: window_start_ms,
548
+ endTime: window_stop_ms - 1,
549
+ symbol: pre_process_pair(pair),
550
+ timestamp: new Date().getTime(),
551
+ }
552
+ let signature = get_signature_bingx(secret_key, params)
553
+ let url = 'https://open-api.bingx.com/openApi/spot/v1/trade/myTrades?' + qs.stringify(params) + '&signature=' + signature
554
+ needle.get(url, options, (err, res, body) => {
555
+ body = parse_body(body)
556
+ if (body && body.code === 0 && body.data && Array.isArray(body.data.fills)) {
557
+ resolve(body)
558
+ } else {
559
+ const error = new Error(`bingx myTrades failed: ${JSON.stringify(body).slice(0, 300)}`)
560
+ error.body = body
561
+ error.code = body?.code
562
+ reject(error)
563
+ }
564
+ })
565
+ }),
566
+ {
567
+ retries: 5,
568
+ should_retry: (error) => error?.code === 100410 || /rate limited/i.test(String(error?.message || '')),
569
+ backoff_ms: (attempt, error) => {
570
+ const unblock_match = String(error?.message || '').match(/unblocked after (\d+)/i)
571
+ if (unblock_match) {
572
+ return Math.max(parseInt(unblock_match[1], 10) - Date.now(), 0) + 1000
573
+ }
574
+ return attempt * 3000
575
+ },
576
+ },
577
+ )
578
+ return body.data.fills.map((trade) => ({
579
+ order_id: trade.orderId != null ? trade.orderId.toString() : '',
580
+ pair,
581
+ type: trade.isBuyer ? 'buy' : 'sell',
582
+ price: parseFloat(trade.price),
583
+ amount: parseFloat(trade.qty),
584
+ total: parseFloat(trade.quoteQty || parseFloat(trade.price) * parseFloat(trade.qty)),
585
+ close_time: new Date(trade.time),
586
+ fees: {
587
+ [post_process_cur(trade.commissionAsset)]: parseFloat(trade.commission),
588
+ },
589
+ role: trade.isMaker ? 'maker' : 'taker',
590
+ }))
591
+ },
592
+ })
593
+ cb({ success: true, body: sort_history_rows(rows) })
594
+ })().catch((error) => {
595
+ cb({ success: false, error: error_codes.other, body: error?.body })
596
+ })
597
+ }
598
+ this.private_limiter.process(get_history_trades_base, pair, start_time_in_ms, end_time_in_ms, cb)
599
+ }
530
600
  get_all_deposits(timeframe_in_ms, cb) {
531
601
  let get_all_deposits_base = (timeframe_in_ms, cb) => {
532
602
  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)
@@ -491,6 +509,70 @@ module.exports = class Bitget extends ExchangeBase {
491
509
  }
492
510
  this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
493
511
  }
512
+ get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
513
+ let [api_key, secret_key] = this.api_secret_key
514
+ let { filter_history_in_range, sort_history_rows, split_windows, sleep } = ExchangeBase.history_utils
515
+ let path = '/api/v2/spot/trade/fills'
516
+ let fetch_page = (params) =>
517
+ new Promise((resolve, reject) => {
518
+ let options = get_options(api_key, secret_key, this.passphrase, 'GET', path, params)
519
+ let url = base_url + path + '?' + qs.stringify(params)
520
+ this.private_limiter.process(needle.get, url, options, (err, res, body) => {
521
+ if (body && body.code === '00000' && Array.isArray(body.data)) {
522
+ resolve(body.data)
523
+ } else if (body) {
524
+ reject({ success: false, error: error_codes.other, body })
525
+ } else {
526
+ reject({ success: false, error: error_codes.timeout })
527
+ }
528
+ })
529
+ })
530
+ ;(async () => {
531
+ let trades = []
532
+ let windows = split_windows(start_time_in_ms, end_time_in_ms, 90 * 24 * 60 * 60 * 1000)
533
+ for (let [window_start_ms, window_stop_ms] of windows) {
534
+ let cursor
535
+ while (true) {
536
+ let params = {
537
+ symbol: pre_process_pair(pair),
538
+ startTime: window_start_ms.toString(),
539
+ endTime: (window_stop_ms - 1).toString(),
540
+ limit: '100',
541
+ }
542
+ if (cursor) params.idLessThan = cursor
543
+ let rows = await fetch_page(params)
544
+ trades.push(
545
+ ...rows.map((trade) => {
546
+ let price = parseFloat(trade.priceAvg || trade.price)
547
+ let amount = parseFloat(trade.size)
548
+ let total = parseFloat(trade.amount)
549
+ let mapped_trade = {
550
+ order_id: trade.orderId.toString(),
551
+ trade_id: (trade.tradeId || trade.id || '').toString(),
552
+ pair: post_process_pair(trade.symbol),
553
+ type: trade.side,
554
+ price,
555
+ amount,
556
+ total: Number.isFinite(total) ? total : price * amount,
557
+ open_time: new Date(parseInt(trade.cTime)),
558
+ close_time: new Date(parseInt(trade.uTime || trade.cTime)),
559
+ role: trade.tradeScope,
560
+ }
561
+ let fees = get_trade_fees_bitget(trade.feeDetail)
562
+ if (_.keys(fees).length > 0) mapped_trade.fees = fees
563
+ return mapped_trade
564
+ })
565
+ )
566
+ if (rows.length < 100) break
567
+ cursor = rows[rows.length - 1].id || rows[rows.length - 1].tradeId
568
+ if (!cursor) break
569
+ await sleep(250)
570
+ }
571
+ }
572
+ trades = sort_history_rows(filter_history_in_range(trades, start_time_in_ms, end_time_in_ms))
573
+ cb({ success: true, body: trades })
574
+ })().catch((error) => cb(error))
575
+ }
494
576
  get_deposits(cur, timeframe_in_ms, cb) {
495
577
  let [api_key, secret_key] = this.api_secret_key
496
578
  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
@@ -685,6 +685,75 @@ module.exports = class Bitmex extends ExchangeBase {
685
685
  }
686
686
  this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
687
687
  }
688
+ get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
689
+ let [api_key, secret_key] = this.api_secret_key
690
+ let { fetch_split_windows, filter_history_in_range, sort_history_rows } = ExchangeBase.history_utils
691
+ let verb = 'GET'
692
+ let endpoint = '/api/v1/execution/tradeHistory'
693
+ let fetch_window = (window_start_ms, window_stop_ms) =>
694
+ new Promise((resolve, reject) => {
695
+ let params = {
696
+ count: 500,
697
+ symbol: pre_process_pair(pair),
698
+ startTime: new Date(window_start_ms).toISOString(),
699
+ endTime: new Date(window_stop_ms - 1).toISOString(),
700
+ }
701
+ let query = qs.stringify(params)
702
+ let nonce = Date.now() + 60 * 1000
703
+ let signature = get_signature_bitmex(verb, endpoint + '?' + query, nonce, null, secret_key)
704
+ let options = get_options(api_key, nonce, signature)
705
+ needle.get(base_url + endpoint + '?' + query, options, (err, res, body) => {
706
+ if (Array.isArray(body)) {
707
+ resolve(
708
+ body
709
+ .filter((trade) => trade.execType === 'Trade')
710
+ .map((trade) => {
711
+ let price = parseFloat(trade.lastPx || trade.avgPx || trade.price)
712
+ let amount = Math.abs(parseFloat(trade.homeNotional))
713
+ if (!Number.isFinite(amount)) {
714
+ let quantity = parseFloat(trade.lastQty || trade.cumQty || trade.foreignNotional)
715
+ amount = Number.isFinite(quantity) && Number.isFinite(price) && price !== 0 ? Math.abs(quantity) / price : 0
716
+ }
717
+ let total = Math.abs(parseFloat(trade.foreignNotional))
718
+ if (!Number.isFinite(total)) total = Math.abs(parseFloat(trade.lastQty || trade.cumQty || 0))
719
+ let mapped_trade = {
720
+ order_id: (trade.orderID || '').toString(),
721
+ trade_id: (trade.execID || trade.trdMatchID || '').toString(),
722
+ pair,
723
+ type: (trade.side || '').toLowerCase(),
724
+ price,
725
+ amount,
726
+ size: total,
727
+ total,
728
+ close_time: new Date(trade.transactTime || trade.timestamp),
729
+ }
730
+ if (trade.lastLiquidityInd === 'AddedLiquidity') mapped_trade.role = 'maker'
731
+ if (trade.lastLiquidityInd === 'RemovedLiquidity') mapped_trade.role = 'taker'
732
+ return mapped_trade
733
+ })
734
+ )
735
+ } else if (body && body.error && body.error.message === 'The system is currently overloaded. Please try again later.') {
736
+ reject({ success: false, error: error_codes.system_overloaded })
737
+ } else if (body) {
738
+ reject({ success: false, error: error_codes.other, body })
739
+ } else {
740
+ reject({ success: false, error: error_codes.timeout })
741
+ }
742
+ })
743
+ })
744
+ ;(async () => {
745
+ let trades = await fetch_split_windows({
746
+ start_ms: start_time_in_ms,
747
+ stop_ms: end_time_in_ms,
748
+ initial_window_ms: 30 * 24 * 60 * 60 * 1000,
749
+ page_limit: 500,
750
+ fetch_window,
751
+ delay_ms: 250,
752
+ })
753
+ trades = sort_history_rows(filter_history_in_range(trades, start_time_in_ms, end_time_in_ms))
754
+ cb({ success: true, body: trades })
755
+ })().catch((error) => cb(error))
756
+ }
688
757
  static get_precision(cb) {
689
758
  let price_precision = {},
690
759
  amount_precision = {}
@@ -8,6 +8,7 @@ const zlib = require('zlib')
8
8
  const ExchangeBase = require('./exchange-base.js')
9
9
  const ExchangeWs = require('./exchange-ws.js')
10
10
  const JSONbig = require('json-bigint')
11
+ const { fetch_split_windows, sort_history_rows } = ExchangeBase.history_utils
11
12
 
12
13
  const { error_codes } = require('../error_codes.json')
13
14
 
@@ -512,6 +513,61 @@ module.exports = class Bitrue extends ExchangeBase {
512
513
  }
513
514
  this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
514
515
  }
516
+ get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
517
+ const get_history_trades_base = (pair, start_time_in_ms, end_time_in_ms, cb) => {
518
+ let [api_key, secret_key] = this.api_secret_key
519
+ const options = get_options(api_key, false)
520
+ ;(async () => {
521
+ const rows = await fetch_split_windows({
522
+ start_ms: start_time_in_ms,
523
+ stop_ms: end_time_in_ms,
524
+ initial_window_ms: 60 * 60 * 1000,
525
+ page_limit: 1000,
526
+ fetch_window: async (window_start_ms, window_stop_ms) =>
527
+ await new Promise((resolve, reject) => {
528
+ let params = {
529
+ symbol: pre_process_pair(pair),
530
+ startTime: window_start_ms,
531
+ endTime: window_stop_ms - 1,
532
+ limit: 1000,
533
+ timestamp: parseInt(Date.now()),
534
+ }
535
+ let query = qs.stringify(params)
536
+ let sign = get_signature_bitrue(secret_key, query)
537
+ let url = 'https://openapi.bitrue.com/api/v2/myTrades?' + query + `&signature=${sign}`
538
+ needle.get(url, options, (err, res, body) => {
539
+ let parsed_body = typeof body === 'string' ? JSONbig.parse(body) : body
540
+ if (parsed_body && Array.isArray(parsed_body)) {
541
+ resolve(
542
+ parsed_body.map((trade) => ({
543
+ order_id: trade.orderId != null ? trade.orderId.toString() : '',
544
+ pair,
545
+ type: trade.isBuyer ? 'buy' : 'sell',
546
+ price: parseFloat(trade.price),
547
+ amount: parseFloat(trade.qty),
548
+ total: parseFloat(trade.price) * parseFloat(trade.qty),
549
+ close_time: new Date(trade.time),
550
+ fees: {
551
+ [post_process_cur(trade.commissionAsset)]: parseFloat(trade.commission),
552
+ },
553
+ role: trade.isMaker ? 'maker' : 'taker',
554
+ })),
555
+ )
556
+ } else if (parsed_body) {
557
+ reject({ error: error_codes.other, body: parsed_body })
558
+ } else {
559
+ reject({ error: error_codes.timeout })
560
+ }
561
+ })
562
+ }),
563
+ })
564
+ cb({ success: true, body: sort_history_rows(rows) })
565
+ })().catch((error) => {
566
+ cb({ success: false, error: error?.error || error_codes.other, body: error?.body })
567
+ })
568
+ }
569
+ this.private_limiter.process(get_history_trades_base, pair, start_time_in_ms, end_time_in_ms, cb)
570
+ }
515
571
  get_deposits(cur, timeframe_in_ms, cb) {
516
572
  let get_deposits_base = (timeframe_in_ms, cb) => {
517
573
  let [api_key, secret_key] = this.api_secret_key