algo tradingZerodhaKite ConnectPythonIndiatrading automation

How to Do Algo Trading in Zerodha: Build an Intraday Bot with Kite Connect and Python

·11 min read

"How to do algo trading in Zerodha" is up over 3,000% on Google Trends in India in the last three months. Most results are course ads or a 20-line script that places one order. I've built and run an intraday bot on Kite Connect, and this post covers the parts those scripts skip: the things that decide whether a bot is safe to leave running.


I won't share my strategy's rules or results. They're not the point, and you shouldn't copy someone else's anyway. Every number below that looks like a strategy setting is an example.


Last checked: 19 October 2026. Zerodha and SEBI change the rules often. Verify against the Kite Connect docs and zerodha.com/charges before trading real money.


Not investment advice. Intraday trading loses money for most retail traders. Paper trade first.


What you need


Item Cost Notes
Zerodha account Free With F&O/intraday enabled for the segments you trade
Kite Connect app (Connect type) ₹500/month Includes live quotes and historical data
Kite Connect app (Personal type) Free Orders and account APIs only. No market data, so not enough for a bot that reacts to prices
A server with a static IP ₹400–1,000/month Required for API orders (below)
Python 3.11+ and kiteconnect Free pip install kiteconnect

Zerodha cut Connect from ₹2,000 to ₹500 a month in 2025, and order placement has been free since March 2025.


The rules you can't skip (2026)


