├── .gitignore ├── .idea ├── .gitignore ├── inspectionProfiles │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── vcs.xml └── xts-pythonclient-api-sdk.iml ├── Connect.py ├── Example.py ├── Exception.py ├── InteractiveSocketClient.py ├── InteractiveSocketExample.py ├── MarketDataSocketClient.py ├── MarketdataSocketExample.py ├── README.md ├── __init__.py ├── __version__.py ├── config.ini └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | /venv/ 2 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/xts-pythonclient-api-sdk.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | -------------------------------------------------------------------------------- /Connect.py: -------------------------------------------------------------------------------- 1 | """ 2 | Connect.py 3 | 4 | API wrapper for XTS Connect REST APIs. 5 | 6 | :copyright: 7 | :license: see LICENSE for details. 8 | """ 9 | import configparser 10 | import json 11 | import logging 12 | import requests 13 | from urllib import parse 14 | import Exception as ex 15 | log = logging.getLogger(__name__) 16 | 17 | 18 | class XTSCommon: 19 | """ 20 | Base variables class 21 | """ 22 | 23 | def __init__(self, token=None, userID=None, isInvestorClient=None): 24 | """Initialize the common variables.""" 25 | self.token = token 26 | self.userID = userID 27 | self.isInvestorClient = isInvestorClient 28 | 29 | 30 | class XTSConnect(XTSCommon): 31 | """ 32 | The XTS Connect API wrapper class. 33 | In production, you may initialise a single instance of this class per `api_key`. 34 | """ 35 | """Get the configurations from config.ini""" 36 | cfg = configparser.ConfigParser() 37 | cfg.read('config.ini') 38 | 39 | # Default root API endpoint. It's possible to 40 | # override this by passing the `root` parameter during initialisation. 41 | _default_root_uri = cfg.get('root_url', 'root') 42 | _default_login_uri = _default_root_uri + "/user/session" 43 | _default_timeout = 7 # In seconds 44 | 45 | # SSL Flag 46 | _ssl_flag = cfg.get('SSL', 'disable_ssl') 47 | 48 | # Constants 49 | # Products 50 | PRODUCT_MIS = "MIS" 51 | PRODUCT_NRML = "NRML" 52 | 53 | # Order types 54 | ORDER_TYPE_MARKET = "MARKET" 55 | ORDER_TYPE_LIMIT = "LIMIT" 56 | ORDER_TYPE_STOPMARKET = "STOPMARKET" 57 | ORDER_TYPE_STOPLIMIT = "STOPLIMIT" 58 | 59 | # Transaction type 60 | TRANSACTION_TYPE_BUY = "BUY" 61 | TRANSACTION_TYPE_SELL = "SELL" 62 | 63 | # Squareoff mode 64 | SQUAREOFF_DAYWISE = "DayWise" 65 | SQUAREOFF_NETWISE = "Netwise" 66 | 67 | # Squareoff position quantity types 68 | SQUAREOFFQUANTITY_EXACTQUANTITY = "ExactQty" 69 | SQUAREOFFQUANTITY_PERCENTAGE = "Percentage" 70 | 71 | # Validity 72 | VALIDITY_DAY = "DAY" 73 | 74 | # Exchange Segments 75 | EXCHANGE_NSECM = "NSECM" 76 | EXCHANGE_NSEFO = "NSEFO" 77 | EXCHANGE_NSECD = "NSECD" 78 | EXCHANGE_MCXFO = "MCXFO" 79 | EXCHANGE_BSECM = "BSECM" 80 | EXCHANGE_BSEFO = "BSEFO" 81 | 82 | # URIs to various calls 83 | _routes = { 84 | # Interactive API endpoints 85 | "interactive.prefix": "interactive", 86 | "user.login": "/interactive/user/session", 87 | "user.logout": "/interactive/user/session", 88 | "user.profile": "/interactive/user/profile", 89 | "user.balance": "/interactive/user/balance", 90 | 91 | "orders": "/interactive/orders", 92 | "trades": "/interactive/orders/trades", 93 | "order.status": "/interactive/orders", 94 | "order.place": "/interactive/orders", 95 | "bracketorder.place": "/interactive/orders/bracket", 96 | "bracketorder.modify": "/interactive/orders/bracket", 97 | "bracketorder.cancel": "/interactive/orders/bracket", 98 | "order.place.cover": "/interactive/orders/cover", 99 | "order.exit.cover": "/interactive/orders/cover", 100 | "order.modify": "/interactive/orders", 101 | "order.cancel": "/interactive/orders", 102 | "order.cancelall": "/interactive/orders/cancelall", 103 | "order.history": "/interactive/orders", 104 | 105 | "portfolio.positions": "/interactive/portfolio/positions", 106 | "portfolio.holdings": "/interactive/portfolio/holdings", 107 | "portfolio.positions.convert": "/interactive/portfolio/positions/convert", 108 | "portfolio.squareoff": "/interactive/portfolio/squareoff", 109 | "portfolio.dealerpositions": "interactive/portfolio/dealerpositions", 110 | "order.dealer.status": "/interactive/orders/dealerorderbook", 111 | "dealer.trades": "/interactive/orders/dealertradebook", 112 | 113 | 114 | 115 | 116 | # Market API endpoints 117 | "marketdata.prefix": "apimarketdata", 118 | "market.login": "/apimarketdata/auth/login", 119 | "market.logout": "/apimarketdata/auth/logout", 120 | 121 | "market.config": "/apimarketdata/config/clientConfig", 122 | 123 | "market.instruments.master": "/apimarketdata/instruments/master", 124 | "market.instruments.subscription": "/apimarketdata/instruments/subscription", 125 | "market.instruments.unsubscription": "/apimarketdata/instruments/subscription", 126 | "market.instruments.ohlc": "/apimarketdata/instruments/ohlc", 127 | "market.instruments.indexlist": "/apimarketdata/instruments/indexlist", 128 | "market.instruments.quotes": "/apimarketdata/instruments/quotes", 129 | 130 | "market.search.instrumentsbyid": '/apimarketdata/search/instrumentsbyid', 131 | "market.search.instrumentsbystring": '/apimarketdata/search/instruments', 132 | 133 | "market.instruments.instrument.series": "/apimarketdata/instruments/instrument/series", 134 | "market.instruments.instrument.equitysymbol": "/apimarketdata/instruments/instrument/symbol", 135 | "market.instruments.instrument.futuresymbol": "/apimarketdata/instruments/instrument/futureSymbol", 136 | "market.instruments.instrument.optionsymbol": "/apimarketdata/instruments/instrument/optionsymbol", 137 | "market.instruments.instrument.optiontype": "/apimarketdata/instruments/instrument/optionType", 138 | "market.instruments.instrument.expirydate": "/apimarketdata/instruments/instrument/expiryDate" 139 | } 140 | 141 | def __init__(self, 142 | apiKey, 143 | secretKey, 144 | source, 145 | root=None, 146 | debug=False, 147 | timeout=None, 148 | pool=None, 149 | disable_ssl=_ssl_flag): 150 | """ 151 | Initialise a new XTS Connect client instance. 152 | 153 | - `api_key` is the key issued to you 154 | - `token` is the token obtained after the login flow. Pre-login, this will default to None, 155 | but once you have obtained it, you should persist it in a database or session to pass 156 | to the XTS Connect class initialisation for subsequent requests. 157 | - `root` is the API end point root. Unless you explicitly 158 | want to send API requests to a non-default endpoint, this 159 | can be ignored. 160 | - `debug`, if set to True, will serialise and print requests 161 | and responses to stdout. 162 | - `timeout` is the time (seconds) for which the API client will wait for 163 | a request to complete before it fails. Defaults to 7 seconds 164 | - `pool` is manages request pools. It takes a dict of params accepted by HTTPAdapter 165 | - `disable_ssl` disables the SSL verification while making a request. 166 | If set requests won't throw SSLError if its set to custom `root` url without SSL. 167 | """ 168 | self.debug = debug 169 | self.apiKey = apiKey 170 | self.secretKey = secretKey 171 | self.source = source 172 | self.disable_ssl = disable_ssl 173 | self.root = root or self._default_root_uri 174 | self.timeout = timeout or self._default_timeout 175 | 176 | super().__init__() 177 | 178 | # Create requests session only if pool exists. Reuse session 179 | # for every request. Otherwise create session for each request 180 | if pool: 181 | self.reqsession = requests.Session() 182 | reqadapter = requests.adapters.HTTPAdapter(**pool) 183 | self.reqsession.mount("https://", reqadapter) 184 | else: 185 | self.reqsession = requests 186 | 187 | # disable requests SSL warning 188 | requests.packages.urllib3.disable_warnings() 189 | 190 | def _set_common_variables(self, access_token,userID, isInvestorClient): 191 | """Set the `access_token` received after a successful authentication.""" 192 | super().__init__(access_token,userID, isInvestorClient) 193 | 194 | def _login_url(self): 195 | """Get the remote login url to which a user should be redirected to initiate the login flow.""" 196 | return self._default_login_uri 197 | 198 | def interactive_login(self): 199 | """Send the login url to which a user should receive the token.""" 200 | try: 201 | params = { 202 | "appKey": self.apiKey, 203 | "secretKey": self.secretKey, 204 | "source": self.source 205 | } 206 | response = self._post("user.login", params) 207 | 208 | if "token" in response['result']: 209 | self._set_common_variables(response['result']['token'], response['result']['userID'], 210 | response['result']['isInvestorClient']) 211 | return response 212 | except Exception as e: 213 | return response['description'] 214 | 215 | def get_order_book(self, clientID=None): 216 | """Request Order book gives states of all the orders placed by an user""" 217 | try: 218 | params = {} 219 | if not self.isInvestorClient: 220 | params['clientID'] = clientID 221 | response = self._get("order.status", params) 222 | return response 223 | except Exception as e: 224 | return response['description'] 225 | 226 | def get_dealer_orderbook(self, clientID=None): 227 | """Request Order book gives states of all the orders placed by an user""" 228 | try: 229 | params = {} 230 | if not self.isInvestorClient: 231 | params['clientID'] = clientID 232 | response = self._get("order.dealer.status", params) 233 | return response 234 | except Exception as e: 235 | return response['description'] 236 | 237 | 238 | def place_order(self, 239 | exchangeSegment, 240 | exchangeInstrumentID, 241 | productType, 242 | orderType, 243 | orderSide, 244 | timeInForce, 245 | disclosedQuantity, 246 | orderQuantity, 247 | limitPrice, 248 | stopPrice, 249 | orderUniqueIdentifier, 250 | apiOrderSource, 251 | clientID=None 252 | ): 253 | """To place an order""" 254 | try: 255 | 256 | params = { 257 | "exchangeSegment": exchangeSegment, 258 | "exchangeInstrumentID": exchangeInstrumentID, 259 | "productType": productType, 260 | "orderType": orderType, 261 | "orderSide": orderSide, 262 | "timeInForce": timeInForce, 263 | "disclosedQuantity": disclosedQuantity, 264 | "orderQuantity": orderQuantity, 265 | "limitPrice": limitPrice, 266 | "stopPrice": stopPrice, 267 | "apiOrderSource":apiOrderSource, 268 | "orderUniqueIdentifier": orderUniqueIdentifier 269 | } 270 | 271 | if not self.isInvestorClient: 272 | params['clientID'] = clientID 273 | 274 | response = self._post('order.place', json.dumps(params)) 275 | return response 276 | except Exception as e: 277 | return response['description'] 278 | 279 | def modify_order(self, 280 | appOrderID, 281 | modifiedProductType, 282 | modifiedOrderType, 283 | modifiedOrderQuantity, 284 | modifiedDisclosedQuantity, 285 | modifiedLimitPrice, 286 | modifiedStopPrice, 287 | modifiedTimeInForce, 288 | orderUniqueIdentifier, 289 | clientID=None 290 | ): 291 | """The facility to modify your open orders by allowing you to change limit order to market or vice versa, 292 | change Price or Quantity of the limit open order, change disclosed quantity or stop-loss of any 293 | open stop loss order. """ 294 | try: 295 | appOrderID = int(appOrderID) 296 | params = { 297 | 'appOrderID': appOrderID, 298 | 'modifiedProductType': modifiedProductType, 299 | 'modifiedOrderType': modifiedOrderType, 300 | 'modifiedOrderQuantity': modifiedOrderQuantity, 301 | 'modifiedDisclosedQuantity': modifiedDisclosedQuantity, 302 | 'modifiedLimitPrice': modifiedLimitPrice, 303 | 'modifiedStopPrice': modifiedStopPrice, 304 | 'modifiedTimeInForce': modifiedTimeInForce, 305 | 'orderUniqueIdentifier': orderUniqueIdentifier 306 | } 307 | 308 | if not self.isInvestorClient: 309 | params['clientID'] = clientID 310 | 311 | response = self._put('order.modify', json.dumps(params)) 312 | return response 313 | except Exception as e: 314 | return response['description'] 315 | 316 | 317 | 318 | def place_bracketorder(self, 319 | exchangeSegment, 320 | exchangeInstrumentID, 321 | orderType, 322 | orderSide, 323 | disclosedQuantity, 324 | orderQuantity, 325 | limitPrice, 326 | squarOff, 327 | stopLossPrice, 328 | trailingStoploss, 329 | isProOrder, 330 | apiOrderSource, 331 | orderUniqueIdentifier, 332 | ): 333 | """To place a bracketorder""" 334 | try: 335 | 336 | params = { 337 | "exchangeSegment": exchangeSegment, 338 | "exchangeInstrumentID": exchangeInstrumentID, 339 | "orderType": orderType, 340 | "orderSide": orderSide, 341 | "disclosedQuantity": disclosedQuantity, 342 | "orderQuantity": orderQuantity, 343 | "limitPrice": limitPrice, 344 | "squarOff": squarOff, 345 | "stopLossPrice": stopLossPrice, 346 | "trailingStoploss": trailingStoploss, 347 | "isProOrder": isProOrder, 348 | "apiOrderSource":apiOrderSource, 349 | "orderUniqueIdentifier": orderUniqueIdentifier 350 | } 351 | response = self._post('bracketorder.place', json.dumps(params)) 352 | print(response) 353 | return response 354 | except Exception as e: 355 | return response['description'] 356 | 357 | def bracketorder_cancel(self, appOrderID, clientID=None): 358 | """This API can be called to cancel any open order of the user by providing correct appOrderID matching with 359 | the chosen open order to cancel. """ 360 | try: 361 | params = {'boEntryOrderId': int(appOrderID)} 362 | if not self.isInvestorClient: 363 | params['clientID'] = clientID 364 | response = self._delete('bracketorder.cancel', params) 365 | return response 366 | except Exception as e: 367 | return response['description'] 368 | 369 | def modify_bracketorder(self, 370 | appOrderID, 371 | orderQuantity, 372 | limitPrice, 373 | stopPrice, 374 | clientID=None 375 | ): 376 | try: 377 | appOrderID = int(appOrderID) 378 | params = { 379 | 'appOrderID': appOrderID, 380 | 'bracketorder.modify': orderQuantity, 381 | 'limitPrice': limitPrice, 382 | 'stopPrice': stopPrice 383 | } 384 | 385 | if not self.isInvestorClient: 386 | params['clientID'] = clientID 387 | 388 | response = self._put('bracketorder.modify', json.dumps(params)) 389 | return response 390 | except Exception as e: 391 | return response['description'] 392 | 393 | 394 | def place_cover_order(self, 395 | exchangeSegment, 396 | exchangeInstrumentID, 397 | orderSide,orderType, 398 | orderQuantity, 399 | disclosedQuantity, 400 | limitPrice, 401 | stopPrice, 402 | apiOrderSource, 403 | orderUniqueIdentifier, 404 | clientID=None): 405 | """A Cover Order is an advance intraday order that is accompanied by a compulsory Stop Loss Order. This helps 406 | users to minimize their losses by safeguarding themselves from unexpected market movements. A Cover Order 407 | offers high leverage and is available in Equity Cash, Equity F&O, Commodity F&O and Currency F&O segments. It 408 | has 2 orders embedded in itself, they are Limit/Market Order Stop Loss Order """ 409 | try: 410 | 411 | params = {'exchangeSegment': exchangeSegment, 412 | 'exchangeInstrumentID': exchangeInstrumentID, 413 | 'orderSide': orderSide, 414 | "orderType": orderType, 415 | 'orderQuantity': orderQuantity, 416 | 'disclosedQuantity': disclosedQuantity, 417 | 'limitPrice': limitPrice, 418 | 'stopPrice': stopPrice, 419 | 'apiOrderSource': apiOrderSource, 420 | 'orderUniqueIdentifier': orderUniqueIdentifier 421 | } 422 | if not self.isInvestorClient: 423 | params['clientID'] = clientID 424 | response = self._post('order.place.cover', json.dumps(params)) 425 | return response 426 | except Exception as e: 427 | return response['description'] 428 | 429 | def exit_cover_order(self, appOrderID, clientID=None): 430 | """Exit Cover API is a functionality to enable user to easily exit an open stoploss order by converting it 431 | into Exit order. """ 432 | try: 433 | 434 | params = {'appOrderID': appOrderID} 435 | if not self.isInvestorClient: 436 | params['clientID'] = clientID 437 | response = self._put('order.exit.cover', json.dumps(params)) 438 | return response 439 | except Exception as e: 440 | return response['description'] 441 | 442 | 443 | 444 | def get_profile(self, clientID=None): 445 | """Using session token user can access his profile stored with the broker, it's possible to retrieve it any 446 | point of time with the http: //ip:port/interactive/user/profile API. """ 447 | try: 448 | params = {} 449 | if not self.isInvestorClient: 450 | params['clientID'] = clientID 451 | 452 | response = self._get('user.profile', params) 453 | return response 454 | except Exception as e: 455 | return response['description'] 456 | 457 | def get_balance(self, clientID=None): 458 | """Get Balance API call grouped under this category information related to limits on equities, derivative, 459 | upfront margin, available exposure and other RMS related balances available to the user.""" 460 | if self.isInvestorClient: 461 | try: 462 | params = {} 463 | if not self.isInvestorClient: 464 | params['clientID'] = clientID 465 | response = self._get('user.balance', params) 466 | return response 467 | except Exception as e: 468 | return response['description'] 469 | else: 470 | print("Balance : Balance API available for retail API users only, dealers can watch the same on dealer " 471 | "terminal") 472 | 473 | 474 | def get_trade(self, clientID=None): 475 | """Trade book returns a list of all trades executed on a particular day , that were placed by the user . The 476 | trade book will display all filled and partially filled orders. """ 477 | try: 478 | params = {} 479 | if not self.isInvestorClient: 480 | params['clientID'] = clientID 481 | response = self._get('trades', params) 482 | return response 483 | except Exception as e: 484 | return response['description'] 485 | 486 | def get_dealer_tradebook(self, clientID=None): 487 | """Trade book returns a list of all trades executed on a particular day , that were placed by the user . The 488 | trade book will display all filled and partially filled orders. """ 489 | try: 490 | params = {} 491 | if not self.isInvestorClient: 492 | params['clientID'] = clientID 493 | response = self._get('dealer.trades', params) 494 | return response 495 | except Exception as e: 496 | return response['description'] 497 | 498 | def get_holding(self, clientID=None): 499 | """Holdings API call enable users to check their long term holdings with the broker.""" 500 | try: 501 | params = {} 502 | if not self.isInvestorClient: 503 | params['clientID'] = clientID 504 | 505 | response = self._get('portfolio.holdings', params) 506 | return response 507 | except Exception as e: 508 | return response['description'] 509 | 510 | 511 | def get_dealerposition_netwise(self, clientID=None): 512 | """The positions API positions by net. Net is the actual, current net position portfolio.""" 513 | try: 514 | params = {'dayOrNet': 'NetWise'} 515 | if not self.isInvestorClient: 516 | params['clientID'] = clientID 517 | response = self._get('portfolio.dealerpositions', params) 518 | return response 519 | except Exception as e: 520 | return response['description'] 521 | 522 | 523 | 524 | def get_dealerposition_daywise(self, clientID=None): 525 | """The positions API returns positions by day, which is a snapshot of the buying and selling activity for 526 | that particular day.""" 527 | try: 528 | params = {'dayOrNet': 'DayWise'} 529 | if not self.isInvestorClient: 530 | params['clientID'] = clientID 531 | 532 | response = self._get('portfolio.dealerpositions', params) 533 | return response 534 | except Exception as e: 535 | return response['description'] 536 | 537 | def get_position_daywise(self, clientID=None): 538 | 539 | """The positions API returns positions by day, which is a snapshot of the buying and selling activity for 540 | that particular day.""" 541 | try: 542 | params = {'dayOrNet': 'DayWise'} 543 | if not self.isInvestorClient: 544 | params['clientID'] = clientID 545 | 546 | response = self._get('portfolio.positions', params) 547 | return response 548 | except Exception as e: 549 | return response['description'] 550 | 551 | def get_position_netwise(self, clientID=None): 552 | """The positions API positions by net. Net is the actual, current net position portfolio.""" 553 | try: 554 | params = {'dayOrNet': 'NetWise'} 555 | if not self.isInvestorClient: 556 | params['clientID'] = clientID 557 | response = self._get('portfolio.positions', params) 558 | return response 559 | except Exception as e: 560 | return response['description'] 561 | 562 | def convert_position(self, exchangeSegment, exchangeInstrumentID, targetQty, isDayWise, oldProductType, 563 | newProductType, clientID=None): 564 | """Convert position API, enable users to convert their open positions from NRML intra-day to Short term MIS or 565 | vice versa, provided that there is sufficient margin or funds in the account to effect such conversion """ 566 | try: 567 | params = { 568 | 'exchangeSegment': exchangeSegment, 569 | 'exchangeInstrumentID': exchangeInstrumentID, 570 | 'targetQty': targetQty, 571 | 'isDayWise': isDayWise, 572 | 'oldProductType': oldProductType, 573 | 'newProductType': newProductType 574 | } 575 | if not self.isInvestorClient: 576 | params['clientID'] = clientID 577 | response = self._put('portfolio.positions.convert', json.dumps(params)) 578 | return response 579 | except Exception as e: 580 | return response['description'] 581 | 582 | def cancel_order(self, appOrderID, orderUniqueIdentifier, clientID=None): 583 | """This API can be called to cancel any open order of the user by providing correct appOrderID matching with 584 | the chosen open order to cancel. """ 585 | try: 586 | params = {'appOrderID': int(appOrderID), 'orderUniqueIdentifier': orderUniqueIdentifier} 587 | if not self.isInvestorClient: 588 | params['clientID'] = clientID 589 | response = self._delete('order.cancel', params) 590 | return response 591 | except Exception as e: 592 | return response['description'] 593 | 594 | def cancelall_order(self, exchangeSegment, exchangeInstrumentID): 595 | """This API can be called to cancel all open order of the user by providing exchange segment and exchange instrument ID """ 596 | try: 597 | params = {"exchangeSegment": exchangeSegment, "exchangeInstrumentID": exchangeInstrumentID} 598 | if not self.isInvestorClient: 599 | params['clientID'] = self.userID 600 | response = self._post('order.cancelall', json.dumps(params)) 601 | return response 602 | except Exception as e: 603 | return response['description'] 604 | 605 | 606 | def squareoff_position(self, exchangeSegment, exchangeInstrumentID, productType, squareoffMode, 607 | positionSquareOffQuantityType, squareOffQtyValue, blockOrderSending, cancelOrders, 608 | clientID=None): 609 | """User can request square off to close all his positions in Equities, Futures and Option. Users are advised 610 | to use this request with caution if one has short term holdings. """ 611 | try: 612 | 613 | params = {'exchangeSegment': exchangeSegment, 'exchangeInstrumentID': exchangeInstrumentID, 614 | 'productType': productType, 'squareoffMode': squareoffMode, 615 | 'positionSquareOffQuantityType': positionSquareOffQuantityType, 616 | 'squareOffQtyValue': squareOffQtyValue, 'blockOrderSending': blockOrderSending, 617 | 'cancelOrders': cancelOrders 618 | } 619 | if not self.isInvestorClient: 620 | params['clientID'] = clientID 621 | response = self._put('portfolio.squareoff', json.dumps(params)) 622 | return response 623 | except Exception as e: 624 | return response['description'] 625 | 626 | def get_order_history(self, appOrderID, clientID=None): 627 | """Order history will provide particular order trail chain. This indicate the particular order & its state 628 | changes. i.e.Pending New to New, New to PartiallyFilled, PartiallyFilled, PartiallyFilled & PartiallyFilled 629 | to Filled etc """ 630 | try: 631 | params = {'appOrderID': appOrderID} 632 | if not self.isInvestorClient: 633 | params['clientID'] = clientID 634 | response = self._get('order.history', params) 635 | return response 636 | except Exception as e: 637 | return response['description'] 638 | 639 | def interactive_logout(self, clientID=None): 640 | """This call invalidates the session token and destroys the API session. After this, the user should go 641 | through login flow again and extract session token from login response before further activities. """ 642 | try: 643 | params = {} 644 | if not self.isInvestorClient: 645 | params['clientID'] = clientID 646 | response = self._delete('user.logout', params) 647 | return response 648 | except Exception as e: 649 | return response['description'] 650 | 651 | ######################################################################################################## 652 | # Market data API 653 | ######################################################################################################## 654 | 655 | def marketdata_login(self): 656 | try: 657 | params = { 658 | "appKey": self.apiKey, 659 | "secretKey": self.secretKey, 660 | "source": self.source 661 | } 662 | response = self._post("market.login", params) 663 | 664 | if "token" in response['result']: 665 | self._set_common_variables(response['result']['token'], response['result']['userID'],False) 666 | return response 667 | except Exception as e: 668 | return response['description'] 669 | 670 | def get_config(self): 671 | try: 672 | params = {} 673 | response = self._get('market.config', params) 674 | return response 675 | except Exception as e: 676 | return response['description'] 677 | 678 | def get_quote(self, Instruments, xtsMessageCode, publishFormat): 679 | try: 680 | 681 | params = {'instruments': Instruments, 'xtsMessageCode': xtsMessageCode, 'publishFormat': publishFormat} 682 | response = self._post('market.instruments.quotes', json.dumps(params)) 683 | return response 684 | except Exception as e: 685 | return response['description'] 686 | 687 | def send_subscription(self, Instruments, xtsMessageCode): 688 | try: 689 | params = {'instruments': Instruments, 'xtsMessageCode': xtsMessageCode} 690 | response = self._post('market.instruments.subscription', json.dumps(params)) 691 | return response 692 | except Exception as e: 693 | return response['description'] 694 | 695 | def send_unsubscription(self, Instruments, xtsMessageCode): 696 | try: 697 | params = {'instruments': Instruments, 'xtsMessageCode': xtsMessageCode} 698 | response = self._put('market.instruments.unsubscription', json.dumps(params)) 699 | return response 700 | except Exception as e: 701 | return response['description'] 702 | 703 | def get_master(self, exchangeSegmentList): 704 | try: 705 | params = {"exchangeSegmentList": exchangeSegmentList} 706 | response = self._post('market.instruments.master', json.dumps(params)) 707 | return response 708 | except Exception as e: 709 | return response['description'] 710 | 711 | def get_ohlc(self, exchangeSegment, exchangeInstrumentID, startTime, endTime, compressionValue): 712 | try: 713 | params = { 714 | 'exchangeSegment': exchangeSegment, 715 | 'exchangeInstrumentID': exchangeInstrumentID, 716 | 'startTime': startTime, 717 | 'endTime': endTime, 718 | 'compressionValue': compressionValue} 719 | response = self._get('market.instruments.ohlc', params) 720 | return response 721 | except Exception as e: 722 | return response['description'] 723 | 724 | def get_series(self, exchangeSegment): 725 | try: 726 | params = {'exchangeSegment': exchangeSegment} 727 | response = self._get('market.instruments.instrument.series', params) 728 | return response 729 | except Exception as e: 730 | return response['description'] 731 | 732 | def get_equity_symbol(self, exchangeSegment, series, symbol): 733 | try: 734 | 735 | params = {'exchangeSegment': exchangeSegment, 'series': series, 'symbol': symbol} 736 | response = self._get('market.instruments.instrument.equitysymbol', params) 737 | return response 738 | except Exception as e: 739 | return response['description'] 740 | 741 | def get_expiry_date(self, exchangeSegment, series, symbol): 742 | try: 743 | params = {'exchangeSegment': exchangeSegment, 'series': series, 'symbol': symbol} 744 | response = self._get('market.instruments.instrument.expirydate', params) 745 | return response 746 | except Exception as e: 747 | return response['description'] 748 | 749 | def get_future_symbol(self, exchangeSegment, series, symbol, expiryDate): 750 | try: 751 | params = {'exchangeSegment': exchangeSegment, 'series': series, 'symbol': symbol, 'expiryDate': expiryDate} 752 | response = self._get('market.instruments.instrument.futuresymbol', params) 753 | return response 754 | except Exception as e: 755 | return response['description'] 756 | 757 | def get_option_symbol(self, exchangeSegment, series, symbol, expiryDate, optionType, strikePrice): 758 | try: 759 | params = {'exchangeSegment': exchangeSegment, 'series': series, 'symbol': symbol, 'expiryDate': expiryDate, 760 | 'optionType': optionType, 'strikePrice': strikePrice} 761 | response = self._get('market.instruments.instrument.optionsymbol', params) 762 | return response 763 | except Exception as e: 764 | return response['description'] 765 | 766 | def get_option_type(self, exchangeSegment, series, symbol, expiryDate): 767 | try: 768 | params = {'exchangeSegment': exchangeSegment, 'series': series, 'symbol': symbol, 'expiryDate': expiryDate} 769 | response = self._get('market.instruments.instrument.optiontype', params) 770 | return response 771 | except Exception as e: 772 | return response['description'] 773 | 774 | def get_index_list(self, exchangeSegment): 775 | try: 776 | params = {'exchangeSegment': exchangeSegment} 777 | response = self._get('market.instruments.indexlist', params) 778 | return response 779 | except Exception as e: 780 | return response['description'] 781 | 782 | def search_by_instrumentid(self, Instruments): 783 | try: 784 | params = {'source': self.source, 'instruments': Instruments} 785 | response = self._post('market.search.instrumentsbyid', json.dumps(params)) 786 | return response 787 | except Exception as e: 788 | return response['description'] 789 | 790 | def search_by_scriptname(self, searchString): 791 | try: 792 | params = {'searchString': searchString} 793 | response = self._get('market.search.instrumentsbystring', params) 794 | return response 795 | except Exception as e: 796 | return response['description'] 797 | 798 | def marketdata_logout(self): 799 | try: 800 | params = {} 801 | response = self._delete('market.logout', params) 802 | return response 803 | except Exception as e: 804 | return response['description'] 805 | 806 | ######################################################################################################## 807 | # Common Methods 808 | ######################################################################################################## 809 | 810 | def _get(self, route, params=None): 811 | """Alias for sending a GET request.""" 812 | return self._request(route, "GET", params) 813 | 814 | def _post(self, route, params=None): 815 | """Alias for sending a POST request.""" 816 | return self._request(route, "POST", params) 817 | 818 | def _put(self, route, params=None): 819 | """Alias for sending a PUT request.""" 820 | return self._request(route, "PUT", params) 821 | 822 | def _delete(self, route, params=None): 823 | """Alias for sending a DELETE request.""" 824 | return self._request(route, "DELETE", params) 825 | 826 | def _request(self, route, method, parameters=None): 827 | """Make an HTTP request.""" 828 | params = parameters if parameters else {} 829 | 830 | # Form a restful URL 831 | uri = self._routes[route].format(params) 832 | url = parse.urljoin(self.root, uri) 833 | headers = {} 834 | 835 | if self.token: 836 | # set authorization header 837 | headers.update({'Content-Type': 'application/json', 'Authorization': self.token}) 838 | 839 | try: 840 | r = self.reqsession.request(method, 841 | url, 842 | data=params if method in ["POST", "PUT"] else None, 843 | params=params if method in ["GET", "DELETE"] else None, 844 | headers=headers, 845 | verify=not self.disable_ssl) 846 | 847 | except Exception as e: 848 | raise e 849 | 850 | if self.debug: 851 | log.debug("Response: {code} {content}".format(code=r.status_code, content=r.content)) 852 | 853 | # Validate the content type. 854 | if "json" in r.headers["content-type"]: 855 | try: 856 | data = json.loads(r.content.decode("utf8")) 857 | except ValueError: 858 | raise ex.XTSDataException("Couldn't parse the JSON response received from the server: {content}".format( 859 | content=r.content)) 860 | 861 | # api error 862 | if data.get("type"): 863 | 864 | if r.status_code == 400 and data["type"] == "error" and data["description"] == "Invalid Token": 865 | raise ex.XTSTokenException(data["description"]) 866 | 867 | if r.status_code == 400 and data["type"] == "error" and data["description"] == "Bad Request": 868 | message = "Description: " + data["description"] + " errors: " + str(data['result']["errors"]) 869 | raise ex.XTSInputException(str(message)) 870 | 871 | return data 872 | else: 873 | raise ex.XTSDataException("Unknown Content-Type ({content_type}) with response: ({content})".format( 874 | content_type=r.headers["content-type"], 875 | content=r.content)) 876 | -------------------------------------------------------------------------------- /Example.py: -------------------------------------------------------------------------------- 1 | # from XTConnect import XTSConnect 2 | from Connect import XTSConnect 3 | 4 | 5 | # ---------------------------------------------------------------------------------------------------------------------- 6 | # Interactive 7 | # ---------------------------------------------------------------------------------------------------------------------- 8 | 9 | # Interactive API Credentials 10 | # API_KEY = "YOUR_API_KEY_HERE" 11 | # API_SECRET = "YOUR_API_SECRET_HERE" 12 | # clientID = "YOUR_CLIENT_ID_HERE" 13 | # userID = "YOUR_USER_ID_HERE" 14 | # XTS_API_BASE_URL = "https://xts-api.trading" 15 | # source = "WEBAPI" 16 | 17 | "Note : For dealer credentials add the clientID and for investor client leave the clientID blank" 18 | 19 | """Dealer credentials""" 20 | API_KEY = "096328d98d30f4c551c503" 21 | API_SECRET = "Letp625#gV" 22 | clientID = "ATHARVA1" 23 | userID = "" 24 | XTS_API_BASE_URL = "https://developers.symphonyfintech.in" 25 | source = "WEBAPI" 26 | 27 | """Investor client credentials""" 28 | # API_KEY = "431c75e076e238bdff8176" 29 | # API_SECRET = "Ieil074#FH" 30 | # clientID = "RUCHA" 31 | # XTS_API_BASE_URL = "https://developers.symphonyfintech.in" 32 | # source = "WEBAPI" 33 | 34 | """Make XTSConnect object by passing your interactive API appKey, secretKey and source""" 35 | xt = XTSConnect(API_KEY, API_SECRET, source) 36 | 37 | """Using the xt object we created call the interactive login Request""" 38 | response = xt.interactive_login() 39 | print("Login: ", response) 40 | 41 | 42 | """Place Order Request""" 43 | response = xt.place_order( 44 | exchangeSegment=xt.EXCHANGE_NSECM, 45 | exchangeInstrumentID=2885, 46 | productType=xt.PRODUCT_MIS, 47 | orderType=xt.ORDER_TYPE_MARKET, 48 | orderSide=xt.TRANSACTION_TYPE_BUY, 49 | timeInForce=xt.VALIDITY_DAY, 50 | disclosedQuantity=0, 51 | orderQuantity=10, 52 | limitPrice=0, 53 | stopPrice=0, 54 | apiOrderSource="", 55 | orderUniqueIdentifier="454845", 56 | clientID=clientID) 57 | 58 | print("Place Order: ", response) 59 | 60 | 61 | clientID = "ATHARVA1" 62 | """Order book Request""" 63 | response = xt.get_order_book(clientID) 64 | print("Order Book: ", response) 65 | 66 | # extracting the order id from response 67 | if response['type'] != 'error': 68 | OrderID = response['result']['AppOrderID'] 69 | 70 | """Get Order History Request""" 71 | response = xt.get_order_history(appOrderID=OrderID,clientID=clientID) 72 | print("Order History: ", response) 73 | 74 | """Modify Order Request""" 75 | response = xt.modify_order( 76 | appOrderID=OrderID, 77 | modifiedProductType=xt.PRODUCT_NRML, 78 | modifiedOrderType=xt.ORDER_TYPE_LIMIT, 79 | modifiedOrderQuantity=8, 80 | modifiedDisclosedQuantity=0, 81 | modifiedLimitPrice=1405, 82 | modifiedStopPrice=0, 83 | modifiedTimeInForce=xt.VALIDITY_DAY, 84 | orderUniqueIdentifier="454845", 85 | clientID=clientID 86 | ) 87 | print("Modify Order: ", response) 88 | 89 | """Cancel Orders Request""" 90 | response = xt.cancel_order( 91 | appOrderID=OrderID, 92 | orderUniqueIdentifier='454845', 93 | clientID=clientID) 94 | print("Cancel Order: ", response) 95 | 96 | """Get Order History Request""" 97 | response = xt.get_order_history(appOrderID=OrderID,clientID=clientID) 98 | print("Order History: ", response) 99 | 100 | 101 | """Place BracketOrder Request""" 102 | response = xt.place_bracketorder( 103 | exchangeSegment=xt.EXCHANGE_NSECM, 104 | exchangeInstrumentID=2885, 105 | orderType=xt.ORDER_TYPE_MARKET, 106 | orderSide=xt.TRANSACTION_TYPE_BUY, 107 | disclosedQuantity=0, 108 | orderQuantity=10, 109 | limitPrice=59, 110 | squarOff=1, 111 | stopLossPrice=1, 112 | trailingStoploss=1, 113 | isProOrder=False, 114 | apiOrderSource="", 115 | orderUniqueIdentifier="454845" 116 | ) 117 | print("Bracket Order: ", response) 118 | 119 | # extracting the order id from response 120 | if response['type'] != 'error': 121 | OrderID = response['result']['AppOrderID'] 122 | 123 | """Cancel BracketOrder Request""" 124 | res = xt.bracketorder_cancel(OrderID) 125 | print("Bracket Cancel: ", response) 126 | 127 | """Modify BracketOrder Request""" 128 | response = xt.modify_order( 129 | appOrderID=OrderID, 130 | orderQuantity=8, 131 | limitPrice=1405, 132 | stopPrice=0, 133 | clientID=clientID 134 | ) 135 | print("Modify BracketOrder: ", response) 136 | 137 | 138 | """Get Profile Request""" 139 | response = xt.get_profile(clientID=userID) 140 | print("Profile: ", response) 141 | 142 | """Get Balance Request""" 143 | response = xt.get_balance(clientID=clientID) 144 | print("Balance: ", response) 145 | 146 | """Get Trade Book Request""" 147 | response = xt.get_trade(clientID=clientID) 148 | print("Trade Book: ", response) 149 | 150 | """Get Holdings Request""" 151 | response = xt.get_holding(clientID=clientID) 152 | print("Holdings: ", response) 153 | 154 | """Get Position by DAY Request""" 155 | response = xt.get_position_daywise(clientID=clientID) 156 | print("Position by Day: ", response) 157 | 158 | """Get Position by NET Request""" 159 | response = xt.get_position_netwise(clientID=clientID) 160 | print("Position by Net: ", response) 161 | 162 | """Get Dealer Position by NET Request""" 163 | response = xt.get_dealerposition_daywise(clientID=clientID) 164 | print("Dealer Position by Net: ", response) 165 | 166 | """Get Dealer Position by DAY Request""" 167 | response = xt.get_dealerposition_netwise(clientID=clientID) 168 | print("Dealer Position by Day: ", response) 169 | 170 | 171 | """Dealer Order book Request""" 172 | response = xt.get_dealer_orderbook(clientID) 173 | print("Dealer Order Book: ", response) 174 | 175 | """Get Dealer Trade Book Request""" 176 | response = xt.get_dealer_tradebook(clientID=clientID) 177 | print("Dealer Trade Book: ", response) 178 | 179 | """Position Convert Request""" 180 | response = xt.convert_position( 181 | exchangeSegment=xt.EXCHANGE_NSECM, 182 | exchangeInstrumentID=2885, 183 | targetQty=10, 184 | isDayWise=True, 185 | oldProductType=xt.PRODUCT_MIS, 186 | newProductType=xt.PRODUCT_NRML, 187 | clientID=clientID) 188 | print("Position Convert: ", response) 189 | 190 | """Place Cover Order Request""" 191 | response = xt.place_cover_order( 192 | exchangeSegment=xt.EXCHANGE_NSECM, 193 | exchangeInstrumentID=2885, 194 | orderSide=xt.TRANSACTION_TYPE_BUY, 195 | orderType=xt.ORDER_TYPE_LIMIT, 196 | orderQuantity=2, 197 | disclosedQuantity=0, 198 | limitPrice=1802, 199 | stopPrice=1899, 200 | apiOrderSource="", 201 | orderUniqueIdentifier="454845", 202 | clientID=clientID) 203 | print("Cover Order:", response) 204 | 205 | # extracting the order id from response 206 | if response['type'] != 'error': 207 | OrderID = response['result']['ExitAppOrderID'] 208 | 209 | """Exit Cover Order Request""" 210 | response = xt.exit_cover_order(appOrderID=OrderID, clientID=clientID) 211 | print("Exit Cover Order:", response) 212 | 213 | """Cancel all Orders Request""" 214 | response = xt.cancelall_order(exchangeInstrumentID=22,exchangeSegment=xt.EXCHANGE_NSECM) 215 | print("Cancel all Orders: ", response) 216 | 217 | """Interactive logout Request""" 218 | response = xt.interactive_logout(clientID=clientID) 219 | print("Interactive Logout: ", response) 220 | 221 | exit() 222 | 223 | # ---------------------------------------------------------------------------------------------------------------------- 224 | # Marketdata 225 | # ---------------------------------------------------------------------------------------------------------------------- 226 | 227 | # Marketdata API Credentials 228 | API_KEY = "YOUR_API_KEY_HERE" 229 | API_SECRET = "YOUR_API_SECRET_HERE" 230 | XTS_API_BASE_URL = "https://xts-api.trading" 231 | source = "WEBAPI" 232 | 233 | """Dealer login credentials""" 234 | API_KEY = "22f6f9dad2fe3982419756" 235 | API_SECRET = "Bxtj027$Dr" 236 | XTS_API_BASE_URL = "https://developers.symphonyfintech.in" 237 | source = "WEBAPI" 238 | 239 | """Investor client login credentials""" 240 | API_KEY = "76179cbae91810ddda7774" 241 | API_SECRET = "Bylg203#fv" 242 | XTS_API_BASE_URL = "https://developers.symphonyfintech.in" 243 | source = "WEBAPI" 244 | 245 | """Make the XTSConnect Object with Marketdata API appKey, secretKey and source""" 246 | xt = XTSConnect(API_KEY, API_SECRET, source) 247 | 248 | """Using the object we call the login function Request""" 249 | response = xt.marketdata_login() 250 | print("MarketData Login: ", response) 251 | 252 | """Get Config Request""" 253 | response = xt.get_config() 254 | print('Config :', response) 255 | 256 | """instruments list""" 257 | instruments = [ 258 | {'exchangeSegment': 1, 'exchangeInstrumentID': 2885}, 259 | {'exchangeSegment': 1, 'exchangeInstrumentID': 22}] 260 | 261 | """Get Quote Request""" 262 | response = xt.get_quote( 263 | Instruments=instruments, 264 | xtsMessageCode=1502, 265 | publishFormat='JSON') 266 | print('Quote :', response) 267 | 268 | """Send Subscription Request""" 269 | response = xt.send_subscription( 270 | Instruments=instruments, 271 | xtsMessageCode=1502) 272 | print('Subscribe :', response) 273 | 274 | """Send Unsubscription Request""" 275 | response = xt.send_unsubscription( 276 | Instruments=instruments, 277 | xtsMessageCode=1502) 278 | print('Unsubscribe :', response) 279 | 280 | """Get Master Instruments Request""" 281 | exchangesegments = [xt.EXCHANGE_NSECM, xt.EXCHANGE_NSEFO] 282 | response = xt.get_master(exchangeSegmentList=exchangesegments) 283 | print("Master: " + str(response)) 284 | 285 | """Get OHLC Request""" 286 | response = xt.get_ohlc( 287 | exchangeSegment=xt.EXCHANGE_NSECM, 288 | exchangeInstrumentID=22, 289 | startTime='Jan 04 2025 090000', 290 | endTime='Jan 04 2019 150000', 291 | compressionValue='60') 292 | print("OHLC: " + str(response)) 293 | 294 | """Get Series Request""" 295 | response = xt.get_series(exchangeSegment=1) 296 | print('Series:', str(response)) 297 | 298 | """Get Equity Symbol Request""" 299 | response = xt.get_equity_symbol( 300 | exchangeSegment=1, 301 | series='EQ', 302 | symbol='Acc') 303 | print('Equity Symbol:', str(response)) 304 | 305 | """Get Expiry Date Request""" 306 | response = xt.get_expiry_date( 307 | exchangeSegment=2, 308 | series='FUTIDX', 309 | symbol='NIFTY') 310 | print('Expiry Date:', str(response)) 311 | 312 | """Get Future Symbol Request""" 313 | response = xt.get_future_symbol( 314 | exchangeSegment=2, 315 | series='FUTIDX', 316 | symbol='NIFTY', 317 | expiryDate='28MAY25JUN') 318 | print('Future Symbol:', str(response)) 319 | 320 | """Get Option Symbol Request""" 321 | response = xt.get_option_symbol( 322 | exchangeSegment=2, 323 | series='OPTIDX', 324 | symbol='NIFTY', 325 | expiryDate='26Mar2020', 326 | optionType='CE', 327 | strikePrice=10000) 328 | print('Option Symbol:', str(response)) 329 | 330 | """Get Option Type Request""" 331 | response = xt.get_option_type( 332 | exchangeSegment=2, 333 | series='OPTIDX', 334 | symbol='NIFTY', 335 | expiryDate='26Mar2020') 336 | print('Option Type:', str(response)) 337 | 338 | """Get Index List Request""" 339 | response = xt.get_index_list(exchangeSegment=xt.EXCHANGE_NSECM) 340 | print('Index List:', str(response)) 341 | 342 | """Search Instrument by ID Request""" 343 | response = xt.search_by_instrumentid(Instruments=instruments) 344 | print('Search By Instrument ID:', str(response)) 345 | 346 | """Search Instrument by Scriptname Request""" 347 | response = xt.search_by_scriptname(searchString='REL') 348 | print('Search By Symbol :', str(response)) 349 | 350 | """Marketdata Logout Request""" 351 | response = xt.marketdata_logout() 352 | print('Marketdata Logout :', str(response)) 353 | -------------------------------------------------------------------------------- /Exception.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | from requests import exceptions 4 | from requests.exceptions import HTTPError 5 | from requests import ConnectTimeout, HTTPError, Timeout, ConnectionError 6 | 7 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 8 | Here we have declared all the exception and responses 9 | If there is any exception occurred we have this code to convey the messages 10 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 11 | 12 | 13 | class XTSException(Exception): 14 | """ 15 | Base exception class representing a XTS client exception. 16 | 17 | Every specific XTS client exception is a subclass of this 18 | and exposes two instance variables `.code` (HTTP error code) 19 | and `.message` (error text). 20 | """ 21 | 22 | def __init__(self, message, code=500): 23 | """Initialize the exception.""" 24 | super(XTSException, self).__init__(message) 25 | self.code = code 26 | 27 | 28 | class XTSGeneralException(XTSException): 29 | """An unclassified, general error. Default code is 500.""" 30 | 31 | def __init__(self, message, code=500): 32 | """Initialize the exception.""" 33 | super(XTSGeneralException, self).__init__(message, code) 34 | 35 | 36 | class XTSTokenException(XTSException): 37 | """Represents all token and authentication related errors. Default code is 400.""" 38 | 39 | def __init__(self, message, code=400): 40 | """Initialize the exception.""" 41 | super(XTSTokenException, self).__init__(message, code) 42 | 43 | 44 | class XTSPermissionException(XTSException): 45 | """Represents permission denied exceptions for certain calls. Default code is 400.""" 46 | 47 | def __init__(self, message, code=400): 48 | """Initialize the exception.""" 49 | super(XTSPermissionException, self).__init__(message, code) 50 | 51 | 52 | class XTSOrderException(XTSException): 53 | """Represents all order placement and manipulation errors. Default code is 500.""" 54 | 55 | def __init__(self, message, code=400): 56 | """Initialize the exception.""" 57 | super(XTSOrderException, self).__init__(message, code) 58 | 59 | 60 | class XTSInputException(XTSException): 61 | """Represents user input errors such as missing and invalid parameters. Default code is 400.""" 62 | 63 | def __init__(self, message, code=400): 64 | """Initialize the exception.""" 65 | super(XTSInputException, self).__init__(message, code) 66 | 67 | 68 | class XTSDataException(XTSException): 69 | """Represents a bad response from the backend Order Management System (OMS). Default code is 500.""" 70 | 71 | def __init__(self, message, code=500): 72 | """Initialize the exception.""" 73 | super(XTSDataException, self).__init__(message, code) 74 | 75 | 76 | class XTSNetworkException(XTSException): 77 | """Represents a network issue between XTS and the backend Order Management System (OMS). Default code is 500.""" 78 | 79 | def __init__(self, message, code=500): 80 | """Initialize the exception.""" 81 | super(XTSNetworkException, self).__init__(message, code) 82 | -------------------------------------------------------------------------------- /InteractiveSocketClient.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import os 3 | 4 | import socketio 5 | 6 | 7 | class OrderSocket_io(socketio.Client): 8 | """A Socket.IO client. 9 | This class implements a fully compliant Socket.IO web client with support 10 | for websocket and long-polling transports. 11 | :param reconnection: 'True'. if the client should automatically attempt to 12 | reconnect to the server after an interruption, or 13 | 'False' to not reconnect. The default is 'True'. 14 | :param reconnection_attempts: How many reconnection attempts to issue 15 | before giving up, or 0 for infinity attempts. 16 | The default is 0. 17 | :param reconnection_delay: How long to wait in seconds before the first 18 | reconnection attempt. Each successive attempt 19 | doubles this delay. 20 | :param reconnection_delay_max: The maximum delay between reconnection 21 | attempts. 22 | :param randomization_factor: Randomization amount for each delay between 23 | reconnection attempts. The default is 0.5, 24 | which means that each delay is randomly 25 | adjusted by +/- 50%. 26 | :param logger: To enable logging set to 'True' or pass a logger object to 27 | use. To disable logging set to 'False'. The default is 28 | 'False'. 29 | :param binary: 'True' to support binary payloads, 'False' to treat all 30 | payloads as text. On Python 2, if this is set to 'True', 31 | 'unicode' values are treated as text, and 'str' and 32 | 'bytes' values are treated as binary. This option has no 33 | effect on Python 3, where text and binary payloads are 34 | always automatically discovered. 35 | :param json: An alternative json module to use for encoding and decoding 36 | packets. Custom json modules must have 'dumps' and 'loads' 37 | functions that are compatible with the standard library 38 | versions. 39 | """ 40 | 41 | def __init__(self, token, userID, reconnection=True, reconnection_attempts=0, reconnection_delay=1, 42 | reconnection_delay_max=50000, randomization_factor=0.5, logger=False, binary=False, json=None, 43 | **kwargs): 44 | self.sid = socketio.Client(logger=True, engineio_logger=True) 45 | self.eventlistener = self.sid 46 | self.sid.on('connect', self.on_connect) 47 | self.sid.on('message', self.on_message) 48 | self.sid.on('joined', self.on_joined) 49 | self.sid.on('error', self.on_error) 50 | self.sid.on('order', self.on_order) 51 | self.sid.on('trade', self.on_trade) 52 | self.sid.on('position', self.on_position) 53 | self.sid.on('tradeConversion', self.on_tradeconversion) 54 | self.sid.on('logout', self.on_messagelogout) 55 | self.sid.on('disconnect', self.on_disconnect) 56 | 57 | self.userID = userID 58 | self.token = token 59 | 60 | """Get root url from config file""" 61 | currDirMain = os.getcwd() 62 | configParser = configparser.RawConfigParser() 63 | configFilePath = os.path.join(currDirMain, 'config.ini') 64 | configParser.read(configFilePath) 65 | self.port = configParser.get('root_url', 'root').strip() 66 | 67 | port = f'{self.port}/?token=' 68 | 69 | self.connection_url = port + self.token + '&userID=' + self.userID + "&apiType=INTERACTIVE" 70 | 71 | def connect(self, headers={}, transports='websocket', namespaces=None, socketio_path='/interactive/socket.io', 72 | verify=False): 73 | """Connect to a Socket.IO server. 74 | :param url: The URL of the Socket.IO server. It can include custom 75 | query string parameters if required by the server. 76 | :param headers: A dictionary with custom headers to send with the 77 | connection request. 78 | :param transports: The list of allowed transports. Valid transports 79 | are 'polling' and 'websocket'. If not 80 | given, the polling transport is connected first, 81 | then an upgrade to websocket is attempted. 82 | :param namespaces: The list of custom namespaces to connect, in 83 | addition to the default namespace. If not given, 84 | the namespace list is obtained from the registered 85 | event handlers. 86 | :param socketio_path: The endpoint where the Socket.IO server is 87 | installed. The default value is appropriate for 88 | most cases. 89 | 90 | """ 91 | """Connect to the socket.""" 92 | url = self.connection_url 93 | 94 | """Connected to the socket.""" 95 | self.sid.connect(url, headers, transports, namespaces, socketio_path) 96 | self.sid.wait() 97 | """Disconnect from the socket.""" 98 | # self.sid.disconnect() 99 | 100 | def on_connect(self): 101 | """Connect from the socket""" 102 | print('Interactive socket connected successfully!') 103 | 104 | def on_message(self): 105 | """On message from socket""" 106 | print('I received a message!') 107 | 108 | def on_joined(self, data): 109 | """On socket joined""" 110 | print('Interactive socket joined successfully!' + data) 111 | 112 | def on_error(self, data): 113 | """On receiving error from socket""" 114 | print('Interactive socket error!' + data) 115 | 116 | def on_order(self, data): 117 | """On receiving order placed data from socket""" 118 | print("Order placed!" + data) 119 | 120 | def on_trade(self, data): 121 | """On receiving trade data from socket""" 122 | print("Trade Received!" + data) 123 | 124 | def on_position(self, data): 125 | """On receiving position data from socket""" 126 | print("Position Retrieved!" + data) 127 | 128 | def on_tradeconversion(self, data): 129 | """On receiving trade conversion data from socket""" 130 | print("Trade Conversion Received!" + data) 131 | 132 | def on_messagelogout(self, data): 133 | """On receiving user logout message""" 134 | print("User logged out!" + data) 135 | 136 | def on_disconnect(self): 137 | """On receiving disconnection from socket""" 138 | print('Interactive Socket disconnected!') 139 | 140 | def get_emitter(self): 141 | """For getting event listener""" 142 | return self.eventlistener 143 | -------------------------------------------------------------------------------- /InteractiveSocketExample.py: -------------------------------------------------------------------------------- 1 | from Connect import XTSConnect 2 | from InteractiveSocketClient import OrderSocket_io 3 | 4 | # Interactive API Credentials 5 | API_KEY = "cccc0397aefe029ce61392" 6 | API_SECRET = "Aoxf623#aa" 7 | source = "WEBAPI" 8 | 9 | # Initialise 10 | xt = XTSConnect(API_KEY, API_SECRET, source) 11 | 12 | # Login for authorization token 13 | response = xt.interactive_login() 14 | 15 | # Store the token and userid 16 | set_interactiveToken = response['result']['token'] 17 | set_iuserID = response['result']['userID'] 18 | print("Login: ", response) 19 | 20 | # Connecting to Interactive socket 21 | soc = OrderSocket_io(set_interactiveToken, set_iuserID) 22 | 23 | 24 | # Callback for connection 25 | def on_connect(): 26 | """Connect from the socket.""" 27 | print('Interactive socket connected successfully!') 28 | 29 | 30 | # Callback for receiving message 31 | def on_message(): 32 | print('I received a message!') 33 | 34 | 35 | # Callback for joined event 36 | def on_joined(data): 37 | print('Interactive socket joined successfully!' + data) 38 | 39 | 40 | # Callback for error 41 | def on_error(data): 42 | print('Interactive socket error!' + data) 43 | 44 | 45 | # Callback for order 46 | def on_order(data): 47 | print("Order placed!" + data) 48 | 49 | 50 | # Callback for trade 51 | def on_trade(data): 52 | print("Trade Received!" + data) 53 | 54 | 55 | # Callback for position 56 | def on_position(data): 57 | print("Position Retrieved!" + data) 58 | 59 | 60 | # Callback for trade conversion event 61 | def on_tradeconversion(data): 62 | print("Trade Conversion Received!" + data) 63 | 64 | 65 | # Callback for message logout 66 | def on_messagelogout(data): 67 | print("User logged out!" + data) 68 | 69 | 70 | # Callback for disconnection 71 | def on_disconnect(): 72 | print('Interactive Socket disconnected!') 73 | 74 | 75 | # Assign the callbacks. 76 | soc.on_connect = on_connect 77 | soc.on_message = on_message 78 | soc.on_joined = on_joined 79 | soc.on_error = on_error 80 | soc.on_order = on_order 81 | soc.on_trade = on_trade 82 | soc.on_position = on_position 83 | soc.on_tradeconversion = on_tradeconversion 84 | soc.on_messagelogout = on_messagelogout 85 | soc.on_disconnect = on_disconnect 86 | 87 | # Event listener 88 | el = soc.get_emitter() 89 | el.on('connect', on_connect) 90 | el.on('order', on_order) 91 | el.on('trade', on_trade) 92 | el.on('position', on_position) 93 | el.on('tradeConversion', on_tradeconversion) 94 | 95 | # Infinite loop on the main thread. Nothing after this will run. 96 | # You have to use the pre-defined callbacks to manage subscriptions. 97 | soc.connect() 98 | -------------------------------------------------------------------------------- /MarketDataSocketClient.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import os 3 | from datetime import datetime 4 | 5 | import socketio 6 | 7 | 8 | class MDSocket_io(socketio.Client): 9 | """A Socket.IO client. 10 | This class implements a fully compliant Socket.IO web client with support 11 | for websocket and long-polling transports. 12 | :param reconnection: 'True'. if the client should automatically attempt to 13 | reconnect to the server after an interruption, or 14 | 'False' to not reconnect. The default is 'True'. 15 | :param reconnection_attempts: How many reconnection attempts to issue 16 | before giving up, or 0 for infinity attempts. 17 | The default is 0. 18 | :param reconnection_delay: How long to wait in seconds before the first 19 | reconnection attempt. Each successive attempt 20 | doubles this delay. 21 | :param reconnection_delay_max: The maximum delay between reconnection 22 | attempts. 23 | :param randomization_factor: Randomization amount for each delay between 24 | reconnection attempts. The default is 0.5, 25 | which means that each delay is randomly 26 | adjusted by +/- 50%. 27 | :param logger: To enable logging set to 'True' or pass a logger object to 28 | use. To disable logging set to 'False'. The default is 29 | 'False'. 30 | :param binary: 'True' to support binary payloads, 'False' to treat all 31 | payloads as text. On Python 2, if this is set to 'True', 32 | 'unicode' values are treated as text, and 'str' and 33 | 'bytes' values are treated as binary. This option has no 34 | effect on Python 3, where text and binary payloads are 35 | always automatically discovered. 36 | :param json: An alternative json module to use for encoding and decoding 37 | packets. Custom json modules must have 'dumps' and 'loads' 38 | functions that are compatible with the standard library 39 | versions. 40 | """ 41 | 42 | def __init__(self, token, userID, reconnection=True, reconnection_attempts=0, reconnection_delay=1, 43 | reconnection_delay_max=50000, randomization_factor=0.5, logger=False, binary=False, json=None, 44 | **kwargs): 45 | self.sid = socketio.Client(logger=False, engineio_logger=False) 46 | self.eventlistener = self.sid 47 | 48 | self.sid.on('connect', self.on_connect) 49 | self.sid.on('message', self.on_message) 50 | 51 | """Similarly implement partial json full and binary json full.""" 52 | self.sid.on('1501-json-full', self.on_message1501_json_full) 53 | self.sid.on('1501-json-partial', self.on_message1501_json_partial) 54 | 55 | self.sid.on('1502-json-full', self.on_message1502_json_full) 56 | self.sid.on('1502-json-partial', self.on_message1502_json_partial) 57 | 58 | self.sid.on('1505-json-full', self.on_message1505_json_full) 59 | self.sid.on('1505-json-partial', self.on_message1505_json_partial) 60 | 61 | 62 | self.sid.on('1510-json-full', self.on_message1510_json_full) 63 | self.sid.on('1510-json-partial', self.on_message1510_json_partial) 64 | 65 | self.sid.on('1512-json-full', self.on_message1512_json_full) 66 | self.sid.on('1512-json-partial', self.on_message1512_json_partial) 67 | 68 | self.sid.on('disconnect', self.on_disconnect) 69 | 70 | """Get the root url from config file""" 71 | currDirMain = os.getcwd() 72 | configParser = configparser.ConfigParser() 73 | configFilePath = os.path.join(currDirMain, 'config.ini') 74 | configParser.read(configFilePath) 75 | 76 | self.port = configParser.get('root_url', 'root') 77 | self.userID = userID 78 | publishFormat = 'JSON' 79 | self.broadcastMode = configParser.get('root_url', 'broadcastMode') 80 | self.token = token 81 | 82 | port = f'{self.port}/?token=' 83 | 84 | self.connection_url = port + token + '&userID=' + self.userID + '&publishFormat=' + publishFormat + '&broadcastMode=' + self.broadcastMode 85 | 86 | def connect(self, headers={}, transports='websocket', namespaces=None, socketio_path='/apimarketdata/socket.io', 87 | verify=False): 88 | """Connect to a Socket.IO server. 89 | :param verify: Verify SSL 90 | :param url: The URL of the Socket.IO server. It can include custom 91 | query string parameters if required by the server. 92 | :param headers: A dictionary with custom headers to send with the 93 | connection request. 94 | :param transports: The list of allowed transports. Valid transports 95 | are 'polling' and 'websocket'. If not 96 | given, the polling transport is connected first, 97 | then an upgrade to websocket is attempted. 98 | :param namespaces: The list of custom namespaces to connect, in 99 | addition to the default namespace. If not given, 100 | the namespace list is obtained from the registered 101 | event handlers. 102 | :param socketio_path: The endpoint where the Socket.IO server is 103 | installed. The default value is appropriate for 104 | most cases. 105 | 106 | self.url = self.connection_url 107 | self.connection_headers = headers 108 | self.connection_transports = transports 109 | self.connection_namespaces = namespaces 110 | self.socketio_path = socketio_path 111 | 112 | Connect to the socket. 113 | """ 114 | url = self.connection_url 115 | """Connected to the socket.""" 116 | self.sid.connect(url, headers, transports, namespaces, socketio_path) 117 | self.sid.wait() 118 | """Disconnected from the socket.""" 119 | # self.sid.disconnect() 120 | 121 | def on_connect(self): 122 | """Connect from the socket.""" 123 | print('Market Data Socket connected successfully!') 124 | 125 | def on_message(self, data): 126 | """On receiving message""" 127 | print('I received a message!' + data) 128 | 129 | def on_message1502_json_full(self, data): 130 | """On receiving message code 1502 full""" 131 | print('I received a 1502 Market depth message!' + data) 132 | 133 | def on_message1512_json_full(self, data): 134 | """On receiving message code 1512 full""" 135 | print('I received a 1512 LTP message!' + data) 136 | 137 | def on_message1505_json_full(self, data): 138 | """On receiving message code 1505 full""" 139 | print('I received a 1505 Candle data message!' + data) 140 | 141 | def on_message1510_json_full(self, data): 142 | """On receiving message code 1510 full""" 143 | print('I received a 1510 Open interest message!' + data) 144 | 145 | def on_message1501_json_full(self, data): 146 | """On receiving message code 1501 full""" 147 | print('I received a 1501 Level1,Touchline message!' + data) 148 | 149 | def on_message1502_json_partial(self, data): 150 | """On receiving message code 1502 partial""" 151 | print('I received a 1502 partial message!' + data) 152 | 153 | def on_message1512_json_partial(self, data): 154 | """On receiving message code 1512 partial""" 155 | print('I received a 1512 LTP message!' + data) 156 | 157 | def on_message1505_json_partial(self, data): 158 | """On receiving message code 1505 partial""" 159 | print('I received a 1505 Candle data message!' + data) 160 | 161 | def on_message1510_json_partial(self, data): 162 | """On receiving message code 1510 partial""" 163 | print('I received a 1510 Open interest message!' + data) 164 | 165 | def on_message1501_json_partial(self, data): 166 | """On receiving message code 1501 partial""" 167 | now = datetime.now() 168 | today = now.strftime("%H:%M:%S") 169 | print(today, 'in main 1501 partial Level1,Touchline message!' + data + ' \n') 170 | 171 | def on_disconnect(self): 172 | """Disconnected from the socket""" 173 | print('Market Data Socket disconnected!') 174 | 175 | def on_error(self, data): 176 | """Error from the socket""" 177 | print('Market Data Error', data) 178 | 179 | def get_emitter(self): 180 | """For getting the event listener""" 181 | return self.eventlistener 182 | -------------------------------------------------------------------------------- /MarketdataSocketExample.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from Connect import XTSConnect 4 | from MarketDataSocketClient import MDSocket_io 5 | 6 | # MarketData API Credentials 7 | API_KEY = "a27e231381266f63c62157" 8 | API_SECRET = "Akko754#C3" 9 | source = "WEBAPI" 10 | 11 | # Initialise 12 | xt = XTSConnect(API_KEY, API_SECRET, source) 13 | 14 | # Login for authorization token 15 | response = xt.marketdata_login() 16 | 17 | # Store the token and userid 18 | set_marketDataToken = response['result']['token'] 19 | set_muserID = response['result']['userID'] 20 | print("Login: ", response) 21 | 22 | # Connecting to Marketdata socket 23 | soc = MDSocket_io(set_marketDataToken, set_muserID) 24 | 25 | # Instruments for subscribing 26 | Instruments = [ 27 | {'exchangeSegment': 1, 'exchangeInstrumentID': 2885}, 28 | {'exchangeSegment': 1, 'exchangeInstrumentID': 26000}, 29 | {'exchangeSegment': 2, 'exchangeInstrumentID': 35013} 30 | ] 31 | 32 | # Callback for connection 33 | def on_connect(): 34 | """Connect from the socket.""" 35 | print('Market Data Socket connected successfully!') 36 | 37 | # # Subscribe to instruments 38 | print('Sending subscription request for Instruments - \n' + str(Instruments)) 39 | response = xt.send_subscription(Instruments, 1501) 40 | print('Sent Subscription request!') 41 | print("Subscription response: ", response) 42 | 43 | # Callback on receiving message 44 | def on_message(data): 45 | print('I received a message!') 46 | 47 | # Callback for message code 1501 FULL 48 | def on_message1501_json_full(data): 49 | print('I received a 1501 Touchline message!' + data) 50 | 51 | # Callback for message code 1502 FULL 52 | def on_message1502_json_full(data): 53 | print('I received a 1502 Market depth message!' + data) 54 | 55 | # Callback for message code 1505 FULL 56 | def on_message1505_json_full(data): 57 | print('I received a 1505 Candle data message!' + data) 58 | 59 | # Callback for message code 1510 FULL 60 | def on_message1510_json_full(data): 61 | print('I received a 1510 Open interest message!' + data) 62 | 63 | # Callback for message code 1512 FULL 64 | def on_message1512_json_full(data): 65 | print('I received a 1512 Level1,LTP message!' + data) 66 | 67 | # Callback for message code 1501 PARTIAL 68 | def on_message1501_json_partial(data): 69 | print('I received a 1501, Touchline Event message!' + data) 70 | 71 | # Callback for message code 1502 PARTIAL 72 | def on_message1502_json_partial(data): 73 | print('I received a 1502 Market depth message!' + data) 74 | 75 | # Callback for message code 1505 PARTIAL 76 | def on_message1505_json_partial(data): 77 | print('I received a 1505 Candle data message!' + data) 78 | 79 | # Callback for message code 1510 PARTIAL 80 | def on_message1510_json_partial(data): 81 | print('I received a 1510 Open interest message!' + data) 82 | 83 | # Callback for message code 1512 PARTIAL 84 | def on_message1512_json_partial(data): 85 | print('I received a 1512, LTP Event message!' + data) 86 | 87 | # Callback for disconnection 88 | def on_disconnect(): 89 | print('Market Data Socket disconnected!') 90 | 91 | 92 | # Callback for error 93 | def on_error(data): 94 | """Error from the socket.""" 95 | print('Market Data Error', data) 96 | 97 | 98 | # Assign the callbacks. 99 | soc.on_connect = on_connect 100 | soc.on_message = on_message 101 | soc.on_message1502_json_full = on_message1502_json_full 102 | soc.on_message1505_json_full = on_message1505_json_full 103 | soc.on_message1510_json_full = on_message1510_json_full 104 | soc.on_message1501_json_full = on_message1501_json_full 105 | soc.on_message1512_json_full = on_message1512_json_full 106 | soc.on_message1502_json_partial = on_message1502_json_partial 107 | soc.on_message1505_json_partial = on_message1505_json_partial 108 | soc.on_message1510_json_partial = on_message1510_json_partial 109 | soc.on_message1501_json_partial = on_message1501_json_partial 110 | soc.on_message1512_json_partial = on_message1512_json_partial 111 | soc.on_disconnect = on_disconnect 112 | soc.on_error = on_error 113 | 114 | 115 | # Event listener 116 | el = soc.get_emitter() 117 | el.on('connect', on_connect) 118 | el.on('1501-json-full', on_message1501_json_full) 119 | el.on('1502-json-full', on_message1502_json_full) 120 | el.on('1505-json-full', on_message1505_json_full) 121 | el.on('1510-json-full', on_message1510_json_full) 122 | el.on('1512-json-full', on_message1512_json_full) 123 | el.on('1501-json-partial', on_message1501_json_partial) 124 | el.on('1502-json-partial', on_message1502_json_partial) 125 | el.on('1505-json-partial', on_message1505_json_partial) 126 | el.on('1510-json-partial', on_message1510_json_partial) 127 | el.on('1512-json-partial', on_message1512_json_partial) 128 | 129 | 130 | # Infinite loop on the main thread. Nothing after this will run. 131 | # You have to use the pre-defined callbacks to manage subscriptions. 132 | soc.connect() 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XTS-SDK-Client Python 2 | 3 | This is the XTS Python API Client library , which has both Marketdata and Interactive services. 4 | API Documentation for XTS-MarketData API and XTS-Trading API can be found in the below link. 5 | 6 | https://symphonyfintech.com/xts-market-data-front-end-api/ 7 | 8 | https://symphonyfintech.com/xts-trading-front-end-api-v2/ 9 | 10 | ## Installation 11 | 12 | ### Prerequisites 13 | 14 | Python 3.8 or above. 15 | Internet Access. 16 | 17 | Execute below command: 18 | pip install -r requirements.txt 19 | 20 | ### Usage 21 | Check the config.ini file, need to add the root url keep source as WEBAPI and disable_ssl as true 22 | ``` 23 | [user] 24 | source=WEBAPI 25 | 26 | [SSL] 27 | disable_ssl=True 28 | 29 | [root_url] 30 | root=https://developers.symphonyfintech.in 31 | broadcastMode=Full 32 | ``` 33 | 34 | #### Create XT Connect Object 35 | 36 | ```js 37 | """API Credentials""" 38 | API_KEY = "YOUR_API_KEY_HERE" 39 | API_SECRET = "YOUR_API_SECRET_HERE" 40 | XTS_API_BASE_URL = "https://xts-api.trading" 41 | source = "WEBAPI" 42 | 43 | """Make XTSConnect object by passing your interactive API appKey, secretKey and source""" 44 | xt = XTSConnect(API_KEY, API_SECRET, source) 45 | ``` 46 | 47 | #### Login 48 | To login into API call the login service which will return a token. This token will help you to access other services throughout the session. 49 | ```js 50 | """Marketdata Login""" 51 | response = xt.marketdata_login() 52 | 53 | """Interactive Login""" 54 | response = xt.interactive_login() 55 | 56 | ``` 57 | 58 | #### Subscribe 59 | To Subscribe to symbol use marketdata API. It returns Subscribe Response object which will contain the tick data like LTP, Open, High etc 60 | ```js 61 | """instruments list""" 62 | instruments = [{'exchangeSegment': 1, 'exchangeInstrumentID': 2885},{'exchangeSegment': 1, 'exchangeInstrumentID': 22}] 63 | 64 | """Send Subscription Request""" 65 | response = xt.send_subscription( 66 | Instruments=instruments, 67 | xtsMessageCode=1502) 68 | ``` 69 | 70 | #### Quotes 71 | Quote service returns Asks, Bids and Touchline 72 | ```js 73 | """instruments list""" 74 | instruments = [ 75 | {'exchangeSegment': 1, 'exchangeInstrumentID': 2885}, 76 | {'exchangeSegment': 1, 'exchangeInstrumentID': 22}] 77 | 78 | """Get Quote Request""" 79 | response = xt.get_quote( 80 | Instruments=instruments, 81 | xtsMessageCode=1504, 82 | publishFormat='JSON') 83 | ``` 84 | #### PlaceOrder 85 | To Place an order you need to use Interactive API. Response will contain an orderid. 86 | ```js 87 | """Place Order Request""" 88 | response = xt.place_order( 89 | exchangeSegment=xt.EXCHANGE_NSECM, 90 | exchangeInstrumentID=2885, 91 | productType=xt.PRODUCT_MIS, 92 | orderType=xt.ORDER_TYPE_MARKET, 93 | orderSide=xt.TRANSACTION_TYPE_BUY, 94 | timeInForce=xt.VALIDITY_DAY, 95 | disclosedQuantity=0, 96 | orderQuantity=10, 97 | limitPrice=0, 98 | stopPrice=0, 99 | orderUniqueIdentifier="454845") 100 | ``` 101 | 102 | #### CancelOrder 103 | To Cancel an order you need to user Interactive api and In response you will get orderid. 104 | ```js 105 | """Cancel Orders Request""" 106 | response = xt.cancel_order( 107 | appOrderID=OrderID, 108 | orderUniqueIdentifier='454845') 109 | ``` 110 | 111 | #### Streams and Events 112 | Events such as TouchLine, MarketData, CandleData, OpenInterest and Index are received from socket.To get those events XTSAPIMarketdataEvents interface needs to be implemented. 113 | Event will be received in the respective overridden methods. 114 | ```js 115 | # Callback for connection 116 | def on_connect(): 117 | """Connect from the socket.""" 118 | print('Market Data Socket connected successfully!') 119 | 120 | # Subscribe to instruments 121 | response = xt.send_subscription(Instruments, 1501) 122 | print("res: ", response) 123 | 124 | 125 | # Callback on receiving message 126 | def on_message(data): 127 | print('I received a message!') 128 | 129 | # Callback for message code 1502 FULL 130 | def on_message1502_json_full(data): 131 | print('I received a 1502 Market depth message!' + data) 132 | message = "1502 >> #{id} >> {data}".format(id=2885, data=data) 133 | sock.send(message) 134 | 135 | # Callback for message code 1505 FULL 136 | def on_message1505_json_full(data): 137 | print('I received a 1505 Candle data message!' + data) 138 | 139 | # Callback for message code 1510 FULL 140 | def on_message1510_json_full(data): 141 | print('I received a 1510 Open interest message!' + data) 142 | 143 | # Callback for message code 1501 FULL 144 | def on_message1501_json_full(data): 145 | print('I received a 1510 Level1,Touchline message!' + data) 146 | message = "1501 >> #{id} >> {data}".format(id=2885, data=data) 147 | sock.send(message) 148 | 149 | # Callback for message code 1502 PARTIAL 150 | def on_message1502_json_partial(data): 151 | print('I received a 1502 partial message!' + data) 152 | 153 | # Callback for message code 1505 PARTIAL 154 | def on_message1505_json_partial(data): 155 | print('I received a 1505 Candle data message!' + data) 156 | 157 | # Callback for message code 1510 PARTIAL 158 | def on_message1510_json_partial(data): 159 | print('I received a 1510 Open interest message!' + data) 160 | 161 | # Callback for message code 1501 PARTIAL 162 | def on_message1501_json_partial(data): 163 | now = datetime.now() 164 | today = now.strftime("%H:%M:%S") 165 | print(today, 'in main 1501 partial Level1,Touchline message!' + data + ' \n') 166 | print('I received a 1510 Level1,Touchline message!' + data) 167 | 168 | # Callback for disconnection 169 | def on_disconnect(): 170 | print('Market Data Socket disconnected!') 171 | 172 | # Callback for error 173 | def on_error(data): 174 | """Error from the socket.""" 175 | print('Market Data Error', data) 176 | ``` 177 | 178 | ### Examples 179 | Example code demonstrating how to use XTS Api can be found in xts-python-api-sdk 180 | 181 | Example.py : Examples of all the API calls for Interactive as well as Marketdata APIs 182 | 183 | InteractiveSocketExample.py : Interactive Socket Streaming Example 184 | 185 | MarketdataSocketExample.py : Marketdata Socket Streaming Example 186 | 187 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | XTS Connect API client for Python. 4 | 5 | Symphony Fintech Pvt. Ltd. 6 | 7 | License 8 | ------- 9 | XTSConnect Python library is licensed. 10 | 11 | The library 12 | ----------- 13 | XTS Connect is a set of RESTful APIs that expose 14 | many capabilities required to build a complete 15 | investment and trading platform. Execute orders in 16 | real time, manage user portfolio, stream live market 17 | data (WebSockets), and more, with the simple HTTP API collection 18 | 19 | This module provides an easy to use abstraction over the HTTP APIs. 20 | The HTTP calls have been converted to methods and their JSON responses 21 | are returned as native Python structures, for example, dicts, lists, bools etc. 22 | See the **[XTS Connect API documentation]** 23 | for the complete list of APIs, supported parameters and values, and response formats. 24 | 25 | """ 26 | 27 | # from __future__ import unicode_literals, absolute_import 28 | 29 | from XTConnect import Exception 30 | from XTConnect.Connect import XTSConnect 31 | 32 | __all__ = ["XTConnect", "Exception"] 33 | -------------------------------------------------------------------------------- /__version__.py: -------------------------------------------------------------------------------- 1 | __title__ = "XTS Connect" 2 | __description__ = "The official Python client for the XTS Connect Interactive Trading and Market Data API" 3 | __url__ = "https://www.symphonyfintech.com" 4 | __download_url__ = "" 5 | __version__ = "1.0.0" 6 | __author__ = "Symphony Fintech Pvt ltd." 7 | __author_email__ = "" 8 | __license__ = "" -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | [user] 2 | source=WEBAPI 3 | 4 | [SSL] 5 | disable_ssl=True 6 | 7 | [root_url] 8 | root=https://developers.symphonyfintech.in 9 | ;root=http://103.69.170.14:10332 10 | broadcastMode=Full -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bidict==0.21.2 2 | certifi==2020.12.5 3 | chardet==4.0.0 4 | idna==2.10 5 | python-engineio==3.13.0 6 | python-socketio==4.6.0 7 | requests==2.25.1 8 | six==1.15.0 9 | urllib3==1.26.4 10 | websocket-client==0.57.0 11 | --------------------------------------------------------------------------------