QuantConnect

Fama French

Introduction

The Fama-French Factors dataset by QuantConnect gives you the academic factor returns that quants use to explain where a portfolio's performance actually came from: the market, size, value, profitability, investment and momentum premiums, plus the risk-free rate. It covers the whole US equity market, goes back to January 1998, and updates daily.

QuantConnect computes the factors instead of republishing them. Every June the universe is sorted into portfolios on size, book-to-market, operating profitability and asset growth using company financials from US Fundamental Data, momentum is ranked daily on the trailing year, and each factor is the value-weighted return of the long portfolios minus the short ones, priced off the US Equity Coarse Universe. The definitions are the ones published by the Kenneth R. French Data Library. The reason for computing them is timing: the library re-publishes on a monthly cycle, so its own values trail the present by about two months, which is too late to trade on. These arrive the day after the session they measure.

Because the inputs are different, these are close to the library's own numbers but not identical to them. Measured against the published daily factors:

FactorCorrelation, 2000 to 2026Correlation, 2020 to 2026Beta
Mkt-RF0.9950.9990.98
Momentum0.9730.9880.90
HML0.9560.9800.90
SMB0.9490.9880.95
RMW0.8190.9310.98
CMA0.8150.9680.88

The two columns differ because the input data gets materially better over time, and RMW and CMA are the ones that feel it. The recent column is the one live trading operates in. Use these factors to read a regime, to measure what your portfolio is exposed to, or to attribute your returns; if you need the exact published values for a paper, take them from the library. The IsEstimate property tells you which rows QuantConnect computed.

For more information about the Fama French dataset, including CLI commands and pricing, see the dataset listing.

About the Provider

The Kenneth R. French Data Library is maintained by Kenneth R. French at the Tuck School of Business at Dartmouth College. It is the canonical public source for the Fama-French factors, built from the CRSP and Compustat databases and refreshed as those databases update. The data is published free of charge.

QuantConnect computes the factors from its own fundamental and price data, to those same definitions, so they are delivered to your algorithm on the date each value became available, with no look-ahead.

Getting Started

The following snippet demonstrates how to request data from the Fama-French Factors dataset:

self.fama_french = self.add_data(FamaFrench, "FF", Resolution.DAILY).symbol
_famaFrench = AddData<FamaFrench>("FF", Resolution.Daily).Symbol;

Data Summary

The following table describes the dataset properties:

PropertyValue
Start DateJanuary 1998
Data Points7,185
Asset Coverage1 US Equity Market Series
ResolutionDaily
TimezoneAmerica/New_York

Requesting Data

To add Fama-French Factors data to your algorithm, call the AddDataadd_data method. The dataset is unlinked, a single market-wide series, so instead of a security Symbol you pass the fixed identifier "FF". Save a reference to the dataset Symbol so you can access the data later in your algorithm.

The factors are a signal rather than a tradeable instrument, so add a separate tradeable security if you want to place orders.

class FamaFrenchDataAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        self.set_start_date(2020, 1, 1)
        self.set_end_date(2020, 12, 31)
        self.set_cash(100000)

        self.spy = self.add_equity("SPY", Resolution.DAILY).symbol
        self.fama_french = self.add_data(FamaFrench, "FF", Resolution.DAILY).symbol
public class FamaFrenchDataAlgorithm : QCAlgorithm
{
    private Symbol _spy, _famaFrench;

    public override void Initialize()
    {
        SetStartDate(2020, 1, 1);
        SetEndDate(2020, 12, 31);
        SetCash(100000);

        _spy = AddEquity("SPY", Resolution.Daily).Symbol;
        _famaFrench = AddData<FamaFrench>("FF", Resolution.Daily).Symbol;
    }
}

Accessing Data

To get the current Fama-French Factors data, index the current Slice with the dataset Symbol. Slice objects deliver unique events to your algorithm as they happen, but the Slice may not contain data for your dataset at every time step. To avoid issues, check if the Slice contains the data you want before you index it.

def on_data(self, slice: Slice) -> None:
    if slice.contains_key(self.fama_french):
        factors = slice[self.fama_french]
        self.log(f"Momentum at {slice.time}: {factors.momentum}")
