kiteconnect

Kite Connect API client for Python -- kite.trade.

Zerodha Technology Pvt. Ltd. (c) 2021

License

KiteConnect Python library is licensed under the MIT License

The library

Kite Connect is a set of REST-like APIs that expose many capabilities required to build a complete investment and trading platform. Execute orders in real time, manage user portfolio, stream live market data (WebSockets), and more, with the simple HTTP API collection

This module provides an easy to use abstraction over the HTTP APIs. The HTTP calls have been converted to methods and their JSON responses are returned as native Python structures, for example, dicts, lists, bools etc. See the Kite Connect API documentation for the complete list of APIs, supported parameters and values, and response formats.

Getting started

#!python
import logging
from kiteconnect import KiteConnect

logging.basicConfig(level=logging.DEBUG)

kite = KiteConnect(api_key="your_api_key")

# Redirect the user to the login url obtained
# from kite.login_url(), and receive the request_token
# from the registered redirect url after the login flow.
# Once you have the request_token, obtain the access_token
# as follows.

data = kite.generate_session("request_token_here", api_secret="your_secret")
kite.set_access_token(data["access_token"])

# Place an order
try:
    order_id = kite.place_order(variety=kite.VARIETY_REGULAR,
                                tradingsymbol="INFY",
                                exchange=kite.EXCHANGE_NSE,
                                transaction_type=kite.TRANSACTION_TYPE_BUY,
                                quantity=1,
                                order_type=kite.ORDER_TYPE_MARKET,
                                product=kite.PRODUCT_CNC,
                                validity=kite.VALIDITY_DAY)

    logging.info("Order placed. ID is: {}".format(order_id))
except Exception as e:
    logging.info("Order placement failed: {}".format(e.message))

# Fetch all orders
kite.orders()

# Get instruments
kite.instruments()

# Place an mutual fund order
kite.place_mf_order(
    tradingsymbol="INF090I01239",
    transaction_type=kite.TRANSACTION_TYPE_BUY,
    amount=5000,
    tag="mytag"
)

# Cancel a mutual fund order
kite.cancel_mf_order(order_id="order_id")

# Get mutual fund instruments
kite.mf_instruments()

A typical web application

In a typical web application where a new instance of views, controllers etc. are created per incoming HTTP request, you will need to initialise a new instance of Kite client per request as well. This is because each individual instance represents a single user that's authenticated, unlike an admin API where you may use one instance to manage many users.

Hence, in your web application, typically:

  • You will initialise an instance of the Kite client
  • Redirect the user to the login_url()
  • At the redirect url endpoint, obtain the request_token from the query parameters
  • Initialise a new instance of Kite client, use generate_session() to obtain the access_token along with authenticated user data
  • Store this response in a session and use the stored access_token and initialise instances of Kite client for subsequent API calls.

Exceptions

Kite Connect client saves you the hassle of detecting API errors by looking at HTTP codes or JSON error responses. Instead, it raises aptly named exceptions that you can catch.

  1# -*- coding: utf-8 -*-
  2"""
  3Kite Connect API client for Python -- [kite.trade](https://kite.trade).
  4
  5Zerodha Technology Pvt. Ltd. (c) 2021
  6
  7License
  8-------
  9KiteConnect Python library is licensed under the MIT License
 10
 11The library
 12-----------
 13Kite Connect is a set of REST-like APIs that expose
 14many capabilities required to build a complete
 15investment and trading platform. Execute orders in
 16real time, manage user portfolio, stream live market
 17data (WebSockets), and more, with the simple HTTP API collection
 18
 19This module provides an easy to use abstraction over the HTTP APIs.
 20The HTTP calls have been converted to methods and their JSON responses
 21are returned as native Python structures, for example, dicts, lists, bools etc.
 22See the **[Kite Connect API documentation](https://kite.trade/docs/connect/v3/)**
 23for the complete list of APIs, supported parameters and values, and response formats.
 24
 25Getting started
 26---------------
 27    #!python
 28    import logging
 29    from kiteconnect import KiteConnect
 30
 31    logging.basicConfig(level=logging.DEBUG)
 32
 33    kite = KiteConnect(api_key="your_api_key")
 34
 35    # Redirect the user to the login url obtained
 36    # from kite.login_url(), and receive the request_token
 37    # from the registered redirect url after the login flow.
 38    # Once you have the request_token, obtain the access_token
 39    # as follows.
 40
 41    data = kite.generate_session("request_token_here", api_secret="your_secret")
 42    kite.set_access_token(data["access_token"])
 43
 44    # Place an order
 45    try:
 46        order_id = kite.place_order(variety=kite.VARIETY_REGULAR,
 47                                    tradingsymbol="INFY",
 48                                    exchange=kite.EXCHANGE_NSE,
 49                                    transaction_type=kite.TRANSACTION_TYPE_BUY,
 50                                    quantity=1,
 51                                    order_type=kite.ORDER_TYPE_MARKET,
 52                                    product=kite.PRODUCT_CNC,
 53                                    validity=kite.VALIDITY_DAY)
 54
 55        logging.info("Order placed. ID is: {}".format(order_id))
 56    except Exception as e:
 57        logging.info("Order placement failed: {}".format(e.message))
 58
 59    # Fetch all orders
 60    kite.orders()
 61
 62    # Get instruments
 63    kite.instruments()
 64
 65    # Place an mutual fund order
 66    kite.place_mf_order(
 67        tradingsymbol="INF090I01239",
 68        transaction_type=kite.TRANSACTION_TYPE_BUY,
 69        amount=5000,
 70        tag="mytag"
 71    )
 72
 73    # Cancel a mutual fund order
 74    kite.cancel_mf_order(order_id="order_id")
 75
 76    # Get mutual fund instruments
 77    kite.mf_instruments()
 78
 79A typical web application
 80-------------------------
 81In a typical web application where a new instance of
 82views, controllers etc. are created per incoming HTTP
 83request, you will need to initialise a new instance of
 84Kite client per request as well. This is because each
 85individual instance represents a single user that's
 86authenticated, unlike an **admin** API where you may
 87use one instance to manage many users.
 88
 89Hence, in your web application, typically:
 90
 91- You will initialise an instance of the Kite client
 92- Redirect the user to the `login_url()`
 93- At the redirect url endpoint, obtain the
 94`request_token` from the query parameters
 95- Initialise a new instance of Kite client,
 96use `generate_session()` to obtain the `access_token`
 97along with authenticated user data
 98- Store this response in a session and use the
 99stored `access_token` and initialise instances
100of Kite client for subsequent API calls.
101
102Exceptions
103----------
104Kite Connect client saves you the hassle of detecting API errors
105by looking at HTTP codes or JSON error responses. Instead,
106it raises aptly named **[exceptions](exceptions.m.html)** that you can catch.
107"""
108
109from __future__ import unicode_literals, absolute_import
110
111from kiteconnect import exceptions
112from kiteconnect.connect import KiteConnect
113from kiteconnect.ticker import KiteTicker
114
115__all__ = ["KiteConnect", "KiteTicker", "exceptions"]
class KiteConnect:
 28class KiteConnect(object):
 29    """
 30    The Kite Connect API wrapper class.
 31
 32    In production, you may initialise a single instance of this class per `api_key`.
 33    """
 34
 35    # Default root API endpoint. It's possible to
 36    # override this by passing the `root` parameter during initialisation.
 37    _default_root_uri = "https://api.kite.trade"
 38    _default_login_uri = "https://kite.zerodha.com/connect/login"
 39    _default_timeout = 7  # In seconds
 40
 41    # Kite connect header version
 42    kite_header_version = "3"
 43
 44    # Constants
 45    # Products
 46    PRODUCT_MIS = "MIS"
 47    PRODUCT_CNC = "CNC"
 48    PRODUCT_NRML = "NRML"
 49    PRODUCT_CO = "CO"
 50
 51    # Order types
 52    ORDER_TYPE_MARKET = "MARKET"
 53    ORDER_TYPE_LIMIT = "LIMIT"
 54    ORDER_TYPE_SLM = "SL-M"
 55    ORDER_TYPE_SL = "SL"
 56
 57    # Varities
 58    VARIETY_REGULAR = "regular"
 59    VARIETY_CO = "co"
 60    VARIETY_AMO = "amo"
 61    VARIETY_ICEBERG = "iceberg"
 62    VARIETY_AUCTION = "auction"
 63
 64    # Transaction type
 65    TRANSACTION_TYPE_BUY = "BUY"
 66    TRANSACTION_TYPE_SELL = "SELL"
 67
 68    # Validity
 69    VALIDITY_DAY = "DAY"
 70    VALIDITY_IOC = "IOC"
 71    VALIDITY_TTL = "TTL"
 72
 73    # Position Type
 74    POSITION_TYPE_DAY = "day"
 75    POSITION_TYPE_OVERNIGHT = "overnight"
 76
 77    # Exchanges
 78    EXCHANGE_NSE = "NSE"
 79    EXCHANGE_BSE = "BSE"
 80    EXCHANGE_NFO = "NFO"
 81    EXCHANGE_CDS = "CDS"
 82    EXCHANGE_BFO = "BFO"
 83    EXCHANGE_MCX = "MCX"
 84    EXCHANGE_BCD = "BCD"
 85
 86    # Margins segments
 87    MARGIN_EQUITY = "equity"
 88    MARGIN_COMMODITY = "commodity"
 89
 90    # Status constants
 91    STATUS_COMPLETE = "COMPLETE"
 92    STATUS_REJECTED = "REJECTED"
 93    STATUS_CANCELLED = "CANCELLED"
 94
 95    # GTT order type
 96    GTT_TYPE_OCO = "two-leg"
 97    GTT_TYPE_SINGLE = "single"
 98
 99    # GTT order status
