kitecli 0.2.6__tar.gz → 0.2.9__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.6 → kitecli-0.2.9}/PKG-INFO +1 -1
- {kitecli-0.2.6 → kitecli-0.2.9}/cli/kite_manager.py +23 -9
- {kitecli-0.2.6 → kitecli-0.2.9}/cli/kotak_manager.py +51 -27
- {kitecli-0.2.6 → kitecli-0.2.9}/cli/live_session.py +110 -20
- {kitecli-0.2.6 → kitecli-0.2.9}/kitecli.egg-info/PKG-INFO +1 -1
- {kitecli-0.2.6 → kitecli-0.2.9}/pyproject.toml +1 -1
- {kitecli-0.2.6 → kitecli-0.2.9}/README.md +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/cli/__init__.py +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/cli/advisor.py +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/cli/api_client.py +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/cli/base_manager.py +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/cli/config.py +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/cli/display.py +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/cli/executor.py +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/cli/main.py +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/cli/nli.py +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/cli/parser.py +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/cli/recorder.py +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/cli/telegram_bot.py +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/kitecli.egg-info/SOURCES.txt +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/kitecli.egg-info/dependency_links.txt +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/kitecli.egg-info/entry_points.txt +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/kitecli.egg-info/requires.txt +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/kitecli.egg-info/top_level.txt +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/setup.cfg +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/tests/test_multi_broker.py +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/tests/test_nli.py +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/tests/test_parser.py +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/tests/test_telegram.py +0 -0
- {kitecli-0.2.6 → kitecli-0.2.9}/tests/test_ui.py +0 -0
|
@@ -1039,22 +1039,36 @@ class KiteAccountManager(BaseBrokerManager):
|
|
|
1039
1039
|
return {}
|
|
1040
1040
|
|
|
1041
1041
|
def get_market_indices(self) -> dict[str, Any]:
|
|
1042
|
-
# 1. Try Kite first
|
|
1042
|
+
# 1. Try Kite ohlc first for last_price and yesterday's close
|
|
1043
1043
|
for api_key in self.get_all_api_keys():
|
|
1044
1044
|
if self.is_authenticated(api_key):
|
|
1045
1045
|
kite = self._clients.get(api_key)
|
|
1046
1046
|
if kite:
|
|
1047
1047
|
try:
|
|
1048
|
-
data = kite.
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1048
|
+
data = kite.ohlc(["NSE:NIFTY 50", "BSE:SENSEX", "NSE:INDIA VIX"])
|
|
1049
|
+
|
|
1050
|
+
def parse_item(item_data):
|
|
1051
|
+
if not isinstance(item_data, dict):
|
|
1052
|
+
return None, None
|
|
1053
|
+
last = item_data.get("last_price")
|
|
1054
|
+
ohlc = item_data.get("ohlc", {})
|
|
1055
|
+
close = ohlc.get("close") if isinstance(ohlc, dict) else None
|
|
1056
|
+
change = (last - close) if (last is not None and close is not None) else None
|
|
1057
|
+
return last, change
|
|
1058
|
+
|
|
1059
|
+
nifty_last, nifty_change = parse_item(data.get("NSE:NIFTY 50"))
|
|
1060
|
+
sensex_last, sensex_change = parse_item(data.get("BSE:SENSEX"))
|
|
1061
|
+
vix_last, vix_change = parse_item(data.get("NSE:INDIA VIX"))
|
|
1062
|
+
|
|
1063
|
+
if nifty_last or sensex_last or vix_last:
|
|
1053
1064
|
return {
|
|
1054
1065
|
"status": "success",
|
|
1055
|
-
"nifty":
|
|
1056
|
-
"
|
|
1057
|
-
"
|
|
1066
|
+
"nifty": nifty_last,
|
|
1067
|
+
"nifty_change": nifty_change,
|
|
1068
|
+
"sensex": sensex_last,
|
|
1069
|
+
"sensex_change": sensex_change,
|
|
1070
|
+
"vix": vix_last,
|
|
1071
|
+
"vix_change": vix_change,
|
|
1058
1072
|
}
|
|
1059
1073
|
except Exception as exc:
|
|
1060
1074
|
logger.warning("Kite indices fetch failed for api_key=%s…: %s", api_key[:8], exc)
|
|
@@ -228,40 +228,46 @@ class KotakAccountManager(BaseBrokerManager):
|
|
|
228
228
|
if 'Content-Type' not in headers:
|
|
229
229
|
headers['Content-Type'] = 'application/json'
|
|
230
230
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
u_with_params += '?' + _urlencode(query_params)
|
|
231
|
+
def _do_req(h, max_proxy_retries: int = 2):
|
|
232
|
+
if query_params and isinstance(query_params, dict):
|
|
233
|
+
base_u = url.split('?')[0]
|
|
234
|
+
u_curr = base_u + '?' + _urlencode(query_params)
|
|
236
235
|
else:
|
|
237
|
-
|
|
238
|
-
else:
|
|
239
|
-
u_with_params = u
|
|
236
|
+
u_curr = url
|
|
240
237
|
|
|
241
|
-
def _do_req(h):
|
|
242
238
|
req_kwargs = {}
|
|
243
239
|
if _proxies:
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
240
|
+
url_lower = u_curr.lower()
|
|
241
|
+
is_order_api = "/order/" in url_lower and any(x in url_lower for x in ["/place", "/modify", "/cancel"])
|
|
242
|
+
is_login_api = "/login/" in url_lower or "tradeapilogin" in url_lower or "tradeapivalidate" in url_lower
|
|
243
|
+
if is_order_api or is_login_api:
|
|
247
244
|
req_kwargs["proxies"] = _proxies
|
|
248
245
|
|
|
249
|
-
logger.debug("Kotak REST [%s] requesting: %s", method,
|
|
246
|
+
logger.debug("Kotak REST [%s] requesting: %s", method, u_curr)
|
|
250
247
|
logger.debug("Kotak REST headers: %s", {k: v[:15] + '...' if len(str(v)) > 15 else v for k, v in h.items()})
|
|
251
248
|
|
|
252
|
-
|
|
253
|
-
if
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
249
|
+
try:
|
|
250
|
+
if method in ['POST', 'PUT', 'PATCH', 'DELETE']:
|
|
251
|
+
if _re.search('json', h.get('Content-Type', ''), _re.IGNORECASE):
|
|
252
|
+
request_body = _json.dumps(body) if body is not None else None
|
|
253
|
+
return _req.post(url=u_curr, headers=h, data=request_body, **req_kwargs)
|
|
254
|
+
elif _re.search('x-www-form-urlencoded', h.get('Content-Type', ''), _re.IGNORECASE):
|
|
255
|
+
request_body = {"jData": _json.dumps(body)} if body is not None else {}
|
|
256
|
+
return _req.post(url=u_curr, headers=h, data=request_body, **req_kwargs)
|
|
257
|
+
elif method == 'GET':
|
|
258
|
+
return _req.get(url=u_curr, headers=h, **req_kwargs)
|
|
259
|
+
return _orig_request(method, url, query_params=query_params, headers=h, body=body)
|
|
260
|
+
except Exception as req_exc:
|
|
261
|
+
exc_str = str(req_exc).lower()
|
|
262
|
+
if max_proxy_retries > 0 and _proxies and ("proxy" in exc_str or "502" in exc_str or "tunnel" in exc_str or "connect" in exc_str):
|
|
263
|
+
import time
|
|
264
|
+
logger.warning("Kotak static proxy transient error (%s). Retrying via proxy in 1s...", req_exc)
|
|
265
|
+
time.sleep(1.0)
|
|
266
|
+
return _do_req(h, max_proxy_retries=max_proxy_retries - 1)
|
|
267
|
+
raise
|
|
262
268
|
|
|
263
269
|
resp = _do_req(headers)
|
|
264
|
-
logger.info("Kotak REST %s %s → status %s", method,
|
|
270
|
+
logger.info("Kotak REST %s %s → status %s", method, url, resp.status_code)
|
|
265
271
|
try:
|
|
266
272
|
logger.debug("Kotak REST response body: %s", resp.json())
|
|
267
273
|
except Exception:
|
|
@@ -284,16 +290,20 @@ class KotakAccountManager(BaseBrokerManager):
|
|
|
284
290
|
logger.info("Kotak session invalid (stCode=%s, msg='%s') for account '%s'. Triggering auto-login...", st_code, resp_json.get("errMsg"), name)
|
|
285
291
|
if self.auto_login(consumer_key):
|
|
286
292
|
# Update headers with new credentials
|
|
293
|
+
new_sid = client.configuration.serverId or client.configuration.edit_sid
|
|
287
294
|
headers["Sid"] = client.configuration.edit_sid
|
|
288
295
|
headers["Auth"] = client.configuration.edit_token
|
|
296
|
+
if new_sid:
|
|
297
|
+
url = _re.sub(r'([?&]sId=)[^&]*', r'\g<1>' + str(new_sid), url)
|
|
298
|
+
url = _re.sub(r'([?&]sid=)[^&]*', r'\g<1>' + str(new_sid), url)
|
|
289
299
|
if isinstance(query_params, dict):
|
|
290
300
|
if "sId" in query_params:
|
|
291
|
-
query_params["sId"] =
|
|
301
|
+
query_params["sId"] = new_sid
|
|
292
302
|
if "sid" in query_params:
|
|
293
|
-
query_params["sid"] =
|
|
303
|
+
query_params["sid"] = new_sid
|
|
294
304
|
logger.info("Retrying request after successful auto-login...")
|
|
295
305
|
resp = _do_req(headers)
|
|
296
|
-
logger.info("Kotak REST retry %s %s → status %s", method,
|
|
306
|
+
logger.info("Kotak REST retry %s %s → status %s", method, url, resp.status_code)
|
|
297
307
|
try:
|
|
298
308
|
logger.debug("Kotak REST retry response body: %s", resp.json())
|
|
299
309
|
except Exception:
|
|
@@ -628,6 +638,18 @@ class KotakAccountManager(BaseBrokerManager):
|
|
|
628
638
|
except ValueError:
|
|
629
639
|
pending_quantity = 0
|
|
630
640
|
|
|
641
|
+
fld_qty_str = str(ord.get("fldQty", ord.get("filled_quantity", "0")))
|
|
642
|
+
try:
|
|
643
|
+
filled_quantity = int(fld_qty_str)
|
|
644
|
+
except ValueError:
|
|
645
|
+
filled_quantity = 0
|
|
646
|
+
|
|
647
|
+
avg_prc_str = str(ord.get("avgPrc", ord.get("average_price", "0")))
|
|
648
|
+
try:
|
|
649
|
+
average_price = float(avg_prc_str)
|
|
650
|
+
except ValueError:
|
|
651
|
+
average_price = 0.0
|
|
652
|
+
|
|
631
653
|
price_str = str(ord.get("prc", ord.get("price", "0")))
|
|
632
654
|
try:
|
|
633
655
|
price = float(price_str)
|
|
@@ -639,8 +661,10 @@ class KotakAccountManager(BaseBrokerManager):
|
|
|
639
661
|
"tradingsymbol": ord.get("trdSym", ord.get("tradingsymbol", "")),
|
|
640
662
|
"transaction_type": str(ord.get("trnsTp", ord.get("transaction_type", ""))).upper(),
|
|
641
663
|
"quantity": quantity,
|
|
664
|
+
"filled_quantity": filled_quantity,
|
|
642
665
|
"pending_quantity": pending_quantity,
|
|
643
666
|
"price": price,
|
|
667
|
+
"average_price": average_price,
|
|
644
668
|
"order_type": str(ord.get("prcTp", ord.get("order_type", "MARKET"))).upper(),
|
|
645
669
|
"status": status,
|
|
646
670
|
"product": str(ord.get("prod", ord.get("product", "NRML"))).upper(),
|
|
@@ -312,6 +312,7 @@ class KCLILiveSession:
|
|
|
312
312
|
# NSE:NIFTY 50 -> 256265, BSE:SENSEX -> 265, NSE:INDIA VIX -> 264969
|
|
313
313
|
self.index_tokens = {256265: "nifty", 265: "sensex", 264969: "vix"}
|
|
314
314
|
self.index_values = {"nifty": None, "sensex": None, "vix": None}
|
|
315
|
+
self.index_close_prices = {}
|
|
315
316
|
|
|
316
317
|
# Designated primary streaming account. Index and option-chain ticks are
|
|
317
318
|
# subscribed only on this account's ticker (chosen in
|
|
@@ -1329,7 +1330,9 @@ class KCLILiveSession:
|
|
|
1329
1330
|
code == 403
|
|
1330
1331
|
or "403" in reason_str
|
|
1331
1332
|
or "auth_failed" in reason_str
|
|
1332
|
-
or "token" in reason_str
|
|
1333
|
+
or "invalid token" in reason_str
|
|
1334
|
+
or "token expired" in reason_str
|
|
1335
|
+
or "unauthorized" in reason_str
|
|
1333
1336
|
)
|
|
1334
1337
|
if is_auth_failure:
|
|
1335
1338
|
self.log_message(
|
|
@@ -1378,9 +1381,22 @@ class KCLILiveSession:
|
|
|
1378
1381
|
if not token or ltp is None:
|
|
1379
1382
|
continue
|
|
1380
1383
|
|
|
1381
|
-
# Market index tick → update the header values.
|
|
1384
|
+
# Market index tick → update the header values & streaming change.
|
|
1382
1385
|
if token in self.index_tokens:
|
|
1383
|
-
self.
|
|
1386
|
+
key = self.index_tokens[token]
|
|
1387
|
+
self.index_values[key] = ltp
|
|
1388
|
+
|
|
1389
|
+
# Extract close from tick OHLC if available, or use cached REST close price
|
|
1390
|
+
tick_close = None
|
|
1391
|
+
if isinstance(tick.get("ohlc"), dict):
|
|
1392
|
+
tick_close = tick["ohlc"].get("close")
|
|
1393
|
+
if tick_close:
|
|
1394
|
+
self.index_close_prices[key] = tick_close
|
|
1395
|
+
|
|
1396
|
+
close_val = self.index_close_prices.get(key)
|
|
1397
|
+
if close_val is not None:
|
|
1398
|
+
self.index_values[f"{key}_change"] = ltp - close_val
|
|
1399
|
+
|
|
1384
1400
|
indices_updated = True
|
|
1385
1401
|
continue
|
|
1386
1402
|
|
|
@@ -1458,18 +1474,82 @@ class KCLILiveSession:
|
|
|
1458
1474
|
self.app.invalidate()
|
|
1459
1475
|
|
|
1460
1476
|
def _render_indices_html(self) -> HTML:
|
|
1461
|
-
"""Build
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
)
|
|
1477
|
+
"""Build formatted market-index header tailored to the Right Pane width."""
|
|
1478
|
+
total_cols = 100
|
|
1479
|
+
if hasattr(self, "app") and self.app and hasattr(self.app, "output") and self.app.output:
|
|
1480
|
+
try:
|
|
1481
|
+
cols = self.app.output.get_size().columns
|
|
1482
|
+
if isinstance(cols, int):
|
|
1483
|
+
total_cols = cols
|
|
1484
|
+
except Exception:
|
|
1485
|
+
pass
|
|
1486
|
+
|
|
1487
|
+
# The indices bar is rendered inside the Right Pane, so compute right pane width
|
|
1488
|
+
lw = getattr(self, "left_pane_weight", 50)
|
|
1489
|
+
rw = getattr(self, "right_pane_weight", 50)
|
|
1490
|
+
pane_cols = int(total_cols * (rw / max(1, lw + rw)))
|
|
1491
|
+
|
|
1492
|
+
def fmt_item(val, chg, is_vix: bool = False, compact: bool = False):
|
|
1493
|
+
if val is None:
|
|
1494
|
+
return "<style fg='#ffffff'>N/A</style>"
|
|
1495
|
+
val_str = f"{val:,.2f}"
|
|
1496
|
+
if chg is None:
|
|
1497
|
+
return f"<style fg='#ffffff'>{val_str}</style>"
|
|
1498
|
+
|
|
1499
|
+
# For VIX: negative change (volatility drop) is green, positive (volatility spike) is red
|
|
1500
|
+
# For NIFTY/SENSEX: positive is green, negative is red
|
|
1501
|
+
if is_vix:
|
|
1502
|
+
color = "#00ff00" if chg <= 0 else "#ff5f5f"
|
|
1503
|
+
else:
|
|
1504
|
+
color = "#00ff00" if chg >= 0 else "#ff5f5f"
|
|
1505
|
+
|
|
1506
|
+
sign = "+" if chg >= 0 else ""
|
|
1507
|
+
if compact:
|
|
1508
|
+
# In tight right-pane spaces, round large changes to integers e.g. (+80) instead of (+80.50)
|
|
1509
|
+
chg_str = f"{sign}{int(round(chg)):,d}" if not is_vix else f"{sign}{chg:.2f}"
|
|
1510
|
+
else:
|
|
1511
|
+
chg_str = f"{sign}{chg:,.2f}"
|
|
1512
|
+
return f"<style fg='{color}'>{val_str} ({chg_str})</style>"
|
|
1513
|
+
|
|
1514
|
+
if pane_cols >= 75:
|
|
1515
|
+
# Full width: NIFTY 50: 24,234.85 (-99.80) │ SENSEX: 77,708.24 (-441.41) │ IV: 13.45 (+0.25)
|
|
1516
|
+
nifty_html = fmt_item(self.index_values.get("nifty"), self.index_values.get("nifty_change"))
|
|
1517
|
+
sensex_html = fmt_item(self.index_values.get("sensex"), self.index_values.get("sensex_change"))
|
|
1518
|
+
vix_html = fmt_item(self.index_values.get("vix"), self.index_values.get("vix_change"), is_vix=True)
|
|
1519
|
+
return HTML(
|
|
1520
|
+
f" <ansicyan><b>NIFTY 50:</b></ansicyan> {nifty_html} "
|
|
1521
|
+
f"│ <ansiyellow><b>SENSEX:</b></ansiyellow> {sensex_html} "
|
|
1522
|
+
f"│ <ansired><b>IV:</b></ansired> {vix_html}"
|
|
1523
|
+
)
|
|
1524
|
+
elif pane_cols >= 55:
|
|
1525
|
+
# Medium width: NIFTY: 24,234.85 (-100) │ SENSEX: 77,708.24 (-441) │ IV: 13.45 (+0.25)
|
|
1526
|
+
nifty_html = fmt_item(self.index_values.get("nifty"), self.index_values.get("nifty_change"), compact=True)
|
|
1527
|
+
sensex_html = fmt_item(self.index_values.get("sensex"), self.index_values.get("sensex_change"), compact=True)
|
|
1528
|
+
vix_html = fmt_item(self.index_values.get("vix"), self.index_values.get("vix_change"), is_vix=True, compact=True)
|
|
1529
|
+
return HTML(
|
|
1530
|
+
f"<ansicyan><b>NIFTY:</b></ansicyan> {nifty_html} "
|
|
1531
|
+
f"│ <ansiyellow><b>SENSEX:</b></ansiyellow> {sensex_html} "
|
|
1532
|
+
f"│ <ansired><b>IV:</b></ansired> {vix_html}"
|
|
1533
|
+
)
|
|
1534
|
+
else:
|
|
1535
|
+
# Ultra compact for narrow right pane (< 55 cols): N: 24,234.85 │ S: 77,708.24 │ IV: 13.45
|
|
1536
|
+
def fmt_val_only(val, chg, is_vix=False):
|
|
1537
|
+
if val is None:
|
|
1538
|
+
return "<style fg='#ffffff'>N/A</style>"
|
|
1539
|
+
val_str = f"{val:,.2f}"
|
|
1540
|
+
if chg is None:
|
|
1541
|
+
return f"<style fg='#ffffff'>{val_str}</style>"
|
|
1542
|
+
color = ("#00ff00" if chg <= 0 else "#ff5f5f") if is_vix else ("#00ff00" if chg >= 0 else "#ff5f5f")
|
|
1543
|
+
return f"<style fg='{color}'>{val_str}</style>"
|
|
1544
|
+
|
|
1545
|
+
nifty_html = fmt_val_only(self.index_values.get("nifty"), self.index_values.get("nifty_change"))
|
|
1546
|
+
sensex_html = fmt_val_only(self.index_values.get("sensex"), self.index_values.get("sensex_change"))
|
|
1547
|
+
vix_html = fmt_val_only(self.index_values.get("vix"), self.index_values.get("vix_change"), is_vix=True)
|
|
1548
|
+
return HTML(
|
|
1549
|
+
f"<ansicyan><b>N:</b></ansicyan> {nifty_html} "
|
|
1550
|
+
f"│ <ansiyellow><b>S:</b></ansiyellow> {sensex_html} "
|
|
1551
|
+
f"│ <ansired><b>IV:</b></ansired> {vix_html}"
|
|
1552
|
+
)
|
|
1473
1553
|
|
|
1474
1554
|
def _get_active_ticker(self):
|
|
1475
1555
|
"""Get the primary ticker if it is connected, else fallback to any connected ticker."""
|
|
@@ -1730,9 +1810,20 @@ class KCLILiveSession:
|
|
|
1730
1810
|
if self._ws_should_log("rest_err:indices", min_interval=30.0):
|
|
1731
1811
|
self.log_message(f"[#ff8700]Indices fetch failed:[/#] {self._clean_error(str(indices_resp))}")
|
|
1732
1812
|
elif isinstance(indices_resp, dict) and indices_resp.get("status") == "success":
|
|
1733
|
-
self.index_values["nifty"]
|
|
1734
|
-
self.index_values["
|
|
1735
|
-
self.index_values["
|
|
1813
|
+
self.index_values["nifty"] = indices_resp.get("nifty")
|
|
1814
|
+
self.index_values["nifty_change"] = indices_resp.get("nifty_change")
|
|
1815
|
+
self.index_values["sensex"] = indices_resp.get("sensex")
|
|
1816
|
+
self.index_values["sensex_change"] = indices_resp.get("sensex_change")
|
|
1817
|
+
self.index_values["vix"] = indices_resp.get("vix")
|
|
1818
|
+
self.index_values["vix_change"] = indices_resp.get("vix_change")
|
|
1819
|
+
|
|
1820
|
+
# Cache close prices (last - change) so live WebSocket ticks can dynamically recalculate change
|
|
1821
|
+
for key in ["nifty", "sensex", "vix"]:
|
|
1822
|
+
last_val = self.index_values.get(key)
|
|
1823
|
+
chg_val = self.index_values.get(f"{key}_change")
|
|
1824
|
+
if last_val is not None and chg_val is not None:
|
|
1825
|
+
self.index_close_prices[key] = last_val - chg_val
|
|
1826
|
+
|
|
1736
1827
|
self.market_indices_control.text = self._render_indices_html()
|
|
1737
1828
|
elif isinstance(indices_resp, dict):
|
|
1738
1829
|
errors_this_cycle += 1
|
|
@@ -3892,7 +3983,7 @@ class KCLILiveSession:
|
|
|
3892
3983
|
Ctrl + E -> Executed Orders
|
|
3893
3984
|
Ctrl + O -> Option Chain
|
|
3894
3985
|
Ctrl + G -> AI Advisor
|
|
3895
|
-
|
|
3986
|
+
F5 / Esc+5 -> Help
|
|
3896
3987
|
|
|
3897
3988
|
Function Keys / Alt:
|
|
3898
3989
|
F1 / F2 / F3 / F4 / F5
|
|
@@ -4359,7 +4450,6 @@ class KCLILiveSession:
|
|
|
4359
4450
|
event.app.invalidate()
|
|
4360
4451
|
|
|
4361
4452
|
@kb.add("c-5")
|
|
4362
|
-
@kb.add("c-h")
|
|
4363
4453
|
@kb.add("escape", "5")
|
|
4364
4454
|
@kb.add("f5")
|
|
4365
4455
|
def _c5(event):
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "kitecli"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.9"
|
|
8
8
|
description = "KiteCLI — Multi-account, multi-broker trading positions viewer (Zerodha + Kotak Neo)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|