@hackape/tardis-dev
Version:
Convenient access to tick-level historical and real-time cryptocurrency market data via Node.js
315 lines (267 loc) • 8.9 kB
text/typescript
import { BookChange, DerivativeTicker, Trade, OptionSummary, Liquidation } from '../types'
import { Mapper, PendingTickerInfoHelper } from './mapper'
// https://docs.deribit.com/v2/#subscriptions
export const deribitTradesMapper: Mapper<'deribit', Trade> = {
canHandle(message: any) {
const channel = message.params !== undefined ? (message.params.channel as string | undefined) : undefined
if (channel === undefined) {
return false
}
return channel.startsWith('trades')
},
getFilters(symbols?: string[]) {
return [
{
channel: 'trades',
symbols
}
]
},
*map(message: DeribitTradesMessage, localTimestamp: Date): IterableIterator<Trade> {
for (const deribitTrade of message.params.data) {
yield {
type: 'trade',
symbol: deribitTrade.instrument_name,
exchange: 'deribit',
id: deribitTrade.trade_id,
price: deribitTrade.price,
amount: deribitTrade.amount,
side: deribitTrade.direction,
timestamp: new Date(deribitTrade.timestamp),
localTimestamp: localTimestamp
}
}
}
}
const mapBookLevel = (level: DeribitBookLevel) => {
const price = level[1]
const amount = level[0] === 'delete' ? 0 : level[2]
return { price, amount }
}
export const deribitBookChangeMapper: Mapper<'deribit', BookChange> = {
canHandle(message: any) {
const channel = message.params && (message.params.channel as string | undefined)
if (channel === undefined) {
return false
}
return channel.startsWith('book')
},
getFilters(symbols?: string[]) {
return [
{
channel: 'book',
symbols
}
]
},
*map(message: DeribitBookMessage, localTimestamp: Date): IterableIterator<BookChange> {
const deribitBookChange = message.params.data
// snapshots do not have prev_change_id set
const isSnapshot = deribitBookChange.prev_change_id === undefined
yield {
type: 'book_change',
symbol: deribitBookChange.instrument_name,
exchange: 'deribit',
isSnapshot,
bids: deribitBookChange.bids.map(mapBookLevel),
asks: deribitBookChange.asks.map(mapBookLevel),
timestamp: new Date(deribitBookChange.timestamp),
localTimestamp: localTimestamp
}
}
}
export class DeribitDerivativeTickerMapper implements Mapper<'deribit', DerivativeTicker> {
private readonly pendingTickerInfoHelper = new PendingTickerInfoHelper()
canHandle(message: any) {
const channel = message.params && (message.params.channel as string | undefined)
if (channel === undefined) {
return false
}
// exclude options tickers
return channel.startsWith('ticker') && message.params.data.greeks === undefined
}
getFilters(symbols?: string[]) {
return [
{
channel: 'ticker',
symbols
} as const
]
}
*map(message: DeribitTickerMessage, localTimestamp: Date): IterableIterator<DerivativeTicker> {
const deribitTicker = message.params.data
const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(deribitTicker.instrument_name, 'deribit')
pendingTickerInfo.updateFundingRate(deribitTicker.current_funding)
pendingTickerInfo.updateIndexPrice(deribitTicker.index_price)
pendingTickerInfo.updateMarkPrice(deribitTicker.mark_price)
pendingTickerInfo.updateOpenInterest(deribitTicker.open_interest)
pendingTickerInfo.updateLastPrice(deribitTicker.last_price)
pendingTickerInfo.updateTimestamp(new Date(deribitTicker.timestamp))
if (pendingTickerInfo.hasChanged()) {
yield pendingTickerInfo.getSnapshot(localTimestamp)
}
}
}
export class DeribitOptionSummaryMapper implements Mapper<'deribit', OptionSummary> {
getFilters(symbols?: string[]) {
return [
{
channel: 'ticker',
symbols
} as const
]
}
canHandle(message: any) {
const channel = message.params && message.params.channel
if (channel === undefined) {
return false
}
// options ticker has greeks
return channel.startsWith('ticker') && message.params.data.greeks !== undefined
}
*map(message: DeribitOptionTickerMessage, localTimestamp: Date) {
const optionInfo = message.params.data
//e.g., BTC-8JUN20-8750-P
const symbolParts = optionInfo.instrument_name.split('-')
const isPut = symbolParts[3] === 'P'
const strikePrice = Number(symbolParts[2])
const expirationDate = new Date(symbolParts[1] + 'Z')
expirationDate.setUTCHours(8)
const optionSummary: OptionSummary = {
type: 'option_summary',
symbol: optionInfo.instrument_name,
exchange: 'deribit',
optionType: isPut ? 'put' : 'call',
strikePrice,
expirationDate,
bestBidPrice: optionInfo.best_bid_price === 0 ? undefined : optionInfo.best_bid_price,
bestBidAmount: optionInfo.best_bid_amount === 0 ? undefined : optionInfo.best_bid_amount,
bestBidIV: optionInfo.bid_iv === 0 ? undefined : optionInfo.bid_iv,
bestAskPrice: optionInfo.best_ask_price === 0 ? undefined : optionInfo.best_ask_price,
bestAskAmount: optionInfo.best_ask_amount === 0 ? undefined : optionInfo.best_ask_amount,
bestAskIV: optionInfo.ask_iv === 0 ? undefined : optionInfo.ask_iv,
lastPrice: optionInfo.last_price !== null ? optionInfo.last_price : undefined,
openInterest: optionInfo.open_interest,
markPrice: optionInfo.mark_price,
markIV: optionInfo.mark_iv,
delta: optionInfo.greeks.delta,
gamma: optionInfo.greeks.gamma,
vega: optionInfo.greeks.vega,
theta: optionInfo.greeks.theta,
rho: optionInfo.greeks.rho,
underlyingPrice: optionInfo.underlying_price,
underlyingIndex: optionInfo.underlying_index,
timestamp: new Date(optionInfo.timestamp),
localTimestamp: localTimestamp
}
yield optionSummary
}
}
export const deribitLiquidationsMapper: Mapper<'deribit', Liquidation> = {
canHandle(message: any) {
const channel = message.params !== undefined ? (message.params.channel as string | undefined) : undefined
if (channel === undefined) {
return false
}
return channel.startsWith('trades')
},
getFilters(symbols?: string[]) {
return [
{
channel: 'trades',
symbols
}
]
},
*map(message: DeribitTradesMessage, localTimestamp: Date): IterableIterator<Liquidation> {
for (const deribitTrade of message.params.data) {
if (deribitTrade.liquidation !== undefined) {
let side
// "T" when liquidity taker side was under liquidation
if (deribitTrade.liquidation === 'T') {
side = deribitTrade.direction
} else {
// "M" when maker (passive) side of trade was under liquidation
side = deribitTrade.direction === 'buy' ? ('sell' as const) : ('buy' as const)
}
yield {
type: 'liquidation',
symbol: deribitTrade.instrument_name,
exchange: 'deribit',
id: deribitTrade.trade_id,
price: deribitTrade.price,
amount: deribitTrade.amount,
side,
timestamp: new Date(deribitTrade.timestamp),
localTimestamp: localTimestamp
}
}
}
}
}
type DeribitMessage = {
params: {
channel: string
}
}
type DeribitTradesMessage = DeribitMessage & {
params: {
data: {
trade_id: string
instrument_name: string
timestamp: number
direction: 'buy' | 'sell'
price: number
amount: number
trade_seq: number
liquidation?: 'M' | 'T' | 'MT'
}[]
}
}
type DeribitBookLevel = ['new' | 'change' | 'delete', number, number]
type DeribitBookMessage = DeribitMessage & {
params: {
data: {
timestamp: number
instrument_name: string
prev_change_id?: number
bids: DeribitBookLevel[]
asks: DeribitBookLevel[]
}
}
}
type DeribitTickerMessage = DeribitMessage & {
params: {
data: {
timestamp: number
open_interest: number
last_price: number | null
mark_price: number
instrument_name: string
index_price: number
current_funding?: number
funding_8h?: number
}
}
}
type DeribitOptionTickerMessage = DeribitMessage & {
params: {
data: {
underlying_price: number
underlying_index: string
timestamp: number
open_interest: number
mark_price: number
mark_iv: number
last_price: number | null
instrument_name: string
greeks: { vega: number; theta: number; rho: number; gamma: number; delta: number }
bid_iv: number | undefined
best_bid_price: number | undefined
best_bid_amount: number | undefined
best_ask_price: number | undefined
best_ask_amount: number | undefined
ask_iv: number | undefined
}
}
}