UNPKG

@thoshpathi/utils-smartapi

Version:

Extended utilities for Angel One's smartapi-javascript SDK, including custom methods and helpers for market data like candles, P&L, and more.

567 lines (555 loc) 18.6 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { OHLCData: () => OHLCData, SmartApiEnhanced: () => SmartApiEnhanced, SmartApiError: () => SmartApiError, atrSeries: () => atrSeries, calculatePL: () => calculatePL, candleFetchDayConfigs: () => candleFetchDayConfigs, candleMaxDayConfigs: () => candleMaxDayConfigs, emaCrossoverTrends: () => emaCrossoverTrends, emaSeries: () => emaSeries, getIndexHistoricalData: () => getIndexHistoricalData, getIndexLatestHistoricalData: () => getIndexLatestHistoricalData, getIndexScripLtpMap: () => getIndexScripLtpMap, getLastCompletedMarketCandle: () => getLastCompletedMarketCandle, getLoggedSmartapi: () => getLoggedSmartapi, getLoggedWebsocket: () => getLoggedWebsocket, getManyScripLtpMap: () => getManyScripLtpMap, getManyScripLtpMapCache: () => getManyScripLtpMapCache, getSmartapi: () => getSmartapi, intervalMinuteConfigs: () => intervalMinuteConfigs, isProfitReached: () => isProfitReached, isStoplossReached: () => isStoplossReached, rmaSeries: () => rmaSeries, smaSeries: () => smaSeries, trSeries: () => trSeries }); module.exports = __toCommonJS(index_exports); // src/configs.ts var candleMaxDayConfigs = { ONE_MINUTE: 30, THREE_MINUTE: 60, FIVE_MINUTE: 100, TEN_MINUTE: 100, FIFTEEN_MINUTE: 200, THIRTY_MINUTE: 200, ONE_HOUR: 400, ONE_DAY: 2e3 }; var candleFetchDayConfigs = Object.fromEntries( Object.entries(candleMaxDayConfigs).map(([key, value]) => [ key, Math.floor(value / 5) ]) ); var intervalMinuteConfigs = { ONE_MINUTE: 1, THREE_MINUTE: 3, FIVE_MINUTE: 5, TEN_MINUTE: 10, FIFTEEN_MINUTE: 15, THIRTY_MINUTE: 30, ONE_HOUR: 60, ONE_DAY: 24 * 60 }; // src/candle_utils.ts function getLastCompletedMarketCandle(interval, marketOpenTime = "09:15:00") { const [openHour, openMinute] = marketOpenTime.split(":").map((s) => +s.trim()); const marketOpen = /* @__PURE__ */ new Date(); marketOpen.setHours(openHour, openMinute, 0, 0); const diffMs = +/* @__PURE__ */ new Date() - +marketOpen; if (diffMs < 0) return null; const diffMinutes = Math.floor(diffMs / 6e4); const intervalMinutes = intervalMinuteConfigs[interval]; let completedCandles = Math.floor(diffMinutes / intervalMinutes); const minutesSinceLastCandle = diffMinutes % intervalMinutes; if (minutesSinceLastCandle === 0 && diffMinutes !== 0) { completedCandles -= 1; } const lastCandleMinutes = completedCandles * intervalMinutes; const lastCandleTime = new Date(marketOpen); lastCandleTime.setMinutes(marketOpen.getMinutes() + lastCandleMinutes); return lastCandleTime; } // src/pl_utils.ts function isProfitReached({ entry, points, trend, ltp }) { return trend === "BUY" ? ltp >= entry + points : ltp <= entry - points; } function isStoplossReached({ entry, points, trend, ltp }) { return trend === "BUY" ? ltp <= entry - points : ltp >= entry + points; } function calculatePL({ entry, qty, trend, ltp }) { const points = trend === "BUY" ? ltp - entry : entry - ltp; return points * qty; } // src/smartapi/index.ts var import_smartapi_javascript2 = require("@thoshpathi/smartapi-javascript"); var import_utils_core = require("@thoshpathi/utils-core"); // src/smartapi/smartapi_enhanced.ts var import_smartapi_javascript = require("@thoshpathi/smartapi-javascript"); // src/smartapi/ohlc_data.ts var OHLCData = class { constructor(date, open, high, low, close) { const parsedDate = new Date(date); if (isNaN(parsedDate.getTime())) { throw new Error("Invalid date"); } if ([open, high, low, close].some((value) => isNaN(value))) { throw new Error("Open, High, Low, and Close must be valid numbers"); } if (low > high) { throw new Error("Low cannot be greater than High"); } this.date = parsedDate; this.open = open; this.high = high; this.low = low; this.close = close; } }; // src/smartapi/smartapi_enhanced.ts var import_date_fns = require("date-fns"); var SmartApiError = class extends Error { constructor(message, errorCode) { super(message); this.errorCode = errorCode; } }; var SmartApiEnhanced = class extends import_smartapi_javascript.SmartAPI { constructor(params, apiRetryDelayMs, logger3) { super(params); this.apiRetryDelayMs = apiRetryDelayMs; this.logger = logger3; this.apiRetryDelayMs = apiRetryDelayMs; this.logger = logger3; } getData(response) { if (response.status && response.errorcode === "") return response.data; this.logger?.e("smart api error response", response); throw new SmartApiError(response.message, response.errorcode); } async retryCallback(callback) { let response = await callback(); if ("status" in response && response.status === 500) { this.logger?.w("smart api 500 error. retry once"); await new Promise((resolve) => setTimeout(resolve, this.apiRetryDelayMs)); response = await callback(); } return this.getData(response); } async generateSession_e(clientCode, clientMpin, totp) { const response = await this.generateSession( clientCode, clientMpin, totp ); return this.getData(response); } async logout_e(clientCode) { const response = await this.logout(clientCode); return this.getData(response); } fetchProfile() { return this.retryCallback(() => this.getProfile()); } fetchRMS() { return this.retryCallback(() => this.getRMS()); } fetchOrderBook() { return this.retryCallback(() => this.getOrderBook()); } async fetchMarketData(params) { const { mode = "LTP", exchangeTokens } = params; for (const prop in exchangeTokens) { exchangeTokens[prop] = [...new Set(exchangeTokens[prop])]; } const responseData = await this.retryCallback( () => this.marketData({ mode, exchangeTokens }) ); return responseData.fetched; } fetchTokensLtp(exchangeTokens) { return this.fetchMarketData({ mode: "LTP", exchangeTokens }); } fetchTokensOhlc(exchangeTokens) { return this.fetchMarketData({ mode: "OHLC", exchangeTokens }); } fetchTokensQuote(exchangeTokens) { return this.fetchMarketData({ mode: "FULL", exchangeTokens }); } placeOrder_e(params) { return this.retryCallback(() => this.placeOrder(params)); } fetchCandlesDatetimeString(date) { return (0, import_date_fns.format)(date, "yyyy-MM-dd HH:mm"); } async fetchCandleData(params) { const { symboltoken, fromdate, todate, exchange = "NSE", interval = "FIFTEEN_MINUTE" } = params; const responseData = await this.retryCallback( () => this.getCandleData({ symboltoken, fromdate: this.fetchCandlesDatetimeString(fromdate), todate: this.fetchCandlesDatetimeString(todate), exchange, interval }) ); return responseData.map((data) => { const date = new Date(data[0]), open = data[1], high = data[2], low = data[3], close = data[4]; return new OHLCData(date, +open, +high, +low, +close); }); } async fetchCompletedOrders(orderResponses) { const ordersBook = await this.fetchOrderBook(); if (ordersBook == null || ordersBook.length <= 0) { this.logger?.c("failed to get orders book"); return null; } const orderIds = orderResponses.map((v) => v.orderid); const filteredOrders = ordersBook.filter( (v) => orderIds.includes(v.orderid) ); if (filteredOrders == null || filteredOrders.length <= 0) { this.logger?.w("specific orders not exist in order book", orderIds); return null; } const completedOrders = filteredOrders.filter( (v) => v.status === "complete" ); if (completedOrders.length <= 0) { this.logger?.c("all orders are not completed", orderIds); return null; } return completedOrders; } async fetchOptionGreeks(params) { const response = await this.optionGreek(params); return this.getData(response); } async fetchGainersLosers(params) { const response = await this.gainersLosers(params); return this.getData(response); } async fetchPutCallRatio() { const response = await this.putCallRatio(); return this.getData(response); } async fetchOIBuildup(params) { const response = await this.oIBuildup(params); return this.getData(response); } }; // src/smartapi/index.ts var defaultConfigs = { apiRetryDelayMs: 1e3, apiTimeoutMs: 4e3 }; function isValidString(s) { return typeof s === "string" && s.trim().length > 0; } function validateBrokerData(brokerData) { if (brokerData == null) { import_utils_core.logger.c("get logged smartapi failed. broker data empty"); return; } const { clientId, apiKey } = brokerData; if (!isValidString(clientId) || !isValidString(apiKey)) { import_utils_core.logger.c("get logged smartapi failed. clientId or apiKey empty"); return; } return brokerData; } function validateBrokerTokenData(brokerTokenData) { if (validateBrokerData(brokerTokenData) == null) return; const { jwtToken, refreshToken, feedToken } = brokerTokenData; if (!isValidString(jwtToken) || !isValidString(refreshToken) || !isValidString(feedToken)) { import_utils_core.logger.c( "get logged smartapi failed. jwtToken or refreshToken or feedToken empty" ); return; } return brokerTokenData; } async function getLoggedSmartapi(params) { const { brokerData, configs: { apiRetryDelayMs, apiTimeoutMs } = defaultConfigs } = params; if (validateBrokerTokenData(brokerData) == null) return; const { apiKey, clientId, jwtToken, refreshToken } = brokerData; const configs = { api_key: apiKey, client_code: clientId, access_token: jwtToken, timeout: apiTimeoutMs, refresh_token: refreshToken }; return new SmartApiEnhanced(configs, apiRetryDelayMs, import_utils_core.logger); } function getSmartapi(params) { const { brokerData, configs: { apiRetryDelayMs, apiTimeoutMs } = defaultConfigs } = params; if (validateBrokerData(brokerData) == null) return; const { apiKey, clientId } = brokerData; const configs = { api_key: apiKey, client_code: clientId, timeout: apiTimeoutMs }; return new SmartApiEnhanced(configs, apiRetryDelayMs, import_utils_core.logger); } async function getLoggedWebsocket(brokerData) { if (validateBrokerTokenData(brokerData) == null) return; const { apiKey, clientId, jwtToken, feedToken } = brokerData; const webSocket = new import_smartapi_javascript2.WebSocketV2({ apikey: apiKey, clientcode: clientId, jwttoken: jwtToken, feedtype: feedToken }); return webSocket; } // src/smartapi_utils.ts var import_date_fns2 = require("date-fns"); var import_utils_core2 = require("@thoshpathi/utils-core"); var import_utils_core3 = require("@thoshpathi/utils-core"); async function getTokensLtpMap(exchangeTokens, smartapi) { const ltpDataArray = await smartapi.fetchTokensLtp(exchangeTokens); const ltpDataMap = (0, import_utils_core3.groupByLatestMap)(ltpDataArray, "tradingSymbol"); import_utils_core3.logger.i("fetching ltp from smartapi", ltpDataMap.size); return ltpDataMap; } async function getManyScripLtpMap(params, instrumentDumps, cacheObject) { const { symbols, smartapi } = params; const scripLtpMap = /* @__PURE__ */ new Map(); if (!symbols.length || !instrumentDumps.length) return scripLtpMap; const exchangeTokens = (0, import_utils_core3.groupByKeyObject)( instrumentDumps, "exchange", "symbolToken" ); const ltpDataMap = await getTokensLtpMap(exchangeTokens, smartapi); const createdAt = (0, import_utils_core2.toDbDatetimeString)(); instrumentDumps.forEach((dumpScrip) => { const ltpData = ltpDataMap.get(dumpScrip.symbol); if (ltpData == null) return; const { name, symbol, symbolToken, exchange } = dumpScrip; scripLtpMap.set(symbol, { id: 0n, name, symbol, symbolToken, exchange, ltp: ltpData.ltp, createdAt }); }); cacheObject?.set(scripLtpMap); return scripLtpMap; } async function getManyScripLtpMapCache(params, instrumentDumps, cacheObject) { let scripLtpMap = cacheObject?.get(); if (scripLtpMap == null) return await getManyScripLtpMap(params, instrumentDumps, cacheObject); const someSymbolsExist = params.symbols.some( (symbol) => scripLtpMap?.has(symbol) ); if (!someSymbolsExist) scripLtpMap = await getManyScripLtpMap( params, instrumentDumps, cacheObject ); return scripLtpMap; } async function getIndexScripLtpMap(indexScrips, smartapi, cacheObject) { return await getManyScripLtpMap( { symbols: indexScrips.map((v) => v.symbol), smartapi }, indexScrips, cacheObject ); } async function getIndexHistoricalData(params, smartapi) { const candleDataArray = await smartapi.fetchCandleData(params); if (!Array.isArray(candleDataArray) || candleDataArray.length == 0) { import_utils_core3.logger.w( "candleDataArray is empty", JSON.stringify((0, import_utils_core2.convertDatesToStrings)(params)) ); return; } try { const lastCompletedCandle = getLastCompletedMarketCandle(params.interval); if (lastCompletedCandle != null) { const lastCandle = candleDataArray[candleDataArray.length - 1]; const lastCandleDate = lastCandle.date; if ((0, import_date_fns2.isAfter)(lastCandleDate, lastCompletedCandle)) { import_utils_core3.logger.d( "not completed candle present. remove it", (0, import_utils_core2.toDatetimeString)(lastCandle.date) ); candleDataArray.pop(); } } } catch (error) { import_utils_core3.logger.e("error in removing not completed candle", error); } return candleDataArray; } async function getIndexLatestHistoricalData(args, smartapi) { const days = candleFetchDayConfigs[args.interval]; const todate = (0, import_date_fns2.endOfToday)(); const fromdate = (0, import_date_fns2.startOfDay)((0, import_date_fns2.subDays)(todate, days)); const params = { ...args, fromdate, todate }; return await getIndexHistoricalData(params, smartapi); } // src/ta_lib.ts function round4Decimal(n) { return Math.round(n * 1e4) / 1e4; } function arrayAverage(numArray) { const sum = numArray.reduce((pv, cv) => pv + cv, 0); const avg = sum / numArray.length; return round4Decimal(avg); } function smaSeries(prices, period = 14) { if (prices.length < period) return []; else if (period === 1) return prices; const sma = []; for (let i = period; i <= prices.length; i++) { const slice = prices.slice(i - period, i); sma.push(arrayAverage(slice)); } return sma; } function emaSeries(prices, period = 9) { if (prices.length < period) return []; else if (period === 1) return prices; const k = 2 / (period + 1); let prevEMA = arrayAverage(prices.slice(0, period)); const ema = [prevEMA]; for (let i = period; i < prices.length; i++) { prevEMA = (prices[i] - prevEMA) * k + prevEMA; ema.push(prevEMA); } return ema; } function emaCrossoverTrends(ohlcsArray, shortLength = 9, longLength = 20) { if (shortLength > longLength) [shortLength, longLength] = [longLength, shortLength]; if (!ohlcsArray || ohlcsArray.length < longLength) return []; const prices = ohlcsArray.map((v) => v.close); const longEmaSeries = emaSeries(prices, longLength); const loopLength = longEmaSeries.length; const shortEmaSeries = emaSeries(prices, shortLength).slice(-loopLength); ohlcsArray = ohlcsArray.slice(-loopLength); const signals = []; for (let i = 0; i < loopLength; i++) { const prevShort = shortEmaSeries[i - 1]; const prevLong = longEmaSeries[i - 1]; const currentShort = shortEmaSeries[i]; const currentLong = longEmaSeries[i]; const ohlc = ohlcsArray[i]; if (prevShort < prevLong && currentShort > currentLong) { signals.push({ date: ohlc.date, trend: "BUY", close: ohlc.close }); } else if (prevShort > prevLong && currentShort < currentLong) { signals.push({ date: ohlc.date, trend: "SELL", close: ohlc.close }); } } return signals; } function trSeries(ohlcArr, includeFirst = true) { if (ohlcArr.length === 0) return []; const trArr = []; if (includeFirst) { const { high, low } = ohlcArr[0]; trArr.push(high - low); } for (let i = 1; i < ohlcArr.length; i++) { const { high, low } = ohlcArr[i]; const { close: prevClose } = ohlcArr[i - 1]; trArr.push( Math.max( high - low, Math.abs(high - prevClose), Math.abs(low - prevClose) ) ); } return trArr; } function rmaSeries(prices, period = 15) { if (prices.length < period) return []; else if (period === 1) return prices; const k = 1 / period; let prevRMA = arrayAverage(prices.slice(0, period)); const rma = [prevRMA]; for (let i = period; i < prices.length; i++) { prevRMA = (prices[i] - prevRMA) * k + prevRMA; rma.push(prevRMA); } return rma; } function atrSeries(ohlcArr, period) { if (ohlcArr.length < period) return []; const trArr = trSeries(ohlcArr, true); return period === 1 ? trArr : rmaSeries(trArr, period); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { OHLCData, SmartApiEnhanced, SmartApiError, atrSeries, calculatePL, candleFetchDayConfigs, candleMaxDayConfigs, emaCrossoverTrends, emaSeries, getIndexHistoricalData, getIndexLatestHistoricalData, getIndexScripLtpMap, getLastCompletedMarketCandle, getLoggedSmartapi, getLoggedWebsocket, getManyScripLtpMap, getManyScripLtpMapCache, getSmartapi, intervalMinuteConfigs, isProfitReached, isStoplossReached, rmaSeries, smaSeries, trSeries });