I don’t need another portfolio dashboard.

I already know where to find stock prices, charts, P/E ratios, earnings dates, and financial statements.

The problem isn’t access to information.

It’s knowing when something actually matters.

If Tesla drops 2%, I probably don’t need an alert.

If Tesla drops 8% after disappointing earnings, declining margins, and negative company-specific news, that’s different.

So I decided to build something more useful:

An AI agent that monitors my portfolio and tells me when something meaningful changes.

For this experiment, I’ll monitor five well-known growth stocks:

  • Palantir (PLTR)
  • Tesla (TSLA)
  • Broadcom (AVGO)
  • Reddit (RDDT)
  • NVIDIA (NVDA)

The goal isn’t to predict tomorrow’s stock price.

The goal is to answer a much more useful question:

Did anything happen today that deserves my attention?

Let’s build it with Python, AI, and real financial data from EODHD.

What the Agent Will Monitor

I decided to focus on five events:

  1. Unusual price movements
  2. Earnings and earnings surprises
  3. Fundamental changes
  4. Important news and sentiment
  5. Corporate events

The architecture is simple:

Portfolio
   ↓
EODHD APIs
   ↓
Python
   ↓
Detect Changes
   ↓
Build Context
   ↓
AI Relevance Filter
   ↓
IGNORE / REVIEW / IMPORTANT
   ↓
Daily Portfolio Report

There’s an important distinction here.

Python detects what changed.

AI helps explain whether the combination of those changes deserves attention.

The AI model isn’t allowed to decide whether I should buy or sell the stock.

Setting Up the Project

First, install the dependencies:

pip install requests python-dotenv anthropic

Then create a .env file:

EODHD_API_KEY=your_eodhd_api_key
ANTHROPIC_API_KEY=your_anthropic_api_key

Never hardcode production API keys directly into your Python script.

Now we can define our portfolio:

PORTFOLIO = {
    "PLTR.US": "Palantir",
    "TSLA.US": "Tesla",
    "AVGO.US": "Broadcom",
    "RDDT.US": "Reddit",
    "NVDA.US": "NVIDIA",
}

For the financial data layer, I’m using EODHD.

It gives us access to historical prices, fundamentals, earnings calendars, earnings trends, financial news, sentiment, dividends, and other corporate data from the same API ecosystem.

If you want to reproduce the project, you can get access here:

Get access to EODHD Financial APIs

Creating Our EODHD Client

Instead of scattering API requests throughout the project, I prefer creating one reusable function.

import os
import requests
from dotenv import load_dotenv
load_dotenv()
EODHD_API_KEY = os.getenv("EODHD_API_KEY")
BASE_URL = "https://eodhd.com/api"
def eodhd_get(endpoint, params=None):
    if not EODHD_API_KEY:
        raise ValueError("EODHD_API_KEY is missing")
    params = params or {}
    params["api_token"] = EODHD_API_KEY
    params["fmt"] = "json"
    response = requests.get(
        f"{BASE_URL}/{endpoint}",
        params=params,
        timeout=30,
    )
    response.raise_for_status()
    return response.json()

Now every request becomes much cleaner.

For example:

data = eodhd_get(
    "eod/TSLA.US",
    {
        "order": "d",
        "from": "2026-08-01",
    }
)

Instead of manually building URLs every time, eodhd_get() automatically adds authentication, requests JSON and checks for HTTP errors.

Now we can start building the actual monitoring system.

1. Detecting Unusual Price Movements

The first API we need is the EOD Historical Data API.

For Tesla, the underlying request looks like:

GET /api/eod/TSLA.US

We can request recent data directly from Python:

from datetime import date, timedelta
def get_recent_prices(ticker, days=10):
    start = date.today() - timedelta(days=days)
    return eodhd_get(
        f"eod/{ticker}",
        {
            "from": start.isoformat(),
            "order": "d",
        },
    )

Notice that I’m requesting descending order.

That means the most recent trading session should appear first.

Now we compare the two latest sessions.

