tardis-machine 18.2.1 → 18.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/ws/subscriptionsmappers.d.ts.map +1 -1
  2. package/dist/ws/subscriptionsmappers.js +17 -0
  3. package/dist/ws/subscriptionsmappers.js.map +1 -1
  4. package/node_modules/tardis-dev/README.md +3 -1
  5. package/node_modules/tardis-dev/dist/consts.d.ts +4 -1
  6. package/node_modules/tardis-dev/dist/consts.d.ts.map +1 -1
  7. package/node_modules/tardis-dev/dist/consts.js +8 -0
  8. package/node_modules/tardis-dev/dist/consts.js.map +1 -1
  9. package/node_modules/tardis-dev/dist/mappers/ascendex.d.ts +4 -4
  10. package/node_modules/tardis-dev/dist/mappers/aster.d.ts +8 -8
  11. package/node_modules/tardis-dev/dist/mappers/binance.d.ts +18 -18
  12. package/node_modules/tardis-dev/dist/mappers/binanceeuropeanoptions.d.ts +1 -1
  13. package/node_modules/tardis-dev/dist/mappers/bitfinex.d.ts +8 -8
  14. package/node_modules/tardis-dev/dist/mappers/bitflyer.d.ts +1 -1
  15. package/node_modules/tardis-dev/dist/mappers/bitget.d.ts +1 -1
  16. package/node_modules/tardis-dev/dist/mappers/bithumb.d.ts +8 -0
  17. package/node_modules/tardis-dev/dist/mappers/bithumb.d.ts.map +1 -0
  18. package/node_modules/tardis-dev/dist/mappers/bithumb.js +60 -0
  19. package/node_modules/tardis-dev/dist/mappers/bithumb.js.map +1 -0
  20. package/node_modules/tardis-dev/dist/mappers/bitmex.d.ts +2 -2
  21. package/node_modules/tardis-dev/dist/mappers/bitnomial.d.ts +1 -1
  22. package/node_modules/tardis-dev/dist/mappers/bitstamp.d.ts +1 -1
  23. package/node_modules/tardis-dev/dist/mappers/bitvavo.d.ts +9 -0
  24. package/node_modules/tardis-dev/dist/mappers/bitvavo.d.ts.map +1 -0
  25. package/node_modules/tardis-dev/dist/mappers/bitvavo.js +168 -0
  26. package/node_modules/tardis-dev/dist/mappers/bitvavo.js.map +1 -0
  27. package/node_modules/tardis-dev/dist/mappers/blockchaincom.d.ts +2 -2
  28. package/node_modules/tardis-dev/dist/mappers/bullish.d.ts +5 -5
  29. package/node_modules/tardis-dev/dist/mappers/bybit.d.ts +7 -7
  30. package/node_modules/tardis-dev/dist/mappers/bybitspot.d.ts +1 -1
  31. package/node_modules/tardis-dev/dist/mappers/coinbase.d.ts +1 -1
  32. package/node_modules/tardis-dev/dist/mappers/coinbaseinternational.d.ts +2 -2
  33. package/node_modules/tardis-dev/dist/mappers/coinflex.d.ts +1 -1
  34. package/node_modules/tardis-dev/dist/mappers/cryptocom.d.ts +5 -5
  35. package/node_modules/tardis-dev/dist/mappers/delta.d.ts +1 -1
  36. package/node_modules/tardis-dev/dist/mappers/deribit.d.ts +3 -3
  37. package/node_modules/tardis-dev/dist/mappers/dydx.d.ts +3 -3
  38. package/node_modules/tardis-dev/dist/mappers/dydxv4.d.ts +4 -4
  39. package/node_modules/tardis-dev/dist/mappers/ftx.d.ts +8 -8
  40. package/node_modules/tardis-dev/dist/mappers/gateio.d.ts +1 -1
  41. package/node_modules/tardis-dev/dist/mappers/gateiofutures.d.ts +4 -4
  42. package/node_modules/tardis-dev/dist/mappers/gemini.d.ts +1 -1
  43. package/node_modules/tardis-dev/dist/mappers/huobi.d.ts +26 -26
  44. package/node_modules/tardis-dev/dist/mappers/hyperliquid.d.ts +3 -3
  45. package/node_modules/tardis-dev/dist/mappers/index.d.ts +200 -170
  46. package/node_modules/tardis-dev/dist/mappers/index.d.ts.map +1 -1
  47. package/node_modules/tardis-dev/dist/mappers/index.js +3 -1
  48. package/node_modules/tardis-dev/dist/mappers/index.js.map +1 -1
  49. package/node_modules/tardis-dev/dist/mappers/kucoin.d.ts +3 -3
  50. package/node_modules/tardis-dev/dist/mappers/kucoinfutures.d.ts +4 -4
  51. package/node_modules/tardis-dev/dist/mappers/lighter.d.ts +164 -5
  52. package/node_modules/tardis-dev/dist/mappers/lighter.d.ts.map +1 -1
  53. package/node_modules/tardis-dev/dist/mappers/lighter.js +41 -15
  54. package/node_modules/tardis-dev/dist/mappers/lighter.js.map +1 -1
  55. package/node_modules/tardis-dev/dist/mappers/mexc.d.ts +3 -3
  56. package/node_modules/tardis-dev/dist/mappers/mexcfutures.d.ts +3 -3
  57. package/node_modules/tardis-dev/dist/mappers/okex.d.ts +15 -15
  58. package/node_modules/tardis-dev/dist/mappers/phemex.d.ts +1 -1
  59. package/node_modules/tardis-dev/dist/mappers/polymarket.d.ts +3 -3
  60. package/node_modules/tardis-dev/dist/mappers/serum.d.ts +9 -9
  61. package/node_modules/tardis-dev/dist/mappers/upbit.d.ts +2 -2
  62. package/node_modules/tardis-dev/dist/parsemessage.d.ts +2 -0
  63. package/node_modules/tardis-dev/dist/parsemessage.d.ts.map +1 -0
  64. package/node_modules/tardis-dev/dist/parsemessage.js +31 -0
  65. package/node_modules/tardis-dev/dist/parsemessage.js.map +1 -0
  66. package/node_modules/tardis-dev/dist/realtimefeeds/bithumb.d.ts +9 -0
  67. package/node_modules/tardis-dev/dist/realtimefeeds/bithumb.d.ts.map +1 -0
  68. package/node_modules/tardis-dev/dist/realtimefeeds/bithumb.js +26 -0
  69. package/node_modules/tardis-dev/dist/realtimefeeds/bithumb.js.map +1 -0
  70. package/node_modules/tardis-dev/dist/realtimefeeds/bitvavo.d.ts +31 -0
  71. package/node_modules/tardis-dev/dist/realtimefeeds/bitvavo.d.ts.map +1 -0
  72. package/node_modules/tardis-dev/dist/realtimefeeds/bitvavo.js +85 -0
  73. package/node_modules/tardis-dev/dist/realtimefeeds/bitvavo.js.map +1 -0
  74. package/node_modules/tardis-dev/dist/realtimefeeds/index.d.ts.map +1 -1
  75. package/node_modules/tardis-dev/dist/realtimefeeds/index.js +7 -4
  76. package/node_modules/tardis-dev/dist/realtimefeeds/index.js.map +1 -1
  77. package/node_modules/tardis-dev/dist/realtimefeeds/lighter.d.ts +8 -2
  78. package/node_modules/tardis-dev/dist/realtimefeeds/lighter.d.ts.map +1 -1
  79. package/node_modules/tardis-dev/dist/realtimefeeds/lighter.js +8 -3
  80. package/node_modules/tardis-dev/dist/realtimefeeds/lighter.js.map +1 -1
  81. package/node_modules/tardis-dev/dist/realtimefeeds/realtimefeed.d.ts +1 -0
  82. package/node_modules/tardis-dev/dist/realtimefeeds/realtimefeed.d.ts.map +1 -1
  83. package/node_modules/tardis-dev/dist/realtimefeeds/realtimefeed.js +4 -5
  84. package/node_modules/tardis-dev/dist/realtimefeeds/realtimefeed.js.map +1 -1
  85. package/node_modules/tardis-dev/dist/replay.d.ts.map +1 -1
  86. package/node_modules/tardis-dev/dist/replay.js +5 -14
  87. package/node_modules/tardis-dev/dist/replay.js.map +1 -1
  88. package/node_modules/tardis-dev/dist/stream.d.ts.map +1 -1
  89. package/node_modules/tardis-dev/dist/stream.js +2 -1
  90. package/node_modules/tardis-dev/dist/stream.js.map +1 -1
  91. package/node_modules/tardis-dev/package.json +1 -1
  92. package/node_modules/tardis-dev/src/consts.ts +9 -0
  93. package/node_modules/tardis-dev/src/mappers/bithumb.ts +104 -0
  94. package/node_modules/tardis-dev/src/mappers/bitvavo.ts +256 -0
  95. package/node_modules/tardis-dev/src/mappers/index.ts +4 -0
  96. package/node_modules/tardis-dev/src/mappers/lighter.ts +69 -35
  97. package/node_modules/tardis-dev/src/parsemessage.ts +33 -0
  98. package/node_modules/tardis-dev/src/realtimefeeds/bithumb.ts +33 -0
  99. package/node_modules/tardis-dev/src/realtimefeeds/bitvavo.ts +107 -0
  100. package/node_modules/tardis-dev/src/realtimefeeds/index.ts +7 -4
  101. package/node_modules/tardis-dev/src/realtimefeeds/lighter.ts +10 -4
  102. package/node_modules/tardis-dev/src/realtimefeeds/realtimefeed.ts +4 -6
  103. package/node_modules/tardis-dev/src/replay.ts +5 -17
  104. package/node_modules/tardis-dev/src/stream.ts +3 -1
  105. package/package.json +2 -2
  106. package/src/ws/subscriptionsmappers.ts +20 -0
  107. package/node_modules/tardis-dev/dist/realtimefeeds/bitmex.d.ts +0 -11
  108. package/node_modules/tardis-dev/dist/realtimefeeds/bitmex.d.ts.map +0 -1
  109. package/node_modules/tardis-dev/dist/realtimefeeds/bitmex.js +0 -37
  110. package/node_modules/tardis-dev/dist/realtimefeeds/bitmex.js.map +0 -1
  111. package/node_modules/tardis-dev/src/realtimefeeds/bitmex.ts +0 -42
