A command-line tool for analyzing funds and ETFs using factor regressions (Fama-French, Carhart models).
This repo was entirely written by claude code. Accuracy or quality are not guaranteed.
- Factor Analysis: Run Fama-French 3-factor, 5-factor, Carhart 4-factor, or 4-factor + momentum regressions
- Factor Visualization: Plot factor returns over time (cumulative, raw, or rolling)
- Telltale Charts: Compare relative performance of two funds
- Implied Equity Risk Premium: Calculate the implied ERP for the S&P 500 using Damodaran's methodology
- Portfolio Analysis: Analyze weighted portfolios of multiple holdings
- Portfolio Comparison: Compare up to 3 portfolios across factor regressions, returns, and rolling returns
- Fund Comparison: Compare factor exposures across multiple funds
- Rolling Analysis: Track how factor loadings change over time
- UK/International Support: Analyze UK ETFs (
.Lsuffix) with developed market factors - Multiple Data Sources: Fetch prices from Yahoo Finance (default) or Stooq
- Caching: Factor data is cached locally for faster subsequent analyses
Requires Python 3.10+.
asdf install
# Install with uv (recommended)
uv sync
# Or install with pip
pip install -e .# Basic analysis with Fama-French 3-factor model
uv run factor analyze SPY
# Use 5-factor model
uv run factor analyze SPY --model ff5
# Carhart 4-factor (adds momentum)
uv run factor analyze SPY --model carhart
# 4-factor + momentum (profitability + momentum, no investment factor)
uv run factor analyze SPY --model ff4_mom
# Specify date range
uv run factor analyze SPY --start 2020-01-01 --end 2024-01-01Example output:
Factor Regression: SPY (1993-02-28 to 2025-11-30)
ββββββββββββββββ³ββββββββββββββ³βββββββββββ³ββββββ
β Factor β Coefficient β t-stat β Sig β
β‘ββββββββββββββββββββββββββββββββββββββββββββββ©
β Alpha (ann.) β -0.06% β (-0.24) β β
β Mkt-RF β 0.985 β (205.33) β *** β
β SMB β -0.172 β (-25.24) β *** β
β HML β 0.028 β (4.34) β *** β
ββββββββββββββββ΄ββββββββββββββ΄βββββββββββ΄ββββββ
RΒ²: 0.991 | Adj RΒ²: 0.991 | N: 394 monthly
# UK ETF (London Stock Exchange)
uv run factor analyze VWRL.L --model ff3 --region developed
# Use developed market factors for non-US funds
uv run factor analyze ISF.L --region developed# Emerging market ETF with FF5 model
uv run factor analyze VWO --model ff5 --region emerging
# Carhart model (includes momentum)
uv run factor analyze EEM --model carhart --region emergingNote: Ken French only provides monthly data for emerging markets. Daily frequency is not available and will return an error.
Use the global region to analyze funds against a weighted combination of regional factors:
# Global factors (weighted combination of US, Developed, and Emerging)
uv run factor analyze VT --region global
# Daily frequency (US + Developed only, no EM daily data available)
uv run factor analyze VT --region global --frequency dailyGlobal factor weights:
| Frequency | US | Developed | Emerging |
|---|---|---|---|
| Monthly | 60% | 30% | 10% |
| Daily | 65% | 35% | β |
The global region combines factor data from multiple regions using weighted averages, which is useful for analyzing globally diversified funds like VT (Vanguard Total World Stock ETF).
If Yahoo Finance has issues with certain tickers, you can use Stooq as an alternative data source:
# Use Stooq for price data
uv run factor analyze VWRL.L --source stooq --region developed
# Works with all commands
uv run factor portfolio VWRL.L:60 ISF.L:40 --source stooq
uv run factor compare SPY QQQ --source stooq
uv run factor rolling SPY --source stooqStooq automatically converts ticker formats:
- UK tickers:
.Lβ.UK(e.g.,VWRL.LβVWRL.UK) - US tickers: adds
.USsuffix (e.g.,SPYβSPY.US)
# Compare funds side by side
uv run factor compare SPY QQQ IWM
# Sort by alpha
uv run factor compare SPY QQQ IWM --sort alpha
# Sort by a specific factor loading
uv run factor compare SPY QQQ IWM --sort SMBExample output:
Fund Comparison (Fama-French 3-Factor)
ββββββββββ³βββββββββββββββ³βββββββββββ³ββββββββββββ³ββββββββββββ³ββββββββ
β Ticker β Alpha (ann.) β Mkt-RF β SMB β HML β RΒ² β
β‘βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ©
β QQQ β +1.77% β 1.24 *** β 0.12 *** β -0.65 *** β 0.877 β
β SPY β -0.06% β 0.99 *** β -0.17 *** β 0.03 *** β 0.991 β
β IWM β -1.54% *** β 1.01 *** β 0.82 *** β 0.25 *** β 0.981 β
ββββββββββ΄βββββββββββββββ΄βββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββ
Track how factor exposures change over time:
# 36-month rolling window (default)
uv run factor rolling SPY
# Custom window size
uv run factor rolling SPY --window 24
# Track a specific factor
uv run factor rolling SPY --factor Mkt-RFAnalyze factor exposures of a weighted portfolio:
# Specify holdings with weights as percentages (must sum to 100)
uv run factor portfolio SPY:60 QQQ:40
# Or use decimals (must sum to 1.0)
uv run factor portfolio SPY:0.6 QQQ:0.4
# Equal-weighted portfolio
uv run factor portfolio SPY QQQ IWM --equal-weight
# With custom name and options
uv run factor portfolio SPY:50 QQQ:30 IWM:20 \
--model ff5 --name "US Equity Mix"
# Specify date range
uv run factor portfolio SPY:60 QQQ:40 --start 2020-01-01Example output:
Portfolio Composition
ββββββββββ³βββββββββββββββββββββββββββββββββββββββ³βββββββββ
β Ticker β Name β Weight β
β‘βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ©
β SPY β SPDR S&P 500 ETF Trust β 60.0% β
β QQQ β Invesco QQQ Trust β 40.0% β
ββββββββββ΄βββββββββββββββββββββββββββββββββββββββ΄βββββββββ
Factor Regression: Portfolio (2010-01-31 to 2025-01-31)
ββββββββββββββββ³ββββββββββββββ³βββββββββββ³ββββββ
β Factor β Coefficient β t-stat β Sig β
β‘ββββββββββββββββββββββββββββββββββββββββββββββ©
β Alpha (ann.) β +0.72% β (1.42) β β
β Mkt-RF β 1.064 β (115.29) β *** β
β SMB β -0.103 β (-7.89) β *** β
β HML β -0.215 β (-15.95) β *** β
ββββββββββββββββ΄ββββββββββββββ΄βββββββββββ΄ββββββ
RΒ²: 0.989 | Adj RΒ²: 0.989 | N: 180 monthly
Compare up to 3 portfolios side by side across factor regressions, returns metrics, and rolling returns:
# Compare two portfolios
uv run factor compare-portfolios "SPY:60 QQQ:40" "VTI:100"
# Compare three portfolios with custom names
uv run factor compare-portfolios "SPY:60 QQQ:40" "VTI:100" "IWM:50 BND:50" \
--name "Growth" --name "Simple" --name "Balanced"
# With different model and rolling window
uv run factor compare-portfolios "SPY:60 QQQ:40" "VTI:70 BND:30" \
--model ff5 --rolling-window 24Each portfolio is specified as a quoted string with space-separated TICKER:WEIGHT pairs.
Example output:
Factor Regression Comparison (Fama-French 3-Factor)
ββββββββββββββββββββ³ββββββββββββββ³βββββββββββββ
β Factor β Growth β Simple β
β‘ββββββββββββββββββββββββββββββββββββββββββββββ©
β Alpha (ann.) β +0.72% β +0.12% β
β Mkt-RF (Market) β 1.06 *** β 1.01 *** β
β SMB (Size) β -0.10 *** β -0.05 *** β
β HML (Value) β -0.22 *** β 0.02 β
β R-squared β 0.989 β 0.998 β
ββββββββββββββββββββ΄ββββββββββββββ΄βββββββββββββ
Returns Comparison
ββββββββββββββββββββββ³ββββββββββββ³βββββββββββ
β Metric β Growth β Simple β
β‘ββββββββββββββββββββββββββββββββββββββββββββ©
β Total Return β +245.2% β +198.1% β
β Annualized Return β +12.3% β +10.5% β
β Annualized Vol. β 15.2% β 13.8% β
β Sharpe Ratio β 0.81 β 0.76 β
β Max Drawdown β -22.3% β -19.1% β
ββββββββββββββββββββββ΄ββββββββββββ΄βββββββββββ
Rolling 12-Period Returns Summary
ββββββββββββββ³ββββββββββββ³βββββββββββ
β Statistic β Growth β Simple β
β‘ββββββββββββββββββββββββββββββββββββ©
β Mean β +14.2% β +11.8% β
β Std Dev β 12.5% β 10.2% β
β Min β -18.3% β -15.1% β
β Max β +42.1% β +35.6% β
ββββββββββββββ΄ββββββββββββ΄βββββββββββ
# Search by name
uv run factor search "vanguard global"
# Search by ticker
uv run factor search VWRL
# Search by ISIN
uv run factor search GB00B4L5F271Visualize factor returns over time as a chart:
# Basic: cumulative returns (growth of $1) for all FF3 factors
uv run factor plot
# Exclude market factor (useful since it dominates other factors)
uv run factor plot --no-market
# Plot specific factors only
uv run factor plot --factors Mkt-RF --factors SMB
# Rolling returns with 24-month window
uv run factor plot --chart-type rolling --window 24
# Raw periodic returns
uv run factor plot --chart-type returns
# FF5 model with date range, save to custom file without opening
uv run factor plot --model ff5 --start 2010-01-01 --output factors.png --no-openChart types:
cumulative(default): Growth of $1 over timereturns: Raw periodic returnsrolling: Rolling N-period returns
The chart is saved as a PNG file and automatically opened in your system's default image viewer. Use --no-open to skip opening.
Create a relative strength chart comparing two funds:
# Basic comparison - SPY vs QQQ
uv run factor telltale SPY QQQ
# With date range
uv run factor telltale VTI BND --start 2020-01-01
# UK ETFs via Stooq
uv run factor telltale VWRL.L ISF.L --source stooq
# Log scale for long time periods
uv run factor telltale SPY IWM --log-scale --output spy_vs_iwm.pngThe telltale chart shows the normalized price ratio of the first fund to the second:
- Line rising: First fund is outperforming the second
- Line falling: Second fund is outperforming the first
- Starting point: Always normalized to 1.0
The chart displays the total outperformance percentage at the end of the period.
Calculate the implied equity risk premium (ERP) for the S&P 500 using Damodaran's methodology:
# Basic calculation with live market data
uv run factor erp
# Show detailed cash flow projections
uv run factor erp --details
# Skip fetching buyback yield, use 2.5% default
uv run factor erp --use-default-buyback
# Override specific inputs
uv run factor erp --base-growth 0.08 --buyback-yield 0.03
# Full custom inputs with details
uv run factor erp -g 0.10 -b 0.025 -t 0.04 -r 0.045 --detailsOptions:
| Option | Short | Description |
|---|---|---|
--dividend-yield |
-d |
Override dividend yield (decimal, e.g., 0.015) |
--buyback-yield |
-b |
Override buyback yield (decimal, e.g., 0.025) |
--base-growth |
-g |
Override base growth rate (decimal, e.g., 0.10) |
--terminal-growth |
-t |
Override terminal growth rate (decimal) |
--risk-free-rate |
-r |
Override risk-free rate (decimal, e.g., 0.045) |
--use-default-buyback |
Skip buyback fetch, use 2.5% default | |
--details |
Show detailed cash flow projections |
The calculation uses a 2-stage dividend discount model with tapering growth rates. It solves for the discount rate that equates the present value of expected cash flows (dividends + buybacks) to the current S&P 500 index level, then subtracts the risk-free rate to get the implied ERP.
uv run factor modelsOutput:
Available Factor Models
βββββββββββ³βββββββββββββββββββββββ³βββββββββββββββββββββββββββββ
β Model β Name β Factors β
β‘ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ©
β ff3 β Fama-French 3-Factor β Mkt-RF, SMB, HML β
β carhart β Carhart 4-Factor β Mkt-RF, SMB, HML, Mom β
β ff5 β Fama-French 5-Factor β Mkt-RF, SMB, HML, RMW, CMA β
β ff4_mom β 4-Factor + Momentum β Mkt-RF, SMB, HML, RMW, Mom β
βββββββββββ΄βββββββββββββββββββββββ΄βββββββββββββββββββββββββββββ
Factor data is cached locally to speed up subsequent analyses:
# View cache info
uv run factor cache --info
# Clear cache
uv run factor cache --clear| Option | Short | Description |
|---|---|---|
--model |
-m |
Factor model: ff3, ff5, carhart, ff4_mom (default: ff3) |
--region |
-r |
Factor region: us, developed, emerging, global (default: us) |
--start |
-s |
Start date (YYYY-MM-DD) |
--end |
-e |
End date (YYYY-MM-DD) |
--frequency |
-f |
Data frequency: monthly, daily (default: monthly) |
--source |
-S |
Price data source: yfinance, stooq (default: yfinance) |
--sort |
Sort comparison by factor (e.g., alpha, Mkt-RF, SMB) |
|
--window |
-w |
Rolling window size in periods (default: 36 for rolling, 12 for plot) |
--rolling-window |
-w |
Rolling window for compare-portfolios (default: 12) |
--equal-weight |
-E |
Use equal weights for portfolio holdings |
--name |
-n |
Custom name for portfolio (repeatable in compare-portfolios) |
--chart-type |
-t |
Chart type for plot: cumulative, returns, rolling (default: cumulative) |
--factors |
-F |
Specific factors to plot (repeatable, e.g., --factors Mkt-RF --factors SMB) |
--output |
-o |
Output file path for plot/telltale (default: factor_plot.png or telltale.png) |
--no-open |
Skip opening the chart file after creation | |
--no-market |
Exclude market factor (Mkt-RF) from plot | |
--log-scale |
-l |
Use logarithmic scale for y-axis (telltale command) |
| Factor | Interpretation |
|---|---|
| Mkt-RF | Market beta. 1.0 = market-like, >1 = aggressive, <1 = defensive |
| SMB | Size. Positive = small-cap tilt, negative = large-cap tilt |
| HML | Value. Positive = value tilt, negative = growth tilt |
| Mom | Momentum. Positive = momentum strategy, negative = contrarian |
| RMW | Profitability. Positive = quality/profitable companies |
| CMA | Investment. Positive = conservative, negative = aggressive |
***p < 0.01 (highly significant)**p < 0.05 (significant)*p < 0.10 (marginally significant)
Alpha represents the annualized excess return not explained by the factor model. A statistically significant positive alpha suggests the fund outperforms its factor exposures.
- Fund prices:
- Yahoo Finance via yfinance (default)
- Stooq via
--source stooq(alternative)
- Factor data: Ken French Data Library
# Install dev dependencies
uv sync --all-extras
# Run tests
uv run pytest tests/ -v
# Run with coverage
uv run pytest tests/ --cov=factor_cli