Building a Wall Street-Grade Stock Screener with Openclaw AI Agents and Free APIs

TL;DR: I built a stock screening system that scans all 500 S&P 500 stocks for technical oversold signals, then runs Warren Buffett’s 10 quality formulas using official SEC data. The entire system runs from Telegram commands, uses 100% free public APIs, and returns results in 15 seconds to 3 minutes depending on the analysis depth.

The Problem: Wall Street Tools Are Expensive

As someone interested in quantitative investing, I faced a familiar dilemma: professional-grade stock screening tools are prohibitively expensive.

  • Bloomberg Terminal: $24,000/year
  • FactSet: $12,000/year
  • Morningstar Premium: $249/year (limited functionality)
  • Trade Ideas: $118/month (basic scanning only)

What I wanted was simple:

  1. Scan all S&P 500 stocks for technical oversold signals (Williams %R < -80)
  2. Filter by fundamental quality using Warren Buffett’s investment criteria
  3. Get results fast, preferably on my phone
  4. Use official, reliable data sources

The solution? Build it myself using AI agents, free public APIs, and open-source tools.

The Architecture: AI-Powered Automation

The system is built on OpenClaw, an AI agent framework that orchestrates complex workflows through natural language. Think of it as having a senior developer who can write code, manage databases, and deploy services — all from chat commands.

Core Components

1. OpenClaw Agent (Orchestration Layer)

The brains of the operation. Claude Opus 4.5 interprets Telegram commands and executes Python scripts with full context awareness.

2. Python Scripts (Processing Layer)

  • technical_only.py — Fast oversold screening (15-30 seconds)
  • screening.py — Combined technical + fundamental analysis (1-3 minutes)
  • analyze.py — Deep dive on individual stocks (2-3 seconds)

3. SQLite Databases (Caching Layer)

  • price_cache.db — Daily OHLCV data for 500 stocks (1-day TTL)
  • askten.db — SEC financial statements (7-day TTL)

4. Telegram Bot (Interface Layer)

Simple commands like oversold, screen, or analyze AAPL trigger full market scans.

500

S&P 500 Stocks Scanned

15s

Technical Scan Time

10

Buffett Formulas

$0

API Costs

Data Sources: Free, Public, and Authoritative

One of the key constraints was using 100% free data sources. Here’s what I chose and why:

1. Yahoo Finance (Price Data)

Via the yfinance Python library, I get:

  • Daily OHLCV (Open, High, Low, Close, Volume) for all S&P 500 stocks
  • Real-time updates during market hours
  • Historical data going back years
  • Same data source used by TradingView, Robinhood, and many professional platforms
import yfinance as yf
# Fetch 90 days of price data
stock = yf.Ticker("AAPL")
df = stock.history(period="90d")# Calculate Williams %R (21-day period)
high = df['High'].rolling(21).max()
low = df['Low'].rolling(21).min()
williams_r = ((high - df['Close']) / (high - low)) * -100

Reliability: 99.4% success rate (500/503 S&P 500 stocks)

Cost: Free, no API key required

2. SEC EDGAR (Fundamental Data)

The U.S. Securities and Exchange Commission provides a free API for company financial statements. This is the official source — the same data Warren Buffett reads in 10-K filings.

import requests
# Fetch Apple's company facts (balance sheet, income statement, cash flow)
cik = "0000320193"  # Apple's CIK number
url = f"https://data.sec.gov/api/xbrl/companyfacts/CIK{cik}.json"response = requests.get(url, headers={'User-Agent': 'YourApp/1.0'})
data = response.json()# Extract assets, liabilities, equity, revenue, etc.
facts = data['facts']['us-gaap']

Authority: 100% official government data, legally required filings

Cost: Free, rate limit of 10 requests/second

3. GitHub (S&P 500 List)

A community-maintained CSV file with all current S&P 500 constituents:

import pandas as pd
url = "https://raw.githubusercontent.com/datasets/s-and-p-500-companies/main/data/constituents.csv"
df = pd.read_csv(url)
tickers = df['Symbol'].tolist()  # 503 tickers

Feature #1: The “oversold” Command

This is the speed demon. In 15–30 seconds, it scans all 500 S&P 500 stocks and identifies which ones are technically oversold.

What It Does

  1. Fetches the S&P 500 ticker list (503 stocks)
  2. Retrieves 90 days of price data for each stock (parallel fetching, 10 workers)
  3. Calculates Williams %R (21-day momentum oscillator)
  4. Calculates EMA(13) of Williams %R for trend confirmation
  5. Filters stocks where Williams %R < -80 (oversold threshold)
  6. Ranks by intensity (Extreme, Very Strong, Strong, Moderate)

Technical Indicator: Williams %R

Williams %R measures where the current price sits within the recent 21-day high-low range:

Williams %R = ((Highest High - Close) / (Highest High - Lowest Low)) × -100

Interpretation:

  • -100 to -80: Oversold (potential buy)
  • -80 to -20: Neutral
  • -20 to 0: Overbought (potential sell)

When a stock hits -99.3 (like Visa did in my scan), it means the price is only 0.7% above the 21-day low — extreme panic selling territory.

Real Results (Jan 31, 2026):

  • 📊 Scanned: 500 S&P 500 stocks
  • ✅ Found: 93 oversold stocks (18.6%)
  • 🔴 Extreme oversold (< -95): 15 stocks
  • ⚡ Time: 15 seconds (cached), 3–5 minutes (first run)

Top 5:

  1. V (Visa) — Williams %R: -99.3 🔴
  2. AXON (Axon Enterprise) — Williams %R: -98.7 🔴
  3. TTD (The Trade Desk) — Williams %R: -98.1 🔴
  4. CEG (Constellation Energy) — Williams %R: -97.6 🔴
  5. FICO (Fair Isaac) — Williams %R: -97.5 🔴

The Code

def run_technical_screening(threshold=-80.0):
    # Step 1: Get S&P 500 tickers
    tickers = get_sp500_tickers_from_github()
# Step 2: Fetch price data (parallel)
    price_manager = PriceDataManager()
    price_data = price_manager.batch_fetch_prices(tickers, days=90)    # Step 3: Calculate technical indicators
    tech_analyzer = TechnicalAnalyzer()
    signals = []    for ticker, df in price_data.items():
        # Calculate Williams %R (21-day)
        williams_r = calculate_williams_r(df, period=21)
        williams_r_ema = calculate_ema(williams_r, period=13)        latest_wr = williams_r.iloc[-1]        if latest_wr < threshold:
            signals.append({
                'ticker': ticker,
                'williams_r': latest_wr,
                'williams_r_ema': williams_r_ema.iloc[-1],
                'signal': 'oversold'
            })    # Step 4: Sort by intensity (most oversold first)
    signals.sort(key=lambda x: x['williams_r'])    return signals

Performance Optimization: Smart Caching

Fetching 500 stocks × 90 days of data would be slow without caching. Here’s how I optimized it:

class PriceDataManager:
    def __init__(self, db_path="price_cache.db"):
        self.db_path = db_path
        self._init_database()
def get_daily_prices(self, ticker, days=90, force_refresh=False):
        # Check cache first (1-day TTL)
        if not force_refresh:
            cached = self._get_cached_prices(ticker, days)
            if cached is not None:
                return cached        # Fetch fresh data from Yahoo Finance
        stock = yf.Ticker(ticker)
        df = stock.history(period=f"{days}d")        # Store in SQLite
        self._store_prices(ticker, df)        return df

Result: First run takes 3–5 minutes, subsequent runs take 15–30 seconds.

Feature #2: The “analyze” Command

This is where fundamental analysis happens. Give it a ticker, and it runs Warren Buffett’s 10 investment formulas using official SEC data.

The 10 Buffett Formulas

These are based on Buffett’s actual investment criteria, extracted from his letters to shareholders and Berkshire Hathaway’s investment philosophy:

  1. Cash Test: Cash + Short-Term Investments > Total Debt
  2. Debt-to-Equity Ratio: Total Liabilities / Equity < 0.5
  3. Free Cash Flow Test: (Operating Cash Flow — CapEx) / Debt > 0.25
  4. Return on Equity (ROE): Net Income / Equity > 15%
  5. Current Ratio: Current Assets / Current Liabilities > 1.5
  6. Operating Margin: Operating Profit / Revenue > 12%
  7. Asset Turnover: Revenue / Total Assets > 0.5
  8. Interest Coverage: Operating Profit / Interest Expense > 3×
  9. Earnings Stability: Positive earnings 8+ of last 10 years
  10. Capital Allocation: ROE > 15% (value creation check)

Example: Analyzing Apple (AAPL)

When I run analyze AAPL, here’s what happens behind the scenes:

def analyze_stock(ticker):
    # Step 1: Fetch SEC data (or use cached)
    cik = resolve_ticker_to_cik(ticker)
    companyfacts = fetch_sec_companyfacts(cik)
# Step 2: Extract financial facts (balance sheet, income, cash flow)
    facts = extract_facts(companyfacts)    # Step 3: Run all 10 Buffett formulas
    engine = FormulaEngine(facts)
    results = engine.evaluate_all()    # Step 4: Generate report
    pass_count = sum(1 for r in results if r.status == 'PASS')    return f"Score: {pass_count}/10 formulas passed"

Real Apple Results (Jan 31, 2026):

📊 AAPL — Buffett Analysis

Score: 7/10 formulas passed

Strengths:

  • ✅ ROE: 151.9% (exceptional)
  • ✅ Operating Margin: 32.0% (strong pricing power)
  • ✅ Free Cash Flow: 1.37× debt coverage
  • ✅ Earnings Stability: 10/10 years positive

Concerns:

  • ❌ Cash Test: Only 0.84× debt coverage (target: > 1.0)
  • ❌ Debt-to-Equity: 3.87 (target: < 0.5)
  • ❌ Current Ratio: 0.89 (weak short-term liquidity)

Verdict: Good quality overall, but leverage is high.

Data Provenance: Every Number Is Traceable

One of my favorite features is full data provenance. Every number links back to its source SEC filing:

Provenance(
    value=94680000000,
    tag='NetIncomeLoss',
    label='Net Income',
    period_end='2025-09-27',
    fiscal_year=2025,
    unit='USD',
    form='10-K'
)

This means I can verify exactly where each data point came from — no black boxes, no estimations, just official SEC filings.

Feature #3: The “screen” Command

This combines technical and fundamental analysis for the ultimate quality filter:

  1. Run technical scan (find oversold stocks)
  2. For each oversold stock, run Buffett analysis
  3. Filter: Keep only stocks with ≥ 5/10 formulas passing
  4. Rank by combined score (30% technical + 70% fundamental)
def run_combined_screening(min_buffett_score=5):
    # Step 1: Find technically oversold stocks
    oversold = run_technical_screening(threshold=-80.0)
# Step 2: Run Buffett analysis on each
    quality_stocks = []    for signal in oversold:
        buffett_results = analyze_stock(signal['ticker'])
        pass_count = count_passed_formulas(buffett_results)        if pass_count >= min_buffett_score:
            # Calculate combined score
            tech_score = (signal['williams_r'] + 100) / 100  # Normalize to 0-100
            fundamental_score = pass_count / 10 * 100
            combined = (tech_score * 0.3) + (fundamental_score * 0.7)            quality_stocks.append({
                'ticker': signal['ticker'],
                'williams_r': signal['williams_r'],
                'buffett_score': pass_count,
                'combined_score': combined
            })    # Step 3: Sort by combined score
    quality_stocks.sort(key=lambda x: x['combined_score'], reverse=True)    return quality_stocks[:10]  # Top 10

Why 30% technical + 70% fundamental?

This weighting reflects Buffett’s philosophy: technical timing matters, but fundamental quality matters more. A stock can be oversold (technical opportunity) but if the fundamentals are weak, it’s a value trap.

The Telegram Interface: Simplicity Meets Power

All of this complexity is hidden behind three simple commands:

oversold                    # Fast technical scan (15-30 sec)
screen                      # Combined quality screening (1-3 min)
analyze AAPL                # Deep dive on specific stock (2-3 sec)

With options for customization:

oversold --threshold -90    # Only extreme oversold
screen --min-score 8        # Only exceptional quality

The OpenClaw agent interprets these commands, executes the appropriate Python scripts, and returns formatted results directly to Telegram.

Security Note: The Telegram bot only responds to authorized users. Gateway runs locally on a VPS with loopback-only binding (127.0.0.1), and the bot token is kept in environment variables.

Technical Challenges and Solutions

Challenge 1: TA-Lib on ARM64

The TA-Lib Python library requires a C library compiled for the target architecture. My VPS runs ARM64, which isn’t officially supported.

Solution: Compiled TA-Lib 0.4.0 from source with ARM64 build flags:

wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz
tar -xzf ta-lib-0.4.0-src.tar.gz
cd ta-lib/
./configure --prefix=/usr --build=aarch64-unknown-linux-gnu
make
make install
ldconfig
pip3 install TA-Lib

Challenge 2: Rate Limits

Yahoo Finance has informal rate limits (~2000 requests/hour). Fetching 500 stocks in parallel could trigger throttling.

Solution: Smart caching with 1-day TTL. After the first scan, subsequent runs read from SQLite instead of hitting Yahoo Finance.

Challenge 3: SEC API Reliability

The SEC API has a 10 requests/second limit and occasionally times out.

Solution: 7-day cache for SEC data (fundamentals don’t change daily) + retry logic with exponential backoff.

Performance Metrics

99.4%

Data Coverage

15s

Cached Scan

5.8MB

Price Cache

359MB

SEC Cache

Breakdown:

  • oversold (technical only): 15-30 seconds (cached), 3-5 minutes (cold)
  • screen (technical + fundamental): 1-3 minutes (cached), 5-7 minutes (cold)
  • analyze TICKER: 2-3 seconds (cached), 3-5 seconds (fresh)

What Makes This Different

There are plenty of stock screeners out there. Here’s what makes this one unique:

1. 100% Free Data

No subscriptions, no API keys, no paywalls. Just public APIs from Yahoo Finance and the SEC.

2. Full S&P 500 Coverage

Most free screeners limit you to a handful of stocks. This scans all 500 (actually 503 current constituents).

3. Complete Buffett Implementation

Not simplified ratios — the actual 10 formulas Buffett uses, calculated from official SEC filings with full provenance.

4. AI-Powered Orchestration

The OpenClaw agent handles database initialization, error recovery, caching logic, and deployment — tasks that would normally require DevOps expertise.

5. Mobile-First

Control everything from Telegram. No need to be at a computer, no web dashboards, no logins.

Future Enhancements

This is version 1.0. Here’s what I’m considering next:

  • Portfolio tracking: Maintain a watchlist and get alerts when stocks hit oversold levels
  • Backtesting: Test historical performance of the screening criteria
  • More indicators: RSI, MACD, Bollinger Bands
  • International markets: Expand beyond S&P 500 to global indices
  • Scheduled scans: Automatic daily scans sent to Telegram at market close
  • Custom formulas: Let users define their own screening criteria

Lessons Learned

1. Free Data Is Good Enough

You don’t need expensive Bloomberg terminals. Yahoo Finance and SEC EDGAR provide institutional-quality data for free.

2. AI Agents Are Powerful DevOps Tools

OpenClaw handled tasks that would’ve taken me hours: compiling C libraries, managing databases, writing deployment scripts. The agent did it all autonomously.

3. Caching Is Critical

Without SQLite caching, this system would be unusable. Smart TTLs (1 day for prices, 7 days for SEC data) balance freshness and speed.

4. Simplicity Wins

The Telegram interface is dead simple: oversold, screen, analyze TICKER. No complex UIs, no configuration files, just three commands.

The Code

Want to build your own? Here’s the core architecture:

stock-analysis/
├── analyze.py              # Individual stock Buffett analysis
├── technical_only.py       # Fast technical oversold scan
├── screening.py            # Combined technical + fundamental
├── price_data.py           # Yahoo Finance data fetching + caching
├── technical_indicators.py # Williams %R, EMA calculations (TA-Lib)
├── sec_api.py              # SEC EDGAR API client
├── formulas.py             # 10 Buffett formulas implementation
├── database.py             # SQLite caching layer
├── sp500_tickers.py        # S&P 500 list fetching
├── SKILL.md                # OpenClaw skill definition
└── data/
    ├── askten.db           # SEC fundamental data cache
    └── price_cache.db      # Price data cache

The entire codebase is ~2,000 lines of Python. The OpenClaw skill definition (SKILL.md) is what makes it accessible via Telegram:

---
name: stock-analysis
description: Analyze stocks using Buffett formulas and technical indicators
user-invocable: true
requires:
  bins:
    - python3
  packages:
    - yfinance
    - TA-Lib
    - pandas
    - numpy
---
# Stock Analysis Skill## Available Commands### Command: oversold
**What it does:** Fast technical scan of all S&P 500 stocks
**How to execute:**
\`\`\`bash
cd /root/.openclaw/workspace/skills/stock-analysis
python3 technical_only.py --top-n 20 --format telegram
\`\`\`

Conclusion: Democratizing Wall Street

This project proves that you don’t need expensive terminals or subscriptions to do professional-grade stock analysis. With free public APIs, open-source tools, and AI agents, anyone can build Wall Street-quality systems.

The total cost? $0 for data + $5/month for a basic VPS.

The real revolution isn’t just the cost — it’s the accessibility. I can now run a full S&P 500 scan while waiting in line at a coffee shop, from my phone, and get results in 15 seconds.

That’s the power of combining AI agents with public APIs.

Try it yourself:

  1. Set up OpenClaw (https://openclaw.ai)
  2. Install Python, TA-Lib, yfinance
  3. Write the Python scripts (or fork my implementation)
  4. Create a Telegram bot
  5. Let the AI agent handle the rest

If you build something cool with this approach, I’d love to hear about it. The future of fintech is open-source, AI-powered, and accessible to everyone.

Happy investing. 📈

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top