tardis-machine 18.4.0 → 18.4.2
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 +9 -0
- package/dist/ws/subscriptionsmappers.js.map +1 -1
- package/node_modules/tardis-dev/dist/consts.d.ts +4 -3
- package/node_modules/tardis-dev/dist/consts.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/consts.js +6 -1
- 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 +2 -2
- 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 +10 -10
- 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/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 +198 -186
- 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/binance.d.ts.map +1 -1
- package/node_modules/tardis-dev/dist/realtimefeeds/binance.js +6 -1
- package/node_modules/tardis-dev/dist/realtimefeeds/binance.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 +2 -0
- 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 +6 -1
- package/node_modules/tardis-dev/src/handy.ts +20 -11
- 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/index.ts +2 -0
- package/node_modules/tardis-dev/src/realtimefeeds/binance.ts +7 -1
- package/node_modules/tardis-dev/src/realtimefeeds/coinbasederivatives.ts +103 -0
- package/node_modules/tardis-dev/src/realtimefeeds/index.ts +2 -0
- package/package.json +2 -2
- package/src/ws/subscriptionsmappers.ts +11 -0
|
@@ -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> {
|
|
@@ -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,
|
|
@@ -437,7 +437,13 @@ export class BinanceFuturesRealTimeFeed extends BinanceRealTimeFeedBase {
|
|
|
437
437
|
|
|
438
438
|
protected *_getRealTimeFeeds(exchange: string, filters: Filter<string>[], timeoutIntervalMS?: number, onError?: (error: Error) => void) {
|
|
439
439
|
const wsFilters = filters.filter(
|
|
440
|
-
(f) =>
|
|
440
|
+
(f) =>
|
|
441
|
+
f.channel !== 'openInterest' &&
|
|
442
|
+
f.channel !== 'recentTrades' &&
|
|
443
|
+
f.channel !== 'fundingInfo' &&
|
|
444
|
+
f.channel !== 'fundingRate' &&
|
|
445
|
+
f.channel !== 'insuranceBalance' &&
|
|
446
|
+
f.channel !== 'tradingSession'
|
|
441
447
|
)
|
|
442
448
|
|
|
443
449
|
const publicWsFilters = wsFilters.filter((f) => BINANCE_FUTURES_PUBLIC_CHANNELS.has(f.channel))
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Writable } from 'node:stream'
|
|
2
|
+
import { batch, getJSON, optimizeFilters } from '../handy.ts'
|
|
3
|
+
import { Filter } from '../types.ts'
|
|
4
|
+
import { MultiConnectionRealTimeFeedBase, PoolingClientBase, RealTimeFeedBase } from './realtimefeed.ts'
|
|
5
|
+
|
|
6
|
+
export class CoinbaseDerivativesRealTimeFeed extends MultiConnectionRealTimeFeedBase {
|
|
7
|
+
protected *_getRealTimeFeeds(exchange: string, filters: Filter<string>[], timeoutIntervalMS?: number, onError?: (error: Error) => void) {
|
|
8
|
+
const wsFilters = optimizeFilters(filters.filter((f) => f.channel !== 'products'))
|
|
9
|
+
for (const filter of wsFilters) {
|
|
10
|
+
if (!['heartbeats', 'subscriptions'].includes(filter.channel) && !filter.symbols?.length) {
|
|
11
|
+
throw new Error('Coinbase derivatives WebSocket subscriptions require explicit symbols')
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const symbols = [...new Set(wsFilters.flatMap((f) => f.symbols ?? []))]
|
|
16
|
+
const groups: Iterable<string[]> = symbols.length > 0 ? batch(symbols, 25) : wsFilters.length > 0 ? [[]] : []
|
|
17
|
+
for (const group of groups) {
|
|
18
|
+
const groupedFilters = wsFilters
|
|
19
|
+
.map((f) => ({
|
|
20
|
+
...f,
|
|
21
|
+
symbols: f.symbols?.filter((s) => group.includes(s))
|
|
22
|
+
}))
|
|
23
|
+
.filter((f) => f.symbols?.length || ['heartbeats', 'subscriptions'].includes(f.channel))
|
|
24
|
+
yield new CoinbaseDerivativesSingleConnectionRealTimeFeed(exchange, groupedFilters, timeoutIntervalMS, onError)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const productFilters = filters.filter((f) => f.channel === 'products')
|
|
28
|
+
if (productFilters.length > 0) {
|
|
29
|
+
const allProducts = productFilters.some((f) => !f.symbols?.length)
|
|
30
|
+
yield new CoinbaseDerivativesProductsClient(
|
|
31
|
+
exchange,
|
|
32
|
+
allProducts ? undefined : new Set(productFilters.flatMap((f) => f.symbols!)),
|
|
33
|
+
onError
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class CoinbaseDerivativesSingleConnectionRealTimeFeed extends RealTimeFeedBase {
|
|
40
|
+
protected wssURL = 'wss://advanced-trade-ws.coinbase.com'
|
|
41
|
+
private sequence?: number
|
|
42
|
+
|
|
43
|
+
protected mapToSubscribeMessages(filters: Filter<string>[]) {
|
|
44
|
+
return [
|
|
45
|
+
{
|
|
46
|
+
type: 'subscribe',
|
|
47
|
+
channel: 'heartbeats'
|
|
48
|
+
},
|
|
49
|
+
...filters
|
|
50
|
+
.filter((f) => !['heartbeats', 'subscriptions'].includes(f.channel))
|
|
51
|
+
.map((f) => ({
|
|
52
|
+
type: 'subscribe',
|
|
53
|
+
channel: f.channel === 'l2_data' ? 'level2' : f.channel,
|
|
54
|
+
product_ids: f.symbols
|
|
55
|
+
}))
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
protected async onConnected() {
|
|
60
|
+
this.sequence = undefined
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
protected messageIsError(message: { type?: string }) {
|
|
64
|
+
return message.type === 'error'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
protected onMessage(message: { sequence_num?: number }) {
|
|
68
|
+
if (message.sequence_num === undefined) return
|
|
69
|
+
if (this.sequence !== undefined && message.sequence_num !== this.sequence + 1) {
|
|
70
|
+
throw new Error(`Coinbase derivatives sequence gap: ${this.sequence} -> ${message.sequence_num}`)
|
|
71
|
+
}
|
|
72
|
+
this.sequence = message.sequence_num
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class CoinbaseDerivativesProductsClient extends PoolingClientBase {
|
|
77
|
+
constructor(
|
|
78
|
+
exchange: string,
|
|
79
|
+
private readonly symbols: Set<string> | undefined,
|
|
80
|
+
onError?: (error: Error) => void
|
|
81
|
+
) {
|
|
82
|
+
super(exchange, 5, onError)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
protected async poolDataToStream(output: Writable) {
|
|
86
|
+
for (let offset = 0; !output.destroyed; offset += 100) {
|
|
87
|
+
const { data } = await getJSON<{ products: Product[]; pagination: { has_next: boolean } }>(
|
|
88
|
+
`https://api.coinbase.com/api/v3/brokerage/market/products?product_type=FUTURE&limit=100&offset=${offset}`
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
for (const product of data.products) {
|
|
92
|
+
if (output.destroyed) return
|
|
93
|
+
|
|
94
|
+
if (this.symbols === undefined || this.symbols.has(product.product_id)) {
|
|
95
|
+
output.write({ channel: 'products', generated: true, events: [product] })
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!data.pagination.has_next) return
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
type Product = { product_id: string }
|
|
@@ -15,6 +15,7 @@ import { BitstampRealTimeFeed } from './bitstamp.ts'
|
|
|
15
15
|
import { BitvavoRealTimeFeed } from './bitvavo.ts'
|
|
16
16
|
import { BybitOptionsRealTimeDataFeed, BybitRealTimeDataFeed, BybitSpotRealTimeDataFeed, BybitSpreadRealTimeDataFeed } from './bybit.ts'
|
|
17
17
|
import { CoinbaseRealTimeFeed } from './coinbase.ts'
|
|
18
|
+
import { CoinbaseDerivativesRealTimeFeed } from './coinbasederivatives.ts'
|
|
18
19
|
import { CryptofacilitiesRealTimeFeed } from './cryptofacilities.ts'
|
|
19
20
|
import { DeribitRealTimeDataFeed } from './deribit.ts'
|
|
20
21
|
import { FtxRealTimeFeed, FtxUSRealTimeFeed } from './ftx.ts'
|
|
@@ -78,6 +79,7 @@ const realTimeFeedsMap: {
|
|
|
78
79
|
bitstamp: BitstampRealTimeFeed,
|
|
79
80
|
bitvavo: BitvavoRealTimeFeed,
|
|
80
81
|
coinbase: CoinbaseRealTimeFeed,
|
|
82
|
+
'coinbase-derivatives': CoinbaseDerivativesRealTimeFeed,
|
|
81
83
|
cryptofacilities: CryptofacilitiesRealTimeFeed,
|
|
82
84
|
deribit: DeribitRealTimeDataFeed,
|
|
83
85
|
ftx: FtxRealTimeFeed,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tardis-machine",
|
|
3
|
-
"version": "18.4.
|
|
3
|
+
"version": "18.4.2",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=24.19"
|
|
6
6
|
},
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"license": "MPL-2.0",
|
|
77
77
|
"dependencies": {
|
|
78
78
|
"debug": "^4.4.3",
|
|
79
|
-
"tardis-dev": "^18.4.
|
|
79
|
+
"tardis-dev": "^18.4.2",
|
|
80
80
|
"ws": "^8.21.3"
|
|
81
81
|
},
|
|
82
82
|
"bundleDependencies": true,
|
|
@@ -58,6 +58,12 @@ const coinbaseMaper: SubscriptionMapper = {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// https://docs.cdp.coinbase.com/coinbase-app/advanced-trade-apis/websocket/websocket-overview
|
|
62
|
+
const coinbaseDerivativesMapper: SubscriptionMapper = {
|
|
63
|
+
canHandle: (message: any) => message.type === 'subscribe',
|
|
64
|
+
map: (message: any) => [{ channel: message.channel === 'level2' ? 'l2_data' : message.channel, symbols: message.product_ids }]
|
|
65
|
+
}
|
|
66
|
+
|
|
61
67
|
// https://docs.deribit.com/v2/#subscription-management
|
|
62
68
|
const deribitMapper: SubscriptionMapper = {
|
|
63
69
|
canHandle: (message: any) => {
|
|
@@ -220,6 +226,10 @@ const binanceMapper: SubscriptionMapper = {
|
|
|
220
226
|
const lastSeparator = param.lastIndexOf('@')
|
|
221
227
|
const firstSeparator = param.indexOf('@')
|
|
222
228
|
|
|
229
|
+
if (firstSeparator === -1) {
|
|
230
|
+
return { channel: param, symbols: undefined }
|
|
231
|
+
}
|
|
232
|
+
|
|
223
233
|
return {
|
|
224
234
|
channel: param.slice(firstSeparator + 1, lastSeparator == firstSeparator ? undefined : lastSeparator),
|
|
225
235
|
symbols: [param.slice(0, firstSeparator)]
|
|
@@ -811,6 +821,7 @@ const polymarketMapper: SubscriptionMapper = {
|
|
|
811
821
|
export const subscriptionsMappers: Record<Exchange, SubscriptionMapper> = {
|
|
812
822
|
bitmex: bitmexMapper,
|
|
813
823
|
coinbase: coinbaseMaper,
|
|
824
|
+
'coinbase-derivatives': coinbaseDerivativesMapper,
|
|
814
825
|
deribit: deribitMapper,
|
|
815
826
|
cryptofacilities: cryptofacilitiesMapper,
|
|
816
827
|
bitstamp: bitstampMapper,
|