@hackape/tardis-dev
Version:
Convenient access to tick-level historical and real-time cryptocurrency market data via Node.js
113 lines (94 loc) • 3.5 kB
text/typescript
import got from 'got'
import { Writable } from 'stream'
import { Filter } from '../types'
import { RealTimeFeedBase, PoolingClientBase, MultiConnectionRealTimeFeedBase } from './realtimefeed'
import { batch } from '../handy'
abstract class FTXRealTimeFeedBase extends MultiConnectionRealTimeFeedBase {
protected abstract wssURL: string
protected abstract httpURL: string
protected *_getRealTimeFeeds(exchange: string, filters: Filter<string>[], timeoutIntervalMS?: number, onError?: (error: Error) => void) {
const wsFilters = filters.filter((f) => f.channel !== 'instrument')
if (wsFilters.length > 0) {
yield new FtxSingleConnectionRealTimeFeed(exchange, wsFilters, this.wssURL, timeoutIntervalMS, onError)
}
const instrumentInfoFilters = filters.filter((f) => f.channel === 'instrument')
if (instrumentInfoFilters.length > 0) {
const instruments = instrumentInfoFilters.flatMap((s) => s.symbols!)
yield new FTXInstrumentInfoClient(exchange, this.httpURL, instruments)
}
}
}
class FtxSingleConnectionRealTimeFeed extends RealTimeFeedBase {
constructor(
exchange: string,
filters: Filter<string>[],
protected wssURL: string,
timeoutIntervalMS: number | undefined,
onError?: (error: Error) => void
) {
super(exchange, filters, timeoutIntervalMS, onError)
}
protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
return filters
.map((filter) => {
if (!filter.symbols || filter.symbols.length === 0) {
throw new Error('FtxRealTimeFeed requires explicitly specified symbols when subscribing to live feed')
}
return filter.symbols.map((symbol) => {
return {
op: 'subscribe',
channel: filter.channel,
market: symbol
}
})
})
.flatMap((c) => c)
}
protected messageIsError(message: any): boolean {
return message.type === 'error'
}
}
class FTXInstrumentInfoClient extends PoolingClientBase {
constructor(exchange: string, private readonly _httpURL: string, private readonly _instruments: string[]) {
super(exchange, 3)
}
protected async poolDataToStream(outputStream: Writable) {
for (const instruments of batch(this._instruments, 10)) {
await Promise.all(
instruments.map(async (instrument) => {
if (outputStream.destroyed) {
return
}
const responses = await Promise.all([
got.get(`${this._httpURL}/futures/${instrument}/stats`, { timeout: 2000 }).json() as any,
got.get(`${this._httpURL}/futures/${instrument}`, { timeout: 2000 }).json() as any
])
if (responses.some((r) => r.success === false)) {
return
}
const instrumentMessage = {
channel: 'instrument',
generated: true,
market: instrument,
type: 'update',
data: {
stats: responses[0].result,
info: responses[1].result
}
}
if (outputStream.writable) {
outputStream.write(instrumentMessage)
}
})
)
}
}
}
export class FtxRealTimeFeed extends FTXRealTimeFeedBase {
protected wssURL = 'wss://ws.ftx.com/ws'
protected httpURL = 'https://ftx.com/api'
}
export class FtxUSRealTimeFeed extends FTXRealTimeFeedBase {
protected wssURL = 'wss://ftx.us/ws/'
protected httpURL = 'https://ftx.us/api'
}