tardis-dev
Version:
Convenient access to tick-level historical and real-time cryptocurrency market data via Node.js
190 lines (172 loc) • 6.2 kB
text/typescript
import { getOptions } from './options.ts'
import type { SymbolType } from './exchangedetails.ts'
import type { Exchange } from './types.ts'
import { getJSON } from './handy.ts'
export async function getInstrumentInfo(exchange: Exchange): Promise<InstrumentInfo[]>
export async function getInstrumentInfo(exchange: Exchange | Exchange[], filter: InstrumentInfoFilter): Promise<InstrumentInfo[]>
export async function getInstrumentInfo(exchange: Exchange, symbol: string): Promise<InstrumentInfo>
export async function getInstrumentInfo(exchange: Exchange | Exchange[], filterOrSymbol?: InstrumentInfoFilter | string) {
if (Array.isArray(exchange)) {
const exchanges = exchange
const results = await Promise.all(exchanges.map((e) => getInstrumentInfoForExchange(e, filterOrSymbol)))
return results.flat()
} else {
return getInstrumentInfoForExchange(exchange, filterOrSymbol)
}
}
async function getInstrumentInfoForExchange(exchange: Exchange, filterOrSymbol?: InstrumentInfoFilter | string) {
const options = getOptions()
let url = `${options.endpoint}/instruments/${exchange}`
if (typeof filterOrSymbol === 'string') {
url += `/${encodeURIComponent(filterOrSymbol)}`
} else if (typeof filterOrSymbol === 'object') {
url += `?filter=${encodeURIComponent(JSON.stringify(filterOrSymbol))}`
}
try {
const { data } = await getJSON(url, {
headers: { Authorization: `Bearer ${options.apiKey}` }
})
return data
} catch (e: any) {
// expose 400 error message from server
if (e.response?.statusCode === 400) {
let err: { code: Number; message: string }
try {
err = JSON.parse(e.response.body)
} catch {
throw e
}
throw err ? new Error(`${err.message} (${err.code})`) : e
} else {
throw e
}
}
}
export async function findInstrumentSymbols(
exchanges: Exchange[],
filter: InstrumentInfoFilter,
selector: InstrumentSymbolSelector = 'id'
): Promise<InstrumentSymbols[]> {
if (selector !== 'id' && selector !== 'datasetId') {
throw new Error("Invalid selector. Supported values are 'id' and 'datasetId'.")
}
return await Promise.all(
exchanges.map(async (exchange) => {
const instruments = (await getInstrumentInfoForExchange(exchange, filter)) as InstrumentInfo[]
return {
exchange,
symbols: instruments.map((instrument) => (selector === 'datasetId' ? (instrument.datasetId ?? instrument.id) : instrument.id))
}
})
)
}
export type InstrumentSymbolSelector = 'id' | 'datasetId'
export type InstrumentSymbols = {
exchange: Exchange
symbols: string[]
}
export type InstrumentInfoFilter = {
baseCurrency?: string | string[]
quoteCurrency?: string | string[]
type?: SymbolType | SymbolType[]
contractType?: ContractType | ContractType[]
underlyingType?: UnderlyingType | UnderlyingType[]
active?: boolean
availableSince?: string
availableTo?: string
}
export type ContractType =
| 'move'
| 'linear_future'
| 'inverse_future'
| 'quanto_future'
| 'linear_perpetual'
| 'inverse_perpetual'
| 'quanto_perpetual'
| 'put_option'
| 'call_option'
| 'turbo_put_option'
| 'turbo_call_option'
| 'spread'
| 'interest_rate_swap'
| 'repo'
| 'index'
export type UnderlyingType = 'native' | 'equity' | 'commodity' | 'fixed_income' | 'fx' | 'index' | 'pre_market'
export interface InstrumentInfo {
/** symbol id */
id: string
/** dataset symbol id, may differ from id */
datasetId?: string
/** exchange id */
exchange: string
/** normalized, so for example bitmex XBTUSD has base currency set to BTC not XBT */
baseCurrency: string
/** normalized, so for example bitfinex BTCUST has quote currency set to USDT, not UST */
quoteCurrency: string
type: SymbolType
/** derivative contract type */
contractType?: ContractType
/** indicates if the instrument can currently be traded. */
active: boolean
/** date in ISO format */
availableSince: string
/** date in ISO format */
availableTo?: string
/** date in ISO format, when the instrument was first listed on the exchange */
listing?: string
/** in ISO format, only for futures and options */
expiry?: string
/** expiration schedule type */
expirationType?: 'daily' | 'weekly' | 'next_week' | 'quarter' | 'next_quarter'
/** the underlying index for derivatives */
underlyingIndex?: string
/** underlying asset class */
underlyingType?: UnderlyingType
/** price tick size, price precision can be calculated from it */
priceIncrement: number
/** amount tick size, amount/size precision can be calculated from it */
amountIncrement: number
/** min order size */
minTradeAmount: number
/** minimum notional value */
minNotional?: number
/** consider it as illustrative only, as it depends in practice on account traded volume levels, different categories, VIP levels, owning exchange currency etc */
makerFee: number
/** consider it as illustrative only, as it depends in practice on account traded volume levels, different categories, VIP levels, owning exchange currency etc */
takerFee: number
/** only for derivatives */
inverse?: boolean
/** only for derivatives */
contractMultiplier?: number
/** only for quanto instruments */
quanto?: boolean
/** only for quanto instruments as settlement currency is different base/quote currency */
settlementCurrency?: string
/** strike price, only for options */
strikePrice?: number
/** option type, only for options */
optionType?: 'call' | 'put'
/** margin mode */
marginMode?: 'isolated' | 'cross'
/** whether margin trading is supported (spot) */
margin?: boolean
/** if this instrument is an alias for another */
aliasFor?: string
/** historical changes to instrument parameters */
changes?: {
until: string
priceIncrement?: number
amountIncrement?: number
contractMultiplier?: number
minTradeAmount?: number
makerFee?: number
takerFee?: number
quanto?: boolean
inverse?: boolean
settlementCurrency?: string
underlyingIndex?: string
contractType?: ContractType
quoteCurrency?: string
type?: string
}[]
}