Post-market geopolitical + fundamental intelligence for your Zerodha portfolio.
ZeraPortfolio is an AI-powered portfolio analysis system that runs after Indian market close (3:30 PM IST) every trading day. It fetches your live Zerodha holdings, scrapes fundamentals from Screener.in, pulls geopolitical signals from World Monitor, and runs a swarm of Google Gemini-powered AI agents to produce a comprehensive daily portfolio report — delivered to your dashboard and WhatsApp.
- Features
- Architecture
- Agent Workflow
- Tech Stack
- Project Structure
- Getting Started
- API Reference
- WhatsApp Setup (Twilio)
- License
- Live Portfolio Sync — Fetches holdings from Zerodha Kite Connect with 1-hour cache
- Fundamental Analysis — Scrapes P/E, ROE, ROCE, debt ratios, quarterly profits from Screener.in
- Geopolitical Intelligence — Maps your portfolio sectors to global geopolitical signals
- AI Agent Swarm — 4 specialist Gemini agents analyze your portfolio in parallel
- Daily Reports — Auto-generated at market close with executive summary, per-stock briefings, and actionable recommendations
- Bloomberg-style Dashboard — Dark terminal aesthetic with risk badges, heatmaps, and sparklines
- PDF Reports — Server-side HTML-to-PDF generation via WeasyPrint
- WhatsApp Delivery — PDF sent to your phone via Twilio after each report
┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
│ Zerodha │ │ Screener.in │ │ World Monitor │
│ Kite API │ │ (Scraper) │ │ (Sidecar) │
└──────┬───────┘ └──────┬───────┘ └────────┬─────────┘
│ │ │
└────────────────────┼──────────────────────┘
│
┌────────▼────────┐
│ FastAPI │
│ Backend │
│ (Port 8000) │
└────────┬────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────┐ ┌───────────┐
│ Gemini │ │ Neon.db │ │ Twilio │
│ AI Agents │ │ Postgres │ │ WhatsApp │
└──────────────┘ └──────────┘ └───────────┘
│
┌────────▼────────┐
│ React Frontend │
│ (Port 5173) │
└─────────────────┘
ZeraPortfolio uses a parallel fan-out + sequential synthesis pattern powered by Google ADK and Gemini:
┌─────────────────────┐
│ Orchestrator │
│ (asyncio.TaskGroup) │
└──────────┬──────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Geopolitical│ │ Fundamental │ │ Risk │
│ Agent │ │ Agent │ │ Agent │
│ (per stock) │ │ (per stock) │ │ (portfolio) │
│ gemini-flash │ │ gemini-flash │ │ gemini-flash │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└────────────────┼─────────────────┘
│
┌─────────▼──────────┐
│ Synthesis Agent │
│ gemini-2.5-pro │
└────────────────────┘
│
▼
Final Report JSON
-
Trigger — At 3:30 PM IST (or via manual
POST /api/reports/trigger), the scheduler kicks off a report. -
Data Collection
- Portfolio Agent fetches live holdings from Zerodha Kite Connect API
- Screener Scraper fetches fundamentals (P/E, ROE, ROCE, debt, quarterly profits) for each stock with polite 2–5s delays
- World Monitor Client fetches geopolitical signals, mapping each stock's NSE sector to global risk categories
-
Parallel Analysis (all run concurrently via
asyncio.TaskGroup)- Geopolitical Agent (1 per stock) — Assesses how current geopolitical events affect each holding. Uses
gemini-2.0-flashfor speed. Outputs risk level (HIGH/MEDIUM/LOW), time horizon, key regions, and recommended action. - Fundamental Agent (1 per stock) — Analyzes financial ratios and quarterly trends. Uses
gemini-2.0-flash. Outputs fundamental strength, key risks/positives, valuation assessment, and analyst note. - Risk Agent (1 per portfolio) — Evaluates portfolio-level risks: sector concentration, single-stock overweight (>20%), correlation clusters, missing diversification. Outputs a risk score (1–10).
- Geopolitical Agent (1 per stock) — Assesses how current geopolitical events affect each holding. Uses
-
Synthesis — The Synthesis Agent receives all outputs and produces a unified report using
gemini-2.5-pro(deeper reasoning). It generates:- Executive Summary (3–5 sentences)
- Portfolio Health Score (1–10)
- Per-Holding Briefings with combined geo + fundamental view
- Portfolio-Level Risk Assessment
- Actionable Recommendations (max 5, prioritized)
- Market Context paragraph
-
Delivery
- Report JSON stored in Neon.db
- HTML rendered via Jinja2 (Bloomberg Terminal aesthetic)
- PDF generated via WeasyPrint
- PDF sent to WhatsApp via Twilio
- Dashboard auto-refreshes to show the new report
Each agent is wrapped in try/except. If one stock's analysis fails, the others continue. The synthesis agent handles missing inputs gracefully. One bad stock never crashes the whole pipeline.
| Layer | Technology |
|---|---|
| LLM | Google Gemini (gemini-2.0-flash for speed, gemini-2.5-pro for synthesis) via Google AI Studio |
| Agent Framework | Google ADK (google-adk) |
| Backend | FastAPI + Uvicorn |
| Database | Neon.db (PostgreSQL via asyncpg + sqlalchemy[asyncio]) |
| Frontend | React 18 + Vite + TailwindCSS + Recharts |
| WeasyPrint (HTML → PDF) | |
| Messaging | Twilio WhatsApp API |
| Scheduling | APScheduler (cron-style) |
| Broker | Zerodha Kite Connect API |
| Scraping | httpx + BeautifulSoup4 |
portfolio_advisor/
├── README.md
├── CLAUDE.md # AI assistant instructions
├── LICENSE
├── docker-compose.yml
├── gstack.yaml
├── .gitignore
│
├── backend/
│ ├── .env.example # Environment variables template
│ ├── Dockerfile
│ ├── requirements.txt
│ ├── main.py # FastAPI app entry + lifespan
│ ├── config.py # Pydantic settings
│ ├── scheduler.py # APScheduler cron job
│ │
│ ├── db/
│ │ ├── base.py # SQLAlchemy async engine
│ │ ├── models.py # ORM models (reports, portfolio_cache, zerodha_tokens)
│ │ └── migrations/ # Alembic migrations
│ │
│ ├── integrations/
│ │ ├── zerodha_mcp.py # Zerodha Kite Connect client
│ │ ├── screener.py # Screener.in scraper
│ │ └── world_monitor.py # World Monitor sidecar client
│ │
│ ├── agents/
│ │ ├── schemas.py # All Pydantic output schemas
│ │ ├── gemini_client.py # Shared Gemini structured output
│ │ ├── orchestrator.py # Swarm orchestrator (fan-out + synthesis)
│ │ ├── portfolio_agent.py # Portfolio data fetcher
│ │ ├── geopolitical_agent.py # Geo risk analyst (per stock)
│ │ ├── fundamental_agent.py # Fundamental analyst (per stock)
│ │ ├── risk_agent.py # Portfolio risk manager
│ │ └── synthesis_agent.py # Report assembler (gemini-2.5-pro)
│ │
│ ├── report/
│ │ ├── generator.py # HTML → PDF via WeasyPrint
│ │ └── templates/
│ │ └── report.html # Bloomberg-style Jinja2 template
│ │
│ ├── delivery/
│ │ └── twilio_sender.py # WhatsApp PDF sender
│ │
│ └── api/
│ ├── auth.py # Zerodha OAuth flow (automated token)
│ ├── health.py # GET /health
│ ├── portfolio.py # GET /api/portfolio/live
│ └── reports.py # All /api/reports/* endpoints
│
├── world_monitor_sidecar/
│ ├── Dockerfile
│ └── server.py # FastAPI wrapper for worldmonitor
│
└── frontend/
├── Dockerfile
├── nginx.conf
├── package.json
├── tsconfig.json
├── vite.config.ts
├── tailwind.config.ts
├── index.html
└── src/
├── main.tsx
├── App.tsx
├── index.css
├── store.ts # Zustand global state
├── api/
│ └── client.ts # Typed API client
├── pages/
│ ├── Dashboard.tsx # Main dashboard
│ └── ReportDetail.tsx # Full report view
└── components/
├── PortfolioTable.tsx # Holdings table with badges
├── ReportCard.tsx # Latest report summary card
├── GeopoliticalHeatmap.tsx # World map risk visualization
└── RiskGauge.tsx # Health score gauge
- Python 3.12+
- Node.js 18+
- Homebrew (macOS) — for WeasyPrint system dependencies
- PostgreSQL (or a Neon.db account for serverless Postgres)
- Google AI Studio API key — free at aistudio.google.com
- Zerodha Kite Connect API key and secret — from kite.trade
- Set the redirect URL to
http://127.0.0.1:8000/api/auth/zerodha/callbackin your Kite Connect app
- Set the redirect URL to
- Twilio account (optional, for WhatsApp delivery) — from twilio.com
# Clone the repository
git clone https://github.com/yourusername/zeraportfolio-advisor.git
cd zeraportfolio-advisor
# System dependencies (macOS — required for PDF generation)
brew install pango
# Backend setup
python3 -m venv .venv
source .venv/bin/activate
pip install -r backend/requirements.txt
# Frontend setup
cd frontend
npm install
cd ..# Copy the example env file
cp backend/.env.example backend/.envEdit backend/.env and fill in your credentials:
# Required
GOOGLE_API_KEY=your-google-ai-studio-key
DATABASE_URL=postgresql+asyncpg://user:pass@host/dbname
ZERODHA_API_KEY=your-kite-api-key
ZERODHA_API_SECRET=your-kite-secret
APP_SECRET_KEY=any-random-secret-string
# Optional (for WhatsApp delivery)
TWILIO_ACCOUNT_SID=your-twilio-sid
TWILIO_AUTH_TOKEN=your-twilio-token
TWILIO_WHATSAPP_FROM=whatsapp:+14155238886
TWILIO_WHATSAPP_TO=whatsapp:+91XXXXXXXXXXNote: You do NOT need to set
ZERODHA_ACCESS_TOKENmanually. The app has a built-in OAuth flow that handles token generation and storage automatically.
You need 3 terminal tabs:
Tab 1 — World Monitor Sidecar:
cd ~/path/to/portfolio_advisor
source .venv/bin/activate
cd world_monitor_sidecar
uvicorn server:app --port 8001Tab 2 — Backend (FastAPI):
cd ~/path/to/portfolio_advisor
source .venv/bin/activate
cd backend
uvicorn main:app --reload --port 8000Tab 3 — Frontend (Vite):
cd ~/path/to/portfolio_advisor/frontend
npm run devStep 4 — Connect Zerodha (once per day):
Open http://localhost:8000/api/auth/zerodha/login in your browser. Log in with your Zerodha credentials. The app automatically exchanges the request token for an access token and saves it to the database. You'll see a green "Zerodha Connected!" confirmation page.
The Zerodha token expires daily at ~6 AM IST. Visit the login link again each morning to re-authenticate.
Step 5 — Open Dashboard & Run Analysis:
Go to http://localhost:5173. Click the "Run Analysis" button in the top-right corner to generate your first report on demand. It will ask for your APP_SECRET_KEY (from .env) once and remember it. Reports also auto-generate daily at 3:30 PM IST via the scheduler.
The Vite dev server proxies all /api/* and /health requests to the backend at port 8000.
# Make sure backend/.env is configured
docker compose up --buildThis starts all 3 services:
- Backend on port 8000
- Frontend on port 5173
- World Monitor sidecar on port 8001
| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
System health check (DB, Zerodha, World Monitor) |
GET |
/api/auth/zerodha/login |
Redirects to Zerodha login (OAuth flow) |
GET |
/api/auth/zerodha/callback |
OAuth callback — auto-exchanges token and saves to DB |
GET |
/api/auth/zerodha/status |
Check if a valid Zerodha token exists |
GET |
/api/portfolio/live |
Current holdings from Zerodha (1-hour cache) |
GET |
/api/reports |
List reports (last 30 days) |
GET |
/api/reports/latest |
Most recent completed report |
GET |
/api/reports/{id} |
Full report by ID |
GET |
/api/reports/{id}/pdf |
Download report PDF |
POST |
/api/reports/trigger |
Manually trigger report generation (requires X-API-Key header) |
curl -X POST http://localhost:8000/api/reports/trigger \
-H "X-API-Key: your-app-secret-key"Or use the "Run Analysis" button on the dashboard — it does the same thing with a visual progress indicator.
- Go to Twilio Console → Messaging → Try it out → Send a WhatsApp message
- Join the sandbox by sending
join <sandbox-word>to +1 415 523 8886 on WhatsApp - Set
TWILIO_WHATSAPP_TOin your.envto your WhatsApp number with country code (e.g.,whatsapp:+919876543210)
The system will send the daily PDF report to your WhatsApp after each successful generation.
WeasyPrint requires system libraries (pango, glib, gobject). On macOS:
brew install pangoThe dashboard and reports work without it — only PDF download will be unavailable until pango is installed.
Visit http://localhost:8000/api/auth/zerodha/login to re-authenticate. Tokens expire daily at ~6 AM IST.
Make sure your DATABASE_URL uses postgresql+asyncpg:// (not postgresql://) and ?ssl=require (not ?sslmode=require&channel_binding=require).
This project is licensed under the MIT License — see the LICENSE file for details.
Built by Neelakandan — post-market intelligence, every trading day.