SEBI's retail algo framework now applies to API trading. In practice, for a personal bot on Zerodha:


  • Static IP. Orders sent through the API must come from an IP address registered in your Kite developer profile. Orders from any other IP are rejected. You can change it about once a week, so pick a server first.
  • One IP per person. Zerodha lets only family members (spouse, dependent children, dependent parents) share an order IP. If a friend or cousin wants to run the same bot on their account, their orders need a different IP.
  • Stay under 10 orders a second. Above that, an algo needs exchange registration. A personal intraday bot is nowhere near it.
  • One Kite Connect app per client ID. Several apps can live under one developer login, but each account holder needs their own app, key and secret.

  • Paper trading needs none of the IP setup, which is one more reason to start there.


    Step 1: the daily login


    Kite Connect has no "log in once" option. The access token expires every morning, so the bot needs a fresh login each trading day:


    1. Open the login URL and sign in with your Zerodha credentials and 2FA.

    2. Zerodha redirects to your app's redirect URL with a request_token.

    3. Exchange it for an access_token using your API secret.


    
    import os
    from kiteconnect import KiteConnect
    
    kite = KiteConnect(api_key=os.environ["KITE_API_KEY"])
    print("Log in here:", kite.login_url())
    
    request_token = input("Paste request_token from the redirect URL: ").strip()
    session = kite.generate_session(request_token, api_secret=os.environ["KITE_API_SECRET"])
    
    # keep the token private: owner-only file permissions
    with open(".kite_token", "w") as f:
        f.write(session["access_token"])
    os.chmod(".kite_token", 0o600)
    

    Zerodha expects this login to be done by a person, so don't script your TOTP. I log in from a small password-protected dashboard each morning: one click, and the redirect lands back on the dashboard, which saves the token.


    If you run several accounts, add &redirect_params=account%3D to the login URL. Zerodha sends it back on the redirect, so one callback URL can tell whose login it was.


    Check the token before trading


    
    def current_kite():
        kite = KiteConnect(api_key=os.environ["KITE_API_KEY"])
        kite.set_access_token(open(".kite_token").read().strip())
        try:
            kite.profile()          # cheap call; fails if today's login hasn't happened
        except Exception:
            return None
        return kite
    

    The bot should refuse to start without a valid token, not fail halfway through the morning.


    Step 2: respect the rate limits


    From the Kite docs:


    Endpoint Limit
    Quote (ltp, quote) 1 request/second
    Historical candles 3 requests/second
    Order placement 10/second, 400/minute, 5,000/day
    Modifications 25 per order, then cancel and re-place

    The quote limit is the one you'll hit. Ask for all your symbols in one call rather than one call per stock:


    
    prices = kite.ltp(["NSE:SBIN", "NSE:ITC", "NSE:INFY"])
    

    If you run more than one bot on the same login, they share that 1/second budget. I put a small lock file in front of ltp, so the bots take turns and reuse a result that's under a second old instead of each calling Kite.


    Kite also drops idle keep-alive connections now and then ("Remote end closed connection without response"). Retry GET requests automatically, but never auto-retry an order: a retried POST can place the order twice.


    
    from urllib3.util.retry import Retry
    pool = {"max_retries": Retry(total=2, backoff_factor=0.2, allowed_methods={"GET"})}
    kite = KiteConnect(api_key=key, pool=pool)
    

    Step 3: place orders that protect themselves


    An intraday (MIS) entry, then an immediate stop-loss order at Zerodha, not just in your code:


    
    entry_id = kite.place_order(
        variety=kite.VARIETY_REGULAR, exchange=kite.EXCHANGE_NSE,
        tradingsymbol="SBIN", transaction_type=kite.TRANSACTION_TYPE_BUY,
        quantity=qty, product=kite.PRODUCT_MIS,
        order_type=kite.ORDER_TYPE_MARKET, market_protection=-1,
    )
    
    stop_id = kite.place_order(
        variety=kite.VARIETY_REGULAR, exchange=kite.EXCHANGE_NSE,
        tradingsymbol="SBIN", transaction_type=kite.TRANSACTION_TYPE_SELL,
        quantity=qty, product=kite.PRODUCT_MIS,
        order_type=kite.ORDER_TYPE_SLM, trigger_price=stop_price, market_protection=-1,
    )
    

    market_protection=-1 lets Zerodha apply automatic protection, so a market order can't fill at a wild price in a thin moment.


    Why the stop goes to Zerodha: if your server crashes, loses network, or the Python process dies, a stop that only lives in your code does nothing. A stop at the exchange still fires.


    The safety rules I'd treat as the minimum:


  • Wait for the stop order to be accepted. If it's rejected, close the position and stop the bot.
  • Cancel the stop before exiting, then exit. If the stop already filled in the meantime, book it as a stop-loss. Don't sell twice.
  • Unknown state (timeout, order won't cancel): check Zerodha's actual positions before doing anything else.
  • On restart, compare your saved state with kite.positions()["day"]. If they disagree, refuse to trade until a human looks.
  • A rejected entry (stock not allowed for MIS, not enough margin) blocks that stock for the day instead of retrying every second.
  • Exit before Zerodha does. Zerodha auto-squares-off MIS positions around 3:20 PM (with a fee). Close your own positions earlier.

  • Targets: use a LIMIT order


    If you watch the price and send a market sell when it touches your target, you'll often fill below it. In my first live trade the market exit filled ₹2.16 a share under the target, which cost about 60% of the planned profit. A LIMIT order at the target price fixes that.


    You can't simply rest a target and a stop together on MIS: GTT OCO isn't available for MIS, and a second resting sell needs its own margin. So the usual pattern is a resting SL-M at Zerodha plus your code placing the LIMIT target when the price gets close.


    Step 4: do the charge math before anything else


    This is where most intraday ideas die. Zerodha's MIS charges per round trip (one buy + one sell) on NSE equity:


    Charge Rate
    Brokerage 0.03% per order, max ₹20
    STT 0.025% on the sell side
    NSE transaction charges 0.00307% both sides
    SEBI fee ₹10 per crore
    Stamp duty 0.003% on the buy side
    GST 18% on brokerage + exchange + SEBI

    As code (keep the rates in config, because budgets change them):


    
    def round_trip(buy, sell):
        brokerage = sum(min(v * 0.0003, 20) for v in (buy, sell))
        exchange = (buy + sell) * 0.0000307
        sebi = (buy + sell) * 10 / 1e7
        stt = sell * 0.00025
        stamp = buy * 0.00003
        gst = (brokerage + exchange + sebi) * 0.18
        return round(brokerage + exchange + sebi + stt + stamp + gst, 2)
    

    Example: a ₹2,00,000 position with a 0.2% target.


    
    Gross profit at target     ₹400
    Charges (round trip)       ₹118
    Net                        ₹282   → charges ate ~30%
    

    And with a 1% stop on the same trade:


    
    Gross loss                −₹2,000
    Charges                    −₹118
    Net                       −₹2,118
    

    With those numbers, one stop-loss wipes out about 7–8 winning trades. A small-target strategy needs a very high win rate just to break even. Put this function in your code before you write a single strategy rule.


    Intraday profit is taxed as speculative business income at your slab rate on the year's net total, not per trade. Talk to a CA.


    Step 5: paper trade, record everything, replay


    Have the bot run in paper mode by default, with live trading needing two switches (mode: live in config and a --live flag). That way a typo can't put money in the market.


    While paper trading, write every price the bot sees to a CSV (prices_YYYYMMDD.csv). Then you can replay real days through new code:


    
    python -m bot.replay logs/prices_*.csv --set target_pct=0.3
    

    Lessons from doing this for weeks:


  • Paper fills are optimistic. A paper fill takes the price you saw; a real stop-loss in a fast market fills worse. Write down the gap between paper and live fills.
  • Tiny timing changes swing results. On one recorded day, moving re-entries by under a second changed a rule from a large profit to a loss. Judge over many days, never one.
  • Backtests and paper disagree. Rules that lost in backtests did fine on paper, and the other way round. Trust a few weeks of paper results over any backtest, and trust live fills over both.
  • Free 1-minute candles (Yahoo keeps about a week) are fine for a rough check, but candles hide the order of ticks inside the minute.

  • Step 6: run it on a VPS, on IST


    The bot needs a machine that's on at 9:15 AM IST with the registered static IP. A ₹400–1,000/month Linux VPS works. Most VPS clocks run on UTC, so convert carefully:


    
    # trading-bot.timer — 09:02 IST = 03:32 UTC, weekdays
    [Timer]
    OnCalendar=Mon..Fri 03:32 UTC
    
    [Install]
    WantedBy=timers.target
    

    The bot itself should skip NSE holidays. Keep them in a holidays.yaml you update every year, and decide what happens on special sessions like Muhurat trading:


    
    def closed_reason(day):
        if day.weekday() >= 5:
            return day.strftime("%A")
        return HOLIDAYS.get(day)        # loaded from holidays.yaml
    

    Two more things worth doing:


  • Never auto-update during market hours. My update job runs at 8:00 AM, runs the tests on the new commit in a scratch copy, and only switches over if they pass and no bot is running.
  • Add alerts (Telegram is easiest) for every halt, rejection and stop-loss. It's the next thing on my own list: without them you end up checking log files during work.

  • Checklist before real money


  • [ ] Kite Connect app (Connect type) and redirect URL set
  • [ ] Static IP registered for your client ID
  • [ ] Daily login flow works from your phone
  • [ ] Quotes batched; GET retries only, orders never retried
  • [ ] Every entry followed by an SL-M order at Zerodha
  • [ ] Restart check against kite.positions()
  • [ ] Charges in the P&L, not just gross
  • [ ] Several weeks of paper trading, prices recorded
  • [ ] Live needs two switches
  • [ ] Holidays and UTC/IST handled; alerts on

  • Want a bot like this built and hosted for your own Zerodha account, with your rules? Get in touch.

    Built something similar or want to talk through the architecture? Get in touch.