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.
- {kitecli-0.2.0 → kitecli-0.2.2}/PKG-INFO +1 -1
- {kitecli-0.2.0 → kitecli-0.2.2}/cli/display.py +0 -6
- {kitecli-0.2.0 → kitecli-0.2.2}/cli/executor.py +1 -1
- {kitecli-0.2.0 → kitecli-0.2.2}/cli/kite_manager.py +28 -65
- {kitecli-0.2.0 → kitecli-0.2.2}/cli/kotak_manager.py +284 -111
- {kitecli-0.2.0 → kitecli-0.2.2}/cli/live_session.py +339 -142
- {kitecli-0.2.0 → kitecli-0.2.2}/cli/telegram_bot.py +63 -25
- {kitecli-0.2.0 → kitecli-0.2.2}/kitecli.egg-info/PKG-INFO +1 -1
- {kitecli-0.2.0 → kitecli-0.2.2}/pyproject.toml +1 -1
- {kitecli-0.2.0 → kitecli-0.2.2}/tests/test_multi_broker.py +67 -0
- {kitecli-0.2.0 → kitecli-0.2.2}/tests/test_telegram.py +62 -0
- kitecli-0.2.2/tests/test_ui.py +120 -0
- kitecli-0.2.0/tests/test_ui.py +0 -74
- {kitecli-0.2.0 → kitecli-0.2.2}/README.md +0 -0
- {kitecli-0.2.0 → kitecli-0.2.2}/cli/__init__.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.2}/cli/advisor.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.2}/cli/api_client.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.2}/cli/base_manager.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.2}/cli/config.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.2}/cli/main.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.2}/cli/nli.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.2}/cli/parser.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.2}/cli/recorder.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.2}/kitecli.egg-info/SOURCES.txt +0 -0
- {kitecli-0.2.0 → kitecli-0.2.2}/kitecli.egg-info/dependency_links.txt +0 -0
- {kitecli-0.2.0 → kitecli-0.2.2}/kitecli.egg-info/entry_points.txt +0 -0
- {kitecli-0.2.0 → kitecli-0.2.2}/kitecli.egg-info/requires.txt +0 -0
- {kitecli-0.2.0 → kitecli-0.2.2}/kitecli.egg-info/top_level.txt +0 -0
- {kitecli-0.2.0 → kitecli-0.2.2}/setup.cfg +0 -0
- {kitecli-0.2.0 → kitecli-0.2.2}/tests/test_nli.py +0 -0
- {kitecli-0.2.0 → kitecli-0.2.2}/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
|
|
|
@@ -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.
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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()."""
|