DEV Community

shakti tiwari
shakti tiwari

Posted on

The Complete Guide to Building an Algo Trading Bot from Scratch

From zero to live trading on Dhan — full stack, full code, real failures

I built my first trading bot in 2024. It lost money.

I rebuilt it in 2025. It broke.

I rebuilt it in 2026. It made ₹3.2 lakh in 6 months.

This is the complete guide — backend, frontend, ML model, deployment, and every mistake I made so you don’t have to.

Architecture overview

┌─────────────────┐     ┌──────────────────┐     ┌──────────────┐
│   Dhan API      │────▶│   Flask Backend  │────▶│  XGBoost ML  │
│   (data/orders) │     │   (port 5050)    │     │  (model.pkl) │
└─────────────────┘     └────────┬─────────┘     └──────────────┘
                                  │
                                  ▼
                         ┌──────────────────┐
                         │  Next.js Frontend│
                         │  (port 3000)     │
                         └────────┬─────────┘
                                  │
                                  ▼
                         ┌──────────────────┐
                         │ Cloudflare Tunnel│
                         │ (public access)  │
                         └──────────────────┘
Enter fullscreen mode Exit fullscreen mode

Step 1: Dhan API setup

1.1 Create API app on Dhan

  1. Go to https://api.dhan.co
  2. Sign up / log in
  3. Create new API app
  4. Save client_id and access_token
  5. Whitelist your IP (see static IP guide)

1.2 Test connection

Mac / Linux / Termux:

curl -X POST https://api.dhan.co/v2/user/profile \
  -H "Content-Type: application/json" \
  -H "access-token: YOUR_TOKEN" \
  -d '{"dhanClientId": "YOUR_CLIENT_ID"}'
Enter fullscreen mode Exit fullscreen mode

Windows CMD:

curl -X POST https://api.dhan.co/v2/user/profile -H "Content-Type: application/json" -H "access-token: YOUR_TOKEN" -d "{\"dhanClientId\": \"YOUR_CLIENT_ID\"}"
Enter fullscreen mode Exit fullscreen mode

Expected response:

{
  "data": {
    "name": "Shakti Tiwari",
    "email": "shaktitiwari715@gmail.com",
    "clientId": "1110480081"
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Backend setup (Flask)

2.1 Project structure

ai-trader-main/
├── backend/
│   ├── app.py              # Main Flask API
│   ├── indicators.py       # Technical indicators
│   ├── predictor.py        # XGBoost model loader
│   ├── dhan_client.py      # Dhan API wrapper
│   └── requirements.txt    # Python dependencies
├── models/
│   └── macro_model.pkl     # Trained model
├── dashboard/
│   ├── app/                # Next.js pages
│   ├── lib/api.ts          # Frontend API client
│   └── next.config.ts      # Proxy config
└── scripts/
    └── train_model.py      # Model training
Enter fullscreen mode Exit fullscreen mode

2.2 Backend code (backend/app.py):

from flask import Flask, jsonify, request
import os
from dotenv import load_dotenv
from indicators import compute_all_indicators
from predictor import load_model, predict
from dhan_client import DhanClient

load_dotenv()
app = Flask(__name__)
dhan = DhanClient()
model = load_model('models/saved/macro_model.pkl')

@app.route('/api/state')
def get_state():
    try:
        df = dhan.get_1min_data('NIFTY')
        df = compute_all_indicators(df)
        latest = df.iloc[-1]
        signal, confidence = predict(model, latest)

        return jsonify({
            "last_price": float(latest['close']),
            "signal": signal,
            "confidence": float(confidence),
            "regime": latest.get('regime', 'UNKNOWN'),
            "timestamp": str(latest['timestamp'])
        })
    except Exception as e:
        return jsonify({"error": str(e)}), 500

@app.route('/api/paper/positions')
def get_positions():
    # Paper trading positions
    return jsonify({"positions": [], "cash": 1000000})

@app.route('/api/risk/profiles')
def get_risk_profiles():
    return jsonify({"profiles": ["conservative", "moderate", "aggressive"]})

if __name__ == '__main__':
    port = int(os.getenv('PORT', 5050))
    app.run(host='0.0.0.0', port=port)
Enter fullscreen mode Exit fullscreen mode

2.3 Run backend:

Mac / Linux / Termux:

cd backend
python -m venv venv
source venv/bin/activate  # Mac/Linux/Termux
pip install -r requirements.txt
python app.py
Enter fullscreen mode Exit fullscreen mode

Windows CMD:

cd backend
python -m venv venv
venv\Scripts\activate
pip install -r requirements.txt
python app.py
Enter fullscreen mode Exit fullscreen mode

Step 3: Frontend setup (Next.js)

3.1 Create Next.js app

# Mac/Linux/Termux
npx create-next-app@latest dashboard --typescript --tailwind --no-eslint
cd dashboard

# Windows CMD
npx create-next-app@latest dashboard --typescript --tailwind --no-eslint
cd dashboard
Enter fullscreen mode Exit fullscreen mode

3.2 Install dependencies

npm install axios recharts lucide-react
Enter fullscreen mode Exit fullscreen mode

3.3 Frontend code (dashboard/app/page.tsx):

'use client'
import { useState, useEffect } from 'react'

interface State {
  last_price: number
  signal: string
  confidence: number
  regime: string
  timestamp: string
}

export default function Home() {
  const [state, setState] = useState<State | null>(null)
  const [error, setError] = useState<string | null>(null)

  useEffect(() => {
    const fetchState = async () => {
      try {
        const res = await fetch('/api/state')
        const data = await res.json()
        setState(data)
      } catch (e) {
        setError('Failed to fetch state')
      }
    }
    fetchState()
    const interval = setInterval(fetchState, 5000)
    return () => clearInterval(interval)
  }, [])

  if (error) return <div className="text-red-500">{error}</div>
  if (!state) return <div>Loading...</div>

  return (
    <main className="p-8">
      <h1 className="text-3xl font-bold">NIFTY Trading Dashboard</h1>
      <div className="mt-4 grid gap-4">
        <div>Price: {state.last_price}</div>
        <div>Signal: <span className={state.signal === 'CALL' ? 'text-green-500' : 'text-red-500'}>{state.signal}</span></div>
        <div>Confidence: {(state.confidence * 100).toFixed(1)}%</div>
        <div>Regime: {state.regime}</div>
      </div>
    </main>
  )
}
Enter fullscreen mode Exit fullscreen mode

3.4 Proxy config (next.config.ts):

/** @type {import('next').NextConfig} */
const nextConfig = {
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: 'http://localhost:5050/api/:path*',
      },
    ]
  },
}

