kitecli 0.2.0__tar.gz → 0.2.2__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.
Files changed (31) hide show
  1. {kitecli-0.2.0 → kitecli-0.2.2}/PKG-INFO +1 -1
  2. {kitecli-0.2.0 → kitecli-0.2.2}/cli/display.py +0 -6
  3. {kitecli-0.2.0 → kitecli-0.2.2}/cli/executor.py +1 -1
  4. {kitecli-0.2.0 → kitecli-0.2.2}/cli/kite_manager.py +28 -65
  5. {kitecli-0.2.0 → kitecli-0.2.2}/cli/kotak_manager.py +284 -111
  6. {kitecli-0.2.0 → kitecli-0.2.2}/cli/live_session.py +339 -142
  7. {kitecli-0.2.0 → kitecli-0.2.2}/cli/telegram_bot.py +63 -25
  8. {kitecli-0.2.0 → kitecli-0.2.2}/kitecli.egg-info/PKG-INFO +1 -1
  9. {kitecli-0.2.0 → kitecli-0.2.2}/pyproject.toml +1 -1
  10. {kitecli-0.2.0 → kitecli-0.2.2}/tests/test_multi_broker.py +67 -0
  11. {kitecli-0.2.0 → kitecli-0.2.2}/tests/test_telegram.py +62 -0
  12. kitecli-0.2.2/tests/test_ui.py +120 -0
  13. kitecli-0.2.0/tests/test_ui.py +0 -74
  14. {kitecli-0.2.0 → kitecli-0.2.2}/README.md +0 -0
  15. {kitecli-0.2.0 → kitecli-0.2.2}/cli/__init__.py +0 -0
  16. {kitecli-0.2.0 → kitecli-0.2.2}/cli/advisor.py +0 -0
  17. {kitecli-0.2.0 → kitecli-0.2.2}/cli/api_client.py +0 -0
  18. {kitecli-0.2.0 → kitecli-0.2.2}/cli/base_manager.py +0 -0
  19. {kitecli-0.2.0 → kitecli-0.2.2}/cli/config.py +0 -0
  20. {kitecli-0.2.0 → kitecli-0.2.2}/cli/main.py +0 -0
  21. {kitecli-0.2.0 → kitecli-0.2.2}/cli/nli.py +0 -0
  22. {kitecli-0.2.0 → kitecli-0.2.2}/cli/parser.py +0 -0
  23. {kitecli-0.2.0 → kitecli-0.2.2}/cli/recorder.py +0 -0
  24. {kitecli-0.2.0 → kitecli-0.2.2}/kitecli.egg-info/SOURCES.txt +0 -0
  25. {kitecli-0.2.0 → kitecli-0.2.2}/kitecli.egg-info/dependency_links.txt +0 -0
  26. {kitecli-0.2.0 → kitecli-0.2.2}/kitecli.egg-info/entry_points.txt +0 -0
  27. {kitecli-0.2.0 → kitecli-0.2.2}/kitecli.egg-info/requires.txt +0 -0
  28. {kitecli-0.2.0 → kitecli-0.2.2}/kitecli.egg-info/top_level.txt +0 -0
  29. {kitecli-0.2.0 → kitecli-0.2.2}/setup.cfg +0 -0
  30. {kitecli-0.2.0 → kitecli-0.2.2}/tests/test_nli.py +0 -0
  31. {kitecli-0.2.0 → kitecli-0.2.2}/tests/test_parser.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kitecli
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: KiteCLI — Multi-account, multi-broker trading positions viewer (Zerodha + Kotak Neo)
5
5
  Author: KiteCLI Team
6
6
  License: MIT
@@ -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
 
@@ -201,7 +201,7 @@ async def execute_command(cmd: Any, context: ExecutionContext) -> dict:
201
201
 
202
202
  res = context.client.exit_positions(
203
203
  api_keys=api_keys,
204
- symbol=symbol,
204
+ tradingsymbol=symbol,
205
205
  price=cmd.price
206
206
  )
207
207
 