100    GTT_STATUS_ACTIVE = "active"
101    GTT_STATUS_TRIGGERED = "triggered"
102    GTT_STATUS_DISABLED = "disabled"
103    GTT_STATUS_EXPIRED = "expired"
104    GTT_STATUS_CANCELLED = "cancelled"
105    GTT_STATUS_REJECTED = "rejected"
106    GTT_STATUS_DELETED = "deleted"
107
108    # URIs to various calls
109    _routes = {
110        "api.token": "/session/token",
111        "api.token.invalidate": "/session/token",
112        "api.token.renew": "/session/refresh_token",
113        "user.profile": "/user/profile",
114        "user.margins": "/user/margins",
115        "user.margins.segment": "/user/margins/{segment}",
116
117        "orders": "/orders",
118        "trades": "/trades",
119
120        "order.info": "/orders/{order_id}",
121        "order.place": "/orders/{variety}",
122        "order.modify": "/orders/{variety}/{order_id}",
123        "order.cancel": "/orders/{variety}/{order_id}",
124        "order.trades": "/orders/{order_id}/trades",
125
126        "portfolio.positions": "/portfolio/positions",
127        "portfolio.holdings": "/portfolio/holdings",
128        "portfolio.holdings.auction": "/portfolio/holdings/auctions",
129        "portfolio.positions.convert": "/portfolio/positions",
130
131        # MF api endpoints
132        "mf.orders": "/mf/orders",
133        "mf.order.info": "/mf/orders/{order_id}",
134        "mf.order.place": "/mf/orders",
135        "mf.order.cancel": "/mf/orders/{order_id}",
136
137        "mf.sips": "/mf/sips",
138        "mf.sip.info": "/mf/sips/{sip_id}",
139        "mf.sip.place": "/mf/sips",
140        "mf.sip.modify": "/mf/sips/{sip_id}",
141        "mf.sip.cancel": "/mf/sips/{sip_id}",
142
143        "mf.holdings": "/mf/holdings",
144        "mf.instruments": "/mf/instruments",
145
146        "market.instruments.all": "/instruments",
147        "market.instruments": "/instruments/{exchange}",
148        "market.margins": "/margins/{segment}",
149        "market.historical": "/instruments/historical/{instrument_token}/{interval}",
150        "market.trigger_range": "/instruments/trigger_range/{transaction_type}",
151
152        "market.quote": "/quote",
153        "market.quote.ohlc": "/quote/ohlc",
154        "market.quote.ltp": "/quote/ltp",
155
156        # GTT endpoints
157        "gtt": "/gtt/triggers",
158        "gtt.place": "/gtt/triggers",
159        "gtt.info": "/gtt/triggers/{trigger_id}",
160        "gtt.modify": "/gtt/triggers/{trigger_id}",
161        "gtt.delete": "/gtt/triggers/{trigger_id}",
162
163        # Margin computation endpoints
164        "order.margins": "/margins/orders",
165        "order.margins.basket": "/margins/basket"
166    }
167
168    def __init__(self,
169                 api_key,
170                 access_token=None,
171                 root=None,
172                 debug=False,
173                 timeout=None,
174                 proxies=None,
175                 pool=None,
176                 disable_ssl=False):
177        """
178        Initialise a new Kite Connect client instance.
179
180        - `api_key` is the key issued to you
181        - `access_token` is the token obtained after the login flow in
182            exchange for the `request_token` . Pre-login, this will default to None,
183        but once you have obtained it, you should
184        persist it in a database or session to pass
185        to the Kite Connect class initialisation for subsequent requests.
186        - `root` is the API end point root. Unless you explicitly
187        want to send API requests to a non-default endpoint, this
188        can be ignored.
189        - `debug`, if set to True, will serialise and print requests
190        and responses to stdout.
191        - `timeout` is the time (seconds) for which the API client will wait for
192        a request to complete before it fails. Defaults to 7 seconds
193        - `proxies` to set requests proxy.
194        Check [python requests documentation](http://docs.python-requests.org/en/master/user/advanced/#proxies) for usage and examples.
195        - `pool` is manages request pools. It takes a dict of params accepted by HTTPAdapter as described here in [python requests documentation](http://docs.python-requests.org/en/master/api/#requests.adapters.HTTPAdapter)
196        - `disable_ssl` disables the SSL verification while making a request.
197        If set requests won't throw SSLError if its set to custom `root` url without SSL.
198        """
199        self.debug = debug
200        self.api_key = api_key
201        self.session_expiry_hook = None
202        self.disable_ssl = disable_ssl
203        self.access_token = access_token
204        self.proxies = proxies if proxies else {}
205
206        self.root = root or self._default_root_uri
207        self.timeout = timeout or self._default_timeout
208
209        # Create requests session by default
210        # Same session to be used by pool connections
211        self.reqsession = requests.Session()
212        if pool:
213            reqadapter = requests.adapters.HTTPAdapter(**pool)
214            self.reqsession.mount("https://", reqadapter)
215
216        # disable requests SSL warning
217        requests.packages.urllib3.disable_warnings()
218
219    def set_session_expiry_hook(self, method):
220        """
221        Set a callback hook for session (`TokenError` -- timeout, expiry etc.) errors.
222
223        An `access_token` (login session) can become invalid for a number of
224        reasons, but it doesn't make sense for the client to
225        try and catch it during every API call.
226
227        A callback method that handles session errors
228        can be set here and when the client encounters
229        a token error at any point, it'll be called.
230
231        This callback, for instance, can log the user out of the UI,
232        clear session cookies, or initiate a fresh login.
233        """
234        if not callable(method):
235            raise TypeError("Invalid input type. Only functions are accepted.")
236
237        self.session_expiry_hook = method
238
239    def set_access_token(self, access_token):
240        """Set the `access_token` received after a successful authentication."""
241        self.access_token = access_token
242
243    def login_url(self):
244        """Get the remote login url to which a user should be redirected to initiate the login flow."""
245        return "%s?api_key=%s&v=%s" % (self._default_login_uri, self.api_key, self.kite_header_version)
246
247    def generate_session(self, request_token, api_secret):
248        """
249        Generate user session details like `access_token` etc by exchanging `request_token`.
250        Access token is automatically set if the session is retrieved successfully.
251
252        Do the token exchange with the `request_token` obtained after the login flow,
253        and retrieve the `access_token` required for all subsequent requests. The
254        response contains not just the `access_token`, but metadata for
255        the user who has authenticated.
256
257        - `request_token` is the token obtained from the GET paramers after a successful login redirect.
258        - `api_secret` is the API api_secret issued with the API key.
259        """
260        h = hashlib.sha256(self.api_key.encode("utf-8") + request_token.encode("utf-8") + api_secret.encode("utf-8"))
261        checksum = h.hexdigest()
262
263        resp = self._post("api.token", params={
264            "api_key": self.api_key,
265            "request_token": request_token,
266            "checksum": checksum
267        })
268
269        if "access_token" in resp:
270            self.set_access_token(resp["access_token"])
271
272        if resp["login_time"] and len(resp["login_time"]) == 19:
273            resp["login_time"] = dateutil.parser.parse(resp["login_time"])
274
275        return resp
276
277    def invalidate_access_token(self, access_token=None):
278        """
279        Kill the session by invalidating the access token.
280
281        - `access_token` to invalidate. Default is the active `access_token`.
282        """
283        access_token = access_token or self.access_token
284        return self._delete("api.token.invalidate", params={
285            "api_key": self.api_key,
286            "access_token": access_token
287        })
288
289    def renew_access_token(self, refresh_token, api_secret):
290        """
291        Renew expired `refresh_token` using valid `refresh_token`.
292
293        - `refresh_token` is the token obtained from previous successful login flow.
294        - `api_secret` is the API api_secret issued with the API key.
295        """
296        h = hashlib.sha256(self.api_key.encode("utf-8") + refresh_token.encode("utf-8") + api_secret.encode("utf-8"))
297        checksum = h.hexdigest()
298
299        resp = self._post("api.token.renew", params={
300            "api_key": self.api_key,
301            "refresh_token": refresh_token,
302            "checksum": checksum
303        })
304
305        if "access_token" in resp:
306            self.set_access_token(resp["access_token"])
307
308        return resp
309
310    def invalidate_refresh_token(self, refresh_token):
311        """
312        Invalidate refresh token.
313
314        - `refresh_token` is the token which is used to renew access token.
315        """
316        return self._delete("api.token.invalidate", params={
317            "api_key": self.api_key,
318            "refresh_token": refresh_token
319        })
320
321    def margins(self, segment=None):
322        """Get account balance and cash margin details for a particular segment.
323
324        - `segment` is the trading segment (eg: equity or commodity)
325        """
326        if segment:
327            return self._get("user.margins.segment", url_args={"segment": segment})
328        else:
329            return self._get("user.margins")
330
331    def profile(self):
332        """Get user profile details."""
333        return self._get("user.profile")
334
335    # orders
336    def place_order(self,
337                    variety,
338                    exchange,
339                    tradingsymbol,
340                    transaction_type,
341                    quantity,
342                    product,
343                    order_type,
344                    price=None,
345                    validity=None,
346                    validity_ttl=None,
347                    disclosed_quantity=None,
348                    trigger_price=None,
349                    iceberg_legs=None,
350                    iceberg_quantity=None,
351                    auction_number=None,
352                    tag=None):
353        """Place an order."""
354        params = locals()
355        del (params["self"])
356
357        for k in list(params.keys()):
358            if params[k] is None:
359                del (params[k])
360
361        return self._post("order.place",
362                          url_args={"variety": variety},
363                          params=params)["order_id"]
364
365    def modify_order(self,
366                     variety,
367                     order_id,
368                     parent_order_id=None,
369                     quantity=None,
370                     price=None,
371                     order_type=None,
372                     trigger_price=None,
373                     validity=None,
374                     disclosed_quantity=None):
375        """Modify an open order."""
376        params = locals()
377        del (params["self"])
378
379        for k in list(params.keys()):
380            if params[k] is None:
381                del (params[k])
382
383        return self._put("order.modify",
384                         url_args={"variety": variety, "order_id": order_id},
385                         params=params)["order_id"]
386
387    def cancel_order(self, variety, order_id, parent_order_id=None):
388        """Cancel an order."""
389        return self._delete("order.cancel",
390                            url_args={"variety": variety, "order_id": order_id},
391                            params={"parent_order_id": parent_order_id})["order_id"]
392
393    def exit_order(self, variety, order_id, parent_order_id=None):
394        """Exit a CO order."""
395        return self.cancel_order(variety, order_id, parent_order_id=parent_order_id)
396
397    def _format_response(self, data):
398        """Parse and format responses."""
399
400        if type(data) == list:
401            _list = data
402        elif type(data) == dict:
403            _list = [data]
404
405        for item in _list:
406            # Convert date time string to datetime object
407            for field in ["order_timestamp", "exchange_timestamp", "created", "last_instalment", "fill_timestamp", "timestamp", "last_trade_time"]:
408                if item.get(field) and len(item[field]) == 19:
409                    item[field] = dateutil.parser.parse(item[field])
410
411        return _list[0] if type(data) == dict else _list
412
413    # orderbook and tradebook
414    def orders(self):
415        """Get list of orders."""
416        return self._format_response(self._get("orders"))
417
418    def order_history(self, order_id):
419        """
420        Get history of individual order.
421
422        - `order_id` is the ID of the order to retrieve order history.
423        """
424        return self._format_response(self._get("order.info", url_args={"order_id": order_id}))
425
426    def trades(self):
427        """
428        Retrieve the list of trades executed (all or ones under a particular order).
429
430        An order can be executed in tranches based on market conditions.
431        These trades are individually recorded under an order.
432        """
433        return self._format_response(self._get("trades"))
434
435    def order_trades(self, order_id):
436        """
437        Retrieve the list of trades executed for a particular order.
438
439        - `order_id` is the ID of the order to retrieve trade history.
440        """
441        return self._format_response(self._get("order.trades", url_args={"order_id": order_id}))
442
443    def positions(self):
444        """Retrieve the list of positions."""
445        return self._get("portfolio.positions")
446
447    def holdings(self):
448        """Retrieve the list of equity holdings."""
449        return self._get("portfolio.holdings")
450
451    def get_auction_instruments(self):
452        """ Retrieves list of available instruments for a auction session """
453        return self._get("portfolio.holdings.auction")
454
455    def convert_position(self,
456                         exchange,
457                         tradingsymbol,
458                         transaction_type,
459                         position_type,
460                         quantity,
461                         old_product,
462                         new_product):
463        """Modify an open position's product type."""
464        return self._put("portfolio.positions.convert", params={
465            "exchange": exchange,
466            "tradingsymbol": tradingsymbol,
467            "transaction_type": transaction_type,
468            "position_type": position_type,
469            "quantity": quantity,
470            "old_product": old_product,
471            "new_product": new_product
472        })
473
474    def mf_orders(self, order_id=None):
475        """Get all mutual fund orders or individual order info."""
476        if order_id:
477            return self._format_response(self._get("mf.order.info", url_args={"order_id": order_id}))
478        else:
479            return self._format_response(self._get("mf.orders"))
480
481    def place_mf_order(self,
482                       tradingsymbol,
483                       transaction_type,
484                       quantity=None,
485                       amount=None,
486                       tag=None):
487        """Place a mutual fund order."""
488        return self._post("mf.order.place", params={
489            "tradingsymbol": tradingsymbol,
490            "transaction_type": transaction_type,
491            "quantity": quantity,
492            "amount": amount,
493            "tag": tag
494        })
495
496    def cancel_mf_order(self, order_id):
497        """Cancel a mutual fund order."""
498        return self._delete("mf.order.cancel", url_args={"order_id": order_id})
499
500    def mf_sips(self, sip_id=None):
501        """Get list of all mutual fund SIP's or individual SIP info."""
502        if sip_id:
503            return self._format_response(self._get("mf.sip.info", url_args={"sip_id": sip_id}))
504        else:
505            return self._format_response(self._get("mf.sips"))
506
507    def place_mf_sip(self,
508                     tradingsymbol,
509                     amount,
510                     instalments,
511                     frequency,
512                     initial_amount=None,
513                     instalment_day=None,
514                     tag=None):
515        """Place a mutual fund SIP."""
516        return self._post("mf.sip.place", params={
517            "tradingsymbol": tradingsymbol,
518            "amount": amount,
519            "initial_amount": initial_amount,
520            "instalments": instalments,
521            "frequency": frequency,
522            "instalment_day": instalment_day,
523            "tag": tag
524        })
525
526    def modify_mf_sip(self,
527                      sip_id,
528                      amount=None,
529                      status=None,
530                      instalments=None,
531                      frequency=None,
532                      instalment_day=None):
533        """Modify a mutual fund SIP."""
534        return self._put("mf.sip.modify",
535                         url_args={"sip_id": sip_id},
536                         params={
537                             "amount": amount,
538                             "status": status,
539                             "instalments": instalments,
540                             "frequency": frequency,
541                             "instalment_day": instalment_day
542                         })
543
544    def cancel_mf_sip(self, sip_id):
545        """Cancel a mutual fund SIP."""
546        return self._delete("mf.sip.cancel", url_args={"sip_id": sip_id})
547
548    def mf_holdings(self):
549        """Get list of mutual fund holdings."""
550        return self._get("mf.holdings")
551
552    def mf_instruments(self):
553        """Get list of mutual fund instruments."""
554        return self._parse_mf_instruments(self._get("mf.instruments"))
555
556    def instruments(self, exchange=None):
557        """
558        Retrieve the list of market instruments available to trade.
559
560        Note that the results could be large, several hundred KBs in size,
561        with tens of thousands of entries in the list.
562
563        - `exchange` is specific exchange to fetch (Optional)
564        """
565        if exchange:
566            return self._parse_instruments(self._get("market.instruments", url_args={"exchange": exchange}))
567        else:
568            return self._parse_instruments(self._get("market.instruments.all"))
569
570    def quote(self, *instruments):
571        """
572        Retrieve quote for list of instruments.
573
574        - `instruments` is a list of instruments, Instrument are in the format of `exchange:tradingsymbol`. For example NSE:INFY
575        """
576        ins = list(instruments)
577
578        # If first element is a list then accept it as instruments list for legacy reason
579        if len(instruments) > 0 and type(instruments[0]) == list:
580            ins = instruments[0]
581
582        data = self._get("market.quote", params={"i": ins})
583        return {key: self._format_response(data[key]) for key in data}
584
585    def ohlc(self, *instruments):
586        """
587        Retrieve OHLC and market depth for list of instruments.
588
589        - `instruments` is a list of instruments, Instrument are in the format of `exchange:tradingsymbol`. For example NSE:INFY
590        """
591        ins = list(instruments)
592
593        # If first element is a list then accept it as instruments list for legacy reason
594        if len(instruments) > 0 and type(instruments[0]) == list:
595            ins = instruments[0]
596
597        return self._get("market.quote.ohlc", params={"i": ins})
598
599    def ltp(self, *instruments):
600        """
601        Retrieve last price for list of instruments.
602
603        - `instruments` is a list of instruments, Instrument are in the format of `exchange:tradingsymbol`. For example NSE:INFY
604        """
605        ins = list(instruments)
606
607        # If first element is a list then accept it as instruments list for legacy reason
608        if len(instruments) > 0 and type(instruments[0]) == list:
609            ins = instruments[0]
610
611        return self._get("market.quote.ltp", params={"i": ins})
612
613    def historical_data(self, instrument_token, from_date, to_date, interval, continuous=False, oi=False):
614        """
615        Retrieve historical data (candles) for an instrument.
616
617        Although the actual response JSON from the API does not have field
618        names such has 'open', 'high' etc., this function call structures
619        the data into an array of objects with field names. For example:
620
621        - `instrument_token` is the instrument identifier (retrieved from the instruments()) call.
622        - `from_date` is the From date (datetime object or string in format of yyyy-mm-dd HH:MM:SS.
623        - `to_date` is the To date (datetime object or string in format of yyyy-mm-dd HH:MM:SS).
624        - `interval` is the candle interval (minute, day, 5 minute etc.).
625        - `continuous` is a boolean flag to get continuous data for futures and options instruments.
626        - `oi` is a boolean flag to get open interest.
627        """
628        date_string_format = "%Y-%m-%d %H:%M:%S"
629        from_date_string = from_date.strftime(date_string_format) if type(from_date) == datetime.datetime else from_date
630        to_date_string = to_date.strftime(date_string_format) if type(to_date) == datetime.datetime else to_date
631
632        data = self._get("market.historical",
633                         url_args={"instrument_token": instrument_token, "interval": interval},
634                         params={
635                             "from": from_date_string,
636                             "to": to_date_string,
637                             "interval": interval,
638                             "continuous": 1 if continuous else 0,
639                             "oi": 1 if oi else 0
640                         })
641
642        return self._format_historical(data)
643
644    def _format_historical(self, data):
645        records = []
646        for d in data["candles"]:
647            record = {
648                "date": dateutil.parser.parse(d[0]),
649                "open": d[1],
650                "high": d[2],
651                "low": d[3],
652                "close": d[4],
653                "volume": d[5],
654            }
655            if len(d) == 7:
656                record["oi"] = d[6]
657            records.append(record)
658
659        return records
660
661    def trigger_range(self, transaction_type, *instruments):
662        """Retrieve the buy/sell trigger range for Cover Orders."""
663        ins = list(instruments)
664
665        # If first element is a list then accept it as instruments list for legacy reason
666        if len(instruments) > 0 and type(instruments[0]) == list:
667            ins = instruments[0]
668
669        return self._get("market.trigger_range",
670                         url_args={"transaction_type": transaction_type.lower()},
671                         params={"i": ins})
672
673    def get_gtts(self):
674        """Fetch list of gtt existing in an account"""
675        return self._get("gtt")
676
677    def get_gtt(self, trigger_id):
678        """Fetch details of a GTT"""
679        return self._get("gtt.info", url_args={"trigger_id": trigger_id})
680
681    def _get_gtt_payload(self, trigger_type, tradingsymbol, exchange, trigger_values, last_price, orders):
682        """Get GTT payload"""
683        if type(trigger_values) != list:
684            raise ex.InputException("invalid type for `trigger_values`")
685        if trigger_type == self.GTT_TYPE_SINGLE and len(trigger_values) != 1:
686            raise ex.InputException("invalid `trigger_values` for single leg order type")
687        elif trigger_type == self.GTT_TYPE_OCO and len(trigger_values) != 2:
688            raise ex.InputException("invalid `trigger_values` for OCO order type")
689
690        condition = {
691            "exchange": exchange,
692            "tradingsymbol": tradingsymbol,
693            "trigger_values": trigger_values,
694            "last_price": last_price,
695        }
696
697        gtt_orders = []
698        for o in orders:
699            # Assert required keys inside gtt order.
700            for req in ["transaction_type", "quantity", "order_type", "product", "price"]:
701                if req not in o:
702                    raise ex.InputException("`{req}` missing inside orders".format(req=req))
703            gtt_orders.append({
704                "exchange": exchange,
705                "tradingsymbol": tradingsymbol,
706                "transaction_type": o["transaction_type"],
707                "quantity": int(o["quantity"]),
708                "order_type": o["order_type"],
709                "product": o["product"],
710                "price": float(o["price"]),
711            })
712
713        return condition, gtt_orders
714
715    def place_gtt(
716        self, trigger_type, tradingsymbol, exchange, trigger_values, last_price, orders
717    ):
718        """
719        Place GTT order
720
721        - `trigger_type` The type of GTT order(single/two-leg).
722        - `tradingsymbol` Trading symbol of the instrument.
723        - `exchange` Name of the exchange.
724        - `trigger_values` Trigger values (json array).
725        - `last_price` Last price of the instrument at the time of order placement.
726        - `orders` JSON order array containing following fields
727            - `transaction_type` BUY or SELL
728            - `quantity` Quantity to transact
729            - `price` The min or max price to execute the order at (for LIMIT orders)
730        """
731        # Validations.
732        assert trigger_type in [self.GTT_TYPE_OCO, self.GTT_TYPE_SINGLE]
733        condition, gtt_orders = self._get_gtt_payload(trigger_type, tradingsymbol, exchange, trigger_values, last_price, orders)
734
735        return self._post("gtt.place", params={
736            "condition": json.dumps(condition),
737            "orders": json.dumps(gtt_orders),
738            "type": trigger_type})
739
740    def modify_gtt(
741        self, trigger_id, trigger_type, tradingsymbol, exchange, trigger_values, last_price, orders
742    ):
743        """
744        Modify GTT order
745
746        - `trigger_type` The type of GTT order(single/two-leg).
747        - `tradingsymbol` Trading symbol of the instrument.
748        - `exchange` Name of the exchange.
749        - `trigger_values` Trigger values (json array).
750        - `last_price` Last price of the instrument at the time of order placement.
751        - `orders` JSON order array containing following fields
752            - `transaction_type` BUY or SELL
753            - `quantity` Quantity to transact
754            - `price` The min or max price to execute the order at (for LIMIT orders)
755        """
756        condition, gtt_orders = self._get_gtt_payload(trigger_type, tradingsymbol, exchange, trigger_values, last_price, orders)
757
758        return self._put("gtt.modify",
759                         url_args={"trigger_id": trigger_id},
760                         params={
761                             "condition": json.dumps(condition),
762                             "orders": json.dumps(gtt_orders),
763                             "type": trigger_type})
764
765    def delete_gtt(self, trigger_id):
766        """Delete a GTT order."""
767        return self._delete("gtt.delete", url_args={"trigger_id": trigger_id})
768
769    def order_margins(self, params):
770        """
771        Calculate margins for requested order list considering the existing positions and open orders
772
773        - `params` is list of orders to retrive margins detail
774        """
775        return self._post("order.margins", params=params, is_json=True)
776
777    def _warn(self, message):
778        """ Add deprecation warning message """
779        warnings.simplefilter('always', DeprecationWarning)
780        warnings.warn(message, DeprecationWarning)
781
782    def _parse_instruments(self, data):
783        # decode to string for Python 3
784        d = data
785        # Decode unicode data
786        if not PY2 and type(d) == bytes:
787            d = data.decode("utf-8").strip()
788
789        records = []
790        reader = csv.DictReader(StringIO(d))
791
792        for row in reader:
793            row["instrument_token"] = int(row["instrument_token"])
794            row["last_price"] = float(row["last_price"])
795            row["strike"] = float(row["strike"])
796            row["tick_size"] = float(row["tick_size"])
797            row["lot_size"] = int(row["lot_size"])
798
799            # Parse date
800            if len(row["expiry"]) == 10:
801                row["expiry"] = dateutil.parser.parse(row["expiry"]).date()
802
803            records.append(row)
804
805        return records
806
807    def _parse_mf_instruments(self, data):
808        # decode to string for Python 3
809        d = data
810        if not PY2 and type(d) == bytes:
811            d = data.decode("utf-8").strip()
812
813        records = []
814        reader = csv.DictReader(StringIO(d))
815
816        for row in reader:
817            row["minimum_purchase_amount"] = float(row["minimum_purchase_amount"])
818            row["purchase_amount_multiplier"] = float(row["purchase_amount_multiplier"])
819            row["minimum_additional_purchase_amount"] = float(row["minimum_additional_purchase_amount"])
820            row["minimum_redemption_quantity"] = float(row["minimum_redemption_quantity"])
821            row["redemption_quantity_multiplier"] = float(row["redemption_quantity_multiplier"])
822            row["purchase_allowed"] = bool(int(row["purchase_allowed"]))
823            row["redemption_allowed"] = bool(int(row["redemption_allowed"]))
824            row["last_price"] = float(row["last_price"])
825
826            # Parse date
827            if len(row["last_price_date"]) == 10:
828                row["last_price_date"] = dateutil.parser.parse(row["last_price_date"]).date()
829
830            records.append(row)
831
832        return records
833
834    def _user_agent(self):
835        return (__title__ + "-python/").capitalize() + __version__
836
837    def _get(self, route, url_args=None, params=None, is_json=False):
838        """Alias for sending a GET request."""
839        return self._request(route, "GET", url_args=url_args, params=params, is_json=is_json)
840
841    def _post(self, route, url_args=None, params=None, is_json=False, query_params=None):
842        """Alias for sending a POST request."""
843        return self._request(route, "POST", url_args=url_args, params=params, is_json=is_json, query_params=query_params)
844
845    def _put(self, route, url_args=None, params=None, is_json=False, query_params=None):
846        """Alias for sending a PUT request."""
847        return self._request(route, "PUT", url_args=url_args, params=params, is_json=is_json, query_params=query_params)
848
849    def _delete(self, route, url_args=None, params=None, is_json=False):
850        """Alias for sending a DELETE request."""
851        return self._request(route, "DELETE", url_args=url_args, params=params, is_json=is_json)
852
853    def _request(self, route, method, url_args=None, params=None, is_json=False, query_params=None):
854        """Make an HTTP request."""
855        # Form a restful URL
856        if url_args:
857            uri = self._routes[route].format(**url_args)
858        else:
859            uri = self._routes[route]
860
861        url = urljoin(self.root, uri)
862
863        # Custom headers
864        headers = {
865            "X-Kite-Version": self.kite_header_version,
866            "User-Agent": self._user_agent()
867        }
868
869        if self.api_key and self.access_token:
870            # set authorization header
871            auth_header = self.api_key + ":" + self.access_token
872            headers["Authorization"] = "token {}".format(auth_header)
873
874        if self.debug:
875            log.debug("Request: {method} {url} {params} {headers}".format(method=method, url=url, params=params, headers=headers))
876
877        # prepare url query params
878        if method in ["GET", "DELETE"]:
879            query_params = params
880
881        try:
882            r = self.reqsession.request(method,
883                                        url,
884                                        json=params if (method in ["POST", "PUT"] and is_json) else None,
885                                        data=params if (method in ["POST", "PUT"] and not is_json) else None,
886                                        params=query_params,
887                                        headers=headers,
888                                        verify=not self.disable_ssl,
889                                        allow_redirects=True,
890                                        timeout=self.timeout,
891                                        proxies=self.proxies)
892        # Any requests lib related exceptions are raised here - https://requests.readthedocs.io/en/latest/api/#exceptions
893        except Exception as e:
894            raise e
895
896        if self.debug:
897            log.debug("Response: {code} {content}".format(code=r.status_code, content=r.content))
898
899        # Validate the content type.
900        if "json" in r.headers["content-type"]:
901            try:
902                data = r.json()
903            except ValueError:
904                raise ex.DataException("Couldn't parse the JSON response received from the server: {content}".format(
905                    content=r.content))
906
907            # api error
908            if data.get("status") == "error" or data.get("error_type"):
909                # Call session hook if its registered and TokenException is raised
910                if self.session_expiry_hook and r.status_code == 403 and data["error_type"] == "TokenException":
911                    self.session_expiry_hook()
912
913                # native Kite errors
914                exp = getattr(ex, data.get("error_type"), ex.GeneralException)
915                raise exp(data["message"], code=r.status_code)
916
917            return data["data"]
918        elif "csv" in r.headers["content-type"]:
919            return r.content
920        else:
921            raise ex.DataException("Unknown Content-Type ({content_type}) with response: ({content})".format(
922                content_type=r.headers["content-type"],
923                content=r.content))