export default nextConfig
Enter fullscreen mode Exit fullscreen mode

3.5 Run frontend:

npm run dev
# Open http://localhost:3000
Enter fullscreen mode Exit fullscreen mode

Step 4: ML model training

4.1 Fetch data

# scripts/fetch_data.py
import requests
import pandas as pd

def fetch_nifty_1min(token, from_date, to_date):
    url = "https://api.dhan.co/v2/chart/history"
    headers = {
        "Content-Type": "application/json",
        "access-token": token
    }
    payload = {
        "securityId": "13",
        "exchangeSegment": "IDX_I",
        "interval": "1",
        "fromDate": from_date,
        "toDate": to_date
    }

    response = requests.post(url, json=payload, headers=headers)
    data = response.json()

    df = pd.DataFrame(data['data'])
    df.to_csv('nifty_1min.csv', index=False)
    return df

df = fetch_nifty_1min('YOUR_TOKEN', '2024-01-01', '2026-07-31')
Enter fullscreen mode Exit fullscreen mode

4.2 Train model

# scripts/train_model.py
import pandas as pd
import xgboost as xgb
from sklearn.model_selection import train_test_split
import joblib

df = pd.read_csv('nifty_1min.csv')
df = compute_all_indicators(df)

# Target: 5-min forward return > 0.2%
df['target'] = (df['close'].shift(-5) / df['close'] - 1 > 0.002).astype(int)

features = [col for col in df.columns if col not in ['timestamp', 'close', 'target']]
X = df[features].fillna(0)
y = df['target']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, shuffle=False)

model = xgb.XGBClassifier(n_estimators=200, max_depth=4, learning_rate=0.05)
model.fit(X_train, y_train)

joblib.dump(model, 'models/saved/macro_model.pkl')
print("Model saved!")
Enter fullscreen mode Exit fullscreen mode

Mac / Linux / Termux:

cd scripts
python train_model.py
Enter fullscreen mode Exit fullscreen mode

Windows CMD:

cd scripts
python train_model.py
Enter fullscreen mode Exit fullscreen mode

Step 5: Auto-start on boot

Mac (launchd):

# Create plist
cat > ~/Library/LaunchAgents/com.ai-trader.backend.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.ai-trader.backend</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/bin/python3</string>
        <string>/Users/shakti/ai-trader-main/backend/app.py</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
</dict>
</plist>
EOF

# Load
launchctl load ~/Library/LaunchAgents/com.ai-trader.backend.plist
Enter fullscreen mode Exit fullscreen mode

Windows (Task Scheduler):

  1. Open Task Scheduler
  2. Create Basic Task → Trigger: At startup
  3. Action: Start program
  4. Program: python
  5. Arguments: C:\ai-trader-main\backend\app.py

Linux/Termux (cron):

(crontab -l 2>/dev/null; echo "@reboot cd /home/user/ai-trader-main/backend && python app.py &") | crontab -
Enter fullscreen mode Exit fullscreen mode

Step 6: Deploy with Cloudflare Tunnel

# Mac/Linux/Termux
cloudflared tunnel create ai-trader
cloudflared tunnel run ai-trader

# Windows CMD
cloudflared tunnel create ai-trader
cloudflared tunnel run ai-trader
Enter fullscreen mode Exit fullscreen mode

Your dashboard is live at https://your-tunnel.trycloudflare.com.

Common failures and fixes

Failure Cause Fix
Backend timeout Dhan API latency Increase timeout to 30s
Model not found Path wrong Use absolute paths
Frontend 404 Proxy misconfigured Check next.config.ts
IP rejected Dynamic IP Use static IP guide
Memory crash 300 rows too many Downsample to 1min bars

TL;DR

Component Tool Cost
Data Dhan API Free
ML XGBoost Open source
Backend Flask Open source
Frontend Next.js Open source
Hosting Cloudflare Tunnel Free

Total: ₹0. P&L: +₹3.2 lakh in 6 months.


Shakti Tiwari is a trader and developer building optiontradingwithai.in. He co-directs CodeVisser and authored books on trading psychology. Find him on Dev.to as @shaktitiwari715-ai.

Top comments (0)