From d7650bbe3634511adf7c66175644173e9beabaa1 Mon Sep 17 00:00:00 2001 From: Rakesh R Date: Thu, 1 Jul 2021 16:55:42 +0530 Subject: [PATCH 1/5] feat: add tz info to all naive datetime object --- examples/order_margins.py | 3 +++ kiteconnect/connect.py | 41 +++++++++++++++++++++++++++------------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/examples/order_margins.py b/examples/order_margins.py index 1c5db4e8..cf093e84 100644 --- a/examples/order_margins.py +++ b/examples/order_margins.py @@ -62,6 +62,9 @@ margin_detail = kite.order_margins(order_param_multi) logging.info("Required margin for order_list: {}".format(margin_detail)) + # Compact margin response + margin_detail_compt = kite.basket_order_margins(order_param_multi, mode='compact') + logging.info("Required margin for order_list in compact form: {}".format(margin_detail_compt)) # Basket orders order_param_basket = [ diff --git a/kiteconnect/connect.py b/kiteconnect/connect.py index 5a205f77..e3a526a8 100644 --- a/kiteconnect/connect.py +++ b/kiteconnect/connect.py @@ -11,7 +11,9 @@ from six.moves.urllib.parse import urljoin import csv import json -import dateutil.parser +from dateutil.parser import parse +from dateutil.tz import tzoffset +from dateutil.utils import default_tzinfo import hashlib import logging import datetime @@ -263,8 +265,8 @@ def generate_session(self, request_token, api_secret): if "access_token" in resp: self.set_access_token(resp["access_token"]) - if resp["login_time"] and len(resp["login_time"]) == 19: - resp["login_time"] = dateutil.parser.parse(resp["login_time"]) + if resp["login_time"] and self.is_timestamp(resp["login_time"]): + resp["login_time"] = self.set_tz(resp["login_time"]) return resp @@ -398,8 +400,8 @@ def _format_response(self, data): for item in _list: # Convert date time string to datetime object for field in ["order_timestamp", "exchange_timestamp", "created", "last_instalment", "fill_timestamp", "timestamp", "last_trade_time"]: - if item.get(field) and len(item[field]) == 19: - item[field] = dateutil.parser.parse(item[field]) + if item.get(field) and self.is_timestamp(item[field]): + item[field] = self.set_tz(item[field]) return _list[0] if type(data) == dict else _list @@ -634,7 +636,7 @@ def _format_historical(self, data): records = [] for d in data["candles"]: record = { - "date": dateutil.parser.parse(d[0]), + "date": self.set_tz(d[0]), "open": d[1], "high": d[2], "low": d[3], @@ -755,13 +757,14 @@ def delete_gtt(self, trigger_id): """Delete a GTT order.""" return self._delete("gtt.delete", url_args={"trigger_id": trigger_id}) - def order_margins(self, params): + def order_margins(self, params, mode=None): """ Calculate margins for requested order list considering the existing positions and open orders - `params` is list of orders to retrive margins detail + - `mode` is margin response mode type. compact - Compact mode will only give the total margins """ - return self._post("order.margins", params=params, is_json=True) + return self._post("order.margins", params=params, is_json=True, query_params={'mode': mode}) def basket_order_margins(self, params, consider_positions=True, mode=None): """ @@ -794,8 +797,8 @@ def _parse_instruments(self, data): row["lot_size"] = int(row["lot_size"]) # Parse date - if len(row["expiry"]) == 10: - row["expiry"] = dateutil.parser.parse(row["expiry"]).date() + if self.is_timestamp(row["expiry"]): + row["expiry"] = self.set_tz(row["expiry"]).date() records.append(row) @@ -821,13 +824,27 @@ def _parse_mf_instruments(self, data): row["last_price"] = float(row["last_price"]) # Parse date - if len(row["last_price_date"]) == 10: - row["last_price_date"] = dateutil.parser.parse(row["last_price_date"]).date() + if self.is_timestamp(row["last_price_date"]): + row["last_price_date"] = self.set_tz(row["last_price_date"]).date() records.append(row) return records + def is_timestamp(self, string): + """Checks if string is timestamp""" + try: + parse(string) + return True + except ValueError: + return False + + def set_tz(self, string): + """Set default timezone to IST for naive time object""" + # Default timezone for all datetime object + default_tz = tzoffset("Asia/Kolkata", 19800) + return default_tzinfo(parse(string), default_tz) + def _user_agent(self): return (__title__ + "-python/").capitalize() + __version__ From 07489fc1bdc95f9805ef72c903dcbeb538bd33ca Mon Sep 17 00:00:00 2001 From: Rakesh R Date: Thu, 1 Jul 2021 17:15:56 +0530 Subject: [PATCH 2/5] fix:lint --- examples/order_margins.py | 2 +- kiteconnect/connect.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/order_margins.py b/examples/order_margins.py index cf093e84..7223c20f 100644 --- a/examples/order_margins.py +++ b/examples/order_margins.py @@ -63,7 +63,7 @@ margin_detail = kite.order_margins(order_param_multi) logging.info("Required margin for order_list: {}".format(margin_detail)) # Compact margin response - margin_detail_compt = kite.basket_order_margins(order_param_multi, mode='compact') + margin_detail_compt = kite.order_margins(order_param_multi, mode='compact') logging.info("Required margin for order_list in compact form: {}".format(margin_detail_compt)) # Basket orders diff --git a/kiteconnect/connect.py b/kiteconnect/connect.py index e3a526a8..0473a368 100644 --- a/kiteconnect/connect.py +++ b/kiteconnect/connect.py @@ -833,7 +833,7 @@ def _parse_mf_instruments(self, data): def is_timestamp(self, string): """Checks if string is timestamp""" - try: + try: parse(string) return True except ValueError: From c68859d95bc3ce81bd50130c61473a4c38cb0641 Mon Sep 17 00:00:00 2001 From: Rakesh R Date: Fri, 2 Jul 2021 15:18:48 +0530 Subject: [PATCH 3/5] fix: exception for response where error_type is not defined --- kiteconnect/connect.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/kiteconnect/connect.py b/kiteconnect/connect.py index 0473a368..3ec6de5f 100644 --- a/kiteconnect/connect.py +++ b/kiteconnect/connect.py @@ -925,8 +925,13 @@ def _request(self, route, method, url_args=None, params=None, is_json=False, que self.session_expiry_hook() # native Kite errors - exp = getattr(ex, data.get("error_type"), ex.GeneralException) - raise exp(data["message"], code=r.status_code) + # mf error response don't have error_type field + if data.get("error_type"): + exp = getattr(ex, data.get("error_type"), ex.GeneralException) + raise exp(data["message"], code=r.status_code) + else: + # Throw general exception for such undefined error type + raise ex.GeneralException(data["message"], code=r.status_code) return data["data"] elif "csv" in r.headers["content-type"]: From 72f2e9b79ed17d32ff0d01eddc8e11da8cfa847a Mon Sep 17 00:00:00 2001 From: Rakesh R Date: Mon, 5 Jul 2021 10:23:40 +0530 Subject: [PATCH 4/5] feat: review request --- kiteconnect/connect.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/kiteconnect/connect.py b/kiteconnect/connect.py index 3ec6de5f..462e8d0b 100644 --- a/kiteconnect/connect.py +++ b/kiteconnect/connect.py @@ -11,7 +11,7 @@ from six.moves.urllib.parse import urljoin import csv import json -from dateutil.parser import parse +from dateutil.parser import parse as datetimeparse from dateutil.tz import tzoffset from dateutil.utils import default_tzinfo import hashlib @@ -266,7 +266,7 @@ def generate_session(self, request_token, api_secret): self.set_access_token(resp["access_token"]) if resp["login_time"] and self.is_timestamp(resp["login_time"]): - resp["login_time"] = self.set_tz(resp["login_time"]) + resp["login_time"] = self.parseDateTime(resp["login_time"]) return resp @@ -401,7 +401,7 @@ def _format_response(self, data): # Convert date time string to datetime object for field in ["order_timestamp", "exchange_timestamp", "created", "last_instalment", "fill_timestamp", "timestamp", "last_trade_time"]: if item.get(field) and self.is_timestamp(item[field]): - item[field] = self.set_tz(item[field]) + item[field] = self.parseDateTime(item[field]) return _list[0] if type(data) == dict else _list @@ -636,7 +636,7 @@ def _format_historical(self, data): records = [] for d in data["candles"]: record = { - "date": self.set_tz(d[0]), + "date": self.parseDateTime(d[0]), "open": d[1], "high": d[2], "low": d[3], @@ -798,7 +798,7 @@ def _parse_instruments(self, data): # Parse date if self.is_timestamp(row["expiry"]): - row["expiry"] = self.set_tz(row["expiry"]).date() + row["expiry"] = self.parseDateTime(row["expiry"]).date() records.append(row) @@ -825,7 +825,7 @@ def _parse_mf_instruments(self, data): # Parse date if self.is_timestamp(row["last_price_date"]): - row["last_price_date"] = self.set_tz(row["last_price_date"]).date() + row["last_price_date"] = self.parseDateTime(row["last_price_date"]).date() records.append(row) @@ -834,16 +834,16 @@ def _parse_mf_instruments(self, data): def is_timestamp(self, string): """Checks if string is timestamp""" try: - parse(string) + datetimeparse(string) return True except ValueError: return False - def set_tz(self, string): + def parseDateTime(self, string): """Set default timezone to IST for naive time object""" # Default timezone for all datetime object default_tz = tzoffset("Asia/Kolkata", 19800) - return default_tzinfo(parse(string), default_tz) + return default_tzinfo(datetimeparse(string), default_tz) def _user_agent(self): return (__title__ + "-python/").capitalize() + __version__ From 5aef5889f47172cc5edae5344e6d59ecf2e59c21 Mon Sep 17 00:00:00 2001 From: Rakesh R Date: Fri, 16 Jul 2021 14:35:46 +0530 Subject: [PATCH 5/5] feat: add all time fields to _format_response --- kiteconnect/connect.py | 45 ++++++------------------------------------ 1 file changed, 6 insertions(+), 39 deletions(-) diff --git a/kiteconnect/connect.py b/kiteconnect/connect.py index 462e8d0b..b189f4f6 100644 --- a/kiteconnect/connect.py +++ b/kiteconnect/connect.py @@ -256,18 +256,15 @@ def generate_session(self, request_token, api_secret): h = hashlib.sha256(self.api_key.encode("utf-8") + request_token.encode("utf-8") + api_secret.encode("utf-8")) checksum = h.hexdigest() - resp = self._post("api.token", params={ + resp = self._format_response(self._post("api.token", params={ "api_key": self.api_key, "request_token": request_token, "checksum": checksum - }) + })) if "access_token" in resp: self.set_access_token(resp["access_token"]) - if resp["login_time"] and self.is_timestamp(resp["login_time"]): - resp["login_time"] = self.parseDateTime(resp["login_time"]) - return resp def invalidate_access_token(self, access_token=None): @@ -399,7 +396,8 @@ def _format_response(self, data): for item in _list: # Convert date time string to datetime object - for field in ["order_timestamp", "exchange_timestamp", "created", "last_instalment", "fill_timestamp", "timestamp", "last_trade_time"]: + for field in ["order_timestamp", "exchange_timestamp", "created", "last_instalment", "fill_timestamp", + "timestamp", "last_trade_time", "login_time", "expiry", "last_price_date"]: if item.get(field) and self.is_timestamp(item[field]): item[field] = self.parseDateTime(item[field]) @@ -786,23 +784,9 @@ def _parse_instruments(self, data): if not PY2 and type(d) == bytes: d = data.decode("utf-8").strip() - records = [] reader = csv.DictReader(StringIO(d)) - for row in reader: - row["instrument_token"] = int(row["instrument_token"]) - row["last_price"] = float(row["last_price"]) - row["strike"] = float(row["strike"]) - row["tick_size"] = float(row["tick_size"]) - row["lot_size"] = int(row["lot_size"]) - - # Parse date - if self.is_timestamp(row["expiry"]): - row["expiry"] = self.parseDateTime(row["expiry"]).date() - - records.append(row) - - return records + return self._format_response(list(reader)) def _parse_mf_instruments(self, data): # decode to string for Python 3 @@ -810,26 +794,9 @@ def _parse_mf_instruments(self, data): if not PY2 and type(d) == bytes: d = data.decode("utf-8").strip() - records = [] reader = csv.DictReader(StringIO(d)) - for row in reader: - row["minimum_purchase_amount"] = float(row["minimum_purchase_amount"]) - row["purchase_amount_multiplier"] = float(row["purchase_amount_multiplier"]) - row["minimum_additional_purchase_amount"] = float(row["minimum_additional_purchase_amount"]) - row["minimum_redemption_quantity"] = float(row["minimum_redemption_quantity"]) - row["redemption_quantity_multiplier"] = float(row["redemption_quantity_multiplier"]) - row["purchase_allowed"] = bool(int(row["purchase_allowed"])) - row["redemption_allowed"] = bool(int(row["redemption_allowed"])) - row["last_price"] = float(row["last_price"]) - - # Parse date - if self.is_timestamp(row["last_price_date"]): - row["last_price_date"] = self.parseDateTime(row["last_price_date"]).date() - - records.append(row) - - return records + return self._format_response(list(reader)) def is_timestamp(self, string): """Checks if string is timestamp"""