The Kite Connect API wrapper class.

In production, you may initialise a single instance of this class per api_key.

KiteConnect( api_key, access_token=None, root=None, debug=False, timeout=None, proxies=None, pool=None, disable_ssl=False)
168    def __init__(self,
169                 api_key,
170                 access_token=None,
171                 root=None,
172                 debug=False,
173                 timeout=None,
174                 proxies=None,
175                 pool=None,
176                 disable_ssl=False):
177        """
178        Initialise a new Kite Connect client instance.
179
180        - `api_key` is the key issued to you
181        - `access_token` is the token obtained after the login flow in
182            exchange for the `request_token` . Pre-login, this will default to None,
183        but once you have obtained it, you should
184        persist it in a database or session to pass
185        to the Kite Connect class initialisation for subsequent requests.
186        - `root` is the API end point root. Unless you explicitly
187        want to send API requests to a non-default endpoint, this
188        can be ignored.
189        - `debug`, if set to True, will serialise and print requests
190        and responses to stdout.
191        - `timeout` is the time (seconds) for which the API client will wait for
192        a request to complete before it fails. Defaults to 7 seconds
193        - `proxies` to set requests proxy.
194        Check [python requests documentation](http://docs.python-requests.org/en/master/user/advanced/#proxies) for usage and examples.
195        - `pool` is manages request pools. It takes a dict of params accepted by HTTPAdapter as described here in [python requests documentation](http://docs.python-requests.org/en/master/api/#requests.adapters.HTTPAdapter)
196        - `disable_ssl` disables the SSL verification while making a request.
197        If set requests won't throw SSLError if its set to custom `root` url without SSL.
198        """
199        self.debug = debug
200        self.api_key = api_key
201        self.session_expiry_hook = None
202        self.disable_ssl = disable_ssl
203        self.access_token = access_token
204        self.proxies = proxies if proxies else {}
205
206        self.root = root or self._default_root_uri
207        self.timeout = timeout or self._default_timeout
208
209        # Create requests session by default
210        # Same session to be used by pool connections
211        self.reqsession = requests.Session()
212        if pool:
213            reqadapter = requests.adapters.HTTPAdapter(**pool)
214            self.reqsession.mount("https://", reqadapter)
215
216        # disable requests SSL warning
217        requests.packages.urllib3.disable_warnings()

