@icgio/icg-exchanges-wrapper 1.17.21 → 1.17.22

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 (2) hide show
  1. package/middleware/orders.js +165 -65
  2. package/package.json +2 -2
@@ -73,6 +73,7 @@ const MARKET_HISTORY_DEFAULT_MS = 10 * 60 * 1000
73
73
  const TRADES_DEFAULT_MS = 7 * 24 * 60 * 60 * 1000
74
74
  const NO_CALLBACK_TIMEOUT_MS = 2 * 60 * 1000
75
75
  const PROCESS_RATES_THROTTLE_MS = 75
76
+ const MAX_ORDER_TRACE_ENTRIES = 20
76
77
  const ORDER_TYPE_PRIORITY = {
77
78
  self: 2,
78
79
  maker_1: 3,
@@ -89,6 +90,13 @@ const getOrderPriority = (order_type, is_cancel = false) => {
89
90
  return is_cancel ? priority - 0.5 : priority
90
91
  }
91
92
 
93
+ const hrtime_diff = (start, end) => {
94
+ if (!Array.isArray(start) || !Array.isArray(end)) {
95
+ return undefined
96
+ }
97
+ return (end[0] - start[0]) * 1e9 + (end[1] - start[1])
98
+ }
99
+
92
100
  module.exports = class Orders {
93
101
  constructor(exchange_cur_quote_cur_dict, cd, settings) {
94
102
  let { exchange_cur_quote_cur, exchange_cur_quote_cur_rates_only, pair_map_rates_only } = exchange_cur_quote_cur_dict
@@ -272,60 +280,125 @@ module.exports = class Orders {
272
280
  ws_client.set_order_info_ref(this.order_info)
273
281
  }
274
282
  }
283
+ build_trace_entry(order, note) {
284
+ return {
285
+ ts: Date.now(),
286
+ status: order.status,
287
+ note,
288
+ f_amount: order.f_amount,
289
+ }
290
+ }
291
+ append_trace(order, note) {
292
+ if (!note || !order) return
293
+ if (!Array.isArray(order.trace)) {
294
+ order.trace = []
295
+ }
296
+ order.trace.push(this.build_trace_entry(order, note))
297
+ if (order.trace.length > MAX_ORDER_TRACE_ENTRIES) {
298
+ order.trace.splice(0, order.trace.length - MAX_ORDER_TRACE_ENTRIES)
299
+ }
300
+ }
301
+ move_order_info_key(exchange, pair, type, from_order_id, to_order_id) {
302
+ if (!from_order_id || !to_order_id || from_order_id === to_order_id) {
303
+ return
304
+ }
305
+ const from_bucket = this.order_info?.[exchange]?.[pair]?.[type]
306
+ if (!from_bucket || !from_bucket[from_order_id]) {
307
+ return
308
+ }
309
+ const order = from_bucket[from_order_id]
310
+ from_bucket[to_order_id] = order
311
+ delete from_bucket[from_order_id]
312
+ if (order.order_type) {
313
+ const by_type = this.order_info_by_type?.[order.order_type]?.[exchange]?.[pair]?.[type]
314
+ if (by_type) {
315
+ by_type[to_order_id] = order
316
+ delete by_type[from_order_id]
317
+ }
318
+ }
319
+ }
275
320
  init_account(balances, benchmarks) {
276
321
  this.account = new Account(balances, benchmarks)
277
322
  }
278
323
  update_non_initial_from_balances(balances) {
279
324
  this.account?.update_non_initial_from_balances(balances)
280
325
  }
281
- limit_trade(exchange, pair, method, price, amount, id = uuid(), order_type = '', limiter_on = true, api_key_index, order_options = {}) {
326
+ limit_trade(exchange, pair, method, price, amount, id = uuid(), order_type = '', limiter_on = true, api_key_index, order_options = {}, decision_context = null) {
282
327
  price = round_price(exchange, pair, price)
283
328
  amount = round_amount(exchange, pair, amount)
284
- // console.log('%s %s %s %s %s %s at %s', limiter_on, order_type, exchange, pair, method, amount, price)
285
- this.update_order_info(
286
- exchange,
287
- pair,
288
- method,
289
- id,
290
- {
291
- order_type,
292
- status: 'INIT',
293
- id,
294
- order_id: id,
295
- type: method,
296
- price,
297
- amount,
298
- f_amount: 0,
299
- f_percentage: 0,
300
- t_amount: amount,
301
- open_time: new Date(),
302
- close_time: null,
303
- },
304
- order_type + ' init',
305
- )
306
- let no_callback_timeout = setTimeout(() => {
307
- if (_.get(this.order_info, [exchange, pair, method, id, 'status']) === 'INIT') {
308
- console.log('no callback: %s %s %s %s %s', exchange, pair, method, id, order_type)
309
- this.update_order_info(
310
- exchange,
311
- pair,
312
- method,
313
- id,
314
- {
315
- status: 'N-OPENED-TIMEOUT',
316
- },
317
- order_type + ' n-opened-timeout-init',
318
- )
329
+ const internal_id = id
330
+ let no_callback_timeout = null
331
+ let init_emitted = false
332
+ const start_no_callback_timeout = () => {
333
+ if (no_callback_timeout) {
334
+ clearTimeout(no_callback_timeout)
335
+ }
336
+ no_callback_timeout = setTimeout(() => {
337
+ if (_.get(this.order_info, [exchange, pair, method, internal_id, 'status']) === 'INIT') {
338
+ console.log('no callback: %s %s %s %s %s', exchange, pair, method, internal_id, order_type)
339
+ this.update_order_info(
340
+ exchange,
341
+ pair,
342
+ method,
343
+ internal_id,
344
+ {
345
+ status: 'N-OPENED-TIMEOUT',
346
+ },
347
+ order_type + ' n-opened-timeout-init',
348
+ )
349
+ }
350
+ }, NO_CALLBACK_TIMEOUT_MS)
351
+ }
352
+ const emit_init = ({ t_order_sent } = {}) => {
353
+ if (init_emitted) {
354
+ return
319
355
  }
320
- }, NO_CALLBACK_TIMEOUT_MS)
356
+ init_emitted = true
357
+ const init_decision_context = decision_context ? { ...decision_context } : null
358
+ const t2t_ns = hrtime_diff(init_decision_context?.t_decision_hr, t_order_sent)
359
+ if (init_decision_context) {
360
+ delete init_decision_context.t_decision_hr
361
+ }
362
+ this.update_order_info(
363
+ exchange,
364
+ pair,
365
+ method,
366
+ internal_id,
367
+ {
368
+ order_type,
369
+ status: 'INIT',
370
+ id: internal_id,
371
+ internal_id,
372
+ order_id: null,
373
+ type: method,
374
+ price,
375
+ amount,
376
+ f_amount: 0,
377
+ f_percentage: 0,
378
+ t_amount: amount,
379
+ open_time: Date.now(),
380
+ close_time: null,
381
+ decision_context: init_decision_context,
382
+ t2t_ns,
383
+ event_hrtime: t_order_sent,
384
+ },
385
+ order_type + ' init',
386
+ )
387
+ start_no_callback_timeout()
388
+ }
321
389
  let limit_trade = (cb) => {
390
+ const order_options_with_meta = Object.assign({}, order_options, {
391
+ on_sent: ({ t_order_sent }) => {
392
+ emit_init({ t_order_sent })
393
+ },
394
+ })
322
395
  if (limiter_on) {
323
396
  if (this.Exchanges[exchange].private_limiter) {
324
397
  this.Exchanges[exchange].private_limiter.priority = getOrderPriority(order_type)
325
398
  }
326
- this.Exchanges[exchange].limit_trade(pair, method, price, amount, order_options, cb)
399
+ this.Exchanges[exchange].limit_trade(pair, method, price, amount, order_options_with_meta, cb)
327
400
  } else {
328
- this.Exchanges[exchange].limit_trade_base(pair, method, price, amount, order_options, cb)
401
+ this.Exchanges[exchange].limit_trade_base(pair, method, price, amount, order_options_with_meta, cb)
329
402
  }
330
403
  }
331
404
  if (api_key_index) {
@@ -334,7 +407,8 @@ module.exports = class Orders {
334
407
  limit_trade((res) => {
335
408
  if (res.success) {
336
409
  let order_id = res.body
337
- this.delete_order_info(exchange, pair, method, id, order_type + ' n-filled')
410
+ emit_init(res.meta || {})
411
+ this.move_order_info_key(exchange, pair, method, internal_id, order_id)
338
412
  this.update_order_info(
339
413
  exchange,
340
414
  pair,
@@ -343,7 +417,8 @@ module.exports = class Orders {
343
417
  {
344
418
  order_type,
345
419
  status: 'N-FILLED',
346
- id,
420
+ id: internal_id,
421
+ internal_id,
347
422
  order_id,
348
423
  type: method,
349
424
  price,
@@ -351,29 +426,32 @@ module.exports = class Orders {
351
426
  f_amount: 0,
352
427
  f_percentage: 0,
353
428
  t_amount: amount,
354
- open_time: new Date(),
355
429
  close_time: null,
430
+ order_rtt_ns: hrtime_diff(_.get(res, ['meta', 't_order_sent']), _.get(res, ['meta', 't_ack_recv'])),
431
+ event_hrtime: _.get(res, ['meta', 't_ack_recv']),
356
432
  },
357
433
  order_type + ' n-filled',
358
434
  )
359
435
  } else if (res.error === 'amount-too-small' || res.error === 'insufficient-funds') {
360
- this.delete_order_info(exchange, pair, method, id, order_type + ' limit-trade-' + res.error)
436
+ this.delete_order_info(exchange, pair, method, internal_id, order_type + ' limit-trade-' + res.error)
361
437
  } else if (res.error === 'connection-error') {
362
438
  setTimeout(() => {
363
- this.delete_order_info(exchange, pair, method, id, order_type + ' limit-trade-' + res.error)
439
+ this.delete_order_info(exchange, pair, method, internal_id, order_type + ' limit-trade-' + res.error)
364
440
  }, this.delete_connection_error_orders_timeout)
365
441
  } else {
366
442
  if (order_type.startsWith('self') || order_type.startsWith('maker_2')) {
367
- this.delete_order_info(exchange, pair, method, id, order_type + ' limit-trade-timeout')
443
+ this.delete_order_info(exchange, pair, method, internal_id, order_type + ' limit-trade-timeout')
368
444
  }
369
445
  // else if (_.get(this.order_info, [exchange, pair, method, id, 'status']) === 'INIT') { // make sure no_callback_timeout is NOT running zero
370
446
  // this.update_order_info(exchange, pair, method, id, {
371
447
  // status: 'N-OPENED-TIMEOUT'
372
448
  // }, order_type + ' n-opened-timeout-callback')
373
449
  // }
374
- console.error('limit trade error: %s %s %s %s %s %s %s %j', order_type, id, exchange, pair, method, price, amount, res)
450
+ console.error('limit trade error: %s %s %s %s %s %s %s %j', order_type, internal_id, exchange, pair, method, price, amount, res)
451
+ }
452
+ if (no_callback_timeout) {
453
+ clearTimeout(no_callback_timeout)
375
454
  }
376
- clearTimeout(no_callback_timeout)
377
455
  })
378
456
  }
379
457
  cancel_order(exchange, pair, order) {
@@ -401,7 +479,9 @@ module.exports = class Orders {
401
479
  order.order_id,
402
480
  {
403
481
  status: 'CANCELED',
404
- close_time: new Date(),
482
+ close_time: Date.now(),
483
+ cancel_latency_ns: hrtime_diff(_.get(res, ['meta', 't_cancel_sent']), _.get(res, ['meta', 't_cancel_ack'])),
484
+ event_hrtime: _.get(res, ['meta', 't_cancel_ack']),
405
485
  },
406
486
  order.order_type + ' canceled',
407
487
  )
@@ -420,7 +500,7 @@ module.exports = class Orders {
420
500
  amount: 0,
421
501
  f_amount: order.amount,
422
502
  f_percentage: 1,
423
- close_time: new Date(),
503
+ close_time: Date.now(),
424
504
  },
425
505
  order.order_type + ' cannot-be-canceled',
426
506
  )
@@ -552,8 +632,9 @@ module.exports = class Orders {
552
632
  amount: 0,
553
633
  f_amount: trade_amount,
554
634
  f_percentage: trade_amount / order_info.t_amount,
555
- close_time: trade.close_time || new Date(),
635
+ close_time: trade.close_time ? new Date(trade.close_time).getTime() : Date.now(),
556
636
  fees: trade.fees,
637
+ event_hrtime: process.hrtime(),
557
638
  },
558
639
  order_info.order_type + ' canceled-order-filled(-more)',
559
640
  )
@@ -592,8 +673,9 @@ module.exports = class Orders {
592
673
  amount: order_info.t_amount - trade_amount,
593
674
  f_amount: trade.amount,
594
675
  f_percentage: trade_amount / order_info.t_amount,
595
- close_time: trade.close_time || new Date(),
676
+ close_time: trade.close_time ? new Date(trade.close_time).getTime() : Date.now(),
596
677
  fees: trade.fees,
678
+ event_hrtime: process.hrtime(),
597
679
  },
598
680
  order_info.order_type + ' order-f-filled-1',
599
681
  )
@@ -678,7 +760,8 @@ module.exports = class Orders {
678
760
  amount: 0,
679
761
  f_amount: order_info_order.t_amount,
680
762
  f_percentage: 1,
681
- close_time: new Date(),
763
+ close_time: Date.now(),
764
+ event_hrtime: process.hrtime(),
682
765
  },
683
766
  order_info_order.order_type + ' order-f-filled-2',
684
767
  )
@@ -713,7 +796,8 @@ module.exports = class Orders {
713
796
  amount: order.amount,
714
797
  f_amount: order_f_amount,
715
798
  f_percentage: order_f_amount / order_info.t_amount,
716
- close_time: order.close_time || new Date(),
799
+ close_time: order.close_time ? new Date(order.close_time).getTime() : Date.now(),
800
+ event_hrtime: process.hrtime(),
717
801
  },
718
802
  order_info.order_type + ' order-f-filled-3',
719
803
  )
@@ -744,8 +828,8 @@ module.exports = class Orders {
744
828
  this.get_order_info_status_all(exchange, pair, 'both', 'INIT').map((init_order) => {
745
829
  let init_order_price = round_price(exchange, pair, init_order.price)
746
830
  let order_price = round_price(exchange, pair, order.price)
747
- let init_order_time = init_order.open_time.getTime()
748
- let order_time = order.open_time.getTime()
831
+ let init_order_time = typeof init_order.open_time === 'number' ? init_order.open_time : new Date(init_order.open_time).getTime()
832
+ let order_time = typeof order.open_time === 'number' ? order.open_time : new Date(order.open_time).getTime()
749
833
  if (
750
834
  init_order.type === order.type &&
751
835
  init_order_price === order_price &&
@@ -753,7 +837,7 @@ module.exports = class Orders {
753
837
  order_time - init_order_time < this.check_init_order_opened_timeout
754
838
  ) {
755
839
  // console.log('ORDERS: %s %s %s %s INIT ORDER OPENED', init_order.order_id, order.order_id, exchange, pair)
756
- this.delete_order_info(exchange, pair, init_order.type, init_order.order_id, init_order.order_type + ' init-order-opened')
840
+ this.move_order_info_key(exchange, pair, init_order.type, init_order.internal_id || init_order.id, order.order_id)
757
841
  this.update_order_info(
758
842
  exchange,
759
843
  pair,
@@ -767,8 +851,9 @@ module.exports = class Orders {
767
851
  amount: order.amount,
768
852
  f_amount: 0,
769
853
  t_amount: order.amount,
770
- open_time: order.open_time || new Date(),
854
+ open_time: order.open_time ? new Date(order.open_time).getTime() : Date.now(),
771
855
  close_time: null,
856
+ event_hrtime: process.hrtime(),
772
857
  },
773
858
  init_order.order_type + ' init-order-opened',
774
859
  )
@@ -788,7 +873,8 @@ module.exports = class Orders {
788
873
  order_info.order_id,
789
874
  {
790
875
  status: 'CANCELED',
791
- close_time: new Date(),
876
+ close_time: Date.now(),
877
+ event_hrtime: process.hrtime(),
792
878
  },
793
879
  (order_info.order_type || '') + ' canceling-canceled-from-open-orders',
794
880
  )
@@ -809,6 +895,8 @@ module.exports = class Orders {
809
895
  const existing = this.order_info[exchange]?.[pair]?.[type]?.[order_id]
810
896
  const old_status = existing && existing.status
811
897
  const old_order_type = existing && existing.order_type
898
+ const event_hrtime = dict.event_hrtime
899
+ delete dict.event_hrtime
812
900
 
813
901
  for (let key in dict) {
814
902
  if (key === 'f_percentage') {
@@ -822,7 +910,15 @@ module.exports = class Orders {
822
910
  }
823
911
 
824
912
  const order = this.order_info[exchange][pair][type][order_id]
913
+ if (order.internal_id == null && order.id != null) {
914
+ order.internal_id = order.id
915
+ }
916
+ if (order.trace == null) {
917
+ order.trace = []
918
+ }
919
+ order.lifecycle_seq = existing && Number.isFinite(existing.lifecycle_seq) ? existing.lifecycle_seq + 1 : dict.lifecycle_seq || 1
825
920
  order.last_update_ts = Date.now()
921
+ this.append_trace(order, note)
826
922
 
827
923
  const new_status = order.status
828
924
  if (old_status !== new_status) {
@@ -879,14 +975,16 @@ module.exports = class Orders {
879
975
 
880
976
  // Send order update via WebSocket to dashboard backend
881
977
  const order_data = _.get(this.order_info, [exchange, pair, type, order_id], {})
882
- // Only send N-FILLED and later statuses (skip INIT which has temporary order_id)
883
- if (dict.status !== 'INIT' && order_data.status !== 'INIT' && ws_client) {
978
+ if (ws_client) {
884
979
  ws_client.send_order({
885
980
  action: 'update',
886
981
  exchange,
887
982
  pair,
888
983
  side: type,
889
984
  order_id,
985
+ internal_id: order_data.internal_id,
986
+ lifecycle_seq: order_data.lifecycle_seq,
987
+ event_hrtime,
890
988
  data: order_data,
891
989
  note,
892
990
  })
@@ -909,14 +1007,16 @@ module.exports = class Orders {
909
1007
  }
910
1008
  console.log('DELETE %s: %s %s %s %s', note, exchange, pair, type, order_id)
911
1009
 
912
- // Send delete event via WebSocket (only for non-INIT orders)
913
- if (order_data.status !== 'INIT' && ws_client) {
1010
+ if (ws_client && order_data.internal_id) {
914
1011
  ws_client.send_order({
915
1012
  action: 'delete',
916
1013
  exchange,
917
1014
  pair,
918
1015
  side: type,
919
1016
  order_id,
1017
+ internal_id: order_data.internal_id,
1018
+ lifecycle_seq: (order_data.lifecycle_seq || 0) + 1,
1019
+ event_hrtime: process.hrtime(),
920
1020
  data: order_data,
921
1021
  note,
922
1022
  })
@@ -1331,7 +1431,7 @@ module.exports = class Orders {
1331
1431
  for (let order_id in order_info[type]) {
1332
1432
  let { open_time } = order_info[type][order_id]
1333
1433
  // console.log(order_id, open_time, open_time < new Date() - 30 * 60 * 1000)
1334
- if (open_time < new Date() - QUERY_ORDER_TIME_IN_MS) {
1434
+ if (open_time < Date.now() - QUERY_ORDER_TIME_IN_MS) {
1335
1435
  // console.log('query_order_maker_1: %s %s %s', exchange, pair, order_id)
1336
1436
  Exchanges[exchange].query_order(pair, order_id, (res) => {
1337
1437
  count++
@@ -1406,10 +1506,10 @@ module.exports = class Orders {
1406
1506
  const throttled_process_rates = _.throttle(process_rates, PROCESS_RATES_THROTTLE_MS, { leading: true, trailing: true })
1407
1507
  let rates_handler = (exchange, pair, res) => {
1408
1508
  if (res.success) {
1409
- let { asks, bids } = res.body
1509
+ let { asks, bids, _ts } = res.body
1410
1510
  asks = asks.slice(0, RATES_LENGTH_MAX)
1411
1511
  bids = bids.slice(0, RATES_LENGTH_MAX)
1412
- _.set(this.rates, [exchange, pair], { asks, bids, update_time: new Date() })
1512
+ _.set(this.rates, [exchange, pair], { asks, bids, _ts, update_time: new Date() })
1413
1513
  /*
1414
1514
  * throttle to 20ms
1415
1515
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@icgio/icg-exchanges-wrapper",
3
- "version": "1.17.21",
3
+ "version": "1.17.22",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "homepage": "https://github.com/icgio/icg-exchanges-wrapper#readme",
19
19
  "dependencies": {
20
- "@icgio/icg-exchanges": "^1.40.44",
20
+ "@icgio/icg-exchanges": "^1.40.45",
21
21
  "@icgio/icg-utils": "^1.9.61",
22
22
  "lodash": "^4.17.23",
23
23
  "pg": "^8.17.2"