@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/lib/exchanges/bitmex.js
CHANGED
|
@@ -141,8 +141,9 @@ module.exports = class Bitmex extends ExchangeBase {
|
|
|
141
141
|
this.ws.market.bids_dict[pair] = utils.ordered_dict(false)
|
|
142
142
|
}
|
|
143
143
|
})
|
|
144
|
-
this.ws.market.on_message((data) => {
|
|
144
|
+
this.ws.market.on_message((data, t_recv) => {
|
|
145
145
|
data = JSON.parse(data)
|
|
146
|
+
const t_parsed = process.hrtime()
|
|
146
147
|
if (data.table === 'orderBookL2') {
|
|
147
148
|
if (data.action === 'partial') {
|
|
148
149
|
if (!data.data[0]) {
|
|
@@ -685,6 +686,75 @@ module.exports = class Bitmex extends ExchangeBase {
|
|
|
685
686
|
}
|
|
686
687
|
this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
|
|
687
688
|
}
|
|
689
|
+
get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
|
|
690
|
+
let [api_key, secret_key] = this.api_secret_key
|
|
691
|
+
let { fetch_split_windows, filter_history_in_range, sort_history_rows } = ExchangeBase.history_utils
|
|
692
|
+
let verb = 'GET'
|
|
693
|
+
let endpoint = '/api/v1/execution/tradeHistory'
|
|
694
|
+
let fetch_window = (window_start_ms, window_stop_ms) =>
|
|
695
|
+
new Promise((resolve, reject) => {
|
|
696
|
+
let params = {
|
|
697
|
+
count: 500,
|
|
698
|
+
symbol: pre_process_pair(pair),
|
|
699
|
+
startTime: new Date(window_start_ms).toISOString(),
|
|
700
|
+
endTime: new Date(window_stop_ms - 1).toISOString(),
|
|
701
|
+
}
|
|
702
|
+
let query = qs.stringify(params)
|
|
703
|
+
let nonce = Date.now() + 60 * 1000
|
|
704
|
+
let signature = get_signature_bitmex(verb, endpoint + '?' + query, nonce, null, secret_key)
|
|
705
|
+
let options = get_options(api_key, nonce, signature)
|
|
706
|
+
needle.get(base_url + endpoint + '?' + query, options, (err, res, body) => {
|
|
707
|
+
if (Array.isArray(body)) {
|
|
708
|
+
resolve(
|
|
709
|
+
body
|
|
710
|
+
.filter((trade) => trade.execType === 'Trade')
|
|
711
|
+
.map((trade) => {
|
|
712
|
+
let price = parseFloat(trade.lastPx || trade.avgPx || trade.price)
|
|
713
|
+
let amount = Math.abs(parseFloat(trade.homeNotional))
|
|
714
|
+
if (!Number.isFinite(amount)) {
|
|
715
|
+
let quantity = parseFloat(trade.lastQty || trade.cumQty || trade.foreignNotional)
|
|
716
|
+
amount = Number.isFinite(quantity) && Number.isFinite(price) && price !== 0 ? Math.abs(quantity) / price : 0
|
|
717
|
+
}
|
|
718
|
+
let total = Math.abs(parseFloat(trade.foreignNotional))
|
|
719
|
+
if (!Number.isFinite(total)) total = Math.abs(parseFloat(trade.lastQty || trade.cumQty || 0))
|
|
720
|
+
let mapped_trade = {
|
|
721
|
+
order_id: (trade.orderID || '').toString(),
|
|
722
|
+
trade_id: (trade.execID || trade.trdMatchID || '').toString(),
|
|
723
|
+
pair,
|
|
724
|
+
type: (trade.side || '').toLowerCase(),
|
|
725
|
+
price,
|
|
726
|
+
amount,
|
|
727
|
+
size: total,
|
|
728
|
+
total,
|
|
729
|
+
close_time: new Date(trade.transactTime || trade.timestamp),
|
|
730
|
+
}
|
|
731
|
+
if (trade.lastLiquidityInd === 'AddedLiquidity') mapped_trade.role = 'maker'
|
|
732
|
+
if (trade.lastLiquidityInd === 'RemovedLiquidity') mapped_trade.role = 'taker'
|
|
733
|
+
return mapped_trade
|
|
734
|
+
})
|
|
735
|
+
)
|
|
736
|
+
} else if (body && body.error && body.error.message === 'The system is currently overloaded. Please try again later.') {
|
|
737
|
+
reject({ success: false, error: error_codes.system_overloaded })
|
|
738
|
+
} else if (body) {
|
|
739
|
+
reject({ success: false, error: error_codes.other, body })
|
|
740
|
+
} else {
|
|
741
|
+
reject({ success: false, error: error_codes.timeout })
|
|
742
|
+
}
|
|
743
|
+
})
|
|
744
|
+
})
|
|
745
|
+
;(async () => {
|
|
746
|
+
let trades = await fetch_split_windows({
|
|
747
|
+
start_ms: start_time_in_ms,
|
|
748
|
+
stop_ms: end_time_in_ms,
|
|
749
|
+
initial_window_ms: 30 * 24 * 60 * 60 * 1000,
|
|
750
|
+
page_limit: 500,
|
|
751
|
+
fetch_window,
|
|
752
|
+
delay_ms: 250,
|
|
753
|
+
})
|
|
754
|
+
trades = sort_history_rows(filter_history_in_range(trades, start_time_in_ms, end_time_in_ms))
|
|
755
|
+
cb({ success: true, body: trades })
|
|
756
|
+
})().catch((error) => cb(error))
|
|
757
|
+
}
|
|
688
758
|
static get_precision(cb) {
|
|
689
759
|
let price_precision = {},
|
|
690
760
|
amount_precision = {}
|
package/lib/exchanges/bitrue.js
CHANGED
|
@@ -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
|
|
|
@@ -121,20 +122,21 @@ module.exports = class Bitrue extends ExchangeBase {
|
|
|
121
122
|
}),
|
|
122
123
|
)
|
|
123
124
|
})
|
|
124
|
-
this.ws.market.on_message((data) => {
|
|
125
|
+
this.ws.market.on_message((data, t_recv) => {
|
|
125
126
|
zlib.gunzip(data, (err, decompressedData) => {
|
|
126
127
|
if (err) {
|
|
127
128
|
console.error('Error decompressing data:', err)
|
|
128
129
|
} else {
|
|
129
130
|
let data = decompressedData.toString('utf8')
|
|
130
131
|
data = JSON.parse(data)
|
|
132
|
+
const t_parsed = process.hrtime()
|
|
131
133
|
if (data && _.includes(data.channel, 'depth_step0') && data.tick) {
|
|
132
134
|
let parts = data.channel.split('_')
|
|
133
135
|
let pair = post_process_pair(parts[1])
|
|
134
136
|
this.ws.market.pair_status[pair] = 'opened'
|
|
135
137
|
this.ws.market.asks_dict[pair] = data.tick.asks.map((ask) => [parseFloat(ask[0]), parseFloat(ask[1])])
|
|
136
138
|
this.ws.market.bids_dict[pair] = data.tick.buys.map((bid) => [parseFloat(bid[0]), parseFloat(bid[1])])
|
|
137
|
-
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: data.ts }
|
|
139
|
+
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: data.ts, t_recv, t_parsed }
|
|
138
140
|
const asks = this.ws.market.asks_dict[pair]
|
|
139
141
|
const bids = this.ws.market.bids_dict[pair]
|
|
140
142
|
if (asks && asks.length > 0 && bids && bids.length > 0) {
|
|
@@ -512,6 +514,61 @@ module.exports = class Bitrue extends ExchangeBase {
|
|
|
512
514
|
}
|
|
513
515
|
this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
|
|
514
516
|
}
|
|
517
|
+
get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
|
|
518
|
+
const get_history_trades_base = (pair, start_time_in_ms, end_time_in_ms, cb) => {
|
|
519
|
+
let [api_key, secret_key] = this.api_secret_key
|
|
520
|
+
const options = get_options(api_key, false)
|
|
521
|
+
;(async () => {
|
|
522
|
+
const rows = await fetch_split_windows({
|
|
523
|
+
start_ms: start_time_in_ms,
|
|
524
|
+
stop_ms: end_time_in_ms,
|
|
525
|
+
initial_window_ms: 60 * 60 * 1000,
|
|
526
|
+
page_limit: 1000,
|
|
527
|
+
fetch_window: async (window_start_ms, window_stop_ms) =>
|
|
528
|
+
await new Promise((resolve, reject) => {
|
|
529
|
+
let params = {
|
|
530
|
+
symbol: pre_process_pair(pair),
|
|
531
|
+
startTime: window_start_ms,
|
|
532
|
+
endTime: window_stop_ms - 1,
|
|
533
|
+
limit: 1000,
|
|
534
|
+
timestamp: parseInt(Date.now()),
|
|
535
|
+
}
|
|
536
|
+
let query = qs.stringify(params)
|
|
537
|
+
let sign = get_signature_bitrue(secret_key, query)
|
|
538
|
+
let url = 'https://openapi.bitrue.com/api/v2/myTrades?' + query + `&signature=${sign}`
|
|
539
|
+
needle.get(url, options, (err, res, body) => {
|
|
540
|
+
let parsed_body = typeof body === 'string' ? JSONbig.parse(body) : body
|
|
541
|
+
if (parsed_body && Array.isArray(parsed_body)) {
|
|
542
|
+
resolve(
|
|
543
|
+
parsed_body.map((trade) => ({
|
|
544
|
+
order_id: trade.orderId != null ? trade.orderId.toString() : '',
|
|
545
|
+
pair,
|
|
546
|
+
type: trade.isBuyer ? 'buy' : 'sell',
|
|
547
|
+
price: parseFloat(trade.price),
|
|
548
|
+
amount: parseFloat(trade.qty),
|
|
549
|
+
total: parseFloat(trade.price) * parseFloat(trade.qty),
|
|
550
|
+
close_time: new Date(trade.time),
|
|
551
|
+
fees: {
|
|
552
|
+
[post_process_cur(trade.commissionAsset)]: parseFloat(trade.commission),
|
|
553
|
+
},
|
|
554
|
+
role: trade.isMaker ? 'maker' : 'taker',
|
|
555
|
+
})),
|
|
556
|
+
)
|
|
557
|
+
} else if (parsed_body) {
|
|
558
|
+
reject({ error: error_codes.other, body: parsed_body })
|
|
559
|
+
} else {
|
|
560
|
+
reject({ error: error_codes.timeout })
|
|
561
|
+
}
|
|
562
|
+
})
|
|
563
|
+
}),
|
|
564
|
+
})
|
|
565
|
+
cb({ success: true, body: sort_history_rows(rows) })
|
|
566
|
+
})().catch((error) => {
|
|
567
|
+
cb({ success: false, error: error?.error || error_codes.other, body: error?.body })
|
|
568
|
+
})
|
|
569
|
+
}
|
|
570
|
+
this.private_limiter.process(get_history_trades_base, pair, start_time_in_ms, end_time_in_ms, cb)
|
|
571
|
+
}
|
|
515
572
|
get_deposits(cur, timeframe_in_ms, cb) {
|
|
516
573
|
let get_deposits_base = (timeframe_in_ms, cb) => {
|
|
517
574
|
let [api_key, secret_key] = this.api_secret_key
|
|
@@ -370,6 +370,77 @@ module.exports = class Bitstamp extends ExchangeBase {
|
|
|
370
370
|
}
|
|
371
371
|
this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
|
|
372
372
|
}
|
|
373
|
+
get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
|
|
374
|
+
let [api_key, secret_key] = this.api_secret_key
|
|
375
|
+
let { filter_history_in_range, sort_history_rows, sleep } = ExchangeBase.history_utils
|
|
376
|
+
let [base_cur, quote_cur] = utils.parse_pair(pair)
|
|
377
|
+
let base_key = base_cur.toLowerCase()
|
|
378
|
+
let quote_key = quote_cur.toLowerCase()
|
|
379
|
+
let price_key = base_key + '_' + quote_key
|
|
380
|
+
let parse_trade_time = (datetime) => {
|
|
381
|
+
let iso = datetime.replace(' ', 'T').replace(/(\.\d{3})\d+$/, '$1')
|
|
382
|
+
return new Date(iso + 'Z')
|
|
383
|
+
}
|
|
384
|
+
let fetch_page = (offset) =>
|
|
385
|
+
new Promise((resolve, reject) => {
|
|
386
|
+
let url = 'https://www.bitstamp.net/api/v2/user_transactions/' + pre_process_pair(pair) + '/'
|
|
387
|
+
let params = {
|
|
388
|
+
key: api_key,
|
|
389
|
+
nonce: Date.now(),
|
|
390
|
+
signature: get_signature_bitstamp(api_key, secret_key, this.account_id),
|
|
391
|
+
limit: 1000,
|
|
392
|
+
offset,
|
|
393
|
+
sort: 'desc',
|
|
394
|
+
}
|
|
395
|
+
needle.post(url, params, (err, res, body) => {
|
|
396
|
+
if (Array.isArray(body)) {
|
|
397
|
+
resolve(body)
|
|
398
|
+
} else if (body) {
|
|
399
|
+
reject({ success: false, error: error_codes.other, body })
|
|
400
|
+
} else {
|
|
401
|
+
reject({ success: false, error: error_codes.timeout })
|
|
402
|
+
}
|
|
403
|
+
})
|
|
404
|
+
})
|
|
405
|
+
;(async () => {
|
|
406
|
+
let trades = []
|
|
407
|
+
let offset = 0
|
|
408
|
+
while (offset <= 200000) {
|
|
409
|
+
let rows = await fetch_page(offset)
|
|
410
|
+
if (rows.length === 0) break
|
|
411
|
+
trades.push(
|
|
412
|
+
...rows
|
|
413
|
+
.filter((trade) => String(trade.type) === '2')
|
|
414
|
+
.map((trade) => {
|
|
415
|
+
let close_time = parse_trade_time(trade.datetime)
|
|
416
|
+
let price = parseFloat(trade[price_key])
|
|
417
|
+
let amount = Math.abs(parseFloat(trade[base_key]))
|
|
418
|
+
let total = Math.abs(parseFloat(trade[quote_key]))
|
|
419
|
+
return {
|
|
420
|
+
order_id: trade.order_id != null ? trade.order_id.toString() : '',
|
|
421
|
+
trade_id: (trade.id || trade.tid || '').toString(),
|
|
422
|
+
pair,
|
|
423
|
+
type: parseFloat(trade[base_key]) < 0 ? 'sell' : 'buy',
|
|
424
|
+
price,
|
|
425
|
+
amount,
|
|
426
|
+
total: Number.isFinite(total) ? total : price * amount,
|
|
427
|
+
close_time,
|
|
428
|
+
}
|
|
429
|
+
})
|
|
430
|
+
)
|
|
431
|
+
let oldest_seen_ms = Math.min(
|
|
432
|
+
...rows
|
|
433
|
+
.map((trade) => parse_trade_time(trade.datetime).getTime())
|
|
434
|
+
.filter((time) => Number.isFinite(time))
|
|
435
|
+
)
|
|
436
|
+
if (rows.length < 1000 || (Number.isFinite(oldest_seen_ms) && oldest_seen_ms < start_time_in_ms)) break
|
|
437
|
+
offset += rows.length
|
|
438
|
+
await sleep(250)
|
|
439
|
+
}
|
|
440
|
+
trades = sort_history_rows(filter_history_in_range(trades, start_time_in_ms, end_time_in_ms))
|
|
441
|
+
cb({ success: true, body: trades })
|
|
442
|
+
})().catch((error) => cb(error))
|
|
443
|
+
}
|
|
373
444
|
static get_min_amount(cb) {
|
|
374
445
|
let min_quote_cur_amount = {}
|
|
375
446
|
needle.get('https://www.bitstamp.net/api/v2/trading-pairs-info/', (err, res, body) => {
|
package/lib/exchanges/blofin.js
CHANGED
|
@@ -150,9 +150,10 @@ module.exports = class Blofin extends ExchangeBase {
|
|
|
150
150
|
}),
|
|
151
151
|
)
|
|
152
152
|
})
|
|
153
|
-
this.ws.market.on_message((data) => {
|
|
153
|
+
this.ws.market.on_message((data, t_recv) => {
|
|
154
154
|
try {
|
|
155
155
|
data = JSON.parse(data)
|
|
156
|
+
const t_parsed = process.hrtime()
|
|
156
157
|
if (data.channel === 'DEPTH') {
|
|
157
158
|
const action = data.data_type
|
|
158
159
|
const pair = post_process_pair(data.symbol)
|
|
@@ -168,7 +169,7 @@ module.exports = class Blofin extends ExchangeBase {
|
|
|
168
169
|
const [price, size] = bid
|
|
169
170
|
this.ws.market.bids_dict[pair].insert(parseFloat(price), parseFloat(size))
|
|
170
171
|
}
|
|
171
|
-
this.ws.market.ts_dict[pair] = { received_ts: Date.now() }
|
|
172
|
+
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), t_recv, t_parsed }
|
|
172
173
|
{
|
|
173
174
|
const _asks = this.ws.market.asks_dict[pair].entries()
|
|
174
175
|
const _bids = this.ws.market.bids_dict[pair].entries()
|
|
@@ -197,7 +198,7 @@ module.exports = class Blofin extends ExchangeBase {
|
|
|
197
198
|
this.ws.market.bids_dict[pair].insert(parseFloat(price), parseFloat(size))
|
|
198
199
|
}
|
|
199
200
|
}
|
|
200
|
-
this.ws.market.ts_dict[pair] = { received_ts: Date.now() }
|
|
201
|
+
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), t_recv, t_parsed }
|
|
201
202
|
{
|
|
202
203
|
const _asks = this.ws.market.asks_dict[pair].entries()
|
|
203
204
|
const _bids = this.ws.market.bids_dict[pair].entries()
|
|
@@ -583,6 +584,78 @@ module.exports = class Blofin extends ExchangeBase {
|
|
|
583
584
|
}
|
|
584
585
|
this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
|
|
585
586
|
}
|
|
587
|
+
get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
|
|
588
|
+
let [api_key, secret_key] = this.api_secret_key
|
|
589
|
+
let { fetch_split_windows, filter_history_in_range, sort_history_rows } = ExchangeBase.history_utils
|
|
590
|
+
let [base_cur, quote_cur] = utils.parse_pair(pair)
|
|
591
|
+
let path = '/api/v1/spot/trade/fills-history'
|
|
592
|
+
let fetch_window = (window_start_ms, window_stop_ms) =>
|
|
593
|
+
new Promise((resolve, reject) => {
|
|
594
|
+
let params = {
|
|
595
|
+
instType: 'SPOT',
|
|
596
|
+
instId: pre_process_pair(pair),
|
|
597
|
+
begin: window_start_ms.toString(),
|
|
598
|
+
end: (window_stop_ms - 1).toString(),
|
|
599
|
+
limit: '100',
|
|
600
|
+
}
|
|
601
|
+
let query = qs.stringify(params)
|
|
602
|
+
let request_path = path + '?' + query
|
|
603
|
+
let timestamp = Date.now()
|
|
604
|
+
let nonce = generate_nonce()
|
|
605
|
+
let signature = get_signature_blofin(secret_key, 'GET', timestamp, request_path, nonce)
|
|
606
|
+
let options = get_options(api_key, signature, timestamp, nonce, this.passphrase)
|
|
607
|
+
let url = 'https://openapi.blofin.com' + request_path
|
|
608
|
+
needle.get(url, options, (err, res, body) => {
|
|
609
|
+
if (body && _.includes(['0', 0, 200], body.code) && Array.isArray(body.data)) {
|
|
610
|
+
resolve(
|
|
611
|
+
body.data.map((trade) => {
|
|
612
|
+
let price = parseFloat(trade.fillPrice || trade.trade_price)
|
|
613
|
+
let amount = parseFloat(trade.fillSize || trade.trade_quantity)
|
|
614
|
+
let total = price * amount
|
|
615
|
+
let fee = Math.abs(parseFloat(trade.fee || trade.trade_fee))
|
|
616
|
+
let fee_currency = trade.feeCurrency || trade.fee_currency
|
|
617
|
+
if (fee_currency === 'base_currency') fee_currency = base_cur
|
|
618
|
+
if (fee_currency === 'quote_currency') fee_currency = quote_cur
|
|
619
|
+
let mapped_trade = {
|
|
620
|
+
order_id: (trade.orderId || trade.order_id || '').toString(),
|
|
621
|
+
trade_id: (trade.tradeId || trade.trade_id || '').toString(),
|
|
622
|
+
pair,
|
|
623
|
+
type: (trade.side || trade.order_side || '').toLowerCase(),
|
|
624
|
+
price,
|
|
625
|
+
amount,
|
|
626
|
+
total,
|
|
627
|
+
close_time: new Date(parseInt(trade.ts || trade.create_time)),
|
|
628
|
+
}
|
|
629
|
+
if (Number.isFinite(fee) && fee_currency) {
|
|
630
|
+
mapped_trade.fees = {
|
|
631
|
+
[post_process_cur(fee_currency)]: fee,
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return mapped_trade
|
|
635
|
+
})
|
|
636
|
+
)
|
|
637
|
+
} else if (body && body.toString().startsWith('<html>')) {
|
|
638
|
+
reject({ success: false, error: error_codes.service_unavailable, body })
|
|
639
|
+
} else if (body) {
|
|
640
|
+
reject({ success: false, error: error_codes.other, body })
|
|
641
|
+
} else {
|
|
642
|
+
reject({ success: false, error: error_codes.timeout })
|
|
643
|
+
}
|
|
644
|
+
})
|
|
645
|
+
})
|
|
646
|
+
;(async () => {
|
|
647
|
+
let trades = await fetch_split_windows({
|
|
648
|
+
start_ms: start_time_in_ms,
|
|
649
|
+
stop_ms: end_time_in_ms,
|
|
650
|
+
initial_window_ms: 30 * 24 * 60 * 60 * 1000,
|
|
651
|
+
page_limit: 100,
|
|
652
|
+
fetch_window,
|
|
653
|
+
delay_ms: 250,
|
|
654
|
+
})
|
|
655
|
+
trades = sort_history_rows(filter_history_in_range(trades, start_time_in_ms, end_time_in_ms))
|
|
656
|
+
cb({ success: true, body: trades })
|
|
657
|
+
})().catch((error) => cb(error))
|
|
658
|
+
}
|
|
586
659
|
get_all_deposits(timeframe_in_ms, cb) {
|
|
587
660
|
let get_all_deposits_base = (timeframe_in_ms, cb) => {
|
|
588
661
|
let [api_key, secret_key] = this.api_secret_key
|
package/lib/exchanges/btse.js
CHANGED
|
@@ -96,11 +96,12 @@ module.exports = class Btse extends ExchangeBase {
|
|
|
96
96
|
})
|
|
97
97
|
this.ws.market.ping('ping')
|
|
98
98
|
})
|
|
99
|
-
this.ws.market.on_message((data) => {
|
|
99
|
+
this.ws.market.on_message((data, t_recv) => {
|
|
100
100
|
if (data.toString() === 'pong') {
|
|
101
101
|
this.ws.market.last_pong = new Date()
|
|
102
102
|
} else {
|
|
103
103
|
data = JSON.parse(data)
|
|
104
|
+
const t_parsed = process.hrtime()
|
|
104
105
|
if (data && data.topic && data.topic.includes('snapshot:' || 'update:') && data.data) {
|
|
105
106
|
let pair = post_process_pair(data.data.symbol)
|
|
106
107
|
let current_timestamp = new Date().getTime()
|
|
@@ -132,7 +133,7 @@ module.exports = class Btse extends ExchangeBase {
|
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
}
|
|
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
|
{
|
|
137
138
|
const _asks = this.ws.market.asks_dict[pair].entries()
|
|
138
139
|
const _bids = this.ws.market.bids_dict[pair].entries()
|
|
@@ -488,6 +489,9 @@ module.exports = class Btse extends ExchangeBase {
|
|
|
488
489
|
}
|
|
489
490
|
this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
|
|
490
491
|
}
|
|
492
|
+
get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
|
|
493
|
+
this._get_history_trades_via_timeframe(pair, start_time_in_ms, end_time_in_ms, cb)
|
|
494
|
+
}
|
|
491
495
|
get_all_deposits(timeframe_in_ms, cb) {
|
|
492
496
|
let get_all_deposits_base = (timeframe_in_ms, cb) => {
|
|
493
497
|
let [api_key, secret_key] = this.api_secret_key
|
package/lib/exchanges/bybit.js
CHANGED
|
@@ -90,9 +90,10 @@ module.exports = class Bybit extends ExchangeBase {
|
|
|
90
90
|
})
|
|
91
91
|
this.ws.market.ping(JSON.stringify({ op: 'ping' }))
|
|
92
92
|
})
|
|
93
|
-
this.ws.market.on_message((data) => {
|
|
93
|
+
this.ws.market.on_message((data, t_recv) => {
|
|
94
94
|
try {
|
|
95
95
|
data = JSON.parse(data)
|
|
96
|
+
const t_parsed = process.hrtime()
|
|
96
97
|
if (data.success && data.ret_msg === 'pong') {
|
|
97
98
|
this.ws.market.last_pong = new Date()
|
|
98
99
|
} else {
|
|
@@ -131,7 +132,7 @@ module.exports = class Bybit extends ExchangeBase {
|
|
|
131
132
|
}
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
|
-
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: data.ts }
|
|
135
|
+
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: data.ts, t_recv, t_parsed }
|
|
135
136
|
// Populate BBO from orderbook data
|
|
136
137
|
if (options.bbo !== false && data.data.a && data.data.a.length > 0 && data.data.b && data.data.b.length > 0) {
|
|
137
138
|
const best_ask = data.data.a[0]
|
|
@@ -7,6 +7,7 @@ const needle = require('needle')
|
|
|
7
7
|
|
|
8
8
|
const ExchangeWs = require('./exchange-ws.js')
|
|
9
9
|
const ExchangeBase = require('./exchange-base.js')
|
|
10
|
+
const { filter_history_in_range, history_time_ms, sleep, sort_history_rows } = ExchangeBase.history_utils
|
|
10
11
|
|
|
11
12
|
const { sorted_book } = require('@icgio/icg-utils')
|
|
12
13
|
|
|
@@ -218,13 +219,14 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
218
219
|
}))
|
|
219
220
|
console.log('coinbase ws-direct: subscribed channels:', channels.join(', '))
|
|
220
221
|
})
|
|
221
|
-
this.ws.market.on_message((data) => {
|
|
222
|
+
this.ws.market.on_message((data, t_recv) => {
|
|
222
223
|
try {
|
|
223
224
|
data = JSON.parse(data)
|
|
224
225
|
} catch (err) {
|
|
225
226
|
console.error('coinbase', 'ws-direct', 'JSON parse error:', err.message)
|
|
226
227
|
return
|
|
227
228
|
}
|
|
229
|
+
const t_parsed = process.hrtime()
|
|
228
230
|
if (data.type === 'snapshot') {
|
|
229
231
|
const pair = post_process_pair(data.product_id)
|
|
230
232
|
if (!this.ws.market.asks_dict[pair]) return
|
|
@@ -251,7 +253,7 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
251
253
|
if (price >= lo && price <= hi) this.ws.market.asks_dict[pair].insert(price, qty)
|
|
252
254
|
}
|
|
253
255
|
this.ws.market.pair_status[pair] = 'opened'
|
|
254
|
-
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: Date.now() }
|
|
256
|
+
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: Date.now(), t_recv, t_parsed }
|
|
255
257
|
const top_bid = this.ws.market.bids_dict[pair].top()
|
|
256
258
|
const top_ask = this.ws.market.asks_dict[pair].top()
|
|
257
259
|
if (top_bid != null && top_ask != null) {
|
|
@@ -282,7 +284,7 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
282
284
|
}
|
|
283
285
|
}
|
|
284
286
|
}
|
|
285
|
-
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: parse_event_ts_ms(data.time) || Date.now() }
|
|
287
|
+
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: parse_event_ts_ms(data.time) || Date.now(), t_recv, t_parsed }
|
|
286
288
|
{
|
|
287
289
|
const top_bid = this.ws.market.bids_dict[pair].top()
|
|
288
290
|
const top_ask = this.ws.market.asks_dict[pair].top()
|
|
@@ -305,7 +307,7 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
305
307
|
if (options.depth === false) {
|
|
306
308
|
this.ws.market.asks_dict[pair] = [[best_ask, best_ask_qty]]
|
|
307
309
|
this.ws.market.bids_dict[pair] = [[best_bid, best_bid_qty]]
|
|
308
|
-
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: parse_event_ts_ms(data.time) || Date.now() }
|
|
310
|
+
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: parse_event_ts_ms(data.time) || Date.now(), t_recv, t_parsed }
|
|
309
311
|
this.ws.market.pair_status[pair] = 'opened'
|
|
310
312
|
} else {
|
|
311
313
|
const bids = this.ws.market.bids_dict[pair]
|
|
@@ -322,7 +324,7 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
322
324
|
for (const k of prune_bids) bids.del(k)
|
|
323
325
|
const prune_asks = asks.keys().filter((k) => k > hi)
|
|
324
326
|
for (const k of prune_asks) asks.del(k)
|
|
325
|
-
if (bbo_changed) this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: parse_event_ts_ms(data.time) || Date.now() }
|
|
327
|
+
if (bbo_changed) this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: parse_event_ts_ms(data.time) || Date.now(), t_recv, t_parsed }
|
|
326
328
|
}
|
|
327
329
|
if (bbo_changed) this.ws.bbo_observable.notify()
|
|
328
330
|
} else if (data.type === 'match' || data.type === 'last_match') {
|
|
@@ -405,13 +407,14 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
405
407
|
this.ws.market.send(JSON.stringify({ type: 'subscribe', product_ids, channel: 'market_trades' }))
|
|
406
408
|
}
|
|
407
409
|
})
|
|
408
|
-
this.ws.market.on_message((data) => {
|
|
410
|
+
this.ws.market.on_message((data, t_recv) => {
|
|
409
411
|
try {
|
|
410
412
|
data = JSON.parse(data)
|
|
411
413
|
} catch (err) {
|
|
412
414
|
console.error('coinbase', 'websocket', 'JSON parse error:', err.message)
|
|
413
415
|
return
|
|
414
416
|
}
|
|
417
|
+
const t_parsed = process.hrtime()
|
|
415
418
|
const seq = data.sequence_num
|
|
416
419
|
if (seq !== undefined) {
|
|
417
420
|
const gap = (this._ws_last_seq !== undefined && seq > this._ws_last_seq + 1) ? seq - this._ws_last_seq - 1 : 0
|
|
@@ -485,6 +488,7 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
485
488
|
this.ws.market.ts_dict[pair] = {
|
|
486
489
|
received_ts: Date.now(),
|
|
487
490
|
event_ts: parse_event_ts_ms(data.timestamp) || Date.now(),
|
|
491
|
+
t_recv, t_parsed,
|
|
488
492
|
}
|
|
489
493
|
{
|
|
490
494
|
const top_bid = this.ws.market.bids_dict[pair].top()
|
|
@@ -531,7 +535,7 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
531
535
|
if (options.depth === false) {
|
|
532
536
|
this.ws.market.asks_dict[pair] = [[best_ask, best_ask_qty]]
|
|
533
537
|
this.ws.market.bids_dict[pair] = [[best_bid, best_bid_qty]]
|
|
534
|
-
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: parse_event_ts_ms(data.timestamp) || Date.now() }
|
|
538
|
+
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: parse_event_ts_ms(data.timestamp) || Date.now(), t_recv, t_parsed }
|
|
535
539
|
this.ws.market.pair_status[pair] = 'opened'
|
|
536
540
|
} else {
|
|
537
541
|
const bids = this.ws.market.bids_dict[pair]
|
|
@@ -549,7 +553,7 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
549
553
|
for (const k of prune_bids) bids.del(k)
|
|
550
554
|
const prune_asks = asks.keys().filter((k) => k > hi)
|
|
551
555
|
for (const k of prune_asks) asks.del(k)
|
|
552
|
-
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: parse_event_ts_ms(data.timestamp) || Date.now() }
|
|
556
|
+
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), event_ts: parse_event_ts_ms(data.timestamp) || Date.now(), t_recv, t_parsed }
|
|
553
557
|
}
|
|
554
558
|
this.ws.bbo_observable.notify()
|
|
555
559
|
}
|
|
@@ -1572,6 +1576,107 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
1572
1576
|
cb({ success: true, body: [] })
|
|
1573
1577
|
}
|
|
1574
1578
|
}
|
|
1579
|
+
get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
|
|
1580
|
+
const get_history_trades_base = async (pair, start_time_in_ms, end_time_in_ms, cb) => {
|
|
1581
|
+
try {
|
|
1582
|
+
const results = []
|
|
1583
|
+
if (this.advanced) {
|
|
1584
|
+
let cursor = null
|
|
1585
|
+
while (true) {
|
|
1586
|
+
let path = '/api/v3/brokerage/orders/historical/fills'
|
|
1587
|
+
let params = { product_id: pre_process_pair(pair) }
|
|
1588
|
+
if (cursor) params.cursor = cursor
|
|
1589
|
+
let options = this._get_auth('GET', path, params)
|
|
1590
|
+
let url = this._base_url() + path + '?' + qs.stringify(params)
|
|
1591
|
+
const response = await new Promise((resolve, reject) => {
|
|
1592
|
+
needle.get(url, options, (err, res, body) => {
|
|
1593
|
+
if (body && Array.isArray(body.fills)) {
|
|
1594
|
+
resolve(body)
|
|
1595
|
+
} else if (body) {
|
|
1596
|
+
reject({ error: error_codes.other, body })
|
|
1597
|
+
} else {
|
|
1598
|
+
reject({ error: error_codes.timeout })
|
|
1599
|
+
}
|
|
1600
|
+
})
|
|
1601
|
+
})
|
|
1602
|
+
if (response.fills.length === 0) break
|
|
1603
|
+
let oldest_ms = Infinity
|
|
1604
|
+
for (const fill of response.fills) {
|
|
1605
|
+
const close_time_ms = history_time_ms(fill.trade_time)
|
|
1606
|
+
if (close_time_ms < oldest_ms) oldest_ms = close_time_ms
|
|
1607
|
+
if (close_time_ms < start_time_in_ms || close_time_ms >= end_time_in_ms) continue
|
|
1608
|
+
let quote_cur = (fill.product_id || '').split('-')[1] || ''
|
|
1609
|
+
let trade = {
|
|
1610
|
+
order_id: fill.order_id != null ? fill.order_id.toString() : '',
|
|
1611
|
+
pair,
|
|
1612
|
+
type: String(fill.side || '').toLowerCase(),
|
|
1613
|
+
price: parseFloat(fill.price),
|
|
1614
|
+
amount: parseFloat(fill.size),
|
|
1615
|
+
total: parseFloat(fill.price) * parseFloat(fill.size),
|
|
1616
|
+
close_time: new Date(close_time_ms),
|
|
1617
|
+
fees: quote_cur && fill.commission != null ? { [post_process_cur(quote_cur)]: parseFloat(fill.commission) } : {},
|
|
1618
|
+
role: fill.liquidity_indicator === 'MAKER' ? 'maker' : 'taker',
|
|
1619
|
+
}
|
|
1620
|
+
if (fill.trade_id != null) trade.trade_id = fill.trade_id.toString()
|
|
1621
|
+
else if (fill.entry_id != null) trade.trade_id = fill.entry_id.toString()
|
|
1622
|
+
results.push(trade)
|
|
1623
|
+
}
|
|
1624
|
+
if (!response.has_next || !response.cursor || oldest_ms < start_time_in_ms) break
|
|
1625
|
+
cursor = response.cursor
|
|
1626
|
+
await sleep(50)
|
|
1627
|
+
}
|
|
1628
|
+
} else {
|
|
1629
|
+
let after = null
|
|
1630
|
+
while (true) {
|
|
1631
|
+
let path = '/fills'
|
|
1632
|
+
let params = { product_id: pre_process_pair(pair) }
|
|
1633
|
+
if (after) params.after = after
|
|
1634
|
+
let options = this._get_auth('GET', path, params)
|
|
1635
|
+
let url = this._base_url() + path + '?' + qs.stringify(params)
|
|
1636
|
+
const response = await new Promise((resolve, reject) => {
|
|
1637
|
+
needle.get(url, options, (err, res, body) => {
|
|
1638
|
+
if (body && Array.isArray(body)) {
|
|
1639
|
+
resolve({ res, body })
|
|
1640
|
+
} else if (body) {
|
|
1641
|
+
reject({ error: error_codes.other, body })
|
|
1642
|
+
} else {
|
|
1643
|
+
reject({ error: error_codes.timeout })
|
|
1644
|
+
}
|
|
1645
|
+
})
|
|
1646
|
+
})
|
|
1647
|
+
if (response.body.length === 0) break
|
|
1648
|
+
let oldest_ms = Infinity
|
|
1649
|
+
for (const fill of response.body) {
|
|
1650
|
+
const close_time_ms = history_time_ms(fill.created_at)
|
|
1651
|
+
if (close_time_ms < oldest_ms) oldest_ms = close_time_ms
|
|
1652
|
+
if (close_time_ms < start_time_in_ms || close_time_ms >= end_time_in_ms) continue
|
|
1653
|
+
let trade = {
|
|
1654
|
+
order_id: fill.order_id != null ? fill.order_id.toString() : '',
|
|
1655
|
+
pair,
|
|
1656
|
+
type: String(fill.side || '').toLowerCase(),
|
|
1657
|
+
price: parseFloat(fill.price),
|
|
1658
|
+
amount: parseFloat(fill.size),
|
|
1659
|
+
total: parseFloat(fill.price) * parseFloat(fill.size),
|
|
1660
|
+
close_time: new Date(close_time_ms),
|
|
1661
|
+
fees: fill.funding_currency && fill.fee != null ? { [post_process_cur(fill.funding_currency)]: parseFloat(fill.fee) } : {},
|
|
1662
|
+
role: fill.liquidity === 'M' ? 'maker' : 'taker',
|
|
1663
|
+
}
|
|
1664
|
+
if (fill.trade_id != null) trade.trade_id = fill.trade_id.toString()
|
|
1665
|
+
results.push(trade)
|
|
1666
|
+
}
|
|
1667
|
+
const next_after = response.res?.headers?.['cb-after']
|
|
1668
|
+
if (!next_after || oldest_ms < start_time_in_ms) break
|
|
1669
|
+
after = next_after
|
|
1670
|
+
await sleep(50)
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
cb({ success: true, body: sort_history_rows(filter_history_in_range(results, start_time_in_ms, end_time_in_ms)) })
|
|
1674
|
+
} catch (error) {
|
|
1675
|
+
cb({ success: false, error: error?.error || error_codes.other, body: error?.body })
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
this.private_limiter.process(get_history_trades_base, pair, start_time_in_ms, end_time_in_ms, cb)
|
|
1679
|
+
}
|
|
1575
1680
|
_convert(from_cur, to_cur, amount, cb) {
|
|
1576
1681
|
if (this.advanced) {
|
|
1577
1682
|
cb({ success: false, error: error_codes.other, body: { message: 'Convert not supported on Advanced API' } })
|
|
@@ -135,14 +135,15 @@ module.exports = class Coinstore extends ExchangeBase {
|
|
|
135
135
|
false,
|
|
136
136
|
)
|
|
137
137
|
})
|
|
138
|
-
this.ws.market.on_message((data) => {
|
|
138
|
+
this.ws.market.on_message((data, t_recv) => {
|
|
139
139
|
data = JSON.parse(data)
|
|
140
|
+
const t_parsed = process.hrtime()
|
|
140
141
|
if (data && data.T === 'depth') {
|
|
141
142
|
let pair = post_process_pair(data.symbol)
|
|
142
143
|
this.ws.market.pair_status[pair] = 'opened'
|
|
143
144
|
this.ws.market.asks_dict[pair] = data.a.map((ask) => [parseFloat(ask[0]), parseFloat(ask[1])])
|
|
144
145
|
this.ws.market.bids_dict[pair] = data.b.map((bid) => [parseFloat(bid[0]), parseFloat(bid[1])])
|
|
145
|
-
this.ws.market.ts_dict[pair] = { received_ts: Date.now() }
|
|
146
|
+
this.ws.market.ts_dict[pair] = { received_ts: Date.now(), t_recv, t_parsed }
|
|
146
147
|
const asks = this.ws.market.asks_dict[pair]
|
|
147
148
|
const bids = this.ws.market.bids_dict[pair]
|
|
148
149
|
if (asks && asks.length > 0 && bids && bids.length > 0) {
|
|
@@ -476,6 +477,9 @@ module.exports = class Coinstore extends ExchangeBase {
|
|
|
476
477
|
}
|
|
477
478
|
this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
|
|
478
479
|
}
|
|
480
|
+
get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
|
|
481
|
+
this._get_history_trades_via_timeframe(pair, start_time_in_ms, end_time_in_ms, cb)
|
|
482
|
+
}
|
|
479
483
|
static get_precision(cb) {
|
|
480
484
|
const price_precision = {}
|
|
481
485
|
const amount_precision = {}
|