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.

421 lines (414 loc) 14 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/helpers/order_helper.ts var order_helper_exports = {}; __export(order_helper_exports, { OrderHelper: () => OrderHelper, default: () => OrderHelper }); module.exports = __toCommonJS(order_helper_exports); 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); } // src/option_scrip_utils.ts var import_utils_core2 = require("@thoshpathi/utils-core"); // 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/option_scrip_utils.ts 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 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; } } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { OrderHelper });