tardis-dev 13.23.5 → 13.24.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.
@@ -0,0 +1,303 @@
1
+ import { asNumberIfValid, upperCaseSymbols } from '../handy'
2
+ import { BookChange, OptionSummary, Trade } from '../types'
3
+ import { Mapper } from './mapper'
4
+
5
+ // https://binance-docs.github.io/apidocs/voptions/en/#websocket-market-streams
6
+
7
+ export class BinanceEuropeanOptionsTradesMapper implements Mapper<'binance-european-options', Trade> {
8
+ canHandle(message: BinanceResponse<any>) {
9
+ if (message.stream === undefined) {
10
+ return false
11
+ }
12
+
13
+ return message.stream.endsWith('@trade')
14
+ }
15
+
16
+ getFilters(symbols?: string[]) {
17
+ symbols = upperCaseSymbols(symbols)
18
+
19
+ return [
20
+ {
21
+ channel: 'trade',
22
+ symbols
23
+ } as const
24
+ ]
25
+ }
26
+
27
+ *map(binanceTradeResponse: BinanceResponse<BinanceOptionsTradeData>, localTimestamp: Date) {
28
+ const trade: Trade = {
29
+ type: 'trade',
30
+ symbol: binanceTradeResponse.data.s,
31
+ exchange: 'binance-european-options',
32
+ id: binanceTradeResponse.data.t,
33
+ price: Number(binanceTradeResponse.data.p),
34
+ amount: Number(binanceTradeResponse.data.q),
35
+ side: binanceTradeResponse.data.S === '-1' ? 'sell' : 'buy',
36
+ timestamp: new Date(binanceTradeResponse.data.T),
37
+ localTimestamp: localTimestamp
38
+ }
39
+
40
+ yield trade
41
+ }
42
+ }
43
+
44
+ export class BinanceEuropeanOptionsBookChangeMapper implements Mapper<'binance-european-options', BookChange> {
45
+ canHandle(message: BinanceResponse<any>) {
46
+ if (message.stream === undefined) {
47
+ return false
48
+ }
49
+
50
+ return message.stream.includes('@depth100')
51
+ }
52
+
53
+ getFilters(symbols?: string[]) {
54
+ symbols = upperCaseSymbols(symbols)
55
+
56
+ return [
57
+ {
58
+ channel: 'depth100',
59
+ symbols
60
+ } as const
61
+ ]
62
+ }
63
+
64
+ *map(message: BinanceResponse<BinanceOptionsDepthData>, localTimestamp: Date) {
65
+ const bookChange: BookChange = {
66
+ type: 'book_change',
67
+ symbol: message.data.s,
68
+ exchange: 'binance-european-options',
69
+ isSnapshot: true,
70
+ bids: message.data.b.map(this.mapBookLevel),
71
+ asks: message.data.a.map(this.mapBookLevel),
72
+ timestamp: new Date(message.data.E),
73
+ localTimestamp
74
+ }
75
+
76
+ yield bookChange
77
+ }
78
+
79
+ protected mapBookLevel(level: BinanceBookLevel) {
80
+ const price = Number(level[0])
81
+ const amount = Number(level[1])
82
+ return { price, amount }
83
+ }
84
+ }
85
+
86
+ export class BinanceEuropeanOptionSummaryMapper implements Mapper<'binance-european-options', OptionSummary> {
87
+ private readonly _indexPrices = new Map<string, number>()
88
+ private readonly _openInterests = new Map<string, number>()
89
+
90
+ canHandle(message: BinanceResponse<any>) {
91
+ if (message.stream === undefined) {
92
+ return false
93
+ }
94
+
95
+ return message.stream.endsWith('@ticker') || message.stream.endsWith('@index') || message.stream.includes('@openInterest')
96
+ }
97
+
98
+ getFilters(symbols?: string[]) {
99
+ symbols = upperCaseSymbols(symbols)
100
+
101
+ const indexes =
102
+ symbols !== undefined
103
+ ? symbols.map((s) => {
104
+ const symbolParts = s.split('-')
105
+ return `${symbolParts[0]}USDT`
106
+ })
107
+ : undefined
108
+
109
+ const underlyings =
110
+ symbols !== undefined
111
+ ? symbols.map((s) => {
112
+ const symbolParts = s.split('-')
113
+ return `${symbolParts[0]}`
114
+ })
115
+ : undefined
116
+
117
+ return [
118
+ {
119
+ channel: 'ticker',
120
+ symbols
121
+ } as const,
122
+ {
123
+ channel: 'index',
124
+ symbols: indexes
125
+ } as const,
126
+ {
127
+ channel: 'openInterest',
128
+ symbols: underlyings
129
+ } as const
130
+ ]
131
+ }
132
+
133
+ *map(
134
+ message: BinanceResponse<BinanceOptionsTickerData | BinanceOptionsIndexData | BinanceOptionsOpenInterestData[]>,
135
+ localTimestamp: Date
136
+ ) {
137
+ if (message.stream.endsWith('@index')) {
138
+ const lastIndexPrice = Number((message.data as any).p)
139
+ if (lastIndexPrice > 0) {
140
+ this._indexPrices.set((message.data as any).s, lastIndexPrice)
141
+ }
142
+ return
143
+ }
144
+
145
+ if (message.stream.includes('@openInterest')) {
146
+ for (let data of message.data as BinanceOptionsOpenInterestData[]) {
147
+ const openInterest = Number(data.o)
148
+ if (openInterest > 0) {
149
+ this._openInterests.set(data.s, openInterest)
150
+ }
151
+ }
152
+
153
+ return
154
+ }
155
+
156
+ const optionInfo = message.data as BinanceOptionsTickerData
157
+
158
+ const [base, expiryPart, strikePrice, optionType] = optionInfo.s.split('-')
159
+
160
+ const expirationDate = new Date(`20${expiryPart.slice(0, 2)}-${expiryPart.slice(2, 4)}-${expiryPart.slice(4, 6)}Z`)
161
+ expirationDate.setUTCHours(8)
162
+
163
+ const isPut = optionType === 'P'
164
+
165
+ const underlyingIndex = `${base}USDT`
166
+
167
+ let bestBidPrice = asNumberIfValid(optionInfo.bo)
168
+ if (bestBidPrice === 0) {
169
+ bestBidPrice = undefined
170
+ }
171
+
172
+ let bestBidAmount = asNumberIfValid(optionInfo.bq)
173
+ if (bestBidAmount === 0) {
174
+ bestBidAmount = undefined
175
+ }
176
+ let bestAskPrice = asNumberIfValid(optionInfo.ao)
177
+ if (bestAskPrice === 0) {
178
+ bestAskPrice = undefined
179
+ }
180
+
181
+ let bestAskAmount = asNumberIfValid(optionInfo.aq)
182
+ if (bestAskAmount === 0) {
183
+ bestAskAmount = undefined
184
+ }
185
+
186
+ let bestBidIV = bestBidPrice !== undefined ? asNumberIfValid(optionInfo.b) : undefined
187
+ if (bestBidIV === -1) {
188
+ bestBidIV = undefined
189
+ }
190
+
191
+ let bestAskIV = bestAskPrice !== undefined ? asNumberIfValid(optionInfo.a) : undefined
192
+ if (bestAskIV === -1) {
193
+ bestAskIV = undefined
194
+ }
195
+
196
+ const optionSummary: OptionSummary = {
197
+ type: 'option_summary',
198
+ symbol: optionInfo.s,
199
+ exchange: 'binance-european-options',
200
+ optionType: isPut ? 'put' : 'call',
201
+ strikePrice: Number(strikePrice),
202
+ expirationDate,
203
+
204
+ bestBidPrice,
205
+ bestBidAmount,
206
+ bestBidIV,
207
+
208
+ bestAskPrice,
209
+ bestAskAmount,
210
+ bestAskIV,
211
+
212
+ lastPrice: asNumberIfValid(optionInfo.c),
213
+
214
+ openInterest: this._openInterests.get(optionInfo.s),
215
+
216
+ markPrice: asNumberIfValid(optionInfo.mp),
217
+ markIV: undefined,
218
+
219
+ delta: asNumberIfValid(optionInfo.d),
220
+ gamma: asNumberIfValid(optionInfo.g),
221
+ vega: asNumberIfValid(optionInfo.v),
222
+ theta: asNumberIfValid(optionInfo.t),
223
+ rho: undefined,
224
+
225
+ underlyingPrice: this._indexPrices.get(underlyingIndex),
226
+ underlyingIndex,
227
+
228
+ timestamp: new Date(optionInfo.E),
229
+ localTimestamp: localTimestamp
230
+ }
231
+
232
+ yield optionSummary
233
+ }
234
+ }
235
+
236
+ type BinanceResponse<T> = {
237
+ stream: string
238
+ data: T
239
+ }
240
+
241
+ type BinanceOptionsTradeData = {
242
+ e: 'trade'
243
+ E: 1696118408137
244
+ s: 'DOGE-231006-0.06-C'
245
+ t: '15'
246
+ p: '2.64'
247
+ q: '0.01'
248
+ b: '4647850284614262784'
249
+ a: '4719907951072796672'
250
+ T: 1696118408134
251
+ S: '-1'
252
+ }
253
+
254
+ type BinanceOptionsDepthData = {
255
+ e: 'depth'
256
+ E: 1696118400038
257
+ T: 1696118399082
258
+ s: 'BTC-231027-34000-C'
259
+ u: 1925729
260
+ pu: 1925729
261
+ b: [['60', '7.31'], ['55', '2.5'], ['50', '15'], ['45', '15'], ['40', '34.04']]
262
+ a: [['65', '8.28'], ['70', '38.88'], ['75', '15'], ['1200', '0.01'], ['4660', '0.42']]
263
+ }
264
+
265
+ type BinanceOptionsTickerData = {
266
+ e: '24hrTicker'
267
+ E: 1696118400043
268
+ T: 1696118400000
269
+ s: 'BNB-231013-200-P'
270
+ o: '1'
271
+ h: '1'
272
+ l: '0.9'
273
+ c: '0.9'
274
+ V: '11.08'
275
+ A: '9.97'
276
+ P: '-0.1'
277
+ p: '-0.1'
278
+ Q: '11'
279
+ F: '0'
280
+ L: '8'
281
+ n: 1
282
+ bo: '1'
283
+ ao: '1.7'
284
+ bq: '50'
285
+ aq: '50'
286
+ b: '0.35929501'
287
+ a: '0.43317497'
288
+ d: '-0.16872899'
289
+ t: '-0.16779034'
290
+ g: '0.0153237'
291
+ v: '0.09935076'
292
+ vo: '0.41658748'
293
+ mp: '1.5'
294
+ hl: '37.1'
295
+ ll: '0.1'
296
+ eep: '0'
297
+ }
298
+
299
+ type BinanceOptionsIndexData = { e: 'index'; E: 1696118400040; s: 'BNBUSDT'; p: '214.6133998' }
300
+
301
+ type BinanceOptionsOpenInterestData = { e: 'openInterest'; E: 1696118400042; s: 'XRP-231006-0.46-P'; o: '39480.0'; h: '20326.64319' }
302
+
303
+ type BinanceBookLevel = [string, string]
@@ -545,6 +545,9 @@ export class HuobiBookTickerMapper implements Mapper<'huobi' | 'huobi-dm' | 'huo
545
545
  const symbol = message.ch.split('.')[1].toUpperCase()
