@alleyway/add-tradingview-alerts-tool
Version:
Add TradingView Alerts in bulk
403 lines • 16.4 kB
JavaScript
import { MasterSymbol } from "../classes.js";
import log from "./log.js";
import kleur from "kleur";
import fs from "fs";
import path from "path";
import { Classification } from "../interfaces.js";
import { isEnvEnabled } from "./common-service.js";
import get from "lodash.get";
export const BINANCE = "binance";
export const BINANCE_FUTURES_USDM = "binance_futures_usdm";
export const BINANCE_FUTURES_COINM = "binance_futures_coinm";
export const BINANCEUS = "binanceus";
export const BITTREX = "bittrex";
export const COINBASE = "coinbase";
// export const FTX = "ftx"
export const KRAKEN = "kraken";
export const KRAKEN_FUTURES = "kraken_futures";
export const KUCOIN = "kucoin";
export const KUCOIN_FUTURES = "kucoin_futures"; // not on TV yet
export const OKX_SPOT = "okx_spot"; //formerly OKEX
export const OKX_SWAP = "okx_swap"; //formerly OKEX
export const OKX_FUTURES = "okx_futures"; //formerly OKEX
export const BYBIT_INVERSE = "bybit_inverse";
export const BYBIT_LINEAR = "bybit_linear";
export const BYBIT_SPOT = "bybit_spot";
export const BITMEX = "bitmex"; // unable to do multiple pairs in 3commas
export const SOURCES_AVAILABLE = [
BINANCE,
BINANCE_FUTURES_USDM,
BINANCE_FUTURES_COINM,
BINANCEUS,
BITTREX,
COINBASE,
BYBIT_INVERSE,
BYBIT_LINEAR,
// BYBIT_SPOT, // spot is STILL not on tradingview
// FTX,
KRAKEN,
KUCOIN,
OKX_SPOT,
OKX_SWAP
];
const logJson = (obj, name = "") => {
log.debug(`${name} \n ${kleur.yellow(JSON.stringify(obj, null, 4))}`);
};
export const proxyMaybe = (url) => {
if (process.env.PROXY_BASE) {
// parse the url to get the host, path, and query segments
const realUrl = new URL(url);
return `${process.env.PROXY_BASE}/api/proxy${realUrl.search || "?"}&host=${realUrl.host}&path=${realUrl.pathname}`;
}
else {
return url;
}
};
const fetchAndTransform = async (url, responsePath, transformer) => {
const responseObject = await (await fetch(url)).json();
const resultsArray = responsePath ? get(responseObject, responsePath) : responseObject;
const masterSymbols = [];
log.info(`${resultsArray.length} results returned from API`);
let count = 0;
for (const obj of resultsArray) {
const masterSymbol = transformer(obj);
if (masterSymbol) {
if (count == 0 && isEnvEnabled(process.env.TEST_SAVE_RESPONSE)) {
fs.writeFileSync(path.join(process.cwd(), "output", masterSymbol.source + "_in.json"), JSON.stringify(resultsArray, null, 2), { encoding: "utf-8" });
}
masterSymbols.push(masterSymbol);
count += 1;
}
}
log.info(`${masterSymbols.length} symbols parsed from results`);
return masterSymbols;
};
export const fetchBitMex = async () => {
const transformer = (obj) => {
if (obj.expiry === null) {
return new MasterSymbol(obj, BITMEX, obj.symbol, obj.quoteCurrency, `BITMEX:${obj.symbol}.P`, Classification.FUTURES_PERPETUAL);
}
else {
return null;
}
};
return fetchAndTransform("https://www.bitmex.com/api/v1/instrument/active", null, transformer);
};
export const fetchByBitInverse = async () => {
const transformer = (obj) => {
if (obj.status === "Trading") {
let classification;
let symbol = obj.symbol;
let tvSuffix = "";
if (obj.contractType == "InversePerpetual") {
classification = Classification.FUTURES_PERPETUAL;
tvSuffix = ".P";
}
else if (obj.contractType == "InverseFutures") {
classification = Classification.FUTURES_DATED;
const match = obj.symbol.match(/(.*?)(\d\d)$/);
symbol = `${match[1]}20${match[2]}`;
}
else {
log.error(`Found unknown contract type: ${obj.contractType}`);
}
return new MasterSymbol(obj, BYBIT_INVERSE, symbol, obj.quoteCoin, `BYBIT:${symbol}${tvSuffix}`, classification);
}
else {
//logJson(obj, "ByBit Discarded:")
return null;
}
};
return fetchAndTransform(proxyMaybe("https://api.bybit.com/v5/market/instruments-info?category=inverse"), "result.list", transformer);
};
export const fetchByBitLinear = async () => {
const transformer = (obj) => {
if (obj.status === "Trading") {
let classification;
let symbol = obj.symbol;
if (obj.contractType == "LinearPerpetual") {
classification = Classification.FUTURES_PERPETUAL;
}
else {
log.error(`Found unknown contract type: ${obj.contractType}`);
}
return new MasterSymbol(obj, BYBIT_LINEAR, symbol, obj.quoteCoin, `BYBIT:${symbol}.P`, classification);
}
else {
//logJson(obj, "ByBit Discarded:")
return null;
}
};
return fetchAndTransform(proxyMaybe("https://api.bybit.com/v5/market/instruments-info?category=linear"), "result.list", transformer);
};
export const fetchByBitSpot = async () => {
// actually these aren't on tradingview yet, even in march of 2023
const transformer = (obj) => {
return new MasterSymbol(obj, BYBIT_SPOT, obj.baseCoin, obj.quoteCoin, `BYBIT:${obj.symbol}`);
};
return fetchAndTransform(proxyMaybe("https://api.bybit.com/v5/market/instruments-info?category=spot&limit=1000"), "result.list", transformer);
};
export const fetchKucoin = async () => {
const transformer = (obj) => {
if (obj.enableTrading) {
const symbol = obj.name.replace("-", "");
return new MasterSymbol(obj, KUCOIN, obj.baseCurrency, obj.quoteCurrency, `KUCOIN:${symbol}`);
}
else {
//logJson(obj, "Kucoin Discarded:")
return null;
}
};
return fetchAndTransform("https://api.kucoin.com/api/v2/symbols", "data", transformer);
};
export const fetchKraken = async () => {
const resp = await fetch("https://api.kraken.com/0/public/AssetPairs");
const responseObject = await resp.json();
// @ts-ignore
const symbolsObject = responseObject.result;
const masterSymbols = [];
const keys = Object.keys(symbolsObject);
log.info(`found ${keys.length} results from the API`);
for (const key of keys) { // key = "AAVEAUD"
const obj = symbolsObject[key];
const [instrument, quoteAsset] = obj.wsname.split("\/"); // "AAVE\/AUD"
masterSymbols.push(new MasterSymbol(obj, KRAKEN, instrument, quoteAsset));
}
log.info(`returning ${masterSymbols.length} results symbols parsed`);
return masterSymbols;
};
export const fetchKrakenFutures = async () => {
/*
{
"tag": "perpetual",
"pair": "XBT:USD",
"symbol": "pi_xbtusd",
"markPrice": 36339.5,
"bid": 36332.5,
"bidSize": 1091,
"ask": 36355.5,
"askSize": 4080,
"vol24h": 341903278,
"openInterest": 89423744,
"open24h": 34915,
"last": 36374.5,
"lastTime": "2022-01-24T18:50:06.030Z",
"lastSize": 58,
"suspended": false,
"fundingRate": 3.55444348e-10,
"fundingRatePrediction": -9.1005881e-10
},
*/
const transformer = (obj) => {
if (obj.suspended === false) {
const [baseCurrency, quoteCurrency] = obj.pair.split(":");
let classification = null;
if (obj.tag === "perpetual") {
classification = Classification.FUTURES_PERPETUAL;
return new MasterSymbol(obj, KRAKEN_FUTURES, obj.symbol, quoteCurrency, `KRAKEN:${baseCurrency}${quoteCurrency}.PM`, classification);
}
else {
// TODO: dated futures
return null;
}
}
else {
logJson(obj, `${KRAKEN_FUTURES} Discarded:"`);
return null;
}
};
return fetchAndTransform("https://futures.kraken.com/derivatives/api/v3/tickers", "tickers", transformer);
};
export const fetchBittrex = async () => {
const transformer = (obj) => {
if (obj.status === "ONLINE") {
const classification = (obj.baseCurrencySymbol.match(/X\s(?:BULL|BEAR)\s/)) ? Classification.LEVERAGED_TOKEN : Classification.SPOT;
return new MasterSymbol(obj, BITTREX, obj.baseCurrencySymbol, obj.quoteCurrencySymbol, null, classification);
}
else {
//logJson(obj, "Bittrex Discarded:")
return null;
}
};
return fetchAndTransform("https://api.bittrex.com/v3/markets", null, transformer);
};
export const fetchCoinbase = async () => {
const transformer = (obj) => {
if (!obj.trading_disabled && obj.status == "online") {
return new MasterSymbol(obj, COINBASE, obj.base_currency, obj.quote_currency);
}
else {
//logJson(obj, "Coinbase Discarded:")
return null;
}
};
return fetchAndTransform("https://api.exchange.coinbase.com/products", null, transformer);
};
// export const fetchFtx = async (): Promise<MasterSymbol[]> => {
//
// const levTokensTransformer = (obj) => {
// return new MasterSymbol(obj, "FTX_LEV_TOKENS", obj.name, "USD", `FTX:${obj.underlying}`, Classification.FUTURES_DATED)
// }
// const levTokenSymbols = await fetchAndTransform("https://ftx.com/api/lt/tokens", "result", levTokensTransformer)
//
// const levTokenNames = levTokenSymbols.map((ms) => ms.instrument)
//
//
// const transformer = (obj) => {
// if (obj.enabled) {
// if (obj.type === "spot") {
// const classification: ClassificationType = levTokenNames.includes(obj.baseCurrency) ? Classification.LEVERAGED_TOKEN : Classification.SPOT
// return new MasterSymbol(obj, FTX, obj.baseCurrency, obj.quoteCurrency, null, classification)
// } else if (obj.type == "future") {
//
// if (obj.tokenizedEquity) return null // eg. AAPL or AAPL-0326
//
// if (obj.name.match(/-PERP$/)) {
// return new MasterSymbol(obj, FTX, obj.name, "USD", `FTX:${obj.underlying}PERP`, Classification.FUTURES_PERPETUAL)
//
// } else if (obj.name.match(/-\d{4}$/)) { //AVAX-0326
// const [base, exp] = obj.name.split("-")
// return new MasterSymbol(obj, FTX, obj.name, "USD", `FTX:${base}${exp}`, Classification.FUTURES_DATED)
// } else {
// return null
// }
// } else {
// return null
// }
// } else {
//
// return null
// }
// }
// return fetchAndTransform("https://ftx.com/api/markets", "result", transformer)
//
// }
export const fetchBinanceFuturesUsdM = async () => {
const transformer = (obj) => {
if (obj.status === "TRADING" && obj.contractType === "PERPETUAL") {
return new MasterSymbol(obj, BINANCE_FUTURES_USDM, obj.symbol, obj.quoteAsset, `BINANCE:${obj.baseAsset}${obj.quoteAsset}.P`, Classification.FUTURES_PERPETUAL);
}
else {
// logJson(obj, "Binance Futures Discarded:")
//TODO: add expiring, contractType: NEXT_QUARTER/CURRENT_QUARTER, etc.
return null;
}
};
return fetchAndTransform(proxyMaybe("https://fapi.binance.com/fapi/v1/exchangeInfo"), "symbols", transformer);
};
export const fetchBinanceFuturesCoinM = async () => {
const transformer = (obj) => {
if (obj.contractStatus === "TRADING" && obj.contractType === "PERPETUAL") {
return new MasterSymbol(obj, BINANCE_FUTURES_COINM, obj.symbol, obj.quoteAsset, `BINANCE:${obj.baseAsset}${obj.quoteAsset}.P`, Classification.FUTURES_PERPETUAL);
}
else {
// logJson(obj, "Binance Futures Discarded:")
return null;
}
};
return fetchAndTransform(proxyMaybe("https://dapi.binance.com/dapi/v1/exchangeInfo"), "symbols", transformer);
};
export const fetchBinance = async (isUs) => {
const exchange = isUs ? BINANCEUS : BINANCE;
const url = isUs ? "https://api.binance.us/api/v3/exchangeInfo" : "https://api.binance.com/api/v3/exchangeInfo";
const transformer = (obj) => {
if (obj.status === "TRADING") {
if (obj.isSpotTradingAllowed === true) {
return new MasterSymbol(obj, exchange, obj.baseAsset, obj.quoteAsset);
}
else {
return new MasterSymbol(obj, exchange, obj.baseAsset, obj.quoteAsset, null, Classification.LEVERAGED_TOKEN);
}
}
else {
// logJson(obj, `${exchange} Discarded`)
return null;
}
};
return fetchAndTransform(proxyMaybe(url), "symbols", transformer);
};
export const fetchOkxSpot = async () => {
const transformer = (obj) => {
return new MasterSymbol(obj, OKX_SPOT, obj.baseCcy, obj.quoteCcy, `OKEX:${obj.baseCcy}${obj.quoteCcy}`);
};
//NOTE: unable to get all instruments in same api call, that's why we separate
return fetchAndTransform("https://www.okx.com/api/v5/public/instruments?instType=SPOT", "data", transformer);
};
export const fetchOkxSwap = async () => {
const transformer = (obj) => {
const symbol = obj.instFamily.replace("-", "");
if (obj.ctType === "inverse") {
return new MasterSymbol(obj, OKX_SWAP, symbol, obj.ctValCcy, `OKX:${symbol}.P`, Classification.FUTURES_PERPETUAL);
}
else if (obj.ctType === "linear") {
return new MasterSymbol(obj, OKX_SWAP, symbol, obj.ctValCcy, `OKX:${obj.ctValCcy}${obj.settleCcy}.P`, Classification.FUTURES_PERPETUAL);
}
else {
log.warn(`unable to parse ctType: ${obj.ctType}`);
return null;
}
};
return fetchAndTransform("https://www.okx.com/api/v5/public/instruments?instType=SWAP", "data", transformer);
};
// export const fetchOkxFutures = async (): Promise<MasterSymbol[]> => {
// const transformer = (obj) => {
// return new MasterSymbol(obj, OKX_FUTURES, obj.base_currency, obj.quote_currency, `OKEX:${obj.base_currency}${obj.quote_currency}`)
// }
// return fetchAndTransform("https://www.okx.com/api/v5/public/instruments?instType=FUTURES", "data", transformer)
// }
export const fetchSymbolsForSource = async (source) => {
let symbolArray;
switch (source) {
case BINANCE_FUTURES_USDM:
symbolArray = await fetchBinanceFuturesUsdM();
break;
case BINANCE_FUTURES_COINM:
symbolArray = await fetchBinanceFuturesCoinM();
break;
case BINANCE:
symbolArray = await fetchBinance(false);
break;
case BINANCEUS:
symbolArray = await fetchBinance(true);
break;
// case FTX:
// symbolArray = await fetchFtx()
// break;
case COINBASE:
symbolArray = await fetchCoinbase();
break;
case BITTREX:
symbolArray = await fetchBittrex();
break;
case KRAKEN:
symbolArray = await fetchKraken();
break;
case KRAKEN_FUTURES:
symbolArray = await fetchKrakenFutures();
break;
case KUCOIN:
symbolArray = await fetchKucoin();
break;
case OKX_SPOT:
symbolArray = await fetchOkxSpot();
break;
case OKX_SWAP:
symbolArray = await fetchOkxSwap();
break;
case BYBIT_SPOT:
symbolArray = await fetchByBitSpot();
break;
case BYBIT_LINEAR:
symbolArray = await fetchByBitLinear();
break;
case BYBIT_INVERSE:
symbolArray = await fetchByBitInverse();
break;
default:
log.error(`Invalid source specified: ${kleur.yellow(source)} \n\n Choose one of the following:\n\n ${kleur.green(SOURCES_AVAILABLE.join(", "))}`);
process.exit(1);
return [];
}
return symbolArray;
};
//# sourceMappingURL=exchange-service.js.map