TL;DR: I built a 4-stage screening funnel on top of the EODHD Screener API, starting from a universe of 3,000+ US-listed stocks with a market cap above $150M. After filtering for profitability, liquidity, and a fair price relative to earnings, only 10 names survived. No hype, no "buy this now." I also cover two ways to extend this: connecting through EODHD's official MCP server so Claude can run the screen conversationally, and a cron-based script that runs the whole funnel every day and flags what changed. Full list, code, and setup below.
Most "AI picks stocks" content is theater.
Someone asks ChatGPT to "recommend 5 undervalued stocks," the model pulls from stale training data, and the article calls it analysis. No live data. No verifiable filter. No way for the reader to reproduce a single number.
I wanted to do the opposite.
So I built a real screening pipeline: Claude connected directly to the EODHD Stock Screener API, ran a live query against thousands of tickers, and narrowed them down using nothing but hard numeric filters. Every ticker, every price, every EPS figure in this article came from that live call, made on the day this was written.
If you're:
this is for you.
Free screeners on the big finance sites give you a handful of filters: market cap, sector, maybe a P/E range. Fine for a Sunday afternoon. Useless if you're trying to build something programmatic.
Paid terminals solve the flexibility problem but cost more than most independent investors are willing to spend just to test an idea.
And "AI-powered" stock tools usually skip the data problem entirely. They generate plausible-sounding tickers based on pattern matching, not a live query against an actual market database.
The real bottleneck isn't intelligence. It's access to structured, filterable data that an AI model can actually query.
EODHD's Screener API exposes exactly what a filtering pipeline needs: market capitalization, EPS, dividend yield, trading volume, sector, and price, across every ticker on the exchange, in one request.
Once Claude can call that endpoint directly, "screen the market" stops being a metaphor. It becomes a real, auditable sequence of API calls.
Here's the funnel I ran:
Stage 1 — Universe:US-listed common stocks with market cap > $150M
Stage 2 — Quality filter:profitable (positive EPS), liquid (200K+ average daily volume), priced above $5
Stage 3 — Value zone:market cap between $1B and $50B, dividend-paying, still liquid
Stage 4 — Ranking:sorted by earnings yield (EPS ÷ price) — the cheapest stocks relative to what they actually earn
Stage 1 alone returned more than 1,000 matches per page and kept going past the API's pagination cap — confirming a universe well north of 3,000 tickers once you include every US exchange EODHD covers at that market cap floor. From there, each stage cuts the field down hard.
The setup is a single authenticated GET request per stage. No scraping, no manual downloads.
import requests
import pandas as pd
API_TOKEN = "YOUR_API_TOKEN"
BASE_URL = "https://eodhd.com/api/screener"
def run_screen(filters, sort="market_capitalization.desc", limit=100, offset=0):
params = {
"api_token": API_TOKEN,
"filters": filters,
"sort": sort,
"limit": limit,
"offset": offset,
}
r = requests.get(BASE_URL, params=params)
r.raise_for_status()
return pd.DataFrame(r.json()["data"])
filters = (
'[["market_capitalization",">",1000000000],'
'["market_capitalization","<",50000000000],'
'["exchange","=","us"],'
'["earnings_share",">",0],'
'["avgvol_1d",">",300000],'
'["adjusted_close",">",5],'
'["dividend_yield",">",0]]'
)
df = run_screen(filters)
df["earnings_yield"] = df["earnings_share"] / df["adjusted_close"]
df["implied_pe"] = 1 / df["earnings_yield"]
top10 = df.sort_values("earnings_yield", ascending=False).head(10)
print(top10[["code", "name", "sector", "adjusted_close", "earnings_share",
"earnings_yield", "dividend_yield", "market_capitalization"]])
That's the whole pipeline. No manual screening, no spreadsheet gymnastics — just filters chained on top of each other until the list is short enough to actually read.
Out of the 3,000+ stock universe, these 10 names survived every filter: profitable, liquid, mid-to-large cap, and cheapest relative to their own earnings on the day of the screen.
| Ticker | Company | Sector | Price | EPS | Earnings Yield | Implied P/E | Dividend Yield | Market Cap |
|---|---|---|---|---|---|---|---|---|
| JBS | JBS N.V. | Consumer Defensive (Packaged Foods) | $11.82 | $1.62 | 13.7% | ~7.3x | 8.2% | $40.1B |
| EIX | Edison International | Utilities (Regulated Electric) | $74.78 | $9.20 | 12.3% | ~8.1x | 4.5% | $28.8B |
| GFI | Gold Fields Ltd ADR | Basic Materials (Gold) | $32.88 | $3.94 | 12.0% | ~8.3x | 6.9% | $30.0B |
| PYPL | PayPal Holdings | Financial Services (Credit Services) | $44.53 | $5.33 | 12.0% | ~8.4x | 0.9% | $40.3B |
| VICI | VICI Properties | Real Estate (REIT – Gaming) | $26.09 | $2.92 | 11.2% | ~8.9x | 6.7% | $29.5B |
| PUK | Prudential PLC ADR | Financial Services (Life Insurance) | $27.36 | $3.02 | 11.0% | ~9.1x | 1.0% | $34.5B |
| HIG | Hartford Financial | Financial Services (Insurance) | $138.74 | $14.12 | 10.2% | ~9.8x | 1.6% | $38.0B |
| EQT | EQT Corporation | Energy (Oil & Gas E&P) | $51.16 | $5.21 | 10.2% | ~9.8x | 1.3% | $32.0B |
| EC | Ecopetrol SA ADR | Energy (Oil & Gas Integrated) | $15.13 | $1.40 | 9.3% | ~10.8x | 4.4% | $30.2B |
| KB | KB Financial Group | Financial Services (Banks – Regional) | $116.74 | $10.40 | 8.9% | ~11.2x | 2.7% | $41.0B |
A few things jump out.
No mega-cap tech. Not one FAANG name survived, because none of them clear an earnings yield above 5% at current prices — the filter mechanically excludes anything priced richly relative to its own profits.
Heavy tilt toward energy, financials, and insurance. That's not a bias I introduced. It's what happens when you rank purely by EPS-to-price, in mid-2026, across a market where growth names still carry premium multiples.
Real geographic spread. A Brazilian meat producer, a South African gold miner, a Colombian state oil company, a South Korean bank, and a UK insurer all made a US-dollar-denominated cut. The screener doesn't care about domicile — it cares about the number.
None of this is a buy recommendation. Cheap relative to earnings doesn't mean safe, and several of these names (EIX, in particular, given wildfire litigation exposure; JBS, given historical governance concerns) carry known company-specific risk that a pure numeric filter can't see. This is a starting list for further research, not a portfolio.
Everything above ran through raw HTTP calls, which is the right approach when you're building something you'll productionize. But there's a second way to do this that's worth knowing about, especially if you want to iterate on filter ideas conversationally instead of rewriting Python every time.
EODHD ships an official Model Context Protocol (MCP) server — a standard that lets AI clients like Claude Desktop, Claude Code, and ChatGPT call external tools directly, without you writing the request-handling code yourself. Anthropic created MCP specifically to solve this "every data source needs its own custom integration" problem.
EODHD's server exposes 70+ read-only tools covering the same ground as the REST API: the screener, fundamentals, historical and intraday prices, news and sentiment, technical indicators, macro indicators, US options, and more. It comes in two flavors:
Once it's connected, you don't write a requests.get()
call at all. You just ask:
"Screen US stocks with market cap between 1B and 50B, positive EPS, dividend yield above 0, and rank them by earnings yield. Show me the top 10."
Claude translates that into the same stock_screener
tool call I used to build this article, executes it against live EODHD data, and returns the table. This is exactly how I pulled the numbers above — no manual API scripting for the exploratory stage, just a direct conversation with the data.
Where this matters in practice: the raw API approach is better once you know exactly what you're building — a cron job, a dashboard, a backtest. The MCP approach is better while you're still deciding what the funnel should even look like, since you can adjust a filter, re-run, and see the new output in seconds without touching code.
Both hit the same underlying EODHD infrastructure and consume the same API quota, so there's no data-quality tradeoff — it's purely a question of whether you want a repeatable script or a fast conversational loop.
This exact funnel took four API calls and about 30 lines of code. From here you can build:
wallstreet_lo
/ wallstreet_hi
signals to cross-check against analyst targets
Get real-time and historical stock data for your own screeners
EODHD gives you programmatic access to fundamentals, screener queries, and live pricing across global exchanges — the same endpoints used for every number in this article.
→[Start with EODHD's API]
A one-time screen is a snapshot. The actual value shows up when you run it every trading day and track what changes — which names drop out because they got expensive, and which new ones enter because they got cheap or a fresh earnings report shifted the EPS.
Here's a script that runs the full 4-stage funnel, saves the day's result, diffs it against yesterday's, and writes a short summary. Point it at cron and forget about it.
import requests
import pandas as pd
from datetime import date
from pathlib import Path
API_TOKEN = "YOUR_API_TOKEN"
BASE_URL = "https://eodhd.com/api/screener"
OUTPUT_DIR = Path("screener_history")
OUTPUT_DIR.mkdir(exist_ok=True)
FILTERS = (
'[["market_capitalization",">",1000000000],'
'["market_capitalization","<",50000000000],'
'["exchange","=","us"],'
'["earnings_share",">",0],'
'["avgvol_1d",">",300000],'
'["adjusted_close",">",5],'
'["dividend_yield",">",0]]'
)
def run_screen():
params = {
"api_token": API_TOKEN,
"filters": FILTERS,
"sort": "market_capitalization.desc",
"limit": 100,
"offset": 0,
}
r = requests.get(BASE_URL, params=params)
r.raise_for_status()
df = pd.DataFrame(r.json()["data"])
df["earnings_yield"] = df["earnings_share"] / df["adjusted_close"]
return df.sort_values("earnings_yield", ascending=False).head(10)
def save_and_diff(today_df):
today_str = date.today().isoformat()
today_path = OUTPUT_DIR / f"{today_str}.csv"
today_df.to_csv(today_path, index=False)
history = sorted(OUTPUT_DIR.glob("*.csv"))
if len(history) < 2:
return "First run — no previous data to compare against."
yesterday_df = pd.read_csv(history[-2])
today_codes = set(today_df["code"])
yesterday_codes = set(yesterday_df["code"])
new_entries = today_codes - yesterday_codes
dropped = yesterday_codes - today_codes
summary = []
if new_entries:
summary.append(f"New in today's top 10: {', '.join(sorted(new_entries))}")
if dropped:
summary.append(f"Dropped out: {', '.join(sorted(dropped))}")
if not new_entries and not dropped:
summary.append("No changes in the top 10 today.")
return "\n".join(summary)
if __name__ == "__main__":
result = run_screen()
summary = save_and_diff(result)
print(summary)
Schedule it with a one-line crontab entry to run every weekday after market close:
0 22 * * 1-5 /usr/bin/python3 /path/to/daily_screener.py >> /path/to/screener.log 2>&1
From there, the summary
string is trivial to route anywhere:
requests.post(SLACK_WEBHOOK_URL, json={"text": summary})
)The interesting part isn't the top 10 on any single day. It's watching turnover — a name entering the list usually means its price dropped or its earnings just improved enough to make the yield attractive. A name leaving usually means the opposite. That turnover signal is something a one-off screenshot can never give you.
❓ Can Claude actually query live financial data, or is this just training data?
✅ Claude called the EODHD Screener API directly for this article, meaning every number reflects the market on the day of the query, not a training snapshot. That distinction matters — most "AI stock pick" content is quietly using stale, memorized data.
❓ Is earnings yield a reliable way to find undervalued stocks?
✅ It's a useful, transparent starting filter, not a complete valuation model. A high earnings yield can mean genuine value or it can mean the market is pricing in a real risk (regulatory, legal, competitive) that the number doesn't capture. Always follow up with qualitative research before acting.
❓ Do I need a paid EODHD plan to run this screener?
✅ The Screener API is available on EODHD's All-In-One and All World Extended plans. Each screener request consumes 5 API calls, so a 4-stage funnel like this one costs 20 calls total — trivial even on a modest plan.
❓ What's the difference between calling the EODHD API directly and using their MCP server?
✅ Same underlying data and the same API quota either way. Direct API calls make sense once you're building a repeatable script, like the daily cron job above. The MCP server makes sense while you're still shaping the filter logic, since you can adjust it in plain language and see results immediately without touching code.
❓ How do I get alerted automatically instead of checking the screener manually?
✅ Run the funnel on a schedule (cron, Task Scheduler, or a serverless function), save each day's output, and diff it against the previous run. Route the diff to Slack, email, or a spreadsheet — the script in this article does exactly that in under 80 lines.
The market doesn't reward the investor with the best opinions.
It rewards the one with the best process for filtering out noise.
Looking for technical content for your company? I can help — LinkedIn · kevinmenesesgonzalez@gmail.com