546
546
 
547
547
  if ('quoteTime' in message.tick) {
548
+ if (message.tick.quoteTime === 0) {
549
+ return
550
+ }
548
551
  yield {
549
552
  type: 'book_ticker',
550
553
  symbol,
@@ -767,7 +770,7 @@ type HuobiBBOMessage =
767
770
  askSize: 2.323241
768
771
  bid: 7541.16
769
772
  bidSize: 0.002329
770
- quoteTime: 1575158404057
773
+ quoteTime: number
771
774
  symbol: 'btcusdt'
772
775
  }
773
776
  }
@@ -10,6 +10,11 @@ import {
10
10
  BinanceTradesMapper
11
11
  } from './binance'
12
12
  import { binanceDexBookChangeMapper, binanceDexBookTickerMapper, binanceDexTradesMapper } from './binancedex'
13
+ import {
14
+ BinanceEuropeanOptionsBookChangeMapper,
15
+ BinanceEuropeanOptionsTradesMapper,
16
+ BinanceEuropeanOptionSummaryMapper
17
+ } from './binanceeuropeanoptions'
13
18
  import { BinanceOptionsBookChangeMapper, BinanceOptionsTradesMapper, BinanceOptionSummaryMapper } from './binanceoptions'
14
19
  import {
15
20
  BitfinexBookChangeMapper,
@@ -238,7 +243,8 @@ const tradesMappers = {
238
243
  bitnomial: () => bitnomialTradesMapper,
239
244
  'woo-x': () => wooxTradesMapper,
240
245
  'blockchain-com': () => new BlockchainComTradesMapper(),
241
- 'bybit-options': () => new BybitV5TradesMapper('bybit-options')
246
+ 'bybit-options': () => new BybitV5TradesMapper('bybit-options'),
247
+ 'binance-european-options': () => new BinanceEuropeanOptionsTradesMapper()
242
248
  }
243
249
 
244
250
  const bookChangeMappers = {
@@ -322,7 +328,8 @@ const bookChangeMappers = {
322
328
  bitnomial: () => new BitnomialBookChangMapper(),
323
329
  'woo-x': () => new WooxBookChangeMapper(),
324
330
  'blockchain-com': () => new BlockchainComBookChangeMapper(),
325
- 'bybit-options': () => new BybitV5BookChangeMapper('bybit-options', 25)
331
+ 'bybit-options': () => new BybitV5BookChangeMapper('bybit-options', 25),
332
+ 'binance-european-options': () => new BinanceEuropeanOptionsBookChangeMapper()
326
333
  }
327
334
 
328
335
  const derivativeTickersMappers = {
@@ -363,7 +370,8 @@ const optionsSummaryMappers = {
363
370
  shouldUseOkexV5Mappers(localTimestamp) ? new OkexV5OptionSummaryMapper() : new OkexOptionSummaryMapper(),
364
371
  'binance-options': () => new BinanceOptionSummaryMapper(),
365
372
  'huobi-dm-options': () => new HuobiOptionsSummaryMapper(),
366
- 'bybit-options': () => new BybitV5OptionSummaryMapper()
373
+ 'bybit-options': () => new BybitV5OptionSummaryMapper(),
374
+ 'binance-european-options': () => new BinanceEuropeanOptionSummaryMapper()
367
375
  }
368
376
 
369
377
  const liquidationsMappers = {
@@ -0,0 +1,36 @@
1
+ import { Filter } from '../types'
2
+ import { RealTimeFeedBase } from './realtimefeed'
3
+
4
+ export class BinanceEuropeanOptionsRealTimeFeed extends RealTimeFeedBase {
5
+ protected wssURL = 'wss://nbstream.binance.com/eoptions/stream'
6
+
7
+ protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
8
+ const payload = filters.map((filter, index) => {
9
+ if (!filter.symbols || filter.symbols.length === 0) {
10
+ throw new Error('BinanceEuropeanOptionsRealTimeFeed requires explicitly specified symbols when subscribing to live feed')
11
+ }
12
+
13
+ return {
14
+ method: 'SUBSCRIBE',
15
+ params: filter.symbols.map((symbol) => {
16
+ if (filter.channel === 'depth100') {
17
+ return `${symbol}@${filter.channel}@100ms`
18
+ }
19
+
20
+ return `${symbol}@${filter.channel}`
21
+ }),
22
+ id: index + 1
23
+ }
24
+ })
25
+
26
+ return payload
27
+ }
28
+
29
+ protected messageIsError(message: any): boolean {
30
+ if (message.data !== undefined && message.data.e === 'error') {
31
+ return true
32
+ }
33
+
34
+ return false
35
+ }
36
+ }
@@ -46,6 +46,7 @@ import { KucoinRealTimeFeed } from './kucoin'
46
46
  import { BitnomialRealTimeFeed } from './bitnomial'
47
47
  import { WooxRealTimeFeed } from './woox'
48
48
  import { BlockchainComRealTimeFeed } from './blockchaincom'
49
+ import { BinanceEuropeanOptionsRealTimeFeed } from './binanceeuropeanoptions'
49
50
 
50
51
  export * from './realtimefeed'
51
52
 
@@ -102,7 +103,8 @@ const realTimeFeedsMap: {
102
103
  kucoin: KucoinRealTimeFeed,
103
104
  bitnomial: BitnomialRealTimeFeed,
104
105
  'woo-x': WooxRealTimeFeed,
105
- 'blockchain-com': BlockchainComRealTimeFeed
106
+ 'blockchain-com': BlockchainComRealTimeFeed,
107
+ 'binance-european-options': BinanceEuropeanOptionsRealTimeFeed
106
108
  }
107
109
 
108
110
  export function getRealTimeFeedFactory(exchange: Exchange): RealTimeFeed {