UNPKG

@suyotech-dev/smartapi-js

Version:

Unofficial sdk for smartapi angelone developed by suyotech.com

514 lines (458 loc) 13.7 kB
import axios from "axios"; import os from "os"; import totp from "totp-generator"; //Constants export class SmartApi { constructor(clientID, mpin, apiKey, totpKey) { this.clientID = clientID; this.mpin = mpin; this.apiKey = apiKey; this.timeout = 5000; this.totpKey = totpKey; this.publicIP = null; this.macAddress = null; this.localIP = null; this.jwtToken = ""; this.requestToken = ""; this.feedToken = ""; this.httpClient = null; this.debug = false; this.InitPromise = this.init(); } /** * @enum {String} */ Exchange = { BSE: "BSE", NSE: "NSE", NFO: "NFO", MCX: "MCX", CDS: "CDS", }; /** * @enum {String} */ OrderDuration = { DAY: "DAY", IOC: "IOC", }; /** * @enum {String} */ // @ts-ignore ProductType = { DELIVERY: "DELIVERY", CARRYFORWARD: "CARRYFORWARD", MARGIN: "MARGIN", INTRADAY: "INTRADAY", BO: "BO", }; /** * @enum {String} */ OrderType = { MARKET: "MARKET", LIMIT: "LIMIT", STOPLOSS_LIMIT: "STOPLOSS_LIMIT", STOPLOSS_MARKET: "STOPLOSS_MARKET", }; /** * @enum {String} */ TransactionType = { BUY: "BUY", SELL: "SELL", SHORT: "SELL", COVER: "BUY", }; /** * @enum {String} */ Variety = { NORMAL: "NORMAL", STOPLOSS: "STOPLOSS", AMO: "AMO", ROBO: "ROBO", }; /** * @enum {String} */ Timeframe = { ONE_MINUTE: "ONE_MINUTE", THREE_MINUTE: "THREE_MINUTE", FIVE_MINUTE: "FIVE_MINUTE", TEN_MINUTE: "TEN_MINUTE", FIFTEEN_MINUTE: "FIFTEEN_MINUTE", THIRTY_MINUTE: "THIRTY_MINUTE", ONE_HOUR: "ONE_HOUR", ONE_DAY: "ONE_DAY", }; async init() { try { this.localIP = getLocalIP(); this.macAddress = getMAC(); const publicIP = await getPublicIP(); this.publicIP = publicIP; this.httpClient = axios.create({ baseURL: routes.burl, timeout: this.timeout, headers: { "Content-Type": "application/json", Accept: "application/json", "X-User-Type": "USER", "X-SourceID": "WEB", "X-ClientLocalIP": this.localIP, "X-ClientPublicIP": this.publicIP, "X-MACAddress": this.macAddress, "X-PrivateKey": this.apiKey, Authorization: `Bearer ${this.jwtToken}`, }, }); this.httpClient.interceptors.response.use( (response) => { if (!response.data.status) { throw new Error(`error ${response.data.message}`); } return response.data.data; }, (error) => { // Timeout error if (error.code === "ECONNABORTED") { throw new Error("request error timeout: The server took too long to respond."); } // Network error (no response at all) if (error.message && error.message.includes("Network Error")) { throw new Error("request error network: Please check your internet connection."); } // Axios couldn't get a response (e.g., DNS issues, server unreachable) if (error.request) { const errMessage = error.message || "unknown error"; throw new Error(`request error ${errMessage}`); } // Server responded with an error status (4xx, 5xx) if (error.response) { const errMessage = error.response.data?.message || "unknown error"; throw new Error(`request error ${errMessage}`); } // Fallback for other unexpected errors throw new Error(`error ${this.debug ? error : error.message}`); } ); this.httpClient.interceptors.request.use( (config) => { return config; }, (error) => { console.log("request error ", error); return Promise.reject(error); } ); } catch (error) { console.log("init error", error); } } async InitDone() { return this.InitPromise; } /** * * @param {true | false} debug */ setDebug(debug = false) { this.debug = debug; } async generateSession() { try { await this.InitDone(); const data = await this.httpClient.post(routes.loginUrl, { clientcode: this.clientID, password: this.mpin, totp: totp(this.totpKey), }); // @ts-ignore const { jwtToken, refreshToken, feedToken } = data; if (data) { this.jwtToken = jwtToken; this.requestToken = refreshToken; this.feedToken = feedToken; this.httpClient.defaults.headers.Authorization = `Bearer ${this.jwtToken}`; } } catch (error) { throw new Error(error.message); } } async getUserProfile() { try { await this.InitDone(); return await this.httpClient.get(routes.profileUrl); } catch (error) { throw new Error(error.message); } } /** * * @param {orderParams} params * @returns {Promise<{script : String,orderid : String,uniqueorderid : String} | undefined>} */ async placeOrder(params) { try { return await this.httpClient.post(routes.placeOrderUrl, params); } catch (error) { throw new Error(error.message); } } /** * * @param {modifyOrderParams} params * @returns */ async modifyOrder(params) { try { return await this.httpClient.post(routes.modifyOrderUrl, params); } catch (error) { throw new Error(error.message); } } /** * Cancel Open Order in OrderBook * @typedef {Object} cancelOrderParams * @property {Variety} variety Normal Order (Regular),Stop loss order,After Market Order,ROBO (Bracket Order) * @property {String} orderid OrderID of Open Order Present in Orderbook */ /** * @method * @param {cancelOrderParams} params * @returns {Promise<any|undefined>} */ async cancelOrder(params) { try { const data = await this.httpClient.post(routes.cancelOrderUrl, params); return data?.data; } catch (error) { throw new Error(error.message); } } /** * @method * @async * @returns {Promise<Array|undefined>} of ordebook */ async getOrderBook() { try { const data = await this.httpClient.get(routes.orderBookUrl); return data; } catch (error) { throw new Error(error.message); } } async getTradeBook() { try { const data = await this.httpClient.get(routes.tradeBookUrl); return data; } catch (error) { throw new Error(error.message); } } async getPositionBook() { const data = await this.httpClient.get(routes.positionBookUrl); return data; } async getHoldingAll() { try { const data = await this.httpClient.get(routes.holdingAllBookUrl); return data; } catch (error) { throw new Error(error.message); } } /** * * @param {"LTP"} mode * @param {import("./SmartapiInstruments.js").Instrument[]} instrumentlist * @returns */ async getMarketData(mode = "LTP", instrumentlist = []) { try { if (!Array.isArray(instrumentlist) || !mode) { console.log("instrument list null or mode is not defined"); return null; } const params = ArrayInstToMarketDataParams(mode, instrumentlist); const data = await this.httpClient.post(routes.marketDataUrl, params); return data; } catch (error) { throw new Error(error.message); } } /** * * @param {candleDataParams} params * @returns {Promise<Candle[] | undefined>} */ async getCandleData(params) { try { if (!params.exchange || !params.symboltoken || !params.interval || !params.fromdate || !params.todate) { throw new Error("some params missing"); } const data = await this.httpClient.post(routes.candleDataUrl, params); return data; } catch (error) { throw new Error(error.message); } } } //utils functions const routes = { burl: "https://apiconnect.angelone.in", loginUrl: "/rest/auth/angelbroking/user/v1/loginByPassword", profileUrl: "/rest/secure/angelbroking/user/v1/getProfile", placeOrderUrl: "/rest/secure/angelbroking/order/v1/placeOrder", modifyOrderUrl: "/rest/secure/angelbroking/order/v1/modifyOrder", cancelOrderUrl: "/rest/secure/angelbroking/order/v1/cancelOrder", orderBookUrl: "/rest/secure/angelbroking/order/v1/getOrderBook", positionBookUrl: "/rest/secure/angelbroking/order/v1/getPosition", holdingAllBookUrl: "/rest/secure/angelbroking/portfolio/v1/getAllHolding", tradeBookUrl: "/rest/secure/angelbroking/order/v1/getTradeBook", marketDataUrl: "/rest/secure/angelbroking/market/v1/quote/", candleDataUrl: "/rest/secure/angelbroking/historical/v1/getCandleData", }; function getLocalIP() { const interfaces = os.networkInterfaces(); let localIP = ""; // Loop through network interfaces Object.keys(interfaces).forEach((interfaceName) => { const interfaceInfo = interfaces[interfaceName]; // Find an IPv4 address that is not internal or loopback const activeInterface = interfaceInfo.find((info) => !info.internal && info.family === "IPv4"); if (activeInterface) { localIP = activeInterface.address; } }); return localIP; } // Function to get the MAC address function getMAC() { const networkInterfaces = os.networkInterfaces(); let macAddress = ""; // Loop through network interfaces to find the MAC address Object.keys(networkInterfaces).forEach((interfaceName) => { const interfaceInfo = networkInterfaces[interfaceName]; const mac = interfaceInfo.find((details) => details.mac && details.mac !== "00:00:00:00:00:00"); if (mac) { macAddress = mac.mac; } }); return macAddress; } const getPublicIP = async () => { try { const response = await axios.get("https://ipv4.icanhazip.com/"); const ipAddress = response.data.trim(); return ipAddress; } catch (error) { console.error("Error fetching IP address:", error.message); } }; /** * * @param {String} mode LTP,OHLC,FULL * @param {Array} instruments Array of Instruments * @returns MarketData Params */ const ArrayInstToMarketDataParams = (mode, instruments = []) => { if (instruments.length == 0) { console.log("no instruments found"); return null; } let params = { mode, exchangeTokens: { NSE: [], NFO: [], MCX: [], CDS: [], BSE: [], BFO: [], }, }; instruments.forEach((inst) => { switch (inst.exch_seg) { case "NSE": params.exchangeTokens.NSE.push(inst.token); break; case "NFO": params.exchangeTokens.NFO.push(inst.token); break; case "MCX": params.exchangeTokens.MCX.push(inst.token); break; case "CDS": params.exchangeTokens.CDS.push(inst.token); break; case "BSE": params.exchangeTokens.BSE.push(inst.token); break; case "BFO": params.exchangeTokens.BFO.push(inst.token); break; default: break; } }); //Cleaning empty exchange token keys Object.keys(params.exchangeTokens).forEach((key) => { if (params.exchangeTokens[key].length == 0) { delete params.exchangeTokens[key]; } }); return params; }; /** * @typedef {Object} orderParams * @property {string} variety - Order variety (NORMAL,ROBO,STOPLOSS) * @property {string} tradingsymbol - Trading Symbol of the instrument. * @property {string} symboltoken - Symbol Token is a unique identifier. * @property {string} exchange - Name of the exchange. * @property {string} transactiontype - BUY or SELL. * @property {string} ordertype - Order type (e.g., MARKET, LIMIT). * @property {number} quantity - Quantity to transact. * @property {string} producttype - Product type (e.g., CNC, MIS). * @property {number} [price] - (Optional) The min or max price to execute the order at (for LIMIT orders). * @property {number} [triggerprice] - (Optional) The price at which an order should be triggered (for SL, SL-M). * @property {number} [squareoff] - (Optional) Only for ROBO (Bracket Order). * @property {number} [stoploss] - (Optional) Only for ROBO (Bracket Order). * @property {number} [trailingStopLoss] - (Optional) Only for ROBO (Bracket Order). * @property {number} [disclosedquantity] - (Optional) Quantity to disclose publicly (for equity trades). * @property {string} [duration] - (Optional) Order duration (e.g., DAY, IOC). * @property {string} [ordertag] - (Optional) Tag to identify the order (max 20 characters). */ /** * Modify Order as long as it is open. * @typedef {Object} modifyOrderParams params required for fetching candle data * @property {String} variety Normal Order (Regular),Stop loss order,After Market Order,ROBO (Bracket Order) * @property {String} tradingsymbol Tradingsymbol of instrument * @property {String} symboltoken Token of instrument * @property {String} exchange Exchange of instrument * @property {String} orderid OrderID of Open Order Present in Orderbook * @property {String} producttype Product type (CNC,MIS, NORMAL) * @property {String} duration Order duration (DAY,IOC) * @property {String} price The min or max price to execute the order at (for LIMIT orders) * @property {String} quantity Quantity in Integer. */ /** * @typedef {Object} Candle * @property {String} time * @property {Number} open * @property {Number} high * @property {Number} low * @property {Number} close * @property {Number} volume */ /** * @typedef {Object} candleDataParams params required for fetching candle data * @property {String} exchange Exchange of instrument * @property {String} symboltoken Token of instrument * @property {String} interval Timeframe of CandleData use timeframe constant Etc.. * @property {String} fromdate From Date like 2023-11-16 09:15 * @property {String} todate To Date like 2023-11-16 09:16 */