A production-grade AI-powered stock research platform built with FastAPI, LangGraph, GPT-4o, and a fully responsive dark UI. Analyze any stock ticker, get deterministic buy/sell/hold decisions backed by a normalized multi-factor scoring engine, real-time technical indicators, fundamental data, news sentiment, and an AI chat assistant — all deployed on AWS EC2.
👉 fintel.rajkumarai.dev — deployed on AWS EC2
Portfolio: rajkumarai.dev
Most AI financial tools let the LLM decide whether to buy or sell. This system does not. The investment decision is made entirely by a deterministic scoring engine — GPT-4o only explains the decision in plain English. This eliminates hallucination risk from the most critical part of the pipeline.
- Normalized scoring — scores are computed as
(actual / max_possible) × 100, so stocks with missing data are judged fairly on what's available rather than penalized - Three-component model — Technical (0–25), Fundamental (0–40), Sentiment (0–15), minus a Volatility penalty (0–10)
- Decision thresholds — BUY ≥ 70%, HOLD 40–69%, SELL < 40%, operating on the normalized score
- Conflict detection — when technical and fundamental signals sharply disagree (variance > 0.15) and the score sits in the 35–55 borderline zone, the system overrides to HOLD to avoid a false signal
- Time horizon adaptation — weight profiles for
short_term(amplifies technicals),long_term(amplifies fundamentals), anddefault(balanced) - Confidence scoring — 5-factor model: data completeness, missing data penalty, signal agreement, signal consistency bonus, volatility/uncertainty penalty
- Live market data — real OHLCV price history via Alpha Vantage TIME_SERIES_DAILY (last 100 trading days)
- Technical indicators — MA50, MA200, RSI, volatility, 1Y price change, golden cross / death cross detection
- Fundamental data — revenue growth, profit margin, P/E ratio, debt-to-equity, EPS via Alpha Vantage
- News sentiment — real-time news via Tavily API, scored by GPT-4o into positive / neutral / negative
- Multi-ticker ranking — submit a list of tickers and receive a ranked table sorted by normalized score
- Allocation percentages — portfolio allocation is distributed proportionally among BUY and HOLD positions; SELL and INSUFFICIENT_DATA positions receive 0%
- Full analysis context — after running an analysis, the chat assistant receives the complete result: recommendation, normalized score, all component scores, fundamentals (P/E, EPS, revenue growth, profit margin, FCF), reasoning, news summary, risk assessment, key factors, and data gaps
- Pronoun resolution — when you ask "why is it a buy?" or "compare it with MSFT", the backend automatically resolves "it" and "this stock" to the currently analyzed ticker using keyword detection
- Dynamic suggestion chips — chips update after every analysis based on the result: "Why BUY/SELL/HOLD?", "Signal conflict?" (when technical and fundamental signals disagree), "Compare {TICKER}", "Key risks", and "Data gaps?" (when incomplete data was detected)
- Smart compare pre-fill — clicking "Compare" sets the input to
"Compare {TICKER} with "and focuses the cursor, prompting you to type the second ticker rather than auto-sending a hardcoded pair - Company name display — the chat panel header shows the full company name alongside the ticker symbol after analysis completes
- Redis caching — analysis results cached for 15 minutes to minimize API calls
- Session memory — chat history persisted per session in Redis
- Fully mobile responsive — tab-based layout on mobile with Analysis and Chat panels switchable via bottom tab bar
| Layer | Technology |
|---|---|
| Backend | FastAPI + Uvicorn |
| AI Orchestration | LangGraph + LangChain |
| LLM | OpenAI GPT-4o |
| Market Data | Alpha Vantage TIME_SERIES_DAILY |
| Fundamental Data | Alpha Vantage API |
| News | Tavily Search API |
| Caching & Sessions | Redis |
| Data Validation | Pydantic v2 |
| Logging | Structlog (structured JSON) |
| Frontend | Vanilla JS + Chart.js |
| Containerization | Docker + Docker Compose |
| Cloud | AWS EC2 (Ubuntu 24.04) |
The orchestrator is a compiled LangGraph StateGraph with 9 nodes. Market data is fetched first (sequential), then fundamental data and news are fetched in parallel (fan-out), before converging back for the scoring and decision nodes (fan-in).
User Request
│
▼
FastAPI REST API
│
▼
LangGraph StateGraph (orchestrator.py)
│
├─ dispatch → validates input, sets initial state
│
├─ fetch_market_data → Alpha Vantage TIME_SERIES_DAILY (OHLCV)
│
├─ [parallel fan-out] ───────────────────────────────────┐
│ fetch_fundamental_data → Alpha Vantage OVERVIEW │
│ fetch_news → Tavily + GPT-4o sentiment │
└────────────────────────────────────────────────────────┘
│ [fan-in: both branches merge into compute_indicators]
▼
compute_indicators → MA50, MA200, RSI, volatility, trend
│
[conditional edge: abort to END if market data failed]
│
compute_scores → deterministic multi-factor scoring
│
make_decision → BUY / HOLD / SELL (no LLM)
│
generate_explanation → GPT-4o writes plain English summary
│
apply_guardrails → INSUFFICIENT_DATA override if needed
│
▼
Redis Cache (TTL: 15 min) → JSON Response → Frontend
State management: AgentState is a TypedDict with Annotated reducers — operator.add for list fields (errors, tool_calls_log) and a last-writer-wins lambda for current_step, preventing conflicts when parallel branches update state simultaneously.
Technical Score (0–25) ← MA cross, RSI, price momentum, volatility
Fundamental Score (0–40) ← revenue growth, profit margin, P/E, D/E, EPS
Sentiment Score (0–15) ← news tone (positive=10, neutral=5, negative=0)
Volatility Penalty(0–10) ← subtracted from total
normalized_score = (total / max_possible) × 100
BUY ≥ 70%
HOLD 40–69% (or conflict override when signals disagree in 35–55 range)
SELL < 40%
financial-research-agent/
├── app/
│ ├── agents/
│ │ ├── scoring_engine.py # Deterministic multi-factor scoring
│ │ ├── portfolio_engine.py # Multi-ticker ranking + allocation
│ │ ├── decision_agent.py # GPT-4o explanation generator
│ │ └── orchestrator.py # LangGraph pipeline
│ ├── api/
│ │ ├── middleware.py # CORS + request logging
│ │ └── routes/
│ │ ├── analysis.py # /analyze, /chat, /portfolio/rank
│ │ └── health.py # /health/live and /health/ready
│ ├── models/
│ │ ├── agent_state.py # LangGraph state (TypedDict)
│ │ ├── requests.py # Pydantic request models
│ │ └── responses.py # Pydantic response models
│ ├── services/
│ │ ├── cache_service.py # Redis async wrapper
│ │ ├── session_service.py # Chat history in Redis
│ │ └── validation_service.py # Data quality checks
│ ├── tools/
│ │ ├── yfinance_tool.py # Market data fetcher (Alpha Vantage)
│ │ └── tavily_tool.py # News fetcher
│ ├── utils/
│ │ ├── config.py # Pydantic BaseSettings
│ │ └── logger.py # Structlog setup
│ └── main.py # FastAPI app + lifespan
├── frontend/
│ └── index.html # Single-page responsive UI
├── Dockerfile
├── docker-compose.yml
└── requirements.txt
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/analyze |
Full AI analysis for a single stock ticker |
| POST | /api/v1/chat |
Conversational AI assistant |
| POST | /api/v1/portfolio/rank |
Rank and allocate across multiple tickers |
| GET | /health/live |
Liveness check |
| GET | /health/ready |
Readiness check (Redis connection) |
curl -X POST http://localhost:8000/api/v1/analyze \
-H "Content-Type: application/json" \
-d '{"ticker": "AAPL", "include_news": true, "time_horizon": "long_term"}'Response fields include: recommendation, normalized_score, confidence_score, conflict_detected, missing_components, time_horizon_used, score_breakdown, technical_indicators, fundamental_data, news_summary, explanation
curl -X POST http://localhost:8000/api/v1/portfolio/rank \
-H "Content-Type: application/json" \
-d '{"tickers": ["AAPL", "MSFT", "TSLA", "NVDA"], "time_horizon": "default"}'curl -X POST http://localhost:8000/api/v1/chat \
-H "Content-Type: application/json" \
-d '{"message": "Why is AAPL rated HOLD?", "session_id": "session-abc123", "ticker": "AAPL"}'- Docker and Docker Compose
- OpenAI API key — platform.openai.com
- Tavily API key — tavily.com
- Alpha Vantage API key — alphavantage.co (free tier available)
git clone https://github.com/Rajkumar2002-Rk/financial-research-agent.git
cd financial-research-agentcp .env.example .envEdit .env and fill in your API keys:
OPENAI_API_KEY=your_openai_key
TAVILY_API_KEY=your_tavily_key
ALPHA_VANTAGE_API_KEY=your_alphavantage_key
REDIS_URL=redis://redis:6379
OPENAI_MODEL=gpt-4odocker compose up --buildOpen http://localhost:8000 in your browser.
- Launch an EC2 instance (Ubuntu 24.04, t2.micro or larger)
- Open port
8000in your security group - SSH in and install Docker:
sudo apt-get update && sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update && sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo usermod -aG docker ubuntu- Clone, configure, and run:
git clone https://github.com/Rajkumar2002-Rk/financial-research-agent.git
cd financial-research-agent
nano .env
docker compose up --build -d- Access at
http://<your-ec2-public-ip>:8000
| Variable | Required | Description |
|---|---|---|
OPENAI_API_KEY |
Yes | OpenAI API key |
TAVILY_API_KEY |
Yes | Tavily Search API key |
ALPHA_VANTAGE_API_KEY |
Yes | Alpha Vantage API key (fundamental data) |
REDIS_URL |
Yes | Redis connection URL |
OPENAI_MODEL |
No | Model name (default: gpt-4o) |
REDIS_CACHE_TTL |
No | Cache TTL in seconds (default: 900) |
REDIS_SESSION_TTL |
No | Session TTL in seconds (default: 3600) |
- Alpha Vantage free tier is limited to 25 API calls/day. Fundamental data may be unavailable for less common tickers.
- INSUFFICIENT_DATA is returned when confidence falls below threshold (e.g., extreme volatility + negative sentiment with no fundamentals). This is intentional — the system refuses to guess.
- Not financial advice. This is a personal portfolio project for demonstrating AI system design. Do not use it to make real investment decisions.
MIT