A comprehensive stock analysis and digest generation system powered by Tavily's Research endpoint. The system uses schema-driven research to gather grounded web data, fill structured reports, and return verified sources.
cd backend
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
uvicorn app:app --reload --host 0.0.0.0 --port 8080cd UI
npm install
npm run devCreate a .env file in the backend directory:
TAVILY_API_KEY=your_tavily_api_key
OPENAI_API_KEY=your_openai_api_key
The core of this system is Tavily's Research endpoint, which provides schema-driven, grounded research. Here's how it works:
We define a Pydantic model (StockReport) that describes the structure we want:
class StockReport(BaseModel):
company_name: str
summary: str
current_performance: str
key_insights: List[str]
recommendation: str
risk_assessment: str
price_outlook: str
market_cap: Optional[float]
pe_ratio: Optional[float]The get_stock_report_schema() function converts this to a JSON schema that Tavily understands.
response = tavily_client.research(
input=RESEARCH_PROMPT.format(ticker=ticker, date=current_date),
output_schema=get_stock_report_schema(),
model="mini" # or "pro" for deeper research
)Tavily's Research endpoint:
- Searches multiple authoritative sources (Reuters, Bloomberg, Yahoo Finance, etc.)
- Extracts relevant information matching your schema fields
- Grounds all data in real web sources
- Returns structured content that fills your schema
The response contains:
content: Structured data matching your schema (company name, summary, insights, etc.)sources: List of sources with URLs, titles, domains, published dates, and relevance scores
result = response["content"] # Your filled schema
sources = response["sources"] # Grounded sources for verification
report = StockReport(
ticker=ticker,
company_name=result.get("company_name"),
summary=result.get("summary"),
key_insights=result.get("key_insights"),
# ... etc
sources=[Source(url=s["url"], title=s["title"], ...) for s in sources]
)This approach ensures:
- β Structured output - Data comes back in your exact format
- β Grounded facts - All information is sourced from real web data
- β Source transparency - Every claim can be traced to its origin
- β No hallucinations - Content is extracted, not generated
stock-portfolio-researcher/
βββ backend/
β βββ agent.py # LangGraph agent with Tavily Research
β βββ app.py # FastAPI server
β βββ models.py # Pydantic models & schema generation
β βββ prompts.py # Research prompts
β βββ requirements.txt # Python dependencies
βββ UI/
β βββ src/
β β βββ components/ # React components
β β βββ lib/utils.ts # PDF generation
β β βββ types/ # TypeScript types
β βββ package.json
βββ README.md
The system uses LangGraph to orchestrate a parallel workflow:
βββββββββββββββββββ
β START β
ββββββββββ¬βββββββββ
β
ββββββββββββββββ΄βββββββββββββββ
βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ
β StockResearch β β StockMetrics β
β (Tavily Researchβ β (Tavily Search β
β Endpoint) β β topic=finance) β
ββββββββββ¬βββββββββ ββββββββββ¬βββββββββ
β β
ββββββββββββββββ¬ββββββββββββββββ
βΌ
βββββββββββββββββββ
β MergeMetrics β
β (Combine data) β
ββββββββββ¬βββββββββ
βΌ
βββββββββββββββββββ
β END β
βββββββββββββββββββ
Uses Tavily's Research endpoint for deep, schema-driven analysis:
def _research_ticker(self, ticker: str) -> tuple[str, StockReport]:
response = self.tavily_client.research(
input=RESEARCH_PROMPT.format(ticker=ticker, date=self.current_date),
output_schema=get_stock_report_schema(),
model=self.research_model
)
response = self._poll_research(response["request_id"])
# Extract structured content
result = response["content"]
# Extract sources for transparency
sources = [Source(...) for src in response.get("sources", [])]
return ticker, StockReport(
company_name=result.get("company_name"),
summary=result.get("summary"),
sources=sources,
# ...
)The research prompt asks for:
- Current stock price and performance
- Market cap and financial metrics
- Earnings results and guidance
- Recent news and developments
- Analyst ratings and price targets
- Risks and opportunities
- Investment recommendation
Uses Tavily Search with topic="finance" for real-time financial data:
def _fetch_metrics(self, ticker: str) -> tuple[str, TavilyMetrics]:
search_results = self.tavily_client.search(
query=f"Tell me about the stock {ticker}",
topic="finance", # Finance-specific search
search_depth="basic",
max_results=5,
)
# Extract from Yahoo Finance results
metrics = self.openai_llm.with_structured_output(TavilyMetrics).invoke(
METRICS_PROMPT.format(ticker=ticker, content=content)
)
return ticker, metricsExtracts metrics like:
- Sharpe Ratio
- Annualized CAGR
- Max Drawdown
- 2-Year Price High/Low
- Trading Volume
- Current Price
Combines research reports with financial metrics:
def merge_metrics_node(self, state: State) -> Dict:
for ticker, report in state["structured_reports"].reports.items():
if ticker in state["tavily_metrics"]:
report.tavily_metrics = state["tavily_metrics"][ticker]
return {"structured_reports": state["structured_reports"]}Both StockResearch and StockMetrics run in parallel using ThreadPoolExecutor:
def _run_parallel(self, tickers, func, event_name, fallback):
with ThreadPoolExecutor(max_workers=min(len(tickers), 4)) as executor:
futures = {executor.submit(func, t): t for t in tickers}
for future in as_completed(futures):
ticker, result = future.result()
results[ticker] = result
return resultsSince Tavily Research is asynchronous, the agent polls until completion:
def _poll_research(self, request_id: str, poll_interval: int = 10) -> dict:
response = self.tavily_client.get_research(request_id)
while response["status"] not in ("completed", "failed"):
time.sleep(poll_interval)
response = self.tavily_client.get_research(request_id)
return responseComplete analysis for a single stock:
ticker,company_name- Stock identificationsummary- Executive summary of findingscurrent_performance- Recent performance analysiskey_insights- Bullet points of important findingsrecommendation- Buy/hold/sell with reasoningrisk_assessment- Key risksprice_outlook- Future price expectationssources- Grounded sources from Tavily
Real-time financial data:
sharpe_ratio,annualized_cagr,max_drawdowncurrent_price,latest_open_pricetwo_year_price_high,two_year_price_lowtrading_volume
Attribution for transparency:
url,title,domainpublished_date,score
Generate a complete stock digest for multiple tickers.
Request:
{
"tickers": ["AAPL", "GOOGL", "MSFT"]
}Response:
{
"reports": {
"AAPL": {
"ticker": "AAPL",
"company_name": "Apple Inc.",
"summary": "...",
"key_insights": ["...", "..."],
"sources": [{"url": "...", "title": "..."}],
"tavily_metrics": {"sharpe_ratio": 1.2, ...}
}
},
"generated_at": "2026-01-08T..."
}- Ticker Input - Enter up to 5 stock symbols
- Report Generation - Triggers backend research via API
- Portfolio Overview - View detailed reports for each ticker
- Source Verification - See all sources used for each analysis
- PDF Export - Download professional reports
- TickerInput - Smart input with validation and suggestions
- DailyDigestReport - Tabbed report viewer with all analysis sections
- PDF Export - Professional formatted reports with all data
- Summary - Executive overview of the stock
- Financial Metrics - Key numbers in a clean table
- Key Insights - Bullet points of important findings
- Current Performance - Recent price and market performance
- Risk Assessment - Key risks to consider
- Price Outlook - Future price expectations
- Recommendation - Clear buy/hold/sell with reasoning
- Sources - All sources used for the analysis
- Tavily Research API - Schema-driven, grounded web research
- Tavily Search API - Real-time financial data with topic="finance"
- LangGraph - Workflow orchestration with parallel execution
- OpenAI GPT - Structured extraction from search results
- FastAPI - Async Python backend
- React + TypeScript - Modern frontend
- jsPDF - Client-side PDF generation
