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