Add factor attribution regression engine
This commit is contained in:
@@ -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"],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user