Skip to content

Instantly share code, notes, and snippets.

@ajinasokan
Last active December 18, 2023 18:47
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save ajinasokan/b36d17da1a58af62782978c16bca309a to your computer and use it in GitHub Desktop.
Save ajinasokan/b36d17da1a58af62782978c16bca309a to your computer and use it in GitHub Desktop.
Kite Ticker Pure JS Example
<script src="ticker.js"></script>
<script>
var ticker = new KiteTicker({api_key: "api_key", access_token: "access_token"});
ticker.connect();
ticker.on("ticks", onTicks);
ticker.on("connect", subscribe);
function onTicks(ticks) {
console.log("Ticks", ticks);
}
function subscribe() {
var items = [738561];
ticker.subscribe(items);
ticker.setMode(ticker.modeFull, items);
}
</script>
/**
* The WebSocket client for connecting to Kite connect streaming quotes service.
*
* Getting started:
* ---------------
*
* var KiteTicker = require("kiteconnect").KiteTicker;
* var ticker = new KiteTicker({
* api_key: "api_key",
* access_token: "access_token"
* });
*
* ticker.connect();
* ticker.on("ticks", onTicks);
* ticker.on("connect", subscribe);
*
* function onTicks(ticks) {
* console.log("Ticks", ticks);
* }
*
* function subscribe() {
* var items = [738561];
* ticker.subscribe(items);
* ticker.setMode(ticker.modeFull, items);
* }
*
* Tick structure (passed to the tick callback you assign):
* ---------------------------
* [{ tradable: true,
* mode: 'full',
* instrument_token: 208947,
* last_price: 3939,
* last_quantity: 1,
* average_price: 3944.77,
* volume: 28940,
* buy_quantity: 4492,
* sell_quantity: 4704,
* ohlc: { open: 3927, high: 3955, low: 3927, close: 3906 },
* change: 0.8448540706605223,
* last_trade_time: 1515491369,
* timestamp: 1515491373,
* oi: 24355,
* oi_day_high: 0,
* oi_day_low: 0,
* depth:
* buy: [{
* quantity: 59,
* price: 3223,
* orders: 5
* },
* {
* quantity: 164,
* price: 3222,
* orders: 15
* },
* {
* quantity: 123,
* price: 3221,
* orders: 7
* },
* {
* quantity: 48,
* price: 3220,
* orders: 7
* },
* {
* quantity: 33,
* price: 3219,
* orders: 5
* }],
* sell: [{
* quantity: 115,
* price: 3224,
* orders: 15
* },
* {
* quantity: 50,
* price: 3225,
* orders: 5
* },
* {
* quantity: 175,
* price: 3226,
* orders: 14
* },
* {
* quantity: 49,
* price: 3227,
* orders: 10
* },
* {
* quantity: 106,
* price: 3228,
* orders: 13
* }]
* }
* }, ...]
*
* Auto reconnection
* -----------------
* Auto reonnection is enabled by default and it can be disabled by passing `reconnect` param while initialising `KiteTicker`.
*
* Auto reonnection mechanism is based on [Exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff) algorithm in which
* next retry interval will be increased exponentially. `max_delay` and `max_tries` params can be used to tweak
* the alogrithm where `max_delay` is the maximum delay after which subsequent reconnection interval will become constant and
* `max_tries` is maximum number of retries before it quits reconnection.
* For example if `max_delay` is 60 seconds and `max_tries` is 50 then the first reconnection interval starts from
* minimum interval which is 2 seconds and keep increasing up to 60 seconds after which it becomes constant and when reconnection attempt
* is reached upto 50 then it stops reconnecting.
* Callback `reconnect` will be called with current reconnect attempt and next reconnect interval and
* `on_noreconnect` is called when reconnection attempts reaches max retries.
*
* Here is an example demonstrating auto reconnection.
*
* var KiteTicker = require("kiteconnect").KiteTicker;
* var ticker = new KiteTicker({
* api_key: "api_key",
* access_token: "access_token"
* });
*
* // set autoreconnect with 10 maximum reconnections and 5 second interval
* ticker.autoReconnect(true, 10, 5)
* ticker.connect();
* ticker.on("ticks", onTicks);
* ticker.on("connect", subscribe);
*
* ticker.on("noreconnect", function() {
* console.log("noreconnect");
* });
*
* ticker.on("reconnect", function(reconnect_count, reconnect_interval) {
* console.log("Reconnecting: attempt - ", reconnect_count, " interval - ", reconnect_interval);
* });
*
* function onTicks(ticks) {
* console.log("Ticks", ticks);
* }
*
* function subscribe() {
* var items = [738561];
* ticker.subscribe(items);
* ticker.setMode(ticker.modeFull, items);
* }
*
*
* @constructor
* @name KiteTicker
* @param {Object} params
* @param {string} params.api_key API key issued you.
* @param {string} params.access_token Access token obtained after successful login flow.
* @param {bool} [params.reconnect] Enable/Disable auto reconnect. Enabled by default.
* @param {number} [params.max_retry=50] is maximum number re-connection attempts. Defaults to 50 attempts and maximum up to 300 attempts.
* @param {number} [params.max_delay=60] in seconds is the maximum delay after which subsequent re-connection interval will become constant. Defaults to 60s and minimum acceptable value is 5s.
* #param {string} [params.root="wss://websocket.kite.trade/"] Kite websocket root.
*/
var KiteTicker = function (params) {
var root = params.root || 'wss://ws.kite.trade/'
var read_timeout = 5, // seconds
reconnect_max_delay = 0,
reconnect_max_tries = 0,
// message flags (outgoing)
mSubscribe = 'subscribe',
mUnSubscribe = 'unsubscribe',
mSetMode = 'mode',
// incoming
mAlert = 10,
mMessage = 11,
mLogout = 12,
mReload = 13,
mClearCache = 14,
// public constants
modeFull = 'full', // Full quote including market depth. 164 bytes.
modeQuote = 'quote', // Quote excluding market depth. 52 bytes.
modeLTP = 'ltp'
// public constants
/**
* @memberOf KiteTicker
* @desc Set mode full
*/
this.modeFull = modeFull
/**
* @memberOf KiteTicker
* @desc Set mode quote
*/
this.modeQuote = modeQuote
/**
* @memberOf KiteTicker
* @desc Set mode LTP
*/
this.modeLTP = modeLTP
var ws = null,
triggers = {'connect': [],
'ticks': [],
'disconnect': [],
'error': [],
'close': [],
'reconnect': [],
'noreconnect': [],
'message': [],
'order_update': []},
read_timer = null,
last_read = 0,
reconnect_timer = null,
auto_reconnect = false,
current_reconnection_count = 0,
last_reconnect_interval = 0
current_ws_url = null,
token_modes = {},
defaultReconnectMaxDelay = 60,
defaultReconnectMaxRetries = 50,
maximumReconnectMaxRetries = 300,
minimumReconnectMaxDelay = 5
// segment constants
var NseCM = 1,
NseFO = 2,
NseCD = 3,
BseCM = 4,
BseFO = 5,
BseCD = 6,
McxFO = 7,
McxSX = 8,
Indices = 9
// Enable auto reconnect by default
if (!params.reconnect) params.reconnect = true
autoReconnect(params.reconnect, params.max_retry, params.max_delay)
/**
* Auto reconnect settings
* @param {bool} Enable or disable auto disconnect, defaults to false
* @param {number} [max_retry=50] is maximum number re-connection attempts. Defaults to 50 attempts and maximum up to 300 attempts.
* @param {number} [max_delay=60] in seconds is the maximum delay after which subsequent re-connection interval will become constant. Defaults to 60s and minimum acceptable value is 5s.
* @memberOf KiteTicker
* @method autoReconnect
*/
this.autoReconnect = function (t, max_retry, max_delay) {
autoReconnect(t, max_retry, max_delay)
}
/**
* Initiate a websocket connection
* @memberOf KiteTicker
* @method connect
* @instance
*/
this.connect = function () {
// Skip if its already connected
if (ws && (ws.readyState == ws.CONNECTING || ws.readyState == ws.OPEN)) return
var url = root + '?api_key=' + params.api_key +
'&access_token=' + params.access_token + '&uid=' + (new Date().getTime().toString())
ws = new WebSocket(url)
ws.binaryType = 'arraybuffer'
ws.onopen = function () {
// Reset last reconnect interval
last_reconnect_interval = null
// Reset current_reconnection_count attempt
current_reconnection_count = 0
// Store current open connection url to check for auto re-connection.
if (!current_ws_url) current_ws_url = this.url
// Trigger on connect event
trigger('connect')
// If there isn't an incoming message in n seconds, assume disconnection.
clearInterval(read_timer)
last_read = new Date()
read_timer = setInterval(function () {
if ((new Date() - last_read) / 1000 >= read_timeout) {
// reset current_ws_url incase current connection times out
// This is determined when last heart beat received time interval
// exceeds read_timeout value
current_ws_url = null
if (ws) ws.close()
clearInterval(read_timer)
triggerDisconnect()
}
}, read_timeout * 1000)
}
ws.onmessage = function (e) {
// Binary tick data.
if (e.data instanceof ArrayBuffer) {
if (e.data.byteLength > 2) {
var d = parseBinary(e.data)
if (d) trigger('ticks', [d])
}
} else {
parseTextMessage(e.data)
}
// Set last read time to check for connection timeout
last_read = new Date()
}
ws.onerror = function (e) {
trigger('error', [e])
// Force close to avoid ghost connections
if (this && this.readyState == this.OPEN) this.close()
}
ws.onclose = function (e) {
trigger('close', [e])
// the ws id doesn't match the current global id,
// meaning it's a ghost close event. just ignore.
if (current_ws_url && (this.url != current_ws_url)) return
triggerDisconnect(e)
}
}
/**
* @memberOf KiteTicker
* @method disconnect
* @instance
*/
this.disconnect = function () {
if (ws && ws.readyState != ws.CLOSING && ws.readyState != ws.CLOSED) {
ws.close()
}
}
/**
* Check if the ticker is connected
* @memberOf KiteTicker
* @method connected
* @instance
* @returns {bool}
*/
this.connected = function () {
if (ws && ws.readyState == ws.OPEN) {
return true
} else {
return false
}
}
/**
* Register websocket event callbacks
* Available events
* ~~~~
* connect - when connection is successfully established.
* ticks - when ticks are available (Arrays of `ticks` object as the first argument).
* disconnect - when socket connection is disconnected. Error is received as a first param.
* error - when socket connection is closed with error. Error is received as a first param.
* close - when socket connection is closed cleanly.
* reconnect - When reconnecting (current re-connection count and reconnect interval as arguments respectively).
* noreconnect - When re-connection fails after n number times.
* order_update - When order update (postback) is received for the connected user (Data object is received as first argument).
* ~~~~
*
* @memberOf KiteTicker
* @method on
* @instance
*
* @example
* ticker.on("ticks", callback);
* ticker.on("connect", callback);
* ticker.on("disconnect", callback);
*/
this.on = function (e, callback) {
if (triggers.hasOwnProperty(e)) {
triggers[e].push(callback)
}
}
/**
* Subscribe to array of tokens
* @memberOf KiteTicker
* @method subscribe
* @instance
* @param {array} tokens Array of tokens to be subscribed
*
* @example
* ticker.subscribe([738561]);
*/
this.subscribe = function (tokens) {
if (tokens.length > 0) {
send({'a': mSubscribe, 'v': tokens})
}
return tokens
}
/**
* Unsubscribe to array of tokens
* @memberOf KiteTicker
* @method unsubscribe
* @instance
* @param {array} tokens Array of tokens to be subscribed
*
* @example
* ticker.unsubscribe([738561]);
*/
this.unsubscribe = function (tokens) {
if (tokens.length > 0) {
send({'a': mUnSubscribe, 'v': tokens})
}
return tokens
}
/**
* Set modes to array of tokens
* @memberOf KiteTicker
* @method setMode
* @instance
* @param {string} mode - mode to set
* @param {array} tokens Array of tokens to be subscribed
*
* @example
* ticker.setMode(ticker.modeFull, [738561]);
*/
this.setMode = function (mode, tokens) {
if (tokens.length > 0) {
send({'a': mSetMode, 'v': [mode, tokens]})
}
return tokens
}
function autoReconnect (t, max_retry, max_delay) {
auto_reconnect = (t == true)
// Set default values
max_retry = max_retry || defaultReconnectMaxRetries
max_delay = max_delay || defaultReconnectMaxDelay
// Set reconnect constraints
reconnect_max_tries = max_retry >= maximumReconnectMaxRetries ? maximumReconnectMaxRetries : max_retry
reconnect_max_delay = max_delay <= minimumReconnectMaxDelay ? minimumReconnectMaxDelay : max_delay
}
function triggerDisconnect (e) {
ws = null
trigger('disconnect', [e])
if (auto_reconnect) attemptReconnection()
}
// send a message via the socket
// automatically encodes json if possible
function send (message) {
if (!ws || ws.readyState != ws.OPEN) return
try {
if (typeof (message) === 'object') {
message = JSON.stringify(message)
}
ws.send(message)
} catch (e) { ws.close() };
}
// trigger event callbacks
function trigger (e, args) {
if (!triggers[e]) return
for (var n = 0; n < triggers[e].length; n++) {
triggers[e][n].apply(triggers[e][n], args || [])
}
}
function parseTextMessage (data) {
try {
data = JSON.parse(data)
} catch (e) {
return
}
if (data.type === 'order') {
trigger('order_update', [data.data])
}
}
// parse received binary message. each message is a combination of multiple tick packets
// [2-bytes num packets][size1][tick1][size2][tick2] ...
function parseBinary (binpacks) {
var packets = splitPackets(binpacks),
ticks = []
for (var n = 0; n < packets.length; n++) {
var bin = packets[n],
instrument_token = buf2long(bin.slice(0, 4)),
segment = instrument_token & 0xff
var tradable = true
if (segment === Indices) tradable = false
var divisor = 100.0
if (segment === NseCD) divisor = 10000000.0
// Parse LTP
if (bin.byteLength === 8) {
ticks.push({
tradable: tradable,
mode: modeLTP,
instrument_token: instrument_token,
last_price: buf2long(bin.slice(4, 8)) / divisor
})
// Parse indices quote and full mode
} else if (bin.byteLength === 28 || bin.byteLength === 32) {
var mode = modeQuote
if (bin.byteLength === 32) mode = modeFull
var tick = {
tradable: tradable,
mode: mode,
instrument_token: instrument_token,
last_price: buf2long(bin.slice(4, 8)) / divisor,
ohlc: {
high: buf2long(bin.slice(8, 12)) / divisor,
low: buf2long(bin.slice(12, 16)) / divisor,
open: buf2long(bin.slice(16, 20)) / divisor,
close: buf2long(bin.slice(20, 24)) / divisor
},
change: buf2long(bin.slice(24, 28))
}
// Compute the change price using close price and last price
if (tick.ohlc.close != 0) {
tick.change = (tick.last_price - tick.ohlc.close) * 100 / tick.ohlc.close
}
// Full mode with timestamp in seconds
if (bin.byteLength === 32) {
tick.timestamp = null
var timestamp = buf2long(bin.slice(28, 32))
if (timestamp) tick.timestamp = new Date(timestamp)
}
ticks.push(tick)
} else if (bin.byteLength === 44 || bin.byteLength === 184) {
var mode = modeQuote
if (bin.byteLength === 184) mode = modeFull
var tick = {
tradable: tradable,
mode: mode,
instrument_token: instrument_token,
last_price: buf2long(bin.slice(4, 8)) / divisor,
last_quantity: buf2long(bin.slice(8, 12)),
average_price: buf2long(bin.slice(12, 16)) / divisor,
volume: buf2long(bin.slice(16, 20)),
buy_quantity: buf2long(bin.slice(20, 24)),
sell_quantity: buf2long(bin.slice(24, 28)),
ohlc: {
open: buf2long(bin.slice(28, 32)) / divisor,
high: buf2long(bin.slice(32, 36)) / divisor,
low: buf2long(bin.slice(36, 40)) / divisor,
close: buf2long(bin.slice(40, 44)) / divisor
}
}
// Compute the change price using close price and last price
if (tick.ohlc.close != 0) {
tick.change = (tick.last_price - tick.ohlc.close) * 100 / tick.ohlc.close
}
// Parse full mode
if (bin.byteLength === 184) {
// Parse last trade time
tick.last_trade_time = null
var last_trade_time = buf2long(bin.slice(44, 48))
if (last_trade_time) tick.last_trade_time = new Date(last_trade_time * 1000)
// Parse timestamp
tick.timestamp = null
var timestamp = buf2long(bin.slice(60, 64))
if (timestamp) tick.timestamp = new Date(timestamp * 1000)
// Parse OI
tick.oi = buf2long(bin.slice(48, 52))
tick.oi_day_high = buf2long(bin.slice(52, 56))
tick.oi_day_low = buf2long(bin.slice(56, 60))
tick.depth = {
buy: [],
sell: []
}
var s = 0, depth = bin.slice(64, 184)
for (var i = 0; i < 10; i++) {
s = i * 12
tick.depth[i < 5 ? 'buy' : 'sell'].push({
quantity: buf2long(depth.slice(s, s + 4)),
price: buf2long(depth.slice(s + 4, s + 8)) / divisor,
orders: buf2long(depth.slice(s + 8, s + 10))
})
}
}
ticks.push(tick)
}
}
return ticks
}
// split one long binary message into individual tick packets
function splitPackets (bin) {
// number of packets
var num = buf2long(bin.slice(0, 2)),
j = 2,
packets = []
for (var i = 0; i < num; i++) {
// first two bytes is the packet length
var size = buf2long(bin.slice(j, j + 2)),
packet = bin.slice(j + 2, j + 2 + size)
packets.push(packet)
j += 2 + size
}
return packets
}
function attemptReconnection () {
// Try reconnecting only so many times.
if (current_reconnection_count > reconnect_max_tries) {
trigger('noreconnect')
process.exit(1)
}
if (current_reconnection_count > 0) {
last_reconnect_interval = Math.pow(2, current_reconnection_count)
} else if (!last_reconnect_interval) {
last_reconnect_interval = 1
}
if (last_reconnect_interval > reconnect_max_delay) {
last_reconnect_interval = reconnect_max_delay
}
current_reconnection_count++
trigger('reconnect', [current_reconnection_count, last_reconnect_interval])
reconnect_timer = setTimeout(function () {
self.connect()
}, last_reconnect_interval * 1000)
}
// Big endian byte array to long.
function buf2long (buf) {
var b = new Uint8Array(buf),
val = 0,
len = b.length
for (var i = 0, j = len - 1; i < len; i++, j--) {
val += b[j] << (i * 8)
}
return val
}
// de-duplicate an array
function arrayUnique () {
var u = {}, a = []
for (var i = 0, l = this.length; i < l; ++i) {
if (u.hasOwnProperty(this[i])) {
continue
}
a.push(this[i])
u[this[i]] = 1
}
return a
}
var self = this
}
@rkolagatla
Copy link

