@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.
Files changed (44) hide show
  1. package/lib/exchanges/ascendex.js +1 -3
  2. package/lib/exchanges/biconomy.js +2 -2
  3. package/lib/exchanges/binance.js +1 -0
  4. package/lib/exchanges/bingx.js +2 -1
  5. package/lib/exchanges/bitget.js +1 -0
  6. package/lib/exchanges/bithumb.js +1 -3
  7. package/lib/exchanges/bitkub.js +1 -3
  8. package/lib/exchanges/bitmart.js +49 -32
  9. package/lib/exchanges/bitmex.js +1 -0
  10. package/lib/exchanges/bitrue.js +2 -1
  11. package/lib/exchanges/bitstamp.js +1 -0
  12. package/lib/exchanges/blofin.js +1 -0
  13. package/lib/exchanges/btse.js +1 -3
  14. package/lib/exchanges/bybit.js +48 -35
  15. package/lib/exchanges/coinbase.js +359 -71
  16. package/lib/exchanges/coinstore.js +1 -3
  17. package/lib/exchanges/coinw.js +48 -32
  18. package/lib/exchanges/cryptocom.js +1 -0
  19. package/lib/exchanges/deepcoin.js +1 -0
  20. package/lib/exchanges/digifinex.js +1 -0
  21. package/lib/exchanges/exchange-base.js +12 -46
  22. package/lib/exchanges/exchange-fix.js +191 -86
  23. package/lib/exchanges/fastex.js +1 -3
  24. package/lib/exchanges/gate.js +49 -34
  25. package/lib/exchanges/gemini.js +1 -0
  26. package/lib/exchanges/hashkey.js +1 -0
  27. package/lib/exchanges/hashkeyglobal.js +1 -0
  28. package/lib/exchanges/hitbtc.js +44 -27
  29. package/lib/exchanges/hkbitex.js +1 -3
  30. package/lib/exchanges/htx.js +4 -3
  31. package/lib/exchanges/indodax.js +1 -3
  32. package/lib/exchanges/kraken.js +1 -0
  33. package/lib/exchanges/kucoin.js +2 -1
  34. package/lib/exchanges/lbank.js +2 -1
  35. package/lib/exchanges/mexc.js +2 -1
  36. package/lib/exchanges/okx.js +51 -34
  37. package/lib/exchanges/phemex.js +47 -31
  38. package/lib/exchanges/poloniex.js +43 -27
  39. package/lib/exchanges/swft.js +1 -3
  40. package/lib/exchanges/upbit.js +1 -0
  41. package/lib/exchanges/weex.js +1 -3
  42. package/lib/exchanges/xt.js +2 -1
  43. package/package.json +1 -1
  44. package/lib/exchanges/coinbase-fix.js +0 -267
