@icgio/icg-exchanges 1.40.45 → 1.40.47
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/README.md +3 -3
- package/lib/exchanges/ascendex.js +4 -3
- package/lib/exchanges/biconomy.js +10 -4
- package/lib/exchanges/binance.js +4 -3
- package/lib/exchanges/bingx.js +3 -2
- package/lib/exchanges/bitget.js +4 -3
- package/lib/exchanges/bitmart.js +3 -2
- package/lib/exchanges/bitrue.js +10 -4
- package/lib/exchanges/blofin.js +4 -3
- package/lib/exchanges/btse.js +3 -2
- package/lib/exchanges/bybit.js +3 -2
- package/lib/exchanges/coinbase-fix.js +12 -4
- package/lib/exchanges/coinbase.js +38 -17
- package/lib/exchanges/coinstore.js +3 -2
- package/lib/exchanges/cryptocom.js +4 -2
- package/lib/exchanges/exchange-base.js +32 -9
- package/lib/exchanges/exchange-ws.js +19 -11
- package/lib/exchanges/gate.js +4 -6
- package/lib/exchanges/hashkey.js +8 -2
- package/lib/exchanges/hashkeyglobal.js +3 -2
- package/lib/exchanges/hkbitex.js +3 -2
- package/lib/exchanges/htx.js +4 -3
- package/lib/exchanges/kucoin.js +26 -14
- package/lib/exchanges/lbank.js +10 -4
- package/lib/exchanges/mexc.js +3 -2
- package/lib/exchanges/okx.js +4 -3
- package/lib/exchanges/phemex.js +12 -3
- package/lib/exchanges/upbit.js +3 -2
- package/lib/exchanges/xt.js +4 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,11 +89,11 @@ Each exchange class extends `ExchangeBase` and implements:
|
|
|
89
89
|
|
|
90
90
|
The WebSocket base class (`exchange-ws.js`) uses three layers of detection to identify and recover from dead or degraded connections:
|
|
91
91
|
|
|
92
|
-
### Layer 1: Depth staleness (`
|
|
92
|
+
### Layer 1: Depth staleness (`market_ts_by_pair` check)
|
|
93
93
|
|
|
94
|
-
Runs every 10 seconds. For each subscribed pair, checks if `
|
|
94
|
+
Runs every 10 seconds. For each subscribed pair, checks if `market_ts_by_pair[pair].ts_ms_local_received` is older than `market_stale_time` (default 120s). If stale, disconnects and auto-reconnects after 5s.
|
|
95
95
|
|
|
96
|
-
`
|
|
96
|
+
`market_ts_by_pair` is updated when:
|
|
97
97
|
|
|
98
98
|
- A depth message is received (all exchanges)
|
|
99
99
|
- A server-initiated ping is received (lbank only — since lbank stops sending depth updates for low-liquidity pairs when the orderbook hasn't changed, the server ping acts as a liveness signal)
|
|
@@ -112,8 +112,9 @@ module.exports = class Ascendex extends ExchangeBase {
|
|
|
112
112
|
}),
|
|
113
113
|
)
|
|
114
114
|
})
|
|
115
|
-
this.ws.market.on_message((data) => {
|
|
115
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
116
116
|
data = JSON.parse(data)
|
|
117
|
+
const hr_parsed = process.hrtime()
|
|
117
118
|
if (data.op === 'pong') {
|
|
118
119
|
this.ws.market.last_pong = new Date()
|
|
119
120
|
} else {
|
|
@@ -126,7 +127,7 @@ module.exports = class Ascendex extends ExchangeBase {
|
|
|
126
127
|
for (let bid of data.data.bids) {
|
|
127
128
|
this.ws.market.bids_dict[pair].insert(bid[0], bid[1])
|
|
128
129
|
}
|
|
129
|
-
this.ws.market.
|
|
130
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ hr_recv, hr_parsed })
|
|
130
131
|
const asks_s = this.ws.market.asks_dict[pair].entries()
|
|
131
132
|
const bids_s = this.ws.market.bids_dict[pair].entries()
|
|
132
133
|
if (asks_s.length > 0 && bids_s.length > 0) {
|
|
@@ -152,7 +153,7 @@ module.exports = class Ascendex extends ExchangeBase {
|
|
|
152
153
|
this.ws.market.bids_dict[pair].insert(parseFloat(bid[0]), parseFloat(bid[1]))
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
|
-
this.ws.market.
|
|
156
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ hr_recv, hr_parsed })
|
|
156
157
|
const asks_u = this.ws.market.asks_dict[pair].entries()
|
|
157
158
|
const bids_u = this.ws.market.bids_dict[pair].entries()
|
|
158
159
|
if (asks_u.length > 0 && bids_u.length > 0) {
|
|
@@ -109,8 +109,9 @@ module.exports = class Biconomy extends ExchangeBase {
|
|
|
109
109
|
}),
|
|
110
110
|
)
|
|
111
111
|
})
|
|
112
|
-
this.ws.market.on_message((data) => {
|
|
112
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
113
113
|
data = JSON.parse(data)
|
|
114
|
+
const hr_parsed = process.hrtime()
|
|
114
115
|
if (data && data.method === 'depth.update') {
|
|
115
116
|
let pair = post_process_pair(data.params[2])
|
|
116
117
|
this.ws.market.pair_status[pair] = 'opened'
|
|
@@ -147,7 +148,7 @@ module.exports = class Biconomy extends ExchangeBase {
|
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
150
|
}
|
|
150
|
-
this.ws.market.
|
|
151
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ hr_recv, hr_parsed })
|
|
151
152
|
const asks_e = this.ws.market.asks_dict[pair].entries()
|
|
152
153
|
const bids_e = this.ws.market.bids_dict[pair].entries()
|
|
153
154
|
if (asks_e.length > 0 && bids_e.length > 0) {
|
|
@@ -173,8 +174,13 @@ module.exports = class Biconomy extends ExchangeBase {
|
|
|
173
174
|
this.ws.market.last_pong = new Date()
|
|
174
175
|
const now = Date.now()
|
|
175
176
|
pair_list.forEach((pair) => {
|
|
176
|
-
if (this.ws.market.
|
|
177
|
-
this.ws.market.
|
|
177
|
+
if (this.ws.market.market_ts_by_pair[pair]) {
|
|
178
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({
|
|
179
|
+
...this.ws.market.market_ts_by_pair[pair],
|
|
180
|
+
ts_ms_local_received: now,
|
|
181
|
+
hr_recv,
|
|
182
|
+
hr_parsed,
|
|
183
|
+
})
|
|
178
184
|
}
|
|
179
185
|
})
|
|
180
186
|
} else {
|
package/lib/exchanges/binance.js
CHANGED
|
@@ -73,8 +73,9 @@ module.exports = class Binance extends ExchangeBase {
|
|
|
73
73
|
;((this.rate_last_id = {}), (this.first_rate = {}))
|
|
74
74
|
this.ws.market_socket = new websocket('wss://stream.binance.com:9443/stream?streams=' + streams.slice(0, -1))
|
|
75
75
|
this.ws.market.on_open(() => {})
|
|
76
|
-
this.ws.market.on_message((data) => {
|
|
76
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
77
77
|
data = JSON.parse(data).data
|
|
78
|
+
const hr_parsed = process.hrtime()
|
|
78
79
|
if (data.e === 'depthUpdate') {
|
|
79
80
|
let pair = post_process_pair(data.s, true)
|
|
80
81
|
this.ws.market.pair_status[pair] = 'opened'
|
|
@@ -103,7 +104,7 @@ module.exports = class Binance extends ExchangeBase {
|
|
|
103
104
|
this.ws.market.bids_dict[pair].insert(parseFloat(bid[0]), parseFloat(bid[1]))
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
|
-
this.ws.market.
|
|
107
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: data.E, hr_recv, hr_parsed })
|
|
107
108
|
} else if (this.first_rate[pair] && data.U > lastu[pair] + 1) {
|
|
108
109
|
this.first_rate[pair] = false
|
|
109
110
|
}
|
|
@@ -134,7 +135,7 @@ module.exports = class Binance extends ExchangeBase {
|
|
|
134
135
|
if (options.depth === false) {
|
|
135
136
|
this.ws.market.asks_dict[pair] = [[parseFloat(data.a), parseFloat(data.A)]]
|
|
136
137
|
this.ws.market.bids_dict[pair] = [[parseFloat(data.b), parseFloat(data.B)]]
|
|
137
|
-
this.ws.market.
|
|
138
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: data.E || Date.now(), hr_recv, hr_parsed })
|
|
138
139
|
this.ws.market.pair_status[pair] = 'opened'
|
|
139
140
|
}
|
|
140
141
|
this.ws.bbo_observable.notify()
|
package/lib/exchanges/bingx.js
CHANGED
|
@@ -136,20 +136,21 @@ module.exports = class Bingx extends ExchangeBase {
|
|
|
136
136
|
this.ws.market.bids_dict[pair] = utils.ordered_dict(false)
|
|
137
137
|
})
|
|
138
138
|
})
|
|
139
|
-
this.ws.market.on_message((data) => {
|
|
139
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
140
140
|
zlib.gunzip(data, (err, decompressedData) => {
|
|
141
141
|
if (err) {
|
|
142
142
|
console.error('Error decompressing data:', err)
|
|
143
143
|
} else {
|
|
144
144
|
let data = decompressedData.toString('utf8')
|
|
145
145
|
data = JSON.parse(data)
|
|
146
|
+
const hr_parsed = process.hrtime()
|
|
146
147
|
if (data && _.includes(data.dataType, 'depth')) {
|
|
147
148
|
let parts = data.dataType.split('@')
|
|
148
149
|
let pair = post_process_pair(parts[0])
|
|
149
150
|
this.ws.market.pair_status[pair] = 'opened'
|
|
150
151
|
this.ws.market.asks_dict[pair] = data.data.asks.map((ask) => [parseFloat(ask[0]), parseFloat(ask[1])]).reverse()
|
|
151
152
|
this.ws.market.bids_dict[pair] = data.data.bids.map((bid) => [parseFloat(bid[0]), parseFloat(bid[1])])
|
|
152
|
-
this.ws.market.
|
|
153
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: data.timestamp, hr_recv, hr_parsed })
|
|
153
154
|
const asks = this.ws.market.asks_dict[pair]
|
|
154
155
|
const bids = this.ws.market.bids_dict[pair]
|
|
155
156
|
if (asks && asks.length > 0 && bids && bids.length > 0) {
|
package/lib/exchanges/bitget.js
CHANGED
|
@@ -147,18 +147,19 @@ module.exports = class Bitget extends ExchangeBase {
|
|
|
147
147
|
})
|
|
148
148
|
this.ws.market.ping('ping')
|
|
149
149
|
})
|
|
150
|
-
this.ws.market.on_message((data) => {
|
|
150
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
151
151
|
if (data.toString() === 'pong') {
|
|
152
152
|
this.ws.market.last_pong = new Date()
|
|
153
153
|
} else {
|
|
154
154
|
data = JSON.parse(data)
|
|
155
|
+
const hr_parsed = process.hrtime()
|
|
155
156
|
let book = data.data
|
|
156
157
|
if (data && data.arg && data.arg.channel === depth_val && Array.isArray(book) && book) {
|
|
157
158
|
let pair = data.arg.instId
|
|
158
159
|
this.ws.market.pair_status[pair] = 'opened'
|
|
159
160
|
this.ws.market.asks_dict[pair] = book[0].asks.map((ask) => [parseFloat(ask[0]), parseFloat(ask[1])])
|
|
160
161
|
this.ws.market.bids_dict[pair] = book[0].bids.map((bid) => [parseFloat(bid[0]), parseFloat(bid[1])])
|
|
161
|
-
this.ws.market.
|
|
162
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: parseInt(book[0].ts), hr_recv, hr_parsed })
|
|
162
163
|
} else if (data && data.arg && data.arg.channel === ticker_val && Array.isArray(book) && book) {
|
|
163
164
|
let pair = data.arg.instId
|
|
164
165
|
let t = book[0]
|
|
@@ -175,7 +176,7 @@ module.exports = class Bitget extends ExchangeBase {
|
|
|
175
176
|
if (options.depth === false) {
|
|
176
177
|
this.ws.market.asks_dict[pair] = [[ask_1_price, ask_1_amount]]
|
|
177
178
|
this.ws.market.bids_dict[pair] = [[bid_1_price, bid_1_amount]]
|
|
178
|
-
this.ws.market.
|
|
179
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: parseInt(t.ts || data.ts) || Date.now(), hr_recv, hr_parsed })
|
|
179
180
|
this.ws.market.pair_status[pair] = 'opened'
|
|
180
181
|
}
|
|
181
182
|
this.ws.bbo_observable.notify()
|
package/lib/exchanges/bitmart.js
CHANGED
|
@@ -165,11 +165,12 @@ module.exports = class Bitmart extends ExchangeBase {
|
|
|
165
165
|
})
|
|
166
166
|
this.ws.market.ping('ping')
|
|
167
167
|
})
|
|
168
|
-
this.ws.market.on_message((data) => {
|
|
168
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
169
169
|
if (data.toString() === 'pong') {
|
|
170
170
|
this.ws.market.last_pong = new Date()
|
|
171
171
|
} else {
|
|
172
172
|
data = JSON.parse(data)
|
|
173
|
+
const hr_parsed = process.hrtime()
|
|
173
174
|
let book = data.data
|
|
174
175
|
// Get the pair from the message's symbol field
|
|
175
176
|
const msg_symbol = data.symbol
|
|
@@ -178,7 +179,7 @@ module.exports = class Bitmart extends ExchangeBase {
|
|
|
178
179
|
this.ws.market.pair_status[target_pair] = 'opened'
|
|
179
180
|
this.ws.market.asks_dict[target_pair] = book.sells.map((ask) => [parseFloat(ask.price), parseFloat(ask.amount)])
|
|
180
181
|
this.ws.market.bids_dict[target_pair] = book.buys.map((bid) => [parseFloat(bid.price), parseFloat(bid.amount)])
|
|
181
|
-
this.ws.market.
|
|
182
|
+
this.ws.market.market_ts_by_pair[target_pair] = this.ws.market.build_ts({ ts_ms_venue_event: parseInt(data.timeStamp) || undefined, hr_recv, hr_parsed })
|
|
182
183
|
const asks = this.ws.market.asks_dict[target_pair]
|
|
183
184
|
const bids = this.ws.market.bids_dict[target_pair]
|
|
184
185
|
if (asks && asks.length > 0 && bids && bids.length > 0) {
|
package/lib/exchanges/bitrue.js
CHANGED
|
@@ -122,20 +122,21 @@ module.exports = class Bitrue extends ExchangeBase {
|
|
|
122
122
|
}),
|
|
123
123
|
)
|
|
124
124
|
})
|
|
125
|
-
this.ws.market.on_message((data) => {
|
|
125
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
126
126
|
zlib.gunzip(data, (err, decompressedData) => {
|
|
127
127
|
if (err) {
|
|
128
128
|
console.error('Error decompressing data:', err)
|
|
129
129
|
} else {
|
|
130
130
|
let data = decompressedData.toString('utf8')
|
|
131
131
|
data = JSON.parse(data)
|
|
132
|
+
const hr_parsed = process.hrtime()
|
|
132
133
|
if (data && _.includes(data.channel, 'depth_step0') && data.tick) {
|
|
133
134
|
let parts = data.channel.split('_')
|
|
134
135
|
let pair = post_process_pair(parts[1])
|
|
135
136
|
this.ws.market.pair_status[pair] = 'opened'
|
|
136
137
|
this.ws.market.asks_dict[pair] = data.tick.asks.map((ask) => [parseFloat(ask[0]), parseFloat(ask[1])])
|
|
137
138
|
this.ws.market.bids_dict[pair] = data.tick.buys.map((bid) => [parseFloat(bid[0]), parseFloat(bid[1])])
|
|
138
|
-
this.ws.market.
|
|
139
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: data.ts, hr_recv, hr_parsed })
|
|
139
140
|
const asks = this.ws.market.asks_dict[pair]
|
|
140
141
|
const bids = this.ws.market.bids_dict[pair]
|
|
141
142
|
if (asks && asks.length > 0 && bids && bids.length > 0) {
|
|
@@ -165,8 +166,13 @@ module.exports = class Bitrue extends ExchangeBase {
|
|
|
165
166
|
// Server ping proves connection is alive — reset stale timer for all opened pairs
|
|
166
167
|
const now = Date.now()
|
|
167
168
|
pair_list.forEach((pair) => {
|
|
168
|
-
if (this.ws.market.
|
|
169
|
-
this.ws.market.
|
|
169
|
+
if (this.ws.market.market_ts_by_pair[pair]) {
|
|
170
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({
|
|
171
|
+
...this.ws.market.market_ts_by_pair[pair],
|
|
172
|
+
ts_ms_local_received: now,
|
|
173
|
+
hr_recv,
|
|
174
|
+
hr_parsed,
|
|
175
|
+
})
|
|
170
176
|
}
|
|
171
177
|
})
|
|
172
178
|
} else {
|
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, hr_recv) => {
|
|
154
154
|
try {
|
|
155
155
|
data = JSON.parse(data)
|
|
156
|
+
const hr_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.
|
|
172
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ hr_recv, hr_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.
|
|
201
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ hr_recv, hr_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()
|
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, hr_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 hr_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.
|
|
136
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: book.timestamp, hr_recv, hr_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()
|
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, hr_recv) => {
|
|
94
94
|
try {
|
|
95
95
|
data = JSON.parse(data)
|
|
96
|
+
const hr_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.
|
|
135
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: data.ts, hr_recv, hr_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]
|
|
@@ -229,10 +229,14 @@ class CoinbaseFixMd {
|
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
_handle_md(fields) {
|
|
232
|
+
const hr_recv = process.hrtime()
|
|
232
233
|
let entry = parse_md_entry(fields)
|
|
233
234
|
if (!entry) return
|
|
234
|
-
|
|
235
|
-
entry.
|
|
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
|
|
236
240
|
this.on_event(entry)
|
|
237
241
|
}
|
|
238
242
|
|
|
@@ -240,10 +244,14 @@ class CoinbaseFixMd {
|
|
|
240
244
|
let entries = []
|
|
241
245
|
let now = Date.now()
|
|
242
246
|
for (let fields of batch) {
|
|
247
|
+
const hr_recv = process.hrtime()
|
|
243
248
|
let entry = parse_md_entry(fields)
|
|
244
249
|
if (!entry) continue
|
|
245
|
-
|
|
246
|
-
entry.
|
|
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
|
|
247
255
|
entries.push(entry)
|
|
248
256
|
}
|
|
249
257
|
if (entries.length) this.on_batch(entries)
|
|
@@ -219,13 +219,14 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
219
219
|
}))
|
|
220
220
|
console.log('coinbase ws-direct: subscribed channels:', channels.join(', '))
|
|
221
221
|
})
|
|
222
|
-
this.ws.market.on_message((data) => {
|
|
222
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
223
223
|
try {
|
|
224
224
|
data = JSON.parse(data)
|
|
225
225
|
} catch (err) {
|
|
226
226
|
console.error('coinbase', 'ws-direct', 'JSON parse error:', err.message)
|
|
227
227
|
return
|
|
228
228
|
}
|
|
229
|
+
const hr_parsed = process.hrtime()
|
|
229
230
|
if (data.type === 'snapshot') {
|
|
230
231
|
const pair = post_process_pair(data.product_id)
|
|
231
232
|
if (!this.ws.market.asks_dict[pair]) return
|
|
@@ -252,7 +253,7 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
252
253
|
if (price >= lo && price <= hi) this.ws.market.asks_dict[pair].insert(price, qty)
|
|
253
254
|
}
|
|
254
255
|
this.ws.market.pair_status[pair] = 'opened'
|
|
255
|
-
this.ws.market.
|
|
256
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: Date.now(), hr_recv, hr_parsed })
|
|
256
257
|
const top_bid = this.ws.market.bids_dict[pair].top()
|
|
257
258
|
const top_ask = this.ws.market.asks_dict[pair].top()
|
|
258
259
|
if (top_bid != null && top_ask != null) {
|
|
@@ -283,7 +284,7 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
283
284
|
}
|
|
284
285
|
}
|
|
285
286
|
}
|
|
286
|
-
this.ws.market.
|
|
287
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: parse_event_ts_ms(data.time) || Date.now(), hr_recv, hr_parsed })
|
|
287
288
|
{
|
|
288
289
|
const top_bid = this.ws.market.bids_dict[pair].top()
|
|
289
290
|
const top_ask = this.ws.market.asks_dict[pair].top()
|
|
@@ -306,7 +307,7 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
306
307
|
if (options.depth === false) {
|
|
307
308
|
this.ws.market.asks_dict[pair] = [[best_ask, best_ask_qty]]
|
|
308
309
|
this.ws.market.bids_dict[pair] = [[best_bid, best_bid_qty]]
|
|
309
|
-
this.ws.market.
|
|
310
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: parse_event_ts_ms(data.time) || Date.now(), hr_recv, hr_parsed })
|
|
310
311
|
this.ws.market.pair_status[pair] = 'opened'
|
|
311
312
|
} else {
|
|
312
313
|
const bids = this.ws.market.bids_dict[pair]
|
|
@@ -323,7 +324,7 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
323
324
|
for (const k of prune_bids) bids.del(k)
|
|
324
325
|
const prune_asks = asks.keys().filter((k) => k > hi)
|
|
325
326
|
for (const k of prune_asks) asks.del(k)
|
|
326
|
-
if (bbo_changed) this.ws.market.
|
|
327
|
+
if (bbo_changed) this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: parse_event_ts_ms(data.time) || Date.now(), hr_recv, hr_parsed })
|
|
327
328
|
}
|
|
328
329
|
if (bbo_changed) this.ws.bbo_observable.notify()
|
|
329
330
|
} else if (data.type === 'match' || data.type === 'last_match') {
|
|
@@ -406,13 +407,14 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
406
407
|
this.ws.market.send(JSON.stringify({ type: 'subscribe', product_ids, channel: 'market_trades' }))
|
|
407
408
|
}
|
|
408
409
|
})
|
|
409
|
-
this.ws.market.on_message((data) => {
|
|
410
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
410
411
|
try {
|
|
411
412
|
data = JSON.parse(data)
|
|
412
413
|
} catch (err) {
|
|
413
414
|
console.error('coinbase', 'websocket', 'JSON parse error:', err.message)
|
|
414
415
|
return
|
|
415
416
|
}
|
|
417
|
+
const hr_parsed = process.hrtime()
|
|
416
418
|
const seq = data.sequence_num
|
|
417
419
|
if (seq !== undefined) {
|
|
418
420
|
const gap = (this._ws_last_seq !== undefined && seq > this._ws_last_seq + 1) ? seq - this._ws_last_seq - 1 : 0
|
|
@@ -483,10 +485,11 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
483
485
|
}
|
|
484
486
|
}
|
|
485
487
|
}
|
|
486
|
-
this.ws.market.
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
488
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({
|
|
489
|
+
ts_ms_venue_event: parse_event_ts_ms(data.timestamp) || Date.now(),
|
|
490
|
+
hr_recv,
|
|
491
|
+
hr_parsed,
|
|
492
|
+
})
|
|
490
493
|
{
|
|
491
494
|
const top_bid = this.ws.market.bids_dict[pair].top()
|
|
492
495
|
const top_ask = this.ws.market.asks_dict[pair].top()
|
|
@@ -532,7 +535,11 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
532
535
|
if (options.depth === false) {
|
|
533
536
|
this.ws.market.asks_dict[pair] = [[best_ask, best_ask_qty]]
|
|
534
537
|
this.ws.market.bids_dict[pair] = [[best_bid, best_bid_qty]]
|
|
535
|
-
this.ws.market.
|
|
538
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({
|
|
539
|
+
ts_ms_venue_event: parse_event_ts_ms(data.timestamp) || Date.now(),
|
|
540
|
+
hr_recv,
|
|
541
|
+
hr_parsed,
|
|
542
|
+
})
|
|
536
543
|
this.ws.market.pair_status[pair] = 'opened'
|
|
537
544
|
} else {
|
|
538
545
|
const bids = this.ws.market.bids_dict[pair]
|
|
@@ -550,7 +557,11 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
550
557
|
for (const k of prune_bids) bids.del(k)
|
|
551
558
|
const prune_asks = asks.keys().filter((k) => k > hi)
|
|
552
559
|
for (const k of prune_asks) asks.del(k)
|
|
553
|
-
this.ws.market.
|
|
560
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({
|
|
561
|
+
ts_ms_venue_event: parse_event_ts_ms(data.timestamp) || Date.now(),
|
|
562
|
+
hr_recv,
|
|
563
|
+
hr_parsed,
|
|
564
|
+
})
|
|
554
565
|
}
|
|
555
566
|
this.ws.bbo_observable.notify()
|
|
556
567
|
}
|
|
@@ -674,7 +685,7 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
674
685
|
const top_ask = asks.top()
|
|
675
686
|
if (top_bid != null && top_ask != null) {
|
|
676
687
|
this.ws.market.tickers[pair] = { bid: top_bid, bid_size: bids.val(top_bid), ask: top_ask, ask_size: asks.val(top_ask) }
|
|
677
|
-
this.ws.market.
|
|
688
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: Date.now() })
|
|
678
689
|
}
|
|
679
690
|
|
|
680
691
|
// Replay buffered events
|
|
@@ -754,8 +765,13 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
754
765
|
const pair = this._fix_md_pair_by_symbol[entry.symbol] || post_process_pair(entry.symbol)
|
|
755
766
|
if (!this.ws.market.asks_dict[pair]) continue
|
|
756
767
|
|
|
757
|
-
this.ws.market.last_message_time = entry.
|
|
758
|
-
this.ws.market.
|
|
768
|
+
this.ws.market.last_message_time = entry.ts_ms_local_received || Date.now()
|
|
769
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({
|
|
770
|
+
ts_ms_local_received: entry.ts_ms_local_received || Date.now(),
|
|
771
|
+
ts_ms_venue_event: entry.ts_ms_venue_event || Date.now(),
|
|
772
|
+
hr_recv: entry.hr_recv,
|
|
773
|
+
hr_parsed: entry.hr_parsed,
|
|
774
|
+
})
|
|
759
775
|
|
|
760
776
|
const snap_seq = this._fix_md_snapshot_seq[pair]
|
|
761
777
|
if (snap_seq === null) {
|
|
@@ -795,11 +811,16 @@ module.exports = class Coinbase extends ExchangeBase {
|
|
|
795
811
|
price: entry.price,
|
|
796
812
|
amount: entry.size,
|
|
797
813
|
total: entry.price * entry.size,
|
|
798
|
-
close_time: entry.
|
|
814
|
+
close_time: entry.ts_ms_venue_event ? new Date(entry.ts_ms_venue_event) : new Date(),
|
|
799
815
|
}
|
|
800
816
|
this.ws.market.market_history_dict[pair].unshift(trade)
|
|
801
817
|
this.ws.market.market_history_dict[pair] = this.ws.market.market_history_dict[pair].filter((tr) => tr.close_time > new Date() - this.ws.market.market_history_store_time)
|
|
802
|
-
this.ws.market.
|
|
818
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({
|
|
819
|
+
ts_ms_local_received: entry.ts_ms_local_received || Date.now(),
|
|
820
|
+
ts_ms_venue_event: entry.ts_ms_venue_event || Date.now(),
|
|
821
|
+
hr_recv: entry.hr_recv,
|
|
822
|
+
hr_parsed: entry.hr_parsed,
|
|
823
|
+
})
|
|
803
824
|
|
|
804
825
|
// Don't modify the book aggregate on trades — let CHANGE/DELETE events manage it.
|
|
805
826
|
// TRADE + CHANGE would double-subtract for partial fills. Instead:
|
|
@@ -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, hr_recv) => {
|
|
139
139
|
data = JSON.parse(data)
|
|
140
|
+
const hr_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.
|
|
146
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ hr_recv, hr_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) {
|
|
@@ -109,10 +109,12 @@ module.exports = class Cryptocom extends ExchangeBase {
|
|
|
109
109
|
)
|
|
110
110
|
}
|
|
111
111
|
})
|
|
112
|
-
this.ws.market.on_message((data) => {
|
|
112
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
113
113
|
let parsed
|
|
114
|
+
let hr_parsed
|
|
114
115
|
try {
|
|
115
116
|
parsed = JSON.parse(data)
|
|
117
|
+
hr_parsed = process.hrtime()
|
|
116
118
|
} catch (error) {
|
|
117
119
|
return
|
|
118
120
|
}
|
|
@@ -130,7 +132,7 @@ module.exports = class Cryptocom extends ExchangeBase {
|
|
|
130
132
|
let pair = post_process_pair(result.instrument_name, true)
|
|
131
133
|
if (result.channel === 'book') {
|
|
132
134
|
this.ws.market.pair_status[pair] = 'opened'
|
|
133
|
-
this.ws.market.
|
|
135
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ hr_recv, hr_parsed })
|
|
134
136
|
let { asks, bids } = result.data[0]
|
|
135
137
|
this.ws.market.asks_dict[pair] = asks.map((e) => [parseFloat(e[0]), parseFloat(e[1])])
|
|
136
138
|
this.ws.market.bids_dict[pair] = bids.map((e) => [parseFloat(e[0]), parseFloat(e[1])])
|
|
@@ -19,6 +19,27 @@ function trim_orderbook_response(res, depth = 1) {
|
|
|
19
19
|
return { ...res, body: trim_orderbook_body(res.body, depth) }
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
function is_finite_number(value) {
|
|
23
|
+
return value != null && Number.isFinite(Number(value))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalize_market_ts(raw_market_ts) {
|
|
27
|
+
if (!raw_market_ts || typeof raw_market_ts !== 'object') {
|
|
28
|
+
return undefined
|
|
29
|
+
}
|
|
30
|
+
const market_ts = {
|
|
31
|
+
ts_ms_venue_event: is_finite_number(raw_market_ts.ts_ms_venue_event) ? Number(raw_market_ts.ts_ms_venue_event) : undefined,
|
|
32
|
+
ts_ms_venue_send: is_finite_number(raw_market_ts.ts_ms_venue_send) ? Number(raw_market_ts.ts_ms_venue_send) : undefined,
|
|
33
|
+
ts_ms_local_received: is_finite_number(raw_market_ts.ts_ms_local_received) ? Number(raw_market_ts.ts_ms_local_received) : undefined,
|
|
34
|
+
hr_recv: Array.isArray(raw_market_ts.hr_recv) ? raw_market_ts.hr_recv : undefined,
|
|
35
|
+
hr_parsed: Array.isArray(raw_market_ts.hr_parsed) ? raw_market_ts.hr_parsed : undefined,
|
|
36
|
+
}
|
|
37
|
+
if (Object.values(market_ts).some((value) => value != null)) {
|
|
38
|
+
return market_ts
|
|
39
|
+
}
|
|
40
|
+
return undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
22
43
|
const DEFAULT_HISTORY_MIN_WINDOW_MS = 60 * 1000
|
|
23
44
|
|
|
24
45
|
function sleep(ms) {
|
|
@@ -229,13 +250,15 @@ class ExchangeBase {
|
|
|
229
250
|
}
|
|
230
251
|
|
|
231
252
|
snapshot_market_ts(pair) {
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
253
|
+
const ticker_market_ts = _.get(this, ['ws', 'market', 'tickers', pair, 'market_ts'])
|
|
254
|
+
const normalized_ticker_market_ts = normalize_market_ts(ticker_market_ts)
|
|
255
|
+
if (normalized_ticker_market_ts) {
|
|
256
|
+
return { ...normalized_ticker_market_ts }
|
|
235
257
|
}
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
258
|
+
const market_ts = _.get(this, ['ws', 'market', 'market_ts_by_pair', pair])
|
|
259
|
+
const normalized_market_ts = normalize_market_ts(market_ts)
|
|
260
|
+
if (normalized_market_ts) {
|
|
261
|
+
return { ...normalized_market_ts }
|
|
239
262
|
}
|
|
240
263
|
return undefined
|
|
241
264
|
}
|
|
@@ -252,7 +275,7 @@ class ExchangeBase {
|
|
|
252
275
|
...res,
|
|
253
276
|
body: {
|
|
254
277
|
...res.body,
|
|
255
|
-
|
|
278
|
+
market_ts: normalize_market_ts(res.body.market_ts) || market_ts,
|
|
256
279
|
},
|
|
257
280
|
}
|
|
258
281
|
}
|
|
@@ -612,12 +635,12 @@ class ExchangeBase {
|
|
|
612
635
|
bbo_ws(pair, cb) {
|
|
613
636
|
const t = this.ws.market.tickers[pair]
|
|
614
637
|
if (t && t.bid > 0 && t.ask > 0) {
|
|
615
|
-
cb({ success: true, body: { asks: [[t.ask, t.ask_size]], bids: [[t.bid, t.bid_size]],
|
|
638
|
+
cb({ success: true, body: { asks: [[t.ask, t.ask_size]], bids: [[t.bid, t.bid_size]], market_ts: normalize_market_ts(t.market_ts) || this.snapshot_market_ts(pair) } })
|
|
616
639
|
} else {
|
|
617
640
|
// Fallback: derive from full orderbook
|
|
618
641
|
this.rate_ws(pair, (res) => {
|
|
619
642
|
if (res.success && res.body.bids.length > 0 && res.body.asks.length > 0) {
|
|
620
|
-
cb({ success: true, body: { asks: [res.body.asks[0]], bids: [res.body.bids[0]],
|
|
643
|
+
cb({ success: true, body: { asks: [res.body.asks[0]], bids: [res.body.bids[0]], market_ts: normalize_market_ts(res.body.market_ts) || this.snapshot_market_ts(pair) } })
|
|
621
644
|
} else {
|
|
622
645
|
cb(res)
|
|
623
646
|
}
|
|
@@ -23,9 +23,16 @@ module.exports = class ExchangeWs {
|
|
|
23
23
|
pair_list: [],
|
|
24
24
|
asks_dict: {},
|
|
25
25
|
bids_dict: {},
|
|
26
|
-
|
|
26
|
+
market_ts_by_pair: {},
|
|
27
27
|
market_history_dict: {},
|
|
28
28
|
tickers: {},
|
|
29
|
+
build_ts: (market_ts = {}) => ({
|
|
30
|
+
ts_ms_local_received: market_ts.ts_ms_local_received != null ? market_ts.ts_ms_local_received : Date.now(),
|
|
31
|
+
...(market_ts.ts_ms_venue_event != null ? { ts_ms_venue_event: market_ts.ts_ms_venue_event } : {}),
|
|
32
|
+
...(market_ts.ts_ms_venue_send != null ? { ts_ms_venue_send: market_ts.ts_ms_venue_send } : {}),
|
|
33
|
+
...(Array.isArray(market_ts.hr_recv) ? { hr_recv: market_ts.hr_recv } : {}),
|
|
34
|
+
...(Array.isArray(market_ts.hr_parsed) ? { hr_parsed: market_ts.hr_parsed } : {}),
|
|
35
|
+
}),
|
|
29
36
|
start_base: () => {
|
|
30
37
|
console.log('start_base empty')
|
|
31
38
|
},
|
|
@@ -37,7 +44,7 @@ module.exports = class ExchangeWs {
|
|
|
37
44
|
this.market.pair_list = []
|
|
38
45
|
this.market.asks_dict = {}
|
|
39
46
|
this.market.bids_dict = {}
|
|
40
|
-
this.market.
|
|
47
|
+
this.market.market_ts_by_pair = {}
|
|
41
48
|
this.market.market_history_dict = {}
|
|
42
49
|
this.market.tickers = {}
|
|
43
50
|
this.market.last_ping = undefined
|
|
@@ -142,15 +149,15 @@ module.exports = class ExchangeWs {
|
|
|
142
149
|
if (this.market.status !== 'opened') return
|
|
143
150
|
const now = Date.now()
|
|
144
151
|
const stale_time = this.market.market_stale_time
|
|
145
|
-
// Check
|
|
146
|
-
const has_ts = Object.keys(this.market.
|
|
152
|
+
// Check market_ts_by_pair (depth-specific staleness, for exchanges with server-initiated pings)
|
|
153
|
+
const has_ts = Object.keys(this.market.market_ts_by_pair).length > 0
|
|
147
154
|
if (this.market.pair_stale_check_enabled && has_ts) {
|
|
148
155
|
const stale_pair = this.market.pair_list.find((pair) => {
|
|
149
|
-
const ts = _.get(this.market.
|
|
156
|
+
const ts = _.get(this.market.market_ts_by_pair, [pair, 'ts_ms_local_received'])
|
|
150
157
|
return ts && now - ts > stale_time
|
|
151
158
|
})
|
|
152
159
|
if (stale_pair) {
|
|
153
|
-
const ts = _.get(this.market.
|
|
160
|
+
const ts = _.get(this.market.market_ts_by_pair, [stale_pair, 'ts_ms_local_received'])
|
|
154
161
|
console.error('market websocket stale depth data, reconnecting: %s %s (%s)', this.name, stale_pair, ts ? Math.round((now - ts) / 1000) + 's' : 'no data')
|
|
155
162
|
this.market.end()
|
|
156
163
|
return
|
|
@@ -160,12 +167,12 @@ module.exports = class ExchangeWs {
|
|
|
160
167
|
// (e.g. Coinbase WS buffering — connection alive, heartbeats flowing, but data is minutes old)
|
|
161
168
|
if (has_ts) {
|
|
162
169
|
const stale_event_pair = this.market.pair_list.find((pair) => {
|
|
163
|
-
const ts = _.get(this.market.
|
|
164
|
-
return ts && ts.
|
|
170
|
+
const ts = _.get(this.market.market_ts_by_pair, [pair])
|
|
171
|
+
return ts && ts.ts_ms_venue_event && ts.ts_ms_local_received && ts.ts_ms_local_received - ts.ts_ms_venue_event > stale_time
|
|
165
172
|
})
|
|
166
173
|
if (stale_event_pair) {
|
|
167
|
-
const ts = _.get(this.market.
|
|
168
|
-
console.error('market websocket stale event data, reconnecting: %s %s (ws_delay: %ds)', this.name, stale_event_pair, Math.round((ts.
|
|
174
|
+
const ts = _.get(this.market.market_ts_by_pair, [stale_event_pair])
|
|
175
|
+
console.error('market websocket stale event data, reconnecting: %s %s (ws_delay: %ds)', this.name, stale_event_pair, Math.round((ts.ts_ms_local_received - ts.ts_ms_venue_event) / 1000))
|
|
169
176
|
this.market.end()
|
|
170
177
|
return
|
|
171
178
|
}
|
|
@@ -219,7 +226,8 @@ module.exports = class ExchangeWs {
|
|
|
219
226
|
let message_handler = (...args) => {
|
|
220
227
|
if (boundSocket !== this.market_socket) return
|
|
221
228
|
this.market.last_message_time = Date.now()
|
|
222
|
-
|
|
229
|
+
const hr_recv = process.hrtime()
|
|
230
|
+
listener(args[0], hr_recv, ...args.slice(1))
|
|
223
231
|
this.market_observable.notify()
|
|
224
232
|
}
|
|
225
233
|
if (this.market_socket.serviceHandlers) {
|
package/lib/exchanges/gate.js
CHANGED
|
@@ -159,8 +159,9 @@ module.exports = class Gate extends ExchangeBase {
|
|
|
159
159
|
}),
|
|
160
160
|
)
|
|
161
161
|
const last_update_dict = {}
|
|
162
|
-
this.ws.market.on_message((data) => {
|
|
162
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
163
163
|
data = JSON.parse(data)
|
|
164
|
+
const hr_parsed = process.hrtime()
|
|
164
165
|
if (data && data.channel === 'spot.pong') {
|
|
165
166
|
this.ws.market.last_pong = new Date()
|
|
166
167
|
} else if (data && data.channel === 'spot.order_book' && data.result && data.result.s && data.result.bids && data.result.asks) {
|
|
@@ -171,10 +172,7 @@ module.exports = class Gate extends ExchangeBase {
|
|
|
171
172
|
this.ws.market.pair_status[pair] = 'opened'
|
|
172
173
|
this.ws.market.asks_dict[pair] = asks.map((ask) => [parseFloat(ask[0]), parseFloat(ask[1])])
|
|
173
174
|
this.ws.market.bids_dict[pair] = bids.map((bid) => [parseFloat(bid[0]), parseFloat(bid[1])])
|
|
174
|
-
this.ws.market.
|
|
175
|
-
received_ts: Date.now(),
|
|
176
|
-
event_ts: parseInt(t),
|
|
177
|
-
}
|
|
175
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: parseInt(t), hr_recv, hr_parsed })
|
|
178
176
|
last_update_dict[pair] = lastUpdateId
|
|
179
177
|
}
|
|
180
178
|
} else if (data && data.channel === 'spot.book_ticker' && data.result && data.result.s) {
|
|
@@ -189,7 +187,7 @@ module.exports = class Gate extends ExchangeBase {
|
|
|
189
187
|
if (options.depth === false) {
|
|
190
188
|
this.ws.market.asks_dict[pair] = [[parseFloat(ask_1_price), parseFloat(ask_1_amount)]]
|
|
191
189
|
this.ws.market.bids_dict[pair] = [[parseFloat(bid_1_price), parseFloat(bid_1_amount)]]
|
|
192
|
-
this.ws.market.
|
|
190
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: parseInt(data.result.t) || Date.now(), hr_recv, hr_parsed })
|
|
193
191
|
this.ws.market.pair_status[pair] = 'opened'
|
|
194
192
|
} else {
|
|
195
193
|
if (!this.ws.market.asks_dict[pair] || !this.ws.market.bids_dict[pair]) {
|
package/lib/exchanges/hashkey.js
CHANGED
|
@@ -108,9 +108,10 @@ module.exports = class Hashkey extends ExchangeBase {
|
|
|
108
108
|
}),
|
|
109
109
|
)
|
|
110
110
|
})
|
|
111
|
-
this.ws.market.on_message((data) => {
|
|
111
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
112
112
|
try {
|
|
113
113
|
data = JSON.parse(data)
|
|
114
|
+
const hr_parsed = process.hrtime()
|
|
114
115
|
if (data.pong) {
|
|
115
116
|
this.ws.market.last_pong = new Date()
|
|
116
117
|
} else {
|
|
@@ -120,7 +121,12 @@ module.exports = class Hashkey extends ExchangeBase {
|
|
|
120
121
|
const { a, b } = data.data
|
|
121
122
|
this.ws.market.asks_dict[pair] = a.map((ask) => [parseFloat(ask[0]), parseFloat(ask[1])])
|
|
122
123
|
this.ws.market.bids_dict[pair] = b.map((bid) => [parseFloat(bid[0]), parseFloat(bid[1])])
|
|
123
|
-
this.ws.market.
|
|
124
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({
|
|
125
|
+
ts_ms_venue_event: parseFloat(data.data.t) || undefined,
|
|
126
|
+
ts_ms_venue_send: parseFloat(data.sendTime) || undefined,
|
|
127
|
+
hr_recv,
|
|
128
|
+
hr_parsed,
|
|
129
|
+
})
|
|
124
130
|
const asks = this.ws.market.asks_dict[pair]
|
|
125
131
|
const bids = this.ws.market.bids_dict[pair]
|
|
126
132
|
if (asks && asks.length > 0 && bids && bids.length > 0) {
|
|
@@ -110,8 +110,9 @@ module.exports = class Hashkeyglobal extends ExchangeBase {
|
|
|
110
110
|
})
|
|
111
111
|
this.ws.market.ping('ping')
|
|
112
112
|
})
|
|
113
|
-
this.ws.market.on_message((data) => {
|
|
113
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
114
114
|
data = JSON.parse(data)
|
|
115
|
+
const hr_parsed = process.hrtime()
|
|
115
116
|
if (data.pong) {
|
|
116
117
|
this.ws.market.last_pong = new Date()
|
|
117
118
|
} else {
|
|
@@ -121,7 +122,7 @@ module.exports = class Hashkeyglobal extends ExchangeBase {
|
|
|
121
122
|
this.ws.market.pair_status[pair] = 'opened'
|
|
122
123
|
this.ws.market.asks_dict[pair] = book[0].a.map((ask) => [parseFloat(ask[0]), parseFloat(ask[1])])
|
|
123
124
|
this.ws.market.bids_dict[pair] = book[0].b.map((bid) => [parseFloat(bid[0]), parseFloat(bid[1])])
|
|
124
|
-
this.ws.market.
|
|
125
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ hr_recv, hr_parsed })
|
|
125
126
|
const asks = this.ws.market.asks_dict[pair]
|
|
126
127
|
const bids = this.ws.market.bids_dict[pair]
|
|
127
128
|
if (asks && asks.length > 0 && bids && bids.length > 0) {
|
package/lib/exchanges/hkbitex.js
CHANGED
|
@@ -142,11 +142,12 @@ module.exports = class Hkbitex extends ExchangeBase {
|
|
|
142
142
|
}, 60 * 1000)
|
|
143
143
|
this.ws.market.ping('ping')
|
|
144
144
|
})
|
|
145
|
-
this.ws.market.on_message((data) => {
|
|
145
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
146
146
|
if (data.toString() === 'pong') {
|
|
147
147
|
this.ws.market.last_pong = new Date()
|
|
148
148
|
} else {
|
|
149
149
|
data = JSON.parse(data)
|
|
150
|
+
const hr_parsed = process.hrtime()
|
|
150
151
|
if (data && data.data && data.data.e === 8) {
|
|
151
152
|
let book = data.data
|
|
152
153
|
let pair = post_process_pair(book.s)
|
|
@@ -157,7 +158,7 @@ module.exports = class Hkbitex extends ExchangeBase {
|
|
|
157
158
|
if (book.d && book.d.b) {
|
|
158
159
|
this.ws.market.bids_dict[pair] = book.d.b.map((bid) => [parseFloat(bid[0]), parseFloat(bid[1])])
|
|
159
160
|
}
|
|
160
|
-
this.ws.market.
|
|
161
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ hr_recv, hr_parsed })
|
|
161
162
|
const asks = this.ws.market.asks_dict[pair]
|
|
162
163
|
const bids = this.ws.market.bids_dict[pair]
|
|
163
164
|
if (asks && asks.length > 0 && bids && bids.length > 0) {
|
package/lib/exchanges/htx.js
CHANGED
|
@@ -120,12 +120,13 @@ module.exports = class Htx extends ExchangeBase {
|
|
|
120
120
|
let depth_re = new RegExp('market.(.+).depth.step0'),
|
|
121
121
|
bbo_re = new RegExp('market.(.+).bbo'),
|
|
122
122
|
trade_re = new RegExp('market.(.+).trade.detail')
|
|
123
|
-
this.ws.market.on_message((data) => {
|
|
123
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
124
124
|
data = parse_body(pako.inflate(new Uint8Array(data), { to: 'string' }))
|
|
125
|
+
const hr_parsed = process.hrtime()
|
|
125
126
|
if (data.ch && depth_re.exec(data.ch)) {
|
|
126
127
|
let pair = post_process_pair(depth_re.exec(data.ch)[1])
|
|
127
128
|
this.ws.market.pair_status[pair] = 'opened'
|
|
128
|
-
this.ws.market.
|
|
129
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: data.ts, hr_recv, hr_parsed })
|
|
129
130
|
this.ws.market.asks_dict[pair] = data.tick.asks
|
|
130
131
|
this.ws.market.bids_dict[pair] = data.tick.bids
|
|
131
132
|
} else if (data.ch && bbo_re.exec(data.ch)) {
|
|
@@ -140,7 +141,7 @@ module.exports = class Htx extends ExchangeBase {
|
|
|
140
141
|
if (options.depth === false) {
|
|
141
142
|
this.ws.market.asks_dict[pair] = [[parseFloat(ask_1_price), parseFloat(ask_1_amount)]]
|
|
142
143
|
this.ws.market.bids_dict[pair] = [[parseFloat(bid_1_price), parseFloat(bid_1_amount)]]
|
|
143
|
-
this.ws.market.
|
|
144
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: data.ts, hr_recv, hr_parsed })
|
|
144
145
|
this.ws.market.pair_status[pair] = 'opened'
|
|
145
146
|
} else {
|
|
146
147
|
if (!Array.isArray(this.ws.market.asks_dict[pair]) || !Array.isArray(this.ws.market.bids_dict[pair])) {
|
package/lib/exchanges/kucoin.js
CHANGED
|
@@ -121,8 +121,9 @@ module.exports = class Kucoin extends ExchangeBase {
|
|
|
121
121
|
type: 'ping',
|
|
122
122
|
}),
|
|
123
123
|
)
|
|
124
|
-
this.ws.market.on_message((data) => {
|
|
124
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
125
125
|
data = JSON.parse(data)
|
|
126
|
+
const hr_parsed = process.hrtime()
|
|
126
127
|
let book = data.data
|
|
127
128
|
if (data.type === 'pong') {
|
|
128
129
|
this.ws.market.last_pong = new Date()
|
|
@@ -132,7 +133,7 @@ module.exports = class Kucoin extends ExchangeBase {
|
|
|
132
133
|
this.ws.market.pair_status[pair] = 'opened'
|
|
133
134
|
this.ws.market.asks_dict[pair] = book.asks.map((ask) => [parseFloat(ask[0]), parseFloat(ask[1])])
|
|
134
135
|
this.ws.market.bids_dict[pair] = book.bids.map((bid) => [parseFloat(bid[0]), parseFloat(bid[1])])
|
|
135
|
-
this.ws.market.
|
|
136
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: book.timestamp, hr_recv, hr_parsed })
|
|
136
137
|
} else if (data && data.type === 'message' && data.topic && data.subject === 'level1' && book && book.timestamp && book.bids && book.asks) {
|
|
137
138
|
let val = data.topic.split(':')
|
|
138
139
|
let pair = post_process_pair(val[1])
|
|
@@ -148,7 +149,7 @@ module.exports = class Kucoin extends ExchangeBase {
|
|
|
148
149
|
if (options.depth === false) {
|
|
149
150
|
this.ws.market.asks_dict[pair] = [[parseFloat(ask_1_price), parseFloat(ask_1_amount)]]
|
|
150
151
|
this.ws.market.bids_dict[pair] = [[parseFloat(bid_1_price), parseFloat(bid_1_amount)]]
|
|
151
|
-
this.ws.market.
|
|
152
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: book.timestamp, hr_recv, hr_parsed })
|
|
152
153
|
this.ws.market.pair_status[pair] = 'opened'
|
|
153
154
|
} else {
|
|
154
155
|
if (!Array.isArray(this.ws.market.asks_dict[pair]) || !Array.isArray(this.ws.market.bids_dict[pair])) {
|
|
@@ -304,7 +305,10 @@ module.exports = class Kucoin extends ExchangeBase {
|
|
|
304
305
|
})
|
|
305
306
|
}
|
|
306
307
|
limit_trade_base(pair, method, price, amount, order_options, cb) {
|
|
307
|
-
if (typeof order_options === 'function') {
|
|
308
|
+
if (typeof order_options === 'function') {
|
|
309
|
+
cb = order_options
|
|
310
|
+
order_options = {}
|
|
311
|
+
}
|
|
308
312
|
if (parseFloat(amount) === 0) {
|
|
309
313
|
cb({ success: false, error: error_codes.amount_too_small })
|
|
310
314
|
return
|
|
@@ -337,7 +341,10 @@ module.exports = class Kucoin extends ExchangeBase {
|
|
|
337
341
|
})
|
|
338
342
|
}
|
|
339
343
|
limit_trade(pair, method, price, amount, order_options, cb) {
|
|
340
|
-
if (typeof order_options === 'function') {
|
|
344
|
+
if (typeof order_options === 'function') {
|
|
345
|
+
cb = order_options
|
|
346
|
+
order_options = {}
|
|
347
|
+
}
|
|
341
348
|
if (parseFloat(amount) === 0) {
|
|
342
349
|
cb({ success: false, error: error_codes.amount_too_small })
|
|
343
350
|
return
|
|
@@ -473,6 +480,7 @@ module.exports = class Kucoin extends ExchangeBase {
|
|
|
473
480
|
.map((t) => {
|
|
474
481
|
return {
|
|
475
482
|
order_id: t.orderId.toString(),
|
|
483
|
+
...(t.tradeId != null ? { trade_id: t.tradeId.toString() } : {}),
|
|
476
484
|
pair,
|
|
477
485
|
type: t.side,
|
|
478
486
|
price: parseFloat(t.price),
|
|
@@ -509,6 +517,7 @@ module.exports = class Kucoin extends ExchangeBase {
|
|
|
509
517
|
.map((t) => {
|
|
510
518
|
return {
|
|
511
519
|
order_id: t.orderId.toString(),
|
|
520
|
+
...(t.tradeId != null ? { trade_id: t.tradeId.toString() } : {}),
|
|
512
521
|
pair: post_process_pair(t.symbol),
|
|
513
522
|
type: t.side,
|
|
514
523
|
price: parseFloat(t.price),
|
|
@@ -549,6 +558,7 @@ module.exports = class Kucoin extends ExchangeBase {
|
|
|
549
558
|
let trades = body.data.items.map((o) => {
|
|
550
559
|
return {
|
|
551
560
|
order_id: o.orderId.toString(),
|
|
561
|
+
...(o.tradeId != null ? { trade_id: o.tradeId.toString() } : {}),
|
|
552
562
|
pair,
|
|
553
563
|
type: o.side,
|
|
554
564
|
price: parseFloat(o.price),
|
|
@@ -694,15 +704,17 @@ module.exports = class Kucoin extends ExchangeBase {
|
|
|
694
704
|
let url = 'https://api.kucoin.com/api/v2/accounts/inner-transfer?' + qs.stringify(params)
|
|
695
705
|
needle.get(url, options, (err, res, body) => {
|
|
696
706
|
if (body && body.data && Array.isArray(body.data.items) && body.code === '200000') {
|
|
697
|
-
let result = body.data.items.map((t) =>
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
707
|
+
let result = body.data.items.map((t) =>
|
|
708
|
+
this.build_transfer_result({
|
|
709
|
+
amount: parseFloat(t.amount),
|
|
710
|
+
currency: post_process_cur(t.currency),
|
|
711
|
+
from: ACCOUNT_TYPE_MAP[t.from] || t.from,
|
|
712
|
+
to: ACCOUNT_TYPE_MAP[t.to] || t.to,
|
|
713
|
+
status: t.status === 'SUCCESS' ? 'SUCCESS' : t.status === 'PENDING' ? 'PENDING' : t.status === 'PROCESSING' ? 'PENDING' : t.status,
|
|
714
|
+
time: new Date(t.createdAt),
|
|
715
|
+
transferId: t.orderId,
|
|
716
|
+
}),
|
|
717
|
+
)
|
|
706
718
|
cb({ success: true, body: result })
|
|
707
719
|
} else if (body && body.code === '429000') {
|
|
708
720
|
cb({ success: false, error: error_codes.service_unavailable, body })
|
package/lib/exchanges/lbank.js
CHANGED
|
@@ -157,8 +157,9 @@ module.exports = class Lbank extends ExchangeBase {
|
|
|
157
157
|
})
|
|
158
158
|
this.ws.market.ping(JSON.stringify({ action: 'ping', ping: 'keepalive' }))
|
|
159
159
|
})
|
|
160
|
-
this.ws.market.on_message((data) => {
|
|
160
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
161
161
|
data = JSON.parse(data)
|
|
162
|
+
const hr_parsed = process.hrtime()
|
|
162
163
|
if (data.action === 'ping') {
|
|
163
164
|
this.ws.market.send(
|
|
164
165
|
JSON.stringify({
|
|
@@ -170,8 +171,13 @@ module.exports = class Lbank extends ExchangeBase {
|
|
|
170
171
|
this.ws.market.last_pong = new Date()
|
|
171
172
|
const now = Date.now()
|
|
172
173
|
pair_list.forEach((pair) => {
|
|
173
|
-
if (this.ws.market.
|
|
174
|
-
this.ws.market.
|
|
174
|
+
if (this.ws.market.market_ts_by_pair[pair]) {
|
|
175
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({
|
|
176
|
+
...this.ws.market.market_ts_by_pair[pair],
|
|
177
|
+
ts_ms_local_received: now,
|
|
178
|
+
hr_recv,
|
|
179
|
+
hr_parsed,
|
|
180
|
+
})
|
|
175
181
|
}
|
|
176
182
|
})
|
|
177
183
|
} else if (data.action === 'pong') {
|
|
@@ -181,7 +187,7 @@ module.exports = class Lbank extends ExchangeBase {
|
|
|
181
187
|
this.ws.market.pair_status[pair] = 'opened'
|
|
182
188
|
this.ws.market.asks_dict[pair] = data.depth.asks.map((ask) => [parseFloat(ask[0]), parseFloat(ask[1])])
|
|
183
189
|
this.ws.market.bids_dict[pair] = data.depth.bids.map((bid) => [parseFloat(bid[0]), parseFloat(bid[1])])
|
|
184
|
-
this.ws.market.
|
|
190
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ hr_recv, hr_parsed })
|
|
185
191
|
const asks = this.ws.market.asks_dict[pair]
|
|
186
192
|
const bids = this.ws.market.bids_dict[pair]
|
|
187
193
|
if (asks && asks.length > 0 && bids && bids.length > 0) {
|
package/lib/exchanges/mexc.js
CHANGED
|
@@ -131,7 +131,7 @@ module.exports = class Mexc extends ExchangeBase {
|
|
|
131
131
|
return false
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
-
this.ws.market.on_message((data) => {
|
|
134
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
135
135
|
if (is_json(data)) {
|
|
136
136
|
try {
|
|
137
137
|
const json = JSON.parse(data.toString())
|
|
@@ -152,6 +152,7 @@ module.exports = class Mexc extends ExchangeBase {
|
|
|
152
152
|
try {
|
|
153
153
|
const message = PushDataV3ApiWrapper.decode(data)
|
|
154
154
|
const obj = PushDataV3ApiWrapper.toObject(message, { enums: String, longs: String })
|
|
155
|
+
const hr_parsed = process.hrtime()
|
|
155
156
|
const pair = post_process_pair(obj.symbol)
|
|
156
157
|
const channel = obj.channel
|
|
157
158
|
if (channel.startsWith('spot@public.limit.depth.v3.api.pb@')) {
|
|
@@ -159,7 +160,7 @@ module.exports = class Mexc extends ExchangeBase {
|
|
|
159
160
|
this.ws.market.pair_status[pair] = 'opened'
|
|
160
161
|
this.ws.market.asks_dict[pair] = asks.map((ask) => [parseFloat(ask.price), parseFloat(ask.quantity)])
|
|
161
162
|
this.ws.market.bids_dict[pair] = bids.map((bid) => [parseFloat(bid.price), parseFloat(bid.quantity)])
|
|
162
|
-
this.ws.market.
|
|
163
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: parseInt(obj.sendTime) || undefined, hr_recv, hr_parsed })
|
|
163
164
|
// Derive BBO from depth data
|
|
164
165
|
if (options.bbo !== false && this.ws.market.asks_dict[pair].length > 0 && this.ws.market.bids_dict[pair].length > 0) {
|
|
165
166
|
this.ws.market.tickers[pair] = {
|
package/lib/exchanges/okx.js
CHANGED
|
@@ -217,13 +217,14 @@ module.exports = class Okx extends ExchangeBase {
|
|
|
217
217
|
this.ws.market.ping('ping')
|
|
218
218
|
start_market_history_socket()
|
|
219
219
|
})
|
|
220
|
-
this.ws.market.on_message((data) => {
|
|
220
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
221
221
|
if (data.toString() === 'pong') {
|
|
222
222
|
this.ws.market.last_pong = new Date()
|
|
223
223
|
}
|
|
224
224
|
if (data.toString() !== 'pong') {
|
|
225
225
|
try {
|
|
226
226
|
data = JSON.parse(data)
|
|
227
|
+
const hr_parsed = process.hrtime()
|
|
227
228
|
if (data && data.arg && data.arg.channel === 'books' && data.data && data.data[0]) {
|
|
228
229
|
const action = data.action
|
|
229
230
|
const pair = post_process_pair(data.arg.instId, true)
|
|
@@ -262,7 +263,7 @@ module.exports = class Okx extends ExchangeBase {
|
|
|
262
263
|
} else {
|
|
263
264
|
}
|
|
264
265
|
}
|
|
265
|
-
this.ws.market.
|
|
266
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: parseInt(data.data[0].ts), hr_recv, hr_parsed })
|
|
266
267
|
} else if (data && data.data && data.arg && (data.arg.channel === 'trades' || data.arg.channel === 'trades-all')) {
|
|
267
268
|
for (let datum of data.data) {
|
|
268
269
|
let pair = post_process_pair(datum.instId)
|
|
@@ -299,7 +300,7 @@ module.exports = class Okx extends ExchangeBase {
|
|
|
299
300
|
if (options.depth === false) {
|
|
300
301
|
this.ws.market.asks_dict[pair] = [[askPrice, askSize]]
|
|
301
302
|
this.ws.market.bids_dict[pair] = [[bidPrice, bidSize]]
|
|
302
|
-
this.ws.market.
|
|
303
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: parseInt(bbo.ts), hr_recv, hr_parsed })
|
|
303
304
|
this.ws.market.pair_status[pair] = 'opened'
|
|
304
305
|
}
|
|
305
306
|
this.ws.bbo_observable.notify()
|
package/lib/exchanges/phemex.js
CHANGED
|
@@ -773,8 +773,9 @@ module.exports = class Phemex extends ExchangeBase {
|
|
|
773
773
|
}),
|
|
774
774
|
)
|
|
775
775
|
})
|
|
776
|
-
this.ws.market.on_message((data) => {
|
|
776
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
777
777
|
data = JSON.parse(data)
|
|
778
|
+
const hr_parsed = process.hrtime()
|
|
778
779
|
if (data && data.book && data.type === 'snapshot') {
|
|
779
780
|
let pair = post_process_pair(data.symbol)
|
|
780
781
|
this.ws.market.pair_status[pair] = 'opened'
|
|
@@ -784,7 +785,11 @@ module.exports = class Phemex extends ExchangeBase {
|
|
|
784
785
|
for (let bid of data.book.bids) {
|
|
785
786
|
this.ws.market.bids_dict[pair].insert(bid[0], bid[1])
|
|
786
787
|
}
|
|
787
|
-
this.ws.market.
|
|
788
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({
|
|
789
|
+
ts_ms_venue_event: data.timestamp ? Math.floor(data.timestamp / 1e6) : undefined,
|
|
790
|
+
hr_recv,
|
|
791
|
+
hr_parsed,
|
|
792
|
+
})
|
|
788
793
|
{
|
|
789
794
|
const _asks = this.ws.market.asks_dict[pair].entries()
|
|
790
795
|
const _bids = this.ws.market.bids_dict[pair].entries()
|
|
@@ -812,7 +817,11 @@ module.exports = class Phemex extends ExchangeBase {
|
|
|
812
817
|
this.ws.market.bids_dict[pair].insert(parseFloat(bid[0]), parseFloat(bid[1]))
|
|
813
818
|
}
|
|
814
819
|
}
|
|
815
|
-
this.ws.market.
|
|
820
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({
|
|
821
|
+
ts_ms_venue_event: data.timestamp ? Math.floor(data.timestamp / 1e6) : undefined,
|
|
822
|
+
hr_recv,
|
|
823
|
+
hr_parsed,
|
|
824
|
+
})
|
|
816
825
|
{
|
|
817
826
|
const _asks = this.ws.market.asks_dict[pair].entries()
|
|
818
827
|
const _bids = this.ws.market.bids_dict[pair].entries()
|
package/lib/exchanges/upbit.js
CHANGED
|
@@ -97,17 +97,18 @@ module.exports = class Upbit extends ExchangeBase {
|
|
|
97
97
|
let message = JSON.stringify([JSON.stringify(sub_channels).toString()])
|
|
98
98
|
this.ws.market.send(message)
|
|
99
99
|
})
|
|
100
|
-
this.ws.market.on_message((data) => {
|
|
100
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
101
101
|
if (data.startsWith('a')) {
|
|
102
102
|
data = _.trim(data, 'a')
|
|
103
103
|
data = JSON.parse(JSON.parse(data)[0])
|
|
104
104
|
}
|
|
105
|
+
const hr_parsed = process.hrtime()
|
|
105
106
|
if (data.type === 'crixOrderbook') {
|
|
106
107
|
let pair = post_process_pair(_.split(data.code, '.')[2])
|
|
107
108
|
this.ws.market.pair_status[pair] = 'opened'
|
|
108
109
|
this.ws.market.asks_dict[pair] = data.orderbookUnits.map((o) => [parseFloat(o.askPrice), parseFloat(o.askSize)])
|
|
109
110
|
this.ws.market.bids_dict[pair] = data.orderbookUnits.map((o) => [parseFloat(o.bidPrice), parseFloat(o.bidSize)])
|
|
110
|
-
this.ws.market.
|
|
111
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ ts_ms_venue_event: data.timestamp, hr_recv, hr_parsed })
|
|
111
112
|
const asks = this.ws.market.asks_dict[pair]
|
|
112
113
|
const bids = this.ws.market.bids_dict[pair]
|
|
113
114
|
if (asks && asks.length > 0 && bids && bids.length > 0) {
|
package/lib/exchanges/xt.js
CHANGED
|
@@ -122,12 +122,14 @@ module.exports = class Xt extends ExchangeBase {
|
|
|
122
122
|
})
|
|
123
123
|
this.ws.market.ping('ping')
|
|
124
124
|
})
|
|
125
|
-
this.ws.market.on_message((data) => {
|
|
125
|
+
this.ws.market.on_message((data, hr_recv) => {
|
|
126
126
|
if (data.toString() === 'pong') {
|
|
127
127
|
this.ws.market.last_pong = new Date()
|
|
128
128
|
} else {
|
|
129
|
+
let hr_parsed
|
|
129
130
|
try {
|
|
130
131
|
data = JSON.parse(data)
|
|
132
|
+
hr_parsed = process.hrtime()
|
|
131
133
|
} catch (err) {
|
|
132
134
|
console.error('xt websocket', data.toString(), err)
|
|
133
135
|
}
|
|
@@ -137,7 +139,7 @@ module.exports = class Xt extends ExchangeBase {
|
|
|
137
139
|
this.ws.market.pair_status[pair] = 'opened'
|
|
138
140
|
this.ws.market.asks_dict[pair] = data.data.a.map((ask) => [parseFloat(ask[0]), parseFloat(ask[1])])
|
|
139
141
|
this.ws.market.bids_dict[pair] = data.data.b.map((bid) => [parseFloat(bid[0]), parseFloat(bid[1])])
|
|
140
|
-
this.ws.market.
|
|
142
|
+
this.ws.market.market_ts_by_pair[pair] = this.ws.market.build_ts({ hr_recv, hr_parsed })
|
|
141
143
|
const asks = this.ws.market.asks_dict[pair]
|
|
142
144
|
const bids = this.ws.market.bids_dict[pair]
|
|
143
145
|
if (asks && asks.length > 0 && bids && bids.length > 0) {
|