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:
- building a quant screening tool,
- evaluating financial data APIs for an AI agent,
- or just tired of "AI stock picks" content with zero data behind it,
this is for you.
The Problem With Most Stock Screeners
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.
Giving Claude a Real Data Source
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.
Building the Screener
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"])
# Stage 2 + 3 combined: profitable, liquid, mid/large-cap, dividend-paying
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.
What Made the Cut
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.
Skipping the Code: EODHD's Official MCP Server
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:
- v2 (OAuth) — the simpler setup for Claude Desktop. You paste the server URL into Settings → Extensions, authorize through a consent flow, and you're done. No API key handling.
- v1 (API key) — for clients like ChatGPT or Claude Code that expect the key passed directly, or if you'd rather manage auth yourself.
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.
From Here You Can Build
This exact funnel took four API calls and about 30 lines of code. From here you can build:
- automated weekly screener reports delivered to Slack or email
- a Streamlit dashboard where the filters are sliders instead of hardcoded values
- a backtesting loop that checks whether "cheap earnings yield + dividend-paying + mid-cap" actually outperforms over rolling 12-month windows
- a signal layer on top using EODHD's
wallstreet_lo/wallstreet_hisignals 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
Turning This Into a Daily Job
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:
- Post it to a Slack channel with a webhook (
requests.post(SLACK_WEBHOOK_URL, json={"text": summary})) - Email it to yourself using the same pattern as any transactional email script
- Append each day's CSV to a running Google Sheet so you get a queryable history instead of a folder of files
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.
Key Takeaways
- A 4-stage numeric funnel turned a 3,000+ stock universe into 10 names in under 30 lines of Python.
- Earnings yield (EPS ÷ price) is a simple, brutally objective ranking metric — no sentiment, no narrative, no model hallucination.
- The filter mechanically excludes expensive growth names and surfaces cheap, profitable, dividend-paying companies across sectors and countries. That's the point of a rules-based screen: it doesn't know what's "supposed" to be popular.
- EODHD's MCP server lets you skip the API scripting entirely while you're still iterating on filter logic — same data, conversational interface.
- The real value isn't a single day's list. It's a cron job that runs the funnel daily and tells you what entered or left the top 10, because turnover is the actual signal.
FAQs
❓ 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
Top comments (0)