Skip to content

joecorkerton/investing-factors-cli

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

18 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Factor CLI

A command-line tool for analyzing funds and ETFs using factor regressions (Fama-French, Carhart models).

Caveat emptor

This repo was entirely written by claude code. Accuracy or quality are not guaranteed.

Features

  • 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 (.L suffix) 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

Installation

Requires Python 3.10+.

asdf install

# Install with uv (recommended)
uv sync

# Or install with pip
pip install -e .

Usage

Analyze a Single Fund

# 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-01

Example 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

Analyze UK/International ETFs

# 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

Analyze Emerging Market ETFs

# 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 emerging

Note: Ken French only provides monthly data for emerging markets. Daily frequency is not available and will return an error.

Global Factor Analysis

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 daily

Global 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).

Alternative Data Source (Stooq)

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 stooq

Stooq automatically converts ticker formats:

  • UK tickers: .L β†’ .UK (e.g., VWRL.L β†’ VWRL.UK)
  • US tickers: adds .US suffix (e.g., SPY β†’ SPY.US)

Compare Multiple Funds

# 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 SMB

Example 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 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”˜

Rolling Factor Analysis

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-RF

Portfolio Analysis

Analyze 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-01

Example 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 Multiple Portfolios

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 24

Each 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 for Funds

# Search by name
uv run factor search "vanguard global"

# Search by ticker
uv run factor search VWRL

# Search by ISIN
uv run factor search GB00B4L5F271

Plot Factor Returns

Visualize 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-open

Chart types:

  • cumulative (default): Growth of $1 over time
  • returns: Raw periodic returns
  • rolling: 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.

Compare Two Funds (Telltale Chart)

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.png

The 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 Implied Equity Risk Premium

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 --details

Options:

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.

List Available Models

uv run factor models

Output:

                    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 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Manage Cache

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

Options Reference

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)

Interpreting Results

Factor Loadings

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

Significance Levels

  • *** p < 0.01 (highly significant)
  • ** p < 0.05 (significant)
  • * p < 0.10 (marginally significant)

Alpha

Alpha represents the annualized excess return not explained by the factor model. A statistically significant positive alpha suggests the fund outperforms its factor exposures.

Data Sources

Development

# Install dev dependencies
uv sync --all-extras

# Run tests
uv run pytest tests/ -v

# Run with coverage
uv run pytest tests/ --cov=factor_cli

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages