tardis-dev 13.0.0 → 13.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/consts.d.ts +4 -4
- package/dist/consts.d.ts.map +1 -1
- package/dist/consts.js +58 -4
- package/dist/consts.js.map +1 -1
- package/dist/mappers/index.d.ts +3 -3
- package/dist/mappers/index.d.ts.map +1 -1
- package/dist/mappers/index.js +43 -23
- package/dist/mappers/index.js.map +1 -1
- package/dist/mappers/okex.d.ts +244 -0
- package/dist/mappers/okex.d.ts.map +1 -1
- package/dist/mappers/okex.js +384 -1
- package/dist/mappers/okex.js.map +1 -1
- package/dist/realtimefeeds/okex.d.ts +4 -2
- package/dist/realtimefeeds/okex.d.ts.map +1 -1
- package/dist/realtimefeeds/okex.js +38 -10
- package/dist/realtimefeeds/okex.js.map +1 -1
- package/package.json +1 -1
- package/src/consts.ts +62 -4
- package/src/mappers/index.ts +77 -24
- package/src/mappers/okex.ts +565 -0
- package/src/realtimefeeds/okex.ts +42 -10
package/src/mappers/okex.ts
CHANGED
|
@@ -2,6 +2,571 @@ import { asNumberIfValid } from '../handy'
|
|
|
2
2
|
import { BookChange, DerivativeTicker, Exchange, Trade, OptionSummary, Liquidation, BookTicker } from '../types'
|
|
3
3
|
import { Mapper, PendingTickerInfoHelper } from './mapper'
|
|
4
4
|
|
|
5
|
+
// V5 Okex API mappers
|
|
6
|
+
// https://www.okex.com/docs-v5/en/#websocket-api-public-channel-trades-channel
|
|
7
|
+
|
|
8
|
+
export class OkexV5TradesMapper implements Mapper<OKEX_EXCHANGES, Trade> {
|
|
9
|
+
constructor(private readonly _exchange: Exchange) {}
|
|
10
|
+
|
|
11
|
+
canHandle(message: any) {
|
|
12
|
+
if (message.event !== undefined || message.arg === undefined) {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
return message.arg.channel === 'trades'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getFilters(symbols?: string[]) {
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
channel: `trades` as const,
|
|
22
|
+
symbols
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
*map(okexTradesMessage: OkexV5TradeMessage, localTimestamp: Date): IterableIterator<Trade> {
|
|
28
|
+
for (const okexTrade of okexTradesMessage.data) {
|
|
29
|
+
yield {
|
|
30
|
+
type: 'trade',
|
|
31
|
+
symbol: okexTrade.instId,
|
|
32
|
+
exchange: this._exchange,
|
|
33
|
+
id: okexTrade.tradeId,
|
|
34
|
+
price: Number(okexTrade.px),
|
|
35
|
+
amount: Number(okexTrade.sz),
|
|
36
|
+
side: okexTrade.side === 'buy' ? 'buy' : 'sell',
|
|
37
|
+
timestamp: new Date(Number(okexTrade.ts)),
|
|
38
|
+
localTimestamp: localTimestamp
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const mapV5BookLevel = (level: OkexV5BookLevel) => {
|
|
45
|
+
const price = Number(level[0])
|
|
46
|
+
const amount = Number(level[1])
|
|
47
|
+
|
|
48
|
+
return { price, amount }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class OkexV5BookChangeMapper implements Mapper<OKEX_EXCHANGES, BookChange> {
|
|
52
|
+
constructor(private readonly _exchange: Exchange) {}
|
|
53
|
+
|
|
54
|
+
canHandle(message: any) {
|
|
55
|
+
if (message.event !== undefined || message.arg === undefined) {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
return message.arg.channel === 'books-l2-tbt'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getFilters(symbols?: string[]) {
|
|
62
|
+
return [
|
|
63
|
+
{
|
|
64
|
+
channel: `books-l2-tbt` as const,
|
|
65
|
+
symbols
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
*map(okexDepthDataMessage: OkexV5BookMessage, localTimestamp: Date): IterableIterator<BookChange> {
|
|
71
|
+
for (const message of okexDepthDataMessage.data) {
|
|
72
|
+
if (okexDepthDataMessage.action === 'update' && message.bids.length === 0 && message.asks.length === 0) {
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const timestamp = new Date(Number(message.ts))
|
|
77
|
+
|
|
78
|
+
if (timestamp.valueOf() === 0) {
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
yield {
|
|
83
|
+
type: 'book_change',
|
|
84
|
+
symbol: okexDepthDataMessage.arg.instId,
|
|
85
|
+
exchange: this._exchange,
|
|
86
|
+
isSnapshot: okexDepthDataMessage.action === 'snapshot',
|
|
87
|
+
bids: message.bids.map(mapV5BookLevel),
|
|
88
|
+
asks: message.asks.map(mapV5BookLevel),
|
|
89
|
+
timestamp,
|
|
90
|
+
localTimestamp: localTimestamp
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export class OkexV5BookTickerMapper implements Mapper<OKEX_EXCHANGES, BookTicker> {
|
|
97
|
+
constructor(private readonly _exchange: Exchange) {}
|
|
98
|
+
|
|
99
|
+
canHandle(message: any) {
|
|
100
|
+
if (message.event !== undefined || message.arg === undefined) {
|
|
101
|
+
return false
|
|
102
|
+
}
|
|
103
|
+
return message.arg.channel === 'tickers'
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getFilters(symbols?: string[]) {
|
|
107
|
+
return [
|
|
108
|
+
{
|
|
109
|
+
channel: `tickers` as const,
|
|
110
|
+
symbols
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
*map(message: OkexV5TickerMessage, localTimestamp: Date): IterableIterator<BookTicker> {
|
|
116
|
+
for (const okexTicker of message.data) {
|
|
117
|
+
const ticker: BookTicker = {
|
|
118
|
+
type: 'book_ticker',
|
|
119
|
+
symbol: okexTicker.instId,
|
|
120
|
+
exchange: this._exchange,
|
|
121
|
+
|
|
122
|
+
askAmount: asNumberIfValid(okexTicker.askSz),
|
|
123
|
+
askPrice: asNumberIfValid(okexTicker.askPx),
|
|
124
|
+
|
|
125
|
+
bidPrice: asNumberIfValid(okexTicker.bidPx),
|
|
126
|
+
bidAmount: asNumberIfValid(okexTicker.bidSz),
|
|
127
|
+
timestamp: new Date(Number(okexTicker.ts)),
|
|
128
|
+
localTimestamp: localTimestamp
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
yield ticker
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export class OkexV5DerivativeTickerMapper implements Mapper<'okex-futures' | 'okex-swap', DerivativeTicker> {
|
|
137
|
+
private readonly pendingTickerInfoHelper = new PendingTickerInfoHelper()
|
|
138
|
+
private readonly _indexPrices = new Map<string, number>()
|
|
139
|
+
|
|
140
|
+
private _futuresChannels = ['tickers', 'open-interest', 'mark-price', 'index-tickers'] as const
|
|
141
|
+
|
|
142
|
+
private _swapChannels = ['tickers', 'open-interest', 'mark-price', 'index-tickers', 'funding-rate'] as const
|
|
143
|
+
|
|
144
|
+
constructor(private readonly _exchange: Exchange) {}
|
|
145
|
+
|
|
146
|
+
canHandle(message: any) {
|
|
147
|
+
const channels = this._exchange === 'okex-futures' ? this._futuresChannels : this._swapChannels
|
|
148
|
+
|
|
149
|
+
if (message.event !== undefined || message.arg === undefined) {
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return channels.includes(message.arg.channel)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
getFilters(symbols?: string[]) {
|
|
157
|
+
const channels = this._exchange === 'okex-futures' ? this._futuresChannels : this._swapChannels
|
|
158
|
+
return channels.map((channel) => {
|
|
159
|
+
if (channel === 'index-tickers') {
|
|
160
|
+
const indexes =
|
|
161
|
+
symbols !== undefined
|
|
162
|
+
? symbols.map((s) => {
|
|
163
|
+
const symbolParts = s.split('-')
|
|
164
|
+
return `${symbolParts[0]}-${symbolParts[1]}`
|
|
165
|
+
})
|
|
166
|
+
: undefined
|
|
167
|
+
return {
|
|
168
|
+
channel,
|
|
169
|
+
symbols: indexes
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
channel,
|
|
175
|
+
symbols
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
*map(
|
|
181
|
+
message: OkexV5TickerMessage | OkexV5OpenInterestMessage | OkexV5MarkPriceMessage | OkexV5IndexTickerMessage | OkexV5FundingRateMessage,
|
|
182
|
+
localTimestamp: Date
|
|
183
|
+
): IterableIterator<DerivativeTicker> {
|
|
184
|
+
if (message.arg.channel === 'index-tickers') {
|
|
185
|
+
for (const dataMessage of message.data) {
|
|
186
|
+
const indexTickerMessage = dataMessage as OkexV5IndexTickerMessage['data'][0]
|
|
187
|
+
|
|
188
|
+
const lastIndexPrice = Number(indexTickerMessage.idxPx)
|
|
189
|
+
if (lastIndexPrice > 0) {
|
|
190
|
+
this._indexPrices.set(indexTickerMessage.instId, lastIndexPrice)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
for (const dataMessage of message.data) {
|
|
198
|
+
const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(dataMessage.instId, this._exchange)
|
|
199
|
+
const symbolParts = dataMessage.instId.split('-')
|
|
200
|
+
const indexSymbol = `${symbolParts[0]}-${symbolParts[1]}`
|
|
201
|
+
|
|
202
|
+
const indexPrice = this._indexPrices.get(indexSymbol)
|
|
203
|
+
|
|
204
|
+
if (indexPrice !== undefined) {
|
|
205
|
+
pendingTickerInfo.updateIndexPrice(indexPrice)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (message.arg.channel === 'mark-price') {
|
|
209
|
+
const markPriceMessage = dataMessage as OkexV5MarkPriceMessage['data'][0]
|
|
210
|
+
|
|
211
|
+
const markPrice = Number(markPriceMessage.markPx)
|
|
212
|
+
if (markPrice > 0) {
|
|
213
|
+
pendingTickerInfo.updateMarkPrice(markPrice)
|
|
214
|
+
pendingTickerInfo.updateTimestamp(new Date(Number(markPriceMessage.ts)))
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (message.arg.channel === 'open-interest') {
|
|
219
|
+
const openInterestMessage = dataMessage as OkexV5OpenInterestMessage['data'][0]
|
|
220
|
+
|
|
221
|
+
const openInterest = Number(openInterestMessage.oi)
|
|
222
|
+
if (openInterest > 0) {
|
|
223
|
+
pendingTickerInfo.updateOpenInterest(openInterest)
|
|
224
|
+
pendingTickerInfo.updateTimestamp(new Date(Number(openInterestMessage.ts)))
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (message.arg.channel === 'funding-rate') {
|
|
229
|
+
const fundingRateMessage = dataMessage as OkexV5FundingRateMessage['data'][0]
|
|
230
|
+
|
|
231
|
+
pendingTickerInfo.updateFundingRate(Number(fundingRateMessage.fundingRate))
|
|
232
|
+
//
|
|
233
|
+
pendingTickerInfo.updateFundingTimestamp(new Date(Number(fundingRateMessage.fundingTime)))
|
|
234
|
+
|
|
235
|
+
if (fundingRateMessage.nextFundingRate !== undefined) {
|
|
236
|
+
pendingTickerInfo.updatePredictedFundingRate(Number(fundingRateMessage.nextFundingRate))
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (message.arg.channel === 'tickers') {
|
|
241
|
+
const tickerMessage = dataMessage as OkexV5TickerMessage['data'][0]
|
|
242
|
+
|
|
243
|
+
const lastPrice = Number(tickerMessage.last)
|
|
244
|
+
|
|
245
|
+
if (lastPrice > 0) {
|
|
246
|
+
pendingTickerInfo.updateLastPrice(lastPrice)
|
|
247
|
+
pendingTickerInfo.updateTimestamp(new Date(Number(tickerMessage.ts)))
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (pendingTickerInfo.hasChanged()) {
|
|
252
|
+
yield pendingTickerInfo.getSnapshot(localTimestamp)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export class OkexV5LiquidationsMapper implements Mapper<OKEX_EXCHANGES, Liquidation> {
|
|
259
|
+
constructor(private readonly _exchange: Exchange) {}
|
|
260
|
+
|
|
261
|
+
canHandle(message: any) {
|
|
262
|
+
if (message.event !== undefined || message.arg === undefined) {
|
|
263
|
+
return false
|
|
264
|
+
}
|
|
265
|
+
return message.arg.channel === 'liquidations'
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
getFilters(symbols?: string[]) {
|
|
269
|
+
return [
|
|
270
|
+
{
|
|
271
|
+
channel: 'liquidations',
|
|
272
|
+
symbols
|
|
273
|
+
} as any
|
|
274
|
+
]
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
*map(okexLiquidationMessage: OkexV5LiquidationMessage, localTimestamp: Date): IterableIterator<Liquidation> {
|
|
278
|
+
for (const okexLiquidation of okexLiquidationMessage.data) {
|
|
279
|
+
const liquidation: Liquidation = {
|
|
280
|
+
type: 'liquidation',
|
|
281
|
+
symbol: okexLiquidationMessage.arg.instId,
|
|
282
|
+
exchange: this._exchange,
|
|
283
|
+
id: undefined,
|
|
284
|
+
price: Number(okexLiquidation.bkPx),
|
|
285
|
+
amount: Number(okexLiquidation.sz),
|
|
286
|
+
side: okexLiquidation.side === 'buy' ? 'buy' : 'sell',
|
|
287
|
+
timestamp: new Date(Number(okexLiquidation.ts)),
|
|
288
|
+
localTimestamp: localTimestamp
|
|
289
|
+
}
|
|
290
|
+
yield liquidation
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export class OkexV5OptionSummaryMapper implements Mapper<'okex-options', OptionSummary> {
|
|
296
|
+
private readonly _indexPrices = new Map<string, number>()
|
|
297
|
+
private readonly _openInterests = new Map<string, number>()
|
|
298
|
+
private readonly _markPrices = new Map<string, number>()
|
|
299
|
+
|
|
300
|
+
private readonly _tickers = new Map<string, OkexV5TickerMessage['data'][0]>()
|
|
301
|
+
private readonly expiration_regex = /(\d{2})(\d{2})(\d{2})/
|
|
302
|
+
|
|
303
|
+
canHandle(message: any) {
|
|
304
|
+
if (message.event !== undefined || message.arg === undefined) {
|
|
305
|
+
return false
|
|
306
|
+
}
|
|
307
|
+
return (
|
|
308
|
+
message.arg.channel === 'opt-summary' ||
|
|
309
|
+
message.arg.channel === 'index-tickers' ||
|
|
310
|
+
message.arg.channel === 'tickers' ||
|
|
311
|
+
message.arg.channel === 'open-interest' ||
|
|
312
|
+
message.arg.channel === 'mark-price'
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
getFilters(symbols?: string[]) {
|
|
317
|
+
const indexes =
|
|
318
|
+
symbols !== undefined
|
|
319
|
+
? symbols.map((s) => {
|
|
320
|
+
const symbolParts = s.split('-')
|
|
321
|
+
return `${symbolParts[0]}-${symbolParts[1]}`
|
|
322
|
+
})
|
|
323
|
+
: undefined
|
|
324
|
+
|
|
325
|
+
return [
|
|
326
|
+
{
|
|
327
|
+
channel: `opt-summary`,
|
|
328
|
+
symbols: [] as string[]
|
|
329
|
+
} as const,
|
|
330
|
+
{
|
|
331
|
+
channel: `index-tickers`,
|
|
332
|
+
symbols: indexes
|
|
333
|
+
} as const,
|
|
334
|
+
{
|
|
335
|
+
channel: `tickers`,
|
|
336
|
+
symbols: symbols
|
|
337
|
+
} as const,
|
|
338
|
+
{
|
|
339
|
+
channel: `open-interest`,
|
|
340
|
+
symbols: symbols
|
|
341
|
+
} as const,
|
|
342
|
+
{
|
|
343
|
+
channel: `mark-price`,
|
|
344
|
+
symbols: symbols
|
|
345
|
+
} as const
|
|
346
|
+
]
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
*map(
|
|
350
|
+
message: OkexV5SummaryMessage | OkexV5IndexTickerMessage | OkexV5TickerMessage | OkexV5OpenInterestMessage | OkexV5MarkPriceMessage,
|
|
351
|
+
localTimestamp: Date
|
|
352
|
+
): IterableIterator<OptionSummary> | undefined {
|
|
353
|
+
if (message.arg.channel === 'index-tickers') {
|
|
354
|
+
for (const dataMessage of message.data) {
|
|
355
|
+
const indexTickerMessage = dataMessage as OkexV5IndexTickerMessage['data'][0]
|
|
356
|
+
|
|
357
|
+
const lastIndexPrice = Number(indexTickerMessage.idxPx)
|
|
358
|
+
if (lastIndexPrice > 0) {
|
|
359
|
+
this._indexPrices.set(indexTickerMessage.instId, lastIndexPrice)
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (message.arg.channel === 'open-interest') {
|
|
366
|
+
for (const dataMessage of message.data) {
|
|
367
|
+
const openInterestMessage = dataMessage as OkexV5OpenInterestMessage['data'][0]
|
|
368
|
+
|
|
369
|
+
const openInterestValue = Number(openInterestMessage.oi)
|
|
370
|
+
if (openInterestValue > 0) {
|
|
371
|
+
this._openInterests.set(openInterestMessage.instId, openInterestValue)
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (message.arg.channel === 'mark-price') {
|
|
378
|
+
for (const dataMessage of message.data) {
|
|
379
|
+
const markPriceMessage = dataMessage as OkexV5MarkPriceMessage['data'][0]
|
|
380
|
+
|
|
381
|
+
const markPrice = Number(markPriceMessage.markPx)
|
|
382
|
+
if (markPrice > 0) {
|
|
383
|
+
this._markPrices.set(markPriceMessage.instId, markPrice)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (message.arg.channel === 'tickers') {
|
|
390
|
+
for (const dataMessage of message.data) {
|
|
391
|
+
const tickerMessage = dataMessage as OkexV5TickerMessage['data'][0]
|
|
392
|
+
|
|
393
|
+
this._tickers.set(tickerMessage.instId, tickerMessage)
|
|
394
|
+
}
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (message.arg.channel === 'opt-summary') {
|
|
399
|
+
for (const dataMessage of message.data) {
|
|
400
|
+
const summary = dataMessage as OkexV5SummaryMessage['data'][0]
|
|
401
|
+
|
|
402
|
+
const symbolParts = summary.instId.split('-')
|
|
403
|
+
const isPut = symbolParts[4] === 'P'
|
|
404
|
+
const strikePrice = Number(symbolParts[3])
|
|
405
|
+
|
|
406
|
+
var dateArray = this.expiration_regex.exec(symbolParts[2])!
|
|
407
|
+
|
|
408
|
+
const expirationDate = new Date(Date.UTC(+('20' + dateArray[1]), +dateArray[2] - 1, +dateArray[3], 8, 0, 0, 0))
|
|
409
|
+
const lastUnderlyingPrice = this._indexPrices.get(summary.uly)
|
|
410
|
+
|
|
411
|
+
const lastOpenInterest = this._openInterests.get(summary.instId)
|
|
412
|
+
|
|
413
|
+
const lastMarkPrice = this._markPrices.get(summary.instId)
|
|
414
|
+
|
|
415
|
+
const lastTickerInfo = this._tickers.get(summary.instId)
|
|
416
|
+
|
|
417
|
+
const optionSummary: OptionSummary = {
|
|
418
|
+
type: 'option_summary',
|
|
419
|
+
symbol: summary.instId,
|
|
420
|
+
exchange: 'okex-options',
|
|
421
|
+
optionType: isPut ? 'put' : 'call',
|
|
422
|
+
strikePrice,
|
|
423
|
+
expirationDate,
|
|
424
|
+
|
|
425
|
+
bestBidPrice: lastTickerInfo !== undefined ? asNumberIfValid(lastTickerInfo.bidPx) : undefined,
|
|
426
|
+
bestBidAmount: lastTickerInfo !== undefined ? asNumberIfValid(lastTickerInfo.bidSz) : undefined,
|
|
427
|
+
bestBidIV: asNumberIfValid(summary.bidVol),
|
|
428
|
+
|
|
429
|
+
bestAskPrice: lastTickerInfo !== undefined ? asNumberIfValid(lastTickerInfo.askPx) : undefined,
|
|
430
|
+
bestAskAmount: lastTickerInfo !== undefined ? asNumberIfValid(lastTickerInfo.askSz) : undefined,
|
|
431
|
+
bestAskIV: asNumberIfValid(summary.askVol),
|
|
432
|
+
|
|
433
|
+
lastPrice: lastTickerInfo !== undefined ? asNumberIfValid(lastTickerInfo.last) : undefined,
|
|
434
|
+
openInterest: lastOpenInterest,
|
|
435
|
+
|
|
436
|
+
markPrice: lastMarkPrice,
|
|
437
|
+
markIV: asNumberIfValid(summary.markVol),
|
|
438
|
+
|
|
439
|
+
delta: asNumberIfValid(summary.delta),
|
|
440
|
+
gamma: asNumberIfValid(summary.gamma),
|
|
441
|
+
vega: asNumberIfValid(summary.vega),
|
|
442
|
+
theta: asNumberIfValid(summary.theta),
|
|
443
|
+
rho: undefined,
|
|
444
|
+
|
|
445
|
+
underlyingPrice: lastUnderlyingPrice,
|
|
446
|
+
underlyingIndex: summary.uly,
|
|
447
|
+
|
|
448
|
+
timestamp: new Date(Number(summary.ts)),
|
|
449
|
+
localTimestamp: localTimestamp
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
yield optionSummary
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
type OkexV5TradeMessage = {
|
|
459
|
+
arg: { channel: 'trades'; instId: 'CRV-USDT' }
|
|
460
|
+
data: [{ instId: 'CRV-USDT'; tradeId: '21300150'; px: '3.973'; sz: '13.491146'; side: 'buy'; ts: '1639999319938' }]
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
type OkexV5BookLevel = [string, string, string, string]
|
|
464
|
+
|
|
465
|
+
type OkexV5BookMessage =
|
|
466
|
+
| {
|
|
467
|
+
arg: { channel: 'books-l2-tbt'; instId: string }
|
|
468
|
+
action: 'snapshot'
|
|
469
|
+
data: [
|
|
470
|
+
{
|
|
471
|
+
asks: OkexV5BookLevel[]
|
|
472
|
+
bids: OkexV5BookLevel[]
|
|
473
|
+
ts: string
|
|
474
|
+
}
|
|
475
|
+
]
|
|
476
|
+
}
|
|
477
|
+
| {
|
|
478
|
+
arg: { channel: 'books-l2-tbt'; instId: string }
|
|
479
|
+
action: 'update'
|
|
480
|
+
data: [{ asks: OkexV5BookLevel[]; bids: OkexV5BookLevel[]; ts: string }]
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
type OkexV5TickerMessage = {
|
|
484
|
+
arg: { channel: 'tickers'; instId: string }
|
|
485
|
+
data: [
|
|
486
|
+
{
|
|
487
|
+
instType: 'SPOT'
|
|
488
|
+
instId: 'ACT-USDT'
|
|
489
|
+
last: '0.00718'
|
|
490
|
+
lastSz: '8052.117146'
|
|
491
|
+
askPx: '0.0072'
|
|
492
|
+
askSz: '54969.407534'
|
|
493
|
+
bidPx: '0.00713'
|
|
494
|
+
bidSz: '4092.326'
|
|
495
|
+
open24h: '0.00717'
|
|
496
|
+
high24h: '0.00722'
|
|
497
|
+
low24h: '0.00696'
|
|
498
|
+
sodUtc0: '0.00714'
|
|
499
|
+
sodUtc8: '0.00721'
|
|
500
|
+
volCcy24h: '278377.765301'
|
|
501
|
+
vol24h: '39168761.49997'
|
|
502
|
+
ts: '1639999318686'
|
|
503
|
+
}
|
|
504
|
+
]
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
type OkexV5OpenInterestMessage = {
|
|
508
|
+
arg: { channel: 'open-interest'; instId: string }
|
|
509
|
+
data: [{ instId: 'FIL-USDT-220325'; instType: 'FUTURES'; oi: '236870'; oiCcy: '23687'; ts: '1640131202886' }]
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
type OkexV5MarkPriceMessage = {
|
|
513
|
+
arg: { channel: 'mark-price'; instId: string }
|
|
514
|
+
data: [{ instId: 'FIL-USDT-220325'; instType: 'FUTURES'; markPx: '36.232'; ts: '1640131204676' }]
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
type OkexV5IndexTickerMessage = {
|
|
518
|
+
arg: { channel: 'index-tickers'; instId: string }
|
|
519
|
+
data: [
|
|
520
|
+
{
|
|
521
|
+
instId: 'FIL-USDT'
|
|
522
|
+
idxPx: '35.583'
|
|
523
|
+
open24h: '34.558'
|
|
524
|
+
high24h: '35.862'
|
|
525
|
+
low24h: '34.529'
|
|
526
|
+
sodUtc0: '35.309'
|
|
527
|
+
sodUtc8: '34.83'
|
|
528
|
+
ts: '1640140200581'
|
|
529
|
+
}
|
|
530
|
+
]
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
type OkexV5FundingRateMessage = {
|
|
534
|
+
arg: { channel: 'funding-rate'; instId: string }
|
|
535
|
+
data: [{ fundingRate: '0.00048105'; fundingTime: '1640131200000'; instId: string; instType: 'SWAP'; nextFundingRate: '0.00114' }]
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
type OkexV5LiquidationMessage = {
|
|
539
|
+
arg: { channel: 'liquidations'; instId: 'BTC-USDT-211231'; generated: true }
|
|
540
|
+
data: [{ bkLoss: '0'; bkPx: '49674.2'; ccy: ''; posSide: 'short'; side: 'buy'; sz: '40'; ts: '1640140211925' }]
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
type OkexV5SummaryMessage = {
|
|
544
|
+
arg: { channel: 'opt-summary'; uly: 'ETH-USD' }
|
|
545
|
+
data: [
|
|
546
|
+
{
|
|
547
|
+
instType: 'OPTION'
|
|
548
|
+
instId: 'ETH-USD-211222-4000-C'
|
|
549
|
+
uly: 'ETH-USD'
|
|
550
|
+
delta: '0.1975745164'
|
|
551
|
+
gamma: '4.7290833601'
|
|
552
|
+
vega: '0.0002005415'
|
|
553
|
+
theta: '-0.004262964'
|
|
554
|
+
lever: '162.472613953'
|
|
555
|
+
markVol: '0.7794507758'
|
|
556
|
+
bidVol: '0.7421960156'
|
|
557
|
+
askVol: '0.8203208593'
|
|
558
|
+
realVol: ''
|
|
559
|
+
deltaBS: '0.2038286081'
|
|
560
|
+
gammaBS: '0.0013437829'
|
|
561
|
+
thetaBS: '-16.4798150221'
|
|
562
|
+
vegaBS: '0.7647227087'
|
|
563
|
+
ts: '1640001659301'
|
|
564
|
+
}
|
|
565
|
+
]
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
//---
|
|
569
|
+
//V3 Okex API mappers
|
|
5
570
|
// https://www.okex.com/docs/en/#ws_swap-README
|
|
6
571
|
|
|
7
572
|
export class OkexTradesMapper implements Mapper<OKEX_EXCHANGES, Trade> {
|
|
@@ -3,14 +3,40 @@ import { Filter } from '../types'
|
|
|
3
3
|
import { RealTimeFeedBase } from './realtimefeed'
|
|
4
4
|
|
|
5
5
|
export class OkexRealTimeFeed extends RealTimeFeedBase {
|
|
6
|
-
protected wssURL = 'wss://
|
|
6
|
+
protected wssURL = 'wss://ws.okex.com:8443/ws/v5/public'
|
|
7
7
|
|
|
8
|
-
protected
|
|
9
|
-
|
|
8
|
+
protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
|
|
9
|
+
const args = filters
|
|
10
|
+
.map((filter) => {
|
|
11
|
+
if (!filter.symbols || filter.symbols.length === 0) {
|
|
12
|
+
throw new Error(`${this._exchange} RealTimeFeed requires explicitly specified symbols when subscribing to live feed`)
|
|
13
|
+
}
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
return filter.symbols.map((symbol) => {
|
|
16
|
+
return {
|
|
17
|
+
channel: filter.channel,
|
|
18
|
+
instId: symbol
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
.flatMap((s) => s)
|
|
23
|
+
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
op: 'subscribe',
|
|
27
|
+
args: [...new Set(args)]
|
|
28
|
+
}
|
|
29
|
+
]
|
|
12
30
|
}
|
|
13
31
|
|
|
32
|
+
protected messageIsError(message: any): boolean {
|
|
33
|
+
return message.event === 'error'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class OKCoinRealTimeFeed extends RealTimeFeedBase {
|
|
38
|
+
protected wssURL = 'wss://real.okcoin.com:8443/ws/v3'
|
|
39
|
+
|
|
14
40
|
protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
|
|
15
41
|
const args = filters
|
|
16
42
|
.map((filter) => {
|
|
@@ -35,17 +61,19 @@ export class OkexRealTimeFeed extends RealTimeFeedBase {
|
|
|
35
61
|
protected messageIsError(message: any): boolean {
|
|
36
62
|
return message.event === 'error'
|
|
37
63
|
}
|
|
38
|
-
}
|
|
39
64
|
|
|
40
|
-
|
|
41
|
-
|
|
65
|
+
protected decompress = (message: any) => {
|
|
66
|
+
message = inflateRawSync(message) as Buffer
|
|
67
|
+
|
|
68
|
+
return message
|
|
69
|
+
}
|
|
42
70
|
}
|
|
43
71
|
|
|
44
72
|
export class OkexOptionsRealTimeFeed extends OkexRealTimeFeed {
|
|
45
|
-
private _defaultIndexes = ['BTC-USD', 'ETH-USD'
|
|
73
|
+
private _defaultIndexes = ['BTC-USD', 'ETH-USD']
|
|
46
74
|
|
|
47
75
|
private _channelRequiresIndexNotSymbol(channel: string) {
|
|
48
|
-
if (channel === 'index
|
|
76
|
+
if (channel === 'index-tickers' || channel === 'opt-summary') {
|
|
49
77
|
return true
|
|
50
78
|
}
|
|
51
79
|
return false
|
|
@@ -70,7 +98,11 @@ export class OkexOptionsRealTimeFeed extends OkexRealTimeFeed {
|
|
|
70
98
|
const symbolParts = symbol.split('-')
|
|
71
99
|
finalSymbol = `${symbolParts[0]}-${symbolParts[1]}`
|
|
72
100
|
}
|
|
73
|
-
return
|
|
101
|
+
return {
|
|
102
|
+
channel: filter.channel,
|
|
103
|
+
instId: filter.channel !== 'opt-summary' ? finalSymbol : undefined,
|
|
104
|
+
uly: filter.channel === 'opt-summary' ? finalSymbol : undefined
|
|
105
|
+
}
|
|
74
106
|
})
|
|
75
107
|
})
|
|
76
108
|
.flatMap((s) => s)
|