vortex_api
Vortex API client for Python -- Visit Api Center. Astha Credit & Securities Pvt. Ltd. (c) 2023
License
Rupeezy's Vortex Python library is licensed under the MIT License
The library
Vortex APIs are meant for clients who want to execute orders based on their own strategy programatically and for partners to build their own applications. These apis provide a fast and secure way to place trades, manage positions and access real time market data.
The python client provides an abstraction over these APIs in order to seamlessly write applications and atrategies without the hassle of managing the apis.
Getting started
#!python
from vortex_api import VortexAPI
client = VortexAPI("your api secret","your application id")
#For client login using SSO
client.login_url(callback_param="hi)
# Place order
client.place_order(client.EXCHANGE_NSE_EQUITY,22,client.TRANSACTION_TYPE_BUY,client.PRODUCT_DELIVERY,client.VARIETY_REGULAR_LIMIT_ORDER,1,1700,0,0,"DAY",1,True)
#Get order book
client.orders(limit=20,offset=1)
1""" 2Vortex API client for Python -- [Visit Api Center](https://vortex.rupeezy.in). 3Astha Credit & Securities Pvt. Ltd. (c) 2023 4 5License 6------- 7Rupeezy's Vortex Python library is licensed under the MIT License 8 9The library 10----------- 11Vortex APIs are meant for clients who want to execute orders based on their own strategy programatically and for partners to build their own applications. 12These apis provide a fast and secure way to place trades, manage positions and access real time market data. 13 14The python client provides an abstraction over these APIs in order to seamlessly write applications and atrategies without 15the hassle of managing the apis. 16 17Getting started 18--------------- 19 #!python 20 from vortex_api import VortexAPI 21 22 client = VortexAPI("your api secret","your application id") 23 24 #For client login using SSO 25 client.login_url(callback_param="hi) 26 27 # Place order 28 29 client.place_order(client.EXCHANGE_NSE_EQUITY,22,client.TRANSACTION_TYPE_BUY,client.PRODUCT_DELIVERY,client.VARIETY_REGULAR_LIMIT_ORDER,1,1700,0,0,"DAY",1,True) 30 31 #Get order book 32 client.orders(limit=20,offset=1) 33 34""" 35from __future__ import unicode_literals, absolute_import 36from vortex_api.api import VortexAPI,Constants 37from vortex_api.vortex_feed import VortexFeed 38__all__ = [VortexAPI,Constants,VortexFeed]
155@validate_selected_methods(['login','place_order','modify_order','cancel_order','get_order_margin','historical_candles','quotes']) 156class VortexAPI: 157 158 def __init__(self, api_key: str = None , application_id: str = None, base_url: str = "https://vortex-api.rupeezy.in/v2",enable_logging: bool=False) -> None: 159 """ 160 Constructor method for VortexAPI class. 161 162 Args: 163 api_key (str, optional): API key for the Vortex API. You can either pass it as an argument or set it as an environment variable named VORTEX_API_KEY. 164 application_id (str, optional): Application ID for the Vortex API. You can either pass it as an argument or set it as an environment variable named VORTEX_APPLICATION_ID. 165 base_url (str, optional): Base URL for the Vortex API. Defaults to "https://vortex-api.rupeezy.in/v2". 166 """ 167 if api_key == None: 168 if os.getenv("VORTEX_API_KEY") != None: 169 api_key = os.getenv("VORTEX_API_KEY") 170 else: 171 raise ValueError("API key must be provided either as an argument or through the VORTEX_API_KEY environment variable.") 172 173 if application_id == None: 174 if os.getenv("VORTEX_APPLICATION_ID") != None: 175 application_id = os.getenv("VORTEX_APPLICATION_ID") 176 else: 177 raise ValueError("Application ID must be provided either as an argument or through the VORTEX_APPLICATION_ID environment variable.") 178 self.api_key = api_key 179 self.application_id = application_id 180 181 if os.getenv("VORTEX_BASE_URL") != None: 182 self.base_url = os.getenv("VORTEX_BASE_URL") 183 else: 184 self.base_url = base_url 185 186 if os.getenv("VORTEX_ACCESS_TOKEN") != None: 187 self.access_token = os.getenv("VORTEX_ACCESS_TOKEN") 188 else: 189 self.access_token = None 190 191 self.enable_logging = enable_logging 192 if self.enable_logging: 193 logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') 194 195 def _make_api_request(self, method: str, endpoint: str, data: dict = None, params=None) -> dict: 196 """ 197 Private method to make HTTP requests to the Vortex API. 198 199 Args: 200 method (str): HTTP method for the request (e.g. "GET", "POST", "PUT", "DELETE"). 201 endpoint (str): API endpoint for the request. 202 data (dict, optional): Payload data for the request. Defaults to None. 203 204 Returns: 205 dict: Dictionary containing the response data from the API. 206 """ 207 if(self.access_token == None): 208 op = {} 209 op["status"]= "error" 210 op["message"] = "please login first" 211 return op 212 bearer_token = f"Bearer {self.access_token}" 213 headers = {"Content-Type": "application/json", "Authorization": bearer_token} 214 url = self.base_url + endpoint 215 if self.enable_logging: 216 logging.debug(f"Making network call to {url} , params: {params}, data: {data}, headers: {headers}") 217 response = requests.request(method, url, headers=headers, json=data,params=params) 218 if self.enable_logging: 219 logging.debug(f"Response received from {url} , body: {response.json()}") 220 response.raise_for_status() 221 return response.json() 222 223 def _make_unauth_request(self, method: str, endpoint: str, data: dict = None, params: dict = None) -> dict: 224 """ 225 Private method to make HTTP requests to the Vortex API. 226 227 Args: 228 method (str): HTTP method for the request (e.g. "GET", "POST", "PUT", "DELETE"). 229 endpoint (str): API endpoint for the request. 230 data (dict, optional): Payload data for the request. Defaults to None. 231 232 Returns: 233 dict: Dictionary containing the response data from the API. 234 """ 235 headers = {"Content-Type": "application/json", "x-api-key": self.api_key} 236 url = self.base_url + endpoint 237 if self.enable_logging: 238 logging.debug(f"Making network call to {url} , params: {params}, data: {data}, headers: {headers}") 239 response = requests.request(method, url, headers=headers, json=data) 240 response.raise_for_status() 241 if self.enable_logging: 242 logging.debug(f"Response received from {url} , body: {response.json()}") 243 return response.json() 244 245 def login(self, client_code: str, password: str, totp: str)->dict: 246 """ 247 Depricating Soon. Use SSO Login instead. Login using password and totp directly 248 249 Documentation: 250 https://vortex.rupeezy.in/docs/latest/authentication/ 251 252 Args: 253 client_code(str): Client Code of the account 254 password(str): Password of the account 255 totp(str): TOTP generated using third party apps like google authenticator etc. 256 257 Returns: 258 dict: JSON response containing the details of the user 259 """ 260 endpoint = "/user/login" 261 data = { 262 "client_code": client_code, 263 "password": password, 264 "totp": totp, 265 "application_id": self.application_id 266 } 267 res = self._make_unauth_request("POST", endpoint= endpoint, data=data) 268 self._setup_client_code(login_object=res) 269 return res 270 271 def download_master(self) -> dict: 272 """ 273 Download list of all available instruments and their details across all exchanges 274 275 Documentation: 276 https://vortex.rupeezy.in/docs/latest/historical/#instrument-list 277 278 Returns: 279 dict: CSV Array of all instruments. The first row contains headers 280 """ 281 endpoint = "https://static.rupeezy.in/master.csv" 282 with requests.Session() as s: 283 download = s.get(url=endpoint) 284 decoded_content = download.content.decode('utf-8') 285 cr = csv.reader(decoded_content.splitlines(), delimiter=',') 286 my_list = list(cr) 287 return my_list 288 289 def place_order(self,exchange: Constants.ExchangeTypes, token: int, transaction_type: Constants.TransactionSides, product: Constants.ProductTypes, variety: Constants.VarietyTypes, 290 quantity: int, price: float, trigger_price: float, disclosed_quantity: int, validity: Constants.ValidityTypes) -> dict: 291 """ 292 Place an order for a specific security 293 294 Documentation: 295 https://vortex.rupeezy.in/docs/latest/order/#placing-an-order 296 297 Args: 298 exchange (Constants.ExchangeTypes): Possible values: [NSE_EQ, NSE_FO, BSE_EQ, BSE_FO, NSE_CD or MCX_FO] 299 token (int): Security token of the scrip. It can be found in the scripmaster file 300 transaction_type (Constants.TransactionSides): Possible values: [BUY, SELL] 301 product (Constants.ProductTypes): Possible values: [INTRADAY, DELIVERY, MTF]. MTF product can only be used in NSE_EQ exchange. 302 variety (Constants.VarietyTypes): Possible values: [RL, RL-MKT, SL, SL-MKT]. RL means regular orders, SL means Stop Loss order. 303 MKT means that the trade will happen at market price 304 quantity (int): For exchange NSE_FO, if you want to trade in 2 lots and lot size is 50, you should pass 100. 305 In all other exchanges, you should pass just the number of lots. For example, in MCX_FO, 306 if you want to trade 5 lots, you should pass just 5. 307 price (float): Price should be an integer multiple of Tick Size. For example, IDEA's tick size is 0.05. 308 So the price entered can be 9.5 or 9.65. It cannot be 9.67. 309 In case of market orders, you should send the Last Trade Price received from the Quote API or Websocket API 310 trigger_price (float): To be used for Stop loss orders. For BUY side SL orders, trigger_price should be 311 lesser than price. for SELL side SL orders, trigger_price should be greater than price. 312 disclosed_quantity (int): Can be any number lesser than or equal to quantity, including 0 313 validity (Constants.ValidityTypes): Can be DAY for orders which are valid throughout the day, or IOC. 314 IOC order will be cancelled if it is not traded immediately 315 Returns: 316 dict: JSON response containing the details of the placed order 317 318 Raises: 319 HTTPError: If any HTTP error occurs during the API call 320 """ 321 322 endpoint = "/trading/orders/regular" 323 if validity == Constants.ValidityTypes.FULL_DAY: 324 validity_days = 1 325 is_amo = False 326 elif validity == Constants.ValidityTypes.IMMEDIATE_OR_CANCEL: 327 validity_days = 0 328 is_amo = False 329 else: 330 validity_days = 1 331 is_amo = True 332 333 data = { 334 "exchange": exchange, 335 "token": token, 336 "transaction_type": transaction_type, 337 "product": product, 338 "variety": variety, 339 "quantity": quantity, 340 "price": price, 341 "trigger_price": trigger_price, 342 "disclosed_quantity": disclosed_quantity, 343 "validity": validity, 344 "validity_days": validity_days, 345 "is_amo": is_amo 346 } 347 348 return self._make_api_request("POST", endpoint, data=data) 349 350 def modify_order(self, order_id: str, variety: Constants.VarietyTypes, quantity: int, traded_quantity: int, price: float, trigger_price: float, disclosed_quantity: int, validity: Constants.ValidityTypes) -> dict: 351 """ 352 Method to modify an order using the Vortex API. 353 354 Documentation: 355 https://vortex.rupeezy.in/docs/latest/order/#modifying-an-order 356 357 Args: 358 exchange (Constants.ExchangeTypes): Possible values: [NSE_EQ, NSE_FO, BSE_EQ, BSE_FO, NSE_CD or MCX_FO] 359 order_id (str): The unique ID of the order to modify. 360 variety (Constants.VarietyTypes): Possible values: [RL, RL-MKT, SL, SL-MKT]. RL means regular orders, SL means Stop Loss order. 361 MKT means that the trade will happen at market price 362 quantity (int): The new quantity for the order. 363 traded_quantity (int): The quantity of the order that has already been traded. 364 price (float): The new price for the order. 365 trigger_price (float): The new trigger price for the order. Required for SL and SL-M orders. 366 disclosed_quantity (int): The new quantity to be disclosed publicly. 367 validity (Constants.ValidityTypes): The new validity for the order (e.g. DAY, IOC, GTD). 368 369 Returns: 370 dict: Dictionary containing the response data from the API. 371 """ 372 373 endpoint = f"/trading/orders/regular/{order_id}" 374 if validity == Constants.ValidityTypes.FULL_DAY: 375 validity_days = 1 376 elif validity == Constants.ValidityTypes.IMMEDIATE_OR_CANCEL: 377 validity_days = 0 378 else: 379 validity_days = 1 380 381 data = { 382 "variety": variety, 383 "quantity": quantity, 384 "traded_quantity": traded_quantity, 385 "price": price, 386 "trigger_price": trigger_price, 387 "disclosed_quantity": disclosed_quantity, 388 "validity": validity, 389 "validity_days": validity_days 390 } 391 return self._make_api_request("PUT", endpoint, data=data) 392 393 def cancel_order(self, order_id: str) -> dict: 394 """ 395 Method to cancel an order using the Vortex API. 396 397 Documentation: 398 https://vortex.rupeezy.in/docs/latest/order/#cancel-an-order 399 400 Args: 401 exchange (Constants.ExchangeTypes): Possible values: [NSE_EQ, NSE_FO, BSE_EQ, BSE_FO, NSE_CD or MCX_FO] 402 order_id (str): The unique ID of the order to cancel. 403 404 Returns: 405 dict: Dictionary containing the response data from the API. 406 """ 407 408 endpoint = f"/trading/orders/regular/{order_id}" 409 return self._make_api_request("DELETE", endpoint) 410 411 def orders(self,limit: int, offset: int) -> dict: 412 """ 413 Method to get all orders. 414 415 Documentation: 416 https://vortex.rupeezy.in/docs/latest/order/#fetching-order-book 417 418 Args: 419 limit (int): Limit is the number of orders to be fetched. 420 offset (int): Offset should atleast be 1 421 422 Returns: 423 dict: Dictionary containing the response data from the API. 424 """ 425 endpoint = f"/trading/orders?limit={limit}&offset={offset}" 426 return self._make_api_request("GET", endpoint) 427 428 def order_history(self,order_id: str) -> dict: 429 """ 430 Method to get the order history of a particular order 431 432 Documentation: 433 https://vortex.rupeezy.in/docs/latest/order/ 434 435 Args: 436 order_id (str): Order id for which history has to be fetched 437 438 Returns: 439 dict: Dictionary containing the response data from the API. 440 """ 441 endpoint = f"/trading/orders/{order_id}" 442 return self._make_api_request("GET", endpoint) 443 444 def positions(self) -> dict: 445 """ 446 Method to get the position book using the Vortex API. 447 448 Documentation: 449 https://vortex.rupeezy.in/docs/latest/positions/#fetch-all-positions 450 451 Returns: 452 dict: Dictionary containing the response data from the API. 453 """ 454 endpoint = f"/trading/portfolio/positions" 455 return self._make_api_request("GET", endpoint) 456 457 def holdings(self) -> dict: 458 """ 459 Method to get the holdings of the user using the Vortex API. 460 461 Documentation: 462 https://vortex.rupeezy.in/docs/latest/holdings/ 463 464 Returns: 465 dict: Dictionary containing the response data from the API. 466 """ 467 endpoint = "/trading/portfolio/holdings" 468 return self._make_api_request("GET", endpoint) 469 470 def trades(self) -> dict: 471 """ 472 Method to get today's trades of the user using the Vortex API. 473 474 Documentation: 475 https://vortex.rupeezy.in/docs/latest/positions/#get-trades 476 477 Returns: 478 dict: Dictionary containing the response data from the API. 479 """ 480 endpoint = "/trading/trades" 481 return self._make_api_request("GET", endpoint) 482 483 def funds(self) -> dict: 484 """ 485 Method to get the funds of the user using the Vortex API. 486 487 Documentation: 488 https://vortex.rupeezy.in/docs/latest/user/#available-funds 489 490 Returns: 491 dict: Dictionary containing the response data from the API. 492 """ 493 endpoint = "/user/funds" 494 return self._make_api_request("GET", endpoint) 495 496 def get_order_margin(self, exchange: Constants.ExchangeTypes, token: int, transaction_type: Constants.TransactionSides, product: Constants.ProductTypes, variety: Constants.VarietyTypes, 497 quantity: int, price: float,mode: Constants.OrderMarginModes, old_quantity: int = 0 , old_price: float = 0 ) -> dict: 498 """ 499 Get the margin required for placing an order for a specific security. 500 501 Documentation: 502 https://vortex.rupeezy.in/docs/latest/margin/#order-margin 503 504 Args: 505 exchange (Constants.ExchangeTypes): Possible values: [NSE_EQ, NSE_FO, BSE_EQ, BSE_FO, NSE_CD or MCX_FO] 506 token (int): Security token of the scrip. It can be found in the scripmaster file 507 transaction_type (Constants.TransactionSides): Possible values: [BUY, SELL] 508 product (Constants.ProductTypes): Possible values: [INTRADAY, DELIVERY, MTF]. MTF product can only be used in NSE_EQ exchange. 509 variety (Constants.VarietyTypes): Possible values: [RL, RL-MKT, SL, SL-MKT]. RL means regular orders, SL means Stop Loss order. 510 MKT means that the trade will happen at market price 511 quantity (int): For exchange NSE_FO, if you want to trade in 2 lots and lot size is 50, you should pass 100. 512 In all other exchanges, you should pass just the number of lots. For example, in MCX_FO, 513 if you want to trade 5 lots, you should pass just 5. 514 price (float): Price should be an integer multiple of Tick Size. For example, IDEA's tick size is 0.05. 515 So the price entered can be 9.5 or 9.65. It cannot be 9.67. 516 In case of market orders, you should send the Last Trade Price received from the Quote API or Websocket API 517 mode (Constants.OrderMarginModes): Possible values: [NEW, MODIFY] , Whether you are trying to modify an existing order or placing a new order. 518 old_quantity (int): For NSE_FO segments, old_quantity is lots * lot_size. For all others, enter just the number of lots. Required if mode is MODIFY 519 old_price (float): Old Price in INR. Required if mode is MODIFY 520 521 Returns: 522 dict: JSON response containing the details of the margin required to place the order 523 524 Raises: 525 HTTPError: If any HTTP error occurs during the API call 526 """ 527 528 endpoint = "/margins/order" 529 530 data = { 531 "exchange": exchange, 532 "token": token, 533 "transaction_type": transaction_type, 534 "product": product, 535 "variety": variety, 536 "quantity": quantity, 537 "price": price, 538 "old_quantity": old_quantity, 539 "old_price": old_price, 540 "mode": mode, 541 } 542 return self._make_api_request("POST", endpoint, data=data) 543 544 def brokerage_plan(self)-> dict: 545 """ 546 Get brokerage plan details of the user. 547 548 Documentation: 549 https://vortex.rupeezy.in/docs/latest/user/ 550 551 Returns: 552 dict: JSON response containing the details of the brokerage plan of the user 553 """ 554 endpoint = "/user/profile/brokerage" 555 return self._make_api_request("GET", endpoint, data=None,params=None) 556 557 def quotes(self, instruments: list, mode: Constants.QuoteModes)-> dict: 558 """ 559 Gets quotes of up to 1000 instruments at a time. 560 561 Documentation: 562 https://vortex.rupeezy.in/docs/latest/historical/#fetch-price-quotes 563 564 Args: 565 instrument(list): List of instruments. The items should be like ( "NSE_EQ-22", "NSE_FO-1234") 566 mode(Constants.QuoteModes): Quote mode. Can be ["ltp","ohlcv", "full"]. LTP quotes just give the last trade price, ohlc give open, high, low, close and volume, full mode also gives depth. 567 568 Returns: 569 dict: JSON response containing quotes. It is possible that not all the symbol identifiers you passed had a quote available. Those inputs will be missing from the response. 570 Also, the order of output might be different than the order of input 571 """ 572 endpoint = "/data/quotes" 573 params = {"q": instruments,"mode": mode} 574 return self._make_api_request("GET", endpoint, data=None,params=params) 575 576 def historical_candles(self, exchange: Constants.ExchangeTypes, token: int, to: datetime.datetime , start: datetime.datetime, resolution: Constants.Resolutions): 577 """ 578 Gets historical candle data of a particular instrument. 579 580 Documentation: 581 https://vortex.rupeezy.in/docs/latest/historical/#fetch-historical-candle-data 582 583 Args: 584 exchange (Constants.ExchangeTypes): Possible values: [NSE_EQ, NSE_FO, BSE_EQ, BSE_FO, NSE_CD or MCX_FO] 585 token (int): Security token of the scrip. It can be found in the instruments master file: 586 to (datetime): datetime up till when you want to receive candles 587 start (datetime): datetime from when you want to receive candles 588 resolution (Constants.Resolutions): resoulution of the candle. can be "1", "2", "3", "4", "5", "10", "15", "30", "45", "60", "120", "180", "240", "1D", "1W", "1M" 589 590 Returns: 591 dict: JSON response containing the historical candles 592 """ 593 594 if not isinstance(token, int): 595 raise TypeError("token must be an integer") 596 if not isinstance(to,datetime.datetime): 597 raise TypeError("to must be a datetime") 598 if not isinstance(start,datetime.datetime): 599 raise TypeError("start must be a datetime") 600 601 602 endpoint = "/data/history" 603 params = {"exchange": exchange,"token": token , "to": int(to.timestamp()), "from": int(start.timestamp()), "resolution": resolution} 604 return self._make_api_request("GET", endpoint, data=None,params=params) 605 606 def login_url(self, callback_param: str) -> str: 607 """ 608 Returns the login URL for the Vortex API. 609 610 Documentation: 611 https://vortex.rupeezy.in/docs/latest/authentication/ 612 613 Returns: 614 str: The login URL for the Vortex API. 615 """ 616 617 return f"https://flow.rupeezy.in?applicationId={self.application_id}&cb_param={callback_param}" 618 619 def exchange_token(self,auth_code: str) -> dict: 620 """ 621 Exchange the auth code received from the login URL for an access token. 622 623 Documentation: 624 https://vortex.rupeezy.in/docs/latest/authentication/ 625 626 Args: 627 auth_code (str): The authorization code received from the login URL. 628 629 Returns: 630 dict: JSON response containing the details of the user 631 """ 632 633 endpoint = "/user/session" 634 data = { 635 "token": auth_code, 636 "applicationId": self.application_id, 637 "checksum": self._sha256_hash(f"{self.application_id}{auth_code}{self.api_key}") 638 } 639 res = self._make_unauth_request("POST", endpoint= endpoint, data=data) 640 self._setup_client_code(login_object=res) 641 return res 642 643 def _sha256_hash(self,text: str) -> str: 644 sha = hashlib.sha256() 645 sha.update(text.encode('utf-8')) 646 return sha.hexdigest() 647 648 def _setup_client_code(self, login_object: dict) -> bool: 649 """ 650 Sets up access token after login 651 652 Args: 653 login_object(dict): Login object received 654 655 Returns: 656 (bool): Whether successful or not 657 """ 658 659 if (('data' in login_object ) and login_object["data"] != None and login_object["data"]["access_token"] != None): 660 self.access_token = login_object["data"]["access_token"] 661 return True 662 663 return False 664 665 def mf_holdings(self) -> dict: 666 """ 667 Get the mutual fund holdings of the user. 668 669 Returns: 670 dict: JSON response with the following structure:: 671 672 { 673 "status": str, 674 "message": str, 675 "data": { 676 "OverallConfig": { 677 "OverAllXirr": float, 678 "MarketValueAmount": float, 679 "InvestedAmount": float, 680 "Returns": float, 681 "ReturnsPercentage": float, 682 "LastImportedAt": str or None, # ISO datetime 683 "FundsImportStatus": str, 684 "OneDayReturns": float, 685 "OneDayReturnsPercentage": float, 686 "NavAsOn": str # ISO datetime 687 }, 688 "IsinWiseData": { 689 "<ISIN>": { 690 "Name": str, 691 "MarketValueAmount": float, 692 "InvestedAmount": float, 693 "Returns": float, 694 "ReturnsPercentage": float, 695 "OneDayReturns": float, 696 "OneDayReturnsPercentage": float, 697 "IsinXirr": float, 698 "BseScheme": dict or None, 699 "CmotsScheme": dict or None, 700 "Folios": [ 701 { 702 "xirr": float, 703 "client_code": str, 704 "dpam_id": int, 705 "folio_number": str, 706 "folio_xirr": float, 707 "isin": str, 708 "co_code": int, 709 "name": str, 710 "type": str, 711 "units": float, 712 "redeemable_units": float, 713 "market_value_amount": float, 714 "redeemable_amount": float, 715 "invested_amount": float, 716 "nav_value": float, 717 "buy_price": float, 718 "as_on": str or None, 719 "hold_type": str, 720 "is_demat": str, 721 "one_day_returns": float, 722 "one_day_returns_percentage": float 723 } 724 ] 725 } 726 } 727 } 728 } 729 """ 730 endpoint = "/mf/holdings" 731 return self._make_api_request("GET", endpoint) 732 733 def fund_details(self, isin: str) -> dict: 734 """ 735 Get details of a mutual fund scheme by ISIN. 736 737 Args: 738 isin (str): ISIN of the mutual fund scheme 739 740 Returns: 741 dict: JSON response with the following structure:: 742 743 { 744 "status": str, 745 "data": { 746 "assetAllocation": [ 747 { 748 "Isin": str, 749 "AssetCode": int, 750 "AssetName": str, 751 "Holding_CurrentMonth": float, 752 "Holding_PrevMonth": float, 753 "CurrentMonth": str 754 } 755 ], 756 "sectorAllocation": [ 757 { 758 "Isin": str, 759 "VALUE": float, 760 "PERC_HOLD": float, 761 "SECTOR": str, 762 "AsOnDate": str # ISO datetime 763 } 764 ], 765 "holdingAllocation": [ 766 { 767 "Isin": str, 768 "co_code": int, 769 "co_name": str, 770 "invdate": str, 771 "InvDate": str, # ISO datetime 772 "Perc_hold": float, 773 "MktValue": float, 774 "TotalShares": float, 775 "AssetType": str, 776 "rating": str 777 } 778 ], 779 "cmotsDetails": { 780 "isin": str, 781 "sch_name": str, 782 "navrs": float, 783 "Navdate": str, 784 "FundManager": str, 785 "SchemeAUM": float, 786 "EXPRATIO": float, 787 "riskometervalue": str, 788 "BenchmarkName": str, 789 "Category": str, 790 "fund_rating": int or None, 791 ... 792 }, 793 "bseSchemeDetails": { 794 "ISIN": str, 795 "SchemeCode": str, 796 "SchemeName": str, 797 "SchemeType": str, 798 "SchemePlan": str, 799 "PurchaseAllowed": str, 800 "RedemptionAllowed": str, 801 "SIPFlag": str, 802 "MinimumPurchaseAmount": float, 803 "MinimumRedemptionQty": float, 804 ... 805 } 806 } 807 } 808 """ 809 endpoint = "/mf/fund/details" 810 params = {"isin": isin} 811 return self._make_api_request("GET", endpoint, params=params) 812 813 def fund_navs(self, isin: str) -> dict: 814 """ 815 Get historical NAV data for a mutual fund scheme. 816 817 Args: 818 isin (str): ISIN of the mutual fund scheme 819 820 Returns: 821 dict: JSON response with the following structure:: 822 823 { 824 "status": str, 825 "data": [ 826 { 827 "date": str, # ISO datetime 828 "value": float # NAV value 829 } 830 ] 831 } 832 """ 833 endpoint = "/mf/fund/navs" 834 params = {"isin": isin} 835 return self._make_api_request("GET", endpoint, params=params) 836 837 def portfolio_impact(self, isin: str, amount: float) -> dict: 838 """ 839 Get the portfolio impact of adding a mutual fund scheme. 840 841 Args: 842 isin (str): ISIN of the mutual fund scheme 843 amount (float): Investment amount 844 845 Returns: 846 dict: JSON response with the following structure:: 847 848 { 849 "status": str, 850 "data": { 851 "New": { 852 "Returns": float, 853 "Risk": float 854 }, 855 "Original": { 856 "Returns": float, 857 "Risk": float 858 } 859 } 860 } 861 """ 862 endpoint = "/mf/fund/portfolio-impact" 863 params = {"isin": isin, "amount": amount} 864 return self._make_api_request("GET", endpoint, params=params) 865 866 def fund_overlap(self, isin: str, amount: float) -> dict: 867 """ 868 Get the fund overlap analysis for a mutual fund scheme against existing holdings. 869 870 Args: 871 isin (str): ISIN of the mutual fund scheme 872 amount (float): Investment amount 873 874 Returns: 875 dict: JSON response with the following structure:: 876 877 { 878 "status": str, 879 "data": { 880 "Value": float, 881 "Overlaps": [ 882 { 883 "Isin": str, 884 "Holdings": [ 885 { 886 "Name": str, 887 "Value": float 888 } 889 ] 890 } 891 ] 892 } 893 } 894 """ 895 endpoint = "/mf/fund/portfolio-overlap" 896 params = {"isin": isin, "amount": amount} 897 return self._make_api_request("GET", endpoint, params=params) 898 899 def sip_book(self) -> dict: 900 """ 901 Get the SIP (Systematic Investment Plan) book of the user. 902 903 Returns: 904 dict: JSON response with the following structure:: 905 906 { 907 "status": str, 908 "data": [ 909 { 910 "ID": int, 911 "CreatedAt": str, # ISO datetime 912 "UpdatedAt": str, # ISO datetime 913 "transactionCode": str, 914 "isin": str, 915 "amount": float, 916 "clientCode": str, 917 "schemeCode": str, 918 "remarks": str, 919 "startDate": str, # ISO datetime 920 "startDay": int, 921 "modifiedStartDate": str or None, # ISO datetime 922 "mandateId": str, 923 "state": str, 924 "frequency": str, 925 "stepUp": bool, 926 "stepUpFrequency": str, 927 "stepUpPercentage": float, 928 "stepUpAmount": float, 929 "isAmcSip": bool, 930 "initialInvestmentAmount": float, 931 "buySellType": str, 932 "nextInstallmentDate": str or None, # ISO datetime 933 "installmentNumber": int, 934 "schemeName": str, 935 "coCode": int, 936 "schemes_details": { 937 "bseSchemeDetails": dict, 938 "cmotsDetails": dict, 939 "nfoDetails": dict 940 } 941 } 942 ] 943 } 944 """ 945 endpoint = "/mf/purchases/synthetic/plan" 946 return self._make_api_request("GET", endpoint) 947 948 def save_backtest_result(self, stats, name: str, symbol: str = "", description: str = "", tags: list = None) -> dict: 949 """ 950 Save backtest results to Rupeezy for viewing on the developer portal. 951 952 Supports multiple backtesting libraries (auto-detected from the result type): 953 - **backtesting.py**: pass the stats object from Backtest.run() 954 - **vectorbt**: pass a vbt.Portfolio object 955 - **backtrader**: pass the strategy from cerebro.run() (i.e. results[0]) 956 957 Args: 958 stats: The result object from any supported backtesting library. 959 name (str): A label for this backtest run (e.g. "SMA Crossover v2"). 960 symbol (str, optional): Primary instrument symbol. 961 description (str, optional): Notes about this run. 962 tags (list[str], optional): Tags for filtering (e.g. ["intraday", "nifty"]). 963 964 Returns: 965 dict: { "status": "success", "backtest_id": "bt_abc123", "url": "https://..." } 966 """ 967 from .backtest import serialize_stats 968 payload = serialize_stats(stats, name, symbol, description, tags or []) 969 endpoint = "/strategies/backtests" 970 971 return self._make_api_request("POST", endpoint, data=payload) 972 973 def save_optimization_result( 974 self, 975 stats, 976 heatmap, 977 name: str, 978 symbol: str = "", 979 description: str = "", 980 maximize="Sharpe Ratio", 981 param_ranges: dict = None, 982 ) -> dict: 983 """ 984 Save optimization results to Rupeezy for viewing on the developer portal. 985 986 Supports multiple backtesting libraries (auto-detected from the result type): 987 - **backtesting.py**: pass stats + heatmap from bt.optimize(return_heatmap=True) 988 - **vectorbt**: pass Portfolio + metric Series from multi-param run 989 - **backtrader**: pass results list from cerebro.run() after optstrategy() 990 991 Args: 992 stats: The result object from any supported backtesting library. 993 heatmap: The heatmap/metric Series (backtesting.py/vectorbt) or 994 metric extraction callable (backtrader). 995 name (str): A label for this optimization run (e.g. "SMA Grid Search"). 996 symbol (str, optional): Primary instrument symbol (e.g. "NIFTY"). 997 description (str, optional): Notes about this optimization run. 998 maximize: The metric that was optimized. Should match the `maximize` 999 argument passed to Backtest.optimize(). Can be a string 1000 metric name (e.g. "Sharpe Ratio") or a callable. 1001 Defaults to "Sharpe Ratio". 1002 param_ranges (dict, optional): Explicit parameter range definitions. 1003 Keys are parameter names, values are range() objects or lists. 1004 Example: {"sma_fast": range(5, 51, 5), "sma_slow": range(20, 201, 10)} 1005 If not provided, ranges are inferred from the heatmap index. 1006 1007 Returns: 1008 dict: {"status": "success", "optimization_id": "opt_xxx", "backtest_id": "bt_xxx"} 1009 1010 Example:: 1011 1012 stats, heatmap = bt.optimize( 1013 sma_fast=range(5, 51, 5), 1014 sma_slow=range(20, 201, 10), 1015 maximize='Sharpe Ratio', 1016 return_heatmap=True, 1017 ) 1018 client.save_optimization_result( 1019 stats=stats, 1020 heatmap=heatmap, 1021 name="SMA Crossover Grid Search", 1022 symbol="NIFTY", 1023 maximize='Sharpe Ratio', 1024 param_ranges={"sma_fast": range(5, 51, 5), "sma_slow": range(20, 201, 10)}, 1025 ) 1026 """ 1027 is_maximize = True 1028 objective_metric = maximize 1029 1030 if isinstance(maximize, bool): 1031 is_maximize = maximize 1032 objective_metric = "Sharpe Ratio" 1033 1034 from .backtest import serialize_optimization 1035 payload = serialize_optimization( 1036 result=stats, 1037 heatmap=heatmap, 1038 name=name, 1039 symbol=symbol, 1040 description=description, 1041 objective_metric=objective_metric, 1042 maximize=is_maximize, 1043 param_ranges=param_ranges, 1044 ) 1045 endpoint = "/strategies/optimizations" 1046 return self._make_api_request("POST", endpoint, data=payload)
158 def __init__(self, api_key: str = None , application_id: str = None, base_url: str = "https://vortex-api.rupeezy.in/v2",enable_logging: bool=False) -> None: 159 """ 160 Constructor method for VortexAPI class. 161 162 Args: 163 api_key (str, optional): API key for the Vortex API. You can either pass it as an argument or set it as an environment variable named VORTEX_API_KEY. 164 application_id (str, optional): Application ID for the Vortex API. You can either pass it as an argument or set it as an environment variable named VORTEX_APPLICATION_ID. 165 base_url (str, optional): Base URL for the Vortex API. Defaults to "https://vortex-api.rupeezy.in/v2". 166 """ 167 if api_key == None: 168 if os.getenv("VORTEX_API_KEY") != None: 169 api_key = os.getenv("VORTEX_API_KEY") 170 else: 171 raise ValueError("API key must be provided either as an argument or through the VORTEX_API_KEY environment variable.") 172 173 if application_id == None: 174 if os.getenv("VORTEX_APPLICATION_ID") != None: 175 application_id = os.getenv("VORTEX_APPLICATION_ID") 176 else: 177 raise ValueError("Application ID must be provided either as an argument or through the VORTEX_APPLICATION_ID environment variable.") 178 self.api_key = api_key 179 self.application_id = application_id 180 181 if os.getenv("VORTEX_BASE_URL") != None: 182 self.base_url = os.getenv("VORTEX_BASE_URL") 183 else: 184 self.base_url = base_url 185 186 if os.getenv("VORTEX_ACCESS_TOKEN") != None: 187 self.access_token = os.getenv("VORTEX_ACCESS_TOKEN") 188 else: 189 self.access_token = None 190 191 self.enable_logging = enable_logging 192 if self.enable_logging: 193 logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
Constructor method for VortexAPI class.
Arguments:
- api_key (str, optional): API key for the Vortex API. You can either pass it as an argument or set it as an environment variable named VORTEX_API_KEY.
- application_id (str, optional): Application ID for the Vortex API. You can either pass it as an argument or set it as an environment variable named VORTEX_APPLICATION_ID.
- base_url (str, optional): Base URL for the Vortex API. Defaults to "https://vortex-api.rupeezy.in/v2".
245 def login(self, client_code: str, password: str, totp: str)->dict: 246 """ 247 Depricating Soon. Use SSO Login instead. Login using password and totp directly 248 249 Documentation: 250 https://vortex.rupeezy.in/docs/latest/authentication/ 251 252 Args: 253 client_code(str): Client Code of the account 254 password(str): Password of the account 255 totp(str): TOTP generated using third party apps like google authenticator etc. 256 257 Returns: 258 dict: JSON response containing the details of the user 259 """ 260 endpoint = "/user/login" 261 data = { 262 "client_code": client_code, 263 "password": password, 264 "totp": totp, 265 "application_id": self.application_id 266 } 267 res = self._make_unauth_request("POST", endpoint= endpoint, data=data) 268 self._setup_client_code(login_object=res) 269 return res
Depricating Soon. Use SSO Login instead. Login using password and totp directly
Documentation:
Arguments:
- client_code(str): Client Code of the account
- password(str): Password of the account
- totp(str): TOTP generated using third party apps like google authenticator etc.
Returns:
dict: JSON response containing the details of the user
271 def download_master(self) -> dict: 272 """ 273 Download list of all available instruments and their details across all exchanges 274 275 Documentation: 276 https://vortex.rupeezy.in/docs/latest/historical/#instrument-list 277 278 Returns: 279 dict: CSV Array of all instruments. The first row contains headers 280 """ 281 endpoint = "https://static.rupeezy.in/master.csv" 282 with requests.Session() as s: 283 download = s.get(url=endpoint) 284 decoded_content = download.content.decode('utf-8') 285 cr = csv.reader(decoded_content.splitlines(), delimiter=',') 286 my_list = list(cr) 287 return my_list
Download list of all available instruments and their details across all exchanges
Documentation:
https://vortex.rupeezy.in/docs/latest/historical/#instrument-list
Returns:
dict: CSV Array of all instruments. The first row contains headers
289 def place_order(self,exchange: Constants.ExchangeTypes, token: int, transaction_type: Constants.TransactionSides, product: Constants.ProductTypes, variety: Constants.VarietyTypes, 290 quantity: int, price: float, trigger_price: float, disclosed_quantity: int, validity: Constants.ValidityTypes) -> dict: 291 """ 292 Place an order for a specific security 293 294 Documentation: 295 https://vortex.rupeezy.in/docs/latest/order/#placing-an-order 296 297 Args: 298 exchange (Constants.ExchangeTypes): Possible values: [NSE_EQ, NSE_FO, BSE_EQ, BSE_FO, NSE_CD or MCX_FO] 299 token (int): Security token of the scrip. It can be found in the scripmaster file 300 transaction_type (Constants.TransactionSides): Possible values: [BUY, SELL] 301 product (Constants.ProductTypes): Possible values: [INTRADAY, DELIVERY, MTF]. MTF product can only be used in NSE_EQ exchange. 302 variety (Constants.VarietyTypes): Possible values: [RL, RL-MKT, SL, SL-MKT]. RL means regular orders, SL means Stop Loss order. 303 MKT means that the trade will happen at market price 304 quantity (int): For exchange NSE_FO, if you want to trade in 2 lots and lot size is 50, you should pass 100. 305 In all other exchanges, you should pass just the number of lots. For example, in MCX_FO, 306 if you want to trade 5 lots, you should pass just 5. 307 price (float): Price should be an integer multiple of Tick Size. For example, IDEA's tick size is 0.05. 308 So the price entered can be 9.5 or 9.65. It cannot be 9.67. 309 In case of market orders, you should send the Last Trade Price received from the Quote API or Websocket API 310 trigger_price (float): To be used for Stop loss orders. For BUY side SL orders, trigger_price should be 311 lesser than price. for SELL side SL orders, trigger_price should be greater than price. 312 disclosed_quantity (int): Can be any number lesser than or equal to quantity, including 0 313 validity (Constants.ValidityTypes): Can be DAY for orders which are valid throughout the day, or IOC. 314 IOC order will be cancelled if it is not traded immediately 315 Returns: 316 dict: JSON response containing the details of the placed order 317 318 Raises: 319 HTTPError: If any HTTP error occurs during the API call 320 """ 321 322 endpoint = "/trading/orders/regular" 323 if validity == Constants.ValidityTypes.FULL_DAY: 324 validity_days = 1 325 is_amo = False 326 elif validity == Constants.ValidityTypes.IMMEDIATE_OR_CANCEL: 327 validity_days = 0 328 is_amo = False 329 else: 330 validity_days = 1 331 is_amo = True 332 333 data = { 334 "exchange": exchange, 335 "token": token, 336 "transaction_type": transaction_type, 337 "product": product, 338 "variety": variety, 339 "quantity": quantity, 340 "price": price, 341 "trigger_price": trigger_price, 342 "disclosed_quantity": disclosed_quantity, 343 "validity": validity, 344 "validity_days": validity_days, 345 "is_amo": is_amo 346 } 347 348 return self._make_api_request("POST", endpoint, data=data)
Place an order for a specific security
Documentation:
https://vortex.rupeezy.in/docs/latest/order/#placing-an-order
Arguments:
- exchange (Constants.ExchangeTypes): Possible values: [NSE_EQ, NSE_FO, BSE_EQ, BSE_FO, NSE_CD or MCX_FO]
- token (int): Security token of the scrip. It can be found in the scripmaster file
- transaction_type (Constants.TransactionSides): Possible values: [BUY, SELL]
- product (Constants.ProductTypes): Possible values: [INTRADAY, DELIVERY, MTF]. MTF product can only be used in NSE_EQ exchange.
- variety (Constants.VarietyTypes): Possible values: [RL, RL-MKT, SL, SL-MKT]. RL means regular orders, SL means Stop Loss order. MKT means that the trade will happen at market price
- quantity (int): For exchange NSE_FO, if you want to trade in 2 lots and lot size is 50, you should pass 100. In all other exchanges, you should pass just the number of lots. For example, in MCX_FO, if you want to trade 5 lots, you should pass just 5.
- price (float): Price should be an integer multiple of Tick Size. For example, IDEA's tick size is 0.05. So the price entered can be 9.5 or 9.65. It cannot be 9.67. In case of market orders, you should send the Last Trade Price received from the Quote API or Websocket API
- trigger_price (float): To be used for Stop loss orders. For BUY side SL orders, trigger_price should be lesser than price. for SELL side SL orders, trigger_price should be greater than price.
- disclosed_quantity (int): Can be any number lesser than or equal to quantity, including 0
- validity (Constants.ValidityTypes): Can be DAY for orders which are valid throughout the day, or IOC. IOC order will be cancelled if it is not traded immediately
Returns:
dict: JSON response containing the details of the placed order
Raises:
- HTTPError: If any HTTP error occurs during the API call
350 def modify_order(self, order_id: str, variety: Constants.VarietyTypes, quantity: int, traded_quantity: int, price: float, trigger_price: float, disclosed_quantity: int, validity: Constants.ValidityTypes) -> dict: 351 """ 352 Method to modify an order using the Vortex API. 353 354 Documentation: 355 https://vortex.rupeezy.in/docs/latest/order/#modifying-an-order 356 357 Args: 358 exchange (Constants.ExchangeTypes): Possible values: [NSE_EQ, NSE_FO, BSE_EQ, BSE_FO, NSE_CD or MCX_FO] 359 order_id (str): The unique ID of the order to modify. 360 variety (Constants.VarietyTypes): Possible values: [RL, RL-MKT, SL, SL-MKT]. RL means regular orders, SL means Stop Loss order. 361 MKT means that the trade will happen at market price 362 quantity (int): The new quantity for the order. 363 traded_quantity (int): The quantity of the order that has already been traded. 364 price (float): The new price for the order. 365 trigger_price (float): The new trigger price for the order. Required for SL and SL-M orders. 366 disclosed_quantity (int): The new quantity to be disclosed publicly. 367 validity (Constants.ValidityTypes): The new validity for the order (e.g. DAY, IOC, GTD). 368 369 Returns: 370 dict: Dictionary containing the response data from the API. 371 """ 372 373 endpoint = f"/trading/orders/regular/{order_id}" 374 if validity == Constants.ValidityTypes.FULL_DAY: 375 validity_days = 1 376 elif validity == Constants.ValidityTypes.IMMEDIATE_OR_CANCEL: 377 validity_days = 0 378 else: 379 validity_days = 1 380 381 data = { 382 "variety": variety, 383 "quantity": quantity, 384 "traded_quantity": traded_quantity, 385 "price": price, 386 "trigger_price": trigger_price, 387 "disclosed_quantity": disclosed_quantity, 388 "validity": validity, 389 "validity_days": validity_days 390 } 391 return self._make_api_request("PUT", endpoint, data=data)
Method to modify an order using the Vortex API.
Documentation:
https://vortex.rupeezy.in/docs/latest/order/#modifying-an-order
Arguments:
- exchange (Constants.ExchangeTypes): Possible values: [NSE_EQ, NSE_FO, BSE_EQ, BSE_FO, NSE_CD or MCX_FO]
- order_id (str): The unique ID of the order to modify.
- variety (Constants.VarietyTypes): Possible values: [RL, RL-MKT, SL, SL-MKT]. RL means regular orders, SL means Stop Loss order. MKT means that the trade will happen at market price
- quantity (int): The new quantity for the order.
- traded_quantity (int): The quantity of the order that has already been traded.
- price (float): The new price for the order.
- trigger_price (float): The new trigger price for the order. Required for SL and SL-M orders.
- disclosed_quantity (int): The new quantity to be disclosed publicly.
- validity (Constants.ValidityTypes): The new validity for the order (e.g. DAY, IOC, GTD).
Returns:
dict: Dictionary containing the response data from the API.
393 def cancel_order(self, order_id: str) -> dict: 394 """ 395 Method to cancel an order using the Vortex API. 396 397 Documentation: 398 https://vortex.rupeezy.in/docs/latest/order/#cancel-an-order 399 400 Args: 401 exchange (Constants.ExchangeTypes): Possible values: [NSE_EQ, NSE_FO, BSE_EQ, BSE_FO, NSE_CD or MCX_FO] 402 order_id (str): The unique ID of the order to cancel. 403 404 Returns: 405 dict: Dictionary containing the response data from the API. 406 """ 407 408 endpoint = f"/trading/orders/regular/{order_id}" 409 return self._make_api_request("DELETE", endpoint)
Method to cancel an order using the Vortex API.
Documentation:
https://vortex.rupeezy.in/docs/latest/order/#cancel-an-order
Arguments:
- exchange (Constants.ExchangeTypes): Possible values: [NSE_EQ, NSE_FO, BSE_EQ, BSE_FO, NSE_CD or MCX_FO]
- order_id (str): The unique ID of the order to cancel.
Returns:
dict: Dictionary containing the response data from the API.
411 def orders(self,limit: int, offset: int) -> dict: 412 """ 413 Method to get all orders. 414 415 Documentation: 416 https://vortex.rupeezy.in/docs/latest/order/#fetching-order-book 417 418 Args: 419 limit (int): Limit is the number of orders to be fetched. 420 offset (int): Offset should atleast be 1 421 422 Returns: 423 dict: Dictionary containing the response data from the API. 424 """ 425 endpoint = f"/trading/orders?limit={limit}&offset={offset}" 426 return self._make_api_request("GET", endpoint)
Method to get all orders.
Documentation:
https://vortex.rupeezy.in/docs/latest/order/#fetching-order-book
Arguments:
- limit (int): Limit is the number of orders to be fetched.
- offset (int): Offset should atleast be 1
Returns:
dict: Dictionary containing the response data from the API.
428 def order_history(self,order_id: str) -> dict: 429 """ 430 Method to get the order history of a particular order 431 432 Documentation: 433 https://vortex.rupeezy.in/docs/latest/order/ 434 435 Args: 436 order_id (str): Order id for which history has to be fetched 437 438 Returns: 439 dict: Dictionary containing the response data from the API. 440 """ 441 endpoint = f"/trading/orders/{order_id}" 442 return self._make_api_request("GET", endpoint)
Method to get the order history of a particular order
Documentation:
Arguments:
- order_id (str): Order id for which history has to be fetched
Returns:
dict: Dictionary containing the response data from the API.
444 def positions(self) -> dict: 445 """ 446 Method to get the position book using the Vortex API. 447 448 Documentation: 449 https://vortex.rupeezy.in/docs/latest/positions/#fetch-all-positions 450 451 Returns: 452 dict: Dictionary containing the response data from the API. 453 """ 454 endpoint = f"/trading/portfolio/positions" 455 return self._make_api_request("GET", endpoint)
Method to get the position book using the Vortex API.
Documentation:
https://vortex.rupeezy.in/docs/latest/positions/#fetch-all-positions
Returns:
dict: Dictionary containing the response data from the API.
457 def holdings(self) -> dict: 458 """ 459 Method to get the holdings of the user using the Vortex API. 460 461 Documentation: 462 https://vortex.rupeezy.in/docs/latest/holdings/ 463 464 Returns: 465 dict: Dictionary containing the response data from the API. 466 """ 467 endpoint = "/trading/portfolio/holdings" 468 return self._make_api_request("GET", endpoint)
Method to get the holdings of the user using the Vortex API.
Documentation:
https://vortex.rupeezy.in/docs/latest/holdings/
Returns:
dict: Dictionary containing the response data from the API.
470 def trades(self) -> dict: 471 """ 472 Method to get today's trades of the user using the Vortex API. 473 474 Documentation: 475 https://vortex.rupeezy.in/docs/latest/positions/#get-trades 476 477 Returns: 478 dict: Dictionary containing the response data from the API. 479 """ 480 endpoint = "/trading/trades" 481 return self._make_api_request("GET", endpoint)
Method to get today's trades of the user using the Vortex API.
Documentation:
https://vortex.rupeezy.in/docs/latest/positions/#get-trades
Returns:
dict: Dictionary containing the response data from the API.
483 def funds(self) -> dict: 484 """ 485 Method to get the funds of the user using the Vortex API. 486 487 Documentation: 488 https://vortex.rupeezy.in/docs/latest/user/#available-funds 489 490 Returns: 491 dict: Dictionary containing the response data from the API. 492 """ 493 endpoint = "/user/funds" 494 return self._make_api_request("GET", endpoint)
Method to get the funds of the user using the Vortex API.
Documentation:
https://vortex.rupeezy.in/docs/latest/user/#available-funds
Returns:
dict: Dictionary containing the response data from the API.
496 def get_order_margin(self, exchange: Constants.ExchangeTypes, token: int, transaction_type: Constants.TransactionSides, product: Constants.ProductTypes, variety: Constants.VarietyTypes, 497 quantity: int, price: float,mode: Constants.OrderMarginModes, old_quantity: int = 0 , old_price: float = 0 ) -> dict: 498 """ 499 Get the margin required for placing an order for a specific security. 500 501 Documentation: 502 https://vortex.rupeezy.in/docs/latest/margin/#order-margin 503 504 Args: 505 exchange (Constants.ExchangeTypes): Possible values: [NSE_EQ, NSE_FO, BSE_EQ, BSE_FO, NSE_CD or MCX_FO] 506 token (int): Security token of the scrip. It can be found in the scripmaster file 507 transaction_type (Constants.TransactionSides): Possible values: [BUY, SELL] 508 product (Constants.ProductTypes): Possible values: [INTRADAY, DELIVERY, MTF]. MTF product can only be used in NSE_EQ exchange. 509 variety (Constants.VarietyTypes): Possible values: [RL, RL-MKT, SL, SL-MKT]. RL means regular orders, SL means Stop Loss order. 510 MKT means that the trade will happen at market price 511 quantity (int): For exchange NSE_FO, if you want to trade in 2 lots and lot size is 50, you should pass 100. 512 In all other exchanges, you should pass just the number of lots. For example, in MCX_FO, 513 if you want to trade 5 lots, you should pass just 5. 514 price (float): Price should be an integer multiple of Tick Size. For example, IDEA's tick size is 0.05. 515 So the price entered can be 9.5 or 9.65. It cannot be 9.67. 516 In case of market orders, you should send the Last Trade Price received from the Quote API or Websocket API 517 mode (Constants.OrderMarginModes): Possible values: [NEW, MODIFY] , Whether you are trying to modify an existing order or placing a new order. 518 old_quantity (int): For NSE_FO segments, old_quantity is lots * lot_size. For all others, enter just the number of lots. Required if mode is MODIFY 519 old_price (float): Old Price in INR. Required if mode is MODIFY 520 521 Returns: 522 dict: JSON response containing the details of the margin required to place the order 523 524 Raises: 525 HTTPError: If any HTTP error occurs during the API call 526 """ 527 528 endpoint = "/margins/order" 529 530 data = { 531 "exchange": exchange, 532 "token": token, 533 "transaction_type": transaction_type, 534 "product": product, 535 "variety": variety, 536 "quantity": quantity, 537 "price": price, 538 "old_quantity": old_quantity, 539 "old_price": old_price, 540 "mode": mode, 541 } 542 return self._make_api_request("POST", endpoint, data=data)
Get the margin required for placing an order for a specific security.
Documentation:
https://vortex.rupeezy.in/docs/latest/margin/#order-margin
Arguments:
- exchange (Constants.ExchangeTypes): Possible values: [NSE_EQ, NSE_FO, BSE_EQ, BSE_FO, NSE_CD or MCX_FO]
- token (int): Security token of the scrip. It can be found in the scripmaster file
- transaction_type (Constants.TransactionSides): Possible values: [BUY, SELL]
- product (Constants.ProductTypes): Possible values: [INTRADAY, DELIVERY, MTF]. MTF product can only be used in NSE_EQ exchange.
- variety (Constants.VarietyTypes): Possible values: [RL, RL-MKT, SL, SL-MKT]. RL means regular orders, SL means Stop Loss order. MKT means that the trade will happen at market price
- quantity (int): For exchange NSE_FO, if you want to trade in 2 lots and lot size is 50, you should pass 100. In all other exchanges, you should pass just the number of lots. For example, in MCX_FO, if you want to trade 5 lots, you should pass just 5.
- price (float): Price should be an integer multiple of Tick Size. For example, IDEA's tick size is 0.05. So the price entered can be 9.5 or 9.65. It cannot be 9.67. In case of market orders, you should send the Last Trade Price received from the Quote API or Websocket API
- mode (Constants.OrderMarginModes): Possible values: [NEW, MODIFY] , Whether you are trying to modify an existing order or placing a new order.
- old_quantity (int): For NSE_FO segments, old_quantity is lots * lot_size. For all others, enter just the number of lots. Required if mode is MODIFY
- old_price (float): Old Price in INR. Required if mode is MODIFY
Returns:
dict: JSON response containing the details of the margin required to place the order
Raises:
- HTTPError: If any HTTP error occurs during the API call
544 def brokerage_plan(self)-> dict: 545 """ 546 Get brokerage plan details of the user. 547 548 Documentation: 549 https://vortex.rupeezy.in/docs/latest/user/ 550 551 Returns: 552 dict: JSON response containing the details of the brokerage plan of the user 553 """ 554 endpoint = "/user/profile/brokerage" 555 return self._make_api_request("GET", endpoint, data=None,params=None)
Get brokerage plan details of the user.
Documentation:
https://vortex.rupeezy.in/docs/latest/user/
Returns:
dict: JSON response containing the details of the brokerage plan of the user
557 def quotes(self, instruments: list, mode: Constants.QuoteModes)-> dict: 558 """ 559 Gets quotes of up to 1000 instruments at a time. 560 561 Documentation: 562 https://vortex.rupeezy.in/docs/latest/historical/#fetch-price-quotes 563 564 Args: 565 instrument(list): List of instruments. The items should be like ( "NSE_EQ-22", "NSE_FO-1234") 566 mode(Constants.QuoteModes): Quote mode. Can be ["ltp","ohlcv", "full"]. LTP quotes just give the last trade price, ohlc give open, high, low, close and volume, full mode also gives depth. 567 568 Returns: 569 dict: JSON response containing quotes. It is possible that not all the symbol identifiers you passed had a quote available. Those inputs will be missing from the response. 570 Also, the order of output might be different than the order of input 571 """ 572 endpoint = "/data/quotes" 573 params = {"q": instruments,"mode": mode} 574 return self._make_api_request("GET", endpoint, data=None,params=params)
Gets quotes of up to 1000 instruments at a time.
Documentation:
https://vortex.rupeezy.in/docs/latest/historical/#fetch-price-quotes
Arguments:
- instrument(list): List of instruments. The items should be like ( "NSE_EQ-22", "NSE_FO-1234")
- mode(Constants.QuoteModes): Quote mode. Can be ["ltp","ohlcv", "full"]. LTP quotes just give the last trade price, ohlc give open, high, low, close and volume, full mode also gives depth.
Returns:
dict: JSON response containing quotes. It is possible that not all the symbol identifiers you passed had a quote available. Those inputs will be missing from the response. Also, the order of output might be different than the order of input
576 def historical_candles(self, exchange: Constants.ExchangeTypes, token: int, to: datetime.datetime , start: datetime.datetime, resolution: Constants.Resolutions): 577 """ 578 Gets historical candle data of a particular instrument. 579 580 Documentation: 581 https://vortex.rupeezy.in/docs/latest/historical/#fetch-historical-candle-data 582 583 Args: 584 exchange (Constants.ExchangeTypes): Possible values: [NSE_EQ, NSE_FO, BSE_EQ, BSE_FO, NSE_CD or MCX_FO] 585 token (int): Security token of the scrip. It can be found in the instruments master file: 586 to (datetime): datetime up till when you want to receive candles 587 start (datetime): datetime from when you want to receive candles 588 resolution (Constants.Resolutions): resoulution of the candle. can be "1", "2", "3", "4", "5", "10", "15", "30", "45", "60", "120", "180", "240", "1D", "1W", "1M" 589 590 Returns: 591 dict: JSON response containing the historical candles 592 """ 593 594 if not isinstance(token, int): 595 raise TypeError("token must be an integer") 596 if not isinstance(to,datetime.datetime): 597 raise TypeError("to must be a datetime") 598 if not isinstance(start,datetime.datetime): 599 raise TypeError("start must be a datetime") 600 601 602 endpoint = "/data/history" 603 params = {"exchange": exchange,"token": token , "to": int(to.timestamp()), "from": int(start.timestamp()), "resolution": resolution} 604 return self._make_api_request("GET", endpoint, data=None,params=params)
Gets historical candle data of a particular instrument.
Documentation:
https://vortex.rupeezy.in/docs/latest/historical/#fetch-historical-candle-data
Arguments:
- exchange (Constants.ExchangeTypes): Possible values: [NSE_EQ, NSE_FO, BSE_EQ, BSE_FO, NSE_CD or MCX_FO]
- token (int): Security token of the scrip. It can be found in the instruments master file:
- to (datetime): datetime up till when you want to receive candles
- start (datetime): datetime from when you want to receive candles
- resolution (Constants.Resolutions): resoulution of the candle. can be "1", "2", "3", "4", "5", "10", "15", "30", "45", "60", "120", "180", "240", "1D", "1W", "1M"
Returns:
dict: JSON response containing the historical candles
606 def login_url(self, callback_param: str) -> str: 607 """ 608 Returns the login URL for the Vortex API. 609 610 Documentation: 611 https://vortex.rupeezy.in/docs/latest/authentication/ 612 613 Returns: 614 str: The login URL for the Vortex API. 615 """ 616 617 return f"https://flow.rupeezy.in?applicationId={self.application_id}&cb_param={callback_param}"
Returns the login URL for the Vortex API.
Documentation:
Returns:
str: The login URL for the Vortex API.
619 def exchange_token(self,auth_code: str) -> dict: 620 """ 621 Exchange the auth code received from the login URL for an access token. 622 623 Documentation: 624 https://vortex.rupeezy.in/docs/latest/authentication/ 625 626 Args: 627 auth_code (str): The authorization code received from the login URL. 628 629 Returns: 630 dict: JSON response containing the details of the user 631 """ 632 633 endpoint = "/user/session" 634 data = { 635 "token": auth_code, 636 "applicationId": self.application_id, 637 "checksum": self._sha256_hash(f"{self.application_id}{auth_code}{self.api_key}") 638 } 639 res = self._make_unauth_request("POST", endpoint= endpoint, data=data) 640 self._setup_client_code(login_object=res) 641 return res
Exchange the auth code received from the login URL for an access token.
Documentation:
Arguments:
- auth_code (str): The authorization code received from the login URL.
Returns:
dict: JSON response containing the details of the user
665 def mf_holdings(self) -> dict: 666 """ 667 Get the mutual fund holdings of the user. 668 669 Returns: 670 dict: JSON response with the following structure:: 671 672 { 673 "status": str, 674 "message": str, 675 "data": { 676 "OverallConfig": { 677 "OverAllXirr": float, 678 "MarketValueAmount": float, 679 "InvestedAmount": float, 680 "Returns": float, 681 "ReturnsPercentage": float, 682 "LastImportedAt": str or None, # ISO datetime 683 "FundsImportStatus": str, 684 "OneDayReturns": float, 685 "OneDayReturnsPercentage": float, 686 "NavAsOn": str # ISO datetime 687 }, 688 "IsinWiseData": { 689 "<ISIN>": { 690 "Name": str, 691 "MarketValueAmount": float, 692 "InvestedAmount": float, 693 "Returns": float, 694 "ReturnsPercentage": float, 695 "OneDayReturns": float, 696 "OneDayReturnsPercentage": float, 697 "IsinXirr": float, 698 "BseScheme": dict or None, 699 "CmotsScheme": dict or None, 700 "Folios": [ 701 { 702 "xirr": float, 703 "client_code": str, 704 "dpam_id": int, 705 "folio_number": str, 706 "folio_xirr": float, 707 "isin": str, 708 "co_code": int, 709 "name": str, 710 "type": str, 711 "units": float, 712 "redeemable_units": float, 713 "market_value_amount": float, 714 "redeemable_amount": float, 715 "invested_amount": float, 716 "nav_value": float, 717 "buy_price": float, 718 "as_on": str or None, 719 "hold_type": str, 720 "is_demat": str, 721 "one_day_returns": float, 722 "one_day_returns_percentage": float 723 } 724 ] 725 } 726 } 727 } 728 } 729 """ 730 endpoint = "/mf/holdings" 731 return self._make_api_request("GET", endpoint)
Get the mutual fund holdings of the user.
Returns:
dict: JSON response with the following structure::
{ "status": str, "message": str, "data": { "OverallConfig": { "OverAllXirr": float, "MarketValueAmount": float, "InvestedAmount": float, "Returns": float, "ReturnsPercentage": float, "LastImportedAt": str or None, # ISO datetime "FundsImportStatus": str, "OneDayReturns": float, "OneDayReturnsPercentage": float, "NavAsOn": str # ISO datetime }, "IsinWiseData": { "<ISIN>": { "Name": str, "MarketValueAmount": float, "InvestedAmount": float, "Returns": float, "ReturnsPercentage": float, "OneDayReturns": float, "OneDayReturnsPercentage": float, "IsinXirr": float, "BseScheme": dict or None, "CmotsScheme": dict or None, "Folios": [ { "xirr": float, "client_code": str, "dpam_id": int, "folio_number": str, "folio_xirr": float, "isin": str, "co_code": int, "name": str, "type": str, "units": float, "redeemable_units": float, "market_value_amount": float, "redeemable_amount": float, "invested_amount": float, "nav_value": float, "buy_price": float, "as_on": str or None, "hold_type": str, "is_demat": str, "one_day_returns": float, "one_day_returns_percentage": float } ] } } } }
733 def fund_details(self, isin: str) -> dict: 734 """ 735 Get details of a mutual fund scheme by ISIN. 736 737 Args: 738 isin (str): ISIN of the mutual fund scheme 739 740 Returns: 741 dict: JSON response with the following structure:: 742 743 { 744 "status": str, 745 "data": { 746 "assetAllocation": [ 747 { 748 "Isin": str, 749 "AssetCode": int, 750 "AssetName": str, 751 "Holding_CurrentMonth": float, 752 "Holding_PrevMonth": float, 753 "CurrentMonth": str 754 } 755 ], 756 "sectorAllocation": [ 757 { 758 "Isin": str, 759 "VALUE": float, 760 "PERC_HOLD": float, 761 "SECTOR": str, 762 "AsOnDate": str # ISO datetime 763 } 764 ], 765 "holdingAllocation": [ 766 { 767 "Isin": str, 768 "co_code": int, 769 "co_name": str, 770 "invdate": str, 771 "InvDate": str, # ISO datetime 772 "Perc_hold": float, 773 "MktValue": float, 774 "TotalShares": float, 775 "AssetType": str, 776 "rating": str 777 } 778 ], 779 "cmotsDetails": { 780 "isin": str, 781 "sch_name": str, 782 "navrs": float, 783 "Navdate": str, 784 "FundManager": str, 785 "SchemeAUM": float, 786 "EXPRATIO": float, 787 "riskometervalue": str, 788 "BenchmarkName": str, 789 "Category": str, 790 "fund_rating": int or None, 791 ... 792 }, 793 "bseSchemeDetails": { 794 "ISIN": str, 795 "SchemeCode": str, 796 "SchemeName": str, 797 "SchemeType": str, 798 "SchemePlan": str, 799 "PurchaseAllowed": str, 800 "RedemptionAllowed": str, 801 "SIPFlag": str, 802 "MinimumPurchaseAmount": float, 803 "MinimumRedemptionQty": float, 804 ... 805 } 806 } 807 } 808 """ 809 endpoint = "/mf/fund/details" 810 params = {"isin": isin} 811 return self._make_api_request("GET", endpoint, params=params)
Get details of a mutual fund scheme by ISIN.
Arguments:
- isin (str): ISIN of the mutual fund scheme
Returns:
dict: JSON response with the following structure::
{ "status": str, "data": { "assetAllocation": [ { "Isin": str, "AssetCode": int, "AssetName": str, "Holding_CurrentMonth": float, "Holding_PrevMonth": float, "CurrentMonth": str } ], "sectorAllocation": [ { "Isin": str, "VALUE": float, "PERC_HOLD": float, "SECTOR": str, "AsOnDate": str # ISO datetime } ], "holdingAllocation": [ { "Isin": str, "co_code": int, "co_name": str, "invdate": str, "InvDate": str, # ISO datetime "Perc_hold": float, "MktValue": float, "TotalShares": float, "AssetType": str, "rating": str } ], "cmotsDetails": { "isin": str, "sch_name": str, "navrs": float, "Navdate": str, "FundManager": str, "SchemeAUM": float, "EXPRATIO": float, "riskometervalue": str, "BenchmarkName": str, "Category": str, "fund_rating": int or None, ... }, "bseSchemeDetails": { "ISIN": str, "SchemeCode": str, "SchemeName": str, "SchemeType": str, "SchemePlan": str, "PurchaseAllowed": str, "RedemptionAllowed": str, "SIPFlag": str, "MinimumPurchaseAmount": float, "MinimumRedemptionQty": float, ... } } }
837 def portfolio_impact(self, isin: str, amount: float) -> dict: 838 """ 839 Get the portfolio impact of adding a mutual fund scheme. 840 841 Args: 842 isin (str): ISIN of the mutual fund scheme 843 amount (float): Investment amount 844 845 Returns: 846 dict: JSON response with the following structure:: 847 848 { 849 "status": str, 850 "data": { 851 "New": { 852 "Returns": float, 853 "Risk": float 854 }, 855 "Original": { 856 "Returns": float, 857 "Risk": float 858 } 859 } 860 } 861 """ 862 endpoint = "/mf/fund/portfolio-impact" 863 params = {"isin": isin, "amount": amount} 864 return self._make_api_request("GET", endpoint, params=params)
Get the portfolio impact of adding a mutual fund scheme.
Arguments:
- isin (str): ISIN of the mutual fund scheme
- amount (float): Investment amount
Returns:
dict: JSON response with the following structure::
{ "status": str, "data": { "New": { "Returns": float, "Risk": float }, "Original": { "Returns": float, "Risk": float } } }
866 def fund_overlap(self, isin: str, amount: float) -> dict: 867 """ 868 Get the fund overlap analysis for a mutual fund scheme against existing holdings. 869 870 Args: 871 isin (str): ISIN of the mutual fund scheme 872 amount (float): Investment amount 873 874 Returns: 875 dict: JSON response with the following structure:: 876 877 { 878 "status": str, 879 "data": { 880 "Value": float, 881 "Overlaps": [ 882 { 883 "Isin": str, 884 "Holdings": [ 885 { 886 "Name": str, 887 "Value": float 888 } 889 ] 890 } 891 ] 892 } 893 } 894 """ 895 endpoint = "/mf/fund/portfolio-overlap" 896 params = {"isin": isin, "amount": amount} 897 return self._make_api_request("GET", endpoint, params=params)
Get the fund overlap analysis for a mutual fund scheme against existing holdings.
Arguments:
- isin (str): ISIN of the mutual fund scheme
- amount (float): Investment amount
Returns:
dict: JSON response with the following structure::
{ "status": str, "data": { "Value": float, "Overlaps": [ { "Isin": str, "Holdings": [ { "Name": str, "Value": float } ] } ] } }
899 def sip_book(self) -> dict: 900 """ 901 Get the SIP (Systematic Investment Plan) book of the user. 902 903 Returns: 904 dict: JSON response with the following structure:: 905 906 { 907 "status": str, 908 "data": [ 909 { 910 "ID": int, 911 "CreatedAt": str, # ISO datetime 912 "UpdatedAt": str, # ISO datetime 913 "transactionCode": str, 914 "isin": str, 915 "amount": float, 916 "clientCode": str, 917 "schemeCode": str, 918 "remarks": str, 919 "startDate": str, # ISO datetime 920 "startDay": int, 921 "modifiedStartDate": str or None, # ISO datetime 922 "mandateId": str, 923 "state": str, 924 "frequency": str, 925 "stepUp": bool, 926 "stepUpFrequency": str, 927 "stepUpPercentage": float, 928 "stepUpAmount": float, 929 "isAmcSip": bool, 930 "initialInvestmentAmount": float, 931 "buySellType": str, 932 "nextInstallmentDate": str or None, # ISO datetime 933 "installmentNumber": int, 934 "schemeName": str, 935 "coCode": int, 936 "schemes_details": { 937 "bseSchemeDetails": dict, 938 "cmotsDetails": dict, 939 "nfoDetails": dict 940 } 941 } 942 ] 943 } 944 """ 945 endpoint = "/mf/purchases/synthetic/plan" 946 return self._make_api_request("GET", endpoint)
Get the SIP (Systematic Investment Plan) book of the user.
Returns:
dict: JSON response with the following structure::
{ "status": str, "data": [ { "ID": int, "CreatedAt": str, # ISO datetime "UpdatedAt": str, # ISO datetime "transactionCode": str, "isin": str, "amount": float, "clientCode": str, "schemeCode": str, "remarks": str, "startDate": str, # ISO datetime "startDay": int, "modifiedStartDate": str or None, # ISO datetime "mandateId": str, "state": str, "frequency": str, "stepUp": bool, "stepUpFrequency": str, "stepUpPercentage": float, "stepUpAmount": float, "isAmcSip": bool, "initialInvestmentAmount": float, "buySellType": str, "nextInstallmentDate": str or None, # ISO datetime "installmentNumber": int, "schemeName": str, "coCode": int, "schemes_details": { "bseSchemeDetails": dict, "cmotsDetails": dict, "nfoDetails": dict } } ] }
948 def save_backtest_result(self, stats, name: str, symbol: str = "", description: str = "", tags: list = None) -> dict: 949 """ 950 Save backtest results to Rupeezy for viewing on the developer portal. 951 952 Supports multiple backtesting libraries (auto-detected from the result type): 953 - **backtesting.py**: pass the stats object from Backtest.run() 954 - **vectorbt**: pass a vbt.Portfolio object 955 - **backtrader**: pass the strategy from cerebro.run() (i.e. results[0]) 956 957 Args: 958 stats: The result object from any supported backtesting library. 959 name (str): A label for this backtest run (e.g. "SMA Crossover v2"). 960 symbol (str, optional): Primary instrument symbol. 961 description (str, optional): Notes about this run. 962 tags (list[str], optional): Tags for filtering (e.g. ["intraday", "nifty"]). 963 964 Returns: 965 dict: { "status": "success", "backtest_id": "bt_abc123", "url": "https://..." } 966 """ 967 from .backtest import serialize_stats 968 payload = serialize_stats(stats, name, symbol, description, tags or []) 969 endpoint = "/strategies/backtests" 970 971 return self._make_api_request("POST", endpoint, data=payload)
Save backtest results to Rupeezy for viewing on the developer portal.
Supports multiple backtesting libraries (auto-detected from the result type):
- backtesting.py: pass the stats object from Backtest.run()
- vectorbt: pass a vbt.Portfolio object
- backtrader: pass the strategy from cerebro.run() (i.e. results[0])
Arguments:
- stats: The result object from any supported backtesting library.
- name (str): A label for this backtest run (e.g. "SMA Crossover v2").
- symbol (str, optional): Primary instrument symbol.
- description (str, optional): Notes about this run.
- tags (list[str], optional): Tags for filtering (e.g. ["intraday", "nifty"]).
Returns:
dict: { "status": "success", "backtest_id": "bt_abc123", "url": "https://..." }
973 def save_optimization_result( 974 self, 975 stats, 976 heatmap, 977 name: str, 978 symbol: str = "", 979 description: str = "", 980 maximize="Sharpe Ratio", 981 param_ranges: dict = None, 982 ) -> dict: 983 """ 984 Save optimization results to Rupeezy for viewing on the developer portal. 985 986 Supports multiple backtesting libraries (auto-detected from the result type): 987 - **backtesting.py**: pass stats + heatmap from bt.optimize(return_heatmap=True) 988 - **vectorbt**: pass Portfolio + metric Series from multi-param run 989 - **backtrader**: pass results list from cerebro.run() after optstrategy() 990 991 Args: 992 stats: The result object from any supported backtesting library. 993 heatmap: The heatmap/metric Series (backtesting.py/vectorbt) or 994 metric extraction callable (backtrader). 995 name (str): A label for this optimization run (e.g. "SMA Grid Search"). 996 symbol (str, optional): Primary instrument symbol (e.g. "NIFTY"). 997 description (str, optional): Notes about this optimization run. 998 maximize: The metric that was optimized. Should match the `maximize` 999 argument passed to Backtest.optimize(). Can be a string 1000 metric name (e.g. "Sharpe Ratio") or a callable. 1001 Defaults to "Sharpe Ratio". 1002 param_ranges (dict, optional): Explicit parameter range definitions. 1003 Keys are parameter names, values are range() objects or lists. 1004 Example: {"sma_fast": range(5, 51, 5), "sma_slow": range(20, 201, 10)} 1005 If not provided, ranges are inferred from the heatmap index. 1006 1007 Returns: 1008 dict: {"status": "success", "optimization_id": "opt_xxx", "backtest_id": "bt_xxx"} 1009 1010 Example:: 1011 1012 stats, heatmap = bt.optimize( 1013 sma_fast=range(5, 51, 5), 1014 sma_slow=range(20, 201, 10), 1015 maximize='Sharpe Ratio', 1016 return_heatmap=True, 1017 ) 1018 client.save_optimization_result( 1019 stats=stats, 1020 heatmap=heatmap, 1021 name="SMA Crossover Grid Search", 1022 symbol="NIFTY", 1023 maximize='Sharpe Ratio', 1024 param_ranges={"sma_fast": range(5, 51, 5), "sma_slow": range(20, 201, 10)}, 1025 ) 1026 """ 1027 is_maximize = True 1028 objective_metric = maximize 1029 1030 if isinstance(maximize, bool): 1031 is_maximize = maximize 1032 objective_metric = "Sharpe Ratio" 1033 1034 from .backtest import serialize_optimization 1035 payload = serialize_optimization( 1036 result=stats, 1037 heatmap=heatmap, 1038 name=name, 1039 symbol=symbol, 1040 description=description, 1041 objective_metric=objective_metric, 1042 maximize=is_maximize, 1043 param_ranges=param_ranges, 1044 ) 1045 endpoint = "/strategies/optimizations" 1046 return self._make_api_request("POST", endpoint, data=payload)
Save optimization results to Rupeezy for viewing on the developer portal.
Supports multiple backtesting libraries (auto-detected from the result type):
- backtesting.py: pass stats + heatmap from bt.optimize(return_heatmap=True)
- vectorbt: pass Portfolio + metric Series from multi-param run
- backtrader: pass results list from cerebro.run() after optstrategy()
Arguments:
- stats: The result object from any supported backtesting library.
- heatmap: The heatmap/metric Series (backtesting.py/vectorbt) or metric extraction callable (backtrader).
- name (str): A label for this optimization run (e.g. "SMA Grid Search").
- symbol (str, optional): Primary instrument symbol (e.g. "NIFTY").
- description (str, optional): Notes about this optimization run.
- maximize: The metric that was optimized. Should match the
maximizeargument passed to Backtest.optimize(). Can be a string metric name (e.g. "Sharpe Ratio") or a callable. Defaults to "Sharpe Ratio". - param_ranges (dict, optional): Explicit parameter range definitions. Keys are parameter names, values are range() objects or lists. Example: {"sma_fast": range(5, 51, 5), "sma_slow": range(20, 201, 10)} If not provided, ranges are inferred from the heatmap index.
Returns:
dict: {"status": "success", "optimization_id": "opt_xxx", "backtest_id": "bt_xxx"}
Example::
stats, heatmap = bt.optimize(
sma_fast=range(5, 51, 5),
sma_slow=range(20, 201, 10),
maximize='Sharpe Ratio',
return_heatmap=True,
)
client.save_optimization_result(
stats=stats,
heatmap=heatmap,
name="SMA Crossover Grid Search",
symbol="NIFTY",
maximize='Sharpe Ratio',
param_ranges={"sma_fast": range(5, 51, 5), "sma_slow": range(20, 201, 10)},
)
11class Constants: 12 """ 13 Constants used in the API 14 """ 15 class ExchangeTypes(str,Enum): 16 """ 17 Constants for exchanges 18 """ 19 NSE_FO = "NSE_FO" 20 BSE_FO = "BSE_FO" 21 NSE_EQUITY = "NSE_EQ" 22 NSE_EQ = "NSE_EQ" 23 BSE_EQUITY = "BSE_EQ" 24 BSE_EQ = "NSE_EQ" 25 NSE_CURRENCY = "NSE_CD" 26 NSE_CD = "NSE_CD" 27 MCX = "MCX_FO" 28 29 def __str__(self): 30 return str(self.value) 31 class VarietyTypes(str,Enum): 32 """ 33 Constants for varieties 34 """ 35 REGULAR_LIMIT_ORDER = "RL" 36 REGULAR_MARKET_ORDER = "RL-MKT" 37 STOP_LIMIT_ORDER = "SL" 38 STOP_MARKET_ORDER = "SL-MKT" 39 40 def __str__(self): 41 return str(self.value) 42 class ProductTypes(str,Enum): 43 """ 44 Constants for product types 45 """ 46 INTRADAY = "INTRADAY" 47 DELIVERY = "DELIVERY" 48 MTF = "MTF" 49 50 def __str__(self): 51 return str(self.value) 52 class ValidityTypes(str,Enum): 53 """ 54 Constants for validity types 55 """ 56 FULL_DAY = "DAY" 57 IMMEDIATE_OR_CANCEL = "IOC" 58 AFTER_MARKET = "AMO" 59 60 def __str__(self): 61 return str(self.value) 62 class TransactionSides(str,Enum): 63 """ 64 Constants for transaction sides 65 """ 66 BUY = "BUY" 67 SELL = "SELL" 68 def __str__(self): 69 return str(self.value) 70 class QuoteModes(str,Enum): 71 """ 72 Constants for quote modes 73 """ 74 LTP = "ltp" 75 FULL = "full" 76 OHLCV = "ohlcv" 77 def __str__(self): 78 return str(self.value) 79 class OrderMarginModes(str,Enum): 80 """ 81 Constants for order margin modes 82 """ 83 NEW_ORDER = "NEW" 84 MODIFY_ORDER = "MODIFY" 85 86 def __str__(self): 87 return str(self.value) 88 class Resolutions(str,Enum): 89 """ 90 Constants for resolutions 91 """ 92 MIN_1 = "1" 93 MIN_2 = "2" 94 MIN_3 = "3" 95 MIN_4 = "4" 96 MIN_5 = "5" 97 MIN_10 = "10" 98 MIN_15 = "15" 99 MIN_30 = "30" 100 MIN_45 = "45" 101 MIN_60 = "60" 102 MIN_120 = "120" 103 MIN_180 = "180" 104 MIN_240 = "240" 105 DAY = "1D" 106 WEEK = "1W" 107 MONTH = "1M" 108 109 def __str__(self): 110 return str(self.value)
Constants used in the API
15 class ExchangeTypes(str,Enum): 16 """ 17 Constants for exchanges 18 """ 19 NSE_FO = "NSE_FO" 20 BSE_FO = "BSE_FO" 21 NSE_EQUITY = "NSE_EQ" 22 NSE_EQ = "NSE_EQ" 23 BSE_EQUITY = "BSE_EQ" 24 BSE_EQ = "NSE_EQ" 25 NSE_CURRENCY = "NSE_CD" 26 NSE_CD = "NSE_CD" 27 MCX = "MCX_FO" 28 29 def __str__(self): 30 return str(self.value)
Constants for exchanges
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
31 class VarietyTypes(str,Enum): 32 """ 33 Constants for varieties 34 """ 35 REGULAR_LIMIT_ORDER = "RL" 36 REGULAR_MARKET_ORDER = "RL-MKT" 37 STOP_LIMIT_ORDER = "SL" 38 STOP_MARKET_ORDER = "SL-MKT" 39 40 def __str__(self): 41 return str(self.value)
Constants for varieties
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
42 class ProductTypes(str,Enum): 43 """ 44 Constants for product types 45 """ 46 INTRADAY = "INTRADAY" 47 DELIVERY = "DELIVERY" 48 MTF = "MTF" 49 50 def __str__(self): 51 return str(self.value)
Constants for product types
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
52 class ValidityTypes(str,Enum): 53 """ 54 Constants for validity types 55 """ 56 FULL_DAY = "DAY" 57 IMMEDIATE_OR_CANCEL = "IOC" 58 AFTER_MARKET = "AMO" 59 60 def __str__(self): 61 return str(self.value)
Constants for validity types
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
62 class TransactionSides(str,Enum): 63 """ 64 Constants for transaction sides 65 """ 66 BUY = "BUY" 67 SELL = "SELL" 68 def __str__(self): 69 return str(self.value)
Constants for transaction sides
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
70 class QuoteModes(str,Enum): 71 """ 72 Constants for quote modes 73 """ 74 LTP = "ltp" 75 FULL = "full" 76 OHLCV = "ohlcv" 77 def __str__(self): 78 return str(self.value)
Constants for quote modes
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
79 class OrderMarginModes(str,Enum): 80 """ 81 Constants for order margin modes 82 """ 83 NEW_ORDER = "NEW" 84 MODIFY_ORDER = "MODIFY" 85 86 def __str__(self): 87 return str(self.value)
Constants for order margin modes
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
88 class Resolutions(str,Enum): 89 """ 90 Constants for resolutions 91 """ 92 MIN_1 = "1" 93 MIN_2 = "2" 94 MIN_3 = "3" 95 MIN_4 = "4" 96 MIN_5 = "5" 97 MIN_10 = "10" 98 MIN_15 = "15" 99 MIN_30 = "30" 100 MIN_45 = "45" 101 MIN_60 = "60" 102 MIN_120 = "120" 103 MIN_180 = "180" 104 MIN_240 = "240" 105 DAY = "1D" 106 WEEK = "1W" 107 MONTH = "1M" 108 109 def __str__(self): 110 return str(self.value)
Constants for resolutions
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
The WebSocket client for connecting to vortex's live price and order streaming service
239 def __init__(self, access_token: str=None, websocket_endpoint="wss://wire.rupeezy.in/ws",reconnect=True, reconnect_max_tries=RECONNECT_MAX_TRIES, reconnect_max_delay=RECONNECT_MAX_DELAY, 240 connect_timeout=CONNECT_TIMEOUT, debug = False) -> None: 241 self._maximum_reconnect_max_tries = self.RECONNECT_MAX_TRIES 242 self._minimum_reconnect_max_delay = 0 243 if reconnect == False: 244 self.reconnect_max_tries = 0 245 elif reconnect_max_tries > self._maximum_reconnect_max_tries: 246 log.warning("`reconnect_max_tries` can not be more than {val}. Setting to highest possible value - {val}.".format( 247 val=self._maximum_reconnect_max_tries)) 248 self.reconnect_max_tries = self._maximum_reconnect_max_tries 249 else: 250 self.reconnect_max_tries = reconnect_max_tries 251 252 if reconnect_max_delay < self._minimum_reconnect_max_delay: 253 log.warning("`reconnect_max_delay` can not be less than {val}. Setting to lowest possible value - {val}.".format( 254 val=self._minimum_reconnect_max_delay)) 255 self.reconnect_max_delay = self._minimum_reconnect_max_delay 256 else: 257 self.reconnect_max_delay = reconnect_max_delay 258 259 self.connect_timeout = connect_timeout 260 if access_token == None: 261 if os.getenv("VORTEX_ACCESS_TOKEN") != None: 262 access_token = os.getenv("VORTEX_ACCESS_TOKEN") 263 else: 264 raise ValueError("Access token must be provided either as an argument or through the VORTEX_ACCESS_TOKEN environment variable.") 265 266 if os.getenv("VORTEX_FEED_BASE_URL") != None: 267 self.socket_url = os.getenv("VORTEX_FEED_BASE_URL") + "?auth_token=" + access_token 268 else: 269 self.socket_url = websocket_endpoint+"?auth_token="+access_token 270 271 self.access_token = access_token 272 self.socket_token = self.__getSocketToken__(self.access_token) 273 274 self.debug = debug 275 # self.on_price_update = None 276 self.on_price_update = None 277 self.on_open = None 278 self.on_close = None 279 self.on_error = None 280 self.on_connect = None 281 self.on_message = None 282 self.on_reconnect = None 283 self.on_noreconnect = None 284 self.on_order_update = None 285 self.subscribed_tokens = {} 286 pass
310 def connect(self, threaded=False, disable_ssl_verification=False): 311 """ 312 Establish a websocket connection. 313 - `disable_ssl_verification` disables building ssl context 314 """ 315 # Init WebSocket client factory 316 self._create_connection(self.socket_url, 317 useragent=self._user_agent()) 318 319 # Set SSL context 320 context_factory = None 321 if self.factory.isSecure and not disable_ssl_verification: 322 from urllib.parse import urlparse 323 hostname = urlparse(self.socket_url).hostname 324 context_factory = ssl.optionsForClientTLS(hostname) 325 326 # Establish WebSocket connection to a server 327 connectWS(self.factory, contextFactory=context_factory, timeout=self.connect_timeout) 328 329 if self.debug: 330 twisted_log.startLogging(sys.stdout) 331 332 # Run in seperate thread of blocking 333 opts = {} 334 # Run when reactor is not running 335 if not reactor.running: 336 if threaded: 337 # Signals are not allowed in non main thread by twisted so suppress it. 338 opts["installSignalHandlers"] = False 339 self.websocket_thread = threading.Thread(target=reactor.run, kwargs=opts) 340 self.websocket_thread.daemon = True 341 self.websocket_thread.start() 342 else: 343 reactor.run(**opts) 344 else: 345 print(reactor.running)
Establish a websocket connection.
disable_ssl_verificationdisables building ssl context
347 def is_connected(self): 348 """Check if WebSocket connection is established.""" 349 if self.ws and self.ws.state == self.ws.STATE_OPEN: 350 return True 351 else: 352 return False
Check if WebSocket connection is established.
359 def close(self, code=None, reason=None): 360 """Close the WebSocket connection.""" 361 self.stop_retry() 362 self._close(code, reason)
Close the WebSocket connection.
364 def stop(self): 365 """Stop the event loop. Should be used if main thread has to be closed in `on_close` method. 366 Reconnection mechanism cannot happen past this method 367 """ 368 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
370 def stop_retry(self): 371 """Stop auto retry when it is in progress.""" 372 if self.factory: 373 self.factory.stopTrying()
Stop auto retry when it is in progress.
375 def subscribe(self, exchange: str,token: int,mode: str)->bool: 376 """ 377 Subscribe to a list of instrument_tokens. 378 - `instrument_tokens` is list of instrument instrument_tokens to subscribe 379 """ 380 try: 381 self.ws.sendMessage(six.b(json.dumps({"message_type": self._message_subscribe, "exchange": exchange,"token": token,"mode": mode}))) 382 383 try: 384 self.subscribed_tokens[exchange][token] = mode 385 except KeyError: 386 self.subscribed_tokens[exchange] = {} 387 self.subscribed_tokens[exchange][token] = mode 388 389 return True 390 except Exception as e: 391 self._close(reason="Error while subscribe: {}".format(str(e))) 392 raise
Subscribe to a list of instrument_tokens.
instrument_tokensis list of instrument instrument_tokens to subscribe
394 def unsubscribe(self, exchange: str,token: int)->bool: 395 """ 396 Unsubscribe the given list of instrument_tokens. 397 - `instrument_tokens` is list of instrument_tokens to unsubscribe. 398 """ 399 try: 400 self.ws.sendMessage(six.b(json.dumps({"message_type": self._message_unsubscribe, "exchange": exchange,"token": token}))) 401 402 try: 403 del(self.subscribed_tokens[exchange][token]) 404 except KeyError: 405 pass 406 407 return True 408 except Exception as e: 409 self._close(reason="Error while unsubscribe: {}".format(str(e))) 410 raise
Unsubscribe the given list of instrument_tokens.
instrument_tokensis list of instrument_tokens to unsubscribe.
412 def resubscribe(self): 413 """Resubscribe to all current subscribed tokens.""" 414 modes = {} 415 416 for exchange in self.subscribed_tokens: 417 for token in self.subscribed_tokens[exchange]: 418 self.subscribe(exchange=exchange, token=token,mode=self.subscribed_tokens[exchange][token])
Resubscribe to all current subscribed tokens.