@@ -97,6 +97,14 @@ class KiteAccountManager(BaseBrokerManager):
97
97
  """Core account initialisation (used both directly and via ABC)."""
98
98
  proxies = {"http": proxy, "https": proxy} if proxy else None
99
99
  kite = KiteConnect(api_key=api_key, proxies=proxies)
100
+
101
+ # Increase connection pool size and timeouts to support slow proxies
102
+ from requests.adapters import HTTPAdapter
103
+ adapter = HTTPAdapter(pool_connections=100, pool_maxsize=100)
104
+ kite.reqsession.mount("https://", adapter)
105
+ kite.reqsession.mount("http://", adapter)
106
+ kite.timeout = 25
107
+
100
108
  self._clients[api_key] = kite
101
109
  self._api_secrets[api_key] = api_secret
102
110
  self._account_names[api_key] = name or api_key
@@ -106,18 +114,9 @@ class KiteAccountManager(BaseBrokerManager):
106
114
  sessions = _load_sessions()
107
115
  saved_token = sessions.get(api_key)
108
116
  if saved_token:
109
- logger.info("Found saved session token for '%s' (api_key=%s…). Verifying...", name, api_key[:8])
110
- try:
111
- kite.set_access_token(saved_token)
112
- kite.profile()
113
- self._authenticated[api_key] = True
114
- logger.info("Successfully restored valid session for '%s' (api_key=%s…)", name, api_key[:8])
115
- except Exception as exc:
116
- logger.info(
117
- "Saved session token for '%s' (api_key=%s…) is invalid or expired: %s",
118
- name, api_key[:8], exc,
119
- )
120
- self._authenticated[api_key] = False
117
+ logger.debug("Found saved session token for '%s' (api_key=%s…). Assuming valid.", name, api_key[:8])
118
+ kite.set_access_token(saved_token)
119
+ self._authenticated[api_key] = True
121
120
 
122
121
  login_url = f"https://kite.zerodha.com/connect/login?v=3&api_key={api_key}"
123
122
  logger.info("Initialized account '%s' (api_key=%s…)", name, api_key[:8])
@@ -374,6 +373,11 @@ class KiteAccountManager(BaseBrokerManager):
374
373
  # Build a requests.Session and apply the per-account proxy so that
375
374
  # login and 2FA calls also flow through the proxy (not just KiteConnect SDK calls).
376
375
  session = http_requests.Session()
376
+ from requests.adapters import HTTPAdapter
377
+ adapter = HTTPAdapter(pool_connections=20, pool_maxsize=20)
378
+ session.mount("https://", adapter)
379
+ session.mount("http://", adapter)
380
+
377
381
  account_proxies = self._proxies.get(api_key, {})
378
382
  if account_proxies:
379
383
  session.proxies.update(account_proxies)
@@ -384,6 +388,7 @@ class KiteAccountManager(BaseBrokerManager):
384
388
  login_resp = session.post(
385
389
  "https://kite.zerodha.com/api/login",
386
390
  data={"user_id": user_id, "password": password},
391
+ timeout=25,
387
392
  )
388
393
  login_data = login_resp.json()
389
394
 
@@ -411,6 +416,7 @@ class KiteAccountManager(BaseBrokerManager):
411
416
  "user_id": user_id,
412
417
  "twofa_type": "totp",
413
418
  },
419
+ timeout=25,
414
420
  )
415
421
  twofa_data = twofa_resp.json()
416
422
 
@@ -434,19 +440,8 @@ class KiteAccountManager(BaseBrokerManager):
434
440
 
435
441
  for hop in range(1, 11):
436
442
  logger.info("Auto-login hop %d: %s", hop, current_url)
437
- try:
438
- resp = session.get(current_url, allow_redirects=False)
439
- except http_requests.exceptions.RequestException as exc:
440
- logger.warning("Network error/unreachable during redirect hop %d: %s", hop, exc)
441
- # Check if the url itself contained the token before network error
442
- parsed = urlparse(current_url)
443
- query_params = parse_qs(parsed.query)
444
- token = query_params.get("request_token", [None])[0]
445
- if token:
446
- request_token = token
447
- break
448
443
 
