Build a financial research agent
Researching a stock means juggling a dozen tabs: investor relations pages, analyst notes, earnings coverage, and a notepad to stitch it into a bull case, a bear case, and the metrics that matter. This guide builds a Python agent that runs the whole loop automatically. It chains all four Superhighway endpoints — /research for the company's business model and competitive position, /search for analyst reports and earnings previews, /scrape for metrics from financial pages, and /news for earnings, guidance, and analyst actions — then uses an LLM to emit a structured research note as JSON.
1. What you'll build
A Python financial research agent that takes a stock ticker or company name and produces a structured investment brief:
- Researches the company's business model, revenue segments, and competitive position
- Finds recent analyst reports and earnings previews
- Scrapes investor relations and financial news pages for key metrics
- Pulls recent market news — earnings, guidance changes, analyst upgrades and downgrades
- Uses an LLM to generate a structured research note with bull case, bear case, and risks as JSON
2. Setup
pip install openai requests python-dotenv
Create a .env file with your two keys:
SUPERHIGHWAY_API_KEY=your_key_here
OPENAI_API_KEY=your_key_here
3. Research the company
Start with /research, which pulls multi-source background into one synthesis — what the company does, how it makes money, its revenue segments, and where it sits competitively. This is the context that grounds every later step.
import requests, os, json
SUPERHIGHWAY_KEY = os.getenv("SUPERHIGHWAY_API_KEY")
BASE = "https://superhighway.walls.sh"
def research_company(company: str, ticker: str) -> str:
"""Deep company research: business model, revenue streams, competitive moat."""
r = requests.get(
f"{BASE}/research",
params={
"q": f"{company} {ticker} business model revenue segments competitive position strategy",
"pages": 5
},
headers={"Authorization": f"Bearer {SUPERHIGHWAY_KEY}"}
)
data = r.json()
return data.get("synthesis", data.get("markdown", ""))[:3000]
4. Find analyst reports and earnings info
/search turns the ticker into a list of analyst notes, earnings previews, and valuation pieces — the candidate pages the agent will scrape for hard numbers.
def find_analyst_coverage(company: str, ticker: str) -> list[dict]:
"""Search for analyst reports, earnings previews, and financial analysis."""
r = requests.get(
f"{BASE}/search",
params={
"q": f"{ticker} {company} analyst report earnings forecast valuation",
"limit": 6
},
headers={"Authorization": f"Bearer {SUPERHIGHWAY_KEY}"}
)
return r.json().get("results", [])
5. Scrape financial data pages
/scrape turns each candidate URL into clean, LLM-ready markdown. This is where the agent pulls actual metrics — revenue, growth rates, margins — out of investor relations and financial news pages.
def scrape_financial_page(url: str) -> dict:
"""Scrape a financial news or IR page for metrics and analysis."""
r = requests.get(
f"{BASE}/scrape",
params={"url": url},
headers={"Authorization": f"Bearer {SUPERHIGHWAY_KEY}"}
)
data = r.json()
return {
"url": url,
"title": data.get("title", ""),
"content": data.get("markdown", "")[:2500],
}
6. Get recent market news
/news surfaces what just happened — earnings beats and misses, guidance changes, and analyst upgrades or downgrades. This is the time-sensitive layer that a static research note misses.
def get_market_news(company: str, ticker: str) -> list[dict]:
"""Get recent earnings, guidance, analyst upgrades/downgrades."""
r = requests.get(
f"{BASE}/news",
params={
"q": f"{ticker} {company} earnings revenue guidance analyst",
"count": 6
},
headers={"Authorization": f"Bearer {SUPERHIGHWAY_KEY}"}
)
return r.json().get("articles", [])
7. Generate the investment brief with an LLM
Now hand everything to the LLM. The system prompt forbids inventing metrics and forbids buy/sell recommendations — the agent summarizes what's in the sources, it does not give advice. The output is structured JSON so it slots straight into a report or a dashboard.
from openai import OpenAI
llm = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def generate_brief(
company: str,
ticker: str,
company_research: str,
analyst_pages: list[dict],
news: list[dict],
focus: str = "long-term investor"
) -> dict | None:
"""Generate a structured investment research note."""
analyst_text = "\n".join(
f"- {p['title']}: {p['content'][:400]}"
for p in analyst_pages[:4]
if p.get("content")
)
news_text = "\n".join(
f"- {n.get('title', '')} ({n.get('source', '')})"
for n in news[:6]
)
response = llm.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": f"""You are a financial analyst writing a research note for a {focus}.
Be specific and factual. Only use information from the provided sources.
Do not invent financial metrics — if a number isn't in the sources, say 'not found in sources.'
Do not make buy/sell recommendations."""
},
{
"role": "user",
"content": f"""Write a research brief for {company} ({ticker}).
Company Research:
{company_research[:2000]}
Analyst Coverage & Reports:
{analyst_text}
Recent News:
{news_text}
Return JSON with:
- company: string
- ticker: string
- business_summary: string (2-3 sentences on what they do and how they make money)
- key_metrics: list of strings (revenue, growth rate, margins — only what's in sources, labeled "not found" if absent)
- bull_case: list of 3 strings (strongest arguments for the company)
- bear_case: list of 3 strings (key risks and challenges)
- recent_developments: list of 2-3 strings (from the news — earnings beats/misses, guidance changes, analyst actions)
- investment_thesis: string (1 paragraph — the core thesis a long-term investor would hold)
- key_risks: list of 2-3 strings
- data_quality: "high" | "medium" | "low" (how much source material was found)"""
}
],
response_format={"type": "json_object"}
)
try:
return json.loads(response.choices[0].message.content)
except (json.JSONDecodeError, KeyError):
return None
8. The full research pipeline
Wire the steps together: research the company, find coverage, scrape the top pages, pull news, then generate the brief. The focus argument lets you slant the note for a long-term investor, a value investor, or a trader.
def research_stock(
company: str,
ticker: str,
focus: str = "long-term investor",
max_pages: int = 5
) -> dict | None:
"""
Run the full research pipeline for a company.
focus: "long-term investor" | "growth investor" | "value investor" | "trader"
"""
print(f"Researching: {company} ({ticker})")
# Step 1: Company background
print("Researching company background...")
company_research = research_company(company, ticker)
# Step 2: Find analyst coverage
print("Finding analyst coverage...")
results = find_analyst_coverage(company, ticker)
# Step 3: Scrape key pages
print(f"Scraping {min(len(results), max_pages)} financial pages...")
analyst_pages = []
for result in results[:max_pages]:
page = scrape_financial_page(result["url"])
if page["content"]:
analyst_pages.append(page)
# Step 4: Recent news
print("Pulling market news...")
news = get_market_news(company, ticker)
# Step 5: Generate brief
print("Generating investment brief...")
return generate_brief(company, ticker, company_research, analyst_pages, news, focus)
def print_brief(brief: dict):
if not brief:
print("Could not generate brief.")
return
print(f"\n{'='*60}")
print(f"{brief.get('company', 'Company')} ({brief.get('ticker', '?')}) — Research Note")
print(f"Data quality: {brief.get('data_quality', '?').upper()}")
print(f"{'='*60}")
print(f"\n{brief.get('business_summary', '')}\n")
metrics = brief.get("key_metrics", [])
if metrics:
print("Key Metrics:")
for m in metrics:
print(f" {m}")
print("\nBull Case:")
for bull in brief.get("bull_case", []):
print(f" + {bull}")
print("\nBear Case:")
for bear in brief.get("bear_case", []):
print(f" - {bear}")
developments = brief.get("recent_developments", [])
if developments:
print("\nRecent Developments:")
for d in developments:
print(f" * {d}")
print(f"\nInvestment Thesis:\n{brief.get('investment_thesis', '')}")
risks = brief.get("key_risks", [])
if risks:
print("\nKey Risks:")
for risk in risks:
print(f" ! {risk}")
if __name__ == "__main__":
import sys
# Usage: python agent.py "Apple" AAPL
if len(sys.argv) >= 3:
company_name = sys.argv[1]
ticker_symbol = sys.argv[2]
investor_focus = sys.argv[3] if len(sys.argv) > 3 else "long-term investor"
else:
company_name = "Microsoft"
ticker_symbol = "MSFT"
investor_focus = "long-term investor"
brief = research_stock(company_name, ticker_symbol, focus=investor_focus)
if brief:
print_brief(brief)
9. What you can research
- Single-stock deep dive — run against any public company for a quick research note before earnings.
- Earnings preview — run the day before earnings; the news feed surfaces analyst estimates and whisper numbers.
- Sector scan — run
research_stock()across 5-10 companies in a sector and compare bull and bear cases side by side. - Portfolio monitoring — set up a weekly cron job that generates fresh briefs for each position and emails the diff.
10. Extending the agent
- Earnings calendar — combine a
/news?q=earnings this weekcall to build a weekly earnings radar before running individual briefs. - Comparison report — run
research_stock()for two competitors and use a second LLM call to compare them head-to-head. - Watchlist automation — store briefs in a JSON file; on the next run, diff the
recent_developmentsto catch what changed. - Sentiment tracking — pass the
newslist to a sentiment classifier to build a week-over-week sentiment trend.
11. Important disclaimer
This agent retrieves and summarizes publicly available information. It does not provide investment advice. Always verify key metrics against official filings (10-K, 10-Q) before making investment decisions.
12. Getting your API key
Grab a free Superhighway key at /pricing (1,000 calls/month, no credit card). For an agent that provisions its own access, skip the key entirely with x402: it pays $0.002 per call in USDC on Base — no signup, no key management. See the x402 pay-per-call guide for the wallet setup.
For related builds, the competitor analysis agent uses the same four-endpoint pattern on your market rivals, and the content research agent covers the deep-research synthesis pattern in more depth.