def detect_price_event(ticker, threshold=5):
prices = get_recent_prices(ticker)
    if len(prices) < 2:
        return {
            "triggered": False,
            "reason": "Not enough price data",
        }
    latest = prices[0]
    previous = prices[1]
    latest_price = float(latest["adjusted_close"])
    previous_price = float(previous["adjusted_close"])
    change = (
        latest_price / previous_price - 1
    ) * 100
    return {
        "triggered": abs(change) >= threshold,
        "date": latest["date"],
        "price": latest_price,
        "previous_price": previous_price,
        "change_pct": round(change, 2),
    }

Now:

print(detect_price_event("TSLA.US"))

might produce something structurally similar to:

{
    "triggered": True,
    "date": "2026-08-14",
    "price": 400.0,
    "previous_price": 425.0,
    "change_pct": -5.88
}

The values above are illustrative.

The important thing is the logic.

A 5% movement does not mean “sell.”

It means:

Something unusual happened. Investigate.

2. Checking Earnings

Now things get more interesting.

EODHD provides a dedicated earnings calendar endpoint.

We can query several portfolio companies in one request:

def get_earnings(tickers):
return eodhd_get(
        "calendar/earnings",
        {
            "symbols": ",".join(tickers),
        },
    )

For our portfolio:

earnings = get_earnings(PORTFOLIO.keys())

The API can return fields including:

code
report_date
actual
estimate
difference
percent

That last field is particularly useful.

It represents the earnings surprise percentage.

See also  Laziest Ways to Make Money with AI (For Beginners)

So we can detect unusually large surprises.

def detect_earnings_events(data, threshold=10):
events = []
    for earning in data.get("earnings", []):
        surprise = earning.get("percent")
        if surprise is None:
            continue
        surprise = float(surprise)
        if abs(surprise) >= threshold:
            events.append({
                "ticker": earning["code"],
                "report_date": earning["report_date"],
                "actual": earning.get("actual"),
                "estimate": earning.get("estimate"),
                "surprise_pct": round(surprise, 2),
            })
    return events

Instead of:

Tesla reported earnings.

our system can identify:

TSLA
EPS surprise: -12.4%

That’s much more actionable information.

3. Monitoring Analyst Expectations

This is one of my favorite parts of the system.

EODHD also provides Earnings Trends data.

We can request:

def get_earnings_trends(tickers):
return eodhd_get(
        "calendar/trends",
        {
            "symbols": ",".join(tickers),
        },
    )

This endpoint can provide fields such as:

epsTrendCurrent
epsTrend7daysAgo
epsTrend30daysAgo
epsTrend60daysAgo
epsTrend90daysAgo
epsRevisionsUpLast30days
epsRevisionsDownLast30days

Now we can detect whether expectations are quietly changing before the next earnings release.

For example:

def calculate_estimate_change(current, previous):
if current is None or previous is None:
        return None
    current = float(current)
    previous = float(previous)
    if previous == 0:
        return None
    return (
        current / previous - 1
    ) * 100

Then:

change = calculate_estimate_change(
    trend["epsTrendCurrent"],
    trend["epsTrend30daysAgo"]
)

Imagine the result is:

EPS consensus 30 days ago: 1.25
EPS consensus today: 1.08
Change: -13.6%

The stock might not have moved much yet.

But expectations have.

That’s exactly the kind of change I want my monitor to detect.

4. Pulling Fundamental Data

Next, we retrieve fundamentals.

The real endpoint is:

GET /api/fundamentals/TSLA.US

Our Python function is:

def get_fundamentals(ticker):
return eodhd_get(
        f"fundamentals/{ticker}"
    )

Now:

tesla = get_fundamentals("TSLA.US")

returns a large JSON object containing multiple sections.

For this project, I don’t need everything.

I mainly want Highlights.

def extract_fundamental_snapshot(data):
highlights = data.get("Highlights", {})
    return {
        "revenue_growth_yoy":
            highlights.get("QuarterlyRevenueGrowthYOY"),
        "earnings_growth_yoy":
            highlights.get("QuarterlyEarningsGrowthYOY"),
        "operating_margin_ttm":
            highlights.get("OperatingMarginTTM"),
        "profit_margin":
            highlights.get("ProfitMargin"),
        "revenue_ttm":
            highlights.get("RevenueTTM"),
        "ebitda":
            highlights.get("EBITDA"),
    }

