diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..abfe687 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog +All notable changes to this project will be documented in this file. + +## v1.0.1-BETA (16-Sep-2021) +### Added : +- Support for multiple default hosts. [ Internal ] +- Support to update endpoint's host after generating session according to `redirect` response. [ Internal ] +- Methods to consume Streaming API: `subscribe()` and `unsubscribe()`. For more details, refer [documentation](docs/StreamingApi.md). [ Feature ] +- Method `get_margin()`, For more details, refer [documentation](docs/MarginApi.md#get_margins) [ Feature ] + +### Updated: +- In method `session_2fa()`, the `access_code` is now an optional parameter. It is able to generate session token with OTT only. [ Feature ] +- Method `order_report()` now takes an additional boolean parameter `is_fno` whose default value is `False`. [ Feature ] +- The documentation. + +### Fixed: + +#### Minor: +- `__doc__` strings of few methods. + +## v1.0.0-BETA (Unreleased) +### Initial commits. +- For more details, refer [documentation](README.md) \ No newline at end of file diff --git a/README.md b/README.md index 68c9075..1f2b562 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@ Python 2.7 and 3.4+ If the python package is hosted on a repository, you can install directly using: ```sh -pip install git+https://github.com/osparamatrix/ks-orderapi-python.git +pip install git+https://github.com/noddy09/ks-orderapi-python.git ``` -(you may need to run `pip` with root permission: `sudo pip install git+https://github.com/osparamatrix/ks-orderapi-python.git`) +(you may need to run `pip` with root permission: `sudo pip install git+https://github.com/noddy09/ks-orderapi-python.git`) Then import the package: ```python @@ -48,10 +48,9 @@ from ks_api_client import ks_api client = ks_api.KSTradeApi(access_token = "", userid = "", \ consumer_key = "", ip = "127.0.0.1", app_id = "") -#For using specific environment use hosts as ["https://tradeapi.kotaksecurities.com/apim","https://ctradeapi.kotaksecurities.com/apim"] -client = ks_api.KSTradeApi(access_token = "", userid = "", \ - consumer_key = "", ip = "127.0.0.1", app_id = "", - hosts = ["https://tradeapi.kotaksecurities.com/apim","https://ctradeapi.kotaksecurities.com/apim"]) +#For using specific environment use host="https://tradeapi.kotaksecurities.com/apim" +client = ks_api.KSTradeApi(access_token = "", userid = "", consumer_key = "", + ip = "127.0.0.1", app_id = "", host ="https://sbx.kotaksecurities.com/apim") # Get session for user client.login(password = "") @@ -90,6 +89,9 @@ order_info = [ ] client.margin_required(transaction_type = "BUY",order_info = order_info) +# Get calculate margins +client.get_margins() + # Get Positions client.positions(position_type = "TODAYS") @@ -102,12 +104,21 @@ client.history("historicalprices-unadjusted",{"exchange":"bse","co_code":"476"," client.history("NSEFNO_HistoricalContinuousChart",{"symbol":"HDFC","expiry type": "near"}) client.history("LiveorEODHistorical",{"exchange":"BSE","co_code":"5400","period":"Y","cnt":"3"}) +# Subscribe to instrument token's stream. +def callback_method(message): + print(message) + print("Your logic/computation will come here.") +client.subscribe(input_token="745,754", auth_token="", callback=callback_method) + +# Unsubscribe from streaming service. +client.unsubscribe() + #Terminate user's Session client.logout() ``` ## Documentation for API Endpoints -All URIs are relative to *https://tradeapi.kotaksecurities.com/apim* +All URIs are relative to *https://tradeapi.kotaksecurities.com/apim* and *https://ctradeapi.kotaksecurities.com/apim* Class | Method | Description ------------ | ------------- | ------------- @@ -120,9 +131,12 @@ Class | Method | Description *ReportsApi* | [**order_report**](docs/ReportsApi.md#order_report) | Get order report *ReportsApi* | [**trade_report**](docs/ReportsApi.md#trade_report) | Get trade report *MarginApi* | [**margin_required**](docs/MarginApi.md#margin_required) | Get Margin Required for an order by amount or quantity. +*MarginApi* | [**get_margins**](docs/MarginApi.md#get_margins) | Get all calculated margins. *PositionsApi* | [**positions**](docs/PositionsApi.md#positions) | Get's Open position. *QuoteApi* | [**quote**](docs/QuoteApi.md#quote_details) | Get Quote details *HistoricalApi* | [**history**](docs/HistoricalApi.md#history) | Get historical data. +*StreamingApi* | [**subscribe**](docs/StreamingApi.md#subscribe) | Subscribe to streaming api of specified instrument tokens. +*StreamingApi* | [**unsubscribe**](docs/StreamingApi.md#unsubscribe) | Unsubscribe from streaming api. *SessionApi* | [**logout**](docs/SessionApi.md#logout) | Invalidate Session Token diff --git a/docs/SessionApi.md b/docs/SessionApi.md index c5d7850..9dff60a 100644 --- a/docs/SessionApi.md +++ b/docs/SessionApi.md @@ -37,7 +37,7 @@ Name | Type | Description | Notes **consumer_key** | **str**| | **ip** | **str**| | **app_id** | **str**| | - **host** | **list**| List of trade API host URLs | [optional] + **host** | **str**| API host URL| [optional] **proxy_url** | **str**| Proxy url | [optional] **proxy_user** | **str**| Proxy user's Username | [optional] **proxy_pass** | **str**| Proxy user's Password | [optional] diff --git a/docs/StreamingApi.md b/docs/StreamingApi.md new file mode 100644 index 0000000..92f700d --- /dev/null +++ b/docs/StreamingApi.md @@ -0,0 +1,78 @@ +# ks_api_client.StreamingApi + +All URIs are relative to *https://tradeapi.kotaksecurities.com/apim* + +Method | Description +------------- | ------------- +[**subscribe**](StreamingApi.md#subscribe) | Get Margin Required for an order by amount or quantity. +[**unsubscribe**](StreamingApi.md#unsubscribe) | Gives complete Margin Details of a Client from RMS. + + +# **subscribe** +> object subscribe(input_tokens, auth_token, callback, broadcast_host): + +Get streaming service subscription for specified instruments inputs. + +### Example + + +```python +from ks_api_client import ks_api + +client = ks_api.KSTradeApi(access_token = "access_token", userid = "userid", \ + consumer_key = "consumer_key", ip = "IP", app_id = "app_id") + +#First initialize session and generate session token + +try: + # Get Margin Required for an order by amount or quantity. + client.subscribe(input_tokens="", consumer_key="", consumer_secret="", callback=print, broadcast_host="https://wstreamer.kotaksecurities.com") +except Exception as e: + print("Exception when calling StreamingApi->subscribe: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- +**input_tokens** | **str** | Instrument tokens with comma seperated. | Example: "475,745" +**consumer_key** | **str** | Consumer Key | Mandatory field +**consumer_secret** | **str** | Consumer Secret | Mandatory field +**callback** | **obj** | Method object | method of function should have one mandatory parameter to accept message. Default method is print() +**broadcast_host** | **str** | String host URL | default value: "https://wstreamer.kotaksecurities.com" + +### Return type + +**object** + + +# **unsubscribe** +> object unsubscribe() + +Request to unsubscribe from streaming service. + +### Example + + +```python +from ks_api_client import ks_api + +client = ks_api.KSTradeApi(access_token = "access_token", userid = "userid", \ + consumer_key = "consumer_key", ip = "IP", app_id = "app_id") + +#First initialize session and generate session token + +try: + # Get Margin details. + client.unsubscribe() +except Exception as e: + print("Exception when calling StreamingApi->unsubscribe: %s\n" % e) +``` + +### Return type + +**object** + + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/ks_api_client/configuration.py b/ks_api_client/configuration.py index 52bfce6..6df3de1 100644 --- a/ks_api_client/configuration.py +++ b/ks_api_client/configuration.py @@ -137,7 +137,7 @@ def __init__(self, host=None, """Debug switch """ - self.verify_ssl = False + self.verify_ssl = True """SSL/TLS verification Set this to false to skip verifying SSL certificate when calling API from https server. diff --git a/ks_api_client/ks_api.py b/ks_api_client/ks_api.py index 5438b91..7aaf394 100644 --- a/ks_api_client/ks_api.py +++ b/ks_api_client/ks_api.py @@ -1,8 +1,11 @@ +from re import S import ks_api_client import base64 import json import os import socketio +import requests +import urllib.parse from urllib3 import make_headers from ks_api_client.exceptions import ApiException, ApiValueError @@ -12,10 +15,10 @@ UserCredentials, UserDetails, NewMISOrder, InlineObject class KSTradeApi(): - def __init__(self, access_token, userid, consumer_key, ip, app_id, - hosts=["https://tradeapi.kotaksecurities.com/apim","https://sbx.kotaksecurities.com/apim"], - proxy_url = '', proxy_user = '', proxy_pass = ''): + def __init__(self, access_token, userid, consumer_key, ip, app_id, host = None, + proxy_url = '', proxy_user = '', proxy_pass = '', verify_ssl=True): self.userid = userid + self.host = host self.consumer_key = consumer_key self.ip = ip self.app_id = app_id @@ -23,39 +26,54 @@ def __init__(self, access_token, userid, consumer_key, ip, app_id, self._proxy_user = proxy_user self._proxy_pass = proxy_pass self._proxy_url = proxy_url + self._verify_ssl = verify_ssl error = None - session_init = None - for host in hosts: - self.host = host - configuration = self.get_config(proxy_url, proxy_user, proxy_pass) - try: - self.api_client = ks_api_client.ApiClient(configuration) - session_init_res = ks_api_client.SessionApi(self.api_client).session_init(self.userid, \ - self.consumer_key, self.ip, self.app_id) - except ApiException as ex: - error = ex - continue + self.__session_init = None + + def init_session(self, session_init_res): if(session_init_res.get("Success")): - session_init = session_init_res.get("Success") + self.__session_init = session_init_res.get("Success") elif(session_init_res.get("success")): - session_init = session_init_res.get("success") - if self.host != session_init['redirect']['host']: - self.host = session_init['redirect']['host'] - configuration = self.get_config(proxy_url, proxy_user, proxy_pass) + self.__session_init = session_init_res.get("success") + if self.host != self.__session_init['redirect']['host']: + self.host = self.__session_init['redirect']['host'] + configuration = self.get_config(proxy_url, proxy_user, proxy_pass, verify_ssl) self.api_client = ks_api_client.ApiClient(configuration) - session_init = ks_api_client.SessionApi(self.api_client).session_init(self.userid, \ + self.__session_init = ks_api_client.SessionApi(self.api_client).session_init(self.userid, \ self.consumer_key, self.ip, self.app_id) - break - if not session_init and error: + if self.host: + configuration = self.get_config(proxy_url, proxy_user, proxy_pass, verify_ssl) + self.api_client = ks_api_client.ApiClient(configuration) + session_init_res = ks_api_client.SessionApi(self.api_client).session_init(self.userid, \ + self.consumer_key, self.ip, self.app_id) + init_session(self, session_init_res) + else: + hosts = ["https://tradeapi.kotaksecurities.com/apim","https://sbx.kotaksecurities.com/apim"] + for host in hosts: + self.host = host + configuration = self.get_config(proxy_url, proxy_user, proxy_pass, verify_ssl) + try: + self.api_client = ks_api_client.ApiClient(configuration) + session_init_res = ks_api_client.SessionApi(self.api_client).session_init(self.userid, \ + self.consumer_key, self.ip, self.app_id) + except ApiException as ex: + error = ex + continue + init_session(self, session_init_res) + break + if not self.__session_init and error: raise error + del self.__session_init - def get_config(self, proxy_url = '', proxy_user = '', proxy_pass = ''): + def get_config(self, proxy_url = '', proxy_user = '', proxy_pass = '', verify_ssl=True): configuration = ks_api_client.Configuration(self.host) configuration.access_token = self.access_token if proxy_url: configuration.proxy = proxy_url if proxy_user: configuration.proxy_headers = make_headers(proxy_basic_auth = ':'.join((proxy_user,proxy_pass))) + if not verify_ssl: + configuration.verify_ssl = False return configuration def login(self, password): @@ -234,7 +252,7 @@ def margin_required(self, transaction_type, order_info): ReqMargin = req_margin) return margin_required - def get_margins(self): + def margin(self): margins = ks_api_client.MarginApi(self.api_client).get_margins(self.consumer_key,self.session_token) return margins @@ -293,7 +311,8 @@ def history(self, resource, json_input): raise ApiValueError("exchange,co_code,period,cnt fields are required.") encoded_json = base64.urlsafe_b64encode(json.dumps(json_input).encode()).decode() data = ks_api_client.HistoricalApi(self.api_client).get_resource(resource,encoded_json) - return data + return data + #-------- Convert Array and object snake_case keys to camelCase ----------- def convertObject(self, object): newObj={} @@ -315,13 +334,12 @@ def convertArray(self, array): return new_array - def subscribe(self, input_tokens, callback, auth_token, broadcast_host="https://wstreamer.kotaksecurities.com"): + def subscribe(self, input_tokens, consumer_key, consumer_secret, callback=print, broadcast_host="https://wstreamer.kotaksecurities.com"): try: + auth_token = ":".join((consumer_key, consumer_secret)) proxy = "" + session = requests.session() if self._proxy_pass or self._proxy_url or self._proxy_user: - import urllib.parse - import requests - session = requests.session() scheme = "" parsed = urllib.parse.urlparse(self._proxy_url) if not parsed.scheme: @@ -334,7 +352,7 @@ def subscribe(self, input_tokens, callback, auth_token, broadcast_host="https:// if parsed.port: proxy += ":" + str(parsed.port) session.proxies.update({'http':proxy, 'https':proxy}) - session.verify = 's' in scheme + session.verify = self._verify_ssl # Generating base64 encoding of consumer credentials AUTH_BASE64 = base64.b64encode(auth_token.encode("UTF-8")) PAYLOAD = {"authentication": AUTH_BASE64.decode("UTF-8")} @@ -348,7 +366,7 @@ def subscribe(self, input_tokens, callback, auth_token, broadcast_host="https:// else: self.sio = socketio.Client( reconnection=True, request_timeout=20, reconnection_attempts=5, engineio_logger=True, - logger=True,http_session=session, ssl_verify=session.verify) + logger=True, http_session=session, ssl_verify=session.verify) @self.sio.event def connect(): @@ -365,8 +383,7 @@ def disconnect(): @self.sio.on('getdata') def on_getdata(data, callback=callback): callback(data) - - # Do the connection using above access token + self.sio.connect(broadcast_host, headers={'Authorization': 'Bearer ' + jsonResponse['result']['token']}, transports=["websocket"], socketio_path='/feed') diff --git a/ks_api_client/settings.py b/ks_api_client/settings.py index 0717f01..f2185d6 100644 --- a/ks_api_client/settings.py +++ b/ks_api_client/settings.py @@ -1,5 +1,4 @@ -host = ["https://tradeapi.kotaksecurities.com/apim", - "https://sbx.kotaksecurities.com/apim"] +host = "https://sbx.kotaksecurities.com/apim" access_token = "" userid = "" consumer_key = "" diff --git a/setup.py b/setup.py index 41101bf..9b16313 100644 --- a/setup.py +++ b/setup.py @@ -11,9 +11,7 @@ # prerequisite: setuptools # http://pypi.python.org/pypi/setuptools -REQUIRES = ["certifi >= 14.05.14", "future; python_version<=2.7", "six >= 1.10", - "python_dateutil >= 2.5.3", "setuptools >= 21.0.0", "urllib3 >= 1.15.1", - "python-socketio[client]==5.3.0", "requests==2.26.0"] +REQUIRES = ["certifi>=14.05.14", "six >= 1.10", "python_dateutil >= 2.5.3", "urllib3 > 1.15", "python-socketio[client]==5.3.0", "requests==2.26.0"] setup( name=NAME,