Add local attribution factor builders
This commit is contained in:
@@ -9,6 +9,7 @@ from pathlib import Path
|
||||
from urllib.error import URLError
|
||||
from unittest import mock
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from factor_attribution import (
|
||||
@@ -17,6 +18,8 @@ from factor_attribution import (
|
||||
KEN_FRENCH_DAILY_FF5_ZIP_URL,
|
||||
_download_kf_zip_bytes,
|
||||
_parse_kf_daily_csv,
|
||||
build_extension_factors,
|
||||
build_proxy_core_factors,
|
||||
load_external_us_factors,
|
||||
)
|
||||
|
||||
@@ -289,3 +292,46 @@ class ExternalFactorLoaderTests(unittest.TestCase):
|
||||
with zipfile.ZipFile(buffer, mode="w") as archive:
|
||||
archive.writestr(filename, contents)
|
||||
return buffer.getvalue()
|
||||
|
||||
|
||||
class LocalFactorConstructionTests(unittest.TestCase):
|
||||
def test_build_extension_factors_returns_expected_columns_with_non_null_values_after_warmup(self):
|
||||
prices = self._make_price_frame(benchmark="SPY")
|
||||
|
||||
factors = build_extension_factors(prices, benchmark="SPY", market="us")
|
||||
|
||||
self.assertListEqual(list(factors.columns), ["MOM", "LOWVOL", "RECOVERY"])
|
||||
self.assertTrue(factors.iloc[260:].notna().all().all())
|
||||
self.assertGreater(factors.iloc[260:].abs().sum().sum(), 0.0)
|
||||
|
||||
def test_build_proxy_core_factors_returns_expected_columns_with_non_null_values_after_warmup(self):
|
||||
prices = self._make_price_frame(benchmark="000300.SS")
|
||||
|
||||
factors = build_proxy_core_factors(prices, benchmark="000300.SS", market="cn")
|
||||
|
||||
self.assertListEqual(
|
||||
list(factors.columns),
|
||||
["MKT", "SMB_PROXY", "HML_PROXY", "RMW_PROXY", "CMA_PROXY"],
|
||||
)
|
||||
self.assertTrue(factors.iloc[260:].notna().all().all())
|
||||
self.assertGreater(factors.iloc[260:].abs().sum().sum(), 0.0)
|
||||
|
||||
def _make_price_frame(self, benchmark: str) -> pd.DataFrame:
|
||||
dates = pd.date_range("2025-01-01", periods=320, freq="B")
|
||||
steps = np.arange(len(dates), dtype=float)
|
||||
symbols = [
|
||||
("A", 45.0, 0.0006, 0.030, 19.0, 0.1),
|
||||
("B", 60.0, 0.0003, 0.025, 23.0, 0.8),
|
||||
("C", 75.0, -0.0002, 0.035, 17.0, 1.4),
|
||||
("D", 90.0, 0.0008, 0.020, 29.0, 0.5),
|
||||
("E", 55.0, -0.0001, 0.028, 31.0, 1.9),
|
||||
("F", 70.0, 0.0005, 0.032, 21.0, 2.5),
|
||||
]
|
||||
data = {}
|
||||
for symbol, base, drift, amplitude, frequency, phase in symbols:
|
||||
log_path = drift * steps + amplitude * np.sin(steps / frequency + phase)
|
||||
data[symbol] = base * np.exp(log_path)
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user