Add factor attribution regression engine

This commit is contained in:
2026-04-07 16:40:24 +08:00
parent 507565c556
commit f2e14ec200
2 changed files with 196 additions and 0 deletions

View File

@@ -21,6 +21,8 @@ from factor_attribution import (
build_extension_factors,
build_proxy_core_factors,
load_external_us_factors,
prepare_factor_models,
run_factor_regression,
)
@@ -421,3 +423,80 @@ class LocalFactorConstructionTests(unittest.TestCase):
benchmark_path = 0.0004 * steps + 0.018 * np.sin(steps / 27.0 + 0.3)
data[benchmark] = 250.0 * np.exp(benchmark_path)
return pd.DataFrame(data, index=dates)
class RegressionTests(unittest.TestCase):
def test_run_factor_regression_recovers_known_coefficients(self):
dates = pd.date_range("2024-01-01", periods=300, freq="B")
angles = np.linspace(0.0, 18.0, len(dates))
factors = pd.DataFrame(
{
"MKT_RF": 0.012 * np.sin(angles),
"SMB": 0.007 * np.cos(angles * 0.7) + np.linspace(-0.002, 0.003, len(dates)),
"RF": np.full(len(dates), 0.0001),
},
index=dates,
)
factors.loc[dates[:4], "SMB"] = np.nan
strategy = (
0.0005
+ 1.2 * factors["MKT_RF"]
+ 0.4 * factors["SMB"]
+ factors["RF"]
)
result = run_factor_regression(
strategy,
factors,
factor_cols=["MKT_RF", "SMB"],
risk_free_col="RF",
)
self.assertAlmostEqual(result["alpha_daily"], 0.0005, places=6)
self.assertAlmostEqual(result["betas"]["MKT_RF"], 1.2, places=6)
self.assertAlmostEqual(result["betas"]["SMB"], 0.4, places=6)
self.assertGreater(result["r_squared"], 0.999999)
self.assertEqual(result["start_date"], "2024-01-05")
self.assertEqual(result["end_date"], "2025-02-21")
self.assertEqual(result["n_obs"], 296)
def test_prepare_factor_models_uses_proxy_family_without_external_us_factors(self):
dates = pd.date_range("2024-01-01", periods=5, freq="B")
extension = pd.DataFrame(
{
"MOM": np.linspace(0.001, 0.005, len(dates)),
"LOWVOL": np.linspace(-0.002, 0.002, len(dates)),
"RECOVERY": np.linspace(0.003, -0.001, len(dates)),
},
index=dates,
)
proxy = pd.DataFrame(
{
"MKT": np.linspace(-0.01, 0.01, len(dates)),
"SMB_PROXY": np.linspace(0.002, 0.004, len(dates)),
"HML_PROXY": np.linspace(-0.003, 0.001, len(dates)),
"RMW_PROXY": np.linspace(0.005, 0.001, len(dates)),
"CMA_PROXY": np.linspace(-0.004, -0.002, len(dates)),
},
index=dates,
)
prepared = prepare_factor_models(
market="us",
extension_factors=extension,
proxy_factors=proxy,
external_factors=None,
)
self.assertEqual(prepared["factor_source"], "proxy_only")
self.assertIsNone(prepared["risk_free_col"])
self.assertListEqual(list(prepared["models"]), ["proxy"])
self.assertListEqual(
prepared["models"]["proxy"],
["MKT", "SMB_PROXY", "HML_PROXY", "RMW_PROXY", "CMA_PROXY", "MOM", "LOWVOL", "RECOVERY"],
)
self.assertListEqual(
list(prepared["factor_frame"].columns),
["MKT", "SMB_PROXY", "HML_PROXY", "RMW_PROXY", "CMA_PROXY", "MOM", "LOWVOL", "RECOVERY"],
)