tardis-dev 16.5.1 → 16.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/consts.d.ts +3 -1
- package/dist/consts.d.ts.map +1 -1
- package/dist/consts.js +21 -1
- package/dist/consts.js.map +1 -1
- package/dist/handy.d.ts.map +1 -1
- package/dist/handy.js +6 -3
- package/dist/handy.js.map +1 -1
- package/dist/mappers/ascendex.d.ts +6 -99
- package/dist/mappers/ascendex.d.ts.map +1 -1
- package/dist/mappers/ascendex.js +13 -4
- package/dist/mappers/ascendex.js.map +1 -1
- package/dist/mappers/binance.d.ts +27 -154
- package/dist/mappers/binance.d.ts.map +1 -1
- package/dist/mappers/binance.js +49 -15
- package/dist/mappers/binance.js.map +1 -1
- package/dist/mappers/binancedex.d.ts +7 -4
- package/dist/mappers/binancedex.d.ts.map +1 -1
- package/dist/mappers/binancedex.js +11 -3
- package/dist/mappers/binancedex.js.map +1 -1
- package/dist/mappers/binanceeuropeanoptions.d.ts +6 -216
- package/dist/mappers/binanceeuropeanoptions.d.ts.map +1 -1
- package/dist/mappers/binanceeuropeanoptions.js +26 -7
- package/dist/mappers/binanceeuropeanoptions.js.map +1 -1
- package/dist/mappers/bitfinex.d.ts +15 -100
- package/dist/mappers/bitfinex.d.ts.map +1 -1
- package/dist/mappers/bitfinex.js +20 -5
- package/dist/mappers/bitfinex.js.map +1 -1
- package/dist/mappers/bitflyer.d.ts +5 -41
- package/dist/mappers/bitflyer.d.ts.map +1 -1
- package/dist/mappers/bitflyer.js +11 -3
- package/dist/mappers/bitflyer.js.map +1 -1
- package/dist/mappers/bitget.d.ts +12 -269
- package/dist/mappers/bitget.d.ts.map +1 -1
- package/dist/mappers/bitget.js +46 -9
- package/dist/mappers/bitget.js.map +1 -1
- package/dist/mappers/bitmex.d.ts +8 -56
- package/dist/mappers/bitmex.d.ts.map +1 -1
- package/dist/mappers/bitmex.js +15 -5
- package/dist/mappers/bitmex.js.map +1 -1
- package/dist/mappers/bitnomial.d.ts +4 -44
- package/dist/mappers/bitnomial.d.ts.map +1 -1
- package/dist/mappers/bitnomial.js +9 -2
- package/dist/mappers/bitnomial.js.map +1 -1
- package/dist/mappers/bitstamp.d.ts +4 -44
- package/dist/mappers/bitstamp.d.ts.map +1 -1
- package/dist/mappers/bitstamp.js +9 -2
- package/dist/mappers/bitstamp.js.map +1 -1
- package/dist/mappers/blockchaincom.d.ts +4 -63
- package/dist/mappers/blockchaincom.d.ts.map +1 -1
- package/dist/mappers/blockchaincom.js +9 -2
- package/dist/mappers/blockchaincom.js.map +1 -1
- package/dist/mappers/bullish.d.ts +8 -158
- package/dist/mappers/bullish.d.ts.map +1 -1
- package/dist/mappers/bullish.js +15 -5
- package/dist/mappers/bullish.js.map +1 -1
- package/dist/mappers/bybit.d.ts +61 -407
- package/dist/mappers/bybit.d.ts.map +1 -1
- package/dist/mappers/bybit.js +60 -15
- package/dist/mappers/bybit.js.map +1 -1
- package/dist/mappers/bybitspot.d.ts +1 -1
- package/dist/mappers/coinbase.d.ts +6 -39
- package/dist/mappers/coinbase.d.ts.map +1 -1
- package/dist/mappers/coinbase.js +11 -3
- package/dist/mappers/coinbase.js.map +1 -1
- package/dist/mappers/coinbaseinternational.d.ts +7 -78
- package/dist/mappers/coinbaseinternational.d.ts.map +1 -1
- package/dist/mappers/coinbaseinternational.js +13 -4
- package/dist/mappers/coinbaseinternational.js.map +1 -1
- package/dist/mappers/coinflex.d.ts +6 -24
- package/dist/mappers/coinflex.d.ts.map +1 -1
- package/dist/mappers/coinflex.js +11 -3
- package/dist/mappers/coinflex.js.map +1 -1
- package/dist/mappers/cryptocom.d.ts +22 -328
- package/dist/mappers/cryptocom.d.ts.map +1 -1
- package/dist/mappers/cryptocom.js +13 -4
- package/dist/mappers/cryptocom.js.map +1 -1
- package/dist/mappers/cryptofacilities.d.ts +8 -96
- package/dist/mappers/cryptofacilities.d.ts.map +1 -1
- package/dist/mappers/cryptofacilities.js +24 -7
- package/dist/mappers/cryptofacilities.js.map +1 -1
- package/dist/mappers/delta.d.ts +7 -114
- package/dist/mappers/delta.d.ts.map +1 -1
- package/dist/mappers/delta.js +30 -10
- package/dist/mappers/delta.js.map +1 -1
- package/dist/mappers/deribit.d.ts +8 -66
- package/dist/mappers/deribit.d.ts.map +1 -1
- package/dist/mappers/deribit.js +17 -6
- package/dist/mappers/deribit.js.map +1 -1
- package/dist/mappers/dydx.d.ts +5 -146
- package/dist/mappers/dydx.d.ts.map +1 -1
- package/dist/mappers/dydx.js +11 -3
- package/dist/mappers/dydx.js.map +1 -1
- package/dist/mappers/dydxv4.d.ts +6 -173
- package/dist/mappers/dydxv4.d.ts.map +1 -1
- package/dist/mappers/dydxv4.js +13 -4
- package/dist/mappers/dydxv4.js.map +1 -1
- package/dist/mappers/ftx.d.ts +12 -107
- package/dist/mappers/ftx.d.ts.map +1 -1
- package/dist/mappers/ftx.js +21 -6
- package/dist/mappers/ftx.js.map +1 -1
- package/dist/mappers/gateio.d.ts +6 -191
- package/dist/mappers/gateio.d.ts.map +1 -1
- package/dist/mappers/gateio.js +28 -8
- package/dist/mappers/gateio.js.map +1 -1
- package/dist/mappers/gateiofutures.d.ts +7 -122
- package/dist/mappers/gateiofutures.d.ts.map +1 -1
- package/dist/mappers/gateiofutures.js +13 -4
- package/dist/mappers/gateiofutures.js.map +1 -1
- package/dist/mappers/gemini.d.ts +6 -3
- package/dist/mappers/gemini.d.ts.map +1 -1
- package/dist/mappers/gemini.js +9 -2
- package/dist/mappers/gemini.js.map +1 -1
- package/dist/mappers/hitbtc.d.ts +6 -3
- package/dist/mappers/hitbtc.d.ts.map +1 -1
- package/dist/mappers/hitbtc.js +9 -2
- package/dist/mappers/hitbtc.js.map +1 -1
- package/dist/mappers/huobi.d.ts +118 -289
- package/dist/mappers/huobi.d.ts.map +1 -1
- package/dist/mappers/huobi.js +45 -7
- package/dist/mappers/huobi.js.map +1 -1
- package/dist/mappers/hyperliquid.d.ts +6 -86
- package/dist/mappers/hyperliquid.d.ts.map +1 -1
- package/dist/mappers/hyperliquid.js +13 -4
- package/dist/mappers/hyperliquid.js.map +1 -1
- package/dist/mappers/index.d.ts +784 -272
- package/dist/mappers/index.d.ts.map +1 -1
- package/dist/mappers/index.js +64 -432
- package/dist/mappers/index.js.map +1 -1
- package/dist/mappers/kraken.d.ts +7 -4
- package/dist/mappers/kraken.d.ts.map +1 -1
- package/dist/mappers/kraken.js +11 -3
- package/dist/mappers/kraken.js.map +1 -1
- package/dist/mappers/kucoin.d.ts +6 -122
- package/dist/mappers/kucoin.d.ts.map +1 -1
- package/dist/mappers/kucoin.js +13 -5
- package/dist/mappers/kucoin.js.map +1 -1
- package/dist/mappers/kucoinfutures.d.ts +6 -207
- package/dist/mappers/kucoinfutures.d.ts.map +1 -1
- package/dist/mappers/kucoinfutures.js +15 -6
- package/dist/mappers/kucoinfutures.js.map +1 -1
- package/dist/mappers/lighter.d.ts +8 -156
- package/dist/mappers/lighter.d.ts.map +1 -1
- package/dist/mappers/lighter.js +15 -5
- package/dist/mappers/lighter.js.map +1 -1
- package/dist/mappers/mapper.d.ts +3 -1
- package/dist/mappers/mapper.d.ts.map +1 -1
- package/dist/mappers/mapper.js.map +1 -1
- package/dist/mappers/mexc.d.ts +122 -0
- package/dist/mappers/mexc.d.ts.map +1 -0
- package/dist/mappers/mexc.js +205 -0
- package/dist/mappers/mexc.js.map +1 -0
- package/dist/mappers/mexcfutures.d.ts +137 -0
- package/dist/mappers/mexcfutures.d.ts.map +1 -0
- package/dist/mappers/mexcfutures.js +225 -0
- package/dist/mappers/mexcfutures.js.map +1 -0
- package/dist/mappers/okex.d.ts +36 -473
- package/dist/mappers/okex.d.ts.map +1 -1
- package/dist/mappers/okex.js +143 -37
- package/dist/mappers/okex.js.map +1 -1
- package/dist/mappers/phemex.d.ts +5 -82
- package/dist/mappers/phemex.d.ts.map +1 -1
- package/dist/mappers/phemex.js +22 -4
- package/dist/mappers/phemex.js.map +1 -1
- package/dist/mappers/poloniex.d.ts +5 -90
- package/dist/mappers/poloniex.d.ts.map +1 -1
- package/dist/mappers/poloniex.js +18 -4
- package/dist/mappers/poloniex.js.map +1 -1
- package/dist/mappers/polymarket.d.ts +7 -31
- package/dist/mappers/polymarket.d.ts.map +1 -1
- package/dist/mappers/polymarket.js +11 -3
- package/dist/mappers/polymarket.js.map +1 -1
- package/dist/mappers/registry.d.ts +41 -0
- package/dist/mappers/registry.d.ts.map +1 -0
- package/dist/mappers/registry.js +111 -0
- package/dist/mappers/registry.js.map +1 -0
- package/dist/mappers/serum.d.ts +16 -89
- package/dist/mappers/serum.d.ts.map +1 -1
- package/dist/mappers/serum.js +21 -3
- package/dist/mappers/serum.js.map +1 -1
- package/dist/mappers/upbit.d.ts +5 -47
- package/dist/mappers/upbit.d.ts.map +1 -1
- package/dist/mappers/upbit.js +9 -2
- package/dist/mappers/upbit.js.map +1 -1
- package/dist/mappers/woox.d.ts +6 -107
- package/dist/mappers/woox.d.ts.map +1 -1
- package/dist/mappers/woox.js +13 -4
- package/dist/mappers/woox.js.map +1 -1
- package/dist/okexsymbols.d.ts +3 -0
- package/dist/okexsymbols.d.ts.map +1 -0
- package/dist/okexsymbols.js +10 -0
- package/dist/okexsymbols.js.map +1 -0
- package/dist/realtimefeeds/hyperliquid.d.ts.map +1 -1
- package/dist/realtimefeeds/hyperliquid.js +3 -1
- package/dist/realtimefeeds/hyperliquid.js.map +1 -1
- package/dist/realtimefeeds/index.d.ts.map +1 -1
- package/dist/realtimefeeds/index.js +4 -0
- package/dist/realtimefeeds/index.js.map +1 -1
- package/dist/realtimefeeds/mexc.d.ts +35 -0
- package/dist/realtimefeeds/mexc.d.ts.map +1 -0
- package/dist/realtimefeeds/mexc.js +261 -0
- package/dist/realtimefeeds/mexc.js.map +1 -0
- package/dist/realtimefeeds/mexcfutures.d.ts +25 -0
- package/dist/realtimefeeds/mexcfutures.d.ts.map +1 -0
- package/dist/realtimefeeds/mexcfutures.js +203 -0
- package/dist/realtimefeeds/mexcfutures.js.map +1 -0
- package/dist/realtimefeeds/okex.d.ts +1 -0
- package/dist/realtimefeeds/okex.d.ts.map +1 -1
- package/dist/realtimefeeds/okex.js +7 -5
- package/dist/realtimefeeds/okex.js.map +1 -1
- package/dist/realtimefeeds/realtimefeed.d.ts +1 -0
- package/dist/realtimefeeds/realtimefeed.d.ts.map +1 -1
- package/dist/realtimefeeds/realtimefeed.js +4 -1
- package/dist/realtimefeeds/realtimefeed.js.map +1 -1
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +62 -13
- package/dist/replay.js.map +1 -1
- package/example.js +133 -31
- package/package.json +2 -1
- package/src/consts.ts +22 -1
- package/src/handy.ts +6 -3
- package/src/mappers/ascendex.ts +14 -4
- package/src/mappers/binance.ts +61 -21
- package/src/mappers/binancedex.ts +12 -3
- package/src/mappers/binanceeuropeanoptions.ts +28 -7
- package/src/mappers/bitfinex.ts +21 -5
- package/src/mappers/bitflyer.ts +12 -3
- package/src/mappers/bitget.ts +48 -9
- package/src/mappers/bitmex.ts +16 -5
- package/src/mappers/bitnomial.ts +10 -2
- package/src/mappers/bitstamp.ts +10 -2
- package/src/mappers/blockchaincom.ts +10 -2
- package/src/mappers/bullish.ts +16 -5
- package/src/mappers/bybit.ts +70 -15
- package/src/mappers/coinbase.ts +12 -3
- package/src/mappers/coinbaseinternational.ts +14 -4
- package/src/mappers/coinflex.ts +12 -3
- package/src/mappers/cryptocom.ts +14 -4
- package/src/mappers/cryptofacilities.ts +30 -6
- package/src/mappers/delta.ts +41 -7
- package/src/mappers/deribit.ts +18 -6
- package/src/mappers/dydx.ts +12 -3
- package/src/mappers/dydxv4.ts +14 -4
- package/src/mappers/ftx.ts +22 -6
- package/src/mappers/gateio.ts +34 -8
- package/src/mappers/gateiofutures.ts +14 -4
- package/src/mappers/gemini.ts +10 -2
- package/src/mappers/hitbtc.ts +10 -2
- package/src/mappers/huobi.ts +47 -10
- package/src/mappers/hyperliquid.ts +14 -4
- package/src/mappers/index.ts +133 -679
- package/src/mappers/kraken.ts +12 -3
- package/src/mappers/kucoin.ts +19 -5
- package/src/mappers/kucoinfutures.ts +20 -5
- package/src/mappers/lighter.ts +16 -5
- package/src/mappers/mapper.ts +3 -1
- package/src/mappers/mexc.ts +324 -0
- package/src/mappers/mexcfutures.ts +352 -0
- package/src/mappers/okex.ts +178 -37
- package/src/mappers/phemex.ts +25 -4
- package/src/mappers/poloniex.ts +20 -4
- package/src/mappers/polymarket.ts +12 -3
- package/src/mappers/registry.ts +203 -0
- package/src/mappers/serum.ts +22 -3
- package/src/mappers/upbit.ts +10 -2
- package/src/mappers/woox.ts +14 -4
- package/src/okexsymbols.ts +10 -0
- package/src/realtimefeeds/hyperliquid.ts +4 -1
- package/src/realtimefeeds/index.ts +4 -0
- package/src/realtimefeeds/mexc.ts +327 -0
- package/src/realtimefeeds/mexcfutures.ts +269 -0
- package/src/realtimefeeds/okex.ts +7 -5
- package/src/realtimefeeds/realtimefeed.ts +5 -1
- package/src/replay.ts +73 -15
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import protobuf from 'protobufjs'
|
|
2
|
+
import { CircularBuffer, getJSON, wait } from '../handy.ts'
|
|
3
|
+
import type { MexcDepthSnapshotMessage } from '../mappers/mexc.ts'
|
|
4
|
+
import { Filter } from '../types.ts'
|
|
5
|
+
import { RealTimeFeedBase } from './realtimefeed.ts'
|
|
6
|
+
|
|
7
|
+
export class MexcRealTimeFeed extends RealTimeFeedBase {
|
|
8
|
+
private static readonly depthChannel = 'spot@public.aggre.depth.v3.api.pb@10ms'
|
|
9
|
+
private static readonly depthSnapshotChannel = 'spot@public.aggre.depth.snapshot.v3.api.pb@10ms'
|
|
10
|
+
private static readonly jsonObjectStart = '{'.charCodeAt(0)
|
|
11
|
+
private static pushDataV3ApiWrapper: protobuf.Type | undefined
|
|
12
|
+
/**
|
|
13
|
+
* MEXC protobuf docs at @see https://www.mexc.com/api-docs/spot-v3/websocket-market-streams/protocol-buffers-integration
|
|
14
|
+
* and schemas from @see https://github.com/mexcdevelop/websocket-proto, mirrored in tardis-recorder/datafeeds/src/mexc/proto.
|
|
15
|
+
*/
|
|
16
|
+
private static readonly pushDataV3ApiWrapperSchema = `
|
|
17
|
+
syntax = "proto3";
|
|
18
|
+
|
|
19
|
+
message PushDataV3ApiWrapper {
|
|
20
|
+
string channel = 1;
|
|
21
|
+
oneof body {
|
|
22
|
+
PublicAggreDepthsV3Api publicAggreDepths = 313;
|
|
23
|
+
PublicAggreDealsV3Api publicAggreDeals = 314;
|
|
24
|
+
PublicAggreBookTickerV3Api publicAggreBookTicker = 315;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
optional string symbol = 3;
|
|
28
|
+
optional string symbolId = 4;
|
|
29
|
+
optional int64 createTime = 5;
|
|
30
|
+
optional int64 sendTime = 6;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
message PublicAggreDealsV3Api {
|
|
34
|
+
repeated PublicAggreDealsV3ApiItem deals = 1;
|
|
35
|
+
string eventType = 2;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
message PublicAggreDealsV3ApiItem {
|
|
39
|
+
string price = 1;
|
|
40
|
+
string quantity = 2;
|
|
41
|
+
int32 tradeType = 3;
|
|
42
|
+
int64 time = 4;
|
|
43
|
+
string tradeId = 5;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
message PublicAggreDepthsV3Api {
|
|
47
|
+
repeated PublicAggreDepthV3ApiItem asks = 1;
|
|
48
|
+
repeated PublicAggreDepthV3ApiItem bids = 2;
|
|
49
|
+
string eventType = 3;
|
|
50
|
+
string fromVersion = 4;
|
|
51
|
+
string toVersion = 5;
|
|
52
|
+
int64 lastOrderCreateTime = 6;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
message PublicAggreDepthV3ApiItem {
|
|
56
|
+
string price = 1;
|
|
57
|
+
string quantity = 2;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
message PublicAggreBookTickerV3Api {
|
|
61
|
+
string bidPrice = 1;
|
|
62
|
+
string bidQuantity = 2;
|
|
63
|
+
string askPrice = 3;
|
|
64
|
+
string askQuantity = 4;
|
|
65
|
+
string version = 5;
|
|
66
|
+
int64 lastOrderCreateTime = 6;
|
|
67
|
+
}
|
|
68
|
+
`
|
|
69
|
+
|
|
70
|
+
private readonly pendingDepthSnapshotSymbols = new Set<string>()
|
|
71
|
+
private readonly bufferedDepthUpdates = new Map<string, CircularBuffer<MexcDepthUpdateData>>()
|
|
72
|
+
protected readonly wssURL = 'wss://wbs-api.mexc.com/ws'
|
|
73
|
+
protected readonly httpURL: string = 'https://api.mexc.com'
|
|
74
|
+
private readonly channels = new Set([
|
|
75
|
+
'spot@public.aggre.deals.v3.api.pb@10ms',
|
|
76
|
+
MexcRealTimeFeed.depthChannel,
|
|
77
|
+
MexcRealTimeFeed.depthSnapshotChannel,
|
|
78
|
+
'spot@public.aggre.bookTicker.v3.api.pb@10ms'
|
|
79
|
+
])
|
|
80
|
+
|
|
81
|
+
protected override mapToSubscribeMessages(filters: Filter<string>[]): any[] {
|
|
82
|
+
const filtersWithSymbols = filters.map<Required<Filter<string>>>((filter) => {
|
|
83
|
+
if (!this.channels.has(filter.channel)) {
|
|
84
|
+
throw new Error(`MexcRealTimeFeed unsupported channel ${filter.channel}`)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!filter.symbols || filter.symbols.length === 0) {
|
|
88
|
+
throw new Error('MexcRealTimeFeed requires explicitly specified symbols when subscribing to live feed')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return filter as Required<Filter<string>>
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const depthSnapshotFilters = filtersWithSymbols.filter((filter) => filter.channel === MexcRealTimeFeed.depthSnapshotChannel)
|
|
95
|
+
this.validateDepthSnapshotFilters(filtersWithSymbols, depthSnapshotFilters)
|
|
96
|
+
this.resetDepthSnapshotTracking(depthSnapshotFilters)
|
|
97
|
+
|
|
98
|
+
return [
|
|
99
|
+
{
|
|
100
|
+
method: 'SUBSCRIPTION',
|
|
101
|
+
params: filtersWithSymbols.flatMap((filter) =>
|
|
102
|
+
filter.channel === MexcRealTimeFeed.depthSnapshotChannel
|
|
103
|
+
? []
|
|
104
|
+
: filter.symbols.map((symbol) => `${filter.channel}@${symbol.toUpperCase()}`)
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
protected override parseMessage(message: Buffer<ArrayBufferLike>): any {
|
|
111
|
+
const buffer = Buffer.isBuffer(message) ? message : Buffer.from(message)
|
|
112
|
+
|
|
113
|
+
if (buffer.length > 0 && buffer[0] === MexcRealTimeFeed.jsonObjectStart) {
|
|
114
|
+
return JSON.parse(buffer.toString())
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const pushDataV3ApiWrapper = this.getPushDataV3ApiWrapper()
|
|
118
|
+
return pushDataV3ApiWrapper.toObject(pushDataV3ApiWrapper.decode(buffer), {
|
|
119
|
+
longs: String,
|
|
120
|
+
arrays: true
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
protected override messageIsError(message: any): boolean {
|
|
125
|
+
return message.code !== undefined && message.code !== 0
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
protected override messageIsHeartbeat(message: any) {
|
|
129
|
+
return message.msg === 'PONG'
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
protected override onMessage(message: any) {
|
|
133
|
+
if (
|
|
134
|
+
message.channel?.startsWith(MexcRealTimeFeed.depthChannel) !== true ||
|
|
135
|
+
message.symbol === undefined ||
|
|
136
|
+
this.pendingDepthSnapshotSymbols.has(message.symbol) === false
|
|
137
|
+
) {
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const firstVersion = Number(message.publicAggreDepths?.fromVersion)
|
|
142
|
+
const lastVersion = Number(message.publicAggreDepths?.toVersion)
|
|
143
|
+
if (Number.isFinite(firstVersion) === false || Number.isFinite(lastVersion) === false) {
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const bufferedUpdates = this.bufferedDepthUpdates.get(message.symbol) ?? new CircularBuffer<MexcDepthUpdateData>(2000)
|
|
148
|
+
bufferedUpdates.append({ firstVersion, lastVersion })
|
|
149
|
+
this.bufferedDepthUpdates.set(message.symbol, bufferedUpdates)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
protected override async provideManualSnapshots(filters: Filter<string>[], shouldCancel: () => boolean) {
|
|
153
|
+
const depthFilter = filters.find((filter) => filter.channel === MexcRealTimeFeed.depthSnapshotChannel)
|
|
154
|
+
if (depthFilter === undefined) {
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.debug('requesting manual snapshots for: %s', depthFilter.symbols!)
|
|
159
|
+
|
|
160
|
+
for (const symbol of depthFilter.symbols!) {
|
|
161
|
+
await this.provideManualSnapshot(symbol.toUpperCase(), shouldCancel)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this.debug('requested manual snapshots successfully for: %s ', depthFilter.symbols!)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
protected sendCustomPing = () => {
|
|
168
|
+
this.send({ method: 'PING' })
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private getPushDataV3ApiWrapper() {
|
|
172
|
+
MexcRealTimeFeed.pushDataV3ApiWrapper ??= protobuf
|
|
173
|
+
.parse(MexcRealTimeFeed.pushDataV3ApiWrapperSchema)
|
|
174
|
+
.root.lookupType('PushDataV3ApiWrapper')
|
|
175
|
+
|
|
176
|
+
return MexcRealTimeFeed.pushDataV3ApiWrapper
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private resetDepthSnapshotTracking(filters: Required<Filter<string>>[]) {
|
|
180
|
+
this.pendingDepthSnapshotSymbols.clear()
|
|
181
|
+
this.bufferedDepthUpdates.clear()
|
|
182
|
+
|
|
183
|
+
for (const filter of filters) {
|
|
184
|
+
for (const symbol of filter.symbols) {
|
|
185
|
+
const upperCaseSymbol = symbol.toUpperCase()
|
|
186
|
+
this.pendingDepthSnapshotSymbols.add(upperCaseSymbol)
|
|
187
|
+
this.bufferedDepthUpdates.set(upperCaseSymbol, new CircularBuffer<MexcDepthUpdateData>(2000))
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private validateDepthSnapshotFilters(filters: Required<Filter<string>>[], depthSnapshotFilters: Required<Filter<string>>[]) {
|
|
193
|
+
if (depthSnapshotFilters.length === 0) {
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const depthSymbols = new Set(
|
|
198
|
+
filters
|
|
199
|
+
.filter((filter) => filter.channel === MexcRealTimeFeed.depthChannel)
|
|
200
|
+
.flatMap((filter) => filter.symbols.map((symbol) => symbol.toUpperCase()))
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
for (const filter of depthSnapshotFilters) {
|
|
204
|
+
for (const symbol of filter.symbols) {
|
|
205
|
+
if (depthSymbols.has(symbol.toUpperCase()) === false) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
`MexcRealTimeFeed requires ${MexcRealTimeFeed.depthChannel} for every ${MexcRealTimeFeed.depthSnapshotChannel} symbol`
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private async provideManualSnapshot(symbol: string, shouldCancel: () => boolean) {
|
|
215
|
+
const maxSnapshotRounds = 4
|
|
216
|
+
const maxSnapshotAttemptsPerRound = 3
|
|
217
|
+
|
|
218
|
+
for (let round = 0; round < maxSnapshotRounds; round++) {
|
|
219
|
+
for (let attempt = 1; attempt <= maxSnapshotAttemptsPerRound; attempt++) {
|
|
220
|
+
if (shouldCancel()) {
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const { data } = await getJSON<MexcDepthSnapshotResponse>(`${this.httpURL}/api/v3/depth?symbol=${symbol}&limit=5000`)
|
|
225
|
+
if (this.snapshotResponseIsValid(data) === false) {
|
|
226
|
+
if (attempt < maxSnapshotAttemptsPerRound) {
|
|
227
|
+
await wait(attempt * 1000)
|
|
228
|
+
}
|
|
229
|
+
continue
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const hasOverlap = await this.waitForSnapshotOverlap(symbol, data.lastUpdateId)
|
|
233
|
+
|
|
234
|
+
if (shouldCancel()) {
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (hasOverlap === false) {
|
|
239
|
+
this.trimBufferedUpdates(symbol)
|
|
240
|
+
if (attempt < maxSnapshotAttemptsPerRound) {
|
|
241
|
+
await wait(attempt * 1000)
|
|
242
|
+
}
|
|
243
|
+
continue
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (hasOverlap === true || attempt === maxSnapshotAttemptsPerRound) {
|
|
247
|
+
this.manualSnapshotsBuffer.push(this.createManualSnapshot(symbol, data))
|
|
248
|
+
this.pendingDepthSnapshotSymbols.delete(symbol)
|
|
249
|
+
this.bufferedDepthUpdates.delete(symbol)
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
throw new Error(`MexcRealTimeFeed could not align depth snapshot for ${symbol}`)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private async waitForSnapshotOverlap(symbol: string, lastUpdateId: number) {
|
|
259
|
+
let hasOverlap = this.validateSnapshotOverlap(symbol, lastUpdateId)
|
|
260
|
+
for (let attempt = 0; attempt < 60; attempt++) {
|
|
261
|
+
if (hasOverlap !== undefined) {
|
|
262
|
+
return hasOverlap
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
await wait(100)
|
|
266
|
+
hasOverlap = this.validateSnapshotOverlap(symbol, lastUpdateId)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return hasOverlap
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private validateSnapshotOverlap(symbol: string, lastUpdateId: number) {
|
|
273
|
+
const bufferedUpdates = this.bufferedDepthUpdates.get(symbol)
|
|
274
|
+
for (const update of bufferedUpdates?.items() ?? []) {
|
|
275
|
+
if (update.lastVersion <= lastUpdateId) {
|
|
276
|
+
continue
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return update.firstVersion <= lastUpdateId + 1 && update.lastVersion >= lastUpdateId + 1
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return undefined
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private trimBufferedUpdates(symbol: string) {
|
|
286
|
+
const bufferedUpdates = this.bufferedDepthUpdates.get(symbol)
|
|
287
|
+
if (bufferedUpdates === undefined || bufferedUpdates.count <= 100) {
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const trimmed = new CircularBuffer<MexcDepthUpdateData>(2000)
|
|
292
|
+
for (const update of [...bufferedUpdates.items()].slice(-100)) {
|
|
293
|
+
trimmed.append(update)
|
|
294
|
+
}
|
|
295
|
+
this.bufferedDepthUpdates.set(symbol, trimmed)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private snapshotResponseIsValid(data: MexcDepthSnapshotResponse) {
|
|
299
|
+
return Number.isFinite(data.lastUpdateId) && Array.isArray(data.asks) && Array.isArray(data.bids)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private createManualSnapshot(symbol: string, data: MexcDepthSnapshotResponse): MexcDepthSnapshotMessage {
|
|
303
|
+
return {
|
|
304
|
+
channel: `${MexcRealTimeFeed.depthSnapshotChannel}@${symbol}`,
|
|
305
|
+
symbol,
|
|
306
|
+
generated: true,
|
|
307
|
+
publicAggreDepthsSnapshot: {
|
|
308
|
+
lastUpdateId: data.lastUpdateId,
|
|
309
|
+
asks: data.asks,
|
|
310
|
+
bids: data.bids,
|
|
311
|
+
timestamp: data.timestamp
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
type MexcDepthSnapshotResponse = {
|
|
318
|
+
lastUpdateId: number
|
|
319
|
+
bids: [string, string][]
|
|
320
|
+
asks: [string, string][]
|
|
321
|
+
timestamp: number
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
type MexcDepthUpdateData = {
|
|
325
|
+
firstVersion: number
|
|
326
|
+
lastVersion: number
|
|
327
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { CircularBuffer, getJSON, wait } from '../handy.ts'
|
|
2
|
+
import type { MexcFuturesDepthSnapshotData, MexcFuturesDepthSnapshotMessage } from '../mappers/mexcfutures.ts'
|
|
3
|
+
import { Filter } from '../types.ts'
|
|
4
|
+
import { RealTimeFeedBase } from './realtimefeed.ts'
|
|
5
|
+
|
|
6
|
+
export class MexcFuturesRealTimeFeed extends RealTimeFeedBase {
|
|
7
|
+
private readonly pendingDepthSnapshotSymbols = new Set<string>()
|
|
8
|
+
private readonly bufferedDepthUpdates = new Map<string, CircularBuffer<MexcFuturesDepthUpdateData>>()
|
|
9
|
+
protected readonly wssURL = 'wss://contract.mexc.com'
|
|
10
|
+
protected readonly httpURL: string = 'https://contract.mexc.com'
|
|
11
|
+
protected readonly channelToSubscriptionMethod = new Map([
|
|
12
|
+
['push.deal', 'sub.deal'],
|
|
13
|
+
['push.depth', 'sub.depth'],
|
|
14
|
+
['push.ticker', 'sub.ticker'],
|
|
15
|
+
['push.index.price', 'sub.index.price'],
|
|
16
|
+
['push.fair.price', 'sub.fair.price'],
|
|
17
|
+
['push.funding.rate', 'sub.funding.rate'],
|
|
18
|
+
['push.contract', 'sub.contract']
|
|
19
|
+
])
|
|
20
|
+
|
|
21
|
+
protected override sendCustomPing = () => {
|
|
22
|
+
this.send({ method: 'ping' })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
protected override async getWebSocketUrl() {
|
|
26
|
+
return 'wss://contract.mexc.com/edge'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
protected override mapToSubscribeMessages(filters: Filter<string>[]): any[] {
|
|
30
|
+
const filtersWithSymbols = filters.map<Required<Filter<string>>>((filter) => {
|
|
31
|
+
if (filter.channel === 'push.contract') {
|
|
32
|
+
return { ...filter, symbols: [] }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (filter.channel !== 'push.depth.snapshot' && this.channelToSubscriptionMethod.has(filter.channel) === false) {
|
|
36
|
+
throw new Error(`Unsupported MEXC futures channel ${filter.channel}`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!filter.symbols || filter.symbols.length === 0) {
|
|
40
|
+
throw new Error('MexcFuturesRealTimeFeed requires explicitly specified symbols when subscribing to live feed')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return filter as Required<Filter<string>>
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const depthSnapshotFilters = filtersWithSymbols.filter((filter) => filter.channel === 'push.depth.snapshot')
|
|
47
|
+
this.validateDepthSnapshotFilters(filtersWithSymbols, depthSnapshotFilters)
|
|
48
|
+
this.resetDepthSnapshotTracking(depthSnapshotFilters)
|
|
49
|
+
|
|
50
|
+
return filtersWithSymbols.flatMap((filter) => {
|
|
51
|
+
if (filter.channel === 'push.depth.snapshot') {
|
|
52
|
+
return []
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (filter.channel === 'push.contract') {
|
|
56
|
+
return [{ method: this.channelToSubscriptionMethod.get(filter.channel) }]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return filter.symbols.map((symbol) => ({
|
|
60
|
+
method: this.channelToSubscriptionMethod.get(filter.channel),
|
|
61
|
+
param: { symbol },
|
|
62
|
+
gzip: false
|
|
63
|
+
}))
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private resetDepthSnapshotTracking(filters: Required<Filter<string>>[]) {
|
|
68
|
+
this.pendingDepthSnapshotSymbols.clear()
|
|
69
|
+
this.bufferedDepthUpdates.clear()
|
|
70
|
+
|
|
71
|
+
for (const filter of filters) {
|
|
72
|
+
for (const symbol of filter.symbols) {
|
|
73
|
+
const upperCaseSymbol = symbol.toUpperCase()
|
|
74
|
+
this.pendingDepthSnapshotSymbols.add(upperCaseSymbol)
|
|
75
|
+
this.bufferedDepthUpdates.set(upperCaseSymbol, new CircularBuffer<MexcFuturesDepthUpdateData>(2000))
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private validateDepthSnapshotFilters(filters: Required<Filter<string>>[], depthSnapshotFilters: Required<Filter<string>>[]) {
|
|
81
|
+
if (depthSnapshotFilters.length === 0) {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const depthSymbols = new Set(
|
|
86
|
+
filters.filter((filter) => filter.channel === 'push.depth').flatMap((filter) => filter.symbols.map((symbol) => symbol.toUpperCase()))
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
for (const filter of depthSnapshotFilters) {
|
|
90
|
+
for (const symbol of filter.symbols) {
|
|
91
|
+
if (depthSymbols.has(symbol.toUpperCase()) === false) {
|
|
92
|
+
throw new Error('MexcFuturesRealTimeFeed requires push.depth for every push.depth.snapshot symbol')
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
protected override onMessage(message: any) {
|
|
99
|
+
if (
|
|
100
|
+
message.channel !== 'push.depth' ||
|
|
101
|
+
message.symbol === undefined ||
|
|
102
|
+
this.pendingDepthSnapshotSymbols.has(message.symbol) === false
|
|
103
|
+
) {
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const firstVersion = Number(
|
|
108
|
+
message.data?.begin !== undefined && message.data.begin !== null && message.data.begin !== 0
|
|
109
|
+
? message.data.begin
|
|
110
|
+
: message.data?.version
|
|
111
|
+
)
|
|
112
|
+
const lastVersion = Number(
|
|
113
|
+
message.data?.end !== undefined && message.data.end !== null && message.data.end !== 0 ? message.data.end : message.data?.version
|
|
114
|
+
)
|
|
115
|
+
if (Number.isFinite(firstVersion) === false || Number.isFinite(lastVersion) === false) {
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const bufferedUpdates = this.bufferedDepthUpdates.get(message.symbol) ?? new CircularBuffer<MexcFuturesDepthUpdateData>(2000)
|
|
120
|
+
bufferedUpdates.append({ firstVersion, lastVersion })
|
|
121
|
+
this.bufferedDepthUpdates.set(message.symbol, bufferedUpdates)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
protected override async provideManualSnapshots(filters: Filter<string>[], shouldCancel: () => boolean) {
|
|
125
|
+
const depthFilter = filters.find((filter) => filter.channel === 'push.depth.snapshot')
|
|
126
|
+
if (depthFilter === undefined) {
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this.debug('requesting manual snapshots for: %s', depthFilter.symbols!)
|
|
131
|
+
|
|
132
|
+
for (const symbol of depthFilter.symbols!) {
|
|
133
|
+
await this.provideManualSnapshot(symbol.toUpperCase(), shouldCancel)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
this.debug('requested manual snapshots successfully for: %s ', depthFilter.symbols!)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
protected override messageIsError(message: any): boolean {
|
|
140
|
+
return message.channel === 'rs.error' || message.success === false || message.error !== undefined
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
protected override messageIsHeartbeat(message: any) {
|
|
144
|
+
return message.channel === 'pong'
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private async provideManualSnapshot(symbol: string, shouldCancel: () => boolean) {
|
|
148
|
+
const maxSnapshotRounds = 4
|
|
149
|
+
const maxSnapshotAttemptsPerRound = 3
|
|
150
|
+
|
|
151
|
+
for (let round = 0; round < maxSnapshotRounds; round++) {
|
|
152
|
+
for (let attempt = 1; attempt <= maxSnapshotAttemptsPerRound; attempt++) {
|
|
153
|
+
if (shouldCancel()) {
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const { data } = await getJSON<MexcFuturesDepthSnapshotResponse>(`${this.httpURL}/api/v1/contract/depth/${symbol}?limit=5000`)
|
|
158
|
+
if (this.snapshotResponseIsValid(data) === false) {
|
|
159
|
+
if (attempt < maxSnapshotAttemptsPerRound) {
|
|
160
|
+
await wait(attempt * 1000)
|
|
161
|
+
}
|
|
162
|
+
continue
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const hasOverlap = await this.waitForSnapshotOverlap(symbol, data.data.version)
|
|
166
|
+
|
|
167
|
+
if (shouldCancel()) {
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (hasOverlap === false) {
|
|
172
|
+
this.trimBufferedUpdates(symbol)
|
|
173
|
+
if (attempt < maxSnapshotAttemptsPerRound) {
|
|
174
|
+
await wait(attempt * 1000)
|
|
175
|
+
}
|
|
176
|
+
continue
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (hasOverlap === true || attempt === maxSnapshotAttemptsPerRound) {
|
|
180
|
+
this.manualSnapshotsBuffer.push(this.createManualSnapshot(symbol, data.data))
|
|
181
|
+
this.pendingDepthSnapshotSymbols.delete(symbol)
|
|
182
|
+
this.bufferedDepthUpdates.delete(symbol)
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
throw new Error(`MexcFuturesRealTimeFeed could not align depth snapshot for ${symbol}`)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private async waitForSnapshotOverlap(symbol: string, snapshotVersion: number) {
|
|
192
|
+
let hasOverlap = this.validateSnapshotOverlap(symbol, snapshotVersion)
|
|
193
|
+
for (let attempt = 0; attempt < 60; attempt++) {
|
|
194
|
+
if (hasOverlap !== undefined) {
|
|
195
|
+
return hasOverlap
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
await wait(100)
|
|
199
|
+
hasOverlap = this.validateSnapshotOverlap(symbol, snapshotVersion)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return hasOverlap
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private validateSnapshotOverlap(symbol: string, snapshotVersion: number) {
|
|
206
|
+
const bufferedUpdates = this.bufferedDepthUpdates.get(symbol)
|
|
207
|
+
for (const update of bufferedUpdates?.items() ?? []) {
|
|
208
|
+
if (update.lastVersion <= snapshotVersion) {
|
|
209
|
+
continue
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return update.firstVersion <= snapshotVersion + 1 && update.lastVersion >= snapshotVersion + 1
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return undefined
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private trimBufferedUpdates(symbol: string) {
|
|
219
|
+
const bufferedUpdates = this.bufferedDepthUpdates.get(symbol)
|
|
220
|
+
if (bufferedUpdates === undefined || bufferedUpdates.count <= 100) {
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const trimmed = new CircularBuffer<MexcFuturesDepthUpdateData>(2000)
|
|
225
|
+
for (const update of [...bufferedUpdates.items()].slice(-100)) {
|
|
226
|
+
trimmed.append(update)
|
|
227
|
+
}
|
|
228
|
+
this.bufferedDepthUpdates.set(symbol, trimmed)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private snapshotResponseIsValid(response: MexcFuturesDepthSnapshotResponse) {
|
|
232
|
+
return (
|
|
233
|
+
response.success === true &&
|
|
234
|
+
response.code === 0 &&
|
|
235
|
+
response.data !== undefined &&
|
|
236
|
+
Number.isFinite(response.data.version) &&
|
|
237
|
+
response.data.version >= 0 &&
|
|
238
|
+
Array.isArray(response.data.asks) &&
|
|
239
|
+
Array.isArray(response.data.bids)
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private createManualSnapshot(symbol: string, data: MexcFuturesDepthSnapshotData): MexcFuturesDepthSnapshotMessage {
|
|
244
|
+
return {
|
|
245
|
+
channel: 'push.depth.snapshot',
|
|
246
|
+
symbol,
|
|
247
|
+
generated: true,
|
|
248
|
+
data: {
|
|
249
|
+
cts: data.cts,
|
|
250
|
+
asks: data.asks,
|
|
251
|
+
bids: data.bids,
|
|
252
|
+
timestamp: data.timestamp,
|
|
253
|
+
version: data.version
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
type MexcFuturesDepthSnapshotResponse = {
|
|
260
|
+
success: boolean
|
|
261
|
+
code: number
|
|
262
|
+
message?: string
|
|
263
|
+
data: MexcFuturesDepthSnapshotData
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
type MexcFuturesDepthUpdateData = {
|
|
267
|
+
firstVersion: number
|
|
268
|
+
lastVersion: number
|
|
269
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import crypto from 'crypto'
|
|
2
2
|
import { wait } from '../handy.ts'
|
|
3
|
+
import { getOkexOptionsFamilyOrIndex, getOkexOptionsUnderlyingIndex } from '../okexsymbols.ts'
|
|
3
4
|
import { Filter } from '../types.ts'
|
|
4
5
|
import { MultiConnectionRealTimeFeedBase, RealTimeFeedBase } from './realtimefeed.ts'
|
|
5
6
|
|
|
@@ -140,7 +141,8 @@ class OkexOptionsSingleRealTimeFeed extends OkexSingleRealTimeFeed {
|
|
|
140
141
|
super(wssURL, exchange, filters, timeoutIntervalMS, onError)
|
|
141
142
|
}
|
|
142
143
|
|
|
143
|
-
private _defaultIndexes = ['BTC-USD', 'ETH-USD']
|
|
144
|
+
private _defaultIndexes = ['BTC-USD', 'ETH-USD', 'SOL-USD']
|
|
145
|
+
private _defaultOptionSummaryInstrumentFamilies = ['BTC-USD', 'BTC-USD_UM', 'ETH-USD', 'ETH-USD_UM', 'SOL-USD_UM']
|
|
144
146
|
|
|
145
147
|
private _channelRequiresIndexNotSymbol(channel: string) {
|
|
146
148
|
if (channel === 'index-tickers' || channel === 'opt-summary') {
|
|
@@ -155,7 +157,7 @@ class OkexOptionsSingleRealTimeFeed extends OkexSingleRealTimeFeed {
|
|
|
155
157
|
const channelRequiresIndexNotSymbol = this._channelRequiresIndexNotSymbol(filter.channel)
|
|
156
158
|
|
|
157
159
|
if (symbols.length === 0 && channelRequiresIndexNotSymbol) {
|
|
158
|
-
symbols = this._defaultIndexes
|
|
160
|
+
symbols = filter.channel === 'opt-summary' ? this._defaultOptionSummaryInstrumentFamilies : this._defaultIndexes
|
|
159
161
|
}
|
|
160
162
|
|
|
161
163
|
if (symbols.length === 0) {
|
|
@@ -165,8 +167,7 @@ class OkexOptionsSingleRealTimeFeed extends OkexSingleRealTimeFeed {
|
|
|
165
167
|
return symbols.map((symbol) => {
|
|
166
168
|
let finalSymbol = symbol
|
|
167
169
|
if (channelRequiresIndexNotSymbol) {
|
|
168
|
-
|
|
169
|
-
finalSymbol = `${symbolParts[0]}-${symbolParts[1]}`
|
|
170
|
+
finalSymbol = filter.channel === 'opt-summary' ? getOkexOptionsFamilyOrIndex(symbol) : getOkexOptionsUnderlyingIndex(symbol)
|
|
170
171
|
}
|
|
171
172
|
return {
|
|
172
173
|
channel: filter.channel,
|
|
@@ -176,11 +177,12 @@ class OkexOptionsSingleRealTimeFeed extends OkexSingleRealTimeFeed {
|
|
|
176
177
|
})
|
|
177
178
|
})
|
|
178
179
|
.flatMap((s) => s)
|
|
180
|
+
const uniqueArgs = [...new Map(args.map((arg) => [`${arg.channel}:${arg.instId ?? ''}:${arg.instFamily ?? ''}`, arg])).values()]
|
|
179
181
|
|
|
180
182
|
return [
|
|
181
183
|
{
|
|
182
184
|
op: 'subscribe',
|
|
183
|
-
args:
|
|
185
|
+
args: uniqueArgs
|
|
184
186
|
}
|
|
185
187
|
]
|
|
186
188
|
}
|
|
@@ -117,7 +117,7 @@ export abstract class RealTimeFeedBase implements RealTimeFeedIterable {
|
|
|
117
117
|
message = message.toString().replace(/"id":([0-9]+),/g, '"id":"$1",') as any
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
const messageDeserialized =
|
|
120
|
+
const messageDeserialized = this.parseMessage(message)
|
|
121
121
|
|
|
122
122
|
if (this.messageIsError(messageDeserialized)) {
|
|
123
123
|
if (this.isIgnoredError(messageDeserialized)) {
|
|
@@ -233,6 +233,10 @@ export abstract class RealTimeFeedBase implements RealTimeFeedIterable {
|
|
|
233
233
|
|
|
234
234
|
protected abstract messageIsError(message: any): boolean
|
|
235
235
|
|
|
236
|
+
protected parseMessage(message: Buffer<ArrayBufferLike>): any {
|
|
237
|
+
return JSON.parse(message as any)
|
|
238
|
+
}
|
|
239
|
+
|
|
236
240
|
protected sendCustomPing: (() => void) | undefined = undefined
|
|
237
241
|
|
|
238
242
|
protected isIgnoredError(_message: any) {
|