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