@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.
- package/API_DOCS.md +6 -6
- package/lib/exchanges/ascendex.js +7 -3
- package/lib/exchanges/biconomy.js +65 -2
- package/lib/exchanges/binance.js +65 -3
- package/lib/exchanges/bingx.js +73 -2
- package/lib/exchanges/bitfinex.js +76 -0
- package/lib/exchanges/bitget.js +86 -3
- package/lib/exchanges/bithumb.js +3 -0
- package/lib/exchanges/bitkub.js +3 -0
- package/lib/exchanges/bitmart.js +3 -2
- package/lib/exchanges/bitmex.js +71 -1
- package/lib/exchanges/bitrue.js +59 -2
- package/lib/exchanges/bitstamp.js +71 -0
- package/lib/exchanges/blofin.js +76 -3
- package/lib/exchanges/btse.js +6 -2
- package/lib/exchanges/bybit.js +3 -2
- package/lib/exchanges/coinbase.js +113 -8
- package/lib/exchanges/coinstore.js +6 -2
- package/lib/exchanges/cryptocom.js +69 -2
- package/lib/exchanges/deepcoin.js +64 -0
- package/lib/exchanges/digifinex.js +62 -0
- package/lib/exchanges/exchange-base.js +167 -1
- package/lib/exchanges/exchange-ws.js +2 -1
- package/lib/exchanges/fastex.js +3 -0
- package/lib/exchanges/gate.js +4 -2
- package/lib/exchanges/gemini.js +70 -0
- package/lib/exchanges/hashkey.js +70 -2
- package/lib/exchanges/hashkeyglobal.js +70 -2
- package/lib/exchanges/hitbtc.js +2 -1
- package/lib/exchanges/hkbitex.js +6 -2
- package/lib/exchanges/htx.js +78 -3
- package/lib/exchanges/indodax.js +3 -0
- package/lib/exchanges/kraken.js +63 -0
- package/lib/exchanges/kucoin.js +4 -3
- package/lib/exchanges/lbank.js +89 -53
- package/lib/exchanges/mexc.js +61 -2
- package/lib/exchanges/okx.js +4 -3
- package/lib/exchanges/phemex.js +4 -3
- package/lib/exchanges/swft.js +12 -9
- package/lib/exchanges/upbit.js +60 -2
- package/lib/exchanges/weex.js +3 -0
- package/lib/exchanges/xt.js +64 -2
- 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
|
|
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/
|
|
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://
|
|
35
|
-
| HashkeyGlobal | https://
|
|
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/
|
|
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
|
// }
|
package/lib/exchanges/binance.js
CHANGED
|
@@ -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]) {
|
package/lib/exchanges/bingx.js
CHANGED
|
@@ -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]) {
|
package/lib/exchanges/bitget.js
CHANGED
|
@@ -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()
|
package/lib/exchanges/bithumb.js
CHANGED
|
@@ -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) => {
|
package/lib/exchanges/bitkub.js
CHANGED
|
@@ -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
|
package/lib/exchanges/bitmart.js
CHANGED
|
@@ -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) {
|