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 theaccess_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"]
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
.
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 youaccess_token
is the token obtained after the login flow in exchange for therequest_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 secondsproxies
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 documentationdisable_ssl
disables the SSL verification while making a request. If set requests won't throw SSLError if its set to customroot
url without SSL.
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.
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.
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.
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.
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 activeaccess_token
.
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.
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.
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)
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.
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.
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.
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.
414 def orders(self): 415 """Get list of orders.""" 416 return self._format_response(self._get("orders"))
Get list of orders.
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.
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.
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.
443 def positions(self): 444 """Retrieve the list of positions.""" 445 return self._get("portfolio.positions")
Retrieve the list of positions.
447 def holdings(self): 448 """Retrieve the list of equity holdings.""" 449 return self._get("portfolio.holdings")
Retrieve the list of equity holdings.
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
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.
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.
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.
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.
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.
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.
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.
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.
548 def mf_holdings(self): 549 """Get list of mutual fund holdings.""" 550 return self._get("mf.holdings")
Get list of mutual fund holdings.
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.
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)
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 ofexchange:tradingsymbol
. For example NSE:INFY
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 ofexchange:tradingsymbol
. For example NSE:INFY
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 ofexchange:tradingsymbol
. For example NSE:INFY
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.
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.
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
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
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 fieldstransaction_type
BUY or SELLquantity
Quantity to transactprice
The min or max price to execute the order at (for LIMIT 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 fieldstransaction_type
BUY or SELLquantity
Quantity to transactprice
The min or max price to execute the order at (for LIMIT orders)
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.
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
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 oftick
object. Check below for sample structure.
on_close(ws, code, reason)
- Triggered when connection is closed.code
- WebSocket standard close event code (https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent)reason
- DOMString indicating the reason the server closed the connection
on_error(ws, code, reason)
- Triggered when connection is closed with an error.code
- WebSocket standard close event code (https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent)reason
- DOMString indicating the reason the server closed the connection
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 exceedsreconnect_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.
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 youaccess_token
is the token obtained after the login flow in exchange for therequest_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.
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 notdisable_ssl_verification
disables building ssl contextproxy
is a dictionary with keyshost
andport
which denotes the proxy settings
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.
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.
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
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.
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
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.
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
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.