Then:

snapshot = extract_fundamental_snapshot(
    get_fundamentals("TSLA.US")
)
print(snapshot)

This gives us a compact representation of the business instead of passing a massive JSON document into the AI model.

That’s important.

Don’t send everything to the LLM just because you can.

Filter first.

Analyze second.

5. Detecting Fundamental Changes

There’s another important problem.

A single fundamental snapshot doesn’t tell us what changed.

For that, the production version of the agent should persist snapshots.

For example:

data/
    PLTR.US.json
    TSLA.US.json
    AVGO.US.json
    RDDT.US.json
    NVDA.US.json

Every time the agent runs, it compares today’s fundamental snapshot against the previous stored snapshot.

import json
from pathlib import Path
DATA_DIR = Path("data")
DATA_DIR.mkdir(exist_ok=True)
def load_previous_snapshot(ticker):
    file = DATA_DIR / f"{ticker}.json"
    if not file.exists():
        return None
    with open(file, "r") as f:
        return json.load(f)
def save_snapshot(ticker, snapshot):
    file = DATA_DIR / f"{ticker}.json"
    with open(file, "w") as f:
        json.dump(snapshot, f, indent=2)

Then we can compare metrics.

def percent_change(current, previous):
if current is None or previous is None:
        return None
    if previous == 0:
        return None
    return (
        (current - previous) / abs(previous)
    ) * 100

For example:

def compare_fundamentals(current, previous):
if previous is None:
        return {}
    changes = {}
    for metric in current:
        change = percent_change(
            current[metric],
            previous.get(metric)
        )
        if change is not None:
            changes[metric] = round(change, 2)
    return changes

This gives the agent memory.

Without persistence, the system knows:

Tesla’s operating margin is X.

With persistence, it can tell me:

Tesla’s operating margin changed materially since my previous snapshot.

That’s far more useful.

6. Monitoring Financial News

Now let’s check recent news.

EODHD provides a financial news endpoint:

GET /api/news

For an individual company:

def get_recent_news(ticker, limit=10):
return eodhd_get(
        "news",
        {
            "s": ticker,
            "limit": limit,
            "offset": 0,
        },
    )

Then:

news = get_recent_news("NVDA.US")

The important idea isn’t to dump 50 headlines into my report.

It’s to give the AI enough context to determine whether anything meaningful happened.

So I reduce the data first.

def prepare_news(news):
prepared = []
    for article in news[:10]:
        prepared.append({
            "date": article.get("date"),
            "title": article.get("title"),
            "content": (
                article.get("content", "")[:800]
            ),
            "sentiment": article.get("sentiment"),
        })
    return prepared

Again:

Filter first. AI second.

7. Adding Sentiment Data

We can also query EODHD’s sentiment endpoint.

def get_sentiment(tickers, start_date, end_date):
eturn eodhd_get(
        "sentiments",
        {
            "s": ",".join(tickers),
            "from": start_date,
            "to": end_date,
        },
    )

This gives us another signal that can complement individual news articles.

I wouldn’t use sentiment alone to make an investment decision.

But it can help the agent understand whether the information environment around a company has changed substantially.

8. Corporate Events

Corporate actions can also matter.

See also  Millionaire Explains: How to Invest for Beginners in 2026

For example:

def get_dividends(ticker, start_date):
return eodhd_get(
        f"div/{ticker}",
        {
            "from": start_date,
        },
    )

The same architecture can later be expanded to stock splits and other corporate events.

For growth stocks like Reddit or Palantir, dividends obviously aren’t the main signal.

But the point is to build a modular architecture.

Each new dataset becomes another detector.

Putting Everything Together

Now we can build a context object for each company.