Initialise a new Kite Connect client instance.

  • api_key is the key issued to you
  • access_token is the token obtained after the login flow in exchange for the request_token . Pre-login, this will default to None, 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.
  • root is the API end point root. Unless you explicitly want to send API requests to a non-default endpoint, this can be ignored.
  • debug, if set to True, will serialise and print requests and responses to stdout.
  • timeout is the time (seconds) for which the API client will wait for a request to complete before it fails. Defaults to 7 seconds
  • proxies to set requests proxy. Check python requests documentation for usage and examples.
  • pool is manages request pools. It takes a dict of params accepted by HTTPAdapter as described here in python requests documentation
  • disable_ssl disables the SSL verification while making a request. If set requests won't throw SSLError if its set to custom root url without SSL.
def set_session_expiry_hook(self, method):
219    def set_session_expiry_hook(self, method):
220        """
221        Set a callback hook for session (`TokenError` -- timeout, expiry etc.) errors.
222
223        An `access_token` (login session) can become invalid for a number of
224        reasons, but it doesn't make sense for the client to
225        try and catch it during every API call.
226
227        A callback method that handles session errors
228        can be set here and when the client encounters
229        a token error at any point, it'll be called.
230
231        This callback, for instance, can log the user out of the UI,
232        clear session cookies, or initiate a fresh login.
233        """
234        if not callable(method):
235            raise TypeError("Invalid input type. Only functions are accepted.")
236
237        self.session_expiry_hook = method

Set a callback hook for session (TokenError -- timeout, expiry etc.) errors.

An 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.

def set_access_token(self, access_token):
239    def set_access_token(self, access_token):
240        """Set the `access_token` received after a successful authentication."""
241        self.access_token = access_token

Set the access_token received after a successful authentication.

def login_url(self):
243    def login_url(self):
244        """Get the remote login url to which a user should be redirected to initiate the login flow."""
245        return "%s?api_key=%s&v=%s" % (self._default_login_uri, self.api_key, self.kite_header_version)

Get the remote login url to which a user should be redirected to initiate the login flow.

def generate_session(self, request_token, api_secret):
247    def generate_session(self, request_token, api_secret):
248        """
249        Generate user session details like `access_token` etc by exchanging `request_token`.
250        Access token is automatically set if the session is retrieved successfully.
251
252        Do the token exchange with the `request_token` obtained after the login flow,
253        and retrieve the `access_token` required for all subsequent requests. The
254        response contains not just the `access_token`, but metadata for
255        the user who has authenticated.
256
257        - `request_token` is the token obtained from the GET paramers after a successful login redirect.
258        - `api_secret` is the API api_secret issued with the API key.
259        """
260        h = hashlib.sha256(self.api_key.encode("utf-8") + request_token.encode("utf-8") + api_secret.encode("utf-8"))
261        checksum = h.hexdigest()
262
263        resp = self._post("api.token", params={
264            "api_key": self.api_key,
265            "request_token": request_token,
266            "checksum": checksum
267        })
268
269        if "access_token" in resp:
270            self.set_access_token(resp["access_token"])
271
272        if resp["login_time"] and len(resp["login_time"]) == 19:
273            resp["login_time"] = dateutil.parser.parse(resp["login_time"])
274
275        return resp

Generate user session details like access_token etc by exchanging request_token. Access token is automatically set if the session is retrieved successfully.

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.

  • request_token is the token obtained from the GET paramers after a successful login redirect.
  • api_secret is the API api_secret issued with the API key.
def invalidate_access_token(self, access_token=None):
277    def invalidate_access_token(self, access_token=None):
278        """
279        Kill the session by invalidating the access token.
280
281        - `access_token` to invalidate. Default is the active `access_token`.
282        """
283        access_token = access_token or self.access_token
284        return self._delete("api.token.invalidate", params={
285            "api_key": self.api_key,
286            "access_token": access_token
287        })

Kill the session by invalidating the access token.

  • access_token to invalidate. Default is the active access_token.
def renew_access_token(self, refresh_token, api_secret):
289    def renew_access_token(self, refresh_token, api_secret):
290        """
291        Renew expired `refresh_token` using valid `refresh_token`.
292
293        - `refresh_token` is the token obtained from previous successful login flow.
294        - `api_secret` is the API api_secret issued with the API key.
295        """
296        h = hashlib.sha256(self.api_key.encode("utf-8") + refresh_token.encode("utf-8") + api_secret.encode("utf-8"))
297        checksum = h.hexdigest()
298
299        resp = self._post("api.token.renew", params={
300            "api_key": self.api_key,
301            "refresh_token": refresh_token,
302            "checksum": checksum
303        })
304
305        if "access_token" in resp:
306            self.set_access_token(resp["access_token"])
307
308        return resp

Renew expired refresh_token using valid refresh_token.

  • refresh_token is the token obtained from previous successful login flow.
  • api_secret is the API api_secret issued with the API key.
def invalidate_refresh_token(self, refresh_token):
310    def invalidate_refresh_token(self, refresh_token):
311        """
312        Invalidate refresh token.
313
314        - `refresh_token` is the token which is used to renew access token.
315        """
316        return self._delete("api.token.invalidate", params={
317            "api_key": self.api_key,
318            "refresh_token": refresh_token
319        })

Invalidate refresh token.

  • refresh_token is the token which is used to renew access token.
def margins(self, segment=None):
321    def margins(self, segment=None):
322        """Get account balance and cash margin details for a particular segment.
323
324        - `segment` is the trading segment (eg: equity or commodity)
325        """
326        if segment:
327            return self._get("user.margins.segment", url_args={"segment": segment})
328        else:
329            return self._get("user.margins")

Get account balance and cash margin details for a particular segment.

  • segment is the trading segment (eg: equity or commodity)
def profile(self):
331    def profile(self):
332        """Get user profile details."""
333        return self._get("user.profile")

Get user profile details.

def place_order( self, variety, exchange, tradingsymbol, transaction_type, quantity, product, order_type, price=None, validity=None, validity_ttl=None, disclosed_quantity=None, trigger_price=None, iceberg_legs=None, iceberg_quantity=None, auction_number=None, tag=None):
336    def place_order(self,
337                    variety,
338                    exchange,
339                    tradingsymbol,
340                    transaction_type,
341                    quantity,
342                    product,
343                    order_type,
344                    price=None,
345                    validity=None,
346                    validity_ttl=None,
347                    disclosed_quantity=None,
348                    trigger_price=None,
349                    iceberg_legs=None,
350                    iceberg_quantity=None,
351                    auction_number=None,
352                    tag=None):
353        """Place an order."""
354        params = locals()
355        del (params["self"])
356
357        for k in list(params.keys()):
358            if params[k] is None:
359                del (params[k])
360
361        return self._post("order.place",
362                          url_args={"variety": variety},
363                          params=params)["order_id"]

Place an order.

def modify_order( self, variety, order_id, parent_order_id=None, quantity=None, price=None, order_type=None, trigger_price=None, validity=None, disclosed_quantity=None):
365    def modify_order(self,
366                     variety,
367                     order_id,
368                     parent_order_id=None,
369                     quantity=None,
370                     price=None,
371                     order_type=None,
372                     trigger_price=None,
373                     validity=None,
374                     disclosed_quantity=None):
375        """Modify an open order."""
376        params = locals()
377        del (params["self"])
378
379        for k in list(params.keys()):
380            if params[k] is None:
381                del (params[k])
382
383        return self._put("order.modify",
384                         url_args={"variety": variety, "order_id": order_id},
385                         params=params)["order_id"]

Modify an open order.

def cancel_order(self, variety, order_id, parent_order_id=None):
387    def cancel_order(self, variety, order_id, parent_order_id=None):
388        """Cancel an order."""
389        return self._delete("order.cancel",
390                            url_args={"variety": variety, "order_id": order_id},
391                            params={"parent_order_id": parent_order_id})["order_id"]

Cancel an order.

def exit_order(self, variety, order_id, parent_order_id=None):
393    def exit_order(self, variety, order_id, parent_order_id=None):
394        """Exit a CO order."""
395        return self.cancel_order(variety, order_id, parent_order_id=parent_order_id)

Exit a CO order.

def orders(self):
414    def orders(self):
415        """Get list of orders."""
416        return self._format_response(self._get("orders"))

Get list of orders.

def order_history(self, order_id):
418    def order_history(self, order_id):
419        """
420        Get history of individual order.
421
422        - `order_id` is the ID of the order to retrieve order history.
423        """
424        return self._format_response(self._get("order.info", url_args={"order_id": order_id}))

Get history of individual order.

  • order_id is the ID of the order to retrieve order history.
def trades(self):
426    def trades(self):
427        """
428        Retrieve the list of trades executed (all or ones under a particular order).
429
430        An order can be executed in tranches based on market conditions.
431        These trades are individually recorded under an order.
432        """
433        return self._format_response(self._get("trades"))

Retrieve the list of trades executed (all or ones under a particular order).

An order can be executed in tranches based on market conditions. These trades are individually recorded under an order.

def order_trades(self, order_id):
435    def order_trades(self, order_id):
436        """
437        Retrieve the list of trades executed for a particular order.
438
439        - `order_id` is the ID of the order to retrieve trade history.
440        """
441        return self._format_response(self._get("order.trades", url_args={"order_id": order_id}))

Retrieve the list of trades executed for a particular order.

  • order_id is the ID of the order to retrieve trade history.
def positions(self):
443    def positions(self):
444        """Retrieve the list of positions."""
445        return self._get("portfolio.positions")

Retrieve the list of positions.

def holdings(self):
447    def holdings(self):
448        """Retrieve the list of equity holdings."""
449        return self._get("portfolio.holdings")

Retrieve the list of equity holdings.

def get_auction_instruments(self):
451    def get_auction_instruments(self):
452        """ Retrieves list of available instruments for a auction session """
453        return self._get("portfolio.holdings.auction")

Retrieves list of available instruments for a auction session

