AI-powered Stock Market Research Assistant
Full-stack platform delivering institutional-grade financial data, technical analysis, NLP sentiment scoring, and actionable recommendations through a unified API and modern React dashboard.
| Service | URL |
|---|---|
| Frontend | https://quantlens-ai.vercel.app |
| API (Render) | https://quantlens-ai.onrender.com |
| Swagger Docs | https://quantlens-ai.onrender.com/docs |
Demo credentials: demo@quantlens.ai / password123
Retail investors face a fragmented landscape: stock data behind paywalls, sentiment analysis requiring ML expertise, and technical indicators scattered across platforms. QuantLens AI consolidates these into a single platform with a REST API and a responsive dashboard, using open-source tooling and freely available data sources.
The system aggregates data from Yahoo Finance (via yfinance and direct HTTP), optionally enhanced by a Finnhub API key. A three-tier fallback architecture ensures resilience when any individual data source is rate-limited or unavailable.
- JWT-based auth with bcrypt password hashing (
python-jose+passlib[bcrypt]) - 24-hour token expiry, Bearer token transport
- CORS restricted to configured origins
- SQL injection prevention via SQLAlchemy ORM parameterized queries
- Ticker search with Yahoo Finance autocomplete
- Company overview: market cap, PE ratio, EPS, dividend yield, beta, 52-week range
- Historical price data with configurable periods (1 month, 6 months, 1 year, 5 years)
- Three-tier data sourcing: Finnhub → Yahoo Direct HTTP → yfinance
- Real-time news aggregation from Yahoo Finance
- Inline VADER sentiment scoring on each article headline
- Rule-based financial lexicon as fallback sentiment engine
- Sentiment cache with 24-hour freshness window
- Rate-limit resilience: cached news served when Yahoo throttles requests
- RSI(14), EMA20, EMA50, SMA200, MACD, Bollinger Bands, ATR(14)
- Computed server-side from 1 year of daily OHLCV data
- NaN-tolerant: malformed rows filtered before calculation
- Combines technical indicators (70% weight) with news sentiment (30% weight)
- Produces STRONG BUY / BUY / HOLD / SELL / STRONG SELL signals
- Risk level derived from ATR as percentage of price
- Always returns a valid recommendation: news failures default to neutral sentiment
- Persistent user watchlist with per-ticker metadata caching
- Background refresh of price, PE, EPS, market cap, volume
- Aggregate portfolio view with computed returns
- Unique constraint per user-ticker pair
- In-memory TTL caching (
cachetools.TTLCache) with 15-minute expiry across 5 caches - Database-backed sentiment cache with 24-hour freshness
- Frontend localStorage caching for recent searches and last viewed stocks
- Debounced search input (300ms) to reduce API calls
QuantLens AI uses a React frontend and FastAPI backend with multi-provider market data aggregation, intelligent fallback mechanisms, sentiment analysis, caching, and watchlist management.
All indicators are computed server-side in MarketDataService.get_stock_technical() using 1 year of daily OHLCV data from yfinance.
| Indicator | Period | Formula | Purpose |
|---|---|---|---|
| RSI | 14 | 100 - (100 / (1 + avg_gain/avg_loss)) |
Measures magnitude of recent price changes to identify overbought (>70) or oversold (<30) conditions |
| EMA20 | 20 | close.ewm(span=20, adjust=False) |
Short-term trend direction; reacts faster than SMA to price changes |
| EMA50 | 50 | close.ewm(span=50, adjust=False) |
Medium-term trend; commonly used by institutional traders |
| SMA200 | 200 | close.rolling(200).mean() |
Long-term trend proxy; price above SMA200 signals secular bull trend |
| MACD | 12, 26, 9 | ema12 - ema26 → signal line (ema9 of MACD) → histogram |
Momentum oscillator; crossover above signal is bullish, below is bearish |
| Bollinger Bands | 20, 2σ | sma20 ± 2 * std20 |
Volatility envelope; price near lower band suggests oversold, near upper band suggests overbought |
| ATR | 14 | max(H-L, H-prevC, prevC-L).rolling(14).mean() |
Absolute volatility measure; used for risk sizing and stop-loss placement |
The recommendation endpoint (/insights/{ticker}/recommendation) produces a weighted signal by combining technical and sentiment scores.
| Component | Weight | Source |
|---|---|---|
| Technical | 70% | get_stock_technical() |
| Sentiment | 30% | get_stock_news() + SentimentService.analyze_news_list() |
| Condition | Adjustment |
|---|---|
| RSI < 30 (oversold) | +25 |
| RSI 30–40 (approaching oversold) | +10 |
| RSI > 70 (overbought) | -25 |
| Price > EMA20 | +10 |
| Price > EMA50 | +15 |
| Price > SMA200 | +20 |
| MACD line > signal line | +15 |
| MACD line < signal line | -15 |
| Price near lower Bollinger Band | +10 |
| Price near upper Bollinger Band | -10 |
Final technical score clamped to [0, 100].
| Compound Score | Sentiment | Score |
|---|---|---|
| ≥ 0.5 | Very Positive | 100 |
| ≥ 0.15 | Positive | 75 |
| > -0.15 | Neutral | 50 |
| > -0.5 | Negative | 25 |
| ≤ -0.5 | Very Negative | 0 |
| Final Score | Signal | Interpretation |
|---|---|---|
| ≥ 80 | STRONG BUY | Overwhelmingly bullish technical + sentiment alignment |
| 65–79 | BUY | Favorable setup with moderate conviction |
| 45–64 | HOLD | Mixed signals; wait for clearer direction |
| 25–44 | SELL | Caution warranted; deteriorating conditions |
| < 25 | STRONG SELL | Multiple bearish indicators active |
| ATR as % of Price | Risk Level |
|---|---|
| > 3% | High |
| 1.5–3% | Medium |
| < 1.5% | Low |
| Technology | Purpose |
|---|---|
| React 19 | UI framework |
| Vite 8 | Build tool / dev server |
| Tailwind CSS 4 | Utility-first styling with glassmorphism theme |
| Recharts 3 | Charting library (AreaChart for price history) |
| Lucide React | Icon library |
| Hash-based SPA routing | Custom router (no react-router dependency) |
| Technology | Purpose |
|---|---|
| Python 3.11+ | Runtime |
| FastAPI | REST framework with auto-generated OpenAPI/Swagger docs |
| SQLAlchemy 2.0 | ORM with async session management |
| Alembic | Database migrations |
| Pydantic v2 | Request/response validation and settings management |
| python-jose | JWT encoding and decoding (HS256) |
| passlib[bcrypt] | Password hashing |
| Source | Access Method | Tier Priority |
|---|---|---|
| Finnhub | REST API (free tier, 60 req/min) | Tier 1 |
| Yahoo Finance | Direct HTTP (query1/query2 endpoints) | Tier 2 |
| Yahoo Finance | yfinance Python library | Tier 3 |
| Engine | Type | Location |
|---|---|---|
| VADER | Rule-based sentiment (compound score) | Inline in get_stock_news() |
| Financial Lexicon | Custom keyword matching with 42 terms | SentimentService.analyze_headline_fallback() |
| Component | Platform | Config |
|---|---|---|
| Frontend | Vercel | vercel.json with Vite build |
| Backend | Render | uvicorn via Render Web Service |
| Database | SQLite | DATABASE_URL env var |
All endpoints require authentication via Authorization: Bearer <token> header unless noted.
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | /api/v1/auth/register |
Create new user account | No |
| POST | /api/v1/auth/token |
Login via OAuth2 form, returns JWT | No |
| GET | /api/v1/auth/me |
Get current authenticated user | Yes |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /api/v1/stocks/search?q= |
Yahoo Finance autocomplete search | Yes |
| GET | /api/v1/stocks/{ticker}/overview |
Company overview (market cap, PE, EPS, beta, etc.) | Yes |
| GET | /api/v1/stocks/{ticker}/history?period= |
OHLCV history (1m/6m/1y/5y) | Yes |
| GET | /api/v1/stocks/{ticker}/news |
News articles with VADER sentiment scores | Yes |
| GET | /api/v1/stocks/{ticker}/technical |
Technical indicators (RSI, EMA, MACD, Bollinger, ATR) | Yes |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /api/v1/watchlist/ |
List all watchlist items for current user | Yes |
| GET | /api/v1/watchlist/{ticker} |
Get single watchlist item | Yes |
| POST | /api/v1/watchlist/ |
Add ticker to watchlist | Yes |
| POST | /api/v1/watchlist/{ticker}/refresh |
Refresh cached watchlist data | Yes |
| DELETE | /api/v1/watchlist/{ticker} |
Remove ticker from watchlist | Yes |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /api/v1/insights/{ticker}/sentiment |
NLP sentiment analysis of recent news | Yes |
| GET | /api/v1/insights/{ticker}/research |
Bull/bear case with strengths, risks, growth drivers | Yes |
| GET | /api/v1/insights/{ticker}/earnings |
Earnings estimates, history, and surprise data | Yes |
| GET | /api/v1/insights/{ticker}/recommendation |
Buy/Hold/Sell signal combining technical + sentiment | Yes |
Total: 16 authenticated endpoints across 4 route groups.
quantlens-ai/
├── backend/
│ ├── app/
│ │ ├── main.py # FastAPI entry point, CORS, table creation
│ │ ├── core/
│ │ │ ├── config.py # Pydantic Settings (SECRET_KEY, DB URL, etc.)
│ │ │ ├── database.py # SQLAlchemy engine + session factory
│ │ │ └── security.py # JWT creation/validation + password hashing
│ │ ├── models/
│ │ │ ├── user.py # User ORM (id, email, hashed_password, ...)
│ │ │ ├── watchlist.py # Watchlist ORM (user_id, ticker, cached fields)
│ │ │ └── sentiment_cache.py # SentimentCache ORM (ticker, score, label)
│ │ ├── schemas/
│ │ │ ├── user.py # UserCreate, Token, TokenPayload
│ │ │ ├── stock.py # 12 Pydantic models (Overview, Technical, etc.)
│ │ │ └── watchlist.py # WatchlistCreate, WatchlistResponse
│ │ ├── api/
│ │ │ ├── router.py # Master router aggregating all v1 routes
│ │ │ └── v1/
│ │ │ ├── auth.py # Register, login, current user
│ │ │ ├── stocks.py # Search, overview, history, news, technical
│ │ │ ├── watchlist.py # CRUD + refresh
│ │ │ └── insights.py # Sentiment, research, earnings, recommendation
│ │ └── services/
│ │ ├── market_data.py # MarketDataService (1,400+ lines, all data logic)
│ │ └── sentiment.py # SentimentService (lexicon + aggregate scoring)
│ ├── alembic/ # DB migration scripts
│ ├── requirements.txt
│ └── .env.example
│
├── frontend/
│ ├── src/
│ │ ├── main.jsx # React entry point
│ │ ├── App.jsx # Hash-based router + auth gate
│ │ ├── components/
│ │ │ ├── Layout.jsx # Sidebar + header shell
│ │ │ ├── GlobalSearch.jsx # Debounced ticker search
│ │ │ ├── StockChart.jsx # Recharts AreaChart with period selector
│ │ │ ├── StockOverview.jsx # Stats panel + company info
│ │ │ ├── SentimentCards.jsx # NLP gauge + scored headlines
│ │ │ └── MetricTooltip.jsx # Hover tooltip
│ │ ├── pages/
│ │ │ ├── Login.jsx # Auth form with demo credentials
│ │ │ ├── Register.jsx # Registration with password strength
│ │ │ ├── Dashboard.jsx # Recent searches, watchlist aggregate
│ │ │ ├── StockDetail.jsx # Full detail: chart, news, sentiment, research
│ │ │ ├── Watchlist.jsx # Sortable table with alerts
│ │ │ ├── Compare.jsx # Side-by-side stock comparison
│ │ │ └── Screener.jsx # AI screener with filters + presets
│ │ ├── context/
│ │ │ ├── AuthContext.jsx # User state, login/logout, token management
│ │ │ └── WatchlistContext.jsx # Watchlist CRUD operations
│ │ ├── services/
│ │ │ └── api.js # HTTP client with auth token injection
│ │ ├── utils/
│ │ │ ├── format.js # Market cap formatter (T/B/M)
│ │ │ └── performance.js # Return computation helpers
│ │ └── index.css # Tailwind + glassmorphism theme
│ ├── vercel.json
│ ├── vite.config.js
│ ├── tailwind.config.js
│ └── package.json
│
├── database/
│ ├── schema.sql # Users table DDL
│ └── seed.sql # Demo user insert
│
└── docs/
├── architecture.md # System design document
└── setup.md # Local development guide
- Python 3.11+
- Node.js 20+
- SQLite (default, no external database required)
git clone https://github.com/yourusername/quantlens-ai.git
cd quantlens-ai/backend
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
# Configure environment
cp .env.example .env
# Edit .env: set SECRET_KEY (required), DATABASE_URL (optional), FINNHUB_API_KEY (optional)
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000cd ../frontend
npm install
# Configure API URL (default points to production)
# Edit .env.production or use VITE_API_URL env var
npm run devThe seed.sql creates a demo user automatically. If using a fresh database:
INSERT INTO users (email, hashed_password, full_name, is_active)
VALUES (
'demo@quantlens.ai',
'$2b$12$...bcrypt_hash...',
'Demo User',
1
);- Connected to the GitHub repository
- Auto-deploys on push to main branch
- Build:
npm run build(outputs todist/) - Production API URL configured via
VITE_API_URLin.env.production
- Web Service running
uvicorn app.main:app --host 0.0.0.0 --port $PORT - Required env vars:
SECRET_KEY,DATABASE_URL(SQLite by default) - Optional:
FINNHUB_API_KEY,BACKEND_CORS_ORIGINS - 512 MB RAM (free tier)
- Database: SQLite
Yahoo Finance rate-limits cloud IPs aggressively, especially for yfinance.Ticker.info and crumb-authenticated endpoints. The solution uses:
- A three-tier fallback (Finnhub → Yahoo Direct HTTP → yfinance) where each tier has different rate-limit characteristics
- TTLCache with 15-minute expiry to cache overview, news, earnings, and technical data
- Rate-limit-specific exception handlers that return cached data when available
- Graceful degradation: if news rate-limits, cached news or empty list returned with HTTP 200
yfinance returns malformed rows with Close=NaN on the last trading day for NSE-traded symbols (.NS). Fixed by filtering df[df["Close"].notna()] before all technical calculations.
Finnhub's free tier returns null peTTM, epsTTM, and weburl for Indian stocks. Solved via a cascading backfill strategy:
- Try
yfinance.fast_info(low rate-limit risk, works on Render) - Fall back to Finnhub metric + earnings endpoints
- Yahoo search API for sector/industry (no crumb required)
FinBERT (transformers + torch) exceeded the 512 MB free-tier limit on Render, causing OOM crashes during deployment. Replaced with VADER Sentiment (270 KB install, no model download), which fits comfortably within the memory constraint while providing adequate financial sentiment classification.
Three-layer cache strategy to minimize API calls:
- TTLCache (in-memory): 5 caches, 15-minute TTL, for overview/news/earnings/technicals
- Database cache: Sentiment results with 24-hour freshness, reusable across sessions
- localStorage (browser): Recent searches and last viewed stocks persist across page reloads
- WebSocket streaming: Push real-time price updates to the frontend without polling
- Portfolio tracking: Add cost basis, position sizing, and P&L calculations
- Backtesting engine: Allow users to test strategies against historical data
- Screening enhancements: Integrate technical filters (RSI ranges, crossover signals)
- Alert system: Email or push notifications when price targets or technical conditions are met
- Multiple watchlist groups: Organize tickers by sector, strategy, or risk level
- AI-generated summaries: Use LLM integration (via API) for narrative research reports
- Designed and deployed a full-stack financial research platform serving 16 REST API endpoints, using FastAPI, React 19, SQLite, and Vercel/Render deployment
- Implemented a resilient three-tier data sourcing architecture (Finnhub → Yahoo Direct → yfinance) with graceful degradation under API rate limits, ensuring 100% endpoint availability
- Built an in-memory TTL caching layer (cachetools) reducing redundant external API calls by up to 90% on repeated requests within 15-minute windows
- Developed a server-side technical analysis engine computing RSI, MACD, Bollinger Bands, EMA/SMA, and ATR from daily OHLCV data using NumPy/Pandas
- Engineered a weighted recommendation system combining technical indicators (70%) with VADER-based news sentiment analysis (30%) to produce actionable BUY/HOLD/SELL signals
- Managed sensitive credential configuration (JWT secrets, API keys, database URLs) through environment variables with Pydantic Settings validation and secure defaults
- Implemented JWT authentication with bcrypt password hashing, CORS middleware, and role-based API access protection across all endpoints
Soham Mangroliya
Aspiring Data Scientist | AI/ML Engineer
Passionate about Machine Learning, Financial Analytics, NLP, and Data Engineering.
-
GitHub: https://github.com/soham-1801