def build_company_context(ticker):
price = detect_price_event(ticker)
    fundamentals_raw = get_fundamentals(ticker)
    fundamentals = extract_fundamental_snapshot(
        fundamentals_raw
    )
    previous = load_previous_snapshot(ticker)
    fundamental_changes = compare_fundamentals(
        fundamentals,
        previous
    )
    news = prepare_news(
        get_recent_news(ticker)
    )
    context = {
        "ticker": ticker,
        "price_event": price,
        "fundamentals": fundamentals,
        "fundamental_changes": fundamental_changes,
        "recent_news": news,
    }
    save_snapshot(
        ticker,
        fundamentals
    )
    return context

Now:

context = build_company_context("TSLA.US")

produces one structured object containing the information our AI layer actually needs.

Notice what we’re doing.

We’re not asking Claude to retrieve financial data.

We’re giving Claude structured financial data from EODHD and asking it to interpret the combination.

That makes the system much easier to understand and debug.

Adding the AI Relevance Filter

Now comes the AI layer.

import json
from anthropic import Anthropic
client = Anthropic(
    api_key=os.getenv("ANTHROPIC_API_KEY")
)

Our analysis function can look like this:

def analyze_company(context):
prompt = f"""
You are monitoring a long-term equity portfolio.
Your job is NOT to recommend BUY, SELL or HOLD.
Your job is to identify whether something
materially changed that deserves investor attention.
Analyze this data:
{json.dumps(context, indent=2)}
Focus on:
1. Unusual price movements
2. Earnings or estimate changes
3. Fundamental deterioration/improvement
4. Important company-specific news
5. Corporate events
Ignore normal market noise.
Return exactly:
PRIORITY: LOW / MEDIUM / HIGH
WHAT CHANGED:
Maximum 3 bullet points.
WHY IT MATTERS:
Maximum 3 sentences.
WHAT TO REVIEW:
Maximum 2 bullet points.
"""
    message = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=500,
        messages=[
            {
                "role": "user",
                "content": prompt,
            }
        ],
    )
    return message.content[0].text

The exact model can obviously be changed.

The more important part is the prompt.

I’m explicitly telling the model:

Don’t make the investment decision.

Its job is to filter information.

Running the Entire Portfolio

Now we can monitor all five companies.

def monitor_portfolio():
reports = {}
    for ticker, company in PORTFOLIO.items():
        print(f"Analyzing {company}...")
        try:
            context = build_company_context(
                ticker
            )
            analysis = analyze_company(
                context
            )
            reports[ticker] = analysis
        except Exception as e:
            reports[ticker] = (
                f"ERROR: {str(e)}"
            )
    return reports

Finally:

reports = monitor_portfolio()
for ticker, report in reports.items():
    print("\n")
    print("=" * 60)
    print(ticker)
    print("=" * 60)
    print(report)

And instead of seeing hundreds of data points, I might get something like:

============================================================
TSLA.US
============================================================
PRIORITY: HIGH
WHAT CHANGED:
- Large price movement detected.
- Earnings expectations have deteriorated.
- Recent company news is predominantly negative.
WHY IT MATTERS:
The combination suggests that the price movement
may be related to changing business expectations
rather than normal daily volatility.
WHAT TO REVIEW:
- Latest earnings report.
- Recent EPS estimate revisions.

While another stock could produce:

============================================================
AVGO.US
============================================================
PRIORITY: LOW
WHAT CHANGED:
- Price volatility increased.
- No significant fundamental deterioration detected.
WHY IT MATTERS:
The movement appears more consistent with market
volatility than a major change in the underlying
business.
WHAT TO REVIEW:
- No immediate action required.

That’s exactly what I wanted.

Not more information.

Better prioritization.

Making the Agent Smarter

This is only version one.

The architecture makes it easy to add more detectors later:

Insider transactions
        ↓
Analyst revisions
        ↓
SEC filings
        ↓
Valuation changes
        ↓
Technical indicators
        ↓
Sector performance
        ↓
Macro events
        ↓
AI relevance layer

But I wouldn’t start there.

A useful agent with five good signals is better than an over-engineered agent watching 50 metrics nobody understands.

One Important Limitation

There’s something important to understand about this system.

The agent doesn’t know whether a stock will go up tomorrow.

