📊 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)
