research: sector residual, earnings gap/SUE, history-depth scaffolding

Tier-1 alpha research (local only, no production deploy):

Sector residual momentum: two-factor SPY+sector residual and sector demean signals, IC harness + A/B. Sector resid clears pre-registered bars narrowly (PROMOTE for human wire design only). Sector demean fails t vs market resid.

Earnings: earnings_events backfill (FMP bulk paid; FMP/AV per-symbol), 2a gap diagnostic report-only, 2b SUE IC (PARK; incomplete 48/506 coverage).

History-depth: pre-registered doc + runner for MacBook deep rebuild/harness.

Do not ship production residual or filters from this branch.
This commit is contained in:
2026-07-19 09:33:34 +02:00
parent 8f285acb00
commit fa25b6ee68
18 changed files with 6093 additions and 25 deletions
+69
View File
@@ -100,6 +100,75 @@ def test_residual_momentum_removes_market_beta_but_keeps_specific_drift():
assert drift["mom_12_1_resid"] > pure["mom_12_1_resid"] + 0.12
def test_sector_residual_momentum_two_factor():
"""Pure market+sector beta stock → sector resid ~0; idiosyncratic drift kept."""
dates, pure_beta, highs, benchmark = _signal_test_series(extra_return=0.0)
# Sector ETF = leveraged market (collinear-ish but not identical).
sector = {d: benchmark[d] * 1.02 + 0.5 for d in dates}
# Stock with pure exposure to market + sector, no alpha.
closes = [100.0]
for i in range(1, len(dates)):
m_prev = benchmark[dates[i - 1]]
m_cur = benchmark[dates[i]]
s_prev = sector[dates[i - 1]]
s_cur = sector[dates[i]]
m_ret = m_cur / m_prev - 1.0
s_ret = s_cur / s_prev - 1.0
closes.append(closes[-1] * (1.0 + 0.7 * m_ret + 0.5 * s_ret))
highs_p = [c * 1.01 for c in closes]
pure = bt._signal_values(
dates, closes, highs_p, 260, benchmark, sector_etf_closes=sector
)
assert "mom_12_1_sector_resid" in pure
assert pure["mom_12_1_sector_resid"] == pytest.approx(0.0, abs=0.05)
# Add idiosyncratic drift — sector residual should keep it.
drift_closes = [100.0]
for i in range(1, len(dates)):
m_prev = benchmark[dates[i - 1]]
m_cur = benchmark[dates[i]]
s_prev = sector[dates[i - 1]]
s_cur = sector[dates[i]]
m_ret = m_cur / m_prev - 1.0
s_ret = s_cur / s_prev - 1.0
drift_closes.append(
drift_closes[-1] * (1.0 + 0.7 * m_ret + 0.5 * s_ret + 0.0008)
)
drift_highs = [c * 1.01 for c in drift_closes]
drift = bt._signal_values(
dates, drift_closes, drift_highs, 260, benchmark, sector_etf_closes=sector
)
assert drift["mom_12_1_sector_resid"] > pure["mom_12_1_sector_resid"] + 0.10
def test_inject_sector_demeaned_momentum():
collected = {
"mom_12_1": {
(2024, 1): [
{"val": 0.20, "fwd": 0.01, "symbol": "AAA"},
{"val": 0.10, "fwd": 0.02, "symbol": "BBB"},
{"val": 0.40, "fwd": -0.01, "symbol": "CCC"},
{"val": 0.00, "fwd": 0.03, "symbol": "DDD"},
]
}
}
symbol_to_sector = {
"AAA": "Information Technology",
"BBB": "Information Technology",
"CCC": "Energy",
"DDD": "Energy",
}
bt._inject_sector_demeaned_momentum(collected, symbol_to_sector)
dem = collected["mom_12_1_sector_demeaned"][(2024, 1)]
by_sym = {r["symbol"]: r["val"] for r in dem}
# IT mean = 0.15 → AAA +0.05, BBB -0.05; Energy mean = 0.20 → CCC +0.20, DDD -0.20
assert by_sym["AAA"] == pytest.approx(0.05)
assert by_sym["BBB"] == pytest.approx(-0.05)
assert by_sym["CCC"] == pytest.approx(0.20)
assert by_sym["DDD"] == pytest.approx(-0.20)
def test_assigns_raw_and_residual_percentiles_independently():
cands = [
{"iso_week": (2026, 1), "momentum": 0.10, "residual_momentum": 0.30},