Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions features/feature_engineer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) ────────────────────
Expand Down
42 changes: 42 additions & 0 deletions tests/test_feature_cfg_package.py
Original file line number Diff line number Diff line change
@@ -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}
Loading