@icgio/icg-exchanges 1.40.49 → 1.41.0
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/lib/exchanges/ascendex.js +1 -3
- package/lib/exchanges/biconomy.js +2 -2
- package/lib/exchanges/binance.js +1 -0
- package/lib/exchanges/bingx.js +2 -1
- package/lib/exchanges/bitget.js +1 -0
- package/lib/exchanges/bithumb.js +1 -3
- package/lib/exchanges/bitkub.js +1 -3
- package/lib/exchanges/bitmart.js +49 -32
- package/lib/exchanges/bitmex.js +1 -0
- package/lib/exchanges/bitrue.js +2 -1
- package/lib/exchanges/bitstamp.js +1 -0
- package/lib/exchanges/blofin.js +1 -0
- package/lib/exchanges/btse.js +1 -3
- package/lib/exchanges/bybit.js +48 -35
- package/lib/exchanges/coinbase.js +359 -71
- package/lib/exchanges/coinstore.js +1 -3
- package/lib/exchanges/coinw.js +48 -32
- package/lib/exchanges/cryptocom.js +1 -0
- package/lib/exchanges/deepcoin.js +1 -0
- package/lib/exchanges/digifinex.js +1 -0
- package/lib/exchanges/exchange-base.js +12 -46
- package/lib/exchanges/exchange-fix.js +191 -86
- package/lib/exchanges/fastex.js +1 -3
- package/lib/exchanges/gate.js +49 -34
- package/lib/exchanges/gemini.js +1 -0
- package/lib/exchanges/hashkey.js +1 -0
- package/lib/exchanges/hashkeyglobal.js +1 -0
- package/lib/exchanges/hitbtc.js +44 -27
- package/lib/exchanges/hkbitex.js +1 -3
- package/lib/exchanges/htx.js +4 -3
- package/lib/exchanges/indodax.js +1 -3
- package/lib/exchanges/kraken.js +1 -0
- package/lib/exchanges/kucoin.js +2 -1
- package/lib/exchanges/lbank.js +2 -1
- package/lib/exchanges/mexc.js +2 -1
- package/lib/exchanges/okx.js +51 -34
- package/lib/exchanges/phemex.js +47 -31
- package/lib/exchanges/poloniex.js +43 -27
- package/lib/exchanges/swft.js +1 -3
- package/lib/exchanges/upbit.js +1 -0
- package/lib/exchanges/weex.js +1 -3
- package/lib/exchanges/xt.js +2 -1
- package/package.json +1 -1
- package/lib/exchanges/coinbase-fix.js +0 -267
|
@@ -440,6 +440,7 @@ module.exports = class Digifinex extends ExchangeBase {
|
|
|
440
440
|
delay_ms: 250,
|
|
441
441
|
})
|
|
442
442
|
trades = sort_history_rows(filter_history_in_range(trades, start_time_in_ms, end_time_in_ms))
|
|
443
|
+
trades = utils.merge_trades_with_same_id(trades)
|
|
443
444
|
cb({ success: true, body: trades })
|
|
444
445
|
})().catch((error) => cb(error))
|
|
445
446
|
}
|
|
@@ -450,9 +450,14 @@ class ExchangeBase {
|
|
|
450
450
|
// ============================================
|
|
451
451
|
|
|
452
452
|
/**
|
|
453
|
-
* Get trade history for a trading pair
|
|
453
|
+
* Get trade history for a trading pair within a relative time window.
|
|
454
|
+
*
|
|
455
|
+
* Implementations should call utils.merge_trades_with_same_id() before returning,
|
|
456
|
+
* which groups fills by order_id, aggregates amount/total/fees, recalculates price,
|
|
457
|
+
* and populates fill_history[] on every returned trade (even single-fill trades).
|
|
458
|
+
*
|
|
454
459
|
* @param {string} pair - Trading pair
|
|
455
|
-
* @param {number} timeframe_in_ms - Time range in milliseconds
|
|
460
|
+
* @param {number} timeframe_in_ms - Time range in milliseconds (now - timeframe_in_ms .. now)
|
|
456
461
|
* @param {function(ApiResponse<TradeResult[]>): void} cb - Callback with trades
|
|
457
462
|
*/
|
|
458
463
|
get_trades(pair, timeframe_in_ms, cb) {
|
|
@@ -461,7 +466,11 @@ class ExchangeBase {
|
|
|
461
466
|
|
|
462
467
|
/**
|
|
463
468
|
* Get trade history for a trading pair in an absolute time window.
|
|
464
|
-
*
|
|
469
|
+
*
|
|
470
|
+
* Implementations should call utils.merge_trades_with_same_id() before
|
|
471
|
+
* returning, which groups fills by order_id, aggregates amount/total/fees,
|
|
472
|
+
* recalculates price, and populates fill_history[] on every returned trade.
|
|
473
|
+
*
|
|
465
474
|
* @param {string} pair - Trading pair
|
|
466
475
|
* @param {number} start_time_in_ms - Inclusive start timestamp in milliseconds
|
|
467
476
|
* @param {number} end_time_in_ms - Exclusive end timestamp in milliseconds
|
|
@@ -471,49 +480,6 @@ class ExchangeBase {
|
|
|
471
480
|
cb({ success: false, error: `${this.name}: get_history_trades not implemented` })
|
|
472
481
|
}
|
|
473
482
|
|
|
474
|
-
/**
|
|
475
|
-
* Best-effort absolute-window history built from the exchange's existing recent trade endpoint.
|
|
476
|
-
* @param {string} pair
|
|
477
|
-
* @param {number} start_time_in_ms
|
|
478
|
-
* @param {number} end_time_in_ms
|
|
479
|
-
* @param {function(ApiResponse<TradeResult[]>): void} cb
|
|
480
|
-
* @param {{use_all_trades?: boolean}} options
|
|
481
|
-
*/
|
|
482
|
-
_get_history_trades_via_timeframe(pair, start_time_in_ms, end_time_in_ms, cb, options = {}) {
|
|
483
|
-
if (!(start_time_in_ms < end_time_in_ms)) {
|
|
484
|
-
cb({ success: true, body: [] })
|
|
485
|
-
return
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
const use_all_trades = options.use_all_trades === true
|
|
489
|
-
const timeframe_in_ms = Math.max(end_time_in_ms - start_time_in_ms, Date.now() - start_time_in_ms, 0)
|
|
490
|
-
const handle = (res) => {
|
|
491
|
-
if (!res || !res.success) {
|
|
492
|
-
cb(res)
|
|
493
|
-
return
|
|
494
|
-
}
|
|
495
|
-
let trades = Array.isArray(res.body) ? res.body : []
|
|
496
|
-
if (pair) {
|
|
497
|
-
trades = trades.filter((trade) => !trade?.pair || trade.pair === pair)
|
|
498
|
-
}
|
|
499
|
-
cb({
|
|
500
|
-
success: true,
|
|
501
|
-
body: ExchangeBase.history_utils.sort_history_rows(ExchangeBase.history_utils.filter_history_in_range(trades, start_time_in_ms, end_time_in_ms)),
|
|
502
|
-
})
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
if (use_all_trades) {
|
|
506
|
-
if (typeof this.get_all_trades !== 'function') {
|
|
507
|
-
cb({ success: false, error: `${this.name}: get_all_trades not implemented` })
|
|
508
|
-
return
|
|
509
|
-
}
|
|
510
|
-
this.get_all_trades(timeframe_in_ms, handle)
|
|
511
|
-
return
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
this.get_trades(pair, timeframe_in_ms, handle)
|
|
515
|
-
}
|
|
516
|
-
|
|
517
483
|
// ============================================
|
|
518
484
|
// Deposit/Withdrawal Methods
|
|
519
485
|
// ============================================
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// Generic FIX 5.0
|
|
1
|
+
// Generic FIX 5.0 transport manager over TLS.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// the caller
|
|
3
|
+
// Exposes `market` and `trading` channels that each own their own FIX session
|
|
4
|
+
// lifecycle: connect, heartbeat, routine reconnect, disconnect, and restart.
|
|
5
|
+
// Exchange-specific message handling lives in the caller.
|
|
6
6
|
|
|
7
7
|
const tls = require('tls')
|
|
8
8
|
|
|
@@ -67,126 +67,231 @@ function parse_fix_messages(recv_buffer) {
|
|
|
67
67
|
// --- ExchangeFix ---
|
|
68
68
|
|
|
69
69
|
module.exports = class ExchangeFix {
|
|
70
|
-
constructor(name, options) {
|
|
70
|
+
constructor(name, options = {}) {
|
|
71
71
|
this.name = name
|
|
72
|
-
this.
|
|
73
|
-
this.
|
|
74
|
-
this.sender_comp_id = options.sender_comp_id
|
|
75
|
-
this.target_comp_id = options.target_comp_id
|
|
76
|
-
this.heartbeat_interval = (options.heartbeat_interval || 30) * 1000
|
|
77
|
-
this.build_logon_fields = options.build_logon_fields
|
|
78
|
-
this.on_execution_report = options.on_execution_report
|
|
79
|
-
this.on_cancel_reject = options.on_cancel_reject
|
|
80
|
-
this.on_reject = options.on_reject
|
|
81
|
-
this.on_market_data_incremental = options.on_market_data_incremental
|
|
82
|
-
this.on_market_data_batch = options.on_market_data_batch // called once per data chunk with array of fields
|
|
83
|
-
this.on_market_data_snapshot = options.on_market_data_snapshot
|
|
84
|
-
this.on_market_data_request_reject = options.on_market_data_request_reject
|
|
85
|
-
this.on_status_change = options.on_status_change || (() => {})
|
|
86
|
-
|
|
87
|
-
this.socket = null
|
|
88
|
-
this.status = 'disconnected'
|
|
89
|
-
this.seq_out = 1
|
|
90
|
-
this.recv_buffer = Buffer.alloc(0)
|
|
91
|
-
this.heartbeat_timer = null
|
|
92
|
-
this.reconnect_timer = null
|
|
72
|
+
this.market = this._create_channel('market', options.market)
|
|
73
|
+
this.trading = this._create_channel('trading', options.trading)
|
|
93
74
|
}
|
|
94
75
|
|
|
95
|
-
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
this.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
76
|
+
_create_channel(kind, options) {
|
|
77
|
+
if (!options) return null
|
|
78
|
+
const channel = {
|
|
79
|
+
kind,
|
|
80
|
+
name: `${this.name}_${kind}`,
|
|
81
|
+
host: options.host,
|
|
82
|
+
port: options.port,
|
|
83
|
+
sender_comp_id: options.sender_comp_id,
|
|
84
|
+
target_comp_id: options.target_comp_id,
|
|
85
|
+
heartbeat_interval: (options.heartbeat_interval || 30) * 1000,
|
|
86
|
+
reconnecting_time: options.reconnecting_time || 2 * 60 * 60 * 1000,
|
|
87
|
+
restart_after_close_time: options.restart_after_close_time || 3 * 1000,
|
|
88
|
+
build_logon_fields: options.build_logon_fields,
|
|
89
|
+
on_execution_report: options.on_execution_report,
|
|
90
|
+
on_cancel_reject: options.on_cancel_reject,
|
|
91
|
+
on_reject: options.on_reject,
|
|
92
|
+
on_market_data_incremental: options.on_market_data_incremental,
|
|
93
|
+
on_market_data_batch: options.on_market_data_batch,
|
|
94
|
+
on_market_data_snapshot: options.on_market_data_snapshot,
|
|
95
|
+
on_market_data_request_reject: options.on_market_data_request_reject,
|
|
96
|
+
on_status_change: options.on_status_change || (() => {}),
|
|
97
|
+
status: 'n-opened',
|
|
98
|
+
session_status: 'disconnected',
|
|
99
|
+
socket: null,
|
|
100
|
+
seq_out: 1,
|
|
101
|
+
recv_buffer: Buffer.alloc(0),
|
|
102
|
+
heartbeat_timer: null,
|
|
103
|
+
restart_timeout: null,
|
|
104
|
+
reconnecting_timeout: null,
|
|
105
|
+
start: () => this._start_channel(channel),
|
|
106
|
+
end: () => this._end_channel(channel),
|
|
107
|
+
close: () => this._close_channel(channel),
|
|
108
|
+
send: (msg_type, fields, sending_time) => this._send(channel, msg_type, fields, sending_time),
|
|
109
|
+
is_ready: () => channel.status === 'opened' && channel.session_status === 'ready',
|
|
110
|
+
}
|
|
111
|
+
return channel
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_start_channel(channel) {
|
|
115
|
+
if (!channel) return
|
|
116
|
+
if (channel.status !== 'n-opened' && channel.status !== 'closed' && channel.status !== 'stopped') return
|
|
117
|
+
|
|
118
|
+
clearTimeout(channel.restart_timeout)
|
|
119
|
+
channel.restart_timeout = null
|
|
120
|
+
clearTimeout(channel.reconnecting_timeout)
|
|
121
|
+
channel.reconnecting_timeout = null
|
|
122
|
+
this._clear_heartbeat(channel)
|
|
123
|
+
|
|
124
|
+
channel.status = 'opening'
|
|
125
|
+
this._set_session_status(channel, 'connecting')
|
|
126
|
+
|
|
127
|
+
const socket = tls.connect({ host: channel.host, port: channel.port }, () => {
|
|
128
|
+
if (socket !== channel.socket) return
|
|
129
|
+
this._set_session_status(channel, 'logging_in')
|
|
130
|
+
const sending_time = fix_timestamp()
|
|
131
|
+
const logon_fields = channel.build_logon_fields(channel.seq_out, sending_time)
|
|
132
|
+
this._send(channel, 'A', logon_fields, sending_time)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
channel.socket = socket
|
|
136
|
+
socket.on('data', (chunk) => {
|
|
137
|
+
if (socket !== channel.socket) return
|
|
138
|
+
this._on_data(channel, chunk)
|
|
103
139
|
})
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
console.log('[FIX %s] error: %s',
|
|
107
|
-
this.
|
|
140
|
+
socket.on('error', (err) => {
|
|
141
|
+
if (socket !== channel.socket) return
|
|
142
|
+
console.log('[FIX %s] error: %s', channel.name, err.message)
|
|
143
|
+
this._handle_disconnect(channel, { restart: channel.status !== 'stopped' })
|
|
108
144
|
})
|
|
109
|
-
|
|
110
|
-
if (
|
|
145
|
+
socket.on('close', () => {
|
|
146
|
+
if (socket !== channel.socket) return
|
|
147
|
+
this._handle_disconnect(channel, { restart: channel.status !== 'stopped' })
|
|
111
148
|
})
|
|
112
149
|
}
|
|
113
150
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
151
|
+
_end_channel(channel) {
|
|
152
|
+
if (!channel) return
|
|
153
|
+
if (channel.status !== 'opened' && channel.status !== 'opening' && !channel.socket) return
|
|
154
|
+
this._handle_disconnect(channel, { restart: true, graceful: true, next_status: 'closed' })
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
_close_channel(channel) {
|
|
158
|
+
if (!channel) return
|
|
159
|
+
clearTimeout(channel.restart_timeout)
|
|
160
|
+
channel.restart_timeout = null
|
|
161
|
+
clearTimeout(channel.reconnecting_timeout)
|
|
162
|
+
channel.reconnecting_timeout = null
|
|
163
|
+
|
|
164
|
+
if (channel.socket || channel.session_status !== 'disconnected') {
|
|
165
|
+
this._handle_disconnect(channel, { restart: false, graceful: true, next_status: 'stopped' })
|
|
166
|
+
} else {
|
|
167
|
+
channel.status = 'stopped'
|
|
168
|
+
channel.session_status = 'disconnected'
|
|
169
|
+
this._reset_session_state(channel)
|
|
121
170
|
}
|
|
122
|
-
this.on_status_change('disconnected')
|
|
123
171
|
}
|
|
124
172
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
173
|
+
_send(channel, msg_type, fields, sending_time) {
|
|
174
|
+
return this._send_on_socket(channel, channel.socket, msg_type, fields, sending_time)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
_send_on_socket(channel, socket, msg_type, fields, sending_time) {
|
|
178
|
+
if (!socket || socket.destroyed) return false
|
|
179
|
+
socket.write(encode_fix_msg(msg_type, fields, channel.seq_out, channel.sender_comp_id, channel.target_comp_id, sending_time))
|
|
180
|
+
channel.seq_out++
|
|
129
181
|
return true
|
|
130
182
|
}
|
|
131
183
|
|
|
132
|
-
|
|
184
|
+
_set_session_status(channel, status) {
|
|
185
|
+
if (channel.session_status === status) return
|
|
186
|
+
channel.session_status = status
|
|
187
|
+
channel.on_status_change(status)
|
|
188
|
+
}
|
|
133
189
|
|
|
134
|
-
|
|
190
|
+
_mark_ready(channel) {
|
|
191
|
+
channel.status = 'opened'
|
|
192
|
+
this._start_heartbeat(channel)
|
|
193
|
+
clearTimeout(channel.reconnecting_timeout)
|
|
194
|
+
channel.reconnecting_timeout = setTimeout(() => {
|
|
195
|
+
channel.reconnecting_timeout = null
|
|
196
|
+
console.log('[FIX %s] routine reconnecting', channel.name)
|
|
197
|
+
channel.end()
|
|
198
|
+
}, channel.reconnecting_time)
|
|
199
|
+
this._set_session_status(channel, 'ready')
|
|
200
|
+
}
|
|
135
201
|
|
|
136
|
-
_on_data(chunk) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
202
|
+
_on_data(channel, chunk) {
|
|
203
|
+
channel.recv_buffer = Buffer.concat([channel.recv_buffer, chunk])
|
|
204
|
+
const { messages, remaining } = parse_fix_messages(channel.recv_buffer)
|
|
205
|
+
channel.recv_buffer = remaining
|
|
140
206
|
let md_batch = null
|
|
141
|
-
|
|
142
|
-
|
|
207
|
+
|
|
208
|
+
for (const msg of messages) {
|
|
209
|
+
const t = msg.msg_type
|
|
143
210
|
if (t === 'A') {
|
|
144
|
-
this.
|
|
145
|
-
this._start_heartbeat()
|
|
211
|
+
this._mark_ready(channel)
|
|
146
212
|
} else if (t === '0') {
|
|
147
213
|
// Heartbeat
|
|
148
214
|
} else if (t === '1') {
|
|
149
|
-
|
|
215
|
+
channel.send('0', [[112, msg.fields['112'] || '']])
|
|
150
216
|
} else if (t === '5') {
|
|
151
|
-
this.
|
|
217
|
+
this._handle_disconnect(channel, { restart: channel.status !== 'stopped' })
|
|
152
218
|
} else if (t === '8') {
|
|
153
|
-
if (
|
|
219
|
+
if (channel.on_execution_report) channel.on_execution_report(msg.fields)
|
|
154
220
|
} else if (t === '9') {
|
|
155
|
-
if (
|
|
221
|
+
if (channel.on_cancel_reject) channel.on_cancel_reject(msg.fields)
|
|
156
222
|
} else if (t === 'W') {
|
|
157
|
-
if (
|
|
223
|
+
if (channel.on_market_data_snapshot) channel.on_market_data_snapshot(msg.fields)
|
|
158
224
|
} else if (t === 'X') {
|
|
159
|
-
if (
|
|
225
|
+
if (channel.on_market_data_batch) {
|
|
160
226
|
if (!md_batch) md_batch = []
|
|
161
227
|
md_batch.push(msg.fields)
|
|
162
|
-
} else if (
|
|
163
|
-
|
|
228
|
+
} else if (channel.on_market_data_incremental) {
|
|
229
|
+
channel.on_market_data_incremental(msg.fields)
|
|
164
230
|
}
|
|
165
231
|
} else if (t === 'Y') {
|
|
166
|
-
if (
|
|
232
|
+
if (channel.on_market_data_request_reject) channel.on_market_data_request_reject(msg.fields)
|
|
167
233
|
} else if (t === '3' || t === 'j') {
|
|
168
|
-
if (
|
|
234
|
+
if (channel.on_reject) channel.on_reject(msg.fields)
|
|
169
235
|
}
|
|
170
236
|
}
|
|
171
|
-
|
|
237
|
+
|
|
238
|
+
if (md_batch && channel.on_market_data_batch) channel.on_market_data_batch(md_batch)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
_start_heartbeat(channel) {
|
|
242
|
+
this._clear_heartbeat(channel)
|
|
243
|
+
channel.heartbeat_timer = setInterval(() => channel.send('0', []), channel.heartbeat_interval)
|
|
172
244
|
}
|
|
173
245
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
246
|
+
_clear_heartbeat(channel) {
|
|
247
|
+
if (channel.heartbeat_timer) {
|
|
248
|
+
clearInterval(channel.heartbeat_timer)
|
|
249
|
+
channel.heartbeat_timer = null
|
|
250
|
+
}
|
|
177
251
|
}
|
|
178
252
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
253
|
+
_reset_session_state(channel) {
|
|
254
|
+
channel.recv_buffer = Buffer.alloc(0)
|
|
255
|
+
channel.seq_out = 1
|
|
182
256
|
}
|
|
183
257
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
258
|
+
_handle_disconnect(channel, options = {}) {
|
|
259
|
+
const restart = options.restart === true
|
|
260
|
+
const next_status = options.next_status || (restart ? 'closed' : 'stopped')
|
|
261
|
+
const socket = channel.socket
|
|
262
|
+
const should_notify = !!socket || channel.session_status !== 'disconnected'
|
|
263
|
+
|
|
264
|
+
if (!socket && !should_notify && channel.status === next_status) return
|
|
265
|
+
|
|
266
|
+
channel.socket = null
|
|
267
|
+
|
|
268
|
+
if (socket && options.graceful) {
|
|
269
|
+
try { this._send_on_socket(channel, socket, '5', []) } catch (e) {}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
this._clear_heartbeat(channel)
|
|
273
|
+
clearTimeout(channel.reconnecting_timeout)
|
|
274
|
+
channel.reconnecting_timeout = null
|
|
275
|
+
clearTimeout(channel.restart_timeout)
|
|
276
|
+
channel.restart_timeout = null
|
|
277
|
+
|
|
278
|
+
this._reset_session_state(channel)
|
|
279
|
+
channel.status = next_status
|
|
280
|
+
channel.session_status = 'disconnected'
|
|
281
|
+
|
|
282
|
+
if (socket) {
|
|
283
|
+
try { socket.destroy() } catch (e) {}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (should_notify) {
|
|
287
|
+
process.nextTick(() => channel.on_status_change('disconnected'))
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (restart && next_status !== 'stopped') {
|
|
291
|
+
channel.restart_timeout = setTimeout(() => {
|
|
292
|
+
channel.restart_timeout = null
|
|
293
|
+
channel.start()
|
|
294
|
+
}, channel.restart_after_close_time)
|
|
295
|
+
}
|
|
191
296
|
}
|
|
192
297
|
}
|
package/lib/exchanges/fastex.js
CHANGED
|
@@ -316,9 +316,7 @@ module.exports = class Fastex extends ExchangeBase {
|
|
|
316
316
|
}
|
|
317
317
|
this.private_limiter.process(get_trades_base, pair, cb)
|
|
318
318
|
}
|
|
319
|
-
get_history_trades
|
|
320
|
-
this._get_history_trades_via_timeframe(pair, start_time_in_ms, end_time_in_ms, cb)
|
|
321
|
-
}
|
|
319
|
+
// get_history_trades — not implemented, no native absolute-window endpoint
|
|
322
320
|
static get_min_amount(cb) {
|
|
323
321
|
let min_quote_cur_amount = {}
|
|
324
322
|
needle.get('https://exchange.fastex.com/api/v1/pair/list?items_per_page=100&page=1', (err, res, body) => {
|
package/lib/exchanges/gate.js
CHANGED
|
@@ -10,6 +10,7 @@ const ExchangeBase = require('./exchange-base.js')
|
|
|
10
10
|
|
|
11
11
|
const { error_codes } = require('../error_codes.json')
|
|
12
12
|
const { utils, rate_limiter: Limiter } = require('@icgio/icg-utils')
|
|
13
|
+
const { fetch_split_windows, sort_history_rows } = ExchangeBase.history_utils
|
|
13
14
|
|
|
14
15
|
function pre_process_pair(pair) {
|
|
15
16
|
let [cur, quote_cur] = utils.parse_pair(pair)
|
|
@@ -663,41 +664,55 @@ module.exports = class Gate extends ExchangeBase {
|
|
|
663
664
|
}
|
|
664
665
|
get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
|
|
665
666
|
let get_history_trades_base = (pair, start_time_in_ms, end_time_in_ms, cb) => {
|
|
666
|
-
let params = {
|
|
667
|
-
currency_pair: pre_process_pair(pair),
|
|
668
|
-
from: parseInt(start_time_in_ms / 1000),
|
|
669
|
-
limit: 1000,
|
|
670
|
-
to: parseInt(end_time_in_ms / 1000),
|
|
671
|
-
}
|
|
672
667
|
let [api_key, secret_key] = this.api_secret_key
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
668
|
+
;(async () => {
|
|
669
|
+
const rows = await fetch_split_windows({
|
|
670
|
+
start_ms: start_time_in_ms,
|
|
671
|
+
stop_ms: end_time_in_ms,
|
|
672
|
+
initial_window_ms: Math.max(end_time_in_ms - start_time_in_ms, 60 * 1000),
|
|
673
|
+
page_limit: 1000,
|
|
674
|
+
fetch_window: async (window_start_ms, window_stop_ms) =>
|
|
675
|
+
await new Promise((resolve, reject) => {
|
|
676
|
+
let params = {
|
|
677
|
+
currency_pair: pre_process_pair(pair),
|
|
678
|
+
from: parseInt(window_start_ms / 1000),
|
|
679
|
+
limit: 1000,
|
|
680
|
+
to: parseInt(window_stop_ms / 1000),
|
|
681
|
+
}
|
|
682
|
+
let timestamp = parseInt(Date.now() / 1000)
|
|
683
|
+
let path = '/api/v4/spot/my_trades'
|
|
684
|
+
let sign = get_signature_gatev4('GET', path, params, secret_key, timestamp)
|
|
685
|
+
let options = get_options(api_key, timestamp, sign)
|
|
686
|
+
let url = 'https://api.gateio.ws' + path + '?' + qs.stringify(params)
|
|
687
|
+
needle.get(url, options, (err, res, body) => {
|
|
688
|
+
if (body && Array.isArray(body)) {
|
|
689
|
+
resolve(
|
|
690
|
+
body.map((trade) => ({
|
|
691
|
+
order_id: trade.order_id,
|
|
692
|
+
pair,
|
|
693
|
+
type: trade.side,
|
|
694
|
+
price: parseFloat(trade.price),
|
|
695
|
+
amount: parseFloat(trade.amount),
|
|
696
|
+
total: parseFloat(trade.price) * parseFloat(trade.amount),
|
|
697
|
+
close_time: new Date(parseInt(trade.create_time_ms)),
|
|
698
|
+
fees: {
|
|
699
|
+
[post_process_cur(trade.fee_currency)]: parseFloat(trade.fee),
|
|
700
|
+
},
|
|
701
|
+
role: trade.role,
|
|
702
|
+
})),
|
|
703
|
+
)
|
|
704
|
+
} else if (body) {
|
|
705
|
+
reject({ error: error_codes.other, body })
|
|
706
|
+
} else {
|
|
707
|
+
reject({ error: error_codes.timeout })
|
|
708
|
+
}
|
|
709
|
+
})
|
|
710
|
+
}),
|
|
711
|
+
})
|
|
712
|
+
const merged = utils.merge_trades_with_same_id(rows)
|
|
713
|
+
cb({ success: true, body: sort_history_rows(merged) })
|
|
714
|
+
})().catch((error) => {
|
|
715
|
+
cb({ success: false, error: error?.error || error_codes.other, body: error?.body })
|
|
701
716
|
})
|
|
702
717
|
}
|
|
703
718
|
this.private_limiter.process(get_history_trades_base, pair, start_time_in_ms, end_time_in_ms, cb)
|
package/lib/exchanges/gemini.js
CHANGED
|
@@ -422,6 +422,7 @@ module.exports = class Gemini extends ExchangeBase {
|
|
|
422
422
|
await sleep(250)
|
|
423
423
|
}
|
|
424
424
|
trades = sort_history_rows(filter_history_in_range(trades, start_time_in_ms, end_time_in_ms))
|
|
425
|
+
trades = utils.merge_trades_with_same_id(trades)
|
|
425
426
|
cb({ success: true, body: trades })
|
|
426
427
|
})().catch((error) => cb(error))
|
|
427
428
|
}
|
package/lib/exchanges/hashkey.js
CHANGED
|
@@ -532,6 +532,7 @@ module.exports = class Hashkey extends ExchangeBase {
|
|
|
532
532
|
delay_ms: 250,
|
|
533
533
|
})
|
|
534
534
|
trades = sort_history_rows(filter_history_in_range(trades, start_time_in_ms, end_time_in_ms))
|
|
535
|
+
trades = utils.merge_trades_with_same_id(trades)
|
|
535
536
|
cb({ success: true, body: trades })
|
|
536
537
|
})().catch((error) => cb(error))
|
|
537
538
|
}
|
|
@@ -520,6 +520,7 @@ module.exports = class Hashkeyglobal extends ExchangeBase {
|
|
|
520
520
|
delay_ms: 250,
|
|
521
521
|
})
|
|
522
522
|
trades = sort_history_rows(filter_history_in_range(trades, start_time_in_ms, end_time_in_ms))
|
|
523
|
+
trades = utils.merge_trades_with_same_id(trades)
|
|
523
524
|
cb({ success: true, body: trades })
|
|
524
525
|
})().catch((error) => cb(error))
|
|
525
526
|
}
|
package/lib/exchanges/hitbtc.js
CHANGED
|
@@ -6,6 +6,7 @@ const needle = require('needle')
|
|
|
6
6
|
const ExchangeWs = require('./exchange-ws.js')
|
|
7
7
|
|
|
8
8
|
const ExchangeBase = require('./exchange-base.js')
|
|
9
|
+
const { fetch_split_windows, sort_history_rows } = ExchangeBase.history_utils
|
|
9
10
|
|
|
10
11
|
const { error_codes } = require('../error_codes.json')
|
|
11
12
|
const { utils, rate_limiter: Limiter } = require('@icgio/icg-utils')
|
|
@@ -651,34 +652,50 @@ module.exports = class Hitbtc extends ExchangeBase {
|
|
|
651
652
|
})
|
|
652
653
|
}
|
|
653
654
|
get_history_trades(pair, start_time_in_ms, end_time_in_ms, cb) {
|
|
654
|
-
let
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
655
|
+
let get_history_trades_base = (pair, start_time_in_ms, end_time_in_ms, cb) => {
|
|
656
|
+
;(async () => {
|
|
657
|
+
const rows = await fetch_split_windows({
|
|
658
|
+
start_ms: start_time_in_ms,
|
|
659
|
+
stop_ms: end_time_in_ms,
|
|
660
|
+
initial_window_ms: Math.max(end_time_in_ms - start_time_in_ms, 60 * 1000),
|
|
661
|
+
page_limit: 1000,
|
|
662
|
+
fetch_window: async (window_start_ms, window_stop_ms) =>
|
|
663
|
+
await new Promise((resolve, reject) => {
|
|
664
|
+
let params = {
|
|
665
|
+
from: new Date(window_start_ms).toISOString(),
|
|
666
|
+
symbol: pre_process_pair(pair),
|
|
667
|
+
till: new Date(window_stop_ms).toISOString(),
|
|
668
|
+
limit: 1000,
|
|
669
|
+
}
|
|
670
|
+
let url = `https://api.hitbtc.com/api/3/spot/history/trade?` + qs.stringify(params)
|
|
671
|
+
needle.get(url, get_signature_hitbtc(this.api_secret_key), (err, res, body) => {
|
|
672
|
+
if (body && Array.isArray(body)) {
|
|
673
|
+
resolve(
|
|
674
|
+
body.map((o) => ({
|
|
675
|
+
order_id: o.client_order_id.toString(),
|
|
676
|
+
pair: post_process_pair(o.symbol),
|
|
677
|
+
type: o.side,
|
|
678
|
+
price: parseFloat(o.price),
|
|
679
|
+
amount: parseFloat(o.quantity),
|
|
680
|
+
total: parseFloat(o.price) * parseFloat(o.quantity),
|
|
681
|
+
close_time: new Date(o.timestamp),
|
|
682
|
+
})),
|
|
683
|
+
)
|
|
684
|
+
} else if (body) {
|
|
685
|
+
reject({ error: error_codes.other, body })
|
|
686
|
+
} else {
|
|
687
|
+
reject({ error: error_codes.timeout })
|
|
688
|
+
}
|
|
689
|
+
})
|
|
690
|
+
}),
|
|
674
691
|
})
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
692
|
+
const merged = utils.merge_trades_with_same_id(rows)
|
|
693
|
+
cb({ success: true, body: sort_history_rows(merged) })
|
|
694
|
+
})().catch((error) => {
|
|
695
|
+
cb({ success: false, error: error?.error || error_codes.other, body: error?.body })
|
|
696
|
+
})
|
|
697
|
+
}
|
|
698
|
+
this.private_limiter.process(get_history_trades_base, pair, start_time_in_ms, end_time_in_ms, cb)
|
|
682
699
|
}
|
|
683
700
|
// get_all_trades_ws (timeframe_in_ms, cb) {
|
|
684
701
|
//
|
package/lib/exchanges/hkbitex.js
CHANGED
|
@@ -557,9 +557,7 @@ module.exports = class Hkbitex extends ExchangeBase {
|
|
|
557
557
|
}
|
|
558
558
|
this.private_limiter.process(get_trades_base, pair, timeframe_in_ms, cb)
|
|
559
559
|
}
|
|
560
|
-
get_history_trades
|
|
561
|
-
this._get_history_trades_via_timeframe(pair, start_time_in_ms, end_time_in_ms, cb)
|
|
562
|
-
}
|
|
560
|
+
// get_history_trades — not implemented, no native absolute-window endpoint
|
|
563
561
|
static get_precision(cb) {
|
|
564
562
|
let price_precision = {},
|
|
565
563
|
amount_precision = {}
|