Documentation

Fama-French factors

QuantCraft can load global Fama-French factor return datasets alongside OHLCV price bars during a backtest or forward run. These are monthly factor returns (FF3 and FF5), fetched once per run — not per ticker. Per-ticker financial statements are a separate feature; see Fundamentals.

This page covers:

  • How Fama-French data differs from fundamentals and from the Trade chart Factor Exposure UI
  • Requirements and how to enable the feature in run modals
  • How to read factor rows in your strategy code

Fama-French vs fundamentals

FundamentalsFama-French factors
DataPer-ticker files (balance sheet, earnings, …)One global monthly factor dataset
Algo accessfundamentals callback argument (fundamentals.current(...))fama_french.qc module singleton
Callback kwargYes — on_bar(..., fundamentals=...)No — read fama_french.qc inside any callback

OHLCV symbols, fundamentals bundles, and Fama-French factors are independent — you can enable any combination in the run modal.


Not the Trade chart Factor Exposure tab

The Trade chart Fundamentals → Factor Exposure tab runs client-side FF3/FF5 regression against chart OHLC. That UI is out of scope for algos. Strategies receive raw monthly factor return rows only (ff3_monthly, ff5_monthly).


Requirements

  • Premium QuantCraft subscription, or free-data access when that override is enabled (same gate as fundamentals).
  • Signed in — a valid QuantCraft session token is sent when the checkbox is enabled.
  • UI: Include Fama-French factors (global) is on by default when the data gate allows it — on Run backtest (Symbol & data), bulk forward, and Chart forward test. Uncheck to omit factor data for that run.

Configure from Test (Running code) for backtests, or from Algo Trading Parameters on chart forward runs.


Quick start

After the engine loads factor data, read the singleton inside a callback:

from quantcraft import fama_french _ff3 = None # oldest-first rows, built once def factor_row_before(bar_t): """Latest FF3 month that ended before this bar's month (no look-ahead).""" global _ff3 if _ff3 is None: _ff3 = fama_french.qc.rows_chronological("ff3_monthly") t = str(bar_t) bar_yyyymm = t[:4] + t[5:7] # works for "2024-01-02" and ISO timestamps rows = [r for r in _ff3 if r["yyyymm"] < bar_yyyymm] return rows[-1] if rows else None def on_bar(bar_index, bar, fundamentals=None, symbol=None): if fama_french.qc is None: return # checkbox off or load failed row = factor_row_before(bar["t"]) if row: mkt_rf = float(row.get("Mkt-RF", 0)) smb = float(row.get("SMB", 0)) hml = float(row.get("HML", 0))

Backtests: don't use latest_row inside callbacks. The factor data is loaded once, as of today, and is not filtered to the bar being simulated. latest_row("ff3_monthly") always returns the newest month in the whole dataset — on a 2015 bar that is a present-day month, which is look-ahead bias. Pick the month from the bar's own date (as above) or with qc.row("ff3_monthly", "YYYYMM"). latest_row is fine in a forward run, where "latest" really is the present.

Preferred pattern: from quantcraft import fama_french, then read fama_french.qc inside callbacks (on_init, on_bar, on_tick, …). Do not use from quantcraft.fama_french import qc at module top level — that captures None at import time before the engine loads data.

Alternatives: the script global qc_fama_french (injected into your root algo file), or import quantcraft.process.runtime as rt then rt.qc_fama_french in callbacks — all the same object when set; prefer fama_french.qc in new code.

The older from ide.factors_module import … path still resolves for existing strategies; write new code against quantcraft.fama_french. quantcraft.fama_french is a module alias, so fama_french.qc reads the live per-session value — see Module handles vs imported names.


Reading factor rows

Monthly data lives in datasets keyed by YYYYMM strings (e.g. "192607"), not list indices.

MethodPurpose
qc.row("ff3_monthly", "202604")One specific month, or None
qc.rows_chronological("ff5_monthly")Oldest-first list; each item includes yyyymm. Empty list for an unknown key.
qc.latest_row("ff3_monthly")Most recent month in the whole dataset as { yyyymm, Mkt-RF, SMB, HML, RF, … }, or None. Not bar-aware — forward runs only (see the warning above).
qc.dataset(key)Raw dataset dict for that key (columns, rows, …), or None when the key is missing
qc.raw / qc.datasets / qc.updated_atFull payload, raw["datasets"], and the parsed UTC update time (or None)

Full import surface (quantcraft.fama_french): QcFamaFrench(token, *, base_url=None) — builds and fetches immediately (you normally never construct it; the run does); load_qc_fama_french_from_config(cfg); and the plain helpers factor_rows_chronological(dataset) / latest_factor_row(dataset), which work on a raw dataset dict.

Dataset ids:

KeyContents
ff3_monthlyFama-French 3-factor monthly returns
ff5_monthlyFama-French 5-factor monthly returns

Typical FF3 keys: Mkt-RF, SMB, HML, RF. FF5 adds RMW, CMA. Values are decimal strings (percent ÷ 100) — use float(...) for arithmetic.

# Helpers (recommended) history = fama_french.qc.rows_chronological("ff5_monthly") # oldest-first jan_2024 = fama_french.qc.row("ff5_monthly", "202401") # Raw dict access (keys are YYYYMM strings — not rows[0] / rows[-1]) ff3_rows = fama_french.qc.datasets["ff3_monthly"]["rows"] july_1926 = ff3_rows["192607"]

Enabling in the IDE

Run backtest

In Test → Run backtest → Symbol & data, tick Include Fama-French factors (global) (on by default when the data gate allows it). Requires sign-in. The engine loads data before your on_init() runs.

There is no factors= callback argument on on_tick / on_bar. Import quantcraft.fama_french and use fama_french.qc.

Chart forward test

In Trade → Algo Trading Parameters, use the same Include Fama-French factors (global) checkbox (default on for Premium).

Bulk forward / QuantCloud

In the bulk forward New Run modal (AutoTrading or QuantCloud), use Include Fama-French factors (global) on the Schedule & data tab (on by default). See QuantCloud and Run presets (.qcs).


Run presets (.qcs)

Saved run settings can include:

{ "includeFactors": true }

Loading a preset with includeFactors: true requires Premium; the free tier clears the flag on load. See Run presets (.qcs).


Common mistakes

  • rows[0] / rows[-1] — invalid. dataset["rows"] is a dict keyed by "YYYYMM", not a list. Use row or rows_chronological (or latest_row in forward runs).
  • from quantcraft.fama_french import qc at file top — captures None before the engine loads data.
  • Expecting a factors= callback — use fama_french.qc inside on_init / on_bar / on_tick instead.
  • latest_row in a backtest — returns today's newest month on every historical bar (look-ahead). Select the month from the bar's date.

Failure behavior

If the checkbox is on but the fetch fails, the run keeps going, prints a message — factors load failed: … (backtest / Debug Test), forward factors load failed: … or bulk factors load failed: … — and leaves fama_french.qc = None. Check the Output panel. Always guard before use.