⚠ Developer Disclaimer: Instacode.in is a software development company — we are NOT a SEBI-registered Investment Advisor (RIA) and do NOT provide investment, trading, or financial advice. All content below is purely educational / technical in nature. Strategies are mentioned only as coding patterns — outcomes, profits, or losses depend entirely on the user's own decisions, capital, and market conditions. Trading in markets carries risk of loss.

This is the whole thing end to end — project setup, strategy, paper trading, going live, and deploying somewhere it keeps running when you close your laptop. By the end you will have a bot on a server, logging everything it does.

It is a learning project, not a money-making one. The strategy is deliberately simple so you can focus on the plumbing, which is the part that actually matters.

What you need first

  • Python 3.10 or newer
  • A Zerodha account with Kite Connect — ₹2,000 a month
  • Enough Python to read the code below without panic
  • A VPS, around ₹500 a month on Vultr or DigitalOcean

Step 1 — Set up the project

mkdir trading-bot && cd trading-bot
python -m venv venv
source venv/bin/activate   # Windows: venv\Scripts\activate
pip install kiteconnect pandas python-dotenv schedule

Layout

trading-bot/
├── .env
├── config.py
├── auth.py
├── strategy.py
├── bot.py
├── logger.py
└── logs/

Splitting it into files this early looks like overkill for a hundred lines. It stops being overkill the first time you want to change the strategy without touching the order code.

Step 2 — Config and authentication

.env

KITE_API_KEY=your_key
KITE_API_SECRET=your_secret
KITE_ACCESS_TOKEN=
SYMBOL=RELIANCE
INSTRUMENT_TOKEN=738561
QUANTITY=10
PAPER_TRADE=true

Note PAPER_TRADE=true. That flag is the most important line in the project and it stays true for weeks.

auth.py

from kiteconnect import KiteConnect
from dotenv import load_dotenv, set_key
import os

load_dotenv()

def get_kite():
    kite = KiteConnect(api_key=os.getenv("KITE_API_KEY"))
    token = os.getenv("KITE_ACCESS_TOKEN")
    if not token:
        print("Visit:", kite.login_url())
        request_token = input("Paste request_token: ")
        data = kite.generate_session(request_token, api_secret=os.getenv("KITE_API_SECRET"))
        token = data["access_token"]
        set_key(".env", "KITE_ACCESS_TOKEN", token)
    kite.set_access_token(token)
    return kite

Add .env to .gitignore now, before you forget. API secrets in a public repository is a bad afternoon.

Step 3 — The strategy

strategy.py

import pandas as pd

def ma_crossover_signal(df):
    df['ma_fast'] = df['close'].rolling(9).mean()
    df['ma_slow'] = df['close'].rolling(21).mean()

    if len(df) < 22:
        return None

    last = df.iloc[-1]
    prev = df.iloc[-2]

    if prev['ma_fast'] <= prev['ma_slow'] and last['ma_fast'] > last['ma_slow']:
        return "BUY"
    if prev['ma_fast'] >= prev['ma_slow'] and last['ma_fast'] < last['ma_slow']:
        return "SELL"
    return None

Notice it compares the previous bar with the current one. Checking only whether fast is above slow would fire a signal on every single bar of a trend instead of once at the crossing.

Step 4 — The main loop

bot.py

import os, time
import pandas as pd
from datetime import datetime, timedelta
from auth import get_kite
from strategy import ma_crossover_signal
from logger import log

PAPER  = os.getenv("PAPER_TRADE", "true") == "true"
SYMBOL = os.getenv("SYMBOL")
TOKEN  = int(os.getenv("INSTRUMENT_TOKEN"))
QTY    = int(os.getenv("QUANTITY"))

def fetch_candles(kite):
    end = datetime.now()
    start = end - timedelta(days=5)
    return kite.historical_data(TOKEN, start, end, "5minute")

def place_order(kite, side):
    if PAPER:
        log(f"[PAPER] {side} {QTY} {SYMBOL}")
        return
    order_id = kite.place_order(
        variety="regular",
        exchange="NSE",
        tradingsymbol=SYMBOL,
        transaction_type=side,
        quantity=QTY,
        product="MIS",
        order_type="MARKET"
    )
    log(f"[LIVE] {side} order: {order_id}")

def main():
    kite = get_kite()
    log("Bot started — paper=" + str(PAPER))
    while True:
        try:
            now = datetime.now()
            if not (9 <= now.hour < 15 or (now.hour == 15 and now.minute < 15)):
                time.sleep(60); continue

            candles = fetch_candles(kite)
            df = pd.DataFrame(candles)
            signal = ma_crossover_signal(df)
            if signal:
                log(f"Signal: {signal}")
                place_order(kite, signal)
            time.sleep(300)
        except Exception as e:
            log(f"ERROR: {e}")
            time.sleep(60)

