NSE equity dividend calendar — upcoming ex-dates, recent payouts, and per-stock detail with price history, dividend records, and quarterly results.
Flask + SQLite + gunicorn. Data from NSE bhavcopy, NSE corporate-actions API, and Yahoo Finance. No JavaScript framework.
┌─ / upcoming + recent ex-dates, search, sort
├─ /stocks all NSE stocks, sort A-Z/gainers/losers/volume, paginated
├─ /stock/<SYM> per-stock: price chart, facts, dividends, results
├─ /feed.json JSON feed of upcoming dividends
├─ /sitemap.xml sitemap
├─ /robots.txt robots
└─ /api/* manual refresh + bulk backfill
# Clone & install
git clone <repo-url> dividendcal
cd dividendcal
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
# Initialize DB + load bundled seeds (~2,900 stocks, 150 dividends)
python scripts/init_db.py
# Pull live data from NSE (one-time, ~5s)
python scripts/refresh_now.py
# Serve
gunicorn --bind 127.0.0.1:8000 wsgi:app
# or with hot-reload during dev:
flask --app wsgi run --debug --port 8000Open http://127.0.0.1:8000.
The repo is configured for Fly.io out of the box.
brew install flyctl
fly auth signup
fly launch --no-deploy # creates app, configures region
fly volumes create dividendcal_data --size 1 --region blr
fly secrets set SECRET_KEY="$(openssl rand -hex 32)" REFRESH_TOKEN="$(openssl rand -hex 24)"
fly deployPersistent SQLite volume at /app/data. The Dockerfile's CMD runs init_db.py then gunicorn on container start; wsgi.py schedules a daily NSE refresh at 16:30 IST.
| Data | Primary | Fallback |
|---|---|---|
| Price history (1Y) | Yahoo Finance via yfinance |
NSE bhavcopy archives |
| Today's price (live) | Yahoo fast_info |
NSE bhavcopy close |
| Dividend history | NSE corp-actions API (per-symbol) | bundled seed |
| Quarterly results | NSE results-comparison API | Yahoo quarterly_income_stmt |
| Symbol + company name | NSE equity list | bundled seed |
| Latest ex-dates (today) | NSE corp-actions bulk | bundled seed |
Most data is fetched on first visit to a stock page and cached forever:
/stock/RELIANCE(cold) → ~1-2s → fetches 1Y history from Yahoo, 16 dividends from NSE, 5 quarters from NSE/stock/RELIANCE(warm) → ~25ms → served from SQLite- Daily refresh (
POST /api/refreshor scheduler) → updates prices + recent dividends
Manual triggers:
# Full daily refresh (bhavcopy + corp-actions + equity list)
curl -X POST https://<app>.fly.dev/api/refresh
# Bulk backfill quarterly results (~0.8s/symbol; 50 symbols ~40s)
curl -X POST "https://<app>.fly.dev/api/backfill-results?limit=50&key=$TOKEN"app/
├── __init__.py # Flask app factory
├── routes.py # / /stocks /stock/<sym> /feed.json /sitemap /robots /api/*
├── db.py # SQLite schema + queries (WAL mode)
├── nse.py # NSE fetcher (bhavcopy, corp-actions, equity list)
├── yahoo.py # Yahoo Finance wrapper (price history + quote)
├── refresh.py # daily refresh + on-demand backfill
├── formatting.py # ₹, lakhs, %, date helpers
├── errors.py # 404 / 500 handlers
├── templates/ # Jinja: base, index, stocks, stock, 404, 500
└── static/style.css # newspaper theme (IBM Plex + Spectral)
data/seed/ # bundled CSVs (NSE bhavcopy snapshot for offline demo)
scripts/ # init_db, refresh_now, backfill_results, cleanup
wsgi.py # gunicorn entry point + APScheduler for daily refresh
stocks (symbol PK, company, isin)
prices (symbol, date, close, prev_close, volume) -- daily OHLCV
actions (id, symbol, ex_date, record_date, purpose, amount, action_type, source)
results (symbol, period_end, consolidated, income_lakhs, pbt_lakhs, pat_lakhs, eps)
refresh_meta (source, last_run, rows, ok, message)Indexes: idx_prices_symbol, idx_prices_date, idx_actions_ex_date, idx_actions_symbol.
pytest tests/ -v # 24 tests, ~5s
ruff check app/ scripts/ # lintMIT
Data is provided for information only, not investment advice. Data sourced from NSE public endpoints and Yahoo Finance. The site is not affiliated with NSE or Yahoo.
