QuantConnect
Fama French
Introduction
The Fama-French Factors dataset by QuantConnect tracks the returns of the Fama-French five-factor model, the momentum factor, and the risk-free rate for US Equities. The data covers the US Equity market, starts in January 1998, and is delivered on a daily frequency. This dataset is created by computing each factor from US Equity fundamentals and prices, built to the published Fama-French definitions.
For more information about the Fama French dataset, including CLI commands and pricing, see the dataset listing.
About the Provider
QuantConnect was founded in 2012 to serve quants everywhere with the best possible algorithmic trading technology. Seeking to disrupt a notoriously closed-source industry, QuantConnect takes a radically open-source approach to algorithmic trading. Through the QuantConnect web platform, more than 50,000 quants are served every month.
Requesting Data
To add Fama-French Factors data to your algorithm, call the AddDataadd_data method. Save a reference to the dataset Symbol so you can access the data later in your algorithm.
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}");
}
}
To iterate through all of the dataset objects in the current Slice, call the Getget method.
def on_data(self, slice: Slice) -> None:
for dataset_symbol, data_point in slice.get(FamaFrench).items():
self.log(f"{dataset_symbol} momentum at {slice.time}: {data_point.momentum}") public override void OnData(Slice slice)
{
foreach (var kvp in slice.Get<FamaFrench>())
{
var datasetSymbol = kvp.Key;
var dataPoint = kvp.Value;
Log($"{datasetSymbol} momentum at {slice.Time}: {dataPoint.Momentum}");
}
}
Historical Data
To get historical Fama-French Factors data, call the Historyhistory 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.
Example Applications
The Fama-French Factors dataset enables you to measure and time factor exposure in your strategies. Examples include the following strategies:
- Regressing strategy returns on the factors to separate alpha from factor exposure
- Rotating between value and growth tilts based on recent value factor returns
- Holding equity risk only when the momentum factor is trending up
Classic Algorithm Example
The following example algorithm buys SPY when the Fama-French momentum factor is positive. Otherwise, it holds cash.
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 emits an up insight for SPY when the Fama-French momentum factor is positive. Otherwise, it emits a flat insight.
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 = []
# Add the Fama-French factors data.
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 []
# Emit an up insight when momentum is positive, otherwise a flat insight.
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)
{
// Add the Fama-French factors data.
_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>();
}
// Emit an up insight when momentum is positive, otherwise a flat insight.
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);
}
}
}