From d99f635c94a0327899e57a1d0c7a3cc15baae69a Mon Sep 17 00:00:00 2001 From: Brian McMahon Date: Fri, 12 Jun 2026 15:38:03 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20FEATURE=5FCFG=20windows=20load=20from?= =?UTF-8?q?=20the=20experiment=20package=20(config#1039)=20=E2=80=94=20per?= =?UTF-8?q?-key=20override,=20fail-loud=20on=20unknown=20keys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- features/feature_engineer.py | 49 +++++++++++++++++++++++++++++-- tests/test_feature_cfg_package.py | 42 ++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 tests/test_feature_cfg_package.py diff --git a/features/feature_engineer.py b/features/feature_engineer.py index 3bfcbf1..324cf63 100644 --- a/features/feature_engineer.py +++ b/features/feature_engineer.py @@ -25,9 +25,12 @@ import pandas as pd -# ── Feature engineering parameters (from predictor.sample.yaml) ────────────── -# These replace `from config import FEATURE_CFG as _FC` in the predictor. -FEATURE_CFG: dict = { +# ── Feature engineering parameters ────────────────────────────────────────── +# config#1039: the window/period choices are experiment beliefs +# (HARNESS_EXPERIMENT_CLASSIFICATION.md §1) — the experiment package's data +# config.yaml `feature_cfg:` section overrides them per-key; these literals +# are the public BASELINE used when no package entry is present. +_BASELINE_FEATURE_CFG: dict = { "rsi_period": 14, "macd_fast": 12, "macd_slow": 26, @@ -64,6 +67,46 @@ "resid_mom_vol_window": 20, } + +def _load_feature_cfg_overrides() -> dict: + """Per-key FEATURE_CFG overrides from the experiment package. + + Search mirrors weekly_collector.load_config + the package layer: + experiments/$ALPHA_ENGINE_EXPERIMENT_ID/data/config.yaml first, legacy + alpha-engine-config/data/config.yaml next, repo-local config.yaml last. + A MISSING file falls through (baseline applies); a present-but-corrupt + file or an unknown key RAISES — a typo'd override silently reverting a + window to baseline would be an invisible behavior change (no-silent- + fails: the failure surfaces at import, caught by SF preflight/CI). + """ + import os + from pathlib import Path + + import yaml + + exp = os.environ.get("ALPHA_ENGINE_EXPERIMENT_ID", "reference") + repo_root = Path(__file__).resolve().parent.parent + roots = [Path.home() / "alpha-engine-config", repo_root.parent / "alpha-engine-config"] + candidates = [r / "experiments" / exp / "data" / "config.yaml" for r in roots] + candidates += [r / "data" / "config.yaml" for r in roots] + candidates.append(repo_root / "config.yaml") + for path in candidates: + if not path.exists(): + continue + cfg = yaml.safe_load(path.read_text()) or {} + overrides = dict(cfg.get("feature_cfg") or {}) + unknown = set(overrides) - set(_BASELINE_FEATURE_CFG) + if unknown: + raise KeyError( + f"feature_cfg override(s) {sorted(unknown)} in {path} do not " + f"match any baseline FEATURE_CFG key — typo or removed param." + ) + return overrides + return {} + + +FEATURE_CFG: dict = {**_BASELINE_FEATURE_CFG, **_load_feature_cfg_overrides()} + _FC = FEATURE_CFG # ── Feature list (from predictor config.py lines 62-124) ──────────────────── diff --git a/tests/test_feature_cfg_package.py b/tests/test_feature_cfg_package.py new file mode 100644 index 0000000..052eccb --- /dev/null +++ b/tests/test_feature_cfg_package.py @@ -0,0 +1,42 @@ +"""config#1039 — FEATURE_CFG windows load from the experiment package. + +Pins: (a) baseline values stay the validated set (spot-pins); (b) per-key +merge semantics; (c) unknown override keys fail loud. +""" +import pytest + +from features import feature_engineer as fe + + +def test_baseline_spot_pins(): + b = fe._BASELINE_FEATURE_CFG + assert b["rsi_period"] == 14 + assert b["ma_short"] == 50 and b["ma_long"] == 200 + assert b["resid_mom_window"] == 252 and b["resid_mom_skip"] == 21 + assert len(b) == 31 + + +def test_active_cfg_is_baseline_plus_overrides(): + overrides = fe._load_feature_cfg_overrides() + assert fe.FEATURE_CFG == {**fe._BASELINE_FEATURE_CFG, **overrides} + + +def test_unknown_override_key_fails_loud(tmp_path, monkeypatch): + pkg = tmp_path / "alpha-engine-config" / "experiments" / "reference" / "data" + pkg.mkdir(parents=True) + (pkg / "config.yaml").write_text("feature_cfg:\n rsi_perod: 9\n") + monkeypatch.setenv("ALPHA_ENGINE_EXPERIMENT_ID", "reference") + # point the home root at tmp_path + monkeypatch.setattr("pathlib.Path.home", staticmethod(lambda: tmp_path)) + with pytest.raises(KeyError, match="rsi_perod"): + fe._load_feature_cfg_overrides() + + +def test_valid_override_merges(tmp_path, monkeypatch): + pkg = tmp_path / "alpha-engine-config" / "experiments" / "reference" / "data" + pkg.mkdir(parents=True) + (pkg / "config.yaml").write_text("feature_cfg:\n rsi_period: 9\n") + monkeypatch.setenv("ALPHA_ENGINE_EXPERIMENT_ID", "reference") + monkeypatch.setattr("pathlib.Path.home", staticmethod(lambda: tmp_path)) + overrides = fe._load_feature_cfg_overrides() + assert overrides == {"rsi_period": 9}