"use strict";
var axios = require("axios");
var csvParse = require("papaparse");
var sha256 = require("crypto-js/sha256");
var querystring = require("querystring");
var utils = require("./utils");
/**
* @classdesc API client class. In production, you may initialise a single instance of this class per `api_key`.
* This module provides an easy to use abstraction over the HTTP APIs.
* The HTTP calls have been converted to methods and their JSON responses.
* See the **[Kite Connect API documentation](https://kite.trade/docs/connect/v1/)**
* for the complete list of APIs, supported parameters and values, and response formats.
*
* Getting started with API
* ------------------------
* ~~~~
*
* var KiteConnect = require("kiteconnect").KiteConnect;
*
* var kc = new KiteConnect({api_key: "your_api_key"});
*
* kc.generateSession("request_token", "api_secret")
* .then(function(response) {
* init();
* })
* .catch(function(err) {
* console.log(err);
* })
*
* function init() {
* // Fetch equity margins.
* // You can have other api calls here.
*
* kc.getMargins()
* .then(function(response) {
* // You got user's margin details.
* }).catch(function(err) {
* // Something went wrong.
* });
* }
* ~~~~
*
* API promises
* -------------
* All API calls returns a promise which you can use to call methods like `.then(...)` and `.catch(...)`.
*
* ~~~~
* kiteConnectApiCall
* .then(function(v) {
* // On success
* })
* .catch(function(e) {
* // On rejected
* });
* ~~~~
*
* @constructor
* @name KiteConnect
*
* @param {Object} params Init params.
* @param {string} params.api_key API key issued to you.
* @param {string} [params.access_token=null] Token obtained after the login flow in
* exchange for the `request_token`. Pre-login, this will default to null,
* but once you have obtained it, you should persist it in a database or session to pass
* to the Kite Connect class initialisation for subsequent requests.
* @param {string} [params.root="https://api.kite.trade"] API end point root. Unless you explicitly
* want to send API requests to a non-default endpoint, this can be ignored.
* @param {string} [params.login_uri="https://kite.trade/connect/login"] Kite connect login url
* @param {bool} [params.debug=false] If set to true, will console log requests and responses.
* @param {number} [params.timeout=7000] Time (milliseconds) for which the API client will wait
* for a request to complete before it fails.
*
* @example <caption>Initialize KiteConnect object</caption>
* var kc = KiteConnect("my_api_key", {timeout: 10, debug: false})
*/
var KiteConnect = function(params) {
var self = this
var defaults = {
"root": "https://api.kite.trade",
"login": "https://kite.trade/connect/login",
"debug": false,
"timeout": 7000
};
self.api_key = params.api_key;
self.root = params.root || defaults.root;
self.timeout = params.timeout || defaults.timeout;
self.debug = params.debug || defaults.debug;
self.access_token = params.access_token || null;
self.default_login_uri = defaults.login;
self.session_expiry_hook = null;
var kiteVersion = 3; // Kite version to send in header
var userAgent = utils.getUserAgent(); // User agent to be sent with every request
var routes = {
"api.token": "/session/token",
"api.token.invalidate": "/session/token",
"api.token.renew": "/session/refresh_token",
"user.profile": "/user/profile",
"user.margins": "/user/margins",
"user.margins.segment": "/user/margins/{segment}",
"orders": "/orders",
"trades": "/trades",
"order.info": "/orders/{order_id}",
"order.place": "/orders/{variety}",
"order.modify": "/orders/{variety}/{order_id}",
"order.cancel": "/orders/{variety}/{order_id}",
"order.trades": "/orders/{order_id}/trades",
"order.margins": "/margins/orders",
"order.margins.basket": "/margins/basket",
"portfolio.positions": "/portfolio/positions",
"portfolio.holdings": "/portfolio/holdings",
"portfolio.positions.convert": "/portfolio/positions",
"mf.orders": "/mf/orders",
"mf.order.info": "/mf/orders/{order_id}",
"mf.order.place": "/mf/orders",
"mf.order.cancel": "/mf/orders/{order_id}",
"mf.sips": "/mf/sips",
"mf.sip.info": "/mf/sips/{sip_id}",
"mf.sip.place": "/mf/sips",
"mf.sip.modify": "/mf/sips/{sip_id}",
"mf.sip.cancel": "/mf/sips/{sip_id}",
"mf.holdings": "/mf/holdings",
"mf.instruments": "/mf/instruments",
"market.instruments.all": "/instruments",
"market.instruments": "/instruments/{exchange}",
"market.historical": "/instruments/historical/{instrument_token}/{interval}",
"market.trigger_range": "/instruments/trigger_range/{transaction_type}",
"market.quote": "/quote",
"market.quote.ohlc": "/quote/ohlc",
"market.quote.ltp": "/quote/ltp",
"gtt.triggers": "/gtt/triggers",
"gtt.trigger_info": "/gtt/triggers/{trigger_id}",
"gtt.place": "/gtt/triggers",
"gtt.modify": "/gtt/triggers/{trigger_id}",
"gtt.delete": "/gtt/triggers/{trigger_id}"
};
var requestInstance = axios.create({
baseURL: self.root,
timeout: self.timeout,
headers: {
"X-Kite-Version": kiteVersion,
"User-Agent": userAgent
},
paramsSerializer: function(params) {
return querystring.stringify(params);
}
});
// Add a request interceptor
requestInstance.interceptors.request.use(function(request) {
if (self.debug) console.log(request);
return request;
});
// Add a response interceptor
requestInstance.interceptors.response.use(function(response) {
if (self.debug) console.log(response);
var contentType = response.headers["content-type"];
if (contentType === "application/json" && typeof response.data === "object") {
// Throw incase of error
if (response.data.error_type) throw response.data;
// Return success data
return response.data.data;
} else if (contentType === "text/csv") {
// Return the response directly
return response.data
} else {
return {
"error_type": "DataException",
"message": "Unknown content type (" + contentType + ") with response: (" + response.data + ")"
};
}
}, function (error) {
let resp = {
"message": "Unknown error",
"error_type": "GeneralException",
"data": null
};
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
if (error.response.data && error.response.data.error_type) {
if (error.response.data.error_type === "TokenException" && self.session_expiry_hook) {
self.session_expiry_hook()
}
resp = error.response.data;
} else {
resp.error_type = "NetworkException";
resp.message = error.response.statusText;
}
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
resp.error_type = "NetworkException";
resp.message = "No response from server with error code: " + error.code;
} else if (error.message) {
resp = error
}
return Promise.reject(resp);
});
// Constants
// Products
/**
* @memberOf KiteConnect
*/
self.PRODUCT_MIS = "MIS";
/**
* @memberOf KiteConnect
*/
self.PRODUCT_CNC = "CNC";
/**
* @memberOf KiteConnect
*/
self.PRODUCT_NRML = "NRML";
/**
* @memberOf KiteConnect
*/
self.PRODUCT_CO = "CO";
/**
* @memberOf KiteConnect
*/
self.PRODUCT_BO = "BO";
// Order types
/**
* @memberOf KiteConnect
*/
self.ORDER_TYPE_MARKET = "MARKET";
/**
* @memberOf KiteConnect
*/
self.ORDER_TYPE_LIMIT = "LIMIT";
/**
* @memberOf KiteConnect
*/
self.ORDER_TYPE_SLM = "SL-M";
/**
* @memberOf KiteConnect
*/
self.ORDER_TYPE_SL = "SL";
// Varities
/**
* @memberOf KiteConnect
*/
self.VARIETY_REGULAR = "regular";
/**
* @memberOf KiteConnect
*/
self.VARIETY_BO = "bo";
/**
* @memberOf KiteConnect
*/
self.VARIETY_CO = "co";
/**
* @memberOf KiteConnect
*/
self.VARIETY_AMO = "amo";
// Transaction type
/**
* @memberOf KiteConnect
*/
self.TRANSACTION_TYPE_BUY = "BUY";
/**
* @memberOf KiteConnect
*/
self.TRANSACTION_TYPE_SELL = "SELL";
// Validity
/**
* @memberOf KiteConnect
*/
self.VALIDITY_DAY = "DAY";
/**
* @memberOf KiteConnect
*/
self.VALIDITY_IOC = "IOC";
// Exchanges
/**
* @memberOf KiteConnect
*/
self.EXCHANGE_NSE = "NSE";
/**
* @memberOf KiteConnect
*/
self.EXCHANGE_BSE = "BSE";
/**
* @memberOf KiteConnect
*/
self.EXCHANGE_NFO = "NFO";
/**
* @memberOf KiteConnect
*/
self.EXCHANGE_CDS = "CDS";
/**
* @memberof KiteConnect
*/
self.EXCHANGE_BCD = "BCD";
/**
* @memberof KiteConnect
*/
self.EXCHANGE_BFO = "BFO";
/**
* @memberOf KiteConnect
*/
self.EXCHANGE_MCX = "MCX";
// Margins segments
/**
* @memberOf KiteConnect
*/
self.MARGIN_EQUITY = "equity";
/**
* @memberOf KiteConnect
*/
self.MARGIN_COMMODITY = "commodity";
/**
* @memberOf KiteConnect
*/
self.STATUS_CANCELLED = "CANCELLED";
/**
* @memberOf KiteConnect
*/
self.STATUS_REJECTED = "REJECTED";
/**
* @memberOf KiteConnect
*/
self.STATUS_COMPLETE = "COMPLETE";
/**
* @memberOf KiteConnect
*/
self.GTT_TYPE_OCO = "two-leg";
/**
* @memberOf KiteConnect
*/
self.GTT_TYPE_SINGLE = "single";
/**
* @memberOf KiteConnect
*/
self.GTT_STATUS_ACTIVE = "active";
/**
* @memberOf KiteConnect
*/
self.GTT_STATUS_TRIGGERED = "triggered";
/**
* @memberOf KiteConnect
*/
self.GTT_STATUS_DISABLED = "disabled";
/**
* @memberOf KiteConnect
*/
self.GTT_STATUS_EXPIRED = "expired";
/**
* @memberOf KiteConnect
*/
self.GTT_STATUS_CANCELLED = "cancelled";
/**
* @memberOf KiteConnect
*/
self.GTT_STATUS_REJECTED = "rejected";
/**
* @memberOf KiteConnect
*/
self.GTT_STATUS_DELETED = "deleted";
/**
* @memberOf KiteConnect
*/
self.POSITION_TYPE_DAY = "day";
/**
* @memberOf KiteConnect
*/
self.POSITION_TYPE_OVERNIGHT = "overnight";
/**
* Set `access_token` received after a successful authentication.
* @method setAccessToken
* @memberOf KiteConnect
* @instance
* @param {string} access_token Token obtained in exchange for `request_token`.
* Once you have obtained `access_token`, you should persist it in a database or session to pass
* to the Kite Connect class initialisation for subsequent requests.
*/
self.setAccessToken = function(access_token) {
self.access_token = access_token;
};
/**
* Set a callback hook for session (`TokenException` -- timeout, expiry etc.) errors.
* `access_token` (login session) can become invalid for a number of
* reasons, but it doesn't make sense for the client to try and catch it during every API call.
*
* A callback method that handles session errors can be set here and when the client encounters
* a token error at any point, it'll be called.
*
* This callback, for instance, can log the user out of the UI,
* clear session cookies, or initiate a fresh login.
* @method setSessionExpiryHook
* @memberOf KiteConnect
* @instance
* @param {function} cb Callback
*/
self.setSessionExpiryHook = function(cb) {
self.session_expiry_hook = cb;
};
/**
* Get the remote login url to which a user should be redirected to initiate the login flow.
* @method getLoginURL
* @memberOf KiteConnect
* @instance
*/
self.getLoginURL = function() {
return self.default_login_uri + "?api_key=" + self.api_key + "&v=3";
};
/**
* Do the token exchange with the `request_token` obtained after the login flow,
* and retrieve the `access_token` required for all subsequent requests. The
* response contains not just the `access_token`, but metadata for
* the user who has authenticated.
* @method generateSession
* @memberOf KiteConnect
* @instance
*
* @param {string} request_token Token obtained from the GET parameters after a successful login redirect.
* @param {string} api_secret API secret issued with the API key.
*/
self.generateSession = function(request_token, api_secret) {
return new Promise(function (resolve, reject) {
var checksum = sha256(self.api_key + request_token + api_secret).toString();
var p = _post("api.token", {
api_key: self.api_key,
request_token: request_token,
checksum: checksum
}, null, formatGenerateSession);
p.then(function(resp) {
// Set access token.
if (resp && resp.access_token) {
self.setAccessToken(resp.access_token);
}
return resolve(resp);
}).catch(function(err) {
return reject(err);
});
});
};
/**
* Kill the session by invalidating the access token.
* If access_token is passed then it will be set as current access token and get in validated.
* @method invalidateAccessToken
* @memberOf KiteConnect
* @instance
* @param {string} [access_token] Token to invalidate. Default is the active `access_token`.
*/
self.invalidateAccessToken = function(access_token) {
access_token = access_token || this.access_token;
return _delete("api.token.invalidate", {
api_key: self.api_key,
access_token: access_token
});
};
/**
* Renew access token by active refresh token.
* Renewed access token is implicitly set.
* @method renewAccessToken
* @memberOf KiteConnect
* @instance
*
* @param {string} refresh_token Token obtained from previous successful login.
* @param {string} api_secret API secret issued with the API key.
*/
self.renewAccessToken = function(refresh_token, api_secret) {
return new Promise(function (resolve, reject) {
var checksum = sha256(self.api_key + refresh_token + api_secret).toString();
var p = _post("api.token.renew", {
api_key: self.api_key,
refresh_token: refresh_token,
checksum: checksum
});
p.then(function(resp) {
if (resp && resp.access_token) {
self.setAccessToken(resp.access_token);
}
return resolve(resp);
}).catch(function(err) {
return reject(err);
});
});
};
/**
* Invalidate the refresh token.
* @method invalidateRefreshToken
* @memberOf KiteConnect
* @instance
* @param {string} refresh_token Token to invalidate.
*/
self.invalidateRefreshToken = function(refresh_token) {
return _delete("api.token.invalidate", {
api_key: this.api_key,
refresh_token: refresh_token
});
};
/**
* Get user profile details.
* @method getProfile
* @memberOf KiteConnect
* @instance
*/
self.getProfile = function() {
return _get("user.profile");
}
/**
* Get account balance and cash margin details for a particular segment.
* @method getMargins
* @memberOf KiteConnect
* @instance
* @param {string} [segment] trading segment (eg: equity or commodity).
*/
self.getMargins = function(segment) {
if (segment) {
return _get("user.margins.segment", {"segment": segment});
} else {
return _get("user.margins");
}
};
/**
* Place an order.
* @method placeOrder
* @memberOf KiteConnect
* @instance
* @param {string} variety Order variety (ex. bo, co, amo, regular).
* @param {string} params Order params.
* @param {string} params.exchange Exchange in which instrument is listed (NSE, BSE, NFO, BFO, CDS, MCX).
* @param {string} params.tradingsymbol Tradingsymbol of the instrument (ex. RELIANCE, INFY).
* @param {string} params.transaction_type Transaction type (BUY or SELL).
* @param {number} params.quantity Order quantity
* @param {string} params.product Product code (NRML, MIS, CNC).
* @param {string} params.order_type Order type (NRML, SL, SL-M, MARKET).
* @param {string} [params.validity] Order validity (DAY, IOC).
* @param {number} [params.price] Order Price
* @param {number} [params.disclosed_quantity] Disclosed quantity
* @param {number} [params.trigger_price] Trigger price
* @param {number} [params.squareoff] Square off value (only for bracket orders)
* @param {number} [params.stoploss] Stoploss value (only for bracket orders)
* @param {number} [params.trailing_stoploss] Trailing stoploss value (only for bracket orders)
* @param {string} [params.tag] An optional tag to apply to an order to identify it (alphanumeric, max 20 chars)
*/
self.placeOrder = function (variety, params) {
params.variety = variety;
return _post("order.place", params);
};
/**
* Modify an order
* @method modifyOrder
* @memberOf KiteConnect
* @instance
* @param {string} variety Order variety (ex. bo, co, amo, regular).
* @param {string} order_id ID of the order.
* @param {Object} params Order modify params.
* @param {number} [params.quantity] Order quantity
* @param {number} [params.price] Order Price
* @param {string} [params.order_type] Order type (NRML, SL, SL-M, MARKET).
* @param {string} [params.validity] Order validity (DAY, IOC).
* @param {number} [params.disclosed_quantity] Disclosed quantity
* @param {number} [params.trigger_price] Trigger price
* @param {string} [params.parent_order_id] Parent order id incase of multilegged orders.
*/
self.modifyOrder = function(variety, order_id, params) {
params.variety = variety;
params.order_id = order_id;
return _put("order.modify", params);
};
/**
* Cancel an order
* @method cancelOrder
* @memberOf KiteConnect
* @instance
* @param {string} variety Order variety (ex. bo, co, amo)
* @param {string} order_id ID of the order.
* @param {Object} [params] Order params.
regular).
* @param {string} [params.parent_order_id] Parent order id incase of multilegged orders.
*/
self.cancelOrder = function (variety, order_id, params) {
params = params || {};
params.variety = variety;
params.order_id = order_id;
return _delete("order.cancel", params);
};
/**
* Exit an order
* @method exitOrder
* @memberOf KiteConnect
* @instance
* @param {string} variety Order variety (ex. bo, co, amo)
* @param {string} order_id ID of the order.
* @param {Object} [params] Order params.
* @param {string} [params.parent_order_id] Parent order id incase of multilegged orders.
*/
self.exitOrder = function (variety, order_id, params) {
return self.cancelOrder(variety, order_id, params);
};
/**
* Get list of orders.
* @method getOrders
* @memberOf KiteConnect
* @instance
*/
self.getOrders = function() {
return _get("orders", null, null, formatResponse);
};
/**
* Get list of order history.
* @method getOrderHistory
* @memberOf KiteConnect
* @instance
* @param {string} order_id ID of the order whose order details to be retrieved.
*/
self.getOrderHistory = function(order_id) {
return _get("order.info", {"order_id": order_id}, null, formatResponse);
};
/**
* Retrieve the list of trades executed.
* @method getTrades
* @memberOf KiteConnect
* @instance
*/
self.getTrades = function() {
return _get("trades", null, null, formatResponse);
};
/**
* Retrieve the list of trades a particular order).
* An order can be executed in tranches based on market conditions.
* These trades are individually recorded under an order.
* @method getOrderTrades
* @memberOf KiteConnect
* @instance
* @param {string} order_id ID of the order whose trades are to be retrieved.
*/
self.getOrderTrades = function(order_id) {
return _get("order.trades", {"order_id": order_id}, null, formatResponse);
};
/**
* Fetch required margin for order/list of orders
* @method orderMargins
* @memberOf KiteConnect
* @instance
* @param {Object[]} orders Margin fetch orders.
* @param {string} orders[].exchange Name of the exchange(eg. NSE, BSE, NFO, CDS, MCX)
* @param {string} orders[].tradingsymbol Trading symbol of the instrument
* @param {string} orders[].transaction_type eg. BUY, SELL
* @param {string} orders[].variety Order variety (regular, amo, bo, co etc.)
* @param {string} orders[].product Margin product to use for the order
* @param {string} orders[].order_type Order type (MARKET, LIMIT etc.)
* @param {number} orders[].quantity Quantity of the order
* @param {number} orders[].price Price at which the order is going to be placed (LIMIT orders)
* @param {number} orders[].trigger_price Trigger price (for SL, SL-M, CO orders)
* @param {string} mode (optional) Compact mode will only give the total margins
*/
self.orderMargins = function(orders, mode=null) {
return _post("order.margins", orders, null, null, true,
{"mode":mode});
}
/**
* Fetch basket margin for list of orders
* @method orderBasketMargins
* @memberOf KiteConnect
* @instance
* @param {Object[]} orders Margin fetch orders.
* @param {string} orders[].exchange Name of the exchange(eg. NSE, BSE, NFO, CDS, MCX)
* @param {string} orders[].tradingsymbol Trading symbol of the instrument
* @param {string} orders[].transaction_type eg. BUY, SELL
* @param {string} orders[].variety Order variety (regular, amo, bo, co etc.)
* @param {string} orders[].product Margin product to use for the order
* @param {string} orders[].order_type Order type (MARKET, LIMIT etc.)
* @param {number} orders[].quantity Quantity of the order
* @param {number} orders[].price Price at which the order is going to be placed (LIMIT orders)
* @param {number} orders[].trigger_price Trigger price (for SL, SL-M, CO orders)
* @param {string} consider_positions Boolean to consider users positions while calculating margins
* @param {string} mode (optional) Compact mode will only give the total margins
*/
self.orderBasketMargins = function(orders, consider_positions=true, mode=null) {
return _post("order.margins.basket", orders, null, null, true,
{"consider_positions":consider_positions, "mode":mode});
}
/**
* Retrieve the list of equity holdings.
* @method getHoldings
* @memberOf KiteConnect
* @instance
*/
self.getHoldings = function() {
return _get("portfolio.holdings");
};
/**
* Retrieve positions.
* @method getPositions
* @memberOf KiteConnect
* @instance
*/
self.getPositions = function() {
return _get("portfolio.positions");
};
/**
* Modify an open position's product type.
* @method convertPosition
* @memberOf KiteConnect
* @instance
* @param {Object} params params.
* @param {string} params.exchange Exchange in which instrument is listed (NSE, BSE, NFO, BFO, CDS, MCX).
* @param {string} params.tradingsymbol Tradingsymbol of the instrument (ex. RELIANCE, INFY).
* @param {string} params.transaction_type Transaction type (BUY or SELL).
* @param {string} params.position_type Position type (overnight, day).
* @param {string} params.quantity Position quantity
* @param {string} params.old_product Current product code (NRML, MIS, CNC).
* @param {string} params.new_product New Product code (NRML, MIS, CNC).
*/
self.convertPosition = function(params) {
return _put("portfolio.positions.convert", params);
};
/**
* Retrieve the list of market instruments available to trade.
* Note that the results could be large, several hundred KBs in size,
* with tens of thousands of entries in the list.
* Response is array for objects. For example
* ~~~~
* {
* instrument_token: '131098372',
* exchange_token: '512103',
* tradingsymbol: 'NIDHGRN',
* name: 'NIDHI GRANITES',
* last_price: '0.0',
* expiry: '',
* strike: '0.0',
* tick_size: '0.05',
* lot_size: '1',
* instrument_type: 'EQ',
* segment: 'BSE',
* exchange: 'BSE' }, ...]
* ~~~~
*
* @method getInstruments
* @memberOf KiteConnect
* @instance
* @param {Array} [segment] Filter instruments based on exchange (NSE, BSE, NFO, BFO, CDS, MCX).
* If no `segment` is specified, all instruments are returned.
*/
self.getInstruments = function(exchange) {
if(exchange) {
return _get("market.instruments", {
"exchange": exchange
}, null, transformInstrumentsResponse);
} else {
return _get("market.instruments.all", null, null, transformInstrumentsResponse);
}
};
/**
* Retrieve quote and market depth for list of instruments.
* @method getQuote
* @memberOf KiteConnect
* @instance
* @param {Array} instruments is a list of instruments, Instrument are in the format of `exchange:tradingsymbol`.
* For example NSE:INFY and for list of instruments ["NSE:RELIANCE", "NSE:SBIN", ..]
*/
self.getQuote = function(instruments) {
return _get("market.quote", {"i": instruments}, null, formatQuoteResponse);
};
/**
* Retrieve OHLC for list of instruments.
* @method getOHLC
* @memberOf KiteConnect
* @instance
* @param {Array} instruments is a list of instruments, Instrument are in the format of `exchange:tradingsymbol`.
* For example NSE:INFY and for list of instruments ["NSE:RELIANCE", "NSE:SBIN", ..]
*/
self.getOHLC = function(instruments) {
return _get("market.quote.ohlc", {"i": instruments});
};
/**
* Retrieve LTP for list of instruments.
* @method getLTP
* @memberOf KiteConnect
* @instance
* @param {Array} instruments is a list of instruments, Instrument are in the format of `exchange:tradingsymbol`.
* For example NSE:INFY and for list of instruments ["NSE:RELIANCE", "NSE:SBIN", ..]
*/
self.getLTP = function(instruments) {
return _get("market.quote.ltp", {"i": instruments});
};
/**
* Retrieve historical data (candles) for an instrument.
* Although the actual response JSON from the API does not have field
* names such has 'open', 'high' etc., this functin call structures
* the data into an array of objects with field names. For example:
*
* ~~~~
* [{
* date: '2015-02-10T00:00:00+0530',
* open: 277.5,
* high: 290.8,
* low: 275.7,
* close: 287.3,
* volume: 22589681
* }, ....]
* ~~~~
*
* @method getHistoricalData
* @memberOf KiteConnect
* @instance
* @param {string} instrument_token Instrument identifier (retrieved from the instruments()) call.
* @param {string} interval candle interval (minute, day, 5 minute etc.)
* @param {string|Date} from_date From date (String in format of 'yyyy-mm-dd HH:MM:SS' or Date object).
* @param {string|Date} to_date To date (String in format of 'yyyy-mm-dd HH:MM:SS' or Date object).
* @param {bool} [continuous=false] is a bool flag to get continuous data for futures and options instruments. Defaults to false.
*/
self.getHistoricalData = function(instrument_token, interval, from_date, to_date, continuous, oi) {
continuous = continuous ? 1 : 0;
oi = oi ? 1 : 0;
if (typeof to_date === "object") to_date = _getDateTimeString(to_date)
if (typeof from_date === "object") from_date = _getDateTimeString(from_date)
return _get("market.historical", {
instrument_token: instrument_token,
interval: interval,
from: from_date,
to: to_date,
continuous: continuous,
oi: oi
}, null, parseHistorical);
};
// Convert Date object to string of format yyyy-mm-dd HH:MM:SS
function _getDateTimeString(date) {
var isoString = date.toISOString();
return isoString.replace("T", " ").split(".")[0];
}
/**
* Retrieve the buy/sell trigger range for Cover Orders.
* @method getTriggerRange
* @memberOf KiteConnect
* @instance
* @param {string} exchange Exchange in which instrument is listed (NSE, BSE, NFO, BFO, CDS, MCX).
* @param {string} tradingsymbol Tranding symbol of the instrument (ex. RELIANCE, INFY).
* @param {string} transaction_type Transaction type (BUY or SELL).
*/
self.getTriggerRange = function(transaction_type, instruments) {
return _get("market.trigger_range",
{
"i": instruments,
"transaction_type": transaction_type.toLowerCase()
}
);
};
/**
* Get list of mutual fund orders.
* @method getMFOrders
* @memberOf KiteConnect
* @instance
* @param {string} [order_id] ID of the order (optional) whose order details are to be retrieved.
* If no `order_id` is specified, all orders for the day are returned.
*/
self.getMFOrders = function (order_id) {
if (order_id) {
return _get("mf.order.info", { "order_id": order_id }, null, formatResponse);
} else {
return _get("mf.orders", null, null, formatResponse);
}
};
/**
* Place a mutual fund order.
* @method placeMFOrder
* @memberOf KiteConnect
* @instance
* @param {string} params Order params.
* @param {string} params.tradingsymbol Tradingsymbol (ISIN) of the fund.
* @param {string} params.transaction_type Transaction type (BUY or SELL).
* @param {string} [params.quantity] Quantity to SELL. Not applicable on BUYs.
* @param {string} [params.amount] Amount worth of units to purchase. Not applicable on SELLs
* @param {string} [params.tag] An optional tag to apply to an order to identify it (alphanumeric, max 20 chars)
*/
self.placeMFOrder = function (params) {
return _post("mf.order.place", params);
}
/**
* Cancel a mutual fund order.
* @method cancelMFOrder
* @memberOf KiteConnect
* @instance
* @param {string} order_id ID of the order.
*/
self.cancelMFOrder = function (order_id) {
return _delete("mf.order.cancel", { "order_id": order_id })
}
/**
* Get list of mutual fund SIPS.
* @method getMFSIPS
* @memberOf KiteConnect
* @instance
* @param {string} sip_id ID of the SIP.
*/
self.getMFSIPS = function (sip_id) {
if (sip_id) {
return _get("mf.sip.info", {"sip_id": sip_id}, null, formatResponse);
} else {
return _get("mf.sips", null, null, formatResponse);
}
}
/**
* Place a mutual fund SIP.
* @method placeMFSIP
* @memberOf KiteConnect
* @instance
* @param {string} params Order params.
* @param {string} params.tradingsymbol Tradingsymbol (ISIN) of the fund.
* @param {string} params.amount Amount worth of units to purchase.
* @param {string} params.instalments Number of instalments to trigger. If set to -1, instalments are triggered at fixed intervals until the SIP is cancelled
* @param {string} params.frequency Order frequency. weekly, monthly, or quarterly.
* @param {string} [params.initial_amount] Amount worth of units to purchase before the SIP starts.
* @param {string} [params.instalment_day] If frequency is monthly, the day of the month (1, 5, 10, 15, 20, 25) to trigger the order on.
* @param {string} [params.tag] An optional tag to apply to an order to identify it (alphanumeric, max 20 chars)
*/
self.placeMFSIP = function (params) {
return _post("mf.sip.place", params);
}
/**
* Modify a mutual fund SIP.
* @method modifyMFSIP
* @memberOf KiteConnect
* @instance
* @param {string} sip_id ID of the SIP.
* @param {string} params Order params.
* @param {string} [params.instalments] Number of instalments to trigger. If set to -1, instalments are triggered at fixed intervals until the SIP is cancelled
* @param {string} [params.frequency] Order frequency. weekly, monthly, or quarterly.
* @param {string} [params.instalment_day] If frequency is monthly, the day of the month (1, 5, 10, 15, 20, 25) to trigger the order on.
* @param {string} [params.status] Pause or unpause an SIP (active or paused).
*/
self.modifyMFSIP = function (sip_id, params) {
params.sip_id = sip_id;
return _put("mf.sip.modify", params);
}
/**
* Cancel a mutual fund SIP.
* @method cancelMFSIP
* @memberOf KiteConnect
* @instance
* @param {string} sip_id ID of the SIP.
*/
self.cancelMFSIP = function (sip_id) {
return _delete("mf.sip.cancel", {"sip_id": sip_id});
}
/**
* Get list of mutual fund holdings.
* @method getMFHoldings
* @memberOf KiteConnect
* @instance
*/
self.getMFHoldings = function () {
return _get("mf.holdings");
}
/**
* Get list of mutual fund instruments.
* @method getMFInstruments
* @memberOf KiteConnect
* @instance
*/
self.getMFInstruments = function () {
return _get("mf.instruments", null, null, transformMFInstrumentsResponse);
}
/**
* Get GTTs list
* @method getGTTs
* @memberOf KiteConnect
* @instance
*/
self.getGTTs = function () {
return _get("gtt.triggers", null, null, formatResponse);
}
/**
* Get list of order history.
* @method getGTT
* @memberOf KiteConnect
* @instance
* @param {string} trigger_id GTT trigger ID
*/
self.getGTT = function (trigger_id) {
return _get("gtt.trigger_info", { "trigger_id": trigger_id }, null, formatResponse);
};
// Get API params from user defined GTT params.
self._getGTTPayload = function (params) {
if (params.trigger_type !== self.GTT_TYPE_OCO && params.trigger_type !== self.GTT_TYPE_SINGLE) {
throw new Error("Invalid `params.trigger_type`")
}
if (params.trigger_type === self.GTT_TYPE_OCO && params.trigger_values.length !== 2) {
throw new Error("Invalid `trigger_values` for `OCO` order type")
}
if (params.trigger_type === self.GTT_TYPE_SINGLE && params.trigger_values.length !== 1) {
throw new Error("Invalid `trigger_values` for `single` order type")
}
let condition = {
exchange: params.exchange,
tradingsymbol: params.tradingsymbol,
trigger_values: params.trigger_values,
last_price: parseFloat(params.last_price)
}
let orders = []
for (let o of params.orders) {
orders.push({
transaction_type: o.transaction_type,
order_type: o.order_type,
product: o.product,
quantity: parseInt(o.quantity),
price: parseFloat(o.price),
exchange: params.exchange,
tradingsymbol: params.tradingsymbol
})
}
return { condition, orders }
};
/**
* Place GTT.
* @method placeGTT
* @memberOf KiteConnect
* @instance
* @param {string} params.trigger_type GTT type, its either `self.GTT_TYPE_OCO` or `self.GTT_TYPE_SINGLE`.
* @param {string} params.tradingsymbol Tradingsymbol of the instrument (ex. RELIANCE, INFY).
* @param {string} params.exchange Exchange in which instrument is listed (NSE, BSE, NFO, BFO, CDS, MCX).
* @param {number[]} params.trigger_values List of trigger values, number of items depends on trigger type.
* @param {number} params.last_price Price at which trigger is created. This is usually the last price of the instrument.
* @param {Object[]} params.orders List of orders.
* @param {string} params.orders.transaction_type Transaction type (BUY or SELL).
* @param {number} params.orders.quantity Order quantity
* @param {string} params.orders.product Product code (NRML, MIS, CNC).
* @param {string} params.orders.order_type Order type (NRML, SL, SL-M, MARKET).
* @param {number} params.orders.price Order price.
*/
self.placeGTT = function (params) {
let payload = self._getGTTPayload(params)
return _post("gtt.place", {
condition: JSON.stringify(payload.condition),
orders: JSON.stringify(payload.orders),
type: params.trigger_type
});
};
/**
* Place GTT.
* @method modifyGTT
* @memberOf KiteConnect
* @instance
* @param {string} trigger_id GTT trigger ID.
* @param {Object} params Modify params
* @param {string} params.trigger_type GTT type, its either `self.GTT_TYPE_OCO` or `self.GTT_TYPE_SINGLE`.
* @param {string} params.tradingsymbol Tradingsymbol of the instrument (ex. RELIANCE, INFY).
* @param {string} params.exchange Exchange in which instrument is listed (NSE, BSE, NFO, BFO, CDS, MCX).
* @param {number[]} params.trigger_values List of trigger values, number of items depends on trigger type.
* @param {number} params.last_price Price at which trigger is created. This is usually the last price of the instrument.
* @param {Object[]} params.orders List of orders.
* @param {string} params.orders.transaction_type Transaction type (BUY or SELL).
* @param {number} params.orders.quantity Order quantity
* @param {string} params.orders.product Product code (NRML, MIS, CNC).
* @param {string} params.orders.order_type Order type (NRML, SL, SL-M, MARKET).
* @param {number} params.orders.price Order price.
*/
self.modifyGTT = function (trigger_id, params) {
let payload = self._getGTTPayload(params)
return _put("gtt.modify", {
trigger_id: trigger_id,
type: params.trigger_type,
condition: JSON.stringify(payload.condition),
orders: JSON.stringify(payload.orders)
});
};
/**
* Get list of order history.
* @method deleteGTT
* @memberOf KiteConnect
* @instance
* @param {string} trigger_id GTT ID
*/
self.deleteGTT = function (trigger_id) {
return _delete("gtt.delete", { "trigger_id": trigger_id }, null, null);
};
/**
* Validate postback data checksum
* @method validatePostback
* @memberOf KiteConnect
* @instance
* @param {object} postback_data Postback data received. Must be an json object with required keys order_id, checksum and order_timestamp
* @param {string} api_secret Api secret of the app
* @returns {bool} Return true if checksum matches else false
* @throws Throws an error if the @postback_data or @api_secret is invalid
*/
self.validatePostback = function(postback_data, api_secret) {
if (!postback_data || !postback_data.checksum || !postback_data.order_id ||
!postback_data.order_timestamp || !api_secret) {
throw new Error("Invalid postback data or api_secret");
}
var inputString = postback_data.order_id + postback_data.order_timestamp + api_secret;
var checksum;
try {
checksum = sha256(inputString).toString();
} catch (e) {
throw(e)
}
if (postback_data.checksum === checksum) {
return true;
} else {
return false;
}
}
// Format generate session response
function formatGenerateSession(data) {
if (!data.data || typeof data.data !== "object") return data;
if (data.data.login_time) {
data.data.login_time = new Date(data.data.login_time);
}
return data;
}
function formatQuoteResponse(data) {
if (!data.data || typeof data.data !== "object") return data;
for (var k in data.data) {
var item = data.data[k];
for (var field of ["timestamp", "last_trade_time"]) {
if (item[field] && item[field].length === 19) {
item[field] = new Date(item[field]);
}
}
}
return data;
}
// Format response ex. datetime string to date
function formatResponse(data) {
if (!data.data || typeof data.data !== "object") return data;
var list = [];
if (data.data instanceof Array) {
list = data.data;
} else {
list = [data.data]
}
var results = [];
var fields = ["order_timestamp", "exchange_timestamp", "created", "last_instalment", "fill_timestamp"];
for (var item of list) {
for (var field of fields) {
if (item[field] && item[field].length === 19) {
item[field] = new Date(item[field]);
}
}
results.push(item);
}
if (data.data instanceof Array) {
data.data = results;
} else {
data.data = results[0];
}
return data;
}
function parseHistorical(jsonData) {
// Return if its an error
if (jsonData.error_type) return jsonData;
var results = [];
for(var i=0; i<jsonData.data.candles.length; i++) {
var d = jsonData.data.candles[i];
var c = {
"date": new Date(d[0]),
"open": d[1],
"high": d[2],
"low": d[3],
"close": d[4],
"volume": d[5]
}
// Add OI field if its returned
if (d[6]) {
c["oi"] = d[6]
}
results.push(c);
}
return { "data": results };
}
function transformInstrumentsResponse(data, headers) {
// Parse CSV responses
if (headers["content-type"] === "text/csv") {
var parsedData = csvParse.parse(data, {"header": true}).data;
for (var item of parsedData) {
item["last_price"] = parseFloat(item["last_price"]);
item["strike"] = parseFloat(item["strike"]);
item["tick_size"] = parseFloat(item["tick_size"]);
item["lot_size"] = parseInt(item["lot_size"]);
if (item["expiry"] && item["expiry"].length === 10) {
item["expiry"] = new Date(item["expiry"]);
}
}
return parsedData;
}
return data;
}
function transformMFInstrumentsResponse(data, headers) {
// Parse CSV responses
if (headers["content-type"] === "text/csv") {
var parsedData = csvParse.parse(data, {"header": true}).data;
for (var item of parsedData) {
item["minimum_purchase_amount"] = parseFloat(item["minimum_purchase_amount"]);
item["purchase_amount_multiplier"] = parseFloat(item["purchase_amount_multiplier"]);
item["minimum_additional_purchase_amount"] = parseFloat(item["minimum_additional_purchase_amount"]);
item["redemption_quantity_multiplier"] = parseFloat(item["redemption_quantity_multiplier"]);
item["minimum_additional_purchase_amount"] = parseFloat(item["minimum_additional_purchase_amount"]);
item["last_price"] = parseFloat(item["last_price"]);
item["purchase_allowed"] = Boolean(parseInt(item["purchase_allowed"]));
item["redemption_allowed"] = Boolean(parseInt(item["redemption_allowed"]));
if (item["last_price_date"] && item["last_price_date"].length === 10) {
item["last_price_date"] = new Date(item["last_price_date"]);
}
}
return parsedData;
}
return data;
}
function _get(route, params, responseType, responseTransformer, isJSON = false) {
return request(route, "GET", params || {}, responseType, responseTransformer, isJSON);
}
function _post(route, params, responseType, responseTransformer, isJSON = false, queryParams=null) {
return request(route, "POST", params || {}, responseType, responseTransformer, isJSON, queryParams);
}
function _put(route, params, responseType, responseTransformer, isJSON = false, queryParams=null) {
return request(route, "PUT", params || {}, responseType, responseTransformer, isJSON, queryParams);
}
function _delete(route, params, responseType, responseTransformer, isJSON = false) {
return request(route, "DELETE", params || {}, responseType, responseTransformer, isJSON);
}
function request(route, method, params, responseType, responseTransformer, isJSON, queryParams) {
// Check access token
if (!responseType) responseType = "json";
var uri = routes[route];
// Replace variables in "RESTful" URLs with corresponding params
if(uri.indexOf("{") !== -1) {
var k;
for(k in params) {
if(params.hasOwnProperty(k)) {
uri = uri.replace("{" + k + "}", params[k]);
}
}
}
let payload = null;
if (method === "GET" || method === "DELETE") {
queryParams = params;
} else {
if (isJSON) {
// post JSON payload
payload = JSON.stringify(params);
} else {
// post url encoded payload
payload = querystring.stringify(params);
}
}
var options = {
method: method,
url: uri,
params: queryParams,
data: payload,
// Set auth header
headers: {}
};
// Send auth token
if (self.access_token) {
var authHeader = self.api_key + ":" + self.access_token;
options["headers"]["Authorization"] = "token " + authHeader;
}
// Set request header content type
if(isJSON){
options["headers"]["Content-Type"] = "application/json"
} else {
options["headers"]["Content-Type"] = "application/x-www-form-urlencoded"
}
// Set response transformer
if (responseTransformer) {
options.transformResponse = axios.defaults.transformResponse.concat(responseTransformer);
}
return requestInstance.request(options);
}
}
module.exports = KiteConnect;