Que fait trader-in-broker ?
The Order Executor is the only skill in this pack that can lead to a real order, so it is the strictest. An order reaches the market only after these gates, every time, for every order:
- trader-in-risk passed this exact order today (matched by fingerprint;
the same
ORDER_FIELDSand hash astrader-in-risk/check.py). "paper": falseintrader-in/config.json. With paper (the default) nothing is ever sent; fills go to a local simulated ledger.- The user typed an explicit yes for this one order in the chat.
- The user clicked Allow once on Claude's own permission card for each
place_ordercall, because order tools are set to "Needs approval".
No yes, no order. A yes covers one ticket (its buy and its stop and target legs), never a batch.
${CLAUDE_SKILL_DIR} below is this skill's folder (where this SKILL.md lives).
python3 "${CLAUDE_SKILL_DIR}/ticket.py" show # every ticket, READY or not
python3 "${CLAUDE_SKILL_DIR}/ticket.py" approve --index 1 --reply "yes"
python3 "${CLAUDE_SKILL_DIR}/ticket.py" paper-fill --index 1 --price 1196.30 # paper: the buy
python3 "${CLAUDE_SKILL_DIR}/ticket.py" paper-fill --symbol RELIANCE --side sell --qty 12 --price 1212.70 --leg target
python3 "${CLAUDE_SKILL_DIR}/ticket.py" paper # the paper ledger and P&L
python3 "${CLAUDE_SKILL_DIR}/ticket.py" result --index 1 --leg entry --status placed --broker-id ID --filled-qty 12 --filled-price 1196.30
python3 "${CLAUDE_SKILL_DIR}/ticket.py" result --index 1 --leg stop --status placed --broker-id ID
python3 "${CLAUDE_SKILL_DIR}/ticket.py" fill --symbol RELIANCE --side sell --qty 12 --price 1212.70 --leg target
python3 "${CLAUDE_SKILL_DIR}/ticket.py" flatten --symbol RELIANCE --qty 12
python3 "${CLAUDE_SKILL_DIR}/ticket.py" log
ticket.py never talks to a broker. It renders tickets, checks that a reply
is a real yes for that order, keeps the paper ledger, builds the flatten
ticket, and keeps the log in trader-in/state/broker.json. --now "2026-10-27 10:15"
(or bare 10:15) pins the clock in IST. Symbols are accepted as RELIANCE,
RELIANCE.NS, NSE:RELIANCE, M&M, BSE:500325, 500325.BO.
What it reads and writes
| file | what |
|---|---|
trader-in/orders.json | the proposed orders (trader-in-budget writes it; flatten replaces it) |
trader-in/config.json | broker (kite or upstox), paper, product, flat_by, costs, capital_inr |
trader-in/state/risk.json | trader-in-risk's verdicts; only a PASS from today is READY |
trader-in/state/broker.json | every yes, no, placed, leg placed and filled, with each entry's max loss incl. costs |
trader-in/state/paper.json | the paper ledger: cash, open positions, closed trades, realised P&L after est. costs |
trader-in/portfolio.json | cash and shares (positions keyed by symbol like RELIANCE.NS, with exchange) |
trader-in/plan.json | the flat-by time for flatten |
trader-in/universe.json | (Upstox) ISINs from the scanner, if present |
trader-in/state/EQUITY_L.csv | (Upstox) NSE's equity list, downloaded once when an ISIN is needed |
One trade, three orders (read this before every bracket)
trader-in-budget writes one "bracket" (entry, stop, target). Zerodha and Upstox no longer offer bracket orders for this, so the ticket turns it into:
| leg | order | when |
|---|---|---|
| 1 | BUY, LIMIT at the entry (or MARKET), MIS or CNC | now |
| 2 | SELL, SL-M with trigger_price = the stop | only after leg 1 has filled |
| 3 | SELL, LIMIT at the target | only after leg 1 has filled |
Legs 2 and 3 are NOT linked (no OCO). When one fills, the other is still
live. Cancel it at once, or it can fill too and leave the user short (for MIS)
or be rejected (for CNC). /trader-in-alerts can watch the stop and target
levels and ring when either is hit. Placed before leg 1 fills, legs 2 and 3
are plain sell orders and can open a short.
Every ticket shows the estimated costs (spec §6: brokerage min(₹20, 0.03%) per MIS leg, STT, exchange and SEBI charges, stamp duty, 18% GST) and the max loss at the stop including the round-trip costs. That same figure is logged for trader-in-risk's daily loss limit. Costs are always an estimate: the contract note has the real ones.
Paper (the default)
Indian brokers have no paper account, so paper is a local ledger in
trader-in/state/paper.json. Nothing is sent anywhere.
ticket.py show, one READY ticket, the user's yes,ticket.py approve.- When the price trades at the entry,
ticket.py paper-fill --index N(the limit price) or--price P. A buy limit cannot fill above its limit. - When the stop or the target trades, record that ONE sell:
paper-fill --symbol RELIANCE --side sell --qty 12 --price 1188.10 --leg stop. The ledger books gross P&L, the estimated round-trip costs and the net. ticket.py paperprints cash, open positions and realised P&L.
The paper entry is logged as placed (with "paper": true), so
trader-in-risk counts it against the trades-per-day and daily-loss limits
exactly as a live one.
Setup A: Zerodha Kite (official MCP server) — the default
Zerodha's server is github.com/zerodha/kite-mcp-server (MIT, Go). Checked against its source and README on 2026-10-27 (latest commit 5e044dc, tag v0.4.0-dev24).
The hosted server cannot place orders. The README says the hosted
https://mcp.kite.trade/mcp "excludes potentially destructive trading
operations for security", and Zerodha's own pages describe it as read-only
(one Z-Connect post says GTT orders were the exception; the scope has changed
over time, so check with Claude's tool list after connecting). It is free and
needs no API key: the login tool returns a Kite link, the user logs in with
their own Kite password and 2FA in the browser, never in the chat.
Hosted, read-only (quotes, holdings, positions, margins, orders, history):
- Claude Desktop: Settings > Developer > Edit Config, add the
"kite"block fromclaude_desktop_config.example.json(it runsnpx mcp-remote https://mcp.kite.trade/mcp; needs Node.js), restart. - Claude Code:
claude mcp add --transport http kite https://mcp.kite.trade/mcp(or the samenpx mcp-remotecommand as a stdio server:claude mcp add kite -- npx mcp-remote https://mcp.kite.trade/mcp).
To place orders through Claude, the user runs their own copy of the server with their own Kite Connect app:
- Create a Kite Connect app at developers.kite.trade (check the current
price there). Its redirect URL must point at the local server's callback;
in the server source the callback path is
/callbackonAPP_HOST:APP_PORT(defaulthttp://localhost:8080/callback). We did not test this end to end. - From 1 April 2026 Zerodha's support pages say order placement through the API needs a static IP whitelisted with Zerodha (NSE/SEBI retail-algo rules). A home connection without a static IP may get orders rejected. Check Zerodha's current rule before going live.
- Build it:
git clone https://github.com/zerodha/kite-mcp-server && cd kite-mcp-server && go build -o kite-mcp-server(Go 1.21+). - Add the
"kite-orders"block fromclaude_desktop_config.example.json: the full path to the binary,APP_MODEstdio, and the user typesKITE_API_KEYandKITE_API_SECRETinto that file themselves. In Claude Code:claude mcp add kite-orders -e APP_MODE=stdio -e KITE_API_KEY="$KITE_API_KEY" -e KITE_API_SECRET="$KITE_API_SECRET" -- /full/path/to/kite-mcp-serverwith the values exported in the user's own shell first. Keep only one ofkite/kite-ordersif you do not need both. - Restart, ask Claude to call
login, the user opens the link and logs in. Kite sessions expire daily (Kite Connect access tokens last until the next morning), so expect to log in again each trading day.
Keys and tokens never go in the chat. Never ask for them, never read the config file back into the conversation. If the user pastes a key or token, tell them to delete that message and regenerate it at developers.kite.trade.
Tool permissions. Customize > Connectors (Claude Desktop), open the Kite server, set:
place_order,modify_order,cancel_order,place_gtt_order,modify_gtt_order,delete_gtt_order: Needs approval. Never Always allow.- read-only tools (
get_profile,get_margins,get_holdings,get_positions,get_orders,get_order_history,get_order_trades,get_trades,get_gtts,get_quotes,get_ltp,get_ohlc,get_historical_data,search_instruments,login): Always allow is fine. - In Claude Code, keep them on ask in
.claude/settings.json:"permissions": {"ask": ["mcp__kite-orders__place_order", "mcp__kite-orders__modify_order", "mcp__kite-orders__cancel_order"]}; never put an order tool inallow. The server also takesEXCLUDED_TOOLS=place_order,modify_order,cancel_orderfor a read-only copy.
place_order arguments (from the server source): variety (regular),
exchange (NSE/BSE), tradingsymbol, transaction_type (BUY/SELL),
quantity (int), product (MIS/CNC), order_type
(MARKET/LIMIT/SL/SL-M), price (LIMIT, SL), trigger_price (SL,
SL-M), validity (DAY), optional tag (≤ 20 chars; the ticket adds
trin + 12 hex of the fingerprint so the order can be found later). The
server's tool has no market_protection argument; NSE now applies market
protection to API MARKET and SL-M orders, so a market order may be converted
to a protected limit by the broker. Limit entries avoid this. For BSE, Kite's
tradingsymbol is the scrip name, not the numeric code: look it up with
search_instruments.
Setup B: Upstox ("broker": "upstox")
- Upstox's official MCP server (
https://mcp.upstox.com/mcp, installed from theupstox/upstox-plugin-marketplacepluginupstox-mcp) is read-only: Upstox's page says you cannot place orders through it. Connections expire daily (OAuth consent in the browser). - Upstox's official agent skill (
/plugin install upstox-skill@upstox-plugins-officialin Claude Code, after/plugin marketplace add upstox/upstox-plugin-marketplace) can place orders with the v3 order API. It reads a daily access token from theUPSTOX_ACCESS_TOKENenvironment variable (set by the user in their own shell, never in chat), shows its own preview and waits for confirmation. Upstox says it does not run in plain Claude Desktop chat. - Or the user places the order in the Upstox app from the ticket.
The ticket prints the body for POST https://api-hft.upstox.com/v3/order/place
(v2 /v2/order/place is marked deprecated; it takes the same fields minus
slice): quantity (int), product (I intraday = MIS, D delivery = CNC),
validity DAY, price (0 for MARKET / SL-M), instrument_token,
order_type, transaction_type, disclosed_quantity 0, trigger_price
(0 unless SL / SL-M), is_amo false (Upstox ignores it), tag, and
market_protection: -1 (automatic) on MARKET and SL-M, since 0 is rejected
for API market orders.
instrument_token is NSE_EQ|<ISIN> (or BSE_EQ|<ISIN>). The ISIN comes
from, in order: --isin, an isin in the order, trader-in/universe.json
from the scanner, else NSE's EQUITY_L.csv (downloaded once with a browser
User-Agent to trader-in/state/EQUITY_L.csv, archives. then nsearchives.).
BSE limitation: the NSE list only maps NSE symbols. A BSE scrip code like
500325.BO cannot be mapped, so pass --isin; for a .BO name symbol the
NSE listing's ISIN is used (same company), but check it. We did not verify the
Upstox instruments file format here. With no ISIN a live Upstox ticket is not
READY; paper tickets do not need one.
Steps: the entry
ticket.py show. Only READY tickets go to the user. A ticket that is NOT READY or BLOCKED goes back to/trader-in-risk(it has no broker JSON).- Show ONE ticket, including the not-OCO warning, and ask: "Place this order? Reply yes or no." One order per question. Never offer "yes to all".
ticket.py approve --index N --reply "<the user's reply, word for word>". "Not recorded" means the reply was not an explicit yes for this order ("sure", "haan", "yes to all", a yes naming another stock or amount). Ask again. Never turn a reply into a yes yourself.- Paper:
paper-fillas above. Live, Kite: callplace_orderwith exactly the leg 1 JSON; the user clicks Allow once. Confirm the fill (get_order_history/get_positions), thenplace_orderleg 2, then leg 3, each with its own Allow once. Live, Upstox: hand the bodies to the Upstox agent skill or the app, one leg at a time, same order. - Record each live leg:
ticket.py result --index N --leg entry|stop|target --status placed|denied|failed --broker-id <id>(--filled-qty,--filled-priceon the entry). Only the entry counts as an order for trader-in-risk; the legs are logged asleg_placed. A failed order is not retried without a new check and a new yes.
Steps: while it is open, and out by flat_by
/trader-in-alertswatches the stop, the target and the flat-by time.- When the stop or the target fills: live,
ticket.py fill ... --leg stop(it reminds you to cancel the other leg now:get_orders, thencancel_order, Needs approval); paper,paper-fill ... --leg stop. - By
flat_by(15:10 IST default), anything still open gets closed. Brokers auto square-off open MIS positions from about 15:20 IST and charge a fee for it (Zerodha lists ₹50 + GST per order), so do not leave it to them:- Live: ask the broker how many shares are held:
get_positions(read only). ticket.py flatten --symbol RELIANCE --qty 12writes a MARKET sell (same product) totrader-in/orders.json./trader-in-risk. A sell that only closes what the user holds is never blocked by the daily limits or the entry window.- Live: first cancel the open SL-M stop and LIMIT target for that
stock (
get_orders,cancel_ordereach). Otherwise either can fill after the flatten and make the user short. ticket.py show, the user's yes, thenplace_order(live) orpaper-fill --index 1 --price P(paper), thenresult.
- Live: ask the broker how many shares are held:
Example
ORDER 1 of 1, PAPER (local simulated ledger, nothing is sent to Zerodha Kite), Zerodha Kite
BUY 12 RELIANCE (NSE), MIS intraday, limit 1196.30, good for today
stop-loss 1188.10 (SL-M sell, placed after the buy fills)
target 1212.70 (LIMIT sell, placed after the buy fills)
est. costs ₹15.16 round trip at the stop, ₹15.35 at the target (estimate)
max loss about ₹113.56 if the stop fills at 1188.10 (₹98.40 + ₹15.16 est. costs; it can fill worse in a fast market)
READY: trader-in-risk PASS, waiting for your yes
Leg 1, the entry: place_order with exactly:
{"variety": "regular", "exchange": "NSE", "tradingsymbol": "RELIANCE", "transaction_type": "BUY",
"quantity": 12, "product": "MIS", "order_type": "LIMIT", "validity": "DAY", "price": 1196.3, "tag": "trin..."}
Leg 2, the stop-loss, ONLY after leg 1 fills: place_order with exactly:
{... "transaction_type": "SELL", "order_type": "SL-M", "trigger_price": 1188.1 ...}
Leg 3, the target, ONLY after leg 1 fills: place_order with exactly:
{... "transaction_type": "SELL", "order_type": "LIMIT", "price": 1212.7 ...}
WARNING: the stop and the target are two separate orders, NOT linked (no OCO). ...
MIS is intraday: be flat by 15:10 IST. Brokers auto square-off open MIS positions from about 15:20 IST ...
The why and the plan stay in the file; they are never sent.
What it never does
- Never places an order without every gate, and never two tickets on one yes.
- Never places orders from a scheduled or background task: nobody is there to say yes.
- Never asks for, reads, stores or repeats API keys, secrets, access tokens, passwords or TOTPs.
- Never switches
"paper"to false. The user does that themselves. - Never places the stop or target leg before the entry has filled.
- Never places F&O, options, short sales or index orders (
^NSEI,^BSESNare context only). - Never promises a fill price: a market order and an SL-M fill at whatever the market gives.
Safety
- Education and tooling, not investment advice. A PASS is a rules check, not a recommendation. For advice, talk to a SEBI-registered investment adviser.
- Paper by default (
"paper": true, a local simulated ledger). Going live is the user's own decision, in their own config. - Nothing in this pack places an order except trader-in-broker, and only when all hold:
trader-in-risk passed that exact order today,
"paper": false, the user typed an explicit yes for that one order in the chat, and the user clicked Allow once on Claude's own card because the order tool is set to "Needs approval". Never set an order tool to "Always allow". - The stop and target are not linked at Indian brokers: cancel the other one the moment one fills.
- Costs on the ticket are estimates; MIS positions are auto squared off around 15:20 IST for a fee.
- Broker keys and tokens live in the user's own config file or environment, never in the chat.