@@ -0,0 +1,104 @@
1
+ import { fromMicroSecondsToDate, upperCaseSymbols } from '../handy.ts'
2
+ import { BookChange, BookPriceLevel, Trade } from '../types.ts'
3
+ import { Mapper } from './mapper.ts'
4
+ import { exchangeMappers } from './registry.ts'
5
+
6
+ export const bithumbMappers = exchangeMappers({
7
+ bithumb: {
8
+ trades: () => new BithumbTradesMapper(),
9
+ bookChanges: () => new BithumbBookChangeMapper()
10
+ }
11
+ })
12
+
13
+ class BithumbTradesMapper implements Mapper<'bithumb', Trade> {
14
+ canHandle(message: BithumbTrade) {
15
+ return message.type === 'trade' && message.stream_type === 'REALTIME'
16
+ }
17
+
18
+ getFilters(symbols?: string[]) {
19
+ return [{ channel: 'trade', symbols: upperCaseSymbols(symbols) } as const]
20
+ }
21
+
22
+ *map(message: BithumbTrade, localTimestamp: Date): IterableIterator<Trade> {
23
+ yield {
24
+ type: 'trade',
25
+ symbol: message.code,
26
+ exchange: 'bithumb',
27
+ id: message.sequential_id,
28
+ price: message.trade_price,
29
+ amount: message.trade_volume,
30
+ side: message.ask_bid === 'ASK' ? 'sell' : 'buy',
31
+ timestamp: new Date(message.trade_timestamp),
32
+ localTimestamp
33
+ }
34
+ }
35
+ }
36
+
37
+ class BithumbBookChangeMapper implements Mapper<'bithumb', BookChange> {
38
+ canHandle(message: BithumbOrderBook) {
39
+ return message.type === 'orderbook'
40
+ }
41
+
42
+ getFilters(symbols?: string[]) {
43
+ return [{ channel: 'orderbook', symbols: upperCaseSymbols(symbols) } as const]
44
+ }
45
+
46
+ *map(message: BithumbOrderBook, localTimestamp: Date): IterableIterator<BookChange> {
47
+ const asks: BookPriceLevel[] = []
48
+ const bids: BookPriceLevel[] = []
49
+
50
+ for (const level of message.orderbook_units) {
51
+ if (level.ask_price > 0 && level.ask_size > 0) {
52
+ asks.push({ price: level.ask_price, amount: level.ask_size })
53
+ }
54
+
55
+ if (level.bid_price > 0 && level.bid_size > 0) {
56
+ bids.push({ price: level.bid_price, amount: level.bid_size })
57
+ }
58
+ }
59
+
60
+ yield {
61
+ type: 'book_change',
62
+ symbol: message.code,
63
+ exchange: 'bithumb',
64
+ isSnapshot: true,
65
+ asks,
66
+ bids,
67
+ timestamp: fromMicroSecondsToDate(message.timestamp),
68
+ localTimestamp
69
+ }
70
+ }
71
+ }
72
+
73
+ /** @see https://apidocs.bithumb.com/reference/%EC%B2%B4%EA%B2%B0-trade */
74
+ type BithumbTrade = {
75
+ type: 'trade'
76
+ code: string
77
+ trade_price: number
78
+ trade_volume: number
79
+ ask_bid: 'ASK' | 'BID'
80
+ prev_closing_price: number
81
+ change: 'RISE' | 'EVEN' | 'FALL'
82
+ change_price: number
83
+ trade_date: string
84
+ trade_time: string
85
+ trade_timestamp: number
86
+ /** Bithumb sends this as a JSON number, but ingestion preserves it as a string because it exceeds Number.MAX_SAFE_INTEGER. */
87
+ sequential_id: string
88
+ timestamp: number
89
+ stream_type: BithumbStreamType
90
+ }
91
+
92
+ /** @see https://apidocs.bithumb.com/reference/%ED%98%B8%EA%B0%80-orderbook */
93
+ type BithumbOrderBook = {
94
+ type: 'orderbook'
95
+ code: string
96
+ total_ask_size: number
97
+ total_bid_size: number
98
+ orderbook_units: { ask_price: number; bid_price: number; ask_size: number; bid_size: number }[]
99
+ timestamp: number
100
+ level: number
101
+ stream_type: BithumbStreamType
102
+ }
103
+
104
+ type BithumbStreamType = 'SNAPSHOT' | 'REALTIME'
@@ -0,0 +1,256 @@
1
+ import { asNonZeroNumberOrUndefined, CircularBuffer, fromMicroSecondsToDate, upperCaseSymbols } from '../handy.ts'
2
+ import { BookChange, BookTicker, Trade } from '../types.ts'
3
+ import { Mapper } from './mapper.ts'
4
+ import { exchangeMappers, isRealTime } from './registry.ts'
5
+
6
+ export const bitvavoMappers = exchangeMappers({
7
+ bitvavo: {
8
+ trades: () => new BitvavoTradesMapper(),
9
+ bookChanges: (localTimestamp) => new BitvavoBookChangeMapper({ validateSequence: isRealTime(localTimestamp) }),
10
+ bookTickers: () => new BitvavoBookTickerMapper()
11
+ }
12
+ })
13
+
14
+ class BitvavoTradesMapper implements Mapper<'bitvavo', Trade> {
15
+ canHandle(message: BitvavoMessage) {
16
+ return message.event === 'trade'
17
+ }
18
+
19
+ getFilters(symbols?: string[]) {
20
+ return [{ channel: 'trade', symbols: upperCaseSymbols(symbols) } as const]
21
+ }
22
+
23
+ *map(message: BitvavoTradeMessage, localTimestamp: Date): IterableIterator<Trade> {
24
+ yield {
25
+ type: 'trade',
26
+ symbol: message.market,
27
+ exchange: 'bitvavo',
28
+ id: message.id,
29
+ price: Number(message.price),
30
+ amount: Number(message.amount),
31
+ side: message.side,
32
+ timestamp: message.timestampNs === undefined ? new Date(message.timestamp) : fromNanoseconds(message.timestampNs),
33
+ localTimestamp
34
+ }
35
+ }
36
+ }
37
+
38
+ class BitvavoBookChangeMapper implements Mapper<'bitvavo', BookChange> {
39
+ private readonly depthInfoBySymbol = new Map<string, BitvavoDepthInfo>()
40
+
41
+ constructor(private readonly options: { validateSequence: boolean }) {}
42
+
43
+ canHandle(message: BitvavoMessage) {
44
+ return message.event === 'book' || message.action === 'getBook'
45
+ }
46
+
47
+ getFilters(symbols?: string[]) {
48
+ const normalizedSymbols = upperCaseSymbols(symbols)
49
+ return [{ channel: 'book', symbols: normalizedSymbols } as const, { channel: 'getBook', symbols: normalizedSymbols } as const]
50
+ }
51
+
52
+ *map(message: BitvavoBookMessage | BitvavoBookSnapshotMessage, localTimestamp: Date): IterableIterator<BookChange> {
53
+ if ('action' in message) {
54
+ const snapshot = message.response
55
+ const depthInfo = this.getDepthInfo(snapshot.market)
56
+ if (depthInfo.snapshotProcessed) {
57
+ return
58
+ }
59
+
60
+ const snapshotSequence = snapshot.mdSeqNo ?? snapshot.nonce
61
+ depthInfo.sequence = snapshotSequence
62
+ depthInfo.snapshotProcessed = true
63
+
64
+ yield {
65
+ type: 'book_change',
66
+ symbol: snapshot.market,
67
+ exchange: 'bitvavo',
68
+ isSnapshot: true,
69
+ bids: snapshot.bids.map(mapBookLevel),
70
+ asks: snapshot.asks.map(mapBookLevel),
71
+ timestamp: fromNanoseconds(snapshot.timestamp),
72
+ localTimestamp
73
+ }
74
+
75
+ for (const update of depthInfo.bufferedUpdates.items()) {
76
+ const mapped = this.mapUpdate(update, localTimestamp, depthInfo)
77
+ if (mapped !== undefined) {
78
+ yield mapped
79
+ }
80
+ }
81
+ depthInfo.bufferedUpdates.clear()
82
+ return
83
+ }
84
+
85
+ const depthInfo = this.getDepthInfo(message.market)
86
+ if (!depthInfo.snapshotProcessed) {
87
+ depthInfo.bufferedUpdates.append(message)
88
+ return
89
+ }
90
+
91
+ const mapped = this.mapUpdate(message, localTimestamp, depthInfo)
92
+ if (mapped !== undefined) {
93
+ yield mapped
94
+ }
95
+ }
96
+
97
+ private mapUpdate(message: BitvavoBookMessage, localTimestamp: Date, depthInfo: BitvavoDepthInfo): BookChange | undefined {
98
+ const firstSequence = message.startMdSeqNo ?? message.nonce
99
+ const lastSequence = message.endMdSeqNo ?? message.nonce
100
+ if (lastSequence <= depthInfo.sequence!) {
101
+ return
102
+ }
103
+ // Recorder validates historical continuity; direct live feeds must detect gaps themselves.
104
+ if (this.options.validateSequence && firstSequence > depthInfo.sequence! + 1) {
105
+ throw new Error(
106
+ `Bitvavo book update sequence gap for ${message.market}: expected ${depthInfo.sequence! + 1}, received ${firstSequence}-${lastSequence}`
107
+ )
108
+ }
109
+
110
+ depthInfo.sequence = lastSequence
111
+ return {
112
+ type: 'book_change',
113
+ symbol: message.market,
114
+ exchange: 'bitvavo',
115
+ isSnapshot: false,
116
+ bids: message.bids.map(mapBookLevel),
117
+ asks: message.asks.map(mapBookLevel),
118
+ timestamp: fromNanoseconds(message.timestamp),
119
+ localTimestamp
120
+ }
121
+ }
122
+
123
+ private getDepthInfo(symbol: string) {
124
+ let depthInfo = this.depthInfoBySymbol.get(symbol)
125
+ if (depthInfo === undefined) {
126
+ depthInfo = { bufferedUpdates: new CircularBuffer<BitvavoBookMessage>(2000) }
127
+ this.depthInfoBySymbol.set(symbol, depthInfo)
128
+ }
129
+ return depthInfo
130
+ }
131
+ }
132
+
133
+ class BitvavoBookTickerMapper implements Mapper<'bitvavo', BookTicker> {
134
+ private readonly quotesByMarket = new Map<string, BitvavoQuote>()
135
+
136
+ canHandle(message: BitvavoMessage) {
137
+ return message.event === 'ticker'
138
+ }
139
+
140
+ getFilters(symbols?: string[]) {
141
+ return [{ channel: 'ticker', symbols: upperCaseSymbols(symbols) } as const]
142
+ }
143
+
144
+ *map(message: BitvavoTickerMessage, localTimestamp: Date): IterableIterator<BookTicker> {
145
+ let quote = this.quotesByMarket.get(message.market)
146
+ if (quote === undefined) {
147
+ quote = { askAmount: undefined, askPrice: undefined, bidAmount: undefined, bidPrice: undefined }
148
+ this.quotesByMarket.set(message.market, quote)
149
+ }
150
+
151
+ // Recorded ticker payloads omit unchanged fields, so retain the previous quote per market.
152
+ let hasQuoteUpdate = false
153
+ if (message.bestAskSize !== undefined) {
154
+ quote.askAmount = asNonZeroNumberOrUndefined(message.bestAskSize)
155
+ hasQuoteUpdate = true
156
+ }
157
+ if (message.bestAsk !== undefined) {
158
+ quote.askPrice = asNonZeroNumberOrUndefined(message.bestAsk)
159
+ hasQuoteUpdate = true
160
+ }
161
+ if (message.bestBidSize !== undefined) {
162
+ quote.bidAmount = asNonZeroNumberOrUndefined(message.bestBidSize)
163
+ hasQuoteUpdate = true
164
+ }
165
+ if (message.bestBid !== undefined) {
166
+ quote.bidPrice = asNonZeroNumberOrUndefined(message.bestBid)
167
+ hasQuoteUpdate = true
168
+ }
169
+
170
+ if (!hasQuoteUpdate) {
171
+ return
172
+ }
173
+
174
+ yield {
175
+ type: 'book_ticker',
176
+ symbol: message.market,
177
+ exchange: 'bitvavo',
178
+ ...quote,
179
+ timestamp: localTimestamp,
180
+ localTimestamp
181
+ }
182
+ }
183
+ }
184
+
185
+ function mapBookLevel([price, amount]: BitvavoBookLevel) {
186
+ return {
187
+ price: Number(price),
188
+ amount: Number(amount)
189
+ }
190
+ }
191
+
192
+ function fromNanoseconds(nanoseconds: string) {
193
+ return fromMicroSecondsToDate(Number(nanoseconds.slice(0, -3)))
194
+ }
195
+
196
+ type BitvavoMessage = { event?: string; action?: string }
197
+
198
+ type BitvavoTradeMessage = {
199
+ event: 'trade'
200
+ id: string
201
+ amount: string
202
+ price: string
203
+ timestamp: number
204
+ timestampNs?: string
205
+ market: string
206
+ side: 'buy' | 'sell'
207
+ }
208
+
209
+ type BitvavoBookMessage = {
210
+ event: 'book'
211
+ market: string
212
+ nonce: number
213
+ bids: BitvavoBookLevel[]
214
+ asks: BitvavoBookLevel[]
215
+ timestamp: string
216
+ startMdSeqNo?: number
217
+ endMdSeqNo?: number
218
+ }
219
+
220
+ type BitvavoBookSnapshotMessage = {
221
+ action: 'getBook'
222
+ requestId: number
223
+ response: {
224
+ market: string
225
+ nonce: number
226
+ bids: BitvavoBookLevel[]
227
+ asks: BitvavoBookLevel[]
228
+ timestamp: string
229
+ mdSeqNo?: number
230
+ }
231
+ }
232
+
233
+ type BitvavoBookLevel = [string, string]
234
+
235
+ type BitvavoTickerMessage = {
236
+ event: 'ticker'
237
+ market: string
238
+ bestAsk?: string
239
+ bestAskSize?: string
240
+ bestBid?: string
241
+ bestBidSize?: string
242
+ lastPrice?: string
243
+ }
244
+
245
+ type BitvavoQuote = {
246
+ askAmount: number | undefined
247
+ askPrice: number | undefined
248
+ bidAmount: number | undefined
249
+ bidPrice: number | undefined
250
+ }
251
+
252
+ type BitvavoDepthInfo = {
253
+ bufferedUpdates: CircularBuffer<BitvavoBookMessage>
254
+ snapshotProcessed?: boolean
255
+ sequence?: number
256
+ }
@@ -7,9 +7,11 @@ import { binanceEuropeanOptionsMappers } from './binanceeuropeanoptions.ts'
7
7
  import { bitfinexMappers } from './bitfinex.ts'
