tardis-machine 18.3.8 → 18.4.1
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 +7 -0
- package/dist/ws/subscriptionsmappers.js.map +1 -1
- package/node_modules/tardis-dev/dist/consts.d.ts +7 -5
- package/node_modules/tardis-dev/dist/consts.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/consts.js +33 -5
- package/node_modules/tardis-dev/dist/consts.js.map +1 -1
- package/node_modules/tardis-dev/dist/handy.d.ts +1 -0
- package/node_modules/tardis-dev/dist/handy.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/handy.js +19 -10
- package/node_modules/tardis-dev/dist/handy.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 +2 -2
- 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 +3 -2
- package/node_modules/tardis-dev/dist/mappers/bitstamp.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/mappers/bitstamp.js +49 -2
- package/node_modules/tardis-dev/dist/mappers/bitstamp.js.map +1 -1
- package/node_modules/tardis-dev/dist/mappers/bitvavo.d.ts +3 -3
- 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 +26 -7
- package/node_modules/tardis-dev/dist/mappers/bybit.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/mappers/bybit.js +4 -0
- package/node_modules/tardis-dev/dist/mappers/bybit.js.map +1 -1
- 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/coinbasederivatives.d.ts +10 -0
- package/node_modules/tardis-dev/dist/mappers/coinbasederivatives.d.ts.map +1 -0
- package/node_modules/tardis-dev/dist/mappers/coinbasederivatives.js +144 -0
- package/node_modules/tardis-dev/dist/mappers/coinbasederivatives.js.map +1 -0
- 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/cryptofacilities.d.ts +2 -2
- 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/dydxv4.js +19 -2
- package/node_modules/tardis-dev/dist/mappers/dydxv4.js.map +1 -1
- package/node_modules/tardis-dev/dist/mappers/ftx.d.ts +8 -8
- package/node_modules/tardis-dev/dist/mappers/ftx.js +1 -1
- package/node_modules/tardis-dev/dist/mappers/ftx.js.map +1 -1
- 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 +216 -183
- package/node_modules/tardis-dev/dist/mappers/index.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/mappers/index.js +2 -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 +10 -10
- 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/realtimefeeds/bitstamp.d.ts +12 -2
- package/node_modules/tardis-dev/dist/realtimefeeds/bitstamp.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/realtimefeeds/bitstamp.js +36 -3
- package/node_modules/tardis-dev/dist/realtimefeeds/bitstamp.js.map +1 -1
- package/node_modules/tardis-dev/dist/realtimefeeds/bybit.d.ts +3 -0
- package/node_modules/tardis-dev/dist/realtimefeeds/bybit.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/realtimefeeds/bybit.js +3 -0
- package/node_modules/tardis-dev/dist/realtimefeeds/bybit.js.map +1 -1
- package/node_modules/tardis-dev/dist/realtimefeeds/coinbasederivatives.d.ts +32 -0
- package/node_modules/tardis-dev/dist/realtimefeeds/coinbasederivatives.d.ts.map +1 -0
- package/node_modules/tardis-dev/dist/realtimefeeds/coinbasederivatives.js +83 -0
- package/node_modules/tardis-dev/dist/realtimefeeds/coinbasederivatives.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 +4 -1
- package/node_modules/tardis-dev/dist/realtimefeeds/index.js.map +1 -1
- package/node_modules/tardis-dev/package.json +1 -1
- package/node_modules/tardis-dev/src/consts.ts +34 -5
- package/node_modules/tardis-dev/src/handy.ts +20 -11
- package/node_modules/tardis-dev/src/mappers/bitstamp.ts +101 -27
- package/node_modules/tardis-dev/src/mappers/bybit.ts +6 -2
- package/node_modules/tardis-dev/src/mappers/coinbasederivatives.ts +230 -0
- package/node_modules/tardis-dev/src/mappers/dydxv4.ts +20 -2
- package/node_modules/tardis-dev/src/mappers/ftx.ts +1 -1
- package/node_modules/tardis-dev/src/mappers/index.ts +2 -0
- package/node_modules/tardis-dev/src/realtimefeeds/bitstamp.ts +50 -3
- package/node_modules/tardis-dev/src/realtimefeeds/bybit.ts +4 -0
- package/node_modules/tardis-dev/src/realtimefeeds/coinbasederivatives.ts +103 -0
- package/node_modules/tardis-dev/src/realtimefeeds/index.ts +4 -1
- package/package.json +2 -2
- package/src/ws/subscriptionsmappers.ts +8 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { lowerCaseSymbols } from '../handy.ts'
|
|
2
|
-
import { BookChange, Trade } from '../types.ts'
|
|
3
|
-
import { Mapper } from './mapper.ts'
|
|
2
|
+
import { BookChange, DerivativeTicker, Trade } from '../types.ts'
|
|
3
|
+
import { Mapper, PendingTickerInfoHelper } from './mapper.ts'
|
|
4
4
|
import { exchangeMappers } from './registry.ts'
|
|
5
5
|
|
|
6
6
|
// https://www.bitstamp.net/websocket/v2/
|
|
@@ -8,12 +8,13 @@ import { exchangeMappers } from './registry.ts'
|
|
|
8
8
|
export const bitstampMappers = exchangeMappers({
|
|
9
9
|
bitstamp: {
|
|
10
10
|
trades: () => bitstampTradesMapper,
|
|
11
|
-
bookChanges: () => new BitstampBookChangeMapper()
|
|
11
|
+
bookChanges: () => new BitstampBookChangeMapper(),
|
|
12
|
+
derivativeTickers: () => new BitstampDerivativeTickerMapper()
|
|
12
13
|
}
|
|
13
14
|
})
|
|
14
15
|
|
|
15
16
|
const bitstampTradesMapper: Mapper<'bitstamp', Trade> = {
|
|
16
|
-
canHandle(message:
|
|
17
|
+
canHandle(message: BitstampMessage) {
|
|
17
18
|
if (message.data === undefined) {
|
|
18
19
|
return false
|
|
19
20
|
}
|
|
@@ -43,7 +44,7 @@ const bitstampTradesMapper: Mapper<'bitstamp', Trade> = {
|
|
|
43
44
|
type: 'trade',
|
|
44
45
|
symbol: symbol.toUpperCase(),
|
|
45
46
|
exchange: 'bitstamp',
|
|
46
|
-
id: String(bitstampTrade.id),
|
|
47
|
+
id: bitstampTrade.id_str ?? String(bitstampTrade.id),
|
|
47
48
|
price: Number(bitstampTrade.price),
|
|
48
49
|
amount: Number(bitstampTrade.amount),
|
|
49
50
|
side: bitstampTrade.type === 0 ? 'buy' : 'sell',
|
|
@@ -53,10 +54,60 @@ const bitstampTradesMapper: Mapper<'bitstamp', Trade> = {
|
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
class BitstampDerivativeTickerMapper implements Mapper<'bitstamp', DerivativeTicker> {
|
|
58
|
+
private readonly _pendingTickerInfoHelper = new PendingTickerInfoHelper()
|
|
59
|
+
|
|
60
|
+
canHandle(message: BitstampMessage) {
|
|
61
|
+
if (message.data === undefined) {
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
if (message.event === 'funding_rate_saved') {
|
|
65
|
+
return message.channel.startsWith('funding_rate_')
|
|
66
|
+
}
|
|
67
|
+
if (message.event === 'ticker') {
|
|
68
|
+
return message.channel.startsWith('ticker_') && message.channel.endsWith('-perp')
|
|
69
|
+
}
|
|
70
|
+
return message.event === 'trade' && message.channel.startsWith('live_trades_') && message.channel.endsWith('-perp')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
getFilters(symbols?: string[]) {
|
|
74
|
+
symbols = lowerCaseSymbols(symbols)
|
|
75
|
+
return [
|
|
76
|
+
{ channel: 'funding_rate', symbols } as const,
|
|
77
|
+
{ channel: 'live_trades', symbols } as const,
|
|
78
|
+
{ channel: 'ticker', symbols } as const
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
*map(message: BitstampTrade | BitstampFundingRate | BitstampTicker, localTimestamp: Date): IterableIterator<DerivativeTicker> {
|
|
83
|
+
const symbol = message.channel.slice(message.channel.lastIndexOf('_') + 1).toUpperCase()
|
|
84
|
+
const ticker = this._pendingTickerInfoHelper.getPendingTickerInfo(symbol, 'bitstamp')
|
|
85
|
+
if (message.event === 'trade') {
|
|
86
|
+
// Funding messages own emission and timestamps; trades only update lastPrice.
|
|
87
|
+
ticker.updateLastPrice(Number(message.data.price))
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
if (message.event === 'ticker') {
|
|
91
|
+
// Recorder-generated REST ticker, polled every 6 seconds; it only adds open interest to the funding-driven ticker.
|
|
92
|
+
ticker.updateOpenInterest(Number(message.data.open_interest))
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
ticker.updateFundingRate(Number(message.data.funding_rate))
|
|
97
|
+
ticker.updateFundingTimestamp(new Date(Number(message.data.next_funding_time) * 1000))
|
|
98
|
+
ticker.updateMarkPrice(Number(message.data.mark_price))
|
|
99
|
+
ticker.updateIndexPrice(Number(message.data.index_price))
|
|
100
|
+
ticker.updateTimestamp(new Date(Number(message.data.timestamp) * 1000))
|
|
101
|
+
if (ticker.hasChanged()) {
|
|
102
|
+
yield ticker.getSnapshot(localTimestamp)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
56
107
|
class BitstampBookChangeMapper implements Mapper<'bitstamp', BookChange> {
|
|
57
108
|
private readonly _symbolToDepthInfoMapping: { [key: string]: LocalDepthInfo } = {}
|
|
58
109
|
|
|
59
|
-
canHandle(message:
|
|
110
|
+
canHandle(message: BitstampMessage) {
|
|
60
111
|
if (message.data === undefined) {
|
|
61
112
|
return false
|
|
62
113
|
}
|
|
@@ -176,44 +227,67 @@ class BitstampBookChangeMapper implements Mapper<'bitstamp', BookChange> {
|
|
|
176
227
|
}
|
|
177
228
|
}
|
|
178
229
|
|
|
179
|
-
type
|
|
180
|
-
event:
|
|
230
|
+
type BitstampMessage<Event extends string = string, Data = unknown> = {
|
|
231
|
+
event: Event
|
|
181
232
|
channel: string
|
|
182
|
-
data:
|
|
233
|
+
data: Data
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
type BitstampTrade = BitstampMessage<
|
|
237
|
+
'trade',
|
|
238
|
+
{
|
|
183
239
|
microtimestamp: string
|
|
184
240
|
amount: number
|
|
185
241
|
price: number
|
|
186
242
|
type: number
|
|
187
243
|
id: number
|
|
244
|
+
id_str?: string
|
|
245
|
+
}
|
|
246
|
+
>
|
|
247
|
+
type BitstampTicker = BitstampMessage<
|
|
248
|
+
'ticker',
|
|
249
|
+
{
|
|
250
|
+
market: string
|
|
251
|
+
open_interest: string
|
|
252
|
+
}
|
|
253
|
+
>
|
|
254
|
+
type BitstampFundingRate = BitstampMessage<
|
|
255
|
+
'funding_rate_saved',
|
|
256
|
+
{
|
|
257
|
+
market: string
|
|
258
|
+
funding_rate: string
|
|
259
|
+
mark_price: string
|
|
260
|
+
index_price: string
|
|
261
|
+
timestamp: string
|
|
262
|
+
next_funding_time: string
|
|
188
263
|
}
|
|
264
|
+
>
|
|
265
|
+
|
|
266
|
+
type LocalDepthInfo = {
|
|
267
|
+
bufferedUpdates: BitstampDiffOrderBook[]
|
|
268
|
+
snapshotProcessed?: boolean
|
|
269
|
+
lastUpdateTimestamp?: number
|
|
270
|
+
lastUpdateMicroTimestamp?: number
|
|
189
271
|
}
|
|
190
|
-
type BitstampBookLevel = [string, string]
|
|
191
272
|
|
|
192
|
-
type BitstampDiffOrderBook =
|
|
193
|
-
data
|
|
194
|
-
|
|
273
|
+
type BitstampDiffOrderBook = BitstampMessage<
|
|
274
|
+
'data',
|
|
275
|
+
{
|
|
195
276
|
timestamp: string
|
|
277
|
+
microtimestamp: string
|
|
196
278
|
bids: BitstampBookLevel[]
|
|
197
279
|
asks: BitstampBookLevel[]
|
|
198
280
|
}
|
|
199
|
-
|
|
200
|
-
channel: string
|
|
201
|
-
}
|
|
281
|
+
>
|
|
202
282
|
|
|
203
|
-
type BitstampDiffOrderBookSnapshot =
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
data: {
|
|
283
|
+
type BitstampDiffOrderBookSnapshot = BitstampMessage<
|
|
284
|
+
'snapshot',
|
|
285
|
+
{
|
|
207
286
|
timestamp: string
|
|
208
287
|
microtimestamp?: string
|
|
209
288
|
bids: BitstampBookLevel[]
|
|
210
289
|
asks: BitstampBookLevel[]
|
|
211
290
|
}
|
|
212
|
-
|
|
291
|
+
>
|
|
213
292
|
|
|
214
|
-
type
|
|
215
|
-
bufferedUpdates: BitstampDiffOrderBook[]
|
|
216
|
-
snapshotProcessed?: boolean
|
|
217
|
-
lastUpdateTimestamp?: number
|
|
218
|
-
lastUpdateMicroTimestamp?: number
|
|
219
|
-
}
|
|
293
|
+
type BitstampBookLevel = [string, string]
|
|
@@ -46,12 +46,16 @@ export const bybitMappers = exchangeMappers({
|
|
|
46
46
|
trades: () => new BybitV5TradesMapper('bybit-options'),
|
|
47
47
|
bookChanges: () => new BybitV5BookChangeMapper('bybit-options', { depth: 25 }),
|
|
48
48
|
optionsSummary: () => new BybitV5OptionSummaryMapper()
|
|
49
|
+
},
|
|
50
|
+
'bybit-spread': {
|
|
51
|
+
trades: () => new BybitV5TradesMapper('bybit-spread'),
|
|
52
|
+
bookChanges: () => new BybitV5BookChangeMapper('bybit-spread', { depth: 25 })
|
|
49
53
|
}
|
|
50
54
|
})
|
|
51
55
|
|
|
52
56
|
// v5 https://bybit-exchange.github.io/docs/v5/ws/connect
|
|
53
57
|
|
|
54
|
-
class BybitV5TradesMapper implements Mapper<'bybit' | 'bybit-spot' | 'bybit-options', Trade> {
|
|
58
|
+
class BybitV5TradesMapper implements Mapper<'bybit' | 'bybit-spot' | 'bybit-options' | 'bybit-spread', Trade> {
|
|
55
59
|
constructor(private readonly _exchange: Exchange) {}
|
|
56
60
|
|
|
57
61
|
canHandle(message: BybitV5Trade) {
|
|
@@ -90,7 +94,7 @@ class BybitV5TradesMapper implements Mapper<'bybit' | 'bybit-spot' | 'bybit-opti
|
|
|
90
94
|
}
|
|
91
95
|
}
|
|
92
96
|
|
|
93
|
-
class BybitV5BookChangeMapper implements Mapper<'bybit' | 'bybit-spot' | 'bybit-options', BookChange> {
|
|
97
|
+
class BybitV5BookChangeMapper implements Mapper<'bybit' | 'bybit-spot' | 'bybit-options' | 'bybit-spread', BookChange> {
|
|
94
98
|
private readonly _depth: number
|
|
95
99
|
|
|
96
100
|
constructor(
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { asNumberOrUndefined, parseISODateWithMicroseconds, upperCaseSymbols } from '../handy.ts'
|
|
2
|
+
import { BookChange, BookTicker, DerivativeTicker, Trade } from '../types.ts'
|
|
3
|
+
import { Mapper, PendingTickerInfoHelper } from './mapper.ts'
|
|
4
|
+
import { exchangeMappers } from './registry.ts'
|
|
5
|
+
|
|
6
|
+
export const coinbaseDerivativesMappers = exchangeMappers({
|
|
7
|
+
'coinbase-derivatives': {
|
|
8
|
+
trades: () => new CoinbaseDerivativesTradesMapper(),
|
|
9
|
+
bookChanges: () => new CoinbaseDerivativesBookChangeMapper(),
|
|
10
|
+
bookTickers: () => new CoinbaseDerivativesBookTickerMapper(),
|
|
11
|
+
derivativeTickers: () => new CoinbaseDerivativesTickerMapper()
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
class CoinbaseDerivativesTradesMapper implements Mapper<'coinbase-derivatives', Trade> {
|
|
16
|
+
// Besides the subscribe snapshot, Coinbase resends recent-trade snapshots mid-connection, each followed by an update with the preceding older trades.
|
|
17
|
+
private readonly _snapshotTradeIds = new Map<string, number>()
|
|
18
|
+
|
|
19
|
+
canHandle(message: CoinbaseDerivativesTrade) {
|
|
20
|
+
return message.channel === 'market_trades'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getFilters(symbols?: string[]) {
|
|
24
|
+
return [{ channel: 'market_trades', symbols: upperCaseSymbols(symbols) } as const]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
*map(message: CoinbaseDerivativesTrade, localTimestamp: Date): IterableIterator<Trade> {
|
|
28
|
+
for (const event of message.events) {
|
|
29
|
+
if (event.type === 'snapshot') {
|
|
30
|
+
for (const trade of event.trades) {
|
|
31
|
+
const tradeId = Number(trade.trade_id)
|
|
32
|
+
this._snapshotTradeIds.set(trade.product_id, Math.max(tradeId, this._snapshotTradeIds.get(trade.product_id) ?? tradeId))
|
|
33
|
+
}
|
|
34
|
+
continue
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (const trade of event.trades) {
|
|
38
|
+
if (Number(trade.trade_id) <= (this._snapshotTradeIds.get(trade.product_id) ?? 0)) {
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
yield {
|
|
43
|
+
type: 'trade',
|
|
44
|
+
exchange: 'coinbase-derivatives',
|
|
45
|
+
symbol: trade.product_id,
|
|
46
|
+
id: trade.trade_id,
|
|
47
|
+
price: Number(trade.price),
|
|
48
|
+
amount: Number(trade.size),
|
|
49
|
+
side: trade.side === 'BUY' ? 'sell' : 'buy',
|
|
50
|
+
timestamp: parseISODateWithMicroseconds(trade.time),
|
|
51
|
+
localTimestamp
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
class CoinbaseDerivativesBookChangeMapper implements Mapper<'coinbase-derivatives', BookChange> {
|
|
59
|
+
canHandle(message: CoinbaseDerivativesBook) {
|
|
60
|
+
return message.channel === 'l2_data'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getFilters(symbols?: string[]) {
|
|
64
|
+
return [{ channel: 'l2_data', symbols: upperCaseSymbols(symbols) } as const]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
*map(message: CoinbaseDerivativesBook, localTimestamp: Date): IterableIterator<BookChange> {
|
|
68
|
+
for (const event of message.events) {
|
|
69
|
+
yield {
|
|
70
|
+
type: 'book_change',
|
|
71
|
+
exchange: 'coinbase-derivatives',
|
|
72
|
+
symbol: event.product_id,
|
|
73
|
+
isSnapshot: event.type === 'snapshot',
|
|
74
|
+
bids: event.updates.filter((u) => u.side === 'bid').map(this.mapBookLevel),
|
|
75
|
+
asks: event.updates.filter((u) => u.side === 'offer').map(this.mapBookLevel),
|
|
76
|
+
timestamp: parseISODateWithMicroseconds(
|
|
77
|
+
event.updates.length > 0 ? event.updates[event.updates.length - 1].event_time : message.timestamp
|
|
78
|
+
),
|
|
79
|
+
localTimestamp
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private mapBookLevel(update: CoinbaseDerivativesBook['events'][number]['updates'][number]) {
|
|
85
|
+
return {
|
|
86
|
+
price: Number(update.price_level),
|
|
87
|
+
amount: Number(update.new_quantity)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
class CoinbaseDerivativesBookTickerMapper implements Mapper<'coinbase-derivatives', BookTicker> {
|
|
93
|
+
canHandle(message: CoinbaseDerivativesTicker) {
|
|
94
|
+
return message.channel === 'ticker'
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
getFilters(symbols?: string[]) {
|
|
98
|
+
return [{ channel: 'ticker', symbols: upperCaseSymbols(symbols) } as const]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
*map(message: CoinbaseDerivativesTicker, localTimestamp: Date): IterableIterator<BookTicker> {
|
|
102
|
+
for (const event of message.events)
|
|
103
|
+
for (const ticker of event.tickers) {
|
|
104
|
+
const bidPrice = asNumberOrUndefined(ticker.best_bid)
|
|
105
|
+
const askPrice = asNumberOrUndefined(ticker.best_ask)
|
|
106
|
+
|
|
107
|
+
if (bidPrice === undefined && askPrice === undefined) {
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
yield {
|
|
112
|
+
type: 'book_ticker',
|
|
113
|
+
exchange: 'coinbase-derivatives',
|
|
114
|
+
symbol: ticker.product_id,
|
|
115
|
+
bidPrice,
|
|
116
|
+
bidAmount: asNumberOrUndefined(ticker.best_bid_quantity),
|
|
117
|
+
askPrice,
|
|
118
|
+
askAmount: asNumberOrUndefined(ticker.best_ask_quantity),
|
|
119
|
+
timestamp: parseISODateWithMicroseconds(message.timestamp),
|
|
120
|
+
localTimestamp
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
class CoinbaseDerivativesTickerMapper implements Mapper<'coinbase-derivatives', DerivativeTicker> {
|
|
127
|
+
private readonly pending = new PendingTickerInfoHelper()
|
|
128
|
+
|
|
129
|
+
canHandle(message: CoinbaseDerivativesProducts | CoinbaseDerivativesTicker) {
|
|
130
|
+
return message.channel === 'products' || message.channel === 'ticker'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
getFilters(symbols?: string[]) {
|
|
134
|
+
return [
|
|
135
|
+
{ channel: 'products', symbols: upperCaseSymbols(symbols) } as const,
|
|
136
|
+
{ channel: 'ticker', symbols: upperCaseSymbols(symbols) } as const
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
*map(message: CoinbaseDerivativesProducts | CoinbaseDerivativesTicker, localTimestamp: Date) {
|
|
141
|
+
if (message.channel === 'ticker') {
|
|
142
|
+
for (const event of message.events)
|
|
143
|
+
for (const ticker of event.tickers) {
|
|
144
|
+
const info = this.pending.getPendingTickerInfo(ticker.product_id, 'coinbase-derivatives')
|
|
145
|
+
info.updateLastPrice(asNumberOrUndefined(ticker.price))
|
|
146
|
+
info.updateTimestamp(parseISODateWithMicroseconds(message.timestamp))
|
|
147
|
+
if (info.hasChanged()) {
|
|
148
|
+
yield info.getSnapshot(localTimestamp)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (const product of message.events) {
|
|
155
|
+
const info = this.pending.getPendingTickerInfo(product.product_id, 'coinbase-derivatives')
|
|
156
|
+
|
|
157
|
+
const details = product.future_product_details
|
|
158
|
+
info.updateOpenInterest(asNumberOrUndefined(details.open_interest))
|
|
159
|
+
info.updateFundingRate(asNumberOrUndefined(details.funding_rate))
|
|
160
|
+
info.updateIndexPrice(asNumberOrUndefined(details.index_price))
|
|
161
|
+
// funding_time identifies the published rate, not the next funding event. No mark price is supplied.
|
|
162
|
+
info.updateTimestamp(localTimestamp)
|
|
163
|
+
|
|
164
|
+
if (info.hasChanged()) {
|
|
165
|
+
yield info.getSnapshot(localTimestamp)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
type CoinbaseDerivativesMessage<Channel extends string, Event> = {
|
|
172
|
+
channel: Channel
|
|
173
|
+
events: Event[]
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
type CoinbaseDerivativesTrade = CoinbaseDerivativesMessage<
|
|
177
|
+
'market_trades',
|
|
178
|
+
{
|
|
179
|
+
type: CoinbaseDerivativesMessageType
|
|
180
|
+
trades: {
|
|
181
|
+
product_id: string
|
|
182
|
+
trade_id: string
|
|
183
|
+
price: string
|
|
184
|
+
size: string
|
|
185
|
+
side: 'BUY' | 'SELL'
|
|
186
|
+
time: string
|
|
187
|
+
}[]
|
|
188
|
+
}
|
|
189
|
+
>
|
|
190
|
+
type CoinbaseDerivativesBook = CoinbaseDerivativesMessage<
|
|
191
|
+
'l2_data',
|
|
192
|
+
{
|
|
193
|
+
type: CoinbaseDerivativesMessageType
|
|
194
|
+
product_id: string
|
|
195
|
+
updates: {
|
|
196
|
+
side: 'bid' | 'offer'
|
|
197
|
+
price_level: string
|
|
198
|
+
new_quantity: string
|
|
199
|
+
event_time: string
|
|
200
|
+
}[]
|
|
201
|
+
}
|
|
202
|
+
> & { timestamp: string }
|
|
203
|
+
|
|
204
|
+
type CoinbaseDerivativesTicker = CoinbaseDerivativesMessage<
|
|
205
|
+
'ticker',
|
|
206
|
+
{
|
|
207
|
+
tickers: {
|
|
208
|
+
product_id: string
|
|
209
|
+
price: string
|
|
210
|
+
best_bid?: string
|
|
211
|
+
best_ask?: string
|
|
212
|
+
best_bid_quantity?: string
|
|
213
|
+
best_ask_quantity?: string
|
|
214
|
+
}[]
|
|
215
|
+
}
|
|
216
|
+
> & { timestamp: string }
|
|
217
|
+
|
|
218
|
+
type CoinbaseDerivativesProducts = CoinbaseDerivativesMessage<
|
|
219
|
+
'products',
|
|
220
|
+
{
|
|
221
|
+
product_id: string
|
|
222
|
+
future_product_details: {
|
|
223
|
+
open_interest: string
|
|
224
|
+
funding_rate: string
|
|
225
|
+
index_price: string
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
> & { generated: true }
|
|
229
|
+
|
|
230
|
+
type CoinbaseDerivativesMessageType = 'snapshot' | 'update'
|
|
@@ -111,6 +111,8 @@ class DydxV4BookChangeMapper implements Mapper<'dydx-v4', BookChange> {
|
|
|
111
111
|
|
|
112
112
|
class DydxV4DerivativeTickerMapper implements Mapper<'dydx-v4', DerivativeTicker> {
|
|
113
113
|
private readonly pendingTickerInfoHelper = new PendingTickerInfoHelper()
|
|
114
|
+
// Oracle prices also cover price-only markets such as USDT-USD, so a price waits here until its market appears in a snapshot, trading update or trade.
|
|
115
|
+
private readonly _unconfirmedOraclePrices = new Map<string, number>()
|
|
114
116
|
|
|
115
117
|
canHandle(message: DydxMarketsSnapshot | DyDxMarketsUpdate | DyDxTrade) {
|
|
116
118
|
return message.channel === 'v4_markets' || (message.channel === 'v4_trades' && message.type === 'channel_data')
|
|
@@ -133,7 +135,7 @@ class DydxV4DerivativeTickerMapper implements Mapper<'dydx-v4', DerivativeTicker
|
|
|
133
135
|
|
|
134
136
|
*map(message: DydxMarketsSnapshot | DyDxMarketsUpdate | DyDxTrade, localTimestamp: Date): IterableIterator<DerivativeTicker> {
|
|
135
137
|
if (message.channel === 'v4_trades') {
|
|
136
|
-
const pendingTickerInfo = this.
|
|
138
|
+
const pendingTickerInfo = this._getMarketTickerInfo(message.id)
|
|
137
139
|
pendingTickerInfo.updateLastPrice(Number(message.contents.trades[message.contents.trades.length - 1].price))
|
|
138
140
|
|
|
139
141
|
return
|
|
@@ -143,7 +145,7 @@ class DydxV4DerivativeTickerMapper implements Mapper<'dydx-v4', DerivativeTicker
|
|
|
143
145
|
for (const key in contents) {
|
|
144
146
|
const marketInfo = (contents as any)[key] as DydxMarketsSnapshotContent | DydxMarketTradeUpdate
|
|
145
147
|
|
|
146
|
-
const pendingTickerInfo = this.
|
|
148
|
+
const pendingTickerInfo = this._getMarketTickerInfo(key)
|
|
147
149
|
|
|
148
150
|
if (marketInfo.oraclePrice !== undefined) {
|
|
149
151
|
pendingTickerInfo.updateMarkPrice(Number(marketInfo.oraclePrice))
|
|
@@ -167,6 +169,12 @@ class DydxV4DerivativeTickerMapper implements Mapper<'dydx-v4', DerivativeTicker
|
|
|
167
169
|
if (message.type === 'channel_data' && message.contents.oraclePrices !== undefined) {
|
|
168
170
|
for (const key in message.contents.oraclePrices) {
|
|
169
171
|
const oraclePriceInfo = (message.contents.oraclePrices as any)[key] as OraclePriceInfo
|
|
172
|
+
if (this.pendingTickerInfoHelper.hasPendingTickerInfo(key) === false) {
|
|
173
|
+
if (oraclePriceInfo.oraclePrice !== undefined) {
|
|
174
|
+
this._unconfirmedOraclePrices.set(key, Number(oraclePriceInfo.oraclePrice))
|
|
175
|
+
}
|
|
176
|
+
continue
|
|
177
|
+
}
|
|
170
178
|
const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(key, 'dydx-v4')
|
|
171
179
|
if (oraclePriceInfo.oraclePrice !== undefined) {
|
|
172
180
|
pendingTickerInfo.updateMarkPrice(Number(oraclePriceInfo.oraclePrice))
|
|
@@ -179,6 +187,16 @@ class DydxV4DerivativeTickerMapper implements Mapper<'dydx-v4', DerivativeTicker
|
|
|
179
187
|
}
|
|
180
188
|
}
|
|
181
189
|
}
|
|
190
|
+
|
|
191
|
+
private _getMarketTickerInfo(market: string) {
|
|
192
|
+
const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(market, 'dydx-v4')
|
|
193
|
+
const oraclePrice = this._unconfirmedOraclePrices.get(market)
|
|
194
|
+
if (oraclePrice !== undefined) {
|
|
195
|
+
pendingTickerInfo.updateMarkPrice(oraclePrice)
|
|
196
|
+
this._unconfirmedOraclePrices.delete(market)
|
|
197
|
+
}
|
|
198
|
+
return pendingTickerInfo
|
|
199
|
+
}
|
|
182
200
|
}
|
|
183
201
|
|
|
184
202
|
class DydxV4LiquidationsMapper implements Mapper<'dydx-v4', Liquidation> {
|
|
@@ -132,7 +132,7 @@ class FTXDerivativeTickerMapper implements Mapper<'ftx', DerivativeTicker> {
|
|
|
132
132
|
return [
|
|
133
133
|
{
|
|
134
134
|
channel: 'instrument',
|
|
135
|
-
symbols
|
|
135
|
+
symbols
|
|
136
136
|
} as const
|
|
137
137
|
]
|
|
138
138
|
}
|
|
@@ -16,6 +16,7 @@ import { blockchainComMappers } from './blockchaincom.ts'
|
|
|
16
16
|
import { bullishMappers } from './bullish.ts'
|
|
17
17
|
import { bybitMappers } from './bybit.ts'
|
|
18
18
|
import { coinbaseMappers } from './coinbase.ts'
|
|
19
|
+
import { coinbaseDerivativesMappers } from './coinbasederivatives.ts'
|
|
19
20
|
import { coinbaseInternationalMappers } from './coinbaseinternational.ts'
|
|
20
21
|
import { coinflexMappers } from './coinflex.ts'
|
|
21
22
|
import { cryptoComMappers } from './cryptocom.ts'
|
|
@@ -74,6 +75,7 @@ const registeredMappers = mergeExchangeMappers(
|
|
|
74
75
|
bullishMappers,
|
|
75
76
|
bybitMappers,
|
|
76
77
|
coinbaseMappers,
|
|
78
|
+
coinbaseDerivativesMappers,
|
|
77
79
|
coinbaseInternationalMappers,
|
|
78
80
|
coinflexMappers,
|
|
79
81
|
cryptoComMappers,
|
|
@@ -1,10 +1,57 @@
|
|
|
1
|
+
import { Writable } from 'stream'
|
|
1
2
|
import { getJSON } from '../handy.ts'
|
|
2
3
|
import { Filter } from '../types.ts'
|
|
3
|
-
import { RealTimeFeedBase } from './realtimefeed.ts'
|
|
4
|
+
import { MultiConnectionRealTimeFeedBase, PoolingClientBase, RealTimeFeedBase } from './realtimefeed.ts'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
const BITSTAMP_HTTP_URL = 'https://www.bitstamp.net/api/v2'
|
|
7
|
+
|
|
8
|
+
export class BitstampRealTimeFeed extends MultiConnectionRealTimeFeedBase {
|
|
9
|
+
protected *_getRealTimeFeeds(exchange: string, filters: Filter<string>[], timeoutIntervalMS?: number, onError?: (error: Error) => void) {
|
|
10
|
+
const wsFilters = filters.filter((f) => f.channel !== 'ticker')
|
|
11
|
+
if (wsFilters.length > 0) {
|
|
12
|
+
yield new BitstampWebSocketRealTimeFeed(exchange, wsFilters, timeoutIntervalMS, onError)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const tickerFilters = filters.filter((f) => f.channel === 'ticker')
|
|
16
|
+
if (tickerFilters.length > 0) {
|
|
17
|
+
yield new BitstampTickerClient(
|
|
18
|
+
exchange,
|
|
19
|
+
tickerFilters.flatMap((f) => f.symbols ?? []),
|
|
20
|
+
onError
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Bitstamp WebSocket has no ticker channel; the recorder polls the REST ticker and stores it in this generated message shape.
|
|
27
|
+
class BitstampTickerClient extends PoolingClientBase {
|
|
28
|
+
constructor(
|
|
29
|
+
exchange: string,
|
|
30
|
+
private readonly _symbols: string[],
|
|
31
|
+
onError?: (error: Error) => void
|
|
32
|
+
) {
|
|
33
|
+
super(exchange, 6, onError)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
protected async poolDataToStream(outputStream: Writable) {
|
|
37
|
+
const { data: tickers } = await getJSON<{ market: string }[]>(`${BITSTAMP_HTTP_URL}/ticker/`, { timeout: 10000 })
|
|
38
|
+
|
|
39
|
+
for (const ticker of tickers) {
|
|
40
|
+
const symbol = ticker.market.replace('/', '').toLowerCase()
|
|
41
|
+
if (this._symbols.length > 0 && this._symbols.includes(symbol) === false) {
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (outputStream.writable) {
|
|
46
|
+
outputStream.write({ data: ticker, channel: `ticker_${symbol}`, event: 'ticker', generated: true })
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
class BitstampWebSocketRealTimeFeed extends RealTimeFeedBase {
|
|
6
53
|
protected wssURL = 'wss://ws.bitstamp.net'
|
|
7
|
-
protected httpURL =
|
|
54
|
+
protected httpURL = BITSTAMP_HTTP_URL
|
|
8
55
|
|
|
9
56
|
protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
|
|
10
57
|
return filters
|
|
@@ -90,6 +90,10 @@ export class BybitSpotRealTimeDataFeed extends BybitSingleConnectionRealTimeData
|
|
|
90
90
|
protected wssURL: string = 'wss://stream.bybit.com/v5/public/spot'
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
export class BybitSpreadRealTimeDataFeed extends BybitSingleConnectionRealTimeDataFeed {
|
|
94
|
+
protected wssURL: string = 'wss://stream.bybit.com/v5/public/spread'
|
|
95
|
+
}
|
|
96
|
+
|
|
93
97
|
export class BybitOptionsRealTimeDataFeed extends BybitSingleConnectionRealTimeDataFeed {
|
|
94
98
|
protected wssURL: string = 'wss://stream.bybit.com/v5/public/option'
|
|
95
99
|
|