"""Factor neutralization (industry / market cap) via cross-sectional regression.""" from __future__ import annotations import numpy as np import pandas as pd from sklearn.linear_model import LinearRegression def neutralize_factor( factor: pd.Series, exposures: pd.DataFrame, date_level: str = "datetime", ) -> pd.Series: """ Neutralize factor against exposure columns (e.g. industry dummies, log market cap). Expects MultiIndex (instrument, datetime) or a panel with date column. """ if isinstance(factor.index, pd.MultiIndex): df = factor.to_frame("factor").join(exposures, how="left") neutralized = [] for dt, group in df.groupby(level=date_level): y = group["factor"].values x = group[exposures.columns].fillna(0).values if len(y) < x.shape[1] + 2: neutralized.append(group["factor"]) continue reg = LinearRegression().fit(x, y) resid = y - reg.predict(x) neutralized.append(pd.Series(resid, index=group.index)) return pd.concat(neutralized).sort_index() raise NotImplementedError("neutralize_factor currently supports MultiIndex panels only") def zscore_by_date(factor: pd.Series, date_level: str = "datetime") -> pd.Series: def _z(x): return (x - x.mean()) / (x.std() + 1e-8) return factor.groupby(level=date_level, group_keys=False).apply(_z)