@icgio/icg-exchanges 1.40.41 → 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-fix.js +0 -1
- package/lib/exchanges/coinbase.js +113 -11
- 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-fix.js +0 -2
- 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/lib/exchanges/hashkey.js
CHANGED
|
@@ -44,6 +44,15 @@ function get_options(api_key) {
|
|
|
44
44
|
return options
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
function get_trade_fees_hashkey(trade) {
|
|
48
|
+
let fee_coin = trade.commissionAsset || trade.feeCoinId || _.get(trade, 'fee.feeCoinName')
|
|
49
|
+
let fee = parseFloat(trade.commission ?? trade.feeAmount ?? _.get(trade, 'fee.fee'))
|
|
50
|
+
if (!fee_coin || !Number.isFinite(fee)) return {}
|
|
51
|
+
return {
|
|
52
|
+
[post_process_cur(fee_coin)]: Math.abs(fee),
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
47
56
|
module.exports = class Hashkey extends ExchangeBase {
|
|
48
57
|
constructor(api_key, secret_key, settings) {
|
|
49
58
|
super('hashkey', settings.id, api_key, secret_key)
|
|
@@ -99,9 +108,10 @@ module.exports = class Hashkey extends ExchangeBase {
|
|
|
99
108
|
}),
|
|
100
109
|
)
|
|
101
110
|
})
|
|
102
|
-
this.ws.market.on_message((data) => {
|
|
111
|
+
this.ws.market.on_message((data, t_recv) => {
|
|
103
112
|
try {
|
|
104
113
|
data = JSON.parse(data)
|
|
114
|
+
const t_parsed = process.hrtime()
|
|
105
115
|
if (data.pong) {
|
|
106
116
|
this.ws.market.last_pong = new Date()
|
|
107
117
|
} else {
|
|
@@ -111,7 +121,7 @@ module.exports = class Hashkey extends ExchangeBase {
|
|
|
111
121
|
const { a, b } = data.data
|
|
112
122
|
this.ws.market.asks_dict[pair] = a.map((ask) => [parseFloat(ask[0]), parseFloat(ask[1])])
|
|
113
123
|
this.ws.market.bids_dict[pair] = b.map((bid) => [parseFloat(bid[0]), parseFloat(bid[1])])
|
|
114
|
-
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: parseFloat(data.data.t) || undefined, send_ts: parseFloat(data.sendTime) || undefined }
|
|
124
|
+
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: parseFloat(data.data.t) || undefined, send_ts: parseFloat(data.sendTime) || undefined, t_recv, t_parsed }
|
|
115
125
|
const asks = this.ws.market.asks_dict[pair]
|
|
116
126
|
const bids = this.ws.market.bids_dict[pair]
|
|
117
127
|
if (asks && asks.length > 0 && bids && bids.length > 0) {
|
|
@@ -462,6 +472,64 @@ module.exports = class Hashkey extends ExchangeBase {
|
|
|
462
472
|
}
|
|
463
473
|
this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
|
|
464
474
|
}
|
|
475
|
+
get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
|
|
476
|
+
let [api_key, secret_key] = this.api_secret_key
|
|
477
|
+
let { fetch_split_windows, filter_history_in_range, sort_history_rows } = ExchangeBase.history_utils
|
|
478
|
+
let path = '/api/v1/account/trades'
|
|
479
|
+
let options = get_options(api_key)
|
|
480
|
+
let fetch_window = (window_start_ms, window_stop_ms) =>
|
|
481
|
+
new Promise((resolve, reject) => {
|
|
482
|
+
let params = {
|
|
483
|
+
symbol: pre_process_pair(pair),
|
|
484
|
+
startTime: window_start_ms,
|
|
485
|
+
endTime: window_stop_ms - 1,
|
|
486
|
+
limit: 1000,
|
|
487
|
+
timestamp: Date.now(),
|
|
488
|
+
}
|
|
489
|
+
params.signature = get_signature_hashkey(params, secret_key)
|
|
490
|
+
let url = base_url + path + '?' + qs.stringify(params)
|
|
491
|
+
this.private_limiter.process(needle.get, url, options, (err, res, body) => {
|
|
492
|
+
if (Array.isArray(body)) {
|
|
493
|
+
resolve(
|
|
494
|
+
body.map((trade) => {
|
|
495
|
+
let price = parseFloat(trade.price)
|
|
496
|
+
let amount = parseFloat(trade.qty)
|
|
497
|
+
let mapped_trade = {
|
|
498
|
+
order_id: (trade.orderId || '').toString(),
|
|
499
|
+
trade_id: (trade.id || trade.tradeId || '').toString(),
|
|
500
|
+
pair: post_process_pair(trade.symbol),
|
|
501
|
+
type: trade.isBuyer ? 'buy' : 'sell',
|
|
502
|
+
price,
|
|
503
|
+
amount,
|
|
504
|
+
total: Number.isFinite(parseFloat(trade.quoteQty)) ? parseFloat(trade.quoteQty) : price * amount,
|
|
505
|
+
close_time: new Date(parseInt(trade.time)),
|
|
506
|
+
role: trade.isMaker ? 'maker' : 'taker',
|
|
507
|
+
}
|
|
508
|
+
let fees = get_trade_fees_hashkey(trade)
|
|
509
|
+
if (_.keys(fees).length > 0) mapped_trade.fees = fees
|
|
510
|
+
return mapped_trade
|
|
511
|
+
})
|
|
512
|
+
)
|
|
513
|
+
} else if (body) {
|
|
514
|
+
reject({ success: false, error: error_codes.other, body })
|
|
515
|
+
} else {
|
|
516
|
+
reject({ success: false, error: error_codes.timeout })
|
|
517
|
+
}
|
|
518
|
+
})
|
|
519
|
+
})
|
|
520
|
+
;(async () => {
|
|
521
|
+
let trades = await fetch_split_windows({
|
|
522
|
+
start_ms: start_time_in_ms,
|
|
523
|
+
stop_ms: end_time_in_ms,
|
|
524
|
+
initial_window_ms: 24 * 60 * 60 * 1000,
|
|
525
|
+
page_limit: 1000,
|
|
526
|
+
fetch_window,
|
|
527
|
+
delay_ms: 250,
|
|
528
|
+
})
|
|
529
|
+
trades = sort_history_rows(filter_history_in_range(trades, start_time_in_ms, end_time_in_ms))
|
|
530
|
+
cb({ success: true, body: trades })
|
|
531
|
+
})().catch((error) => cb(error))
|
|
532
|
+
}
|
|
465
533
|
get_all_deposits(timeframe_in_ms, cb) {
|
|
466
534
|
let [api_key, secret_key] = this.api_secret_key
|
|
467
535
|
let params = {
|
|
@@ -44,6 +44,15 @@ function get_options(api_key) {
|
|
|
44
44
|
return options
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
function get_trade_fees_hashkey(trade) {
|
|
48
|
+
let fee_coin = trade.commissionAsset || trade.feeCoinId || _.get(trade, 'fee.feeCoinName')
|
|
49
|
+
let fee = parseFloat(trade.commission ?? trade.feeAmount ?? _.get(trade, 'fee.fee'))
|
|
50
|
+
if (!fee_coin || !Number.isFinite(fee)) return {}
|
|
51
|
+
return {
|
|
52
|
+
[post_process_cur(fee_coin)]: Math.abs(fee),
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
47
56
|
module.exports = class Hashkeyglobal extends ExchangeBase {
|
|
48
57
|
constructor(api_key, secret_key, settings) {
|
|
49
58
|
super('hashkey', settings.id, api_key, secret_key)
|
|
@@ -101,8 +110,9 @@ module.exports = class Hashkeyglobal extends ExchangeBase {
|
|
|
101
110
|
})
|
|
102
111
|
this.ws.market.ping('ping')
|
|
103
112
|
})
|
|
104
|
-
this.ws.market.on_message((data) => {
|
|
113
|
+
this.ws.market.on_message((data, t_recv) => {
|
|
105
114
|
data = JSON.parse(data)
|
|
115
|
+
const t_parsed = process.hrtime()
|
|
106
116
|
if (data.pong) {
|
|
107
117
|
this.ws.market.last_pong = new Date()
|
|
108
118
|
} else {
|
|
@@ -112,7 +122,7 @@ module.exports = class Hashkeyglobal extends ExchangeBase {
|
|
|
112
122
|
this.ws.market.pair_status[pair] = 'opened'
|
|
113
123
|
this.ws.market.asks_dict[pair] = book[0].a.map((ask) => [parseFloat(ask[0]), parseFloat(ask[1])])
|
|
114
124
|
this.ws.market.bids_dict[pair] = book[0].b.map((bid) => [parseFloat(bid[0]), parseFloat(bid[1])])
|
|
115
|
-
this.ws.market.ts_dict[pair] = { received_ts: Date.now() }
|
|
125
|
+
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), t_recv, t_parsed }
|
|
116
126
|
const asks = this.ws.market.asks_dict[pair]
|
|
117
127
|
const bids = this.ws.market.bids_dict[pair]
|
|
118
128
|
if (asks && asks.length > 0 && bids && bids.length > 0) {
|
|
@@ -455,6 +465,64 @@ module.exports = class Hashkeyglobal extends ExchangeBase {
|
|
|
455
465
|
}
|
|
456
466
|
this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
|
|
457
467
|
}
|
|
468
|
+
get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
|
|
469
|
+
let [api_key, secret_key] = this.api_secret_key
|
|
470
|
+
let { fetch_split_windows, filter_history_in_range, sort_history_rows } = ExchangeBase.history_utils
|
|
471
|
+
let path = '/api/v1/account/trades'
|
|
472
|
+
let options = get_options(api_key)
|
|
473
|
+
let fetch_window = (window_start_ms, window_stop_ms) =>
|
|
474
|
+
new Promise((resolve, reject) => {
|
|
475
|
+
let params = {
|
|
476
|
+
symbol: pre_process_pair(pair),
|
|
477
|
+
startTime: window_start_ms,
|
|
478
|
+
endTime: window_stop_ms - 1,
|
|
479
|
+
limit: 1000,
|
|
480
|
+
timestamp: Date.now(),
|
|
481
|
+
}
|
|
482
|
+
params.signature = get_signature_hashkey(params, secret_key)
|
|
483
|
+
let url = base_url + path + '?' + qs.stringify(params)
|
|
484
|
+
this.private_limiter.process(needle.get, url, options, (err, res, body) => {
|
|
485
|
+
if (Array.isArray(body)) {
|
|
486
|
+
resolve(
|
|
487
|
+
body.map((trade) => {
|
|
488
|
+
let price = parseFloat(trade.price)
|
|
489
|
+
let amount = parseFloat(trade.qty)
|
|
490
|
+
let mapped_trade = {
|
|
491
|
+
order_id: (trade.orderId || '').toString(),
|
|
492
|
+
trade_id: (trade.id || trade.tradeId || '').toString(),
|
|
493
|
+
pair: post_process_pair(trade.symbol),
|
|
494
|
+
type: trade.isBuyer ? 'buy' : 'sell',
|
|
495
|
+
price,
|
|
496
|
+
amount,
|
|
497
|
+
total: Number.isFinite(parseFloat(trade.quoteQty)) ? parseFloat(trade.quoteQty) : price * amount,
|
|
498
|
+
close_time: new Date(parseInt(trade.time)),
|
|
499
|
+
role: trade.isMaker ? 'maker' : 'taker',
|
|
500
|
+
}
|
|
501
|
+
let fees = get_trade_fees_hashkey(trade)
|
|
502
|
+
if (_.keys(fees).length > 0) mapped_trade.fees = fees
|
|
503
|
+
return mapped_trade
|
|
504
|
+
})
|
|
505
|
+
)
|
|
506
|
+
} else if (body) {
|
|
507
|
+
reject({ success: false, error: error_codes.other, body })
|
|
508
|
+
} else {
|
|
509
|
+
reject({ success: false, error: error_codes.timeout })
|
|
510
|
+
}
|
|
511
|
+
})
|
|
512
|
+
})
|
|
513
|
+
;(async () => {
|
|
514
|
+
let trades = await fetch_split_windows({
|
|
515
|
+
start_ms: start_time_in_ms,
|
|
516
|
+
stop_ms: end_time_in_ms,
|
|
517
|
+
initial_window_ms: 24 * 60 * 60 * 1000,
|
|
518
|
+
page_limit: 1000,
|
|
519
|
+
fetch_window,
|
|
520
|
+
delay_ms: 250,
|
|
521
|
+
})
|
|
522
|
+
trades = sort_history_rows(filter_history_in_range(trades, start_time_in_ms, end_time_in_ms))
|
|
523
|
+
cb({ success: true, body: trades })
|
|
524
|
+
})().catch((error) => cb(error))
|
|
525
|
+
}
|
|
458
526
|
get_all_deposits(timeframe_in_ms, cb) {
|
|
459
527
|
let [api_key, secret_key] = this.api_secret_key
|
|
460
528
|
let params = {
|
package/lib/exchanges/hitbtc.js
CHANGED
|
@@ -80,8 +80,9 @@ module.exports = class Hitbtc extends ExchangeBase {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
})
|
|
83
|
-
this.ws.market.on_message((data) => {
|
|
83
|
+
this.ws.market.on_message((data, t_recv) => {
|
|
84
84
|
data = JSON.parse(data)
|
|
85
|
+
const t_parsed = process.hrtime()
|
|
85
86
|
if (data.method === 'snapshotOrderbook') {
|
|
86
87
|
let pair = post_process_pair(data.params.symbol)
|
|
87
88
|
this.ws.market.pair_status[pair] = 'opened'
|
package/lib/exchanges/hkbitex.js
CHANGED
|
@@ -142,11 +142,12 @@ module.exports = class Hkbitex extends ExchangeBase {
|
|
|
142
142
|
}, 60 * 1000)
|
|
143
143
|
this.ws.market.ping('ping')
|
|
144
144
|
})
|
|
145
|
-
this.ws.market.on_message((data) => {
|
|
145
|
+
this.ws.market.on_message((data, t_recv) => {
|
|
146
146
|
if (data.toString() === 'pong') {
|
|
147
147
|
this.ws.market.last_pong = new Date()
|
|
148
148
|
} else {
|
|
149
149
|
data = JSON.parse(data)
|
|
150
|
+
const t_parsed = process.hrtime()
|
|
150
151
|
if (data && data.data && data.data.e === 8) {
|
|
151
152
|
let book = data.data
|
|
152
153
|
let pair = post_process_pair(book.s)
|
|
@@ -157,7 +158,7 @@ module.exports = class Hkbitex extends ExchangeBase {
|
|
|
157
158
|
if (book.d && book.d.b) {
|
|
158
159
|
this.ws.market.bids_dict[pair] = book.d.b.map((bid) => [parseFloat(bid[0]), parseFloat(bid[1])])
|
|
159
160
|
}
|
|
160
|
-
this.ws.market.ts_dict[pair] = { received_ts: Date.now() }
|
|
161
|
+
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), t_recv, t_parsed }
|
|
161
162
|
const asks = this.ws.market.asks_dict[pair]
|
|
162
163
|
const bids = this.ws.market.bids_dict[pair]
|
|
163
164
|
if (asks && asks.length > 0 && bids && bids.length > 0) {
|
|
@@ -556,6 +557,9 @@ module.exports = class Hkbitex extends ExchangeBase {
|
|
|
556
557
|
}
|
|
557
558
|
this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
|
|
558
559
|
}
|
|
560
|
+
get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
|
|
561
|
+
this._get_history_trades_via_timeframe(pair, start_time_in_ms, end_time_in_ms, cb)
|
|
562
|
+
}
|
|
559
563
|
static get_precision(cb) {
|
|
560
564
|
let price_precision = {},
|
|
561
565
|
amount_precision = {}
|
package/lib/exchanges/htx.js
CHANGED
|
@@ -8,6 +8,7 @@ const pako = require('pako')
|
|
|
8
8
|
const ExchangeWs = require('./exchange-ws.js')
|
|
9
9
|
|
|
10
10
|
const ExchangeBase = require('./exchange-base.js')
|
|
11
|
+
const { fetch_split_windows, sort_history_rows, stringify_sorted } = ExchangeBase.history_utils
|
|
11
12
|
|
|
12
13
|
const { error_codes } = require('../error_codes.json')
|
|
13
14
|
const { utils, rate_limiter: Limiter } = require('@icgio/icg-utils')
|
|
@@ -119,12 +120,13 @@ module.exports = class Htx extends ExchangeBase {
|
|
|
119
120
|
let depth_re = new RegExp('market.(.+).depth.step0'),
|
|
120
121
|
bbo_re = new RegExp('market.(.+).bbo'),
|
|
121
122
|
trade_re = new RegExp('market.(.+).trade.detail')
|
|
122
|
-
this.ws.market.on_message((data) => {
|
|
123
|
+
this.ws.market.on_message((data, t_recv) => {
|
|
123
124
|
data = parse_body(pako.inflate(new Uint8Array(data), { to: 'string' }))
|
|
125
|
+
const t_parsed = process.hrtime()
|
|
124
126
|
if (data.ch && depth_re.exec(data.ch)) {
|
|
125
127
|
let pair = post_process_pair(depth_re.exec(data.ch)[1])
|
|
126
128
|
this.ws.market.pair_status[pair] = 'opened'
|
|
127
|
-
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: data.ts }
|
|
129
|
+
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: data.ts, t_recv, t_parsed }
|
|
128
130
|
this.ws.market.asks_dict[pair] = data.tick.asks
|
|
129
131
|
this.ws.market.bids_dict[pair] = data.tick.bids
|
|
130
132
|
} else if (data.ch && bbo_re.exec(data.ch)) {
|
|
@@ -139,7 +141,7 @@ module.exports = class Htx extends ExchangeBase {
|
|
|
139
141
|
if (options.depth === false) {
|
|
140
142
|
this.ws.market.asks_dict[pair] = [[parseFloat(ask_1_price), parseFloat(ask_1_amount)]]
|
|
141
143
|
this.ws.market.bids_dict[pair] = [[parseFloat(bid_1_price), parseFloat(bid_1_amount)]]
|
|
142
|
-
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: data.ts }
|
|
144
|
+
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: data.ts, t_recv, t_parsed }
|
|
143
145
|
this.ws.market.pair_status[pair] = 'opened'
|
|
144
146
|
} else {
|
|
145
147
|
if (!Array.isArray(this.ws.market.asks_dict[pair]) || !Array.isArray(this.ws.market.bids_dict[pair])) {
|
|
@@ -539,6 +541,79 @@ module.exports = class Htx extends ExchangeBase {
|
|
|
539
541
|
}
|
|
540
542
|
this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
|
|
541
543
|
}
|
|
544
|
+
get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
|
|
545
|
+
const get_history_trades_base = (pair, start_time_in_ms, end_time_in_ms, cb) => {
|
|
546
|
+
const account_id = this.account_id
|
|
547
|
+
if (!account_id) {
|
|
548
|
+
cb({ success: false, error: error_codes.other, body: { message: 'htx account_id missing' } })
|
|
549
|
+
return
|
|
550
|
+
}
|
|
551
|
+
const [api_key, secret_key] = this.api_secret_key
|
|
552
|
+
;(async () => {
|
|
553
|
+
const rows = await fetch_split_windows({
|
|
554
|
+
start_ms: start_time_in_ms,
|
|
555
|
+
stop_ms: end_time_in_ms,
|
|
556
|
+
initial_window_ms: 60 * 60 * 1000,
|
|
557
|
+
page_limit: 1000,
|
|
558
|
+
fetch_window: async (window_start_ms, window_stop_ms) =>
|
|
559
|
+
await new Promise((resolve, reject) => {
|
|
560
|
+
let base_url = 'api.huobi.pro'
|
|
561
|
+
let path = '/v1/order/matchresults'
|
|
562
|
+
let params = {
|
|
563
|
+
AccessKeyId: api_key,
|
|
564
|
+
SignatureMethod: 'HmacSHA256',
|
|
565
|
+
SignatureVersion: 2,
|
|
566
|
+
Timestamp: new Date().toISOString().slice(0, 19),
|
|
567
|
+
'account-id': account_id,
|
|
568
|
+
'start-time': window_start_ms,
|
|
569
|
+
'end-time': window_stop_ms - 1,
|
|
570
|
+
size: 1000,
|
|
571
|
+
symbol: pre_process_pair(pair),
|
|
572
|
+
}
|
|
573
|
+
const signature = crypto.createHmac('sha256', secret_key).update(`GET\n${base_url}\n${path}\n${stringify_sorted(params)}`).digest('base64')
|
|
574
|
+
params.Signature = signature
|
|
575
|
+
let url = 'https://' + base_url + path + '?' + stringify_sorted(params)
|
|
576
|
+
needle.get(url, (err, res, body) => {
|
|
577
|
+
if (body && Array.isArray(body.data)) {
|
|
578
|
+
resolve(
|
|
579
|
+
body.data.map((trade) => {
|
|
580
|
+
let result = {
|
|
581
|
+
order_id: trade['order-id'] != null ? trade['order-id'].toString() : '',
|
|
582
|
+
pair: post_process_pair(trade.symbol),
|
|
583
|
+
type: String(trade.type || '').startsWith('buy') ? 'buy' : 'sell',
|
|
584
|
+
price: parseFloat(trade.price),
|
|
585
|
+
amount: parseFloat(trade['filled-amount']),
|
|
586
|
+
total: parseFloat(trade['filled-amount']) * parseFloat(trade.price),
|
|
587
|
+
close_time: new Date(trade['created-at']),
|
|
588
|
+
fees: {
|
|
589
|
+
[post_process_cur(trade['fee-currency'])]: parseFloat(trade['filled-fees']),
|
|
590
|
+
},
|
|
591
|
+
role: String(trade.role || '').toLowerCase(),
|
|
592
|
+
}
|
|
593
|
+
let trade_id = trade['match-id'] || trade['trade-id'] || trade.id
|
|
594
|
+
if (trade_id != null) result.trade_id = trade_id.toString()
|
|
595
|
+
return result
|
|
596
|
+
}),
|
|
597
|
+
)
|
|
598
|
+
} else if (body && body.status === 'error' && body['err-code'] === 'api-signature-not-valid') {
|
|
599
|
+
reject({ error: error_codes.invalid_key, body })
|
|
600
|
+
} else if (body && body.toString().startsWith('<html>')) {
|
|
601
|
+
reject({ error: error_codes.service_unavailable, body })
|
|
602
|
+
} else if (body) {
|
|
603
|
+
reject({ error: error_codes.other, body })
|
|
604
|
+
} else {
|
|
605
|
+
reject({ error: error_codes.timeout })
|
|
606
|
+
}
|
|
607
|
+
})
|
|
608
|
+
}),
|
|
609
|
+
})
|
|
610
|
+
cb({ success: true, body: sort_history_rows(rows) })
|
|
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
|
+
}
|
|
542
617
|
// get_all_trades(timeframe_in_ms, cb) {
|
|
543
618
|
// let get_all_trades_base = (timeframe_in_ms, cb) => {
|
|
544
619
|
// let base_url = 'api.huobi.pro'
|
package/lib/exchanges/indodax.js
CHANGED
|
@@ -391,6 +391,9 @@ module.exports = class Indodax extends ExchangeBase {
|
|
|
391
391
|
}
|
|
392
392
|
this.private_limiter.process(get_trades_base, pair, cb)
|
|
393
393
|
}
|
|
394
|
+
get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
|
|
395
|
+
this._get_history_trades_via_timeframe(pair, start_time_in_ms, end_time_in_ms, cb)
|
|
396
|
+
}
|
|
394
397
|
static get_min_amount(cb) {
|
|
395
398
|
let min_quote_cur_amount = {}
|
|
396
399
|
needle.get('https://indodax.com/api/pairs', (err, res, body) => {
|
package/lib/exchanges/kraken.js
CHANGED
|
@@ -333,6 +333,69 @@ module.exports = class Kraken extends ExchangeBase {
|
|
|
333
333
|
}
|
|
334
334
|
})
|
|
335
335
|
}
|
|
336
|
+
get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
|
|
337
|
+
let api_secret_key = this.api_secret_key
|
|
338
|
+
let { filter_history_in_range, sort_history_rows, sleep } = ExchangeBase.history_utils
|
|
339
|
+
let path = '/0/private/TradesHistory'
|
|
340
|
+
let fetch_page = (ofs) =>
|
|
341
|
+
new Promise((resolve, reject) => {
|
|
342
|
+
let params = {
|
|
343
|
+
nonce: Date.now() + ofs,
|
|
344
|
+
start: Math.floor(start_time_in_ms / 1000),
|
|
345
|
+
end: Math.floor((end_time_in_ms - 1) / 1000),
|
|
346
|
+
ofs,
|
|
347
|
+
}
|
|
348
|
+
let options = get_options(path, params, api_secret_key, params.nonce)
|
|
349
|
+
this.private_limiter.process(needle.post, 'https://api.kraken.com' + path, params, options, (err, res, body) => {
|
|
350
|
+
if (body && body.result && body.result.trades) {
|
|
351
|
+
let trades = []
|
|
352
|
+
for (let trade_id in body.result.trades) {
|
|
353
|
+
let trade = body.result.trades[trade_id]
|
|
354
|
+
let pair = post_process_pair(trade.pair)
|
|
355
|
+
let [, quote_cur] = utils.parse_pair(pair)
|
|
356
|
+
let mapped_trade = {
|
|
357
|
+
order_id: trade.ordertxid.toString(),
|
|
358
|
+
trade_id: (trade.trade_id || trade_id || trade.postxid).toString(),
|
|
359
|
+
pair,
|
|
360
|
+
type: trade.type,
|
|
361
|
+
price: parseFloat(trade.price),
|
|
362
|
+
amount: parseFloat(trade.vol),
|
|
363
|
+
total: parseFloat(trade.cost),
|
|
364
|
+
close_time: new Date(parseFloat(trade.time) * 1000),
|
|
365
|
+
}
|
|
366
|
+
if (trade.maker !== undefined) mapped_trade.role = trade.maker ? 'maker' : 'taker'
|
|
367
|
+
if (trade.fee !== undefined) {
|
|
368
|
+
mapped_trade.fees = {
|
|
369
|
+
[quote_cur]: parseFloat(trade.fee),
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
trades.push({
|
|
373
|
+
...mapped_trade,
|
|
374
|
+
})
|
|
375
|
+
}
|
|
376
|
+
resolve({ trades, count: parseInt(body.result.count || trades.length) })
|
|
377
|
+
} else if (body) {
|
|
378
|
+
reject({ success: false, error: error_codes.other, body })
|
|
379
|
+
} else {
|
|
380
|
+
reject({ success: false, error: error_codes.timeout })
|
|
381
|
+
}
|
|
382
|
+
})
|
|
383
|
+
})
|
|
384
|
+
;(async () => {
|
|
385
|
+
let trades = []
|
|
386
|
+
let offset = 0
|
|
387
|
+
while (true) {
|
|
388
|
+
let page = await fetch_page(offset)
|
|
389
|
+
trades.push(...page.trades)
|
|
390
|
+
offset += page.trades.length
|
|
391
|
+
if (page.trades.length === 0 || offset >= page.count) break
|
|
392
|
+
await sleep(250)
|
|
393
|
+
}
|
|
394
|
+
trades = trades.filter((trade) => trade.pair === pair)
|
|
395
|
+
trades = sort_history_rows(filter_history_in_range(trades, start_time_in_ms, end_time_in_ms))
|
|
396
|
+
cb({ success: true, body: trades })
|
|
397
|
+
})().catch((error) => cb(error))
|
|
398
|
+
}
|
|
336
399
|
static get_precision(cb) {
|
|
337
400
|
let price_precision = {},
|
|
338
401
|
amount_precision = {}
|
package/lib/exchanges/kucoin.js
CHANGED
|
@@ -121,8 +121,9 @@ module.exports = class Kucoin extends ExchangeBase {
|
|
|
121
121
|
type: 'ping',
|
|
122
122
|
}),
|
|
123
123
|
)
|
|
124
|
-
this.ws.market.on_message((data) => {
|
|
124
|
+
this.ws.market.on_message((data, t_recv) => {
|
|
125
125
|
data = JSON.parse(data)
|
|
126
|
+
const t_parsed = process.hrtime()
|
|
126
127
|
let book = data.data
|
|
127
128
|
if (data.type === 'pong') {
|
|
128
129
|
this.ws.market.last_pong = new Date()
|
|
@@ -132,7 +133,7 @@ module.exports = class Kucoin extends ExchangeBase {
|
|
|
132
133
|
this.ws.market.pair_status[pair] = 'opened'
|
|
133
134
|
this.ws.market.asks_dict[pair] = book.asks.map((ask) => [parseFloat(ask[0]), parseFloat(ask[1])])
|
|
134
135
|
this.ws.market.bids_dict[pair] = book.bids.map((bid) => [parseFloat(bid[0]), parseFloat(bid[1])])
|
|
135
|
-
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: book.timestamp }
|
|
136
|
+
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: book.timestamp, t_recv, t_parsed }
|
|
136
137
|
} else if (data && data.type === 'message' && data.topic && data.subject === 'level1' && book && book.timestamp && book.bids && book.asks) {
|
|
137
138
|
let val = data.topic.split(':')
|
|
138
139
|
let pair = post_process_pair(val[1])
|
|
@@ -148,7 +149,7 @@ module.exports = class Kucoin extends ExchangeBase {
|
|
|
148
149
|
if (options.depth === false) {
|
|
149
150
|
this.ws.market.asks_dict[pair] = [[parseFloat(ask_1_price), parseFloat(ask_1_amount)]]
|
|
150
151
|
this.ws.market.bids_dict[pair] = [[parseFloat(bid_1_price), parseFloat(bid_1_amount)]]
|
|
151
|
-
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: book.timestamp }
|
|
152
|
+
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: book.timestamp, t_recv, t_parsed }
|
|
152
153
|
this.ws.market.pair_status[pair] = 'opened'
|
|
153
154
|
} else {
|
|
154
155
|
if (!Array.isArray(this.ws.market.asks_dict[pair]) || !Array.isArray(this.ws.market.bids_dict[pair])) {
|
package/lib/exchanges/lbank.js
CHANGED
|
@@ -7,6 +7,7 @@ const needle = require('needle')
|
|
|
7
7
|
const ExchangeWs = require('./exchange-ws.js')
|
|
8
8
|
|
|
9
9
|
const ExchangeBase = require('./exchange-base.js')
|
|
10
|
+
const { filter_history_in_range, sleep, sort_history_rows, split_windows } = ExchangeBase.history_utils
|
|
10
11
|
|
|
11
12
|
const { error_codes } = require('../error_codes.json')
|
|
12
13
|
const { utils, rate_limiter: Limiter } = require('@icgio/icg-utils')
|
|
@@ -69,11 +70,29 @@ const uuid38 = () => uuid16() + uuid22()
|
|
|
69
70
|
function get_signature_lbank(secret_key, request, timestamp, echostr) {
|
|
70
71
|
request = _.extend(request, { timestamp, echostr, signature_method: 'HmacSHA256' })
|
|
71
72
|
request = _.fromPairs(_.sortBy(_.toPairs(request), [0]))
|
|
72
|
-
let message = qs.stringify(request)
|
|
73
|
+
let message = qs.stringify(request, { encode: false })
|
|
73
74
|
message = crypto.createHash('md5').update(message).digest('hex').toUpperCase()
|
|
74
75
|
return crypto.createHmac('sha256', secret_key).update(message).digest('hex')
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
function format_history_time_utc8(ms) {
|
|
79
|
+
const date = new Date(ms + 8 * 60 * 60 * 1000)
|
|
80
|
+
const pad = (value) => String(value).padStart(2, '0')
|
|
81
|
+
return (
|
|
82
|
+
date.getUTCFullYear() +
|
|
83
|
+
'-' +
|
|
84
|
+
pad(date.getUTCMonth() + 1) +
|
|
85
|
+
'-' +
|
|
86
|
+
pad(date.getUTCDate()) +
|
|
87
|
+
' ' +
|
|
88
|
+
pad(date.getUTCHours()) +
|
|
89
|
+
':' +
|
|
90
|
+
pad(date.getUTCMinutes()) +
|
|
91
|
+
':' +
|
|
92
|
+
pad(date.getUTCSeconds())
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
77
96
|
function parse_lbank_ws_timestamp(value) {
|
|
78
97
|
if (value instanceof Date) return value
|
|
79
98
|
if (typeof value === 'number') return new Date(value)
|
|
@@ -138,8 +157,9 @@ module.exports = class Lbank extends ExchangeBase {
|
|
|
138
157
|
})
|
|
139
158
|
this.ws.market.ping(JSON.stringify({ action: 'ping', ping: 'keepalive' }))
|
|
140
159
|
})
|
|
141
|
-
this.ws.market.on_message((data) => {
|
|
160
|
+
this.ws.market.on_message((data, t_recv) => {
|
|
142
161
|
data = JSON.parse(data)
|
|
162
|
+
const t_parsed = process.hrtime()
|
|
143
163
|
if (data.action === 'ping') {
|
|
144
164
|
this.ws.market.send(
|
|
145
165
|
JSON.stringify({
|
|
@@ -162,7 +182,7 @@ module.exports = class Lbank extends ExchangeBase {
|
|
|
162
182
|
this.ws.market.pair_status[pair] = 'opened'
|
|
163
183
|
this.ws.market.asks_dict[pair] = data.depth.asks.map((ask) => [parseFloat(ask[0]), parseFloat(ask[1])])
|
|
164
184
|
this.ws.market.bids_dict[pair] = data.depth.bids.map((bid) => [parseFloat(bid[0]), parseFloat(bid[1])])
|
|
165
|
-
this.ws.market.ts_dict[pair] = { received_ts: Date.now() }
|
|
185
|
+
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), t_recv, t_parsed }
|
|
166
186
|
const asks = this.ws.market.asks_dict[pair]
|
|
167
187
|
const bids = this.ws.market.bids_dict[pair]
|
|
168
188
|
if (asks && asks.length > 0 && bids && bids.length > 0) {
|
|
@@ -580,61 +600,77 @@ module.exports = class Lbank extends ExchangeBase {
|
|
|
580
600
|
this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
|
|
581
601
|
}
|
|
582
602
|
get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
603
|
+
const get_history_trades_base = (pair, start_time_in_ms, end_time_in_ms, cb) => {
|
|
604
|
+
let [api_key, secret_key] = this.api_secret_key
|
|
605
|
+
;(async () => {
|
|
606
|
+
const rows = []
|
|
607
|
+
const max_window_ms = 2 * 24 * 60 * 60 * 1000
|
|
608
|
+
const windows = split_windows(start_time_in_ms, end_time_in_ms, Math.min(max_window_ms, end_time_in_ms - start_time_in_ms))
|
|
609
|
+
for (const [window_start_ms, window_stop_ms] of windows) {
|
|
610
|
+
let last_trade_id = null
|
|
611
|
+
while (true) {
|
|
612
|
+
const raw_items = await new Promise((resolve, reject) => {
|
|
613
|
+
let timestamp = new Date().getTime().toString()
|
|
614
|
+
let echostr = uuid38()
|
|
615
|
+
let params = {
|
|
616
|
+
api_key: api_key,
|
|
617
|
+
endTime: format_history_time_utc8(window_stop_ms),
|
|
618
|
+
limit: '100',
|
|
619
|
+
startTime: format_history_time_utc8(window_start_ms),
|
|
620
|
+
symbol: pre_process_pair(pair),
|
|
621
|
+
}
|
|
622
|
+
if (last_trade_id) params.fromId = last_trade_id
|
|
623
|
+
params.sign = get_signature_lbank(secret_key, params, timestamp, echostr)
|
|
624
|
+
let options = get_options(timestamp, echostr)
|
|
625
|
+
needle.post('https://api.lbank.info/v2/supplement/transaction_history.do', params, options, (err, res, body) => {
|
|
626
|
+
body = parse_body(body)
|
|
627
|
+
if (body && body.result && body.error_code === 0 && !body.data) {
|
|
628
|
+
resolve([])
|
|
629
|
+
} else if (body && body.result && Array.isArray(body.data)) {
|
|
630
|
+
resolve(
|
|
631
|
+
body.data.map((trade) => {
|
|
632
|
+
let result = {
|
|
633
|
+
order_id: trade.orderId != null ? trade.orderId.toString() : '',
|
|
634
|
+
pair,
|
|
635
|
+
type: trade.isBuyer ? 'buy' : 'sell',
|
|
636
|
+
price: parseFloat(trade.price),
|
|
637
|
+
amount: parseFloat(trade.qty),
|
|
638
|
+
total: parseFloat(trade.quoteQty),
|
|
639
|
+
close_time: new Date(trade.time),
|
|
640
|
+
fees: {
|
|
641
|
+
[trade.isBuyer ? pair_to_base_cur(pair) : pair_to_quote_cur(pair)]: parseFloat(trade.commission),
|
|
642
|
+
},
|
|
643
|
+
role: trade.isMaker ? 'maker' : 'taker',
|
|
644
|
+
}
|
|
645
|
+
if (trade.id != null) result.trade_id = trade.id.toString()
|
|
646
|
+
return result
|
|
647
|
+
}),
|
|
648
|
+
)
|
|
649
|
+
} else if (body) {
|
|
650
|
+
reject({ error: error_codes.other, body })
|
|
651
|
+
} else {
|
|
652
|
+
reject({ error: error_codes.timeout })
|
|
653
|
+
}
|
|
654
|
+
})
|
|
655
|
+
})
|
|
656
|
+
if (raw_items.length === 0) break
|
|
657
|
+
let items = raw_items
|
|
658
|
+
if (last_trade_id && items[0] && items[0].trade_id === last_trade_id) {
|
|
659
|
+
items = items.slice(1)
|
|
626
660
|
}
|
|
661
|
+
if (items.length === 0) break
|
|
662
|
+
rows.push(...items)
|
|
663
|
+
last_trade_id = raw_items[raw_items.length - 1]?.trade_id || null
|
|
664
|
+
if (raw_items.length < 100 || !last_trade_id) break
|
|
665
|
+
await sleep(50)
|
|
627
666
|
}
|
|
628
|
-
cb({ success: true, body: all_results })
|
|
629
|
-
} else if (body) {
|
|
630
|
-
cb({ success: false, error: error_codes.other, body })
|
|
631
|
-
} else {
|
|
632
|
-
cb({ success: false, error: error_codes.timeout })
|
|
633
667
|
}
|
|
668
|
+
cb({ success: true, body: sort_history_rows(filter_history_in_range(rows, start_time_in_ms, end_time_in_ms)) })
|
|
669
|
+
})().catch((error) => {
|
|
670
|
+
cb({ success: false, error: error?.error || error_codes.other, body: error?.body })
|
|
634
671
|
})
|
|
635
672
|
}
|
|
636
|
-
|
|
637
|
-
this.private_limiter.process(fetch_page, start_time_in_ms)
|
|
673
|
+
this.private_limiter.process(get_history_trades_base, pair, start_time_in_ms, end_time_in_ms, cb)
|
|
638
674
|
}
|
|
639
675
|
get_all_deposits(timeframe_in_ms, cb) {
|
|
640
676
|
const get_all_deposits_base = (timeframe_in_ms, cb) => {
|