Backtesting needs data. We'll use Dhan's free API to download 1-minute OHLCV candles and save them as a Parquet file — compact, fast, ready for pandas.
You can't test a strategy on vibes. You need historical minute-level candles. But where do you get them — free, reliable, and for Indian markets?
| Source | 1-Min Candles | Free | Lookback | Verdict |
|---|---|---|---|---|
Yahoo Finance (yfinance) |
Yes | Yes | ~7 days | Useless for backtesting |
| NSE Bhav Copy | No (daily only) | Yes | Years | No intraday |
| Upstox API | Yes | Free* | ~1 year | Needs trading account |
| Dhan API | Yes | Free | 90+ days | Best free option |
nifty_1min.parquet — 1-minute OHLCV candles for NIFTY 50 (last 30 days)banknifty_1min.parquet — same for BANKNIFTYpip install and you're connected.Go to dhan.co and create an account. No minimum balance required.
Aadhaar + PAN verification. Takes about 24 hours to get approved.
Go to Profile → API Access in your Dhan dashboard. Generate your access token.
Dhan access tokens reset every day. You'll need to regenerate before each download. This is normal — it's a security feature.
$ python3 -m pip install dhanhq pandas pyarrow Collecting dhanhq Downloading dhanhq-2.1.0-py3-none-any.whl Collecting pandas Downloading pandas-2.2.2-cp312-cp312-manylinux.whl Collecting pyarrow Downloading pyarrow-16.1.0-cp312-cp312-manylinux.whl Successfully installed dhanhq-2.1.0 pandas-2.2.2 pyarrow-16.1.0
Dhan's official SDK. Handles auth, endpoints, pagination.
Data manipulation. Turns raw API response into a DataFrame.
Parquet engine. Reads and writes .parquet files. 10x smaller than CSV.
# Set these before running the script $ export DHAN_CLIENT_ID="1234567890" $ export DHAN_ACCESS_TOKEN="eyJ0eXAiOiJKV1QiLCJhbGciOi..." # Verify they're set $ echo $DHAN_CLIENT_ID 1234567890 Ready.
Pasting tokens into your code is how secrets end up on GitHub. Environment variables keep them out of your source files. This is a habit — build it now.
from dhanhq import DhanHQ import pandas as pd dhan = DhanHQ("YOUR_CLIENT_ID", "YOUR_ACCESS_TOKEN") # NIFTY 50 index — security_id = 13 resp = dhan.intraday_minute_data( security_id='13', exchange_segment='IDX_I', instrument_type='INDEX', from_date='2026-04-20', to_date='2026-05-20', interval=1 ) df = pd.DataFrame(resp['data']) df.to_parquet('nifty_1min.parquet') print(f"Saved {len(df)} rows")
This is the core idea. The full script (which we provide) handles chunking, both instruments, and error handling automatically.
$ DHAN_CLIENT_ID="1234567890" DHAN_ACCESS_TOKEN="eyJ..." \ python scripts/download_dhan_history.py Downloading NIFTY (security_id=13) NIFTY 2026-04-20 → 2026-04-25 ... 1875 rows NIFTY 2026-04-26 → 2026-05-01 ... 1870 rows NIFTY 2026-05-02 → 2026-05-07 ... 1880 rows NIFTY 2026-05-08 → 2026-05-13 ... 1865 rows NIFTY 2026-05-14 → 2026-05-19 ... 1870 rows NIFTY 2026-05-20 → 2026-05-20 ... 375 rows Saved 9735 rows → data/nifty_1min.parquet Downloading BANKNIFTY (security_id=25) BANKNIFTY 2026-04-20 → 2026-04-25 ... 1875 rows ... Saved 9740 rows → data/banknifty_1min.parquet Done.
data/ folder.>>> import pandas as pd >>> df = pd.read_parquet("data/nifty_1min.parquet") >>> df.head() timestamp open high low close volume 0 2026-04-20 09:15 24510 24525 24498 24520 13400 1 2026-04-20 09:16 24520 24535 24515 24530 8200 2 2026-04-20 09:17 24530 24540 24525 24538 6100 3 2026-04-20 09:18 24538 24545 24530 24542 5800 4 2026-04-20 09:19 24540 24548 24535 24545 7200 >>> print(f"Total candles: {len(df)}") Total candles: 9735 >>> print(f"Date range: {df['timestamp'].min()} to {df['timestamp'].max()}") Date range: 2026-04-20 09:15:00 to 2026-05-20 15:29:00
| Column | Type | Example | Meaning |
|---|---|---|---|
timestamp |
datetime | 2026-04-20 09:15 | Start of the 1-minute candle (IST) |
open |
float | 24510.00 | Price at the start of the minute |
high |
float | 24525.00 | Highest price during the minute |
low |
float | 24498.00 | Lowest price during the minute |
close |
float | 24520.00 | Price at the end of the minute |
volume |
int | 13400 | Total contracts/shares traded |
Weekends and market holidays return empty. This is normal — the market was closed. Your script skips them cleanly.
Dhan tokens expire daily. Go to your Dhan dashboard, regenerate the token, and set the new value in your environment variable.
Check that your KYC is complete and API access is enabled in your Dhan profile. New accounts may take 24 hours.
You forgot to install. Run python3 -m pip install dhanhq pandas pyarrow and try again.
python3 -m pip install dhanhq pandas pyarrow.parquet files appear in data/df['close'].plot()