def convert_position( self, exchange, tradingsymbol, transaction_type, position_type, quantity, old_product, new_product):
455    def convert_position(self,
456                         exchange,
457                         tradingsymbol,
458                         transaction_type,
459                         position_type,
460                         quantity,
461                         old_product,
462                         new_product):
463        """Modify an open position's product type."""
464        return self._put("portfolio.positions.convert", params={
465            "exchange": exchange,
466            "tradingsymbol": tradingsymbol,
467            "transaction_type": transaction_type,
468            "position_type": position_type,
469            "quantity": quantity,
470            "old_product": old_product,
471            "new_product": new_product
472        })

Modify an open position's product type.

def mf_orders(self, order_id=None):
474    def mf_orders(self, order_id=None):
475        """Get all mutual fund orders or individual order info."""
476        if order_id:
477            return self._format_response(self._get("mf.order.info", url_args={"order_id": order_id}))
478        else:
479            return self._format_response(self._get("mf.orders"))

Get all mutual fund orders or individual order info.

def place_mf_order( self, tradingsymbol, transaction_type, quantity=None, amount=None, tag=None):
481    def place_mf_order(self,
482                       tradingsymbol,
483                       transaction_type,
484                       quantity=None,
485                       amount=None,
486                       tag=None):
487        """Place a mutual fund order."""
488        return self._post("mf.order.place", params={
489            "tradingsymbol": tradingsymbol,
490            "transaction_type": transaction_type,
491            "quantity": quantity,
492            "amount": amount,
493            "tag": tag
494        })

Place a mutual fund order.

def cancel_mf_order(self, order_id):
496    def cancel_mf_order(self, order_id):
497        """Cancel a mutual fund order."""
498        return self._delete("mf.order.cancel", url_args={"order_id": order_id})

Cancel a mutual fund order.

def mf_sips(self, sip_id=None):
500    def mf_sips(self, sip_id=None):
501        """Get list of all mutual fund SIP's or individual SIP info."""
502        if sip_id:
503            return self._format_response(self._get("mf.sip.info", url_args={"sip_id": sip_id}))
504        else:
505            return self._format_response(self._get("mf.sips"))

Get list of all mutual fund SIP's or individual SIP info.

def place_mf_sip( self, tradingsymbol, amount, instalments, frequency, initial_amount=None, instalment_day=None, tag=None):
507    def place_mf_sip(self,
508                     tradingsymbol,
509                     amount,
510                     instalments,
511                     frequency,
512                     initial_amount=None,
513                     instalment_day=None,
514                     tag=None):
515        """Place a mutual fund SIP."""
516        return self._post("mf.sip.place", params={
517            "tradingsymbol": tradingsymbol,
518            "amount": amount,
519            "initial_amount": initial_amount,
520            "instalments": instalments,
521            "frequency": frequency,
522            "instalment_day": instalment_day,
523            "tag": tag
524        })

Place a mutual fund SIP.

def modify_mf_sip( self, sip_id, amount=None, status=None, instalments=None, frequency=None, instalment_day=None):
526    def modify_mf_sip(self,
527                      sip_id,
528                      amount=None,
529                      status=None,
530                      instalments=None,
531                      frequency=None,
532                      instalment_day=None):
533        """Modify a mutual fund SIP."""
534        return self._put("mf.sip.modify",
535                         url_args={"sip_id": sip_id},
536                         params={
537                             "amount": amount,
538                             "status": status,
539                             "instalments": instalments,
540                             "frequency": frequency,
541                             "instalment_day": instalment_day
542                         })

Modify a mutual fund SIP.

def cancel_mf_sip(self, sip_id):
544    def cancel_mf_sip(self, sip_id):
545        """Cancel a mutual fund SIP."""
546        return self._delete("mf.sip.cancel", url_args={"sip_id": sip_id})

Cancel a mutual fund SIP.

def mf_holdings(self):
548    def mf_holdings(self):
549        """Get list of mutual fund holdings."""
550        return self._get("mf.holdings")

Get list of mutual fund holdings.

def mf_instruments(self):
552    def mf_instruments(self):
553        """Get list of mutual fund instruments."""
554        return self._parse_mf_instruments(self._get("mf.instruments"))

Get list of mutual fund instruments.

def instruments(self, exchange=None):
556    def instruments(self, exchange=None):
557        """
558        Retrieve the list of market instruments available to trade.
559
560        Note that the results could be large, several hundred KBs in size,
561        with tens of thousands of entries in the list.
562
563        - `exchange` is specific exchange to fetch (Optional)
564        """
565        if exchange:
566            return self._parse_instruments(self._get("market.instruments", url_args={"exchange": exchange}))
567        else:
568            return self._parse_instruments(self._get("market.instruments.all"))

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.

  • exchange is specific exchange to fetch (Optional)
def quote(self, *instruments):
570    def quote(self, *instruments):
571        """
572        Retrieve quote for list of instruments.
573
574        - `instruments` is a list of instruments, Instrument are in the format of `exchange:tradingsymbol`. For example NSE:INFY
575        """
576        ins = list(instruments)
577
578        # If first element is a list then accept it as instruments list for legacy reason
579        if len(instruments) > 0 and type(instruments[0]) == list:
580            ins = instruments[0]
581
582        data = self._get("market.quote", params={"i": ins})
583        return {key: self._format_response(data[key]) for key in data}

Retrieve quote for list of instruments.

  • instruments is a list of instruments, Instrument are in the format of exchange:tradingsymbol. For example NSE:INFY
def ohlc(self, *instruments):
585    def ohlc(self, *instruments):
586        """
587        Retrieve OHLC and market depth for list of instruments.
588
589        - `instruments` is a list of instruments, Instrument are in the format of `exchange:tradingsymbol`. For example NSE:INFY
590        """
591        ins = list(instruments)
592
593        # If first element is a list then accept it as instruments list for legacy reason
594        if len(instruments) > 0 and type(instruments[0]) == list:
595            ins = instruments[0]
596
597        return self._get("market.quote.ohlc", params={"i": ins})

Retrieve OHLC and market depth for list of instruments.

  • instruments is a list of instruments, Instrument are in the format of exchange:tradingsymbol. For example NSE:INFY
def ltp(self, *instruments):
599    def ltp(self, *instruments):
600        """
601        Retrieve last price for list of instruments.
602
603        - `instruments` is a list of instruments, Instrument are in the format of `exchange:tradingsymbol`. For example NSE:INFY
604        """
605        ins = list(instruments)
606
607        # If first element is a list then accept it as instruments list for legacy reason
608        if len(instruments) > 0 and type(instruments[0]) == list:
609            ins = instruments[0]
610
611        return self._get("market.quote.ltp", params={"i": ins})

Retrieve last price for list of instruments.

  • instruments is a list of instruments, Instrument are in the format of exchange:tradingsymbol. For example NSE:INFY