public override void OnData(Slice slice)
{
    if (slice.ContainsKey(_famaFrench))
    {
        var factors = slice[_famaFrench];
        Log($"Momentum at {slice.Time}: {factors.Momentum}");
    }
}

Every factor is nullable, because the source marks a missing observation with a sentinel that the processor writes as an empty cell, so check for a missing value before you act on it. The Value property carries the value factor (HML), and the IsEstimate property tells you whether the row was published by the Ken French Data Library or reconstructed by QuantConnect.

Historical Data

To get historical Fama-French Factors data, call the History method with the dataset Symbol. If there is no data in the period you request, the history result is empty.

# DataFrame
history_df = self.history(self.fama_french, 100, Resolution.DAILY)

# Dataset objects
history_bars = self.history[FamaFrench](self.fama_french, 100, Resolution.DAILY)
var history = History<FamaFrench>(_famaFrench, 100, Resolution.Daily);

For more information about historical data, see History Requests.

Remove Subscriptions

To remove your subscription to Fama-French Factors data, call the RemoveSecurity method.

self.remove_security(self.fama_french)
RemoveSecurity(_famaFrench);

Example Applications

The Fama-French Factors dataset lets you measure and time your exposure to the classic equity style premiums. Examples include:

  • Regressing a strategy's daily returns on the factors to split its performance into alpha and factor bets.
  • Timing a broad equity position with the momentum factor, holding the risk only while the regime stays positive.
  • Rotating between a value and a growth tilt on the recent trend in HML.
  • Using the market excess return and the risk-free rate to compute Sharpe ratios and CAPM betas against your own equity curve.

Classic Algorithm Example

The following example algorithm holds the market while the Fama-French momentum factor is positive and stays in cash otherwise:

from AlgorithmImports import *

class FamaFrenchFactorsAlgorithm(QCAlgorithm):
    def initialize(self):
        self.set_start_date(2020, 1, 1)
        self.set_end_date(2020, 12, 31)
        self.set_cash(100000)

        self.spy = self.add_equity("SPY", Resolution.DAILY).symbol
        self.fama_french = self.add_data(FamaFrench, "FF", Resolution.DAILY).symbol

        history = self.history[FamaFrench](self.fama_french, 60, Resolution.DAILY)
        self.debug(f"We got {len(list(history))} items from our history request")

    def on_data(self, slice):
        if not slice.contains_key(self.fama_french):
            return

        momentum = slice[self.fama_french].momentum
        if momentum is None:
            return

        # Hold the market while the momentum factor is positive
        if momentum > 0:
            self.set_holdings(self.spy, 1)

        # Step aside while it is negative
        else:
            self.liquidate(self.spy)
public class FamaFrenchFactorsAlgorithm : QCAlgorithm
{
    private Symbol _spy, _famaFrench;

    public override void Initialize()
    {
        SetStartDate(2020, 1, 1);
        SetEndDate(2020, 12, 31);
        SetCash(100000);

        _spy = AddEquity("SPY", Resolution.Daily).Symbol;
        _famaFrench = AddData<FamaFrench>("FF", Resolution.Daily).Symbol;

        var history = History<FamaFrench>(_famaFrench, 60, Resolution.Daily);
        Debug($"We got {history.Count()} items from our history request");
    }

    public override void OnData(Slice slice)
    {
        if (!slice.ContainsKey(_famaFrench))
        {
            return;
        }

        var momentum = slice.Get<FamaFrench>(_famaFrench).Momentum;
        if (!momentum.HasValue)
        {
            return;
        }

        // Hold the market while the momentum factor is positive
        if (momentum > 0)
        {
            SetHoldings(_spy, 1);
        }
        // Step aside while it is negative
        else
        {
            Liquidate(_spy);
        }
    }
}

Framework Algorithm Example

The following example algorithm uses the Algorithm Framework to trade a manually selected universe from Fama-French Factors data. An alpha model subscribes to the factors and emits insights from the sign of the momentum factor, holding the universe while the momentum regime is positive:

from AlgorithmImports import *

class FamaFrenchFrameworkAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2020, 1, 1)
        self.set_end_date(2020, 12, 31)
        self.set_cash(100000)

        self.universe_settings.resolution = Resolution.DAILY

        symbols = [Symbol.create("SPY", SecurityType.EQUITY, Market.USA)]
        self.set_universe_selection(ManualUniverseSelectionModel(symbols))

        self.add_alpha(FamaFrenchAlphaModel(self))

        self.set_portfolio_construction(EqualWeightingPortfolioConstructionModel())
        self.set_execution(ImmediateExecutionModel())


class FamaFrenchAlphaModel(AlphaModel):

    def __init__(self, algorithm: QCAlgorithm) -> None:
        self._tradable_symbols = []

        # One subscription carries every factor, keyed by a fixed identifier
        self._fama_french = algorithm.add_data(FamaFrench, "FF", Resolution.DAILY).symbol

        history = algorithm.history[FamaFrench](self._fama_french, 60, Resolution.DAILY)
        algorithm.debug(f"We got {len(list(history))} items from our history request")

    def update(self, algorithm: QCAlgorithm, data: Slice) -> List[Insight]:
        if self._fama_french not in data:
            return []

        momentum = data[self._fama_french].momentum
        if momentum is None:
            return []

        # A positive momentum factor is a risk-on regime, so hold the universe and step aside otherwise
        direction = InsightDirection.UP if momentum > 0 else InsightDirection.FLAT
        return [Insight.price(symbol, timedelta(days=7), direction) for symbol in self._tradable_symbols]

    def on_securities_changed(self, algorithm: QCAlgorithm, changes: SecurityChanges) -> None:
        for security in changes.added_securities:
            if security.symbol != self._fama_french:
                self._tradable_symbols.append(security.symbol)

        for security in changes.removed_securities:
            if security.symbol in self._tradable_symbols:
                self._tradable_symbols.remove(security.symbol)
public class FamaFrenchFrameworkAlgorithm : QCAlgorithm
{
    public override void Initialize()
    {
        SetStartDate(2020, 1, 1);
        SetEndDate(2020, 12, 31);
        SetCash(100000);

        UniverseSettings.Resolution = Resolution.Daily;

        var symbols = new[] { QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA) };
        SetUniverseSelection(new ManualUniverseSelectionModel(symbols));

        AddAlpha(new FamaFrenchAlphaModel(this));

        SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());
        SetExecution(new ImmediateExecutionModel());
    }
}

public class FamaFrenchAlphaModel : AlphaModel
{
    private readonly List<Symbol> _tradableSymbols = new();
    private readonly Symbol _famaFrench;

    public FamaFrenchAlphaModel(QCAlgorithm algorithm)
    {
        // One subscription carries every factor, keyed by a fixed identifier
        _famaFrench = algorithm.AddData<FamaFrench>("FF", Resolution.Daily).Symbol;

        var history = algorithm.History<FamaFrench>(_famaFrench, 60, Resolution.Daily);
        algorithm.Debug($"We got {history.Count()} items from our history request");
    }

    public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
    {
        if (!data.ContainsKey(_famaFrench))
        {
            return Enumerable.Empty<Insight>();
        }

        var momentum = data.Get<FamaFrench>(_famaFrench).Momentum;
        if (momentum == null)
        {
            return Enumerable.Empty<Insight>();
        }

        // A positive momentum factor is a risk-on regime, so hold the universe and step aside otherwise
        var direction = momentum > 0 ? InsightDirection.Up : InsightDirection.Flat;
        return _tradableSymbols.Select(symbol => Insight.Price(symbol, TimeSpan.FromDays(7), direction));
    }

    public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
    {
        foreach (var security in changes.AddedSecurities)
        {
            if (security.Symbol != _famaFrench)
            {
                _tradableSymbols.Add(security.Symbol);
            }
        }

        foreach (var security in changes.RemovedSecurities)
        {
            _tradableSymbols.Remove(security.Symbol);
        }
    }
}

Data Point Attributes

The Fama-French Factors dataset provides FamaFrench objects, which have the following attributes:

You can also see our Videos. You can also get in touch with us via Discord.

Did you find this page helpful?

Contribute to the documentation: