kitecli 0.3.1__tar.gz → 0.3.3__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.3.1 → kitecli-0.3.3}/PKG-INFO +1 -1
- {kitecli-0.3.1 → kitecli-0.3.3}/cli/api_client.py +15 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/cli/base_manager.py +4 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/cli/charges.py +3 -3
- {kitecli-0.3.1 → kitecli-0.3.3}/cli/display.py +122 -69
- {kitecli-0.3.1 → kitecli-0.3.3}/cli/executor.py +81 -1
- kitecli-0.3.3/cli/greeks.py +334 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/cli/indicators.py +6 -3
- {kitecli-0.3.1 → kitecli-0.3.3}/cli/kite_manager.py +83 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/cli/kotak_manager.py +8 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/cli/live_session.py +973 -73
- {kitecli-0.3.1 → kitecli-0.3.3}/cli/nli.py +1 -1
- kitecli-0.3.3/cli/parser.py +317 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/cli/recorder.py +281 -6
- {kitecli-0.3.1 → kitecli-0.3.3}/kitecli.egg-info/PKG-INFO +1 -1
- {kitecli-0.3.1 → kitecli-0.3.3}/kitecli.egg-info/SOURCES.txt +1 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/pyproject.toml +1 -1
- kitecli-0.3.3/tests/test_conditional_orders.py +515 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/tests/test_parser.py +12 -0
- kitecli-0.3.3/tests/test_ui.py +329 -0
- kitecli-0.3.1/cli/greeks.py +0 -160
- kitecli-0.3.1/cli/parser.py +0 -181
- kitecli-0.3.1/tests/test_ui.py +0 -153
- {kitecli-0.3.1 → kitecli-0.3.3}/README.md +0 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/cli/__init__.py +0 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/cli/advisor.py +0 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/cli/config.py +0 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/cli/main.py +0 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/cli/telegram_bot.py +0 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/kitecli.egg-info/dependency_links.txt +0 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/kitecli.egg-info/entry_points.txt +0 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/kitecli.egg-info/requires.txt +0 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/kitecli.egg-info/top_level.txt +0 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/setup.cfg +0 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/tests/test_multi_broker.py +0 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/tests/test_nli.py +0 -0
- {kitecli-0.3.1 → kitecli-0.3.3}/tests/test_telegram.py +0 -0
|
@@ -636,3 +636,18 @@ class KCLIClient:
|
|
|
636
636
|
return mgr.get_ltp_and_tokens(api_key, symbols)
|
|
637
637
|
return {}
|
|
638
638
|
|
|
639
|
+
def get_market_depth(self, symbol: str) -> dict:
|
|
640
|
+
"""Fetch 5-depth order book, OHLC, and market stats for a symbol."""
|
|
641
|
+
if hasattr(_kite_manager, "get_market_depth"):
|
|
642
|
+
res = _kite_manager.get_market_depth(symbol)
|
|
643
|
+
if res.get("status") == "success":
|
|
644
|
+
return res
|
|
645
|
+
|
|
646
|
+
for mgr in [_kotak_manager]:
|
|
647
|
+
if hasattr(mgr, "get_market_depth"):
|
|
648
|
+
res = mgr.get_market_depth(symbol)
|
|
649
|
+
if res.get("status") == "success":
|
|
650
|
+
return res
|
|
651
|
+
|
|
652
|
+
return {"status": "error", "message": f"Market depth not available for symbol '{symbol}'"}
|
|
653
|
+
|
|
@@ -157,3 +157,7 @@ class BaseBrokerManager(ABC):
|
|
|
157
157
|
) -> dict[str, Any]:
|
|
158
158
|
"""Calculate margin required for a proposed order. Default: fallback estimation."""
|
|
159
159
|
return {"status": "error", "message": "not supported by this broker"}
|
|
160
|
+
|
|
161
|
+
def get_market_depth(self, symbol: str) -> dict[str, Any]:
|
|
162
|
+
"""Fetch 5-depth order book, OHLC, and market stats for a symbol. Default: not supported."""
|
|
163
|
+
return {"status": "error", "message": "not supported by this broker"}
|
|
@@ -7,7 +7,7 @@ Official Rates from https://zerodha.com/charges/#charges-explained:
|
|
|
7
7
|
- GST: 18% on (brokerage + SEBI charges + transaction charges).
|
|
8
8
|
- SEBI charges: ₹10 / crore (0.0001%).
|
|
9
9
|
- Stamp charges: 0.003% or ₹300 / crore on buy side.
|
|
10
|
-
- IPFT fee: ₹
|
|
10
|
+
- IPFT fee: ₹0.01 / crore (0.0000001%).
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
from typing import Any
|
|
@@ -55,8 +55,8 @@ def calculate_option_charges(
|
|
|
55
55
|
# 4. SEBI turnover fee: ₹10 / crore (0.0001%)
|
|
56
56
|
sebi_fee = round(total_val * 0.000001, 2)
|
|
57
57
|
|
|
58
|
-
# 5. IPFT fee: ₹
|
|
59
|
-
ipft_fee = round(total_val *
|
|
58
|
+
# 5. IPFT fee: ₹0.01 / crore (0.0000001%) for NSE/NFO
|
|
59
|
+
ipft_fee = round(total_val * 1e-9, 2) if is_nse else 0.0
|
|
60
60
|
|
|
61
61
|
# 6. GST: 18% on (Brokerage + Exchange Fee + SEBI Fee)
|
|
62
62
|
gst = round((brokerage + exch_fee + sebi_fee) * 0.18, 2)
|
|
@@ -6,10 +6,13 @@ through the functions in this module so the CLI has a consistent,
|
|
|
6
6
|
polished look.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
from datetime import date, datetime
|
|
10
|
+
from typing import Optional, Any
|
|
9
11
|
from rich.console import Console
|
|
10
12
|
from rich.panel import Panel
|
|
11
13
|
from rich.table import Table
|
|
12
14
|
from rich.text import Text
|
|
15
|
+
from cli.greeks import get_position_expiry_info, format_expiry_header, format_strike_display
|
|
13
16
|
|
|
14
17
|
console = Console()
|
|
15
18
|
|
|
@@ -70,12 +73,121 @@ def _format_pnl_pct(value: float) -> str:
|
|
|
70
73
|
return f"{prefix}{value:.2f}%"
|
|
71
74
|
|
|
72
75
|
|
|
76
|
+
def _build_account_positions_table(
|
|
77
|
+
positions: list[dict],
|
|
78
|
+
show_indices: bool = False,
|
|
79
|
+
start_idx: int = 1,
|
|
80
|
+
today: Optional[date] = None,
|
|
81
|
+
) -> tuple[Table, int]:
|
|
82
|
+
"""Build a grouped-by-expiry Table for an account's positions.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
(table, next_pos_idx)
|
|
86
|
+
"""
|
|
87
|
+
table = Table(
|
|
88
|
+
show_header=True,
|
|
89
|
+
header_style="bold #58a6ff",
|
|
90
|
+
border_style="#30363d",
|
|
91
|
+
row_styles=["", "dim"],
|
|
92
|
+
pad_edge=True,
|
|
93
|
+
expand=True,
|
|
94
|
+
)
|
|
95
|
+
table.add_column("Symbol", style="bold #e6edf3", ratio=1)
|
|
96
|
+
table.add_column("Strike", style="bold #79c0ff", justify="center", no_wrap=True)
|
|
97
|
+
table.add_column("Lots/Qty", justify="right", no_wrap=True)
|
|
98
|
+
table.add_column("Avg Price", justify="right", no_wrap=True)
|
|
99
|
+
table.add_column("LTP", justify="right", no_wrap=True)
|
|
100
|
+
table.add_column("P&L", justify="right", no_wrap=True)
|
|
101
|
+
|
|
102
|
+
if not positions:
|
|
103
|
+
return table, start_idx
|
|
104
|
+
|
|
105
|
+
if today is None:
|
|
106
|
+
today = date.today()
|
|
107
|
+
|
|
108
|
+
# 1. Group positions by expiry
|
|
109
|
+
groups: dict[Optional[date], list[tuple[dict, dict]]] = {}
|
|
110
|
+
for pos in positions:
|
|
111
|
+
info = get_position_expiry_info(pos)
|
|
112
|
+
exp = info.get("expiry_date")
|
|
113
|
+
groups.setdefault(exp, []).append((info, pos))
|
|
114
|
+
|
|
115
|
+
# 2. Sort expiry keys: real dates ascending, None (Equity) at end
|
|
116
|
+
sorted_exp_keys = sorted([k for k in groups.keys() if k is not None])
|
|
117
|
+
if None in groups:
|
|
118
|
+
sorted_exp_keys.append(None)
|
|
119
|
+
|
|
120
|
+
pos_idx = start_idx
|
|
121
|
+
first_group = True
|
|
122
|
+
|
|
123
|
+
for exp_k in sorted_exp_keys:
|
|
124
|
+
items = groups[exp_k]
|
|
125
|
+
# Sort within group: strike ascending, CE before PE, tradingsymbol
|
|
126
|
+
items.sort(
|
|
127
|
+
key=lambda x: (
|
|
128
|
+
x[0].get("strike", 0.0),
|
|
129
|
+
0 if x[0].get("option_type") == "CE" else (1 if x[0].get("option_type") == "PE" else 2),
|
|
130
|
+
str(x[1].get("tradingsymbol", "")),
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
group_pnl = sum(float(p.get("pnl", 0)) for _, p in items)
|
|
135
|
+
date_str, dte_str = format_expiry_header(exp_k, today=today)
|
|
136
|
+
|
|
137
|
+
# Section header
|
|
138
|
+
if not first_group:
|
|
139
|
+
table.add_section()
|
|
140
|
+
|
|
141
|
+
pnl_col = _pnl_style(group_pnl)
|
|
142
|
+
pnl_sign = "+" if group_pnl > 0 else ""
|
|
143
|
+
header_title = Text.assemble(
|
|
144
|
+
("📅 " + date_str, "bold #58a6ff"),
|
|
145
|
+
(f" ({dte_str})" if dte_str else "", "#8b949e"),
|
|
146
|
+
)
|
|
147
|
+
subtotal_title = Text(f"{pnl_sign}{_format_currency(group_pnl)}", style=f"bold {pnl_col}")
|
|
148
|
+
table.add_row(header_title, "", "", "", "", subtotal_title, style="bold")
|
|
149
|
+
table.add_section()
|
|
150
|
+
|
|
151
|
+
for info, pos in items:
|
|
152
|
+
pnl = float(pos.get("pnl", 0))
|
|
153
|
+
style = _pnl_style(pnl)
|
|
154
|
+
pos_sign = "+" if pnl > 0 else ""
|
|
155
|
+
|
|
156
|
+
symbol = str(pos.get("tradingsymbol", ""))
|
|
157
|
+
if show_indices:
|
|
158
|
+
symbol = f"[{pos_idx}] {symbol}"
|
|
159
|
+
pos_idx += 1
|
|
160
|
+
|
|
161
|
+
strike_display = format_strike_display(info)
|
|
162
|
+
|
|
163
|
+
lot_size = pos.get("lot_size", 1) or 1
|
|
164
|
+
qty = pos.get("quantity", 0)
|
|
165
|
+
if lot_size > 1:
|
|
166
|
+
lots = qty / lot_size
|
|
167
|
+
qty_display = f"{int(lots)}L" if lots == int(lots) else f"{lots:.1f}L"
|
|
168
|
+
else:
|
|
169
|
+
qty_display = str(qty)
|
|
170
|
+
|
|
171
|
+
table.add_row(
|
|
172
|
+
symbol,
|
|
173
|
+
strike_display,
|
|
174
|
+
qty_display,
|
|
175
|
+
_format_currency(float(pos.get("average_price", 0))),
|
|
176
|
+
_format_currency(float(pos.get("last_price", 0))),
|
|
177
|
+
Text(f"{pos_sign}{_format_currency(pnl)}", style=style),
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
first_group = False
|
|
181
|
+
|
|
182
|
+
return table, pos_idx
|
|
183
|
+
|
|
184
|
+
|
|
73
185
|
def display_positions(accounts_data: list[dict]) -> None:
|
|
74
|
-
"""
|
|
186
|
+
"""Print a Rich table of open positions across all accounts.
|
|
75
187
|
|
|
76
188
|
Args:
|
|
77
|
-
accounts_data: List of dicts, each with keys ``name``,
|
|
78
|
-
|
|
189
|
+
accounts_data: List of account dicts, each with keys ``name``,
|
|
190
|
+
``total_pnl``, and ``positions``.
|
|
79
191
|
"""
|
|
80
192
|
grand_total_pnl = 0.0
|
|
81
193
|
|
|
@@ -86,6 +198,7 @@ def display_positions(accounts_data: list[dict]) -> None:
|
|
|
86
198
|
grand_total_pnl += total_pnl
|
|
87
199
|
|
|
88
200
|
pnl_color = _pnl_style(total_pnl)
|
|
201
|
+
|
|
89
202
|
header_text = Text.assemble(
|
|
90
203
|
(f" {name} ", "bold #e6edf3"),
|
|
91
204
|
(" │ ", "#8b949e"),
|
|
@@ -104,31 +217,7 @@ def display_positions(accounts_data: list[dict]) -> None:
|
|
|
104
217
|
console.print()
|
|
105
218
|
continue
|
|
106
219
|
|
|
107
|
-
table =
|
|
108
|
-
show_header=True,
|
|
109
|
-
header_style="bold #58a6ff",
|
|
110
|
-
border_style="#30363d",
|
|
111
|
-
row_styles=["", "dim"],
|
|
112
|
-
pad_edge=True,
|
|
113
|
-
expand=True,
|
|
114
|
-
)
|
|
115
|
-
table.add_column("Symbol", style="bold #e6edf3", no_wrap=True)
|
|
116
|
-
table.add_column("Qty", justify="right")
|
|
117
|
-
table.add_column("Avg Price", justify="right")
|
|
118
|
-
table.add_column("LTP", justify="right")
|
|
119
|
-
table.add_column("P&L", justify="right")
|
|
120
|
-
|
|
121
|
-
for pos in positions:
|
|
122
|
-
pnl = float(pos.get("pnl", 0))
|
|
123
|
-
style = _pnl_style(pnl)
|
|
124
|
-
|
|
125
|
-
table.add_row(
|
|
126
|
-
str(pos.get("tradingsymbol", "")),
|
|
127
|
-
str(pos.get("quantity", 0)),
|
|
128
|
-
_format_currency(float(pos.get("average_price", 0))),
|
|
129
|
-
_format_currency(float(pos.get("last_price", 0))),
|
|
130
|
-
Text(_format_currency(pnl), style=style),
|
|
131
|
-
)
|
|
220
|
+
table, _ = _build_account_positions_table(positions, show_indices=False)
|
|
132
221
|
|
|
133
222
|
panel = Panel(
|
|
134
223
|
table,
|
|
@@ -181,7 +270,7 @@ def render_positions_to_string(accounts_data: list[dict], width: int = 80, show_
|
|
|
181
270
|
|
|
182
271
|
pnl_color = _pnl_style(total_pnl)
|
|
183
272
|
|
|
184
|
-
margin_net
|
|
273
|
+
margin_net = account.get("margin_net")
|
|
185
274
|
margin_cash = account.get("margin_cash")
|
|
186
275
|
|
|
187
276
|
header_parts: list[tuple[str, str]] = [
|
|
@@ -205,7 +294,6 @@ def render_positions_to_string(accounts_data: list[dict], width: int = 80, show_
|
|
|
205
294
|
|
|
206
295
|
header_text = Text.assemble(*header_parts)
|
|
207
296
|
|
|
208
|
-
|
|
209
297
|
if not positions:
|
|
210
298
|
panel = Panel(
|
|
211
299
|
Text(" No open positions", style="#8b949e italic"),
|
|
@@ -217,46 +305,11 @@ def render_positions_to_string(accounts_data: list[dict], width: int = 80, show_
|
|
|
217
305
|
capture_console.print()
|
|
218
306
|
continue
|
|
219
307
|
|
|
220
|
-
table =
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
row_styles=["", "dim"],
|
|
225
|
-
pad_edge=True,
|
|
226
|
-
expand=True,
|
|
308
|
+
table, pos_idx = _build_account_positions_table(
|
|
309
|
+
positions,
|
|
310
|
+
show_indices=show_indices,
|
|
311
|
+
start_idx=pos_idx,
|
|
227
312
|
)
|
|
228
|
-
table.add_column("Symbol", style="bold #e6edf3", no_wrap=True)
|
|
229
|
-
table.add_column("Lots/Qty", justify="right")
|
|
230
|
-
table.add_column("Avg Price", justify="right")
|
|
231
|
-
table.add_column("LTP", justify="right")
|
|
232
|
-
table.add_column("P&L", justify="right")
|
|
233
|
-
|
|
234
|
-
for pos in positions:
|
|
235
|
-
pnl = float(pos.get("pnl", 0))
|
|
236
|
-
style = _pnl_style(pnl)
|
|
237
|
-
|
|
238
|
-
symbol = str(pos.get("tradingsymbol", ""))
|
|
239
|
-
if show_indices:
|
|
240
|
-
symbol = f"[{pos_idx}] {symbol}"
|
|
241
|
-
pos_idx += 1
|
|
242
|
-
|
|
243
|
-
lot_size = pos.get("lot_size", 1) or 1
|
|
244
|
-
qty = pos.get("quantity", 0)
|
|
245
|
-
if lot_size > 1:
|
|
246
|
-
lots = qty / lot_size
|
|
247
|
-
# Show as integer lots if whole number, else 1 decimal
|
|
248
|
-
qty_display = f"{int(lots)}L" if lots == int(lots) else f"{lots:.1f}L"
|
|
249
|
-
else:
|
|
250
|
-
qty_display = str(qty)
|
|
251
|
-
|
|
252
|
-
table.add_row(
|
|
253
|
-
symbol,
|
|
254
|
-
qty_display,
|
|
255
|
-
_format_currency(float(pos.get("average_price", 0))),
|
|
256
|
-
_format_currency(float(pos.get("last_price", 0))),
|
|
257
|
-
Text(_format_currency(pnl), style=style),
|
|
258
|
-
)
|
|
259
|
-
|
|
260
313
|
|
|
261
314
|
panel = Panel(
|
|
262
315
|
table,
|
|
@@ -3,7 +3,8 @@ from cli.api_client import KCLIClient
|
|
|
3
3
|
from cli.parser import (
|
|
4
4
|
AccountSelectCommand, PlaceOrderCommand, ExitCommand,
|
|
5
5
|
StatusCommand, PositionsCommand, OrdersCommand,
|
|
6
|
-
CancelOrderCommand, ModifyOrderCommand
|
|
6
|
+
CancelOrderCommand, ModifyOrderCommand, MarketDepthCommand,
|
|
7
|
+
ConditionalOrderCommand, CancelConditionalOrderCommand, ConditionalOrdersViewCommand
|
|
7
8
|
)
|
|
8
9
|
|
|
9
10
|
class ExecutionContext:
|
|
@@ -370,4 +371,83 @@ async def execute_command(cmd: Any, context: ExecutionContext) -> dict:
|
|
|
370
371
|
"message": "\n".join(output_lines)
|
|
371
372
|
}
|
|
372
373
|
|
|
374
|
+
# 9. Market Depth Command
|
|
375
|
+
elif isinstance(cmd, MarketDepthCommand):
|
|
376
|
+
target_sym = cmd.symbol or context.selected_symbol
|
|
377
|
+
if not target_sym:
|
|
378
|
+
raise ValueError("Usage: md <symbol> (e.g. 'md NIFTY2682025000CE' or 'md INFY')")
|
|
379
|
+
|
|
380
|
+
res = context.client.get_market_depth(target_sym)
|
|
381
|
+
if res.get("status") != "success":
|
|
382
|
+
return {
|
|
383
|
+
"status": "executed",
|
|
384
|
+
"message": f"❌ Failed to fetch market depth for {target_sym}: {res.get('message', 'Unknown error')}"
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
sym = res.get("symbol", target_sym)
|
|
388
|
+
ltp = res.get("last_price", 0.0)
|
|
389
|
+
chg = res.get("change", 0.0)
|
|
390
|
+
chg_pct = res.get("change_pct", 0.0)
|
|
391
|
+
vol = res.get("volume", 0)
|
|
392
|
+
oi = res.get("oi", 0)
|
|
393
|
+
tot_b = res.get("total_buy_quantity", 0)
|
|
394
|
+
tot_s = res.get("total_sell_quantity", 0)
|
|
395
|
+
|
|
396
|
+
output_lines = [
|
|
397
|
+
f"📊 Market Depth: {sym} | LTP: ₹{ltp:.2f} ({chg:+.2f} / {chg_pct:+.2f}%) | Vol: {vol:,} | OI: {oi:,}",
|
|
398
|
+
"┌─────────┬─────────┬──────────────┬──────────────┬─────────┬─────────┐",
|
|
399
|
+
"│ Orders │ Qty │ Bid Price │ Ask Price │ Qty │ Orders │",
|
|
400
|
+
"├─────────┼─────────┼──────────────┼──────────────┼─────────┼─────────┤"
|
|
401
|
+
]
|
|
402
|
+
|
|
403
|
+
bids = res.get("buy_depth", [])
|
|
404
|
+
asks = res.get("sell_depth", [])
|
|
405
|
+
for i in range(5):
|
|
406
|
+
b = bids[i] if i < len(bids) else {}
|
|
407
|
+
a = asks[i] if i < len(asks) else {}
|
|
408
|
+
b_ord = f"{b.get('orders', 0):>7}" if b.get('orders') else " -"
|
|
409
|
+
b_qty = f"{b.get('quantity', 0):>7}" if b.get('quantity') else " -"
|
|
410
|
+
b_pr = f"₹{b.get('price', 0.0):>9.2f}" if b.get('price') else " -"
|
|
411
|
+
a_pr = f"₹{a.get('price', 0.0):>9.2f}" if a.get('price') else " -"
|
|
412
|
+
a_qty = f"{a.get('quantity', 0):>7}" if a.get('quantity') else " -"
|
|
413
|
+
a_ord = f"{a.get('orders', 0):>7}" if a.get('orders') else " -"
|
|
414
|
+
output_lines.append(f"│ {b_ord} │ {b_qty} │ {b_pr} │ {a_pr} │ {a_qty} │ {a_ord} │")
|
|
415
|
+
|
|
416
|
+
output_lines.append("├─────────┼─────────┼──────────────┼──────────────┼─────────┼─────────┤")
|
|
417
|
+
output_lines.append(f"│ Total Buy Qty: {tot_b:<17,} │ Total Sell Qty: {tot_s:<16,} │")
|
|
418
|
+
output_lines.append("└────────────────────────────────┬──────────────────────────────┘")
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
"status": "executed",
|
|
422
|
+
"message": "\n".join(output_lines)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
# 10. Conditional Order Command (Headless / CLI)
|
|
426
|
+
elif isinstance(cmd, ConditionalOrderCommand):
|
|
427
|
+
parent_cmd = cmd.parent_cmd
|
|
428
|
+
if not cmd.confirmed:
|
|
429
|
+
parent_res = await execute_command(parent_cmd, context)
|
|
430
|
+
if parent_res.get("status") == "pending_confirmation":
|
|
431
|
+
return {
|
|
432
|
+
"status": "pending_confirmation",
|
|
433
|
+
"message": f"Confirm CONDITIONAL CHAIN:\n 1) {parent_res.get('message')}\n 2) THEN ON FILL: {cmd.child_raw_text}",
|
|
434
|
+
"command": cmd
|
|
435
|
+
}
|
|
436
|
+
parent_cmd.confirmed = True
|
|
437
|
+
return await execute_command(parent_cmd, context)
|
|
438
|
+
|
|
439
|
+
# 11. Cancel Conditional Order Command
|
|
440
|
+
elif isinstance(cmd, CancelConditionalOrderCommand):
|
|
441
|
+
return {
|
|
442
|
+
"status": "executed",
|
|
443
|
+
"message": f"Cancelled conditional orders targeting '{cmd.target}'."
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
# 12. Conditional Orders View Command
|
|
447
|
+
elif isinstance(cmd, ConditionalOrdersViewCommand):
|
|
448
|
+
return {
|
|
449
|
+
"status": "executed",
|
|
450
|
+
"message": "Conditional orders viewer is active in live TUI dashboard ('kcli live')."
|
|
451
|
+
}
|
|
452
|
+
|
|
373
453
|
raise TypeError(f"Execution not implemented for command type '{type(cmd)}'.")
|