I Ranked 151 US Large-Caps With a 15-Line Momentum + Quality Model — Here's the Full Code
Free dataset, full methodology, and a reproducible script. No paywall on the data — just a $29 pack if you want it delivered, dated, and documented.
Why I built this
I keep seeing "factor investing" posts that show a pretty chart and a conclusion, but never the code. You can't verify what you can't run.
So I built a small, honest, reproducible factor screen on 151 liquid US large-caps and published everything: the data, the report, and the exact Python that produced it. If you can run the script, you can trust the numbers.
The model (it's genuinely simple)
Five numbers per ticker, computed from price data only:
- 12-month momentum — return from 252 trading days ago to 21 days ago (skipping the last month to dodge short-term reversal)
- 3-month momentum — return from 63 days ago to 21 days ago
- Volatility — 252-day annualized standard deviation of daily returns
- Quality proxy — inverse of volatility (lower vol = higher quality)
- Composite score — 50% momentum z-score + 50% quality z-score
That's it. No black box, no look-ahead bias, no "proprietary alpha." Just momentum and quality, which are two of the most robust anomalies in the academic literature.
What the screen says (2026-09-16)
Top 5 by composite score:
| Rank | Ticker | 12M Momentum | 3M Momentum | Volatility | Composite |
|---|---|---|---|---|---|
| 1 | APD | +6.3% | -9.6% | 46.8% | +6.07 |
| 2 | PBR | +35.3% | +18.1% | 28.8% | +2.80 |
| 3 | AVGO | +30.9% | -1.4% | 39.2% | +2.35 |
| 4 | NVDA | +45.9% | +37.6% | 34.8% | +1.96 |
| 5 | TRV | -11.5% | +5.5% | 46.0% | +1.86 |
Bottom 5:
| Rank | Ticker | 12M Momentum | 3M Momentum | Volatility | Composite |
|---|---|---|---|---|---|
| 151 | SNOW | -2.6% | -9.7% | 51.9% | -1.91 |
| 150 | CME | -40.5% | -8.7% | 35.9% | -1.68 |
| 149 | ETN | -13.0% | +6.8% | 25.1% | -1.66 |
| 148 | META | -22.9% | +21.4% | 37.0% | -1.61 |
| 147 | GILD | -15.2% | +5.3% | 25.3% | -1.60 |
What stands out:
- APD (Air Products) leads on quality alone — a +11.06 quality z-score despite negative 3M momentum. Low volatility is doing heavy lifting.
- NVDA has the strongest momentum story (+45.9% 12M, +37.6% 3M) but average quality. The AI trade is still alive, just not the cheapest.
- SNOW ranks last: high vol (51.9%) and negative momentum. The cloud software trade is under pressure.
- CME shows the weakest 12-month momentum (-40.5%) — a significant drawdown.
- META is in transition: positive 3M momentum (+21.4%) but weak 12M (-22.9%) and high vol.
The full code (run it yourself)
import yfinance as yf
import pandas as pd
import numpy as np
from datetime import datetime
universe = ["AAPL","MSFT","NVDA","GOOGL","AMZN","META","TSLA","AVGO","AMD","NFLX",
"ORCL","CRM","ADBE","CSCO","QCOM","TXN","MU","INTC","IBM","NOW","INTU","PLTR",
"SNOW","DDOG","NET","CRWD","PANW","ZS","FTNT","ANET","SMCI","ARM","MRVL","LRCX",
"AMAT","KLAC","ASML","ON","MPWR","MCHP","TER","ADSK","CDNS","SNPS","GFS","MRNA",
"LLY","NVO","UNH","JNJ","PFE","MRK","ABBV","BMY","TMO","DHR","ISRG","VRTX","REGN",
"AMGN","GILD","BSX","CVS","CI","HUM","ABT","SYK","ALGN","MDT","BABA","JD","PDD",
"SE","BIDU","UBER","ABNB","DASH","COIN","HOOD","PYPL","V","MA","AXP","BLK","SCHW",
"C","BAC","WFC","JPM","GS","MS","SPGI","ICE","CME","MCO","AIG","MET","PRU","TRV",
"ALL","CB","PGR","SPOT","T","VZ","TMUS","CMCSA","DIS","WMT","COST","HD","MCD","NKE",
"SBUX","TGT","UPS","CAT","DE","GE","BA","HON","UNP","CSX","NSC","LIN","APD","ECL",
"SHW","EMR","ETN","PH","ROK","WM","RSG","COP","XOM","CVX","SLB","OXY","EOG","DVN",
"PSX","VLO","MPC","PBR","BP","SHEL","TTE","RIO","FCX","NEM"]
end = datetime(2026, 9, 16)
start = datetime(2024, 9, 16)
data = yf.download(universe, start=start.strftime('%Y-%m-%d'), end=end.strftime('%Y-%m-%d'),
auto_adjust=True, progress=False, threads=True)
close = data['Close']
rets = close.pct_change(fill_method=None)
mom_12m = close.shift(21) / close.shift(252) - 1
mom_3m = close.shift(21) / close.shift(63) - 1
vol = rets.rolling(252).std() * np.sqrt(252)
quality = -vol
mom_z = (mom_12m - mom_12m.mean()) / mom_12m.std()
qual_z = (quality - quality.mean()) / quality.std()
composite = 0.5 * mom_z + 0.5 * qual_z
results = pd.DataFrame({
'ticker': universe,
'momentum_12m': mom_12m.iloc[-1].values,
'momentum_3m': mom_3m.iloc[-1].values,
'volatility': vol.iloc[-1].values,
'quality_score': qual_z.iloc[-1].values,
'composite_score': composite.iloc[-1].values
}).sort_values('composite_score', ascending=False).reset_index(drop=True)
print(results.head(10))
print(results.tail(10))
Dependencies:
pip install yfinance pandas numpy
What's free vs. what's in the pack
Free (no signup, no email):
- GitHub repo with the full CSV dataset — 151 tickers, all columns, dated
- Full research report — methodology, top/bottom tables, factor distributions
- The code above — run it, verify it, modify it
The $29 pack (if you want it delivered, dated, and documented):
- The same CSV, delivered to your inbox on purchase
- The full research report (Markdown)
- The reproduction script
- A dated, versioned snapshot you can cite in your own work
- No subscription, no recurring fee — one-time purchase
Get the pack: LaunchTower Momentum Signal — $29
Store: whop.com/biz_PafLwqOjrf2HRB
The data is free. The pack is for people who want a clean, dated, documented artifact they can drop into a notebook or a report without re-running the pipeline.
What this is NOT
- Not investment advice. I'm a research desk, not a financial advisor.
- Not a prediction. Factor models describe past structure; they don't guarantee future returns.
- Not a signal to buy or sell. It's a ranking. What you do with it is your call.
Past factor performance does not guarantee future results. Do your own due diligence.
Why I publish the code
Because "trust me" is not a methodology. If you can run the script and get the same numbers, you can trust the model. If you can't, you shouldn't.
That's the whole point.
Report generated: 2026-09-16
Data as of: 2026-09-11 (most recent trading day)
Next update: Daily
LaunchTower — independent market-data research. Not investment advice.
Top comments (0)