@icgio/icg-exchanges-wrapper 1.15.2 → 1.15.3
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.
|
@@ -86,11 +86,7 @@ function reset_ws_failure(failed_pairs_dict, exchange_name, pair) {
|
|
|
86
86
|
*/
|
|
87
87
|
function should_skip_websocket(failed_pairs_dict, exchange_name, pair) {
|
|
88
88
|
const failure_info = failed_pairs_dict[exchange_name]?.[pair]
|
|
89
|
-
return (
|
|
90
|
-
failure_info &&
|
|
91
|
-
failure_info.count >= WS_FAILURE_THRESHOLD &&
|
|
92
|
-
(Date.now() - failure_info.last_failed) < WS_RETRY_INTERVAL_MS
|
|
93
|
-
)
|
|
89
|
+
return failure_info && failure_info.count >= WS_FAILURE_THRESHOLD && Date.now() - failure_info.last_failed < WS_RETRY_INTERVAL_MS
|
|
94
90
|
}
|
|
95
91
|
|
|
96
92
|
/**
|
|
@@ -127,7 +123,7 @@ const get_exchanges_rates = function (Exchanges, exchange_pair_dict, cb, ws_enab
|
|
|
127
123
|
for (let Exchange_name in exchange_instances) {
|
|
128
124
|
let Exchange = exchange_instances[Exchange_name]
|
|
129
125
|
let all_pairs = exchange_all_pairs[Exchange_name]
|
|
130
|
-
|
|
126
|
+
|
|
131
127
|
if (Exchange.ws_market && ws_enabled) {
|
|
132
128
|
// Check if WebSocket is already opening/opened for this exchange
|
|
133
129
|
const ws_key = Exchange_name
|
|
@@ -173,162 +169,158 @@ const get_exchanges_rates = function (Exchanges, exchange_pair_dict, cb, ws_enab
|
|
|
173
169
|
// This ensures only ONE WebSocket per exchange, even with multiple clients/pairs
|
|
174
170
|
Exchange.ws_market(all_pairs)
|
|
175
171
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
Exchange_name,
|
|
180
|
-
WS_TIMEOUT_MS,
|
|
181
|
-
MAX_CHECKS,
|
|
182
|
-
(elapsed) => {
|
|
172
|
+
const WS_TIMEOUT_MS = 30000 // 30 seconds timeout
|
|
173
|
+
const MAX_CHECKS = 300 // 30 seconds / 100ms
|
|
174
|
+
const check_timeout = create_ws_timeout_checker(Exchange_name, WS_TIMEOUT_MS, MAX_CHECKS, (elapsed) => {
|
|
183
175
|
console.error(`WebSocket timeout for ${Exchange_name} after ${elapsed}ms`)
|
|
184
176
|
reject(new Error(`WebSocket timeout for ${Exchange_name}`))
|
|
185
|
-
}
|
|
186
|
-
)
|
|
177
|
+
})
|
|
187
178
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
179
|
+
const check_status = () => {
|
|
180
|
+
if (check_timeout()) {
|
|
181
|
+
return
|
|
182
|
+
}
|
|
192
183
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
184
|
+
// Safety check for WebSocket object
|
|
185
|
+
if (!Exchange.ws || !Exchange.ws.market) {
|
|
186
|
+
console.error(`${Exchange_name} WebSocket object missing during check`)
|
|
187
|
+
reject(new Error(`WebSocket object missing for ${Exchange_name}`))
|
|
188
|
+
return
|
|
189
|
+
}
|
|
199
190
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
// Wait for pair to become ready with retry logic
|
|
223
|
-
const pair_start_time = Date.now()
|
|
224
|
-
const check_pair_ready = () => {
|
|
225
|
-
// Check if this specific pair is ready before calling rate_ws
|
|
226
|
-
const pair_ready = (
|
|
227
|
-
Exchange.ws.market.pair_status &&
|
|
228
|
-
Exchange.ws.market.pair_status[pair] === 'opened' &&
|
|
229
|
-
Exchange.ws.market.asks_dict &&
|
|
230
|
-
Exchange.ws.market.asks_dict[pair] &&
|
|
231
|
-
Exchange.ws.market.asks_dict[pair].length > 0 &&
|
|
232
|
-
Exchange.ws.market.bids_dict &&
|
|
233
|
-
Exchange.ws.market.bids_dict[pair] &&
|
|
234
|
-
Exchange.ws.market.bids_dict[pair].length > 0
|
|
235
|
-
)
|
|
236
|
-
|
|
237
|
-
if (pair_ready) {
|
|
238
|
-
// Pair is ready, try to get rate
|
|
239
|
-
Exchange.rate_ws(pair, function (res) {
|
|
240
|
-
if (res.success) {
|
|
241
|
-
// Success - reset failure count
|
|
242
|
-
reset_ws_failure(ws_failed_pairs, Exchange_name, pair)
|
|
243
|
-
set_pair_data(exchange_pair_rates, pair_exchange_rates, Exchange_name, pair, res.body)
|
|
244
|
-
rate_resolve({ pair, failed: false })
|
|
245
|
-
} else {
|
|
246
|
-
console.error(`getting rate ws failed for ${Exchange_name} ${pair}:`, res)
|
|
247
|
-
delete_pair_data(exchange_pair_rates, pair_exchange_rates, Exchange_name, pair)
|
|
248
|
-
track_ws_failure(ws_failed_pairs, Exchange_name, pair)
|
|
249
|
-
rate_resolve({ pair, failed: true })
|
|
250
|
-
}
|
|
251
|
-
})
|
|
252
|
-
} else {
|
|
253
|
-
// Pair not ready yet - check if we've exceeded timeout
|
|
254
|
-
const elapsed = Date.now() - pair_start_time
|
|
255
|
-
if (elapsed >= PAIR_READY_TIMEOUT_MS) {
|
|
256
|
-
// Timeout reached - mark as failed, will fall back to REST
|
|
257
|
-
console.log(`[WebSocket] ${Exchange_name} ${pair}: Not ready after ${elapsed}ms (status: ${Exchange.ws.market.pair_status?.[pair] || 'unknown'})`)
|
|
258
|
-
rate_resolve({ pair, failed: true })
|
|
259
|
-
} else {
|
|
260
|
-
// Retry after a short delay
|
|
261
|
-
setTimeout(check_pair_ready, PAIR_CHECK_INTERVAL_MS)
|
|
262
|
-
}
|
|
191
|
+
// Check only the pairs we need for this call (not all pairs in connection)
|
|
192
|
+
let pairs_to_check = exchange_pair_dict[Exchange_name] || all_pairs
|
|
193
|
+
|
|
194
|
+
// Check if WebSocket connection is at least opening/opened (not closed/stopped)
|
|
195
|
+
const ws_connection_ready = Exchange.ws.market.status === 'opening' || Exchange.ws.market.status === 'opened'
|
|
196
|
+
|
|
197
|
+
if (ws_connection_ready) {
|
|
198
|
+
// Try each pair individually - wait a bit for pairs to become ready
|
|
199
|
+
// This allows us to detect individual pair failures while giving time for subscription
|
|
200
|
+
const PAIR_READY_TIMEOUT_MS = 10000 // Wait up to 10 seconds for pair to become ready
|
|
201
|
+
const PAIR_CHECK_INTERVAL_MS = 100 // Check every 100ms
|
|
202
|
+
|
|
203
|
+
let rate_promises = pairs_to_check.map((pair) => {
|
|
204
|
+
return new Promise((rate_resolve) => {
|
|
205
|
+
// Check if this pair should skip WebSocket due to persistent failures
|
|
206
|
+
if (should_skip_websocket(ws_failed_pairs, Exchange_name, pair)) {
|
|
207
|
+
const failure_info = ws_failed_pairs[Exchange_name][pair]
|
|
208
|
+
console.log(
|
|
209
|
+
`[WebSocket Skip] ${Exchange_name} ${pair}: Skipping WebSocket (${failure_info.count} consecutive failures, last failed ${Math.round((Date.now() - failure_info.last_failed) / 1000)}s ago)`,
|
|
210
|
+
)
|
|
211
|
+
rate_resolve({ pair, failed: true, skip_ws: true })
|
|
212
|
+
return
|
|
263
213
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
214
|
+
|
|
215
|
+
// Wait for pair to become ready with retry logic
|
|
216
|
+
const pair_start_time = Date.now()
|
|
217
|
+
const check_pair_ready = () => {
|
|
218
|
+
// Check if this specific pair is ready before calling rate_ws
|
|
219
|
+
const pair_ready =
|
|
220
|
+
Exchange.ws.market.pair_status &&
|
|
221
|
+
Exchange.ws.market.pair_status[pair] === 'opened' &&
|
|
222
|
+
Exchange.ws.market.asks_dict &&
|
|
223
|
+
Exchange.ws.market.asks_dict[pair] &&
|
|
224
|
+
Exchange.ws.market.asks_dict[pair].length > 0 &&
|
|
225
|
+
Exchange.ws.market.bids_dict &&
|
|
226
|
+
Exchange.ws.market.bids_dict[pair] &&
|
|
227
|
+
Exchange.ws.market.bids_dict[pair].length > 0
|
|
228
|
+
|
|
229
|
+
if (pair_ready) {
|
|
230
|
+
// Pair is ready, try to get rate
|
|
231
|
+
Exchange.rate_ws(pair, function (res) {
|
|
279
232
|
if (res.success) {
|
|
233
|
+
// Success - reset failure count
|
|
234
|
+
reset_ws_failure(ws_failed_pairs, Exchange_name, pair)
|
|
280
235
|
set_pair_data(exchange_pair_rates, pair_exchange_rates, Exchange_name, pair, res.body)
|
|
281
|
-
|
|
236
|
+
rate_resolve({ pair, failed: false })
|
|
282
237
|
} else {
|
|
283
|
-
console.error(`
|
|
238
|
+
console.error(`getting rate ws failed for ${Exchange_name} ${pair}:`, res)
|
|
284
239
|
delete_pair_data(exchange_pair_rates, pair_exchange_rates, Exchange_name, pair)
|
|
240
|
+
track_ws_failure(ws_failed_pairs, Exchange_name, pair)
|
|
241
|
+
rate_resolve({ pair, failed: true })
|
|
285
242
|
}
|
|
286
|
-
|
|
243
|
+
})
|
|
244
|
+
} else {
|
|
245
|
+
// Pair not ready yet - check if we've exceeded timeout
|
|
246
|
+
const elapsed = Date.now() - pair_start_time
|
|
247
|
+
if (elapsed >= PAIR_READY_TIMEOUT_MS) {
|
|
248
|
+
// Timeout reached - mark as failed, will fall back to REST
|
|
249
|
+
console.log(`[WebSocket] ${Exchange_name} ${pair}: Not ready after ${elapsed}ms (status: ${Exchange.ws.market.pair_status?.[pair] || 'unknown'})`)
|
|
250
|
+
rate_resolve({ pair, failed: true })
|
|
251
|
+
} else {
|
|
252
|
+
// Retry after a short delay
|
|
253
|
+
setTimeout(check_pair_ready, PAIR_CHECK_INTERVAL_MS)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Start checking
|
|
259
|
+
check_pair_ready()
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
Promise.all(rate_promises)
|
|
263
|
+
.then((results) => {
|
|
264
|
+
// Check which pairs failed and fall back to REST for those pairs only
|
|
265
|
+
const failed_pairs = results.filter((r) => r && r.failed).map((r) => r.pair)
|
|
266
|
+
if (failed_pairs.length > 0 && Exchange.rate) {
|
|
267
|
+
console.log(`[WebSocket Fallback] ${Exchange_name}: Falling back to REST for ${failed_pairs.length} pair(s): ${failed_pairs.join(', ')}`)
|
|
268
|
+
const rest_promises = failed_pairs.map((pair) => {
|
|
269
|
+
return new Promise((rest_resolve) => {
|
|
270
|
+
Exchange.rate(pair, DEFAULT_ORDERBOOK_LEVEL, function (res) {
|
|
271
|
+
if (res.success) {
|
|
272
|
+
set_pair_data(exchange_pair_rates, pair_exchange_rates, Exchange_name, pair, res.body)
|
|
273
|
+
console.log(`[REST Fallback] ${Exchange_name} ${pair}: Successfully fetched via REST`)
|
|
274
|
+
} else {
|
|
275
|
+
console.error(`[REST Fallback] getting rate failed for ${Exchange_name} ${pair}:`, res)
|
|
276
|
+
delete_pair_data(exchange_pair_rates, pair_exchange_rates, Exchange_name, pair)
|
|
277
|
+
}
|
|
278
|
+
rest_resolve()
|
|
279
|
+
})
|
|
287
280
|
})
|
|
288
281
|
})
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
282
|
+
// Wait for REST fallback to complete before resolving
|
|
283
|
+
return Promise.all(rest_promises)
|
|
284
|
+
.catch((err) => {
|
|
285
|
+
console.error(`[REST Fallback] Error fetching rates for ${Exchange_name}:`, err)
|
|
286
|
+
})
|
|
287
|
+
.then(() => {
|
|
288
|
+
// Resolve after REST fallback completes
|
|
289
|
+
resolve()
|
|
290
|
+
})
|
|
291
|
+
} else {
|
|
292
|
+
// if (Exchange.ws.market.end_base) {
|
|
293
|
+
// Exchange.ws.market.end_base(); // Close the connection and prevent reconnection
|
|
294
|
+
// console.log(`${Exchange_name} WebSocket connection closed.`);
|
|
295
|
+
// }
|
|
296
|
+
resolve()
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
.catch((err) => {
|
|
300
|
+
console.error(`Error in rate_ws for ${Exchange_name}:`, err)
|
|
304
301
|
resolve()
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
})
|
|
311
|
-
} else {
|
|
312
|
-
// WebSocket connection not ready yet, wait and retry
|
|
313
|
-
setTimeout(check_status, 100)
|
|
302
|
+
})
|
|
303
|
+
} else {
|
|
304
|
+
// WebSocket connection not ready yet, wait and retry
|
|
305
|
+
setTimeout(check_status, 100)
|
|
306
|
+
}
|
|
314
307
|
}
|
|
315
|
-
}
|
|
316
308
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
309
|
+
check_status()
|
|
310
|
+
})
|
|
311
|
+
// Clean up promise reference when done (but keep connection open for reuse)
|
|
312
|
+
ws_promise.finally(() => {
|
|
313
|
+
// Don't delete immediately - keep for a short time to allow reuse
|
|
314
|
+
setTimeout(() => {
|
|
315
|
+
if (ws_connection_promises[ws_key] === ws_promise) {
|
|
316
|
+
delete ws_connection_promises[ws_key]
|
|
317
|
+
// Also clean up wrapped promise if it exists
|
|
318
|
+
if (ws_connection_promises_with_fallback[ws_key]) {
|
|
319
|
+
delete ws_connection_promises_with_fallback[ws_key]
|
|
320
|
+
}
|
|
328
321
|
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
})
|
|
322
|
+
}, 1000)
|
|
323
|
+
})
|
|
332
324
|
}
|
|
333
325
|
|
|
334
326
|
// Wrap WebSocket promise to handle rejection and fall back to REST
|
|
@@ -352,7 +344,7 @@ const get_exchanges_rates = function (Exchanges, exchange_pair_dict, cb, ws_enab
|
|
|
352
344
|
rest_resolve()
|
|
353
345
|
})
|
|
354
346
|
})
|
|
355
|
-
})
|
|
347
|
+
}),
|
|
356
348
|
).then(() => {
|
|
357
349
|
// Resolve after REST fallback completes
|
|
358
350
|
return Promise.resolve()
|
|
@@ -485,152 +477,150 @@ const get_exchanges_market_history = function (Exchanges, exchange_pair_dict, ti
|
|
|
485
477
|
first_time[Exchange_name] = true
|
|
486
478
|
}
|
|
487
479
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
Exchange_name,
|
|
492
|
-
WS_TIMEOUT_MS,
|
|
493
|
-
MAX_CHECKS,
|
|
494
|
-
(elapsed) => {
|
|
480
|
+
const WS_TIMEOUT_MS = 30000 // 30 seconds timeout
|
|
481
|
+
const MAX_CHECKS = 300 // 30 seconds / 100ms
|
|
482
|
+
const check_timeout = create_ws_timeout_checker(Exchange_name, WS_TIMEOUT_MS, MAX_CHECKS, (elapsed) => {
|
|
495
483
|
console.error(`WebSocket timeout for ${Exchange_name} market history after ${elapsed}ms`)
|
|
496
484
|
reject(new Error(`WebSocket timeout for ${Exchange_name} market history`))
|
|
497
|
-
}
|
|
498
|
-
)
|
|
485
|
+
})
|
|
499
486
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
487
|
+
const check_status = () => {
|
|
488
|
+
if (check_timeout()) {
|
|
489
|
+
return
|
|
490
|
+
}
|
|
504
491
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
492
|
+
// Safety check for WebSocket object
|
|
493
|
+
if (!Exchange.ws || !Exchange.ws.market) {
|
|
494
|
+
console.error(`${Exchange_name} WebSocket object missing during check`)
|
|
495
|
+
reject(new Error(`WebSocket object missing for ${Exchange_name}`))
|
|
496
|
+
return
|
|
497
|
+
}
|
|
511
498
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
// Wait for pair to become ready with retry logic
|
|
535
|
-
const pair_start_time = Date.now()
|
|
536
|
-
const check_pair_ready = () => {
|
|
537
|
-
// Check if this specific pair is ready before calling market_history_ws
|
|
538
|
-
const pair_ready = (
|
|
539
|
-
Exchange.ws.market.pair_status &&
|
|
540
|
-
Exchange.ws.market.pair_status[pair] === 'opened' &&
|
|
541
|
-
Exchange.ws.market.market_history_dict &&
|
|
542
|
-
Exchange.ws.market.market_history_dict[pair] &&
|
|
543
|
-
Exchange.ws.market.market_history_dict[pair].length >= 0
|
|
544
|
-
)
|
|
545
|
-
|
|
546
|
-
if (pair_ready) {
|
|
547
|
-
// Pair is ready, try to get market history
|
|
548
|
-
Exchange.market_history_ws(pair, timeframe_in_ms, function (res) {
|
|
549
|
-
if (res.success) {
|
|
550
|
-
// Success - reset failure count
|
|
551
|
-
reset_ws_failure(ws_market_history_failed_pairs, Exchange_name, pair)
|
|
552
|
-
set_pair_data(exchange_pair_market_history, pair_exchange_market_history, Exchange_name, pair, res.body)
|
|
553
|
-
rate_resolve({ pair, failed: false })
|
|
554
|
-
} else {
|
|
555
|
-
console.error(`getting market history ws failed for ${Exchange_name} ${pair}:`, res)
|
|
556
|
-
delete_pair_data(exchange_pair_market_history, pair_exchange_market_history, Exchange_name, pair)
|
|
557
|
-
track_ws_failure(ws_market_history_failed_pairs, Exchange_name, pair)
|
|
558
|
-
rate_resolve({ pair, failed: true })
|
|
559
|
-
}
|
|
560
|
-
})
|
|
561
|
-
} else {
|
|
562
|
-
// Pair not ready yet - check if we've exceeded timeout
|
|
563
|
-
const elapsed = Date.now() - pair_start_time
|
|
564
|
-
if (elapsed >= PAIR_READY_TIMEOUT_MS) {
|
|
565
|
-
// Timeout reached - mark as failed, will fall back to REST
|
|
566
|
-
console.log(`[WebSocket Market History] ${Exchange_name} ${pair}: Not ready after ${elapsed}ms (status: ${Exchange.ws.market.pair_status?.[pair] || 'unknown'})`)
|
|
567
|
-
rate_resolve({ pair, failed: true })
|
|
568
|
-
} else {
|
|
569
|
-
// Retry after a short delay
|
|
570
|
-
setTimeout(check_pair_ready, PAIR_CHECK_INTERVAL_MS)
|
|
571
|
-
}
|
|
499
|
+
// Check only the pairs we need for this call
|
|
500
|
+
let pairs_to_check = exchange_pair_dict[Exchange_name] || all_pairs
|
|
501
|
+
|
|
502
|
+
// Check if WebSocket connection is at least opening/opened (not closed/stopped)
|
|
503
|
+
const ws_connection_ready = Exchange.ws.market.status === 'opening' || Exchange.ws.market.status === 'opened'
|
|
504
|
+
|
|
505
|
+
if (ws_connection_ready) {
|
|
506
|
+
// Try each pair individually - wait a bit for pairs to become ready
|
|
507
|
+
// This allows us to detect individual pair failures while giving time for subscription
|
|
508
|
+
const PAIR_READY_TIMEOUT_MS = 10000 // Wait up to 10 seconds for pair to become ready
|
|
509
|
+
const PAIR_CHECK_INTERVAL_MS = 100 // Check every 100ms
|
|
510
|
+
|
|
511
|
+
let market_history_promises = pairs_to_check.map((pair) => {
|
|
512
|
+
return new Promise((rate_resolve) => {
|
|
513
|
+
// Check if this pair should skip WebSocket due to persistent failures
|
|
514
|
+
if (should_skip_websocket(ws_market_history_failed_pairs, Exchange_name, pair)) {
|
|
515
|
+
const failure_info = ws_market_history_failed_pairs[Exchange_name][pair]
|
|
516
|
+
console.log(
|
|
517
|
+
`[WebSocket Skip] ${Exchange_name} ${pair} market history: Skipping WebSocket (${failure_info.count} consecutive failures, last failed ${Math.round((Date.now() - failure_info.last_failed) / 1000)}s ago)`,
|
|
518
|
+
)
|
|
519
|
+
rate_resolve({ pair, failed: true, skip_ws: true })
|
|
520
|
+
return
|
|
572
521
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
Exchange.
|
|
522
|
+
|
|
523
|
+
// Wait for pair to become ready with retry logic
|
|
524
|
+
const pair_start_time = Date.now()
|
|
525
|
+
const check_pair_ready = () => {
|
|
526
|
+
// Check if this specific pair is ready before calling market_history_ws
|
|
527
|
+
const pair_ready =
|
|
528
|
+
Exchange.ws.market.pair_status &&
|
|
529
|
+
Exchange.ws.market.pair_status[pair] === 'opened' &&
|
|
530
|
+
Exchange.ws.market.market_history_dict &&
|
|
531
|
+
Exchange.ws.market.market_history_dict[pair] &&
|
|
532
|
+
Exchange.ws.market.market_history_dict[pair].length >= 0
|
|
533
|
+
|
|
534
|
+
if (pair_ready) {
|
|
535
|
+
// Pair is ready, try to get market history
|
|
536
|
+
Exchange.market_history_ws(pair, timeframe_in_ms, function (res) {
|
|
588
537
|
if (res.success) {
|
|
538
|
+
// Success - reset failure count
|
|
539
|
+
reset_ws_failure(ws_market_history_failed_pairs, Exchange_name, pair)
|
|
589
540
|
set_pair_data(exchange_pair_market_history, pair_exchange_market_history, Exchange_name, pair, res.body)
|
|
590
|
-
|
|
541
|
+
rate_resolve({ pair, failed: false })
|
|
591
542
|
} else {
|
|
592
|
-
console.error(`
|
|
543
|
+
console.error(`getting market history ws failed for ${Exchange_name} ${pair}:`, res)
|
|
593
544
|
delete_pair_data(exchange_pair_market_history, pair_exchange_market_history, Exchange_name, pair)
|
|
545
|
+
track_ws_failure(ws_market_history_failed_pairs, Exchange_name, pair)
|
|
546
|
+
rate_resolve({ pair, failed: true })
|
|
594
547
|
}
|
|
595
|
-
|
|
548
|
+
})
|
|
549
|
+
} else {
|
|
550
|
+
// Pair not ready yet - check if we've exceeded timeout
|
|
551
|
+
const elapsed = Date.now() - pair_start_time
|
|
552
|
+
if (elapsed >= PAIR_READY_TIMEOUT_MS) {
|
|
553
|
+
// Timeout reached - mark as failed, will fall back to REST
|
|
554
|
+
console.log(
|
|
555
|
+
`[WebSocket Market History] ${Exchange_name} ${pair}: Not ready after ${elapsed}ms (status: ${Exchange.ws.market.pair_status?.[pair] || 'unknown'})`,
|
|
556
|
+
)
|
|
557
|
+
rate_resolve({ pair, failed: true })
|
|
558
|
+
} else {
|
|
559
|
+
// Retry after a short delay
|
|
560
|
+
setTimeout(check_pair_ready, PAIR_CHECK_INTERVAL_MS)
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Start checking
|
|
566
|
+
check_pair_ready()
|
|
567
|
+
})
|
|
568
|
+
})
|
|
569
|
+
Promise.all(market_history_promises)
|
|
570
|
+
.then((results) => {
|
|
571
|
+
// Check which pairs failed and fall back to REST for those pairs only
|
|
572
|
+
const failed_pairs = results.filter((r) => r && r.failed).map((r) => r.pair)
|
|
573
|
+
if (failed_pairs.length > 0 && Exchange.market_history) {
|
|
574
|
+
console.log(`[WebSocket Fallback] ${Exchange_name} market history: Falling back to REST for ${failed_pairs.length} pair(s): ${failed_pairs.join(', ')}`)
|
|
575
|
+
const rest_promises = failed_pairs.map((pair) => {
|
|
576
|
+
return new Promise((rest_resolve) => {
|
|
577
|
+
Exchange.market_history(pair, timeframe_in_ms, function (res) {
|
|
578
|
+
if (res.success) {
|
|
579
|
+
set_pair_data(exchange_pair_market_history, pair_exchange_market_history, Exchange_name, pair, res.body)
|
|
580
|
+
console.log(`[REST Fallback] ${Exchange_name} ${pair} market history: Successfully fetched via REST`)
|
|
581
|
+
} else {
|
|
582
|
+
console.error(`[REST Fallback] getting market history failed for ${Exchange_name} ${pair}:`, res)
|
|
583
|
+
delete_pair_data(exchange_pair_market_history, pair_exchange_market_history, Exchange_name, pair)
|
|
584
|
+
}
|
|
585
|
+
rest_resolve()
|
|
586
|
+
})
|
|
596
587
|
})
|
|
597
588
|
})
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
589
|
+
// Wait for REST fallback to complete before resolving
|
|
590
|
+
return Promise.all(rest_promises)
|
|
591
|
+
.catch((err) => {
|
|
592
|
+
console.error(`[REST Fallback] Error fetching market history for ${Exchange_name}:`, err)
|
|
593
|
+
})
|
|
594
|
+
.then(() => {
|
|
595
|
+
// Resolve after REST fallback completes
|
|
596
|
+
resolve()
|
|
597
|
+
})
|
|
598
|
+
} else {
|
|
599
|
+
resolve()
|
|
600
|
+
}
|
|
601
|
+
})
|
|
602
|
+
.catch((err) => {
|
|
603
|
+
console.error(`Error in market_history_ws for ${Exchange_name}:`, err)
|
|
609
604
|
resolve()
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
console.error(`Error in market_history_ws for ${Exchange_name}:`, err)
|
|
614
|
-
resolve()
|
|
615
|
-
})
|
|
616
|
-
} else {
|
|
617
|
-
setTimeout(check_status, 100)
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
check_status()
|
|
621
|
-
})
|
|
622
|
-
// Clean up promise reference when done (but keep connection open for reuse)
|
|
623
|
-
ws_promise.finally(() => {
|
|
624
|
-
setTimeout(() => {
|
|
625
|
-
if (ws_market_history_promises[ws_key] === ws_promise) {
|
|
626
|
-
delete ws_market_history_promises[ws_key]
|
|
627
|
-
// Also clean up wrapped promise if it exists
|
|
628
|
-
if (ws_market_history_promises_with_fallback[ws_key]) {
|
|
629
|
-
delete ws_market_history_promises_with_fallback[ws_key]
|
|
605
|
+
})
|
|
606
|
+
} else {
|
|
607
|
+
setTimeout(check_status, 100)
|
|
630
608
|
}
|
|
631
609
|
}
|
|
632
|
-
|
|
633
|
-
|
|
610
|
+
check_status()
|
|
611
|
+
})
|
|
612
|
+
// Clean up promise reference when done (but keep connection open for reuse)
|
|
613
|
+
ws_promise.finally(() => {
|
|
614
|
+
setTimeout(() => {
|
|
615
|
+
if (ws_market_history_promises[ws_key] === ws_promise) {
|
|
616
|
+
delete ws_market_history_promises[ws_key]
|
|
617
|
+
// Also clean up wrapped promise if it exists
|
|
618
|
+
if (ws_market_history_promises_with_fallback[ws_key]) {
|
|
619
|
+
delete ws_market_history_promises_with_fallback[ws_key]
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}, 1000)
|
|
623
|
+
})
|
|
634
624
|
}
|
|
635
625
|
|
|
636
626
|
// If WebSocket promise rejects (timeout/error), fall back to REST for all pairs
|
|
@@ -654,7 +644,7 @@ const get_exchanges_market_history = function (Exchanges, exchange_pair_dict, ti
|
|
|
654
644
|
rest_resolve()
|
|
655
645
|
})
|
|
656
646
|
})
|
|
657
|
-
})
|
|
647
|
+
}),
|
|
658
648
|
).then(() => {
|
|
659
649
|
// Resolve after REST fallback completes
|
|
660
650
|
return Promise.resolve()
|