QuantJourney Backtester

QuantJourney Backtester

Share product feedback

Thank you.

Your note is now in the QuantJourney inbox.

backtester/portfolio/calc/attribution.py

attribution.py:
OLS factor exposure and contribution decomposition.

This file provides a compact factor attribution layer: estimate exposures by OLS, extract alpha, multiply factor returns by exposures and aggregate excess-return contributions.

Import from backtester.portfolio.calc import attribution

When To Read This

  • 01
    You want to show whether returns came from factor exposure or residual alpha.
  • 02
    You are connecting factor datasets from the API to local strategy reports.
  • 03
    You need a simple OLS attribution baseline before adding richer risk models.

File Anatomy

  • OLS exposure estimation through pseudo-inverse, robust to collinearity in small examples.
  • Optional intercept renamed to alpha.
  • Factor attribution by matrix multiplication: factor returns dot transposed exposures.
  • Performance attribution by summing excess-return contribution columns into Total.

Data Contract

Inputs

  • returns: DataFrame dates x instruments or strategies.
  • factor_returns: DataFrame dates x factor names.
  • factor_exposures: DataFrame instruments x factor names for attribution.

Outputs

  • Exposure DataFrame indexed by instrument, columns are alpha and factor names.
  • Alpha Series when intercept is present.
  • Contribution DataFrames by date and instrument/strategy.

Invariants

  • Returns and factor returns are inner-aligned by date before regression.
  • Factor names must overlap between factor_returns and factor_exposures.
  • This is a linear attribution baseline, not a full risk model.

Public API And Key Internals

compute_factor_exposures_ols

function
compute_factor_exposures_ols(returns, factor_returns, *, add_intercept=True)

Fits factor coefficients per instrument using OLS and pseudo-inverse.

Returns

pd.DataFrame indexed by instrument.

compute_factor_alpha

function
compute_factor_alpha(returns, factor_returns, *, add_intercept=True)

Returns the intercept column from OLS exposures, or zero if no intercept is used.

Returns

pd.Series.

compute_factor_attribution

function
compute_factor_attribution(factor_returns, factor_exposures)

Converts factor returns and exposures into contribution time series.

Returns

pd.DataFrame.

compute_performance_attribution

function
compute_performance_attribution(excess_returns)

Adds a Total column to excess return contribution components.

Returns

pd.DataFrame.

Implementation Notes

  • The pseudo-inverse keeps the function usable in small examples where factors may be correlated.
  • The function is intentionally transparent; users can replace it with a more sophisticated risk model later.
  • The attribution output is suitable for charts, tables and report appendix sections.

Code Walkthrough

Estimate factor exposures and contribution paths

This is the minimal factor workflow: estimate exposures, read alpha, then attribute factor returns.

attribution_usage.py Python
from backtester.portfolio.calc import attribution

exposures_ols = attribution.compute_factor_exposures_ols(
    returns=strategy_returns,
    factor_returns=factor_returns,
)
alpha = attribution.compute_factor_alpha(
    returns=strategy_returns,
    factor_returns=factor_returns,
)
contributions = attribution.compute_factor_attribution(
    factor_returns=factor_returns,
    factor_exposures=exposures_ols.drop(columns=["alpha"], errors="ignore"),
)

Key implementation: OLS with pseudo-inverse

The function estimates one coefficient vector per instrument after date alignment.

backtester/portfolio/calc/attribution.py Python
ret_al, fac_al = returns.align(factor_returns, join="inner", axis=0)
X = fac_al.copy()
if add_intercept:
    X = pd.concat([pd.Series(1.0, index=X.index, name="const"), X], axis=1)

XtX = X.T.dot(X)
XtX_inv = np.linalg.pinv(XtX.values)
Xt = X.T.values

coefs = {}
for inst in ret_al.columns:
    y = ret_al[inst].values
    beta = XtX_inv.dot(Xt.dot(y))
    coefs[inst] = beta