def historical_data( self, instrument_token, from_date, to_date, interval, continuous=False, oi=False):
613    def historical_data(self, instrument_token, from_date, to_date, interval, continuous=False, oi=False):
614        """
615        Retrieve historical data (candles) for an instrument.
616
617        Although the actual response JSON from the API does not have field
618        names such has 'open', 'high' etc., this function call structures
619        the data into an array of objects with field names. For example:
620
621        - `instrument_token` is the instrument identifier (retrieved from the instruments()) call.
622        - `from_date` is the From date (datetime object or string in format of yyyy-mm-dd HH:MM:SS.
623        - `to_date` is the To date (datetime object or string in format of yyyy-mm-dd HH:MM:SS).
624        - `interval` is the candle interval (minute, day, 5 minute etc.).
625        - `continuous` is a boolean flag to get continuous data for futures and options instruments.
626        - `oi` is a boolean flag to get open interest.
627        """
628        date_string_format = "%Y-%m-%d %H:%M:%S"
629        from_date_string = from_date.strftime(date_string_format) if type(from_date) == datetime.datetime else from_date
630        to_date_string = to_date.strftime(date_string_format) if type(to_date) == datetime.datetime else to_date
631
632        data = self._get("market.historical",
633                         url_args={"instrument_token": instrument_token, "interval": interval},
634                         params={
635                             "from": from_date_string,
636                             "to": to_date_string,
637                             "interval": interval,
638                             "continuous": 1 if continuous else 0,
639                             "oi": 1 if oi else 0
640                         })
641
642        return self._format_historical(data)

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 function call structures the data into an array of objects with field names. For example:

  • instrument_token is the instrument identifier (retrieved from the instruments()) call.
  • from_date is the From date (datetime object or string in format of yyyy-mm-dd HH:MM:SS.
  • to_date is the To date (datetime object or string in format of yyyy-mm-dd HH:MM:SS).
  • interval is the candle interval (minute, day, 5 minute etc.).
  • continuous is a boolean flag to get continuous data for futures and options instruments.
  • oi is a boolean flag to get open interest.
def trigger_range(self, transaction_type, *instruments):
661    def trigger_range(self, transaction_type, *instruments):
662        """Retrieve the buy/sell trigger range for Cover Orders."""
663        ins = list(instruments)
664
665        # If first element is a list then accept it as instruments list for legacy reason
666        if len(instruments) > 0 and type(instruments[0]) == list:
667            ins = instruments[0]
668
669        return self._get("market.trigger_range",
670                         url_args={"transaction_type": transaction_type.lower()},
671                         params={"i": ins})

Retrieve the buy/sell trigger range for Cover Orders.

def get_gtts(self):
673    def get_gtts(self):
674        """Fetch list of gtt existing in an account"""
675        return self._get("gtt")

Fetch list of gtt existing in an account

def get_gtt(self, trigger_id):
677    def get_gtt(self, trigger_id):
678        """Fetch details of a GTT"""
679        return self._get("gtt.info", url_args={"trigger_id": trigger_id})

Fetch details of a GTT

def place_gtt( self, trigger_type, tradingsymbol, exchange, trigger_values, last_price, orders):
715    def place_gtt(
716        self, trigger_type, tradingsymbol, exchange, trigger_values, last_price, orders
717    ):
718        """
719        Place GTT order
720
721        - `trigger_type` The type of GTT order(single/two-leg).
722        - `tradingsymbol` Trading symbol of the instrument.
723        - `exchange` Name of the exchange.
724        - `trigger_values` Trigger values (json array).
725        - `last_price` Last price of the instrument at the time of order placement.
726        - `orders` JSON order array containing following fields
727            - `transaction_type` BUY or SELL
728            - `quantity` Quantity to transact
729            - `price` The min or max price to execute the order at (for LIMIT orders)
730        """
731        # Validations.
732        assert trigger_type in [self.GTT_TYPE_OCO, self.GTT_TYPE_SINGLE]
733        condition, gtt_orders = self._get_gtt_payload(trigger_type, tradingsymbol, exchange, trigger_values, last_price, orders)
734
735        return self._post("gtt.place", params={
736            "condition": json.dumps(condition),
737            "orders": json.dumps(gtt_orders),
738            "type": trigger_type})

Place GTT order

  • trigger_type The type of GTT order(single/two-leg).
  • tradingsymbol Trading symbol of the instrument.
  • exchange Name of the exchange.
  • trigger_values Trigger values (json array).
  • last_price Last price of the instrument at the time of order placement.
  • orders JSON order array containing following fields
    • transaction_type BUY or SELL
    • quantity Quantity to transact
    • price The min or max price to execute the order at (for LIMIT orders)
def modify_gtt( self, trigger_id, trigger_type, tradingsymbol, exchange, trigger_values, last_price, orders):
740    def modify_gtt(
741        self, trigger_id, trigger_type, tradingsymbol, exchange, trigger_values, last_price, orders
742    ):
743        """
744        Modify GTT order
745
746        - `trigger_type` The type of GTT order(single/two-leg).
747        - `tradingsymbol` Trading symbol of the instrument.
748        - `exchange` Name of the exchange.
749        - `trigger_values` Trigger values (json array).
750        - `last_price` Last price of the instrument at the time of order placement.
751        - `orders` JSON order array containing following fields
752            - `transaction_type` BUY or SELL
753            - `quantity` Quantity to transact
754            - `price` The min or max price to execute the order at (for LIMIT orders)
755        """
756        condition, gtt_orders = self._get_gtt_payload(trigger_type, tradingsymbol, exchange, trigger_values, last_price, orders)
757
758        return self._put("gtt.modify",
759                         url_args={"trigger_id": trigger_id},
760                         params={
761                             "condition": json.dumps(condition),
762                             "orders": json.dumps(gtt_orders),
763                             "type": trigger_type})

Modify GTT order

  • trigger_type The type of GTT order(single/two-leg).
  • tradingsymbol Trading symbol of the instrument.
  • exchange Name of the exchange.
  • trigger_values Trigger values (json array).
  • last_price Last price of the instrument at the time of order placement.
  • orders JSON order array containing following fields
    • transaction_type BUY or SELL
    • quantity Quantity to transact
    • price The min or max price to execute the order at (for LIMIT orders)
def delete_gtt(self, trigger_id):
765    def delete_gtt(self, trigger_id):
766        """Delete a GTT order."""
767        return self._delete("gtt.delete", url_args={"trigger_id": trigger_id})

Delete a GTT order.

def order_margins(self, params):
769    def order_margins(self, params):
770        """
771        Calculate margins for requested order list considering the existing positions and open orders
772
773        - `params` is list of orders to retrive margins detail
774        """
775        return self._post("order.margins", params=params, is_json=True)

Calculate margins for requested order list considering the existing positions and open orders

  • params is list of orders to retrive margins detail
class KiteTicker:
209class KiteTicker(object):
210    """
211    The WebSocket client for connecting to Kite Connect's streaming quotes service.
212
213    Getting started:
214    ---------------
215        #!python
216        import logging
217        from kiteconnect import KiteTicker
218
219        logging.basicConfig(level=logging.DEBUG)
220
221        # Initialise
222        kws = KiteTicker("your_api_key", "your_access_token")
223
224        def on_ticks(ws, ticks):
225            # Callback to receive ticks.
226            logging.debug("Ticks: {}".format(ticks))
227
228        def on_connect(ws, response):
229            # Callback on successful connect.
230            # Subscribe to a list of instrument_tokens (RELIANCE and ACC here).
231            ws.subscribe([738561, 5633])
232
233            # Set RELIANCE to tick in `full` mode.
234            ws.set_mode(ws.MODE_FULL, [738561])
235
236        def on_close(ws, code, reason):
237            # On connection close stop the event loop.
238            # Reconnection will not happen after executing `ws.stop()`
239            ws.stop()
240
241        # Assign the callbacks.
242        kws.on_ticks = on_ticks
243        kws.on_connect = on_connect
244        kws.on_close = on_close
245
246        # Infinite loop on the main thread. Nothing after this will run.
247        # You have to use the pre-defined callbacks to manage subscriptions.
248        kws.connect()
249
250    Callbacks
251    ---------
252    In below examples `ws` is the currently initialised WebSocket object.
253
254    - `on_ticks(ws, ticks)` -  Triggered when ticks are recevied.
255        - `ticks` - List of `tick` object. Check below for sample structure.
256    - `on_close(ws, code, reason)` -  Triggered when connection is closed.
257        - `code` - WebSocket standard close event code (https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent)
258        - `reason` - DOMString indicating the reason the server closed the connection
259    - `on_error(ws, code, reason)` -  Triggered when connection is closed with an error.
260        - `code` - WebSocket standard close event code (https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent)
261        - `reason` - DOMString indicating the reason the server closed the connection
262    - `on_connect` -  Triggered when connection is established successfully.
263        - `response` - Response received from server on successful connection.
264    - `on_message(ws, payload, is_binary)` -  Triggered when message is received from the server.
265        - `payload` - Raw response from the server (either text or binary).
266        - `is_binary` - Bool to check if response is binary type.
267    - `on_reconnect(ws, attempts_count)` -  Triggered when auto reconnection is attempted.
268        - `attempts_count` - Current reconnect attempt number.
269    - `on_noreconnect(ws)` -  Triggered when number of auto reconnection attempts exceeds `reconnect_tries`.
270    - `on_order_update(ws, data)` -  Triggered when there is an order update for the connected user.
271
272
273    Tick structure (passed to the `on_ticks` callback)
274    ---------------------------
275        [{
276            'instrument_token': 53490439,
277            'mode': 'full',
278            'volume_traded': 12510,
279            'last_price': 4084.0,
280            'average_traded_price': 4086.55,
281            'last_traded_quantity': 1,
282            'total_buy_quantity': 2356
283            'total_sell_quantity': 2440,
284            'change': 0.46740467404674046,
285            'last_trade_time': datetime.datetime(2018, 1, 15, 13, 16, 54),
286            'exchange_timestamp': datetime.datetime(2018, 1, 15, 13, 16, 56),
287            'oi': 21845,
288            'oi_day_low': 0,
289            'oi_day_high': 0,
290            'ohlc': {
291                'high': 4093.0,
292                'close': 4065.0,
293                'open': 4088.0,
294                'low': 4080.0
295            },
296            'tradable': True,
297            'depth': {
298                'sell': [{
299                    'price': 4085.0,
300                    'orders': 1048576,
301                    'quantity': 43
302                }, {
303                    'price': 4086.0,
304                    'orders': 2752512,
305                    'quantity': 134
306                }, {
307                    'price': 4087.0,
308                    'orders': 1703936,
309                    'quantity': 133
310                }, {
311                    'price': 4088.0,
312                    'orders': 1376256,
313                    'quantity': 70
314                }, {
315                    'price': 4089.0,
316                    'orders': 1048576,
317                    'quantity': 46
318                }],
319                'buy': [{
320                    'price': 4084.0,
321                    'orders': 589824,
322                    'quantity': 53
323                }, {
324                    'price': 4083.0,
325                    'orders': 1245184,
326                    'quantity': 145
327                }, {
328                    'price': 4082.0,
329                    'orders': 1114112,
330                    'quantity': 63
331                }, {
332                    'price': 4081.0,
333                    'orders': 1835008,
334                    'quantity': 69
335                }, {
336                    'price': 4080.0,
337                    'orders': 2752512,
338                    'quantity': 89
339                }]
340            }
341        },
342        ...,
343        ...]
344
345    Auto reconnection
346    -----------------
347
348    Auto reconnection is enabled by default and it can be disabled by passing `reconnect` param while initialising `KiteTicker`.
349    On a side note, reconnection mechanism cannot happen if event loop is terminated using `stop` method inside `on_close` callback.
350
351    Auto reonnection mechanism is based on [Exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff) algorithm in which
352    next retry interval will be increased exponentially. `reconnect_max_delay` and `reconnect_max_tries` params can be used to tewak
353    the alogrithm where `reconnect_max_delay` is the maximum delay after which subsequent reconnection interval will become constant and
354    `reconnect_max_tries` is maximum number of retries before its quiting reconnection.
355
356    For example if `reconnect_max_delay` is 60 seconds and `reconnect_max_tries` is 50 then the first reconnection interval starts from
357    minimum interval which is 2 seconds and keep increasing up to 60 seconds after which it becomes constant and when reconnection attempt
358    is reached upto 50 then it stops reconnecting.
359
360    method `stop_retry` can be used to stop ongoing reconnect attempts and `on_reconnect` callback will be called with current reconnect
361    attempt and `on_noreconnect` is called when reconnection attempts reaches max retries.
362    """
363
364    EXCHANGE_MAP = {
365        "nse": 1,
366        "nfo": 2,
367        "cds": 3,
368        "bse": 4,
369        "bfo": 5,
370        "bcd": 6,
371        "mcx": 7,
372        "mcxsx": 8,
373        "indices": 9,
374        # bsecds is replaced with it's official segment name bcd
375        # so,bsecds key will be depreciated in next version
376        "bsecds": 6,
377    }
378
379    # Default connection timeout
380    CONNECT_TIMEOUT = 30
381    # Default Reconnect max delay.
382    RECONNECT_MAX_DELAY = 60
383    # Default reconnect attempts
384    RECONNECT_MAX_TRIES = 50
385    # Default root API endpoint. It's possible to
386    # override this by passing the `root` parameter during initialisation.
387    ROOT_URI = "wss://ws.kite.trade"
388
389    # Available streaming modes.
390    MODE_FULL = "full"
391    MODE_QUOTE = "quote"
392    MODE_LTP = "ltp"
393
394    # Flag to set if its first connect
395    _is_first_connect = True
396
397    # Available actions.
398    _message_code = 11
399    _message_subscribe = "subscribe"
400    _message_unsubscribe = "unsubscribe"
401    _message_setmode = "mode"
402
403    # Minimum delay which should be set between retries. User can't set less than this
404    _minimum_reconnect_max_delay = 5
405    # Maximum number or retries user can set
406    _maximum_reconnect_max_tries = 300
407
408    def __init__(self, api_key, access_token, debug=False, root=None,
409                 reconnect=True, reconnect_max_tries=RECONNECT_MAX_TRIES, reconnect_max_delay=RECONNECT_MAX_DELAY,
410                 connect_timeout=CONNECT_TIMEOUT):
411        """
412        Initialise websocket client instance.
413
414        - `api_key` is the API key issued to you
415        - `access_token` is the token obtained after the login flow in
416            exchange for the `request_token`. Pre-login, this will default to None,
417            but once you have obtained it, you should
418            persist it in a database or session to pass
419            to the Kite Connect class initialisation for subsequent requests.
420        - `root` is the websocket API end point root. Unless you explicitly
421            want to send API requests to a non-default endpoint, this
422            can be ignored.
423        - `reconnect` is a boolean to enable WebSocket autreconnect in case of network failure/disconnection.
424        - `reconnect_max_delay` in seconds is the maximum delay after which subsequent reconnection interval will become constant. Defaults to 60s and minimum acceptable value is 5s.
425        - `reconnect_max_tries` is maximum number reconnection attempts. Defaults to 50 attempts and maximum up to 300 attempts.
426        - `connect_timeout` in seconds is the maximum interval after which connection is considered as timeout. Defaults to 30s.
427        """
428        self.root = root or self.ROOT_URI
429
430        # Set max reconnect tries
431        if reconnect_max_tries > self._maximum_reconnect_max_tries:
432            log.warning("`reconnect_max_tries` can not be more than {val}. Setting to highest possible value - {val}.".format(
433                val=self._maximum_reconnect_max_tries))
434            self.reconnect_max_tries = self._maximum_reconnect_max_tries
435        else:
436            self.reconnect_max_tries = reconnect_max_tries
437
438        # Set max reconnect delay
439        if reconnect_max_delay < self._minimum_reconnect_max_delay:
440            log.warning("`reconnect_max_delay` can not be less than {val}. Setting to lowest possible value - {val}.".format(
441                val=self._minimum_reconnect_max_delay))
442            self.reconnect_max_delay = self._minimum_reconnect_max_delay
443        else:
444            self.reconnect_max_delay = reconnect_max_delay
445
446        self.connect_timeout = connect_timeout
447
448        self.socket_url = "{root}?api_key={api_key}"\
449            "&access_token={access_token}".format(
450                root=self.root,
451                api_key=api_key,
452                access_token=access_token
453            )
454
455        # Debug enables logs
456        self.debug = debug
457
458        # Initialize default value for websocket object
459        self.ws = None
460
461        # Placeholders for callbacks.
462        self.on_ticks = None
463        self.on_open = None
464        self.on_close = None
465        self.on_error = None
466        self.on_connect = None
467        self.on_message = None
468        self.on_reconnect = None
469        self.on_noreconnect = None
470
471        # Text message updates
472        self.on_order_update = None
473
474        # List of current subscribed tokens
475        self.subscribed_tokens = {}
476
477    def _create_connection(self, url, **kwargs):
478        """Create a WebSocket client connection."""
479        self.factory = KiteTickerClientFactory(url, **kwargs)
480
481        # Alias for current websocket connection
482        self.ws = self.factory.ws
483
484        self.factory.debug = self.debug
485
486        # Register private callbacks
487        self.factory.on_open = self._on_open
488        self.factory.on_error = self._on_error
489        self.factory.on_close = self._on_close
490        self.factory.on_message = self._on_message
491        self.factory.on_connect = self._on_connect
492        self.factory.on_reconnect = self._on_reconnect
493        self.factory.on_noreconnect = self._on_noreconnect
494
495        self.factory.maxDelay = self.reconnect_max_delay
496        self.factory.maxRetries = self.reconnect_max_tries
497
498    def _user_agent(self):
499        return (__title__ + "-python/").capitalize() + __version__
500
501    def connect(self, threaded=False, disable_ssl_verification=False, proxy=None):
502        """
503        Establish a websocket connection.
504
505        - `threaded` is a boolean indicating if the websocket client has to be run in threaded mode or not
506        - `disable_ssl_verification` disables building ssl context
507        - `proxy` is a dictionary with keys `host` and `port` which denotes the proxy settings
508        """
509        # Custom headers
510        headers = {
511            "X-Kite-Version": "3",  # For version 3
512        }
513
514        # Init WebSocket client factory
515        self._create_connection(self.socket_url,
516                                useragent=self._user_agent(),
517                                proxy=proxy, headers=headers)
518
519        # Set SSL context
520        context_factory = None
521        if self.factory.isSecure and not disable_ssl_verification:
522            context_factory = ssl.ClientContextFactory()
523
524        # Establish WebSocket connection to a server
525        connectWS(self.factory, contextFactory=context_factory, timeout=self.connect_timeout)
526
527        if self.debug:
528            twisted_log.startLogging(sys.stdout)
529
530        # Run in seperate thread of blocking
531        opts = {}
532
533        # Run when reactor is not running
534        if not reactor.running:
535            if threaded:
536                # Signals are not allowed in non main thread by twisted so suppress it.
537                opts["installSignalHandlers"] = False
538                self.websocket_thread = threading.Thread(target=reactor.run, kwargs=opts)
539                self.websocket_thread.daemon = True
540                self.websocket_thread.start()
541            else:
542                reactor.run(**opts)
543
544    def is_connected(self):
545        """Check if WebSocket connection is established."""
546        if self.ws and self.ws.state == self.ws.STATE_OPEN:
547            return True
548        else:
549            return False
550
551    def _close(self, code=None, reason=None):
552        """Close the WebSocket connection."""
553        if self.ws:
554            self.ws.sendClose(code, reason)
555
556    def close(self, code=None, reason=None):
557        """Close the WebSocket connection."""
558        self.stop_retry()
559        self._close(code, reason)
560
561    def stop(self):
562        """Stop the event loop. Should be used if main thread has to be closed in `on_close` method.
563        Reconnection mechanism cannot happen past this method
564        """
565        reactor.stop()
566
567    def stop_retry(self):
568        """Stop auto retry when it is in progress."""
569        if self.factory:
570            self.factory.stopTrying()
571
572    def subscribe(self, instrument_tokens):
573        """
574        Subscribe to a list of instrument_tokens.
575
576        - `instrument_tokens` is list of instrument instrument_tokens to subscribe
577        """
578        try:
579            self.ws.sendMessage(
580                six.b(json.dumps({"a": self._message_subscribe, "v": instrument_tokens}))
581            )
582
583            for token in instrument_tokens:
584                self.subscribed_tokens[token] = self.MODE_QUOTE
585
586            return True
587        except Exception as e:
588            self._close(reason="Error while subscribe: {}".format(str(e)))
589            raise
590
591    def unsubscribe(self, instrument_tokens):
592        """
593        Unsubscribe the given list of instrument_tokens.
594
595        - `instrument_tokens` is list of instrument_tokens to unsubscribe.
596        """
597        try:
598            self.ws.sendMessage(
599                six.b(json.dumps({"a": self._message_unsubscribe, "v": instrument_tokens}))
600            )
601
602            for token in instrument_tokens:
603                try:
604                    del (self.subscribed_tokens[token])
605                except KeyError:
606                    pass
607
608            return True
609        except Exception as e:
610            self._close(reason="Error while unsubscribe: {}".format(str(e)))
611            raise
612
613    def set_mode(self, mode, instrument_tokens):
614        """
615        Set streaming mode for the given list of tokens.
616
617        - `mode` is the mode to set. It can be one of the following class constants:
618            MODE_LTP, MODE_QUOTE, or MODE_FULL.
619        - `instrument_tokens` is list of instrument tokens on which the mode should be applied
620        """
621        try:
622            self.ws.sendMessage(
623                six.b(json.dumps({"a": self._message_setmode, "v": [mode, instrument_tokens]}))
624            )
625
626            # Update modes
627            for token in instrument_tokens:
628                self.subscribed_tokens[token] = mode
629
630            return True
631        except Exception as e:
632            self._close(reason="Error while setting mode: {}".format(str(e)))
633            raise
634
635    def resubscribe(self):
636        """Resubscribe to all current subscribed tokens."""
637        modes = {}
638
639        for token in self.subscribed_tokens:
640            m = self.subscribed_tokens[token]
641
642            if not modes.get(m):
643                modes[m] = []
644
645            modes[m].append(token)
646
647        for mode in modes:
648            if self.debug:
649                log.debug("Resubscribe and set mode: {} - {}".format(mode, modes[mode]))
650
651            self.subscribe(modes[mode])
652            self.set_mode(mode, modes[mode])
653
654    def _on_connect(self, ws, response):
655        self.ws = ws
656        if self.on_connect:
657            self.on_connect(self, response)
658
659    def _on_close(self, ws, code, reason):
660        """Call `on_close` callback when connection is closed."""
661        log.error("Connection closed: {} - {}".format(code, str(reason)))
662
663        if self.on_close:
664            self.on_close(self, code, reason)
665
666    def _on_error(self, ws, code, reason):
667        """Call `on_error` callback when connection throws an error."""
668        log.error("Connection error: {} - {}".format(code, str(reason)))
669
670        if self.on_error:
671            self.on_error(self, code, reason)
672
673    def _on_message(self, ws, payload, is_binary):
674        """Call `on_message` callback when text message is received."""
675        if self.on_message:
676            self.on_message(self, payload, is_binary)
677
678        # If the message is binary, parse it and send it to the callback.
679        if self.on_ticks and is_binary and len(payload) > 4:
680            self.on_ticks(self, self._parse_binary(payload))
681
682        # Parse text messages
683        if not is_binary:
684            self._parse_text_message(payload)
685
686    def _on_open(self, ws):
687        # Resubscribe if its reconnect
688        if not self._is_first_connect:
689            self.resubscribe()
690
691        # Set first connect to false once its connected first time
692        self._is_first_connect = False
693
694        if self.on_open:
695            return self.on_open(self)
696
697    def _on_reconnect(self, attempts_count):
698        if self.on_reconnect:
699            return self.on_reconnect(self, attempts_count)
700
701    def _on_noreconnect(self):
702        if self.on_noreconnect:
703            return self.on_noreconnect(self)
704
705    def _parse_text_message(self, payload):
706        """Parse text message."""
707        # Decode unicode data
708        if not six.PY2 and type(payload) == bytes:
709            payload = payload.decode("utf-8")
710
711        try:
712            data = json.loads(payload)
713        except ValueError:
714            return
715
716        # Order update callback
717        if self.on_order_update and data.get("type") == "order" and data.get("data"):
718            self.on_order_update(self, data["data"])
719
720        # Custom error with websocket error code 0
721        if data.get("type") == "error":
722            self._on_error(self, 0, data.get("data"))
723
724    def _parse_binary(self, bin):
725        """Parse binary data to a (list of) ticks structure."""
726        packets = self._split_packets(bin)  # split data to individual ticks packet
727        data = []
728
729        for packet in packets:
730            instrument_token = self._unpack_int(packet, 0, 4)
731            segment = instrument_token & 0xff  # Retrive segment constant from instrument_token
732
733            # Add price divisor based on segment
734            if segment == self.EXCHANGE_MAP["cds"]:
735                divisor = 10000000.0
736            elif segment == self.EXCHANGE_MAP["bcd"]:
737                divisor = 10000.0
738            else:
739                divisor = 100.0
740
741            # All indices are not tradable
742            tradable = False if segment == self.EXCHANGE_MAP["indices"] else True
743
744            # LTP packets
745            if len(packet) == 8:
746                data.append({
747                    "tradable": tradable,
748                    "mode": self.MODE_LTP,
749                    "instrument_token": instrument_token,
750                    "last_price": self._unpack_int(packet, 4, 8) / divisor
751                })
752            # Indices quote and full mode
753            elif len(packet) == 28 or len(packet) == 32:
754                mode = self.MODE_QUOTE if len(packet) == 28 else self.MODE_FULL
755
756                d = {
757                    "tradable": tradable,
758                    "mode": mode,
759                    "instrument_token": instrument_token,
760                    "last_price": self._unpack_int(packet, 4, 8) / divisor,
761                    "ohlc": {
762                        "high": self._unpack_int(packet, 8, 12) / divisor,
763                        "low": self._unpack_int(packet, 12, 16) / divisor,
764                        "open": self._unpack_int(packet, 16, 20) / divisor,
765                        "close": self._unpack_int(packet, 20, 24) / divisor
766                    }
767                }
768
769                # Compute the change price using close price and last price
770                d["change"] = 0
771                if (d["ohlc"]["close"] != 0):
772                    d["change"] = (d["last_price"] - d["ohlc"]["close"]) * 100 / d["ohlc"]["close"]
773
774                # Full mode with timestamp
775                if len(packet) == 32:
776                    try:
777                        timestamp = datetime.fromtimestamp(self._unpack_int(packet, 28, 32))
778                    except Exception:
779                        timestamp = None
780
781                    d["exchange_timestamp"] = timestamp
782
783                data.append(d)
784            # Quote and full mode
785            elif len(packet) == 44 or len(packet) == 184:
786                mode = self.MODE_QUOTE if len(packet) == 44 else self.MODE_FULL
787
788                d = {
789                    "tradable": tradable,
790                    "mode": mode,
791                    "instrument_token": instrument_token,
792                    "last_price": self._unpack_int(packet, 4, 8) / divisor,
793                    "last_traded_quantity": self._unpack_int(packet, 8, 12),
794                    "average_traded_price": self._unpack_int(packet, 12, 16) / divisor,
795                    "volume_traded": self._unpack_int(packet, 16, 20),
796                    "total_buy_quantity": self._unpack_int(packet, 20, 24),
797                    "total_sell_quantity": self._unpack_int(packet, 24, 28),
798                    "ohlc": {
799                        "open": self._unpack_int(packet, 28, 32) / divisor,
800                        "high": self._unpack_int(packet, 32, 36) / divisor,
801                        "low": self._unpack_int(packet, 36, 40) / divisor,
802                        "close": self._unpack_int(packet, 40, 44) / divisor
803                    }
804                }
805
806                # Compute the change price using close price and last price
807                d["change"] = 0
808                if (d["ohlc"]["close"] != 0):
809                    d["change"] = (d["last_price"] - d["ohlc"]["close"]) * 100 / d["ohlc"]["close"]
810
811                # Parse full mode
812                if len(packet) == 184:
813                    try:
814                        last_trade_time = datetime.fromtimestamp(self._unpack_int(packet, 44, 48))
815                    except Exception:
816                        last_trade_time = None
817
818                    try:
819                        timestamp = datetime.fromtimestamp(self._unpack_int(packet, 60, 64))
820                    except Exception:
821                        timestamp = None
822
823                    d["last_trade_time"] = last_trade_time
824                    d["oi"] = self._unpack_int(packet, 48, 52)
825                    d["oi_day_high"] = self._unpack_int(packet, 52, 56)
826                    d["oi_day_low"] = self._unpack_int(packet, 56, 60)
827                    d["exchange_timestamp"] = timestamp
828
829                    # Market depth entries.
830                    depth = {
831                        "buy": [],
832                        "sell": []
833                    }
834
835                    # Compile the market depth lists.
836                    for i, p in enumerate(range(64, len(packet), 12)):
837                        depth["sell" if i >= 5 else "buy"].append({
838                            "quantity": self._unpack_int(packet, p, p + 4),
839                            "price": self._unpack_int(packet, p + 4, p + 8) / divisor,
840                            "orders": self._unpack_int(packet, p + 8, p + 10, byte_format="H")
841                        })
842
843                    d["depth"] = depth
844
845                data.append(d)
846
847        return data
848
849    def _unpack_int(self, bin, start, end, byte_format="I"):
850        """Unpack binary data as unsgined interger."""
851        return struct.unpack(">" + byte_format, bin[start:end])[0]
852
853    def _split_packets(self, bin):
854        """Split the data to individual packets of ticks."""
855        # Ignore heartbeat data.
856        if len(bin) < 2:
857            return []
858
859        number_of_packets = self._unpack_int(bin, 0, 2, byte_format="H")
860        packets = []
861
862        j = 2
863        for i in range(number_of_packets):
864            packet_length = self._unpack_int(bin, j, j + 2, byte_format="H")
865            packets.append(bin[j + 2: j + 2 + packet_length])
866            j = j + 2 + packet_length
867
868        return packets

The WebSocket client for connecting to Kite Connect's streaming quotes service.

Getting started:

#!python
import logging
from kiteconnect import KiteTicker

logging.basicConfig(level=logging.DEBUG)

# Initialise
kws = KiteTicker("your_api_key", "your_access_token")

def on_ticks(ws, ticks):
    # Callback to receive ticks.
    logging.debug("Ticks: {}".format(ticks))

def on_connect(ws, response):
    # Callback on successful connect.
    # Subscribe to a list of instrument_tokens (RELIANCE and ACC here).
    ws.subscribe([738561, 5633])

    # Set RELIANCE to tick in `full` mode.
    ws.set_mode(ws.MODE_FULL, [738561])

def on_close(ws, code, reason):
    # On connection close stop the event loop.
    # Reconnection will not happen after executing `ws.stop()`
    ws.stop()

# Assign the callbacks.
kws.on_ticks = on_ticks
kws.on_connect = on_connect
kws.on_close = on_close

# Infinite loop on the main thread. Nothing after this will run.
# You have to use the pre-defined callbacks to manage subscriptions.
kws.connect()

Callbacks

In below examples ws is the currently initialised WebSocket object.

  • on_ticks(ws, ticks) - Triggered when ticks are recevied.
    • ticks - List of tick object. Check below for sample structure.
  • on_close(ws, code, reason) - Triggered when connection is closed.
  • on_error(ws, code, reason) - Triggered when connection is closed with an error.
  • on_connect - Triggered when connection is established successfully.
    • response - Response received from server on successful connection.
  • on_message(ws, payload, is_binary) - Triggered when message is received from the server.
    • payload - Raw response from the server (either text or binary).
    • is_binary - Bool to check if response is binary type.
  • on_reconnect(ws, attempts_count) - Triggered when auto reconnection is attempted.
    • attempts_count - Current reconnect attempt number.
  • on_noreconnect(ws) - Triggered when number of auto reconnection attempts exceeds reconnect_tries.
  • on_order_update(ws, data) - Triggered when there is an order update for the connected user.

Tick structure (passed to the on_ticks callback)

[{
    'instrument_token': 53490439,
    'mode': 'full',
    'volume_traded': 12510,
    'last_price': 4084.0,
    'average_traded_price': 4086.55,
    'last_traded_quantity': 1,
    'total_buy_quantity': 2356
    'total_sell_quantity': 2440,
    'change': 0.46740467404674046,
    'last_trade_time': datetime.datetime(2018, 1, 15, 13, 16, 54),
    'exchange_timestamp': datetime.datetime(2018, 1, 15, 13, 16, 56),
    'oi': 21845,
    'oi_day_low': 0,
    'oi_day_high': 0,
    'ohlc': {
        'high': 4093.0,
        'close': 4065.0,
        'open': 4088.0,
        'low': 4080.0
    },
    'tradable': True,
    'depth': {
        'sell': [{
            'price': 4085.0,
            'orders': 1048576,
            'quantity': 43
        }, {
            'price': 4086.0,
            'orders': 2752512,
            'quantity': 134
        }, {
            'price': 4087.0,
            'orders': 1703936,
            'quantity': 133
        }, {
            'price': 4088.0,
            'orders': 1376256,
            'quantity': 70
        }, {
            'price': 4089.0,
            'orders': 1048576,
            'quantity': 46
        }],
        'buy': [{
            'price': 4084.0,
            'orders': 589824,
            'quantity': 53
        }, {
            'price': 4083.0,
            'orders': 1245184,
            'quantity': 145
        }, {
            'price': 4082.0,
            'orders': 1114112,
            'quantity': 63
        }, {
            'price': 4081.0,
            'orders': 1835008,
            'quantity': 69
        }, {
            'price': 4080.0,
            'orders': 2752512,
            'quantity': 89
        }]
    }
},
...,
...]

Auto reconnection

Auto reconnection is enabled by default and it can be disabled by passing reconnect param while initialising KiteTicker. On a side note, reconnection mechanism cannot happen if event loop is terminated using stop method inside on_close callback.

Auto reonnection mechanism is based on Exponential backoff algorithm in which next retry interval will be increased exponentially. reconnect_max_delay and reconnect_max_tries params can be used to tewak the alogrithm where reconnect_max_delay is the maximum delay after which subsequent reconnection interval will become constant and reconnect_max_tries is maximum number of retries before its quiting reconnection.

For example if reconnect_max_delay is 60 seconds and reconnect_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.

method stop_retry can be used to stop ongoing reconnect attempts and on_reconnect callback will be called with current reconnect attempt and on_noreconnect is called when reconnection attempts reaches max retries.

KiteTicker( api_key, access_token, debug=False, root=None, reconnect=True, reconnect_max_tries=50, reconnect_max_delay=60, connect_timeout=30)
408    def __init__(self, api_key, access_token, debug=False, root=None,
409                 reconnect=True, reconnect_max_tries=RECONNECT_MAX_TRIES, reconnect_max_delay=RECONNECT_MAX_DELAY,
410                 connect_timeout=CONNECT_TIMEOUT):
411        """
412        Initialise websocket client instance.
413
414        - `api_key` is the API key issued to you
415        - `access_token` is the token obtained after the login flow in
416            exchange for the `request_token`. Pre-login, this will default to None,
417            but once you have obtained it, you should
418            persist it in a database or session to pass
419            to the Kite Connect class initialisation for subsequent requests.
420        - `root` is the websocket API end point root. Unless you explicitly
421            want to send API requests to a non-default endpoint, this
422            can be ignored.
423        - `reconnect` is a boolean to enable WebSocket autreconnect in case of network failure/disconnection.
424        - `reconnect_max_delay` in seconds is the maximum delay after which subsequent reconnection interval will become constant. Defaults to 60s and minimum acceptable value is 5s.
425        - `reconnect_max_tries` is maximum number reconnection attempts. Defaults to 50 attempts and maximum up to 300 attempts.
426        - `connect_timeout` in seconds is the maximum interval after which connection is considered as timeout. Defaults to 30s.
427        """
428        self.root = root or self.ROOT_URI
429
430        # Set max reconnect tries
431        if reconnect_max_tries > self._maximum_reconnect_max_tries:
432            log.warning("`reconnect_max_tries` can not be more than {val}. Setting to highest possible value - {val}.".format(
433                val=self._maximum_reconnect_max_tries))
434            self.reconnect_max_tries = self._maximum_reconnect_max_tries
435        else:
436            self.reconnect_max_tries = reconnect_max_tries
437
438        # Set max reconnect delay
439        if reconnect_max_delay < self._minimum_reconnect_max_delay:
440            log.warning("`reconnect_max_delay` can not be less than {val}. Setting to lowest possible value - {val}.".format(
441                val=self._minimum_reconnect_max_delay))
442            self.reconnect_max_delay = self._minimum_reconnect_max_delay
443        else:
444            self.reconnect_max_delay = reconnect_max_delay
445
446        self.connect_timeout = connect_timeout
447
448        self.socket_url = "{root}?api_key={api_key}"\
449            "&access_token={access_token}".format(
450                root=self.root,
451                api_key=api_key,
452                access_token=access_token
453            )
454
455        # Debug enables logs
456        self.debug = debug
457
458        # Initialize default value for websocket object
459        self.ws = None
460
461        # Placeholders for callbacks.
462        self.on_ticks = None
463        self.on_open = None
464        self.on_close = None
465        self.on_error = None
466        self.on_connect = None
467        self.on_message = None
468        self.on_reconnect = None
469        self.on_noreconnect = None
470
471        # Text message updates
472        self.on_order_update = None
473
474        # List of current subscribed tokens
475        self.subscribed_tokens = {}

Initialise websocket client instance.

  • api_key is the API key issued to you
  • access_token is the token obtained after the login flow in exchange for the request_token. Pre-login, this will default to None, 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.
  • root is the websocket API end point root. Unless you explicitly want to send API requests to a non-default endpoint, this can be ignored.
  • reconnect is a boolean to enable WebSocket autreconnect in case of network failure/disconnection.
  • reconnect_max_delay in seconds is the maximum delay after which subsequent reconnection interval will become constant. Defaults to 60s and minimum acceptable value is 5s.
  • reconnect_max_tries is maximum number reconnection attempts. Defaults to 50 attempts and maximum up to 300 attempts.
  • connect_timeout in seconds is the maximum interval after which connection is considered as timeout. Defaults to 30s.
def connect(self, threaded=False, disable_ssl_verification=False, proxy=None):
501    def connect(self, threaded=False, disable_ssl_verification=False, proxy=None):
502        """
503        Establish a websocket connection.
504
505        - `threaded` is a boolean indicating if the websocket client has to be run in threaded mode or not
506        - `disable_ssl_verification` disables building ssl context
507        - `proxy` is a dictionary with keys `host` and `port` which denotes the proxy settings
508        """
509        # Custom headers
510        headers = {
511            "X-Kite-Version": "3",  # For version 3
512        }
513
514        # Init WebSocket client factory
515        self._create_connection(self.socket_url,
516                                useragent=self._user_agent(),
517                                proxy=proxy, headers=headers)
518
519        # Set SSL context
520        context_factory = None
521        if self.factory.isSecure and not disable_ssl_verification:
522            context_factory = ssl.ClientContextFactory()
523
524        # Establish WebSocket connection to a server
525        connectWS(self.factory, contextFactory=context_factory, timeout=self.connect_timeout)
526
527        if self.debug:
528            twisted_log.startLogging(sys.stdout)
529
530        # Run in seperate thread of blocking
531        opts = {}
532
533        # Run when reactor is not running
534        if not reactor.running:
535            if threaded:
536                # Signals are not allowed in non main thread by twisted so suppress it.
537                opts["installSignalHandlers"] = False
538                self.websocket_thread = threading.Thread(target=reactor.run, kwargs=opts)
539                self.websocket_thread.daemon = True
540                self.websocket_thread.start()
541            else:
542                reactor.run(**opts)

Establish a websocket connection.

  • threaded is a boolean indicating if the websocket client has to be run in threaded mode or not
  • disable_ssl_verification disables building ssl context
  • proxy is a dictionary with keys host and port which denotes the proxy settings
def is_connected(self):
544    def is_connected(self):
545        """Check if WebSocket connection is established."""
546        if self.ws and self.ws.state == self.ws.STATE_OPEN:
547            return True
548        else:
549            return False

Check if WebSocket connection is established.

def close(self, code=None, reason=None):
556    def close(self, code=None, reason=None):
557        """Close the WebSocket connection."""
558        self.stop_retry()
559        self._close(code, reason)

Close the WebSocket connection.

def stop(self):
561    def stop(self):
562        """Stop the event loop. Should be used if main thread has to be closed in `on_close` method.
563        Reconnection mechanism cannot happen past this method
564        """
565        reactor.stop()

Stop the event loop. Should be used if main thread has to be closed in on_close method. Reconnection mechanism cannot happen past this method

def stop_retry(self):
567    def stop_retry(self):
568        """Stop auto retry when it is in progress."""
569        if self.factory:
570            self.factory.stopTrying()

Stop auto retry when it is in progress.

def subscribe(self, instrument_tokens):
572    def subscribe(self, instrument_tokens):
573        """
574        Subscribe to a list of instrument_tokens.
575
576        - `instrument_tokens` is list of instrument instrument_tokens to subscribe
577        """
578        try:
579            self.ws.sendMessage(
580                six.b(json.dumps({"a": self._message_subscribe, "v": instrument_tokens}))
581            )
582
583            for token in instrument_tokens:
584                self.subscribed_tokens[token] = self.MODE_QUOTE
585
586            return True
587        except Exception as e:
588            self._close(reason="Error while subscribe: {}".format(str(e)))
589            raise

Subscribe to a list of instrument_tokens.

  • instrument_tokens is list of instrument instrument_tokens to subscribe
def unsubscribe(self, instrument_tokens):
591    def unsubscribe(self, instrument_tokens):
592        """
593        Unsubscribe the given list of instrument_tokens.
594
595        - `instrument_tokens` is list of instrument_tokens to unsubscribe.
596        """
597        try:
598            self.ws.sendMessage(
599                six.b(json.dumps({"a": self._message_unsubscribe, "v": instrument_tokens}))
600            )
601
602            for token in instrument_tokens:
603                try:
604                    del (self.subscribed_tokens[token])
605                except KeyError:
606                    pass
607
608            return True
609        except Exception as e:
610            self._close(reason="Error while unsubscribe: {}".format(str(e)))
611            raise

Unsubscribe the given list of instrument_tokens.

  • instrument_tokens is list of instrument_tokens to unsubscribe.
def set_mode(self, mode, instrument_tokens):
613    def set_mode(self, mode, instrument_tokens):
614        """
615        Set streaming mode for the given list of tokens.
616
617        - `mode` is the mode to set. It can be one of the following class constants:
618            MODE_LTP, MODE_QUOTE, or MODE_FULL.
619        - `instrument_tokens` is list of instrument tokens on which the mode should be applied
620        """
621        try:
622            self.ws.sendMessage(
623                six.b(json.dumps({"a": self._message_setmode, "v": [mode, instrument_tokens]}))
624            )
625
626            # Update modes
627            for token in instrument_tokens:
628                self.subscribed_tokens[token] = mode
629
630            return True
631        except Exception as e:
632            self._close(reason="Error while setting mode: {}".format(str(e)))
633            raise

Set streaming mode for the given list of tokens.

  • mode is the mode to set. It can be one of the following class constants: MODE_LTP, MODE_QUOTE, or MODE_FULL.
  • instrument_tokens is list of instrument tokens on which the mode should be applied
def resubscribe(self):
635    def resubscribe(self):
636        """Resubscribe to all current subscribed tokens."""
637        modes = {}
638
639        for token in self.subscribed_tokens:
640            m = self.subscribed_tokens[token]
641
642            if not modes.get(m):
643                modes[m] = []
644
645            modes[m].append(token)
646
647        for mode in modes:
648            if self.debug:
649                log.debug("Resubscribe and set mode: {} - {}".format(mode, modes[mode]))
650
651            self.subscribe(modes[mode])
652            self.set_mode(mode, modes[mode])

Resubscribe to all current subscribed tokens.