449
- # Check if current_url already contains the request_token
444
+ # Check if current_url already contains the request_token BEFORE requesting it
450
445
  parsed = urlparse(current_url)
451
446
  query_params = parse_qs(parsed.query)
452
447
  token = query_params.get("request_token", [None])[0]
@@ -454,6 +449,12 @@ class KiteAccountManager(BaseBrokerManager):
454
449
  request_token = token
455
450
  break
456
451
 
452
+ try:
453
+ resp = session.get(current_url, allow_redirects=False, timeout=25)
454
+ except http_requests.exceptions.RequestException as exc:
455
+ logger.warning("Network error/unreachable during redirect hop %d: %s", hop, exc)
456
+ break
457
+
457
458
  location = resp.headers.get("Location")
458
459
  if not location:
459
460
  break
@@ -465,21 +466,14 @@ class KiteAccountManager(BaseBrokerManager):
465
466
 
466
467
  current_url = location
467
468
 
468
- # Check if target redirect URL contains the request_token
469
- parsed = urlparse(current_url)
470
- query_params = parse_qs(parsed.query)
471
- token = query_params.get("request_token", [None])[0]
472
- if token:
473
- request_token = token
474
- break
475
-
476
469
  # If target URL is not a Zerodha domain (e.g. localhost or client app domain),
477
470
  # do not actually request it (it may not be reachable or could trigger external side-effects),
478
471
  # and we've already checked if it has the request_token.
479
472
  parsed_loc = urlparse(current_url)
480
473
  if parsed_loc.netloc and not parsed_loc.netloc.endswith(".zerodha.com") and "zerodha.com" not in parsed_loc.netloc:
481
474
  logger.info("Reached non-Zerodha redirect target: %s", current_url)
482
- break
475
+ # We will extract the token at the start of the next iteration
476
+ pass
483
477
 
484
478
  if not request_token:
485
479
  logger.error("Could not extract request_token from redirect chain for %s", user_id)
@@ -1064,38 +1058,7 @@ class KiteAccountManager(BaseBrokerManager):
1064
1058
  except Exception as exc:
1065
1059
  logger.warning("Kite indices fetch failed for api_key=%s…: %s", api_key[:8], exc)
1066
1060
 
1067
- # 2. Fallback to Yahoo Finance chart API (in parallel)
1068
- try:
1069
- logger.info("Fetching indices from Yahoo Finance fallback...")
1070
- import requests as http_requests
1071
- headers = {"User-Agent": "Mozilla/5.0"}
1072
- from concurrent.futures import ThreadPoolExecutor
1073
-
1074
- symbols = {
1075
- "nifty": "^NSEI",
1076
- "sensex": "^BSESN",
1077
- "vix": "^INDIAVIX",
1078
- }
1079
-
1080
- def fetch_symbol_price(item):
1081
- name, code = item
1082
- url = f"https://query1.finance.yahoo.com/v8/finance/chart/{code}?interval=1m&range=1d"
1083
- resp = http_requests.get(url, headers=headers, timeout=5)
1084
- price = resp.json()["chart"]["result"][0]["meta"]["regularMarketPrice"]
1085
- return name, float(price)
1086
-
1087
- with ThreadPoolExecutor(max_workers=3) as executor:
1088
- results = dict(executor.map(fetch_symbol_price, symbols.items()))
1089
-
1090
- return {
1091
- "status": "success",
1092
- "nifty": results["nifty"],
1093
- "sensex": results["sensex"],
1094
- "vix": results["vix"],
1095
- }
1096
- except Exception as exc:
1097
- logger.error("Yahoo Finance fallback failed: %s", exc)
1098
- return {"status": "error", "message": f"Kite call had insufficient permission, and Yahoo fallback failed: {exc}"}
1061
+ return {"status": "error", "message": "All authenticated Zerodha account indices fetches failed."}
1099
1062
 
1100
1063
  def get_ltp_and_tokens(self, api_key: str, symbols: list[str]) -> dict:
1101
1064
  """Fetch LTP and instrument tokens for the given symbols using Zerodha ltp()."""