UNPKG

@hackape/tardis-dev

Version:

Convenient access to tick-level historical and real-time cryptocurrency market data via Node.js

307 lines (305 loc) 10.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getMarketInfo = void 0; const got_1 = __importDefault(require("got")); const marketInfoAPIs = { binance: 'https://api.binance.com/api/v3/exchangeInfo', 'binance-futures': 'https://fapi.binance.com/fapi/v1/exchangeInfo', 'binance-delivery': 'https://dapi.binance.com/dapi/v1/exchangeInfo', okex: 'https://www.okex.com/api/spot/v3/instruments', 'okex-swap': 'https://www.okex.com/api/swap/v3/instruments', 'okex-futures': 'https://www.okex.com/api/futures/v3/instruments', huobi: 'https://api.huobi.pro/v1/common/symbols', 'huobi-dm-swap': 'https://api.hbdm.com/swap-api/v1/swap_contract_info', 'huobi-dm': 'https://api.hbdm.com/api/v1/contract_contract_info' }; async function getRawMarketInfo(exchange) { const url = marketInfoAPIs[exchange]; if (!url) return; const info = await got_1.default.get(url).json(); return { exchange, info }; } async function getMarketInfo(exchange) { const resp = await getRawMarketInfo(exchange); if (!resp) return; const database = new MarketInfoDatabase(); switch (resp.exchange) { case 'binance': { const symbols = resp.info.symbols; symbols.forEach((symbol) => { const id = symbol.symbol; database.add({ id, type: 'spot', base: symbol.baseAsset, quote: symbol.quoteAsset, precision: getBinancePrecisionFromFilters(symbol.filters) }); }); break; } case 'binance-futures': // all perpetual case 'binance-delivery': { // some perpetual, some delivery const symbols = resp.info.symbols; symbols.forEach((symbol) => { const id = symbol.symbol; let multiplier = 1; let contractType = 'perpetual'; if ('contractSize' in symbol) { multiplier = symbol.contractSize; contractType = symbol.contractType === 'PERPETUAL' ? 'perpetual' : 'future'; } let contract; if (exchange === 'binance-delivery') { contract = { isInverse: true, unit: symbol.quoteAsset, multiplier }; } else { contract = { isInverse: false, unit: symbol.baseAsset, multiplier }; } database.add({ id, type: contractType, base: symbol.baseAsset, quote: symbol.quoteAsset, precision: { price: symbol.pricePrecision, amount: symbol.quantityPrecision }, contract }); }); break; } case 'okex': { const symbols = resp.info; symbols.forEach((symbol) => { database.add({ id: symbol.instrument_id, type: 'spot', base: symbol.base_currency, quote: symbol.quote_currency, precision: { price: getDecimalPlaces(symbol.tick_size), amount: getDecimalPlaces(symbol.size_increment) } }); }); break; } case 'okex-swap': case 'okex-futures': { const contractType = resp.exchange === 'okex-swap' ? 'perpetual' : 'future'; const symbols = resp.info; symbols.forEach((symbol) => { const id = symbol.instrument_id; const contract = { isInverse: symbol.is_inverse === 'true', multiplier: Number(symbol.contract_val), unit: symbol.contract_val_currency }; database.add({ id, type: contractType, base: symbol.base_currency, quote: symbol.quote_currency, precision: { price: getDecimalPlaces(symbol.tick_size), amount: 0 // okex contract is traded in "cont", which is integer }, contract }); }); break; } case 'huobi': { const symbols = resp.info.data; symbols.forEach((symbol) => { database.add({ id: symbol.symbol, type: 'spot', base: symbol['base-currency'], quote: symbol['quote-currency'], precision: { price: symbol['price-precision'], amount: symbol['amount-precision'] } }); }); break; } case 'huobi-dm-swap': case 'huobi-dm': { const contractType = resp.exchange === 'huobi-dm' ? 'future' : 'perpetual'; const symbols = resp.info.data; symbols.forEach((symbol) => { const id = getHuobiSymbolKey(symbol); database.add({ id, type: contractType, base: symbol.symbol, quote: 'USD', precision: { price: getDecimalPlaces(symbol.price_tick), amount: 0 }, contract: { isInverse: true, multiplier: symbol.contract_size, unit: 'USD' } }); }); break; } } return database; } exports.getMarketInfo = getMarketInfo; class MarketInfoDatabase { constructor() { this.db = new Map(); this.assetInfo = new Map(); this.count = 0; } add(record) { this.count++; this.assetInfo.set(record.id.toLowerCase(), { base: record.base, quote: record.quote }); const key = `${record.base}_${record.quote}_${record.type}`.toLowerCase(); if (this.db.has(key)) { const prevRecord = this.db.get(key); const isEqual = compareMarketInfoRecord(prevRecord, record); if (!isEqual) { throw Error('wierd case'); } } else { this.db.set(key, record); } } // @ts-ignore mixin(exchange, symbol) { const assetInfo = this.assetInfo.get(symbol.id.toLowerCase()); if (!assetInfo) { console.warn(`cannot find asset info for ${exchange}:${symbol.id}`); return symbol; } const key = `${assetInfo.base}_${assetInfo.quote}_${symbol.type}`.toLowerCase(); const record = this.db.get(key); if (record) { symbol.market = { base: record.base, quote: record.quote, precision: record.precision, contract: record.contract }; } else { console.warn(`cannot find market info for ${exchange}:${symbol.id}`); } return symbol; } } function getBinancePrecisionFromFilters(filters) { const priceFilter = filters.find((filter) => filter.filterType === 'PRICE_FILTER'); const lotSize = filters.find((filter) => filter.filterType === 'LOT_SIZE'); const price = getDecimalPlaces(priceFilter.tickSize); const amount = getDecimalPlaces(lotSize.stepSize); return { price, amount }; } function compareMarketInfoRecord(a, b) { const symbolInfoIsEqual = a.base === b.base && a.quote === b.quote && a.type === b.type && a.precision.price === b.precision.price && a.precision.amount === b.precision.amount; if (a.contract && b.contract) { const contractInfoIsEqual = a.contract.isInverse === b.contract.isInverse && a.contract.multiplier === b.contract.multiplier && a.contract.unit === b.contract.unit; return contractInfoIsEqual && symbolInfoIsEqual; } else { // this is impossible throw Error('missing contract field'); } } function getDecimalPlaces(x) { if (typeof x === 'string') x = Number(x); const str = String(x); if (str.includes('e')) { if (str.includes('e-')) { return Number(str.split('e-')[1]); } else { return 0; } } else { const decimals = str.split('.')[1]; return decimals ? decimals.length : 0; } } function getHuobiSymbolKey(symbol) { if ('contract_type' in symbol) { switch (symbol.contract_type) { case 'this_week': return `${symbol.symbol}_CW`; case 'next_week': return `${symbol.symbol}_NW`; case 'quarter': return `${symbol.symbol}_CQ`; case 'next_quarter': return `${symbol.symbol}_NQ`; } } else { return symbol.contract_code; } } /* function getRecordKeyForSymbol(exchange: Exchange, symbol: AvailableSymbol) { let symbolId = symbol.id const type = symbol.type switch (exchange) { case 'binance-futures': { // "btcusdt" return `${symbolId.slice(0, -4)}_usdt_${type}`.toLowerCase() } case 'binance-delivery': { // "btcusd_perp" or "btcusd_210326" symbolId = symbolId.slice(0, symbolId.indexOf('_')) return `${symbolId.slice(0, -3)}_usd_${type}`.toLowerCase() } case 'okex-swap': case 'okex-futures': { const parts = symbolId.split('-') return `${parts[0]}_${parts[1]}_${type}`.toLowerCase() } case 'huobi-dm': { // "BTC_CW" return `${symbolId.slice(0, -3)}_usd_${type}`.toLowerCase() } case 'huobi-dm-swap': { // "BTC-USD" return `${symbolId.replace('-', '_')}_${type}`.toLowerCase() } } return } */ //# sourceMappingURL=marketinfo.js.map