UNPKG

@thoshpathi/utils-smartapi-order

Version:

Utility functions for placing live and dummy orders using Angel One's SmartAPI, with helper methods for streamlined trading workflows.

750 lines (740 loc) 25.1 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, { DummyOrderHelper: () => DummyOrderHelper, LiveOrderHelper: () => LiveOrderHelper, OrderHelper: () => OrderHelper, createOrderHelper: () => createOrderHelper, delay: () => delay, findNearestPremiumData: () => findNearestPremiumData, generateNakedTrendOptionSymbols: () => generateNakedTrendOptionSymbols, generateOptionLabels: () => generateOptionLabels, generateOrderId: () => generateOrderId, getNakedConfig: () => getNakedConfig, getNakedScripData: () => getNakedScripData, getOptionTypeOfSpread: () => getOptionTypeOfSpread, getSpreadConfig: () => getSpreadConfig, getSpreadScripData: () => getSpreadScripData, toggleTrend: () => toggleTrend }); module.exports = __toCommonJS(index_exports); // src/config_utils.ts var spreadConfig = { BUY_call_spread: ["BUY", "SELL"], BUY_put_spread: ["SELL", "BUY"], SELL_call_spread: ["SELL", "BUY"], SELL_put_spread: ["BUY", "SELL"] }; function getSpreadConfig(params) { const { trend, spreadType } = params; return spreadConfig[`${trend}_${spreadType}`]; } var nakedConfig = { BUY_BUY: ["BUY", "CE"], BUY_SELL: ["BUY", "PE"], SELL_BUY: ["SELL", "PE"], SELL_SELL: ["SELL", "CE"] }; function getNakedConfig(params) { const { trend, transaction } = params; return nakedConfig[`${transaction}_${trend}`]; } var spreadOptionConfig = { call_spread: "CE", put_spread: "PE" }; function getOptionTypeOfSpread(spreadType) { return spreadOptionConfig[spreadType]; } // src/helpers/order_helper.ts var import_utils_core3 = require("@thoshpathi/utils-core"); var import_utils_smartapi = require("@thoshpathi/utils-smartapi"); // src/utils.ts var import_utils_core = require("@thoshpathi/utils-core"); function generateOrderId(length = 20, prefix = "O_") { const id = (0, import_utils_core.generateUniqueString)(length); return `${prefix}_${id}`.substring(0, length); } function toggleTrend(trend) { if (trend !== "BUY" && trend !== "SELL") throw new Error("Invalid trend value"); return trend === "BUY" ? "SELL" : "BUY"; } function delay(ms = 0) { return new Promise((resolve) => setTimeout(resolve, ms)); } // src/option_scrip_utils.ts var import_utils_core2 = require("@thoshpathi/utils-core"); function getSpreadScripData(params) { const { strike, trend, spreadType, contractPrefix, leg1Gap, leg2Gap } = params; const spreadScripMap = /* @__PURE__ */ new Map(); const [leg1Trend, leg2Trend] = getSpreadConfig({ trend, spreadType }); if (leg1Trend && leg2Trend) { const optionType = getOptionTypeOfSpread(spreadType); const symbol1 = getOptionScripOfLabel( contractPrefix, strike, leg1Gap, optionType ); const symbol2 = getOptionScripOfLabel( contractPrefix, strike, leg2Gap, optionType ); spreadScripMap.set(symbol1, { symbol: symbol1, trend: leg1Trend }).set(symbol2, { symbol: symbol2, trend: leg2Trend }); } return spreadScripMap; } function getNakedScripData(params) { const { strike, trend, transaction, contractPrefix, legGap } = params; const nakedScripMap = /* @__PURE__ */ new Map(); const tradeConfig = getNakedConfig({ transaction, trend }); if (Array.isArray(tradeConfig)) { const symbol = getOptionScripOfLabel( contractPrefix, strike, legGap, tradeConfig[1] ); nakedScripMap.set(symbol, { symbol, trend: tradeConfig[0] }); } return nakedScripMap; } function generateNakedTrendOptionSymbols(params) { const { strike: atmStrike, trend, transaction, contractPrefix, roundTo = 100, length = 8 } = params; const tradeConfig = getNakedConfig({ transaction, trend }); const scripSymbols = (0, import_utils_core2.range)(-length, length + 1, 1).map((n) => { const strike = atmStrike + n * roundTo; return getOptionSymbol(contractPrefix, strike, tradeConfig[1]); }); return { trend: tradeConfig[0], symbols: scripSymbols }; } function findNearestPremiumData(scripLtpMap, premium) { const scripLtpArr = Array.from(scripLtpMap.values()); return scripLtpArr.reduce((nearest, current) => { const currentDiff = Math.abs(current.ltp - premium); const nearestDiff = Math.abs(nearest.ltp - premium); return currentDiff < nearestDiff ? current : nearest; }, scripLtpArr[0]); } function generateOptionLabels(gap = 10, step = 100) { const numbers = []; for (let n = 1; n <= gap; n++) numbers.push(n * step); const otmLabels = numbers.map((n) => `${n}-OTM`); const itmLabels = numbers.reverse().map((n) => `${n}-ITM`); return [...itmLabels, "ATM", ...otmLabels]; } function getOptionScripOfLabel(prefix, strike, optionLabel, optionType) { if (optionLabel === "ATM") return `${prefix}${strike}${optionType}`; const match = optionLabel.match(/^(\d+)-(ITM|OTM)$/); if (!match) throw new Error(`Invalid optionLabel format: ${optionLabel}`); const offset = parseInt(match[1], 10); const labelType = match[2]; const isCall = optionType === "CE"; const isItm = labelType === "ITM"; const isOtm = labelType === "OTM"; if (isCall) { strike += isOtm ? offset : -offset; } else { strike += isOtm ? -offset : offset; } return getOptionSymbol(prefix, strike, optionType); } function getOptionSymbol(prefix, strike, optionType) { return `${prefix}${strike}${optionType}`.toUpperCase(); } // src/helpers/order_helper.ts var OrderHelper = class { constructor(params) { this.smartapi = params.smartapi; this.logger = params.logger; this.instrumentDumpRepo = params.instrumentDumpRepo; this.indexScripRepo = params.indexScripRepo; this.orderRepo = params.orderRepo; this.orderVerfiyWaitMs = params.orderVerfiyWaitMs ?? 1e3; this.strikeRoundBy = params.strikeRoundBy ?? 100; this.minimumPremium = params.minimumPremium ?? 10; } async getScripLtpMap(symbols, instrumentDumps) { return await (0, import_utils_smartapi.getManyScripLtpMap)( { symbols, smartapi: this.smartapi }, instrumentDumps ); } async getOrdersLtpMap(orders) { const symbols = orders.map((v) => v.symbol); const instrumentDumps = orders.map((v) => { return { lotSize: 0, name: v.name, symbol: v.symbol, symbolToken: v.symbolToken, exchange: v.exchange, id: 0n, createdAt: "" }; }); return await (0, import_utils_smartapi.getManyScripLtpMap)( { symbols, smartapi: this.smartapi }, instrumentDumps ); } generateOrderId() { return generateOrderId(); } getStrike(ltp) { const strikeRoundBy = this.strikeRoundBy; const strike = Math.round(ltp / strikeRoundBy) * strikeRoundBy; return strike; } async getNakedFixedPremiumScrips(indexTrendData) { const { indexScrip: { strategy, contractPrefix, premium }, ltp, trend } = indexTrendData; const strike = this.getStrike(ltp); const spreadScripMap = /* @__PURE__ */ new Map(); const trendSymbols = generateNakedTrendOptionSymbols({ strike, trend, transaction: strategy, contractPrefix }); const instrumentDumps = await this.instrumentDumpRepo.getMany( trendSymbols.symbols ); const scripLtpMap = await this.getScripLtpMap( trendSymbols.symbols, instrumentDumps ); const scripLtp = findNearestPremiumData(scripLtpMap, premium); if (scripLtp != null) { const { symbol, ltp: ltp2 } = scripLtp; spreadScripMap.set(symbol, { symbol, trend: trendSymbols.trend, ltp: ltp2 }); } return spreadScripMap; } async getStrategyScripMap(indexTrendData) { const { indexScrip, ltp, trend } = indexTrendData; const { strategy, leg1Gap, contractPrefix } = indexScrip; const strike = this.getStrike(ltp); if (strategy === "SPREAD") { const { leg2Gap, contractPrefix: contractPrefix2, bullishSpread, bearishSpread } = indexScrip; const spreadType = trend === "BUY" ? bullishSpread : bearishSpread; return getSpreadScripData({ strike, trend, spreadType, contractPrefix: contractPrefix2, leg1Gap: leg1Gap.trim(), leg2Gap: leg2Gap.trim() }); } else { const premium = indexScrip.premium; if (premium != null && !isNaN(premium) && premium > this.minimumPremium) { return await this.getNakedFixedPremiumScrips(indexTrendData); } return getNakedScripData({ strike, trend, transaction: strategy, contractPrefix, legGap: leg1Gap.trim() }); } } async getTrendNewOrders({ candleInterval, scripTrendMap }) { const newOrders = []; for (const trendMap of scripTrendMap.values()) { const { indexScrip } = trendMap; const spreadScripMap = await this.getStrategyScripMap(trendMap); if (spreadScripMap.size == 0) { this.logger.w(indexScrip.name, "spreadScripMap empty."); continue; } const symbols = Array.from(spreadScripMap.keys()); const instrumentDumps = await this.instrumentDumpRepo.getMany(symbols); instrumentDumps.forEach((insDump) => { const { symbol, symbolToken, name, exchange, lotSize } = insDump; const trendData = spreadScripMap.get(symbol); if (trendData != null) newOrders.push({ candleInterval, name, symbol, symbolToken, exchange, transaction: trendData.trend, qty: lotSize * indexScrip.noOfLots, entryOrderId: generateOrderId(), entryPrice: trendData.ltp ?? 0, entryAt: (0, import_utils_core3.toDbDatetimeString)(), isClosed: false, status: "open" }); }); } if (newOrders.length == 0) { this.logger.w(candleInterval, "newOrders empty. no new orders"); return; } return newOrders; } async updateEntryPriceOfOrders(newOrders) { const scripLtpMap = await this.getOrdersLtpMap(newOrders); newOrders.forEach((order) => { const ltpData = scripLtpMap.get(order.symbol); if (ltpData != null) order.entryPrice = ltpData.ltp; }); return newOrders; } async getOpenOrders(params) { const openOrders = await this.orderRepo.getOpenOrders(params); if (openOrders.length == 0) { this.logger.i("no open orders exist", params.candleInterval); return; } return openOrders; } async getOrderCloseTrackDatas() { this.logger.d("fetch today open orders"); const openOrders = await this.getOpenOrders({}); if (openOrders == null) return; this.logger.d("fetch active index scrips"); const indexScrips = await this.indexScripRepo.getAll({ activeOnly: true }); if (indexScrips.length == 0) { this.logger.w("indexScrips empty. stop here"); return; } const allPropsDisabled = indexScrips.every((idx) => { return ( // idx.exitPremium == 0 && idx.profitPoints == 0 && idx.stoplossPoints == 0 ); }); if (allPropsDisabled) { this.logger.w("all index scrip props are disabled. stop here"); return; } const scripLtpMap = await this.getOrdersLtpMap(openOrders); const openOrderMap = (0, import_utils_core3.groupByMap)(openOrders, "name"); const orderCloseTracks = []; for (const indexScrip of indexScrips) { const { name, strategy, profitPoints, stoplossPoints } = indexScrip; if (!profitPoints && !stoplossPoints) continue; const orders = openOrderMap.get(name); if (!orders?.length) continue; if (strategy === "SPREAD") { const pl = orders.reduce((acc, order) => { const { entryPrice: entry, qty, transaction: trend } = order; const ltp = scripLtpMap.get(order.symbol)?.ltp; if (ltp) acc += (0, import_utils_smartapi.calculatePL)({ entry, qty, trend, ltp }); return acc; }, 0); let status = void 0; if (profitPoints && pl > profitPoints) { status = "target"; } else if (stoplossPoints && Math.abs(pl) > stoplossPoints) { status = "stoploss"; } if (status != null) { for (const order of orders) { const ltp = scripLtpMap.get(order.symbol).ltp; orderCloseTracks.push({ order, ltp, status }); } } } else { for (const order of orders) { const ltp = scripLtpMap.get(order.symbol)?.ltp; if (!ltp) continue; const { entryPrice: entry, transaction: trend, qty } = order; let status = void 0; const pl = (0, import_utils_smartapi.calculatePL)({ entry, trend, qty, ltp }); if (profitPoints && pl > profitPoints) { status = "target"; } else if (stoplossPoints && Math.abs(pl) > stoplossPoints) { status = "stoploss"; } if (status != null) { orderCloseTracks.push({ order, ltp, status }); } } } } if (orderCloseTracks.length == 0) { this.logger.w("orderCloseTracks empty. No orders to close"); return; } this.logger.i( "Close orders available", orderCloseTracks.map(({ order }) => `[${order.id}]: ${order.symbol}`) ); return orderCloseTracks; } async createOrders(newOrders, candleInterval) { try { this.logger.i( candleInterval, "create new orders", newOrders.map((v) => v.symbol) ); const result = await this.orderRepo.createOrders(newOrders); this.logger.i(candleInterval, "create new orders complete", result.info); return result; } catch (error) { this.logger.c("create new orders error", error); return; } } async updateOrdersClose(closeOrders) { try { const results = await this.orderRepo.closeOpenOrders(closeOrders); this.logger.i( "close open orders complete", closeOrders.map((v) => v.symbol), results.length ); return results; } catch (error) { this.logger.e("close open orders error", error); return; } } }; // src/helpers/dummy_order_helper.ts var DummyOrderHelper = class extends OrderHelper { async handleScripsTrendReversal(scripTrendMapData) { this.logger.d("run close open orders"); await this.closeTrendOpenOrders(scripTrendMapData); this.logger.d("run place new orders"); await this.placeNewTrendOrders(scripTrendMapData); } async handleDaySquareoff() { const openOrders = await this.getOpenOrders({}); if (openOrders == null) return; return await this.processOrdersSquareoff(openOrders, "squareoff"); } async processOrdersSquareoff(openOrders, status) { const scripLtpMap = await this.getOrdersLtpMap(openOrders); const squareoffOrders = await this.generateCloseOrderParams( openOrders, status, scripLtpMap ); if (squareoffOrders == null) return false; await this.updateOrdersClose(squareoffOrders); return true; } async handleOpenOrdersTracking() { const orderCloseTracks = await this.getOrderCloseTrackDatas(); if (orderCloseTracks == null) return; const closeOrders = orderCloseTracks.map( ({ order, status, ltp }) => { return { id: order.id, symbol: order.symbol, candleInterval: order.candleInterval, exitOrderid: this.generateOrderId(), exitPrice: ltp, status }; } ); await this.updateOrdersClose(closeOrders); } async closeTrendOpenOrders({ scripTrendMap }) { const names = Array.from(scripTrendMap.values()).map( (v) => v.indexScrip.name ); const openOrders = await this.getOpenOrders({ names }); if (openOrders == null) return; const scripLtpMap = await this.getOrdersLtpMap(openOrders); const closeOrders = await this.generateCloseOrderParams( openOrders, "closed", scripLtpMap ); if (closeOrders == null) return false; await this.updateOrdersClose(closeOrders); return true; } async placeNewTrendOrders(scripTrendMapData) { let newOrders = await this.getTrendNewOrders(scripTrendMapData); if (newOrders == null) return; newOrders = await this.updateEntryPriceOfOrders(newOrders); await this.createOrders(newOrders, scripTrendMapData.candleInterval); } async generateCloseOrderParams(openOrders, status, scripLtpMap) { const closeOrders = []; for (const { id, symbol, candleInterval } of openOrders) { const exitPrice = scripLtpMap.get(symbol)?.ltp; if (!exitPrice) continue; closeOrders.push({ id, symbol, candleInterval, exitOrderid: this.generateOrderId(), exitPrice, status }); } if (closeOrders.length == 0) { this.logger.w(`closeOrders empty. no ${status} orders`); return; } return closeOrders; } }; // src/helpers/live_order_helper.ts var import_utils_core4 = require("@thoshpathi/utils-core"); var LiveOrderHelper = class extends OrderHelper { async handleScripsTrendReversal(scripTrendMapData) { this.logger.d("run close open orders"); await this.closeTrendOpenOrders(scripTrendMapData); this.logger.d("run place new orders"); await this.placeNewTrendOrders(scripTrendMapData); } async handleDaySquareoff() { const openOrders = await this.getOpenOrders({}); if (openOrders == null) return; return await this.processOrdersSquareoff(openOrders, "squareoff"); } async processOrdersSquareoff(openOrders, status) { const squareoffOrders = await this.generateCloseOrderParams( openOrders, status ); if (squareoffOrders == null) return false; await this.updateOrdersClose(squareoffOrders); return true; } async handleOpenOrdersTracking() { const orderCloseTracks = await this.getOrderCloseTrackDatas(); if (orderCloseTracks == null) return; const completedOrderMap = await this.getCompletedOrderMap({ orders: orderCloseTracks.map((v) => v.order), isOrderClosing: true }); if (completedOrderMap == null) return; const closeOrders = []; for (const { order, status, ltp } of orderCloseTracks) { const { id, symbol, candleInterval } = order; const orderData = completedOrderMap.get(symbol); if (orderData == null) continue; closeOrders.push({ id, symbol, candleInterval, exitOrderid: orderData.orderid, exitPrice: orderData.price ?? orderData.averageprice ?? ltp, status }); } if (closeOrders.length == 0) { this.logger.w("closeOrders empty. no close track orders"); return; } await this.updateOrdersClose(closeOrders); } async closeTrendOpenOrders({ scripTrendMap }) { const names = Array.from(scripTrendMap.values()).map( (v) => v.indexScrip.name ); const openOrders = await this.getOpenOrders({ names }); if (openOrders == null) return; const closeOrders = await this.generateCloseOrderParams( openOrders, "closed" ); if (closeOrders == null) return false; await this.updateOrdersClose(closeOrders); return true; } async placeNewTrendOrders(scripTrendMapData) { const newOrders = await this.getTrendNewOrders(scripTrendMapData); if (newOrders == null) return; const createNewOrders = await this.generateCreateNewOrders(newOrders); if (createNewOrders == null) return; await this.createOrders(createNewOrders, scripTrendMapData.candleInterval); } async generateCloseOrderParams(openOrders, status) { const completedOrderMap = await this.getCompletedOrderMap({ orders: openOrders, isOrderClosing: true }); if (completedOrderMap == null) return; const closeOrders = []; for (const { id, symbol, candleInterval } of openOrders) { const orderData = completedOrderMap.get(symbol); if (orderData == null) continue; const exitPrice = orderData.price ?? orderData.averageprice; if (!exitPrice) { this.logger.w( symbol, `[${orderData.orderid}]: price / avg.price empty` ); continue; } closeOrders.push({ id, symbol, candleInterval, exitOrderid: orderData.orderid, exitPrice, status }); } if (closeOrders.length == 0) { this.logger.w(`closeOrders empty. no ${status} orders`); return; } return closeOrders; } async generateCreateNewOrders(newOrders) { const completedOrderMap = await this.getCompletedOrderMap({ orders: newOrders, isOrderClosing: false }); if (completedOrderMap == null) return; const createNewOrders = []; for (const order of newOrders) { const completedOrder = completedOrderMap.get(order.symbol); if (completedOrder == null) continue; const { orderid, price, averageprice } = completedOrder; order.entryOrderId = orderid; order.entryPrice = price ?? averageprice ?? order.entryPrice; createNewOrders.push(order); } if (createNewOrders.length == 0) { this.logger.w("createNewOrders empty. no create orders"); return; } return createNewOrders; } /** * Returns data of Map(symbol, OrderDetails) | undefined */ async getCompletedOrderMap(params) { const { orders, isOrderClosing = false } = params; const orderParams = orders.map((order) => { const transactionType = isOrderClosing ? toggleTrend(order.transaction) : order.transaction; return this.liveOrderParams({ symbol: order.symbol, symbolToken: order.symbolToken, exchange: order.exchange, qty: order.qty, transactionType }); }); const completedOrders = await this.placeLiveOrders(orderParams); if (completedOrders == null) return; return (0, import_utils_core4.groupByLatestMap)(completedOrders, "tradingsymbol"); } async placeLiveOrders(orderParams) { try { const buyPromises = orderParams.filter((v) => v.transactiontype === "BUY").map((params) => { return this.smartapi.placeOrder_e(params); }); const sellPromises = orderParams.filter((v) => v.transactiontype === "SELL").map((params) => { return this.smartapi.placeOrder_e(params); }); const responses = []; if (buyPromises.length > 0) { const buyResponses = await Promise.all(buyPromises); responses.push(...buyResponses); await delay(200); } if (sellPromises.length > 0) { const sellResponses = await Promise.all(sellPromises); responses.push(...sellResponses); } await delay(this.orderVerfiyWaitMs); const completedOrders = await this.smartapi.fetchCompletedOrders( responses ); if (completedOrders == null || completedOrders.length == 0) { this.logger.e("completedOrders empty. place order failed"); return; } return completedOrders; } catch (error) { this.logger.c("place live orders error", error); } return; } liveOrderParams(params) { return { tradingsymbol: params.symbol, symboltoken: params.symbolToken, exchange: params.exchange, transactiontype: params.transactionType, quantity: params.qty, producttype: params.productType ?? "INTRADAY", duration: params.duration ?? "DAY", variety: "NORMAL", ordertype: "MARKET" }; } }; // src/helpers/create_order_helper.ts function createOrderHelper(params, algoMode = "dummy") { if (!params.smartapi) throw new Error("smartapi is required in live mode"); return algoMode === "live" ? new LiveOrderHelper(params) : new DummyOrderHelper(params); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { DummyOrderHelper, LiveOrderHelper, OrderHelper, createOrderHelper, delay, findNearestPremiumData, generateNakedTrendOptionSymbols, generateOptionLabels, generateOrderId, getNakedConfig, getNakedScripData, getOptionTypeOfSpread, getSpreadConfig, getSpreadScripData, toggleTrend });