tardis-dev 14.1.5 → 14.2.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.map +1 -1
- package/dist/consts.js +16 -1
- package/dist/consts.js.map +1 -1
- package/dist/mappers/binanceeuropeanoptions.d.ts +108 -1
- package/dist/mappers/binanceeuropeanoptions.d.ts.map +1 -1
- package/dist/mappers/binanceeuropeanoptions.js +231 -1
- package/dist/mappers/binanceeuropeanoptions.js.map +1 -1
- package/dist/mappers/index.d.ts +5 -4
- package/dist/mappers/index.d.ts.map +1 -1
- package/dist/mappers/index.js +15 -4
- package/dist/mappers/index.js.map +1 -1
- package/dist/realtimefeeds/binanceeuropeanoptions.d.ts +8 -2
- package/dist/realtimefeeds/binanceeuropeanoptions.d.ts.map +1 -1
- package/dist/realtimefeeds/binanceeuropeanoptions.js +62 -14
- package/dist/realtimefeeds/binanceeuropeanoptions.js.map +1 -1
- package/package.json +1 -1
- package/src/consts.ts +16 -1
- package/src/mappers/binanceeuropeanoptions.ts +351 -2
- package/src/mappers/index.ts +25 -5
- package/src/realtimefeeds/binanceeuropeanoptions.ts +88 -13
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { asNumberIfValid, upperCaseSymbols } from '../handy'
|
|
2
|
-
import { BookChange, OptionSummary, Trade } from '../types'
|
|
1
|
+
import { asNumberIfValid, lowerCaseSymbols, upperCaseSymbols } from '../handy'
|
|
2
|
+
import { BookChange, BookTicker, OptionSummary, Trade } from '../types'
|
|
3
3
|
import { Mapper } from './mapper'
|
|
4
4
|
|
|
5
5
|
// https://binance-docs.github.io/apidocs/voptions/en/#websocket-market-streams
|
|
@@ -41,6 +41,43 @@ export class BinanceEuropeanOptionsTradesMapper implements Mapper<'binance-europ
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
export class BinanceEuropeanOptionsTradesMapperV2 implements Mapper<'binance-european-options', Trade> {
|
|
45
|
+
canHandle(message: BinanceResponse<any>) {
|
|
46
|
+
if (message.stream === undefined) {
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return message.stream.endsWith('@optionTrade')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getFilters(symbols?: string[]) {
|
|
54
|
+
symbols = lowerCaseSymbols(symbols)
|
|
55
|
+
|
|
56
|
+
return [
|
|
57
|
+
{
|
|
58
|
+
channel: 'optionTrade',
|
|
59
|
+
symbols
|
|
60
|
+
} as const
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
*map(binanceTradeResponse: BinanceResponse<BinanceOptionsTradeDataV2>, localTimestamp: Date) {
|
|
65
|
+
const trade: Trade = {
|
|
66
|
+
type: 'trade',
|
|
67
|
+
symbol: binanceTradeResponse.data.s,
|
|
68
|
+
exchange: 'binance-european-options',
|
|
69
|
+
id: String(binanceTradeResponse.data.t),
|
|
70
|
+
price: Number(binanceTradeResponse.data.p),
|
|
71
|
+
amount: Number(binanceTradeResponse.data.q),
|
|
72
|
+
side: binanceTradeResponse.data.m ? 'sell' : 'buy',
|
|
73
|
+
timestamp: new Date(binanceTradeResponse.data.T),
|
|
74
|
+
localTimestamp: localTimestamp
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
yield trade
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
44
81
|
export class BinanceEuropeanOptionsBookChangeMapper implements Mapper<'binance-european-options', BookChange> {
|
|
45
82
|
canHandle(message: BinanceResponse<any>) {
|
|
46
83
|
if (message.stream === undefined) {
|
|
@@ -83,6 +120,90 @@ export class BinanceEuropeanOptionsBookChangeMapper implements Mapper<'binance-e
|
|
|
83
120
|
}
|
|
84
121
|
}
|
|
85
122
|
|
|
123
|
+
export class BinanceEuropeanOptionsBookChangeMapperV2 implements Mapper<'binance-european-options', BookChange> {
|
|
124
|
+
canHandle(message: BinanceResponse<any>) {
|
|
125
|
+
if (message.stream === undefined) {
|
|
126
|
+
return false
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return message.stream.includes('@depth20')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
getFilters(symbols?: string[]) {
|
|
133
|
+
symbols = lowerCaseSymbols(symbols)
|
|
134
|
+
|
|
135
|
+
return [
|
|
136
|
+
{
|
|
137
|
+
channel: 'depth20',
|
|
138
|
+
symbols
|
|
139
|
+
} as const
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
*map(message: BinanceResponse<BinanceOptionsDepthDataV2>, localTimestamp: Date) {
|
|
144
|
+
const bookChange: BookChange = {
|
|
145
|
+
type: 'book_change',
|
|
146
|
+
symbol: message.data.s,
|
|
147
|
+
exchange: 'binance-european-options',
|
|
148
|
+
isSnapshot: true,
|
|
149
|
+
bids: message.data.b.map(this.mapBookLevel),
|
|
150
|
+
asks: message.data.a.map(this.mapBookLevel),
|
|
151
|
+
timestamp: new Date(message.data.T),
|
|
152
|
+
localTimestamp
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
yield bookChange
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
protected mapBookLevel(level: BinanceBookLevel) {
|
|
159
|
+
const price = Number(level[0])
|
|
160
|
+
const amount = Number(level[1])
|
|
161
|
+
return { price, amount }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export class BinanceEuropeanOptionsBookTickerMapper implements Mapper<'binance-european-options', BookTicker> {
|
|
166
|
+
canHandle(message: BinanceResponse<any>) {
|
|
167
|
+
if (message.stream === undefined) {
|
|
168
|
+
return false
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return message.stream.endsWith('@bookTicker')
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
getFilters(symbols?: string[]) {
|
|
175
|
+
symbols = lowerCaseSymbols(symbols)
|
|
176
|
+
|
|
177
|
+
return [
|
|
178
|
+
{
|
|
179
|
+
channel: 'bookTicker',
|
|
180
|
+
symbols
|
|
181
|
+
} as const
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
*map(message: BinanceResponse<BinanceOptionsBookTickerData>, localTimestamp: Date) {
|
|
186
|
+
const bestBidPrice = Number(message.data.b)
|
|
187
|
+
const bestBidAmount = Number(message.data.B)
|
|
188
|
+
const bestAskPrice = Number(message.data.a)
|
|
189
|
+
const bestAskAmount = Number(message.data.A)
|
|
190
|
+
|
|
191
|
+
const bookTicker: BookTicker = {
|
|
192
|
+
type: 'book_ticker',
|
|
193
|
+
symbol: message.data.s,
|
|
194
|
+
exchange: 'binance-european-options',
|
|
195
|
+
bidPrice: bestBidPrice > 0 ? bestBidPrice : undefined,
|
|
196
|
+
bidAmount: bestBidAmount > 0 ? bestBidAmount : undefined,
|
|
197
|
+
askPrice: bestAskPrice > 0 ? bestAskPrice : undefined,
|
|
198
|
+
askAmount: bestAskAmount > 0 ? bestAskAmount : undefined,
|
|
199
|
+
timestamp: new Date(message.data.T),
|
|
200
|
+
localTimestamp
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
yield bookTicker
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
86
207
|
export class BinanceEuropeanOptionSummaryMapper implements Mapper<'binance-european-options', OptionSummary> {
|
|
87
208
|
private readonly _indexPrices = new Map<string, number>()
|
|
88
209
|
private readonly _openInterests = new Map<string, number>()
|
|
@@ -233,6 +354,165 @@ export class BinanceEuropeanOptionSummaryMapper implements Mapper<'binance-europ
|
|
|
233
354
|
}
|
|
234
355
|
}
|
|
235
356
|
|
|
357
|
+
export class BinanceEuropeanOptionSummaryMapperV2 implements Mapper<'binance-european-options', OptionSummary> {
|
|
358
|
+
private readonly _lastPrices = new Map<string, number>()
|
|
359
|
+
private readonly _openInterests = new Map<string, number>()
|
|
360
|
+
|
|
361
|
+
canHandle(message: BinanceResponse<any>) {
|
|
362
|
+
if (message.stream === undefined) {
|
|
363
|
+
return false
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return (
|
|
367
|
+
message.stream.endsWith('@optionMarkPrice') ||
|
|
368
|
+
message.stream.endsWith('@optionTicker') ||
|
|
369
|
+
message.stream.includes('@optionOpenInterest')
|
|
370
|
+
)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
getFilters(symbols?: string[]) {
|
|
374
|
+
symbols = lowerCaseSymbols(symbols)
|
|
375
|
+
|
|
376
|
+
const underlyings =
|
|
377
|
+
symbols !== undefined
|
|
378
|
+
? symbols.map((s) => {
|
|
379
|
+
const symbolParts = s.split('-')
|
|
380
|
+
return `${symbolParts[0]}usdt`
|
|
381
|
+
})
|
|
382
|
+
: undefined
|
|
383
|
+
|
|
384
|
+
return [
|
|
385
|
+
{
|
|
386
|
+
channel: 'optionMarkPrice',
|
|
387
|
+
symbols: underlyings
|
|
388
|
+
} as const,
|
|
389
|
+
{
|
|
390
|
+
channel: 'optionTicker',
|
|
391
|
+
symbols
|
|
392
|
+
} as const,
|
|
393
|
+
{
|
|
394
|
+
channel: 'optionOpenInterest',
|
|
395
|
+
symbols: underlyings
|
|
396
|
+
} as const
|
|
397
|
+
]
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
*map(
|
|
401
|
+
message: BinanceResponse<BinanceOptionsMarkPriceData[] | BinanceOptionsTickerData | BinanceOptionsOpenInterestDataV2[]>,
|
|
402
|
+
localTimestamp: Date
|
|
403
|
+
) {
|
|
404
|
+
// Handle optionTicker messages to track last prices
|
|
405
|
+
if (message.stream.endsWith('@optionTicker')) {
|
|
406
|
+
const tickerData = message.data as BinanceOptionsTickerData
|
|
407
|
+
const lastPrice = Number(tickerData.c)
|
|
408
|
+
if (lastPrice > 0) {
|
|
409
|
+
this._lastPrices.set(tickerData.s, lastPrice)
|
|
410
|
+
}
|
|
411
|
+
return
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Handle optionOpenInterest messages to track open interest
|
|
415
|
+
if (message.stream.includes('@optionOpenInterest')) {
|
|
416
|
+
const openInterestArray = message.data as BinanceOptionsOpenInterestDataV2[]
|
|
417
|
+
for (let oi of openInterestArray) {
|
|
418
|
+
const openInterest = Number(oi.o)
|
|
419
|
+
if (openInterest >= 0) {
|
|
420
|
+
this._openInterests.set(oi.s, openInterest)
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// optionMarkPrice contains all data needed: greeks, IV, best bid/ask, mark price, and index price
|
|
427
|
+
const markPriceArray = message.data as BinanceOptionsMarkPriceData[]
|
|
428
|
+
for (let markData of markPriceArray) {
|
|
429
|
+
const [base, expiryPart, strikePrice, optionType] = markData.s.split('-')
|
|
430
|
+
|
|
431
|
+
const expirationDate = new Date(`20${expiryPart.slice(0, 2)}-${expiryPart.slice(2, 4)}-${expiryPart.slice(4, 6)}Z`)
|
|
432
|
+
expirationDate.setUTCHours(8)
|
|
433
|
+
|
|
434
|
+
const isPut = optionType === 'P'
|
|
435
|
+
const underlyingIndex = `${base}USDT`
|
|
436
|
+
|
|
437
|
+
let bestBidPrice = asNumberIfValid(markData.bo)
|
|
438
|
+
if (bestBidPrice === 0) {
|
|
439
|
+
bestBidPrice = undefined
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
let bestBidAmount = asNumberIfValid(markData.bq)
|
|
443
|
+
if (bestBidAmount === 0) {
|
|
444
|
+
bestBidAmount = undefined
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
let bestAskPrice = asNumberIfValid(markData.ao)
|
|
448
|
+
if (bestAskPrice === 0) {
|
|
449
|
+
bestAskPrice = undefined
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
let bestAskAmount = asNumberIfValid(markData.aq)
|
|
453
|
+
if (bestAskAmount === 0) {
|
|
454
|
+
bestAskAmount = undefined
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
let bestBidIV = bestBidPrice !== undefined ? asNumberIfValid(markData.b) : undefined
|
|
458
|
+
if (bestBidIV === -1) {
|
|
459
|
+
bestBidIV = undefined
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
let bestAskIV = bestAskPrice !== undefined ? asNumberIfValid(markData.a) : undefined
|
|
463
|
+
if (bestAskIV === -1) {
|
|
464
|
+
bestAskIV = undefined
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const markPrice = asNumberIfValid(markData.mp)
|
|
468
|
+
const markIV = asNumberIfValid(markData.vo)
|
|
469
|
+
const delta = asNumberIfValid(markData.d)
|
|
470
|
+
const gamma = asNumberIfValid(markData.g)
|
|
471
|
+
const vega = asNumberIfValid(markData.v)
|
|
472
|
+
const theta = asNumberIfValid(markData.t)
|
|
473
|
+
const underlyingPrice = asNumberIfValid(markData.i) // Index price is included in mark price data
|
|
474
|
+
|
|
475
|
+
const optionSummary: OptionSummary = {
|
|
476
|
+
type: 'option_summary',
|
|
477
|
+
symbol: markData.s,
|
|
478
|
+
exchange: 'binance-european-options',
|
|
479
|
+
optionType: isPut ? 'put' : 'call',
|
|
480
|
+
strikePrice: Number(strikePrice),
|
|
481
|
+
expirationDate,
|
|
482
|
+
|
|
483
|
+
bestBidPrice,
|
|
484
|
+
bestBidAmount,
|
|
485
|
+
bestBidIV,
|
|
486
|
+
|
|
487
|
+
bestAskPrice,
|
|
488
|
+
bestAskAmount,
|
|
489
|
+
bestAskIV,
|
|
490
|
+
|
|
491
|
+
lastPrice: this._lastPrices.get(markData.s),
|
|
492
|
+
|
|
493
|
+
openInterest: this._openInterests.get(markData.s),
|
|
494
|
+
|
|
495
|
+
markPrice,
|
|
496
|
+
markIV,
|
|
497
|
+
|
|
498
|
+
delta,
|
|
499
|
+
gamma,
|
|
500
|
+
vega,
|
|
501
|
+
theta,
|
|
502
|
+
rho: undefined,
|
|
503
|
+
|
|
504
|
+
underlyingPrice,
|
|
505
|
+
underlyingIndex,
|
|
506
|
+
|
|
507
|
+
timestamp: new Date(markData.E),
|
|
508
|
+
localTimestamp: localTimestamp
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
yield optionSummary
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
236
516
|
type BinanceResponse<T> = {
|
|
237
517
|
stream: string
|
|
238
518
|
data: T
|
|
@@ -301,3 +581,72 @@ type BinanceOptionsIndexData = { e: 'index'; E: 1696118400040; s: 'BNBUSDT'; p:
|
|
|
301
581
|
type BinanceOptionsOpenInterestData = { e: 'openInterest'; E: 1696118400042; s: 'XRP-231006-0.46-P'; o: '39480.0'; h: '20326.64319' }
|
|
302
582
|
|
|
303
583
|
type BinanceBookLevel = [string, string]
|
|
584
|
+
|
|
585
|
+
// V2 Types for new format (Dec 17, 2025+)
|
|
586
|
+
type BinanceOptionsTradeDataV2 = {
|
|
587
|
+
e: 'trade'
|
|
588
|
+
E: number // event time
|
|
589
|
+
T: number // trade completed time
|
|
590
|
+
s: string // option symbol
|
|
591
|
+
t: number // trade ID
|
|
592
|
+
p: string // price
|
|
593
|
+
q: string // quantity
|
|
594
|
+
X: 'MARKET' | 'BLOCK' // trade type
|
|
595
|
+
S: 'BUY' | 'SELL' // direction
|
|
596
|
+
m: boolean // is buyer market maker
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
type BinanceOptionsDepthDataV2 = {
|
|
600
|
+
e: 'depthUpdate'
|
|
601
|
+
E: number // event time
|
|
602
|
+
T: number // transaction time
|
|
603
|
+
s: string // symbol
|
|
604
|
+
U: number // first update ID
|
|
605
|
+
u: number // final update ID
|
|
606
|
+
pu: number // previous final update ID
|
|
607
|
+
b: [string, string][] // bids
|
|
608
|
+
a: [string, string][] // asks
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
type BinanceOptionsBookTickerData = {
|
|
612
|
+
e: 'bookTicker'
|
|
613
|
+
u: number // order book update ID
|
|
614
|
+
s: string // symbol
|
|
615
|
+
b: string // best bid price
|
|
616
|
+
B: string // best bid qty
|
|
617
|
+
a: string // best ask price
|
|
618
|
+
A: string // best ask qty
|
|
619
|
+
T: number // transaction time
|
|
620
|
+
E: number // event time
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
type BinanceOptionsOpenInterestDataV2 = {
|
|
624
|
+
e: 'openInterest'
|
|
625
|
+
E: number // event time
|
|
626
|
+
s: string // symbol
|
|
627
|
+
o: string // open interest (quantity)
|
|
628
|
+
h: string // open interest in notional value (USD)
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
type BinanceOptionsMarkPriceData = {
|
|
632
|
+
s: string // option symbol
|
|
633
|
+
mp: string // mark price
|
|
634
|
+
E: number // event time
|
|
635
|
+
e: 'markPrice'
|
|
636
|
+
i: string // index price
|
|
637
|
+
P: string // premium
|
|
638
|
+
bo: string // best bid price
|
|
639
|
+
ao: string // best ask price
|
|
640
|
+
bq: string // best bid quantity
|
|
641
|
+
aq: string // best ask quantity
|
|
642
|
+
b: string // bid IV
|
|
643
|
+
a: string // ask IV
|
|
644
|
+
hl: string // high limit price
|
|
645
|
+
ll: string // low limit price
|
|
646
|
+
vo: string // mark IV
|
|
647
|
+
rf: string // risk free rate
|
|
648
|
+
d: string // delta
|
|
649
|
+
t: string // theta
|
|
650
|
+
g: string // gamma
|
|
651
|
+
v: string // vega
|
|
652
|
+
}
|
package/src/mappers/index.ts
CHANGED
|
@@ -12,8 +12,12 @@ import {
|
|
|
12
12
|
import { binanceDexBookChangeMapper, binanceDexBookTickerMapper, binanceDexTradesMapper } from './binancedex'
|
|
13
13
|
import {
|
|
14
14
|
BinanceEuropeanOptionsBookChangeMapper,
|
|
15
|
+
BinanceEuropeanOptionsBookChangeMapperV2,
|
|
16
|
+
BinanceEuropeanOptionsBookTickerMapper,
|
|
15
17
|
BinanceEuropeanOptionsTradesMapper,
|
|
16
|
-
|
|
18
|
+
BinanceEuropeanOptionsTradesMapperV2,
|
|
19
|
+
BinanceEuropeanOptionSummaryMapper,
|
|
20
|
+
BinanceEuropeanOptionSummaryMapperV2
|
|
17
21
|
} from './binanceeuropeanoptions'
|
|
18
22
|
import { BinanceOptionsBookChangeMapper, BinanceOptionsTradesMapper, BinanceOptionSummaryMapper } from './binanceoptions'
|
|
19
23
|
import {
|
|
@@ -221,6 +225,12 @@ const shouldUseOKXTradesAllChannel = (localTimestamp: Date) => {
|
|
|
221
225
|
return isRealTime(localTimestamp) || localTimestamp.valueOf() >= new Date('2023-10-19T00:00:00.000Z').valueOf()
|
|
222
226
|
}
|
|
223
227
|
|
|
228
|
+
const BINANCE_EUROPEAN_OPTIONS_V2_API_SWITCH_DATE = new Date('2025-12-17T00:00:00.000Z')
|
|
229
|
+
|
|
230
|
+
const shouldUseBinanceEuropeanOptionsV2Mappers = (localTimestamp: Date) => {
|
|
231
|
+
return isRealTime(localTimestamp) || localTimestamp.valueOf() >= BINANCE_EUROPEAN_OPTIONS_V2_API_SWITCH_DATE.valueOf()
|
|
232
|
+
}
|
|
233
|
+
|
|
224
234
|
const tradesMappers = {
|
|
225
235
|
bitmex: () => bitmexTradesMapper,
|
|
226
236
|
binance: () => new BinanceTradesMapper('binance'),
|
|
@@ -296,7 +306,10 @@ const tradesMappers = {
|
|
|
296
306
|
'woo-x': () => wooxTradesMapper,
|
|
297
307
|
'blockchain-com': () => new BlockchainComTradesMapper(),
|
|
298
308
|
'bybit-options': () => new BybitV5TradesMapper('bybit-options'),
|
|
299
|
-
'binance-european-options': () =>
|
|
309
|
+
'binance-european-options': (localTimestamp: Date) =>
|
|
310
|
+
shouldUseBinanceEuropeanOptionsV2Mappers(localTimestamp)
|
|
311
|
+
? new BinanceEuropeanOptionsTradesMapperV2()
|
|
312
|
+
: new BinanceEuropeanOptionsTradesMapper(),
|
|
300
313
|
'okex-spreads': () => new OkexSpreadsTradesMapper(),
|
|
301
314
|
bitget: () => new BitgetTradesMapper('bitget'),
|
|
302
315
|
'bitget-futures': () => new BitgetTradesMapper('bitget-futures'),
|
|
@@ -390,7 +403,10 @@ const bookChangeMappers = {
|
|
|
390
403
|
'woo-x': () => new WooxBookChangeMapper(),
|
|
391
404
|
'blockchain-com': () => new BlockchainComBookChangeMapper(),
|
|
392
405
|
'bybit-options': () => new BybitV5BookChangeMapper('bybit-options', 25),
|
|
393
|
-
'binance-european-options': () =>
|
|
406
|
+
'binance-european-options': (localTimestamp: Date) =>
|
|
407
|
+
shouldUseBinanceEuropeanOptionsV2Mappers(localTimestamp)
|
|
408
|
+
? new BinanceEuropeanOptionsBookChangeMapperV2()
|
|
409
|
+
: new BinanceEuropeanOptionsBookChangeMapper(),
|
|
394
410
|
'okex-spreads': () => new OkexSpreadsBookChangeMapper(),
|
|
395
411
|
bitget: () => new BitgetBookChangeMapper('bitget'),
|
|
396
412
|
'bitget-futures': () => new BitgetBookChangeMapper('bitget-futures'),
|
|
@@ -442,7 +458,10 @@ const optionsSummaryMappers = {
|
|
|
442
458
|
'binance-options': () => new BinanceOptionSummaryMapper(),
|
|
443
459
|
'huobi-dm-options': () => new HuobiOptionsSummaryMapper(),
|
|
444
460
|
'bybit-options': () => new BybitV5OptionSummaryMapper(),
|
|
445
|
-
'binance-european-options': () =>
|
|
461
|
+
'binance-european-options': (localTimestamp: Date) =>
|
|
462
|
+
shouldUseBinanceEuropeanOptionsV2Mappers(localTimestamp)
|
|
463
|
+
? new BinanceEuropeanOptionSummaryMapperV2()
|
|
464
|
+
: new BinanceEuropeanOptionSummaryMapper()
|
|
446
465
|
}
|
|
447
466
|
|
|
448
467
|
const liquidationsMappers = {
|
|
@@ -532,7 +551,8 @@ const bookTickersMappers = {
|
|
|
532
551
|
bitget: () => new BitgetBookTickerMapper('bitget'),
|
|
533
552
|
'bitget-futures': () => new BitgetBookTickerMapper('bitget-futures'),
|
|
534
553
|
'coinbase-international': () => coinbaseInternationalBookTickerMapper,
|
|
535
|
-
hyperliquid: () => new HyperliquidBookTickerMapper()
|
|
554
|
+
hyperliquid: () => new HyperliquidBookTickerMapper(),
|
|
555
|
+
'binance-european-options': () => new BinanceEuropeanOptionsBookTickerMapper()
|
|
536
556
|
}
|
|
537
557
|
|
|
538
558
|
export const normalizeTrades = <T extends keyof typeof tradesMappers>(exchange: T, localTimestamp: Date): Mapper<T, Trade> => {
|
|
@@ -1,9 +1,54 @@
|
|
|
1
1
|
import { onlyUnique } from '../handy'
|
|
2
2
|
import { Filter } from '../types'
|
|
3
|
-
import { RealTimeFeedBase } from './realtimefeed'
|
|
3
|
+
import { MultiConnectionRealTimeFeedBase, RealTimeFeedBase } from './realtimefeed'
|
|
4
4
|
|
|
5
|
-
export class BinanceEuropeanOptionsRealTimeFeed extends
|
|
6
|
-
protected
|
|
5
|
+
export class BinanceEuropeanOptionsRealTimeFeed extends MultiConnectionRealTimeFeedBase {
|
|
6
|
+
protected *_getRealTimeFeeds(exchange: string, filters: Filter<string>[], timeoutIntervalMS?: number, onError?: (error: Error) => void) {
|
|
7
|
+
// V2 API uses two separate WebSocket endpoints:
|
|
8
|
+
// 1. Public path: for optionTrade, depth20, bookTicker, optionTicker
|
|
9
|
+
// 2. Market path: for optionIndexPrice, optionMarkPrice, optionOpenInterest
|
|
10
|
+
|
|
11
|
+
const publicChannels = ['optionTrade', 'depth20', 'bookTicker', 'optionTicker']
|
|
12
|
+
const marketChannels = ['optionIndexPrice', 'optionMarkPrice', 'optionOpenInterest']
|
|
13
|
+
|
|
14
|
+
const publicFilters = filters.filter((f) => publicChannels.includes(f.channel))
|
|
15
|
+
const marketFilters = filters.filter((f) => marketChannels.includes(f.channel))
|
|
16
|
+
|
|
17
|
+
if (publicFilters.length > 0) {
|
|
18
|
+
yield new BinanceEuropeanOptionsSingleFeed(
|
|
19
|
+
'wss://fstream.binance.com/public/stream',
|
|
20
|
+
exchange,
|
|
21
|
+
publicFilters,
|
|
22
|
+
filters, // Pass all filters so we can look up optionTicker when processing optionOpenInterest
|
|
23
|
+
timeoutIntervalMS,
|
|
24
|
+
onError
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (marketFilters.length > 0) {
|
|
29
|
+
yield new BinanceEuropeanOptionsSingleFeed(
|
|
30
|
+
'wss://fstream.binance.com/market/stream',
|
|
31
|
+
exchange,
|
|
32
|
+
marketFilters,
|
|
33
|
+
filters, // Pass all filters so we can look up optionTicker when processing optionOpenInterest
|
|
34
|
+
timeoutIntervalMS,
|
|
35
|
+
onError
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class BinanceEuropeanOptionsSingleFeed extends RealTimeFeedBase {
|
|
42
|
+
constructor(
|
|
43
|
+
protected wssURL: string,
|
|
44
|
+
exchange: string,
|
|
45
|
+
filters: Filter<string>[],
|
|
46
|
+
private readonly _allFilters: Filter<string>[],
|
|
47
|
+
timeoutIntervalMS: number | undefined,
|
|
48
|
+
onError?: (error: Error) => void
|
|
49
|
+
) {
|
|
50
|
+
super(exchange, filters, timeoutIntervalMS, onError)
|
|
51
|
+
}
|
|
7
52
|
|
|
8
53
|
protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
|
|
9
54
|
const payload = filters.map((filter, index) => {
|
|
@@ -15,29 +60,59 @@ export class BinanceEuropeanOptionsRealTimeFeed extends RealTimeFeedBase {
|
|
|
15
60
|
method: 'SUBSCRIBE',
|
|
16
61
|
params: filter.symbols
|
|
17
62
|
.map((symbol) => {
|
|
18
|
-
|
|
19
|
-
|
|
63
|
+
const lowerSymbol = symbol.toLowerCase()
|
|
64
|
+
|
|
65
|
+
// Public path channels - use lowercase symbol directly
|
|
66
|
+
if (filter.channel === 'optionTrade') {
|
|
67
|
+
return [`${lowerSymbol}@${filter.channel}`]
|
|
20
68
|
}
|
|
21
69
|
|
|
22
|
-
if (filter.channel === '
|
|
23
|
-
|
|
70
|
+
if (filter.channel === 'depth20') {
|
|
71
|
+
return [`${lowerSymbol}@${filter.channel}@100ms`]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (filter.channel === 'bookTicker') {
|
|
75
|
+
return [`${lowerSymbol}@${filter.channel}`]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (filter.channel === 'optionTicker') {
|
|
79
|
+
return [`${lowerSymbol}@${filter.channel}`]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Market path channels - use lowercase underlying
|
|
83
|
+
if (filter.channel === 'optionIndexPrice' || filter.channel === 'optionMarkPrice') {
|
|
84
|
+
// Symbol is the underlying (e.g., 'btcusdt')
|
|
85
|
+
return [`${lowerSymbol}@${filter.channel}`]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (filter.channel === 'optionOpenInterest') {
|
|
89
|
+
// Need to extract expirations from option symbols
|
|
90
|
+
// The symbol here is the underlying (e.g., 'btcusdt')
|
|
91
|
+
// We need to find all option symbols that match this underlying to extract expirations
|
|
92
|
+
|
|
93
|
+
// Look for optionTicker filter in all filters to get actual option symbols
|
|
94
|
+
const optionTickerFilter = this._allFilters.find((f) => f.channel === 'optionTicker')
|
|
95
|
+
|
|
96
|
+
if (optionTickerFilter !== undefined) {
|
|
97
|
+
// Extract expirations from option symbols that match this underlying
|
|
98
|
+
const underlyingBase = lowerSymbol.replace('usdt', '').toUpperCase()
|
|
24
99
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
.symbols!.filter((s) => s.startsWith(symbol))
|
|
100
|
+
const expirations = optionTickerFilter
|
|
101
|
+
.symbols!.filter((s) => s.toUpperCase().startsWith(underlyingBase + '-'))
|
|
28
102
|
.map((s) => {
|
|
29
103
|
const symbolParts = s.split('-')
|
|
30
|
-
return
|
|
104
|
+
return symbolParts[1] // Extract expiration (e.g., '251219')
|
|
31
105
|
})
|
|
32
106
|
.filter(onlyUnique)
|
|
107
|
+
.map((exp) => exp.toLowerCase())
|
|
33
108
|
|
|
34
109
|
return expirations.map((expiration) => {
|
|
35
|
-
return `${
|
|
110
|
+
return `${lowerSymbol}@${filter.channel}@${expiration}`
|
|
36
111
|
})
|
|
37
112
|
}
|
|
38
113
|
}
|
|
39
114
|
|
|
40
|
-
return [`${
|
|
115
|
+
return [`${lowerSymbol}@${filter.channel}`]
|
|
41
116
|
})
|
|
42
117
|
.flatMap((s) => s),
|
|
43
118
|
id: index + 1
|