And neither does the API.

We’re building an information filtering system, not a crystal ball.

AI can also misinterpret financial information.

That’s why I deliberately separate the architecture into:

EODHD
   ↓
Raw financial data
Python
   ↓
Deterministic calculationsAI
   ↓
InterpretationHuman
   ↓
Investment decision

The closer we get to the actual investment decision, the more important human judgment becomes.

Why I Prefer This to Another Portfolio Dashboard

Most dashboards are designed around:

What is my portfolio worth?

This agent is designed around:

Has my investment thesis potentially changed?

Those are completely different problems.

Imagine monitoring 30 companies.

Most days, nothing important happens to 25 of them.

Maybe three experience unusual volatility.

See also  Making Money Online is Easy, Actually (Just Copy Me)

Maybe one reports earnings.

Maybe another has genuinely important news.

Instead of manually checking 30 companies, my attention goes directly to those two.

That’s the value.

Build Your Own Version

EODHD currently provides the APIs needed for the core data layer used in this project, including historical market prices, company fundamentals, earnings calendars and trends, financial news, sentiment and corporate actions.

You can use the same architecture with five stocks or scale it to a much larger watchlist.

Start building with EODHD Financial APIs

The goal isn’t to know everything happening in the market.

It’s to know when something happens that actually deserves your attention.

Frequently Asked Questions

Does this AI agent automatically buy or sell stocks?

No.

And I deliberately designed it that way.

The system retrieves financial data, detects changes and uses AI to prioritize information.

The final investment decision remains with the investor.

Does the code use real financial data?

Yes.

The Python functions call actual EODHD endpoints for historical prices, fundamentals, earnings, earnings trends, financial news, sentiment and corporate actions.

You need an EODHD API key to access the datasets available under your plan.

Can I test it for free?

EODHD provides a demo token for a limited selection of symbols.

Tesla is one of the symbols that can currently be tested with the demo token.

For the complete portfolio used in this project, including PLTR, AVGO, RDDT and NVDA, you should use your own EODHD API token.

Why use AI if Python already detects the changes?

Because they’re solving different problems.

Python is excellent at deterministic rules:

Price change > 5%
EPS surprise < -10%
Margin declined
EPS estimates declined

But investors usually care about the relationship between those events.

The AI layer can analyze several signals together and produce a concise explanation of why the combination might deserve attention.

Why not let Claude retrieve all the financial data itself?

I prefer separating data retrieval from interpretation.

EODHD provides the structured financial data.

Python performs deterministic calculations.

Claude receives a controlled set of information and interprets it.

That architecture is easier to audit, test and debug.

How often should the agent run?

For a long-term portfolio, I don’t think this needs to run every minute.

Once after the US market closes is enough for many investors.

You could schedule the Python script with cron, GitHub Actions, a cloud function or an automation platform.

Could I monitor 100 stocks instead of five?

Yes.

The architecture doesn’t fundamentally change.

However, API usage, LLM costs and noise increase with the number of companies.

A better architecture at larger scale is to run cheap deterministic filters first and only send interesting companies to the AI model.

In other words:

100 stocks
    ↓
Python filters
    ↓
8 unusual companies
    ↓
AI analysis
    ↓
2 important alerts

That’s much more efficient than asking an LLM to deeply analyze all 100 companies every day.

Can I add Telegram or email alerts?

Yes.

The monitor_portfolio() output can easily become the input to Telegram, Slack, Discord or an email service.

You could even configure the system so that HIGH priority events trigger an immediate notification while LOW priority events only appear in the daily report.

Is this financial advice?

No.

This project is an example of how financial APIs, Python and AI can be combined to organize and analyze information.

The agent can still make mistakes, miss context or incorrectly interpret data.

Use it as a research tool, not as an autonomous investment advisor.

Building something with financial data?
If you’re an API or fintech company looking to explain your product through practical, code-first content instead of marketing fluff, I write pieces exactly like this one.
See more of my work

Looking for technical content for your company? I can help — LinkedIn · kevinmenesesgonzalez@gmail.com

Source link