@@ -1,267 +0,0 @@
1
- // Coinbase FIX session wrappers.
2
- //
3
- // CoinbaseFixOrd — order entry (fix-ord.exchange.coinbase.com:6121)
4
- // CoinbaseFixMd — market data (fix-md.exchange.coinbase.com:6121)
5
-
6
- const { createHmac, randomUUID } = require('crypto')
7
- const ExchangeFix = require('./exchange-fix.js')
8
-
9
- const SOH = '\x01'
10
-
11
- // --- Shared helpers ---
12
-
13
- function build_logon_fields(api_key, secret_key, passphrase, seq_num, sending_time) {
14
- let prehash = sending_time + SOH + 'A' + SOH + seq_num + SOH + api_key + SOH + 'Coinbase' + SOH + passphrase
15
- let signature = createHmac('sha256', Buffer.from(secret_key, 'base64')).update(prehash).digest('base64')
16
- return [
17
- [553, api_key], [554, passphrase],
18
- [95, signature.length], [96, signature],
19
- [98, 0], [108, 30], [141, 'Y'], [1137, 9],
20
- ]
21
- }
22
-
23
- function parse_event_ts_ms(fix_time) {
24
- if (!fix_time || fix_time.length < 17) return undefined
25
- let iso = fix_time.slice(0, 4) + '-' + fix_time.slice(4, 6) + '-' + fix_time.slice(6, 8) + 'T' + fix_time.slice(9, 21) + 'Z'
26
- let ms = Date.parse(iso)
27
- return Number.isFinite(ms) ? ms : undefined
28
- }
29
-
30
- function parse_md_entry(fields) {
31
- let symbol = fields['55']
32
- let entry_type = fields['269']
33
- let action = fields['279']
34
- let md_entry_id = fields['278']
35
- let order_id = fields['37']
36
- if (!symbol || entry_type === undefined || action === undefined) return null
37
- let price = parseFloat(fields['270'])
38
- let size = parseFloat(fields['271'])
39
- if (!Number.isFinite(price)) return null
40
- let rpt_seq = fields['83'] ? parseInt(fields['83'], 10) : undefined
41
-
42
- if (entry_type === '2') {
43
- return {
44
- kind: 'trade', symbol, price, size,
45
- time: fields['60'], trade_id: fields['1003'],
46
- aggressor_side: fields['5797'], rpt_seq,
47
- }
48
- }
49
- if (entry_type !== '0' && entry_type !== '1') return null
50
- if (!md_entry_id && order_id) return null
51
-
52
- let action_str
53
- if (action === '0') action_str = 'new'
54
- else if (action === '1') action_str = 'change'
55
- else if (action === '2') action_str = 'delete'
56
- else return null
57
-
58
- return {
59
- kind: 'l2', symbol,
60
- side: entry_type === '0' ? 'bid' : 'offer',
61
- action: action_str,
62
- price, size,
63
- time: fields['60'], md_entry_id, rpt_seq,
64
- }
65
- }
66
-
67
- // --- CoinbaseFixOrd ---
68
-
69
- class CoinbaseFixOrd {
70
- constructor(api_key, secret_key, passphrase, options = {}) {
71
- this.error_codes = options.error_codes || {}
72
- this.pre_process_pair = options.pre_process_pair || ((p) => p)
73
- this.pending = new Map()
74
-
75
- this.fix = new ExchangeFix('coinbase_ord', {
76
- host: options.host || 'fix-ord.exchange.coinbase.com',
77
- port: options.port || 6121,
78
- sender_comp_id: api_key,
79
- target_comp_id: 'Coinbase',
80
- heartbeat_interval: 30,
81
- build_logon_fields: (seq, time) => {
82
- let fields = build_logon_fields(api_key, secret_key, passphrase, seq, time)
83
- fields.push([8013, 'S'])
84
- return fields
85
- },
86
- on_execution_report: (fields) => this._handle_exec_report(fields),
87
- on_cancel_reject: (fields) => this._handle_cancel_reject(fields),
88
- on_reject: (fields) => this._handle_reject(fields),
89
- on_status_change: (status) => {
90
- console.log('[coinbase FIX ord] status:', status)
91
- if (status === 'disconnected') this._flush_pending()
92
- },
93
- })
94
- }
95
-
96
- connect() { this.fix.connect() }
97
- disconnect() { this.fix.disconnect() }
98
- is_ready() { return this.fix.is_ready() }
99
-
100
- send_new_order(pair, method, price, amount, order_options, cb) {
101
- if (typeof order_options === 'function') { cb = order_options; order_options = {} }
102
- let cl_ord_id = randomUUID()
103
- let side = method === 'buy' ? '1' : '2'
104
- let tif = '1'
105
- if (order_options && order_options.ioc) tif = '3'
106
- else if (order_options && order_options.fok) tif = '4'
107
- let fields = [
108
- [11, cl_ord_id], [55, this.pre_process_pair(pair)], [54, side],
109
- [44, price.toString()], [38, amount.toString()], [40, 2], [59, tif], [21, 1],
110
- ]
111
- if (order_options && order_options.post_only) fields.push([18, 'A'])
112
- let timeout_id = setTimeout(() => this._resolve_pending(cl_ord_id, { success: false, error: this.error_codes.timeout }), 15000)
113
- this.pending.set(cl_ord_id, { cb, timeout_id, type: 'new' })
114
- this.fix.send('D', fields)
115
- }
116
-
117
- send_cancel(pair, order_id, side, cb) {
118
- let cl_ord_id = randomUUID()
119
- let fix_side = side === 'buy' ? '1' : '2'
120
- let fields = [
121
- [11, cl_ord_id], [41, cl_ord_id], [37, order_id],
122
- [55, this.pre_process_pair(pair)], [54, fix_side],
123
- ]
124
- let timeout_id = setTimeout(() => this._resolve_pending(cl_ord_id, { success: false, error: this.error_codes.timeout }), 15000)
125
- this.pending.set(cl_ord_id, { cb, timeout_id, type: 'cancel', order_id })
126
- this.fix.send('F', fields)
127
- }
128
-
129
- _handle_exec_report(fields) {
130
- let cl_ord_id = fields['11'], ord_status = fields['39'], order_id = fields['37'] || '', ec = this.error_codes
131
- if (ord_status === '0') {
132
- this._resolve_pending(cl_ord_id, { success: true, body: order_id })
133
- } else if (ord_status === '8') {
134
- let text = fields['58'] || '', error = ec.other
135
- if (text.includes('nsufficient') || text.includes('NSUFFICIENT')) error = ec.insufficient_funds
136
- else if (fields['103'] === '2' || text.includes('too small') || text.includes('TOO_SMALL')) error = ec.amount_too_small
137
- this._resolve_pending(cl_ord_id, { success: false, error, body: text })
138
- } else if (ord_status === '4') {
139
- if (this.pending.has(cl_ord_id)) this._resolve_pending(cl_ord_id, { success: true, body: order_id })
140
- else this._resolve_pending_by_order_id(order_id, { success: true, body: order_id })
141
- } else if (ord_status === '1' || ord_status === '2') {
142
- if (this.pending.has(cl_ord_id) && this.pending.get(cl_ord_id).type === 'cancel')
143
- this._resolve_pending(cl_ord_id, { success: false, error: ec.order_not_open, body: order_id })
144
- else this._resolve_pending_by_order_id(order_id, { success: false, error: ec.order_not_open, body: order_id }, 'cancel')
145
- }
146
- }
147
-
148
- _handle_cancel_reject(fields) {
149
- let error = this.error_codes.other
150
- if (fields['102'] === '1') error = this.error_codes.order_not_open
151
- this._resolve_pending(fields['11'], { success: false, error, body: fields['58'] || '' })
152
- }
153
-
154
- _handle_reject(fields) {
155
- let cl_ord_id = fields['379'] || fields['11'] || ''
156
- if (cl_ord_id && this.pending.has(cl_ord_id))
157
- this._resolve_pending(cl_ord_id, { success: false, error: this.error_codes.other, body: fields['58'] || '' })
158
- }
159
-
160
- _resolve_pending(cl_ord_id, result) {
161
- let p = this.pending.get(cl_ord_id)
162
- if (!p) return
163
- clearTimeout(p.timeout_id)
164
- this.pending.delete(cl_ord_id)
165
- p.cb(result)
166
- }
167
-
168
- _resolve_pending_by_order_id(order_id, result, type_filter) {
169
- for (let [id, p] of this.pending) {
170
- if (p.order_id === order_id && (!type_filter || p.type === type_filter)) {
171
- this._resolve_pending(id, result)
172
- return
173
- }
174
- }
175
- }
176
-
177
- _flush_pending() {
178
- for (let [id, p] of this.pending) { clearTimeout(p.timeout_id); p.cb({ success: false, error: this.error_codes.timeout }) }
179
- this.pending.clear()
180
- }
181
- }
182
-
183
- // --- CoinbaseFixMd ---
184
-
185
- class CoinbaseFixMd {
186
- constructor(options) {
187
- this.symbols = options.symbols || []
188
- this.on_event = options.on_event || (() => {})
189
- this.on_batch = options.on_batch || null // called with array of parsed entries per data chunk
190
- this.on_status = options.on_status || (() => {})
191
- this.subscribed = false
192
-
193
- this.fix = new ExchangeFix('coinbase_md', {
194
- host: options.host || 'fix-md.exchange.coinbase.com',
195
- port: options.port || 6121,
196
- sender_comp_id: options.api_key,
197
- target_comp_id: 'Coinbase',
198
- heartbeat_interval: 30,
199
- build_logon_fields: (seq, time) => build_logon_fields(options.api_key, options.secret_key, options.passphrase, seq, time),
200
- on_market_data_batch: this.on_batch ? (batch) => this._handle_md_batch(batch) : undefined,
201
- on_market_data_incremental: this.on_batch ? undefined : (fields) => this._handle_md(fields),
202
- on_market_data_snapshot: (fields) => this._handle_md(fields),
203
- on_market_data_request_reject: (fields) => {
204
- console.log('[coinbase FIX md] request reject:', fields['58'] || '')
205
- },
206
- on_status_change: (status) => {
207
- if (status === 'ready' && !this.subscribed) this._send_subscribe()
208
- if (status === 'disconnected') this.subscribed = false
209
- this.on_status(status)
210
- },
211
- })
212
- }
213
-
214
- connect() { this.fix.connect() }
215
- disconnect() { this.fix.disconnect() }
216
- is_ready() { return this.fix.is_ready() }
217
-
218
- _send_subscribe() {
219
- if (!this.symbols.length) return
220
- let fields = [
221
- [262, 'icg-md-' + Date.now()], [263, 1], [264, 0], [265, 1],
222
- [267, 3], [269, 0], [269, 1], [269, 2],
223
- [146, this.symbols.length],
224
- ]
225
- for (let s of this.symbols) fields.push([55, s])
226
- this.fix.send('V', fields)
227
- this.subscribed = true
228
- console.log('[coinbase FIX md] subscribed:', this.symbols.join(','))
229
- }
230
-
231
- _handle_md(fields) {
232
- const hr_recv = process.hrtime()
233
- let entry = parse_md_entry(fields)
234
- if (!entry) return
235
- const hr_parsed = process.hrtime()
236
- entry.ts_ms_venue_event = parse_event_ts_ms(entry.time)
237
- entry.ts_ms_local_received = Date.now()
238
- entry.hr_recv = hr_recv
239
- entry.hr_parsed = hr_parsed
240
- this.on_event(entry)
241
- }
242
-
243
- _handle_md_batch(batch) {
244
- let entries = []
245
- let now = Date.now()
246
- for (let fields of batch) {
247
- const hr_recv = process.hrtime()
248
- let entry = parse_md_entry(fields)
249
- if (!entry) continue
250
- const hr_parsed = process.hrtime()
251
- entry.ts_ms_venue_event = parse_event_ts_ms(entry.time)
252
- entry.ts_ms_local_received = now
253
- entry.hr_recv = hr_recv
254
- entry.hr_parsed = hr_parsed
255
- entries.push(entry)
256
- }
257
- if (entries.length) this.on_batch(entries)
258
- }
259
- }
260
-
261
- module.exports = {
262
- CoinbaseFixOrd,
263
- CoinbaseFixMd,
264
- build_logon_fields,
265
- parse_md_entry,
266
- parse_event_ts_ms,
267
- }