8
8
  import { bitflyerMappers } from './bitflyer.ts'
9
9
  import { bitgetMappers } from './bitget.ts'
10
+ import { bithumbMappers } from './bithumb.ts'
10
11
  import { bitmexMappers } from './bitmex.ts'
11
12
  import { bitnomialMappers } from './bitnomial.ts'
12
13
  import { bitstampMappers } from './bitstamp.ts'
14
+ import { bitvavoMappers } from './bitvavo.ts'
13
15
  import { blockchainComMappers } from './blockchaincom.ts'
14
16
  import { bullishMappers } from './bullish.ts'
15
17
  import { bybitMappers } from './bybit.ts'
@@ -63,9 +65,11 @@ const registeredMappers = mergeExchangeMappers(
63
65
  bitfinexMappers,
64
66
  bitflyerMappers,
65
67
  bitgetMappers,
68
+ bithumbMappers,
66
69
  bitmexMappers,
67
70
  bitnomialMappers,
68
71
  bitstampMappers,
72
+ bitvavoMappers,
69
73
  blockchainComMappers,
70
74
  bullishMappers,
71
75
  bybitMappers,
@@ -4,15 +4,22 @@ import { Mapper, PendingTickerInfoHelper } from './mapper.ts'
4
4
  import { exchangeMappers } from './registry.ts'
5
5
 
6
6
  export const lighterMappers = exchangeMappers({
7
- lighter: {
8
- trades: () => new LighterTradesMapper(),
9
- bookChanges: () => new LighterBookChangeMapper(),
10
- derivativeTickers: () => new LighterDerivativeTickerMapper(),
11
- liquidations: () => new LighterLiquidationMapper(),
12
- bookTickers: () => new LighterBookTickerMapper()
13
- }
7
+ lighter: createLighterMappers('lighter'),
8
+ 'lighter-rh': createLighterMappers('lighter-rh')
14
9
  })
15
10
 
11
+ type LighterExchange = 'lighter' | 'lighter-rh'
12
+
13
+ function createLighterMappers<T extends LighterExchange>(exchange: T) {
14
+ return {
15
+ trades: () => new LighterTradesMapper(exchange),
16
+ bookChanges: () => new LighterBookChangeMapper(exchange),
17
+ derivativeTickers: () => new LighterDerivativeTickerMapper(exchange),
18
+ liquidations: () => new LighterLiquidationMapper(exchange),
19
+ bookTickers: () => new LighterBookTickerMapper(exchange)
20
+ }
21
+ }
22
+
16
23
  function parseChannelMarketId(channel: string): string | undefined {
17
24
  const colonIndex = channel.indexOf(':')
18
25
  if (colonIndex < 0) {
@@ -25,7 +32,9 @@ function parseChannelMarketId(channel: string): string | undefined {
25
32
  return suffix
26
33
  }
27
34
 
28
- class LighterTradesMapper implements Mapper<'lighter', Trade> {
35
+ class LighterTradesMapper<T extends LighterExchange> implements Mapper<T, Trade> {
36
+ constructor(private readonly exchange: T) {}
37
+
29
38
  canHandle(message: LighterTradeMessage) {
30
39
  return message.type === 'update/trade'
31
40
  }
@@ -44,7 +53,7 @@ class LighterTradesMapper implements Mapper<'lighter', Trade> {
44
53
  yield {
45
54
  type: 'trade',
46
55
  symbol: trade.market_id.toString(),
47
- exchange: 'lighter',
56
+ exchange: this.exchange,
48
57
  id: trade.trade_id_str,
49
58
  price: Number(trade.price),
50
59
  amount: Number(trade.size),
@@ -56,7 +65,9 @@ class LighterTradesMapper implements Mapper<'lighter', Trade> {
56
65
  }
57
66
  }
58
67
 
59
- class LighterLiquidationMapper implements Mapper<'lighter', Liquidation> {
68
+ class LighterLiquidationMapper<T extends LighterExchange> implements Mapper<T, Liquidation> {
69
+ constructor(private readonly exchange: T) {}
70
+
60
71
  canHandle(message: LighterTradeMessage) {
61
72
  return message.type === 'update/trade'
62
73
  }
@@ -83,7 +94,7 @@ class LighterLiquidationMapper implements Mapper<'lighter', Liquidation> {
83
94
  yield {
84
95
  type: 'liquidation',
85
96
  symbol: trade.market_id.toString(),
86
- exchange: 'lighter',
97
+ exchange: this.exchange,
87
98
  id: trade.trade_id_str,
88
99
  price: Number(trade.price),
89
100
  amount: Number(trade.size),
@@ -95,7 +106,9 @@ class LighterLiquidationMapper implements Mapper<'lighter', Liquidation> {
95
106
  }
96
107
  }
97
108
 
98
- class LighterBookChangeMapper implements Mapper<'lighter', BookChange> {
109
+ class LighterBookChangeMapper<T extends LighterExchange> implements Mapper<T, BookChange> {
110
+ constructor(private readonly exchange: T) {}
111
+
99
112
  canHandle(message: LighterOrderBookMessage) {
100
113
  return message.type === 'subscribed/order_book' || message.type === 'update/order_book'
101
114
  }
@@ -116,7 +129,7 @@ class LighterBookChangeMapper implements Mapper<'lighter', BookChange> {
116
129
  yield {
117
130
  type: 'book_change',
118
131
  symbol,
119
- exchange: 'lighter',
132
+ exchange: this.exchange,
120
133
  isSnapshot: message.type === 'subscribed/order_book',
121
134
  bids: message.order_book.bids.map(this.mapLevel),
122
135
  asks: message.order_book.asks.map(this.mapLevel),
@@ -133,9 +146,11 @@ class LighterBookChangeMapper implements Mapper<'lighter', BookChange> {
133
146
  }
134
147
  }
135
148
 
136
- class LighterBookTickerMapper implements Mapper<'lighter', BookTicker> {
149
+ class LighterBookTickerMapper<T extends LighterExchange> implements Mapper<T, BookTicker> {
150
+ constructor(private readonly exchange: T) {}
151
+
137
152
  canHandle(message: LighterTickerMessage) {
138
- return message.type === 'update/ticker'
153
+ return message.type === 'update/ticker' || message.type === 'subscribed/ticker'
139
154
  }
140
155
 
141
156
  getFilters(symbols?: string[]) {
@@ -154,7 +169,7 @@ class LighterBookTickerMapper implements Mapper<'lighter', BookTicker> {
154
169
  yield {
155
170
  type: 'book_ticker',
156
171
  symbol,
157
- exchange: 'lighter',
172
+ exchange: this.exchange,
158
173
  askAmount: asNonZeroNumberOrUndefined(message.ticker?.a?.size),
159
174
  askPrice: asNonZeroNumberOrUndefined(message.ticker?.a?.price),
160
175
  bidPrice: asNonZeroNumberOrUndefined(message.ticker?.b?.price),
@@ -165,11 +180,13 @@ class LighterBookTickerMapper implements Mapper<'lighter', BookTicker> {
165
180
  }
166
181
  }
167
182
 
168
- class LighterDerivativeTickerMapper implements Mapper<'lighter', DerivativeTicker> {
183
+ class LighterDerivativeTickerMapper<T extends LighterExchange> implements Mapper<T, DerivativeTicker> {
169
184
  private readonly pendingTickerInfoHelper = new PendingTickerInfoHelper()
170
185
 
186
+ constructor(private readonly exchange: T) {}
187
+
171
188
  canHandle(message: LighterMarketStatsMessage) {
172
- return message.type === 'update/market_stats'
189
+ return message.type === 'update/market_stats' || message.type === 'subscribed/market_stats'
173
190
  }
174
191
 
175
192
  getFilters(_symbols?: string[]) {
@@ -183,11 +200,13 @@ class LighterDerivativeTickerMapper implements Mapper<'lighter', DerivativeTicke
183
200
 
184
201
  *map(message: LighterMarketStatsMessage, localTimestamp: Date): IterableIterator<DerivativeTicker> {
185
202
  for (const entry of this.iterateMarketStats(message)) {
186
- const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(entry.market_id.toString(), 'lighter')
203
+ const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(entry.market_id.toString(), this.exchange)
187
204
 
188
205
  pendingTickerInfo.updateMarkPrice(Number(entry.mark_price))
189
206
  pendingTickerInfo.updateIndexPrice(Number(entry.index_price))
190
- pendingTickerInfo.updateFundingRate(Number(entry.current_funding_rate))
207
+ // WS funding is an hourly percentage; normalized fundingRate is a decimal fraction.
208
+ // https://docs.lighter.xyz/trading/funding#funding-rate-calculation
209
+ pendingTickerInfo.updateFundingRate(Number(entry.current_funding_rate) / 100)
191
210
  pendingTickerInfo.updateLastPrice(Number(entry.last_trade_price))
192
211
  pendingTickerInfo.updateOpenInterest(Number(entry.open_interest))
193
212
 
@@ -210,12 +229,12 @@ class LighterDerivativeTickerMapper implements Mapper<'lighter', DerivativeTicke
210
229
  }
211
230
  }
212
231
 
213
- type LighterLevel = {
232
+ export type LighterLevel = {
214
233
  price: string
215
234
  size: string
216
235
  }
217
236
 
218
- type LighterOrderBook = {
237
+ export type LighterOrderBook = {
219
238
  asks: LighterLevel[]
220
239
  bids: LighterLevel[]
221
240
  code: number
@@ -225,7 +244,7 @@ type LighterOrderBook = {
225
244
  last_updated_at: number
226
245
  }
227
246
 
228
- type LighterOrderBookMessage = {
247
+ export type LighterOrderBookMessage = {
229
248
  type: 'subscribed/order_book' | 'update/order_book'
230
249
  channel: `order_book:${number}`
231
250
  last_updated_at: number
@@ -234,14 +253,14 @@ type LighterOrderBookMessage = {
234
253
  order_book: LighterOrderBook
235
254
  }
236
255
 
237
- type LighterTicker = {
256
+ export type LighterTicker = {
238
257
  s: string
239
258
  a?: Partial<LighterLevel>
240
259
  b?: Partial<LighterLevel>
241
260
  last_updated_at: number
242
261
  }
243
262
 
244
- type LighterTickerMessage = {
263
+ export type LighterTickerMessage = {
245
264
  type: 'subscribed/ticker' | 'update/ticker'
246
265
  channel: `ticker:${number}`
247
266
  last_updated_at: number
@@ -250,7 +269,7 @@ type LighterTickerMessage = {
250
269
  timestamp: number
251
270
  }
252
271
 
253
- type LighterTrade = {
272
+ export type LighterTrade = {
254
273
  trade_id: number
255
274
  trade_id_str: string
256
275
  tx_hash: string
@@ -278,17 +297,26 @@ type LighterTrade = {
278
297
  taker_initial_margin_fraction_before?: number
279
298
  taker_position_sign_changed?: boolean
280
299
  taker_allocated_margin_usdc_before?: number
300
+ taker_allocated_margin_usdc_after?: number
281
301
  maker_fee?: number
282
302
  maker_position_size_before?: string
283
303
  maker_entry_quote_before?: string
284
304
  maker_initial_margin_fraction_before?: number
285
305
  maker_position_sign_changed?: boolean
306
+ maker_allocated_margin_usdc_before?: number
307
+ maker_allocated_margin_usdc_after?: number
286
308
  transaction_time: number
287
309
  ask_account_pnl?: string
288
310
  bid_account_pnl?: string
311
+ integrator_taker_fee?: number
312
+ integrator_taker_fee_collector_index?: number
313
+ integrator_maker_fee?: number
314
+ integrator_maker_fee_collector_index?: number
315
+ ask_order_version?: number
316
+ bid_order_version?: number
289
317
  }
290
318
 
291
- type LighterTradeMessage = {
319
+ export type LighterTradeMessage = {
292
320
  type: 'subscribed/trade' | 'update/trade'
293
321
  channel: `trade:${number}`
294
322
  nonce: number
@@ -296,12 +324,14 @@ type LighterTradeMessage = {
296
324
  liquidation_trades?: LighterTrade[]
297
325
  }
298
326
 
299
- type LighterMarketStats = {
327
+ export type LighterMarketStats = {
300
328
  symbol: string
301
329
  market_id: number
302
330
  index_price: string
303
331
  mark_price: string
304
332
  mid_price: string
333
+ best_ask_price: string
334
+ best_bid_price: string
305
335
  open_interest: string
306
336
  open_interest_limit: string
307
337
  funding_clamp_small: string
@@ -315,29 +345,33 @@ type LighterMarketStats = {
315
345
  daily_price_low: number
316
346
  daily_price_high: number
317
347
  daily_price_change: number
348
+ base_interest_rate: string
349
+ premium?: string
318
350
  }
319
351
 
320
- type LighterMarketStatsAllMessage = {
352
+ export type LighterMarketStatsAllMessage = {
321
353
  type: 'subscribed/market_stats' | 'update/market_stats'
322
354
  channel: 'market_stats:all'
323
355
  timestamp: number
324
356
  market_stats: Record<string, LighterMarketStats>
325
357
  }
326
358
 
327
- type LighterMarketStatsMarketIdMessage = {
359
+ export type LighterMarketStatsMarketIdMessage = {
328
360
  type: 'subscribed/market_stats' | 'update/market_stats'
329
361
  channel: `market_stats:${number}`
330
362
  timestamp: number
331
363
  market_stats: LighterMarketStats
332
364
  }
333
365
 
334
- type LighterMarketStatsMessage = LighterMarketStatsAllMessage | LighterMarketStatsMarketIdMessage
366
+ export type LighterMarketStatsMessage = LighterMarketStatsAllMessage | LighterMarketStatsMarketIdMessage
335
367
 
336
- type LighterSpotMarketStats = {
368
+ export type LighterSpotMarketStats = {
337
369
  symbol: string
338
370
  market_id: number
339
371
  index_price: string
340
372
  mid_price: string
373
+ best_ask_price: string
374
+ best_bid_price: string
341
375
  last_trade_price: string
342
376
  daily_base_token_volume: number
343
377
  daily_quote_token_volume: number
@@ -346,18 +380,18 @@ type LighterSpotMarketStats = {
346
380
  daily_price_change: number
347
381
  }
348
382
 
349
- type LighterSpotMarketStatsAllMessage = {
383
+ export type LighterSpotMarketStatsAllMessage = {
350
384
  type: 'subscribed/spot_market_stats' | 'update/spot_market_stats'
351
385
  channel: 'spot_market_stats:all'
352
386
  timestamp: number
353
387
  spot_market_stats: Record<string, LighterSpotMarketStats>
354
388
  }
355
389
 
356
- type LighterSpotMarketStatsMarketIdMessage = {
390
+ export type LighterSpotMarketStatsMarketIdMessage = {
357
391
  type: 'subscribed/spot_market_stats' | 'update/spot_market_stats'
358
392
  channel: `spot_market_stats:${number}`
359
393
  timestamp: number
360
394
  spot_market_stats: LighterSpotMarketStats
361
395
  }
362
396
 
363
- type LighterSpotMarketStatsMessage = LighterSpotMarketStatsAllMessage | LighterSpotMarketStatsMarketIdMessage
397
+ export type LighterSpotMarketStatsMessage = LighterSpotMarketStatsAllMessage | LighterSpotMarketStatsMarketIdMessage