Hi Ajin,
I am trying to use the code above and I am getting the following error:

ticker.js:263 WebSocket connection to 'wss://ws.kite.trade/?api_key="-------------------"&access_token="------------------------------------"&uid="--------------------------------------"' failed: Error during WebSocket handshake: Unexpected response code: 403

Also do I have to subscribe for the Historical API from Zerodha for the Websocket Ticker to work.

Thanks,
Rajesh

@ajinasokan
Copy link
Author

If it is 403 that means either your API key or access token is invalid. For ticker, you don't need historical API subscription.
If you have more questions please create a thread here: kite.trade/forum

@rahulmr
Copy link

rahulmr commented Aug 27, 2020

If it is 403 that means either your API key or access token is invalid. For ticker, you don't need historical API subscription.
If you have more questions please create a thread here: kite.trade/forum

Great work @ajinasokan . Is it possible to implement a dynamic way to subscribe to token? Mean to say how can we process ticks further using some UI button etc..

@ajinasokan
Copy link
Author

@rahulmr I don't know what you meant by dynamic. It already is. You can provide any token to subscribe from anywhere in the script. You can build UI in that HTML file and connect it to JS.

@rahulmr
Copy link

rahulmr commented Sep 9, 2020

@ajinasokan Got it, thanks. I was looking for an example where I can type symbol and then subscribe. I understood you just wanted to have a simple example in here.

@sheshagiri-tammanagoudar

@ajinasokan Could you please share a similar js file for kiteconnect as well. I tried creating one using browserify. But facing few issues.

@rkolagatla
Copy link

rkolagatla commented Oct 14, 2023

There is a place where Microsoft and Google meet and it is Browser Wars as well as they conspired through JavaScript and Adobe Flash.

Zerodha has made a case up for taking me to UNITED NATIONS SECURITY COUNCIL as this user is/was an Employee of ZERODHA.
He is a Malayali. Auction.com and Ooty Schools. Shanti........

This just won't work - Don't waste your TIME and Microsoft buys GitHub.....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment