Saturday, March 28, 2026
HomeAI ToolsBuild a Free Stock Momentum Scanner with Python (No API Key Needed)

Build a Free Stock Momentum Scanner with Python (No API Key Needed)

Build time: ~15 min
📊 Data source: yfinance (free, no API key)
🔧 Difficulty: Beginner
📈 Output: Ranked CSV of S&P 500 momentum stocks

Python Investor’s Toolkit: Part 1: Momentum · Part 2: Monte Carlo · Part 3: Mean Reversion · Part 4: Earnings Quality · Part 5: Macro Regime

Part 1 of 5 — The Python Investor’s Toolkit: Practical quant tools built with free data.

Momentum is one of the most well-documented factors in finance: stocks that have outperformed over the past 3–12 months tend to continue outperforming over the next 1–3 months. Academic research going back to Jegadeesh & Titman (1993) consistently confirms it. Yet most retail investors never act on it because the screening tools that surface momentum signals cost hundreds of dollars a month.

This post builds a working momentum scanner from scratch using only free tools. You’ll be able to scan the entire S&P 500, rank stocks by composite momentum, and export results to a CSV — all in under 60 lines of Python.

What You’ll Need

pip install yfinance pandas numpy

💡 Setup Note: This script uses yfinance to pull price data directly from Yahoo Finance — no API key required. For scanning all 500 S&P stocks it scrapes the ticker list from Wikipedia. You’ll need an internet connection and Python 3.8+. Total runtime: approximately 10–15 minutes for a full scan.

The Strategy: 3/6/12-Month Composite Momentum

Rather than relying on a single lookback period, this scanner uses a composite momentum score — the equal-weighted average of 3-month, 6-month, and 12-month price returns. This reduces sensitivity to any single period’s noise and is closer to how institutional quant desks implement momentum factors.

Lookback Period Trading Days What It Captures Sensitivity
3-Month ~63 days Short-term trend, earnings follow-through High noise
6-Month ~126 days Mid-cycle positioning, sector rotation Balanced
12-Month (skip 1M) ~252 days Fundamental trend persistence Lower noise
Composite Equal-weighted average of all three Smoothed signal

The Code

import yfinance as yf
import pandas as pd
import numpy as np
from datetime import datetime, timedelta

def get_sp500_tickers():
    """Scrape S&P 500 tickers from Wikipedia — no API key needed."""
    table = pd.read_html('https://en.wikipedia.org/wiki/List_of_S%26P_500_companies')[0]
    return table['Symbol'].str.replace('.', '-').tolist()

def calculate_momentum(ticker, periods=[63, 126, 252]):
    """
    Calculate momentum returns for a single ticker.
    63 ≈ 3 months, 126 ≈ 6 months, 252 ≈ 12 months of trading days.
    """
    try:
        end = datetime.today()
        start = end - timedelta(days=400)  # buffer for 252 trading days
        data = yf.download(ticker, start=start, end=end, progress=False)

        if data.empty or len(data) < 252:
            return None

        close = data['Adj Close']
        results = {'ticker': ticker}

        for period in periods:
            ret = (close.iloc[-1] / close.iloc[-period] - 1) * 100
            results[f'mom_{period}d'] = round(ret, 2)

        # Composite momentum score (equal-weighted average)
        results['composite'] = round(
            np.mean([results[f'mom_{p}d'] for p in periods]), 2
        )
        return results
    except Exception:
        return None

def run_momentum_scan(tickers, top_n=20):
    """Run the full momentum scan and print top N results."""
    results = []
    total = len(tickers)

    for i, ticker in enumerate(tickers):
        if i % 50 == 0:
            print(f"  Scanning {i}/{total}...")
        result = calculate_momentum(ticker)
        if result:
            results.append(result)

    df = pd.DataFrame(results)
    df = df.sort_values('composite', ascending=False).reset_index(drop=True)
    df.index += 1  # rank starts at 1

    print(f"n--- Top {top_n} Momentum Stocks ---")
    print(df.head(top_n).to_string())

    # Also show bottom 10 (contrarian short candidates)
    print(f"n--- Bottom 10 Momentum (Weakest) ---")
    print(df.tail(10).to_string())

    df.to_csv('momentum_scan.csv', index=True)
    print("nFull results saved to momentum_scan.csv")
    return df

# --- Run it ---
tickers = get_sp500_tickers()
results = run_momentum_scan(tickers, top_n=20)

How to Read the Output

The scanner prints a ranked table. The key column is composite — the average momentum across all three periods. A stock ranked #1 with a composite of +85 has averaged +85% across its 3/6/12-month returns, placing it at the top of the momentum universe.

📈 Key Insight: The research edge in momentum comes from holding top-decile stocks for 1–3 months and rebalancing — not from trading every signal. Run this scan monthly, hold the top 20–30 names equally weighted, and replace exits with the next-ranked stock. This systematic approach is what separates momentum investing from chasing hot tickers.

Practical Customization

You don’t have to scan the S&P 500. Replace the ticker list with any universe you track:

# Scan your personal watchlist instead
tickers = ['AAPL', 'MSFT', 'NVDA', 'GOOGL', 'META', 'AMZN', 'TSLA', 'AVGO']
results = run_momentum_scan(tickers, top_n=len(tickers))

# Or scan a specific sector ETF's holdings
# (pull holdings list from ETF provider's website and paste tickers)

⚠️ Watch Out: Momentum strategies have well-known crash risk — they tend to reverse sharply during market regime changes (e.g., March 2020, Q4 2022). Never run a pure momentum strategy without a drawdown stop. Consider adding a simple market filter: if SPY is below its 200-day moving average, move to cash rather than buying momentum names into a falling market.

📊 Portfolio Takeaway

Momentum is a systematic factor — not a hot tip. Use the scanner monthly: buy the top 20 composite-score names equally weighted, set a hard drawdown stop at −15% on the portfolio (switch to SPY or cash), and only add a name if it stays in the top decile at rebalance. If SPY is below its 200-day MA, skip the scan entirely and hold cash — momentum strategies have historically lost the most in sharp reversals, and sitting out those regimes accounts for a significant share of the long-run edge.

What’s Next

Now that you can identify the strongest-trending stocks, Part 2 of this series shows you how to stress-test your portfolio with Monte Carlo simulation — so you understand your actual downside before you size any position.

Series: The Python Investor’s Toolkit
Part 1: Momentum Scanner (this post)
Part 2: Monte Carlo Portfolio Stress-Test
Part 3: Mean Reversion Alert System
Part 4: Earnings Quality Analyzer (SEC EDGAR)
Part 5: Macro Regime Detector (FRED API)

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here