@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
JavaScript
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
;