kitecli 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kitecli-0.2.0 → kitecli-0.2.1}/PKG-INFO +1 -1
- {kitecli-0.2.0 → kitecli-0.2.1}/cli/display.py +0 -6
- {kitecli-0.2.0 → kitecli-0.2.1}/cli/executor.py +1 -1
- {kitecli-0.2.0 → kitecli-0.2.1}/cli/kotak_manager.py +209 -33
- {kitecli-0.2.0 → kitecli-0.2.1}/cli/live_session.py +165 -31
- {kitecli-0.2.0 → kitecli-0.2.1}/cli/telegram_bot.py +63 -25
- {kitecli-0.2.0 → kitecli-0.2.1}/kitecli.egg-info/PKG-INFO +1 -1
- {kitecli-0.2.0 → kitecli-0.2.1}/pyproject.toml +1 -1
- {kitecli-0.2.0 → kitecli-0.2.1}/tests/test_multi_broker.py +67 -0
- {kitecli-0.2.0 → kitecli-0.2.1}/tests/test_telegram.py +62 -0
- kitecli-0.2.1/tests/test_ui.py +98 -0
- kitecli-0.2.0/tests/test_ui.py +0 -74
- {kitecli-0.2.0 → kitecli-0.2.1}/README.md +0 -0
- {kitecli-0.2.0 → kitecli-0.2.1}/cli/__init__.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.1}/cli/advisor.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.1}/cli/api_client.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.1}/cli/base_manager.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.1}/cli/config.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.1}/cli/kite_manager.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.1}/cli/main.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.1}/cli/nli.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.1}/cli/parser.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.1}/cli/recorder.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.1}/kitecli.egg-info/SOURCES.txt +0 -0
- {kitecli-0.2.0 → kitecli-0.2.1}/kitecli.egg-info/dependency_links.txt +0 -0
- {kitecli-0.2.0 → kitecli-0.2.1}/kitecli.egg-info/entry_points.txt +0 -0
- {kitecli-0.2.0 → kitecli-0.2.1}/kitecli.egg-info/requires.txt +0 -0
- {kitecli-0.2.0 → kitecli-0.2.1}/kitecli.egg-info/top_level.txt +0 -0
- {kitecli-0.2.0 → kitecli-0.2.1}/setup.cfg +0 -0
- {kitecli-0.2.0 → kitecli-0.2.1}/tests/test_nli.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.1}/tests/test_parser.py +0 -0
|
@@ -117,11 +117,9 @@ def display_positions(accounts_data: list[dict]) -> None:
|
|
|
117
117
|
table.add_column("Avg Price", justify="right")
|
|
118
118
|
table.add_column("LTP", justify="right")
|
|
119
119
|
table.add_column("P&L", justify="right")
|
|
120
|
-
table.add_column("P&L %", justify="right")
|
|
121
120
|
|
|
122
121
|
for pos in positions:
|
|
123
122
|
pnl = float(pos.get("pnl", 0))
|
|
124
|
-
pnl_pct = float(pos.get("pnl_pct", 0))
|
|
125
123
|
style = _pnl_style(pnl)
|
|
126
124
|
|
|
127
125
|
table.add_row(
|
|
@@ -130,7 +128,6 @@ def display_positions(accounts_data: list[dict]) -> None:
|
|
|
130
128
|
_format_currency(float(pos.get("average_price", 0))),
|
|
131
129
|
_format_currency(float(pos.get("last_price", 0))),
|
|
132
130
|
Text(_format_currency(pnl), style=style),
|
|
133
|
-
Text(_format_pnl_pct(pnl_pct), style=style),
|
|
134
131
|
)
|
|
135
132
|
|
|
136
133
|
panel = Panel(
|
|
@@ -233,11 +230,9 @@ def render_positions_to_string(accounts_data: list[dict], width: int = 80, show_
|
|
|
233
230
|
table.add_column("Avg Price", justify="right")
|
|
234
231
|
table.add_column("LTP", justify="right")
|
|
235
232
|
table.add_column("P&L", justify="right")
|
|
236
|
-
table.add_column("P&L %", justify="right")
|
|
237
233
|
|
|
238
234
|
for pos in positions:
|
|
239
235
|
pnl = float(pos.get("pnl", 0))
|
|
240
|
-
pnl_pct = float(pos.get("pnl_pct", 0))
|
|
241
236
|
style = _pnl_style(pnl)
|
|
242
237
|
|
|
243
238
|
symbol = str(pos.get("tradingsymbol", ""))
|
|
@@ -260,7 +255,6 @@ def render_positions_to_string(accounts_data: list[dict], width: int = 80, show_
|
|
|
260
255
|
_format_currency(float(pos.get("average_price", 0))),
|
|
261
256
|
_format_currency(float(pos.get("last_price", 0))),
|
|
262
257
|
Text(_format_currency(pnl), style=style),
|
|
263
|
-
Text(_format_pnl_pct(pnl_pct), style=style),
|
|
264
258
|
)
|
|
265
259
|
|
|
266
260
|
|
|
@@ -39,6 +39,26 @@ def _patched_thread_init(self, *args, **kwargs):
|
|
|
39
39
|
_orig_thread_init(self, *args, **kwargs)
|
|
40
40
|
_threading.Thread.__init__ = _patched_thread_init
|
|
41
41
|
|
|
42
|
+
import builtins as _builtins
|
|
43
|
+
_orig_print = _builtins.print
|
|
44
|
+
_sdk_logger = logging.getLogger("kotak_sdk_print")
|
|
45
|
+
|
|
46
|
+
def _safe_print(*args, **kwargs):
|
|
47
|
+
try:
|
|
48
|
+
import inspect
|
|
49
|
+
frame = inspect.currentframe().f_back
|
|
50
|
+
if frame:
|
|
51
|
+
mod_name = frame.f_globals.get("__name__", "")
|
|
52
|
+
if mod_name and ("neo_api_client" in mod_name or "neo_api" in mod_name):
|
|
53
|
+
# Suppress SDK print pollution and redirect to debug logs
|
|
54
|
+
_sdk_logger.debug("SDK print suppressed: %s", " ".join(str(a) for a in args))
|
|
55
|
+
return
|
|
56
|
+
except Exception:
|
|
57
|
+
pass
|
|
58
|
+
_orig_print(*args, **kwargs)
|
|
59
|
+
|
|
60
|
+
_builtins.print = _safe_print
|
|
61
|
+
|
|
42
62
|
from cli.base_manager import BaseBrokerManager
|
|
43
63
|
|
|
44
64
|
logger = logging.getLogger(__name__)
|
|
@@ -65,7 +85,7 @@ def _check_neo_error(resp: Any) -> None:
|
|
|
65
85
|
|
|
66
86
|
# 2. Backend server-level errors
|
|
67
87
|
stat = str(resp.get("stat", "")).upper()
|
|
68
|
-
if stat in ("NOT_OK", "FAIL", "ERROR"):
|
|
88
|
+
if stat in ("NOT_OK", "FAIL", "ERROR") or "ERROR" in stat:
|
|
69
89
|
err_msg = resp.get("errMsg") or resp.get("desc") or resp.get("message") or "Unknown Kotak API error"
|
|
70
90
|
st_code = resp.get("stCode") or resp.get("stcode")
|
|
71
91
|
# If the error is simply 'No Data' (empty order book or positions), do not raise an exception
|
|
@@ -209,37 +229,91 @@ class KotakAccountManager(BaseBrokerManager):
|
|
|
209
229
|
|
|
210
230
|
client = NeoAPI(environment="prod", consumer_key=consumer_key)
|
|
211
231
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
232
|
+
import requests as _req
|
|
233
|
+
import json as _json
|
|
234
|
+
import re as _re
|
|
235
|
+
from six.moves.urllib.parse import urlencode as _urlencode
|
|
236
|
+
_proxies = {"http": proxy, "https": proxy} if proxy else None
|
|
237
|
+
_orig_request = client.api_client.rest_client.request
|
|
238
|
+
|
|
239
|
+
def _proxied_request(method, url, query_params=None, headers=None, body=None):
|
|
240
|
+
method = method.upper()
|
|
241
|
+
headers = headers or {}
|
|
242
|
+
if 'Content-Type' not in headers:
|
|
243
|
+
headers['Content-Type'] = 'application/json'
|
|
244
|
+
|
|
245
|
+
u = url
|
|
246
|
+
if query_params:
|
|
247
|
+
u_with_params = u
|
|
248
|
+
if '?' not in u:
|
|
249
|
+
u_with_params += '?' + _urlencode(query_params)
|
|
250
|
+
else:
|
|
251
|
+
pass
|
|
252
|
+
else:
|
|
253
|
+
u_with_params = u
|
|
254
|
+
|
|
255
|
+
def _do_req(h):
|
|
256
|
+
req_kwargs = {}
|
|
257
|
+
if _proxies:
|
|
258
|
+
req_kwargs["proxies"] = _proxies
|
|
259
|
+
|
|
260
|
+
logger.debug("Kotak REST [%s] requesting: %s", method, u_with_params)
|
|
261
|
+
logger.debug("Kotak REST headers: %s", {k: v[:15] + '...' if len(str(v)) > 15 else v for k, v in h.items()})
|
|
262
|
+
|
|
227
263
|
if method in ['POST', 'PUT', 'PATCH', 'DELETE']:
|
|
228
|
-
if
|
|
229
|
-
url += '?' + _urlencode(query_params)
|
|
230
|
-
if _re.search('json', headers['Content-Type'], _re.IGNORECASE):
|
|
264
|
+
if _re.search('json', h['Content-Type'], _re.IGNORECASE):
|
|
231
265
|
request_body = _json.dumps(body) if body is not None else None
|
|
232
|
-
return _req.post(url=
|
|
233
|
-
elif _re.search('x-www-form-urlencoded',
|
|
266
|
+
return _req.post(url=u_with_params, headers=h, data=request_body, **req_kwargs)
|
|
267
|
+
elif _re.search('x-www-form-urlencoded', h['Content-Type'], _re.IGNORECASE):
|
|
234
268
|
request_body = {"jData": _json.dumps(body)} if body is not None else {}
|
|
235
|
-
return _req.post(url=
|
|
269
|
+
return _req.post(url=u_with_params, headers=h, data=request_body, **req_kwargs)
|
|
236
270
|
elif method == 'GET':
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
271
|
+
return _req.get(url=u_with_params, headers=h, **req_kwargs)
|
|
272
|
+
return _orig_request(method, url, query_params=query_params, headers=h, body=body)
|
|
273
|
+
|
|
274
|
+
resp = _do_req(headers)
|
|
275
|
+
logger.info("Kotak REST %s %s → status %s", method, u_with_params, resp.status_code)
|
|
276
|
+
try:
|
|
277
|
+
logger.debug("Kotak REST response body: %s", resp.json())
|
|
278
|
+
except Exception:
|
|
279
|
+
logger.debug("Kotak REST response body (raw): %s", resp.text[:200])
|
|
241
280
|
|
|
242
|
-
|
|
281
|
+
# Check for IP mismatch error (stCode 1037) or Session expired (stCode 100008)
|
|
282
|
+
try:
|
|
283
|
+
resp_json = resp.json()
|
|
284
|
+
if isinstance(resp_json, dict):
|
|
285
|
+
st_code = resp_json.get("stCode")
|
|
286
|
+
err_msg = str(resp_json.get("errMsg", "")).lower()
|
|
287
|
+
if (
|
|
288
|
+
st_code == 1037 or
|
|
289
|
+
st_code == 100008 or
|
|
290
|
+
"session ip" in err_msg or
|
|
291
|
+
"unauthorized" in err_msg
|
|
292
|
+
):
|
|
293
|
+
logger.info("Kotak session invalid (stCode=%s, msg='%s') for account '%s'. Triggering auto-login...", st_code, resp_json.get("errMsg"), name)
|
|
294
|
+
if self.auto_login(consumer_key):
|
|
295
|
+
# Update headers with new credentials
|
|
296
|
+
headers["Sid"] = client.configuration.edit_sid
|
|
297
|
+
headers["Auth"] = client.configuration.edit_token
|
|
298
|
+
if isinstance(query_params, dict):
|
|
299
|
+
if "sId" in query_params:
|
|
300
|
+
query_params["sId"] = client.configuration.serverId
|
|
301
|
+
if "sid" in query_params:
|
|
302
|
+
query_params["sid"] = client.configuration.edit_sid
|
|
303
|
+
logger.info("Retrying request after successful auto-login...")
|
|
304
|
+
resp = _do_req(headers)
|
|
305
|
+
logger.info("Kotak REST retry %s %s → status %s", method, u_with_params, resp.status_code)
|
|
306
|
+
try:
|
|
307
|
+
logger.debug("Kotak REST retry response body: %s", resp.json())
|
|
308
|
+
except Exception:
|
|
309
|
+
logger.debug("Kotak REST retry response body (raw): %s", resp.text[:200])
|
|
310
|
+
except Exception as e:
|
|
311
|
+
logger.debug("Failed to check response for session validity: %s", e)
|
|
312
|
+
|
|
313
|
+
return resp
|
|
314
|
+
|
|
315
|
+
client.api_client.rest_client.request = _proxied_request
|
|
316
|
+
if proxy:
|
|
243
317
|
logger.info("Kotak account '%s': HTTP proxy enabled (%s…)", name, proxy[:20])
|
|
244
318
|
|
|
245
319
|
# Also monkeypatch websocket-client (used by Kotak Neo WebSocket) to route over the proxy.
|
|
@@ -302,11 +376,18 @@ class KotakAccountManager(BaseBrokerManager):
|
|
|
302
376
|
)
|
|
303
377
|
except Exception as exc:
|
|
304
378
|
logger.info(
|
|
305
|
-
"Saved Kotak token for '%s' (key=%s…) expired: %s",
|
|
379
|
+
"Saved Kotak token for '%s' (key=%s…) expired: %s. Will attempt auto-login.",
|
|
306
380
|
name, consumer_key[:8], exc,
|
|
307
381
|
)
|
|
308
382
|
self._authenticated[consumer_key] = False
|
|
309
383
|
|
|
384
|
+
if not self._authenticated.get(consumer_key):
|
|
385
|
+
logger.info("No valid Kotak session found for '%s' (key=%s…). Attempting auto-login...", name, consumer_key[:8])
|
|
386
|
+
try:
|
|
387
|
+
self.auto_login(consumer_key)
|
|
388
|
+
except Exception as e:
|
|
389
|
+
logger.error("Auto-login failed for Kotak account '%s': %s", name, e)
|
|
390
|
+
|
|
310
391
|
return "" # no login URL for Kotak
|
|
311
392
|
|
|
312
393
|
def auto_login(self, account_key: str, **_kwargs) -> bool:
|
|
@@ -675,11 +756,22 @@ class KotakAccountManager(BaseBrokerManager):
|
|
|
675
756
|
}
|
|
676
757
|
kotak_transaction_type = transaction_type_map.get(transaction_type, transaction_type)
|
|
677
758
|
|
|
759
|
+
# Resolve option lot size to align split legs
|
|
760
|
+
from cli.api_client import _kite_manager
|
|
761
|
+
lot_size = _kite_manager.get_nfo_lot_sizes().get(tradingsymbol, 1)
|
|
762
|
+
|
|
678
763
|
KOTAK_FREEZE_LIMIT = 1800
|
|
764
|
+
if lot_size > 1:
|
|
765
|
+
freeze_limit = (KOTAK_FREEZE_LIMIT // lot_size) * lot_size
|
|
766
|
+
if freeze_limit == 0:
|
|
767
|
+
freeze_limit = lot_size
|
|
768
|
+
else:
|
|
769
|
+
freeze_limit = KOTAK_FREEZE_LIMIT
|
|
770
|
+
|
|
679
771
|
legs: list[int] = []
|
|
680
772
|
remaining = quantity
|
|
681
773
|
while remaining > 0:
|
|
682
|
-
leg_qty = min(remaining,
|
|
774
|
+
leg_qty = min(remaining, freeze_limit)
|
|
683
775
|
legs.append(leg_qty)
|
|
684
776
|
remaining -= leg_qty
|
|
685
777
|
|
|
@@ -856,10 +948,12 @@ class KotakTicker:
|
|
|
856
948
|
MODE_QUOTE = "quote"
|
|
857
949
|
MODE_FULL = "full"
|
|
858
950
|
|
|
859
|
-
def __init__(self, api_key: str, access_token: str | None, client: Any) -> None:
|
|
951
|
+
def __init__(self, api_key: str, access_token: str | None, client: Any, reconnect: bool = True, reconnect_max_tries: int = 50) -> None:
|
|
860
952
|
self.api_key = api_key
|
|
861
953
|
self.access_token = access_token
|
|
862
954
|
self.client = client
|
|
955
|
+
self.reconnect = reconnect
|
|
956
|
+
self.reconnect_max_tries = reconnect_max_tries
|
|
863
957
|
|
|
864
958
|
self.on_connect = None
|
|
865
959
|
self.on_ticks = None
|
|
@@ -867,8 +961,14 @@ class KotakTicker:
|
|
|
867
961
|
self.on_close = None
|
|
868
962
|
self.on_error = None
|
|
869
963
|
|
|
964
|
+
self._reconnect_attempt = 0
|
|
965
|
+
self._stop_reconnect = False
|
|
966
|
+
self._reconnect_timer = None
|
|
967
|
+
self._reconnect_lock = _threading.Lock()
|
|
968
|
+
|
|
870
969
|
def connect(self, **kwargs) -> None:
|
|
871
970
|
"""Register callbacks and connect the Kotak Neo WebSocket threads."""
|
|
971
|
+
self._stop_reconnect = False
|
|
872
972
|
self.client.on_open = self._on_open
|
|
873
973
|
self.client.on_message = self._on_message
|
|
874
974
|
self.client.on_error = self._on_error
|
|
@@ -881,10 +981,17 @@ class KotakTicker:
|
|
|
881
981
|
except Exception as e:
|
|
882
982
|
logger.error("Failed to connect Kotak Neo order feed: %s", e)
|
|
883
983
|
if self.on_error:
|
|
884
|
-
|
|
984
|
+
error_str = str(e).lower()
|
|
985
|
+
code = 403 if ("unauthorized" in error_str or "auth" in error_str or "session" in error_str or "token" in error_str) else 0
|
|
986
|
+
self.on_error(self, code, str(e))
|
|
885
987
|
|
|
886
988
|
def close(self) -> None:
|
|
887
989
|
"""Disconnect and clean up Kotak Neo WebSocket connections."""
|
|
990
|
+
self._stop_reconnect = True
|
|
991
|
+
with self._reconnect_lock:
|
|
992
|
+
if self._reconnect_timer:
|
|
993
|
+
self._reconnect_timer.cancel()
|
|
994
|
+
self._reconnect_timer = None
|
|
888
995
|
logger.info("Closing Kotak Neo WebSocket (api_key=%s…)...", self.api_key[:8])
|
|
889
996
|
neo_ws = getattr(self.client, "NeoWebSocket", None)
|
|
890
997
|
if neo_ws:
|
|
@@ -917,6 +1024,7 @@ class KotakTicker:
|
|
|
917
1024
|
|
|
918
1025
|
def _on_open(self, message: Any = None) -> None:
|
|
919
1026
|
logger.info("Kotak Neo WebSocket connected: %s", message or "Session opened")
|
|
1027
|
+
self._reconnect_attempt = 0
|
|
920
1028
|
if self.on_connect:
|
|
921
1029
|
self.on_connect(self, None)
|
|
922
1030
|
|
|
@@ -924,11 +1032,76 @@ class KotakTicker:
|
|
|
924
1032
|
logger.info("Kotak Neo WebSocket closed: %s", message or "Session closed")
|
|
925
1033
|
if self.on_close:
|
|
926
1034
|
self.on_close(self, 1000, str(message or "Closed"))
|
|
1035
|
+
if self.reconnect and not self._stop_reconnect:
|
|
1036
|
+
self._trigger_reconnect()
|
|
927
1037
|
|
|
928
1038
|
def _on_error(self, error: Any) -> None:
|
|
929
|
-
|
|
1039
|
+
error_str = str(error).lower()
|
|
1040
|
+
code = 403 if ("unauthorized" in error_str or "auth" in error_str or "session" in error_str or "token" in error_str) else 0
|
|
1041
|
+
logger.error("Kotak Neo WebSocket error: %s (mapped code=%s)", error, code)
|
|
930
1042
|
if self.on_error:
|
|
931
|
-
self.on_error(self,
|
|
1043
|
+
self.on_error(self, code, str(error))
|
|
1044
|
+
|
|
1045
|
+
# Trigger reconnection if it's not a permanent auth failure (403)
|
|
1046
|
+
if self.reconnect and not self._stop_reconnect and code != 403:
|
|
1047
|
+
self._trigger_reconnect()
|
|
1048
|
+
|
|
1049
|
+
def _trigger_reconnect(self) -> None:
|
|
1050
|
+
with self._reconnect_lock:
|
|
1051
|
+
if self._stop_reconnect:
|
|
1052
|
+
return
|
|
1053
|
+
if self._reconnect_timer:
|
|
1054
|
+
return
|
|
1055
|
+
|
|
1056
|
+
if self._reconnect_attempt >= self.reconnect_max_tries:
|
|
1057
|
+
logger.error("Kotak Neo WS: Max reconnect attempts (%s) reached. Stopping.", self.reconnect_max_tries)
|
|
1058
|
+
return
|
|
1059
|
+
|
|
1060
|
+
# Exponential backoff delay
|
|
1061
|
+
delay = min(2 ** (self._reconnect_attempt + 1), 60)
|
|
1062
|
+
self._reconnect_attempt += 1
|
|
1063
|
+
logger.info("Kotak Neo WS: Connection lost. Scheduling reconnect attempt %s/%s in %s seconds...",
|
|
1064
|
+
self._reconnect_attempt, self.reconnect_max_tries, delay)
|
|
1065
|
+
|
|
1066
|
+
self._reconnect_timer = _threading.Timer(delay, self._run_reconnect)
|
|
1067
|
+
self._reconnect_timer.daemon = True
|
|
1068
|
+
self._reconnect_timer.start()
|
|
1069
|
+
|
|
1070
|
+
def _run_reconnect(self) -> None:
|
|
1071
|
+
with self._reconnect_lock:
|
|
1072
|
+
self._reconnect_timer = None
|
|
1073
|
+
if self._stop_reconnect:
|
|
1074
|
+
return
|
|
1075
|
+
|
|
1076
|
+
logger.info("Kotak Neo WS: Attempting reconnection now...")
|
|
1077
|
+
try:
|
|
1078
|
+
# First, clean up sockets
|
|
1079
|
+
neo_ws = getattr(self.client, "NeoWebSocket", None)
|
|
1080
|
+
if neo_ws:
|
|
1081
|
+
if getattr(neo_ws, "hsWebsocket", None):
|
|
1082
|
+
try:
|
|
1083
|
+
neo_ws.hsWebsocket.close()
|
|
1084
|
+
except Exception:
|
|
1085
|
+
pass
|
|
1086
|
+
if getattr(neo_ws, "hsiWebsocket", None):
|
|
1087
|
+
try:
|
|
1088
|
+
neo_ws.hsiWebsocket.close()
|
|
1089
|
+
except Exception:
|
|
1090
|
+
pass
|
|
1091
|
+
neo_ws.is_hsw_open = 0
|
|
1092
|
+
neo_ws.is_hsi_open = 0
|
|
1093
|
+
|
|
1094
|
+
# Re-register open/message/error/close hooks on client in case they got cleared
|
|
1095
|
+
self.client.on_open = self._on_open
|
|
1096
|
+
self.client.on_message = self._on_message
|
|
1097
|
+
self.client.on_error = self._on_error
|
|
1098
|
+
self.client.on_close = self._on_close
|
|
1099
|
+
|
|
1100
|
+
# Reconnect by subscribing to orderfeed
|
|
1101
|
+
self.client.subscribe_to_orderfeed()
|
|
1102
|
+
except Exception as e:
|
|
1103
|
+
logger.error("Kotak Neo WS: Reconnection attempt failed: %s", e)
|
|
1104
|
+
self._trigger_reconnect()
|
|
932
1105
|
|
|
933
1106
|
def _on_message(self, message: Any) -> None:
|
|
934
1107
|
if not isinstance(message, dict):
|
|
@@ -1009,6 +1182,9 @@ class KotakTicker:
|
|
|
1009
1182
|
"status_message": str(data.get("rejRsn", data.get("status_message", ""))),
|
|
1010
1183
|
}
|
|
1011
1184
|
|
|
1185
|
+
if not norm_data["order_id"] and not norm_data["tradingsymbol"]:
|
|
1186
|
+
return
|
|
1187
|
+
|
|
1012
1188
|
if self.on_order_update:
|
|
1013
1189
|
self.on_order_update(self, norm_data)
|
|
1014
1190
|
|