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.
- package/dist/ws/subscriptionsmappers.d.ts.map +1 -1
- package/dist/ws/subscriptionsmappers.js +17 -0
- package/dist/ws/subscriptionsmappers.js.map +1 -1
- package/node_modules/tardis-dev/README.md +3 -1
- package/node_modules/tardis-dev/dist/consts.d.ts +4 -1
- package/node_modules/tardis-dev/dist/consts.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/consts.js +8 -0
- package/node_modules/tardis-dev/dist/consts.js.map +1 -1
- package/node_modules/tardis-dev/dist/mappers/ascendex.d.ts +4 -4
- package/node_modules/tardis-dev/dist/mappers/aster.d.ts +8 -8
- package/node_modules/tardis-dev/dist/mappers/binance.d.ts +18 -18
- package/node_modules/tardis-dev/dist/mappers/binanceeuropeanoptions.d.ts +1 -1
- package/node_modules/tardis-dev/dist/mappers/bitfinex.d.ts +8 -8
- package/node_modules/tardis-dev/dist/mappers/bitflyer.d.ts +1 -1
- package/node_modules/tardis-dev/dist/mappers/bitget.d.ts +1 -1
- package/node_modules/tardis-dev/dist/mappers/bithumb.d.ts +8 -0
- package/node_modules/tardis-dev/dist/mappers/bithumb.d.ts.map +1 -0
- package/node_modules/tardis-dev/dist/mappers/bithumb.js +60 -0
- package/node_modules/tardis-dev/dist/mappers/bithumb.js.map +1 -0
- package/node_modules/tardis-dev/dist/mappers/bitmex.d.ts +2 -2
- package/node_modules/tardis-dev/dist/mappers/bitnomial.d.ts +1 -1
- package/node_modules/tardis-dev/dist/mappers/bitstamp.d.ts +1 -1
- package/node_modules/tardis-dev/dist/mappers/bitvavo.d.ts +9 -0
- package/node_modules/tardis-dev/dist/mappers/bitvavo.d.ts.map +1 -0
- package/node_modules/tardis-dev/dist/mappers/bitvavo.js +168 -0
- package/node_modules/tardis-dev/dist/mappers/bitvavo.js.map +1 -0
- package/node_modules/tardis-dev/dist/mappers/blockchaincom.d.ts +2 -2
- package/node_modules/tardis-dev/dist/mappers/bullish.d.ts +5 -5
- package/node_modules/tardis-dev/dist/mappers/bybit.d.ts +7 -7
- package/node_modules/tardis-dev/dist/mappers/bybitspot.d.ts +1 -1
- package/node_modules/tardis-dev/dist/mappers/coinbase.d.ts +1 -1
- package/node_modules/tardis-dev/dist/mappers/coinbaseinternational.d.ts +2 -2
- package/node_modules/tardis-dev/dist/mappers/coinflex.d.ts +1 -1
- package/node_modules/tardis-dev/dist/mappers/cryptocom.d.ts +5 -5
- package/node_modules/tardis-dev/dist/mappers/delta.d.ts +1 -1
- package/node_modules/tardis-dev/dist/mappers/deribit.d.ts +3 -3
- package/node_modules/tardis-dev/dist/mappers/dydx.d.ts +3 -3
- package/node_modules/tardis-dev/dist/mappers/dydxv4.d.ts +4 -4
- package/node_modules/tardis-dev/dist/mappers/ftx.d.ts +8 -8
- package/node_modules/tardis-dev/dist/mappers/gateio.d.ts +1 -1
- package/node_modules/tardis-dev/dist/mappers/gateiofutures.d.ts +4 -4
- package/node_modules/tardis-dev/dist/mappers/gemini.d.ts +1 -1
- package/node_modules/tardis-dev/dist/mappers/huobi.d.ts +26 -26
- package/node_modules/tardis-dev/dist/mappers/hyperliquid.d.ts +3 -3
- package/node_modules/tardis-dev/dist/mappers/index.d.ts +200 -170
- package/node_modules/tardis-dev/dist/mappers/index.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/mappers/index.js +3 -1
- package/node_modules/tardis-dev/dist/mappers/index.js.map +1 -1
- package/node_modules/tardis-dev/dist/mappers/kucoin.d.ts +3 -3
- package/node_modules/tardis-dev/dist/mappers/kucoinfutures.d.ts +4 -4
- package/node_modules/tardis-dev/dist/mappers/lighter.d.ts +164 -5
- package/node_modules/tardis-dev/dist/mappers/lighter.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/mappers/lighter.js +41 -15
- package/node_modules/tardis-dev/dist/mappers/lighter.js.map +1 -1
- package/node_modules/tardis-dev/dist/mappers/mexc.d.ts +3 -3
- package/node_modules/tardis-dev/dist/mappers/mexcfutures.d.ts +3 -3
- package/node_modules/tardis-dev/dist/mappers/okex.d.ts +15 -15
- package/node_modules/tardis-dev/dist/mappers/phemex.d.ts +1 -1
- package/node_modules/tardis-dev/dist/mappers/polymarket.d.ts +3 -3
- package/node_modules/tardis-dev/dist/mappers/serum.d.ts +9 -9
- package/node_modules/tardis-dev/dist/mappers/upbit.d.ts +2 -2
- package/node_modules/tardis-dev/dist/parsemessage.d.ts +2 -0
- package/node_modules/tardis-dev/dist/parsemessage.d.ts.map +1 -0
- package/node_modules/tardis-dev/dist/parsemessage.js +31 -0
- package/node_modules/tardis-dev/dist/parsemessage.js.map +1 -0
- package/node_modules/tardis-dev/dist/realtimefeeds/bithumb.d.ts +9 -0
- package/node_modules/tardis-dev/dist/realtimefeeds/bithumb.d.ts.map +1 -0
- package/node_modules/tardis-dev/dist/realtimefeeds/bithumb.js +26 -0
- package/node_modules/tardis-dev/dist/realtimefeeds/bithumb.js.map +1 -0
- package/node_modules/tardis-dev/dist/realtimefeeds/bitvavo.d.ts +31 -0
- package/node_modules/tardis-dev/dist/realtimefeeds/bitvavo.d.ts.map +1 -0
- package/node_modules/tardis-dev/dist/realtimefeeds/bitvavo.js +85 -0
- package/node_modules/tardis-dev/dist/realtimefeeds/bitvavo.js.map +1 -0
- package/node_modules/tardis-dev/dist/realtimefeeds/index.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/realtimefeeds/index.js +7 -4
- package/node_modules/tardis-dev/dist/realtimefeeds/index.js.map +1 -1
- package/node_modules/tardis-dev/dist/realtimefeeds/lighter.d.ts +8 -2
- package/node_modules/tardis-dev/dist/realtimefeeds/lighter.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/realtimefeeds/lighter.js +8 -3
- package/node_modules/tardis-dev/dist/realtimefeeds/lighter.js.map +1 -1
- package/node_modules/tardis-dev/dist/realtimefeeds/realtimefeed.d.ts +1 -0
- package/node_modules/tardis-dev/dist/realtimefeeds/realtimefeed.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/realtimefeeds/realtimefeed.js +4 -5
- package/node_modules/tardis-dev/dist/realtimefeeds/realtimefeed.js.map +1 -1
- package/node_modules/tardis-dev/dist/replay.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/replay.js +5 -14
- package/node_modules/tardis-dev/dist/replay.js.map +1 -1
- package/node_modules/tardis-dev/dist/stream.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/stream.js +2 -1
- package/node_modules/tardis-dev/dist/stream.js.map +1 -1
- package/node_modules/tardis-dev/package.json +1 -1
- package/node_modules/tardis-dev/src/consts.ts +9 -0
- package/node_modules/tardis-dev/src/mappers/bithumb.ts +104 -0
- package/node_modules/tardis-dev/src/mappers/bitvavo.ts +256 -0
- package/node_modules/tardis-dev/src/mappers/index.ts +4 -0
- package/node_modules/tardis-dev/src/mappers/lighter.ts +69 -35
- package/node_modules/tardis-dev/src/parsemessage.ts +33 -0
- package/node_modules/tardis-dev/src/realtimefeeds/bithumb.ts +33 -0
- package/node_modules/tardis-dev/src/realtimefeeds/bitvavo.ts +107 -0
- package/node_modules/tardis-dev/src/realtimefeeds/index.ts +7 -4
- package/node_modules/tardis-dev/src/realtimefeeds/lighter.ts +10 -4
- package/node_modules/tardis-dev/src/realtimefeeds/realtimefeed.ts +4 -6
- package/node_modules/tardis-dev/src/replay.ts +5 -17
- package/node_modules/tardis-dev/src/stream.ts +3 -1
- package/package.json +2 -2
- package/src/ws/subscriptionsmappers.ts +20 -0
- package/node_modules/tardis-dev/dist/realtimefeeds/bitmex.d.ts +0 -11
- package/node_modules/tardis-dev/dist/realtimefeeds/bitmex.d.ts.map +0 -1
- package/node_modules/tardis-dev/dist/realtimefeeds/bitmex.js +0 -37
- package/node_modules/tardis-dev/dist/realtimefeeds/bitmex.js.map +0 -1
- 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
|
-
|
|
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<
|
|
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:
|
|
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<
|
|
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:
|
|
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<
|
|
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:
|
|
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<
|
|
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:
|
|
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<
|
|
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(),
|
|
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
|
-
|
|
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
|