if __name__ == "__main__":
    main()

Two details worth pointing out. The try-except around the whole loop means a network blip logs an error and retries instead of killing the process — which is the difference between a bot and a script. And the market hours check keeps it from placing orders at eight in the evening when a bug in your time logic would otherwise be very expensive.

Step 5 — Logging

logger.py

import os
from datetime import datetime

os.makedirs("logs", exist_ok=True)

def log(msg):
    ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    line = f"[{ts}] {msg}"
    print(line)
    with open(f"logs/{datetime.now().strftime('%Y-%m-%d')}.log", "a") as f:
        f.write(line + "\n")

Twelve lines, and it will be the file you open every time something surprises you. Log more than feels necessary.

Step 6 — Paper trade, properly

python bot.py

Run it in paper mode for at least two weeks and actually read the logs each evening. You are checking three things: does it fire when you expect, does it fire when you do not expect, and does it survive a day where the API misbehaves.

Only when all three look right should you set PAPER_TRADE=false — and then start with a quantity small enough that a bad day is a lesson rather than a problem.

Step 7 — Deploy to a VPS

  1. Create an Ubuntu 22.04 server on Vultr or DigitalOcean — ₹400 to ₹600 a month.
  2. SSH in and clone your repository.
  3. Repeat the virtualenv setup there.
  4. Run it under systemd so it restarts on failure and survives your SSH session closing.

systemd service

# /etc/systemd/system/tradingbot.service
[Unit]
Description=Trading Bot
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/trading-bot
ExecStart=/home/ubuntu/trading-bot/venv/bin/python bot.py
Restart=always

[Install]
WantedBy=multi-user.target
sudo systemctl enable tradingbot
sudo systemctl start tradingbot
sudo journalctl -u tradingbot -f

Restart=always is doing real work here. If the process dies at 10:30, systemd brings it back in seconds instead of leaving you flat with an open position.

Step 8 — Know when something breaks

A bot running silently on a server you never look at is a liability. Push alerts somewhere you actually read.

import requests

def telegram_alert(msg):
    requests.post(
        f"https://api.telegram.org/bot{TOKEN}/sendMessage",
        data={"chat_id": CHAT_ID, "text": msg}
    )

Alert on every order and every exception. Not on every loop iteration — you will mute the channel within a day, and then it is worse than having no alerts at all.

What to build next

  • Stop-loss and target, which this version does not have
  • Position sizing based on capital rather than a fixed quantity
  • Support for multiple symbols
  • A daily loss limit and a kill switch
  • A backtesting module so you can test changes before deploying them

The stop-loss is not optional in any real version. It is left out here only so the main loop stays readable.

Before you use this with money

This is a teaching example. A production system needs thorough backtesting, proper risk management, order state reconciliation and a tested kill switch. If you want that built properly, we build complete systems — and we hand over the source code, so it stays yours.

FAQs

Does the bot need to run 24/7?

It only trades during market hours, but keeping the process running on a VPS avoids the real failure modes — a laptop that sleeps, a Wi-Fi drop, or a power cut at 10:15.

How long should I paper trade before going live?

At least two weeks, and ideally across different market conditions. You are checking that it fires when expected, does not fire when it should not, and survives a day when the API is unreliable.

Why does the code split into so many files for such a small bot?

So you can change the strategy without touching the order code. The first time you want to test a different signal, that separation saves you from breaking something that already worked.

Is a stop-loss included in this example?

No, deliberately — it is left out to keep the main loop readable. Any version that touches real money needs one, along with a daily loss limit and a kill switch.

📚 More Algo Trading Guides

Common Algo Trading Strategies for Nifty &amp; BankNifty — A Developer's View (2026)Zerodha Kite Connect API — Step-by-step Tutorial (2026)Python Algo Trading in India — A Beginner's Guide (2026)Python vs VBA for Trading Automation — Which One Should You Pick?Backtesting a Trading Strategy in Python — Step by StepSEBI Algo Trading Rules — A Plain-English Guide (2026)
Found this useful? Share it:

Need a custom solution?

Instacode builds production-grade software — algo trading, ecommerce, web apps. Let's talk.

Get in Touch

💬 Comments (0)

Leave a comment

Apna sawaal ya feedback share karo — Sonali aur team padhte hain.

Be the first to comment 🚀