Add minute bar feature pipeline

This commit is contained in:
Yuxuan Yan
2026-06-16 13:57:17 +08:00
parent 17fa75495d
commit 83a006bbe4
19 changed files with 1289 additions and 11 deletions
+75
View File
@@ -0,0 +1,75 @@
"""Feature computation and validation."""
import logging
from pathlib import Path
from typing import Iterable
import pandas as pd
from pandas.api.types import is_numeric_dtype
from pipeline.features.registry import get_feature
logger = logging.getLogger(__name__)
FEATURE_KEY_COLUMNS = ["symbol_id", "date"]
def validate_feature_frame(features: pd.DataFrame) -> pd.DataFrame:
"""Validate and normalize a daily feature frame.
A valid feature frame is keyed by unique ``symbol_id,date`` rows and has at
least one numeric feature column beyond those keys.
"""
duplicated = features.columns[features.columns.duplicated()].tolist()
if duplicated:
raise ValueError(f"Feature output has duplicate columns: {duplicated}")
missing = [col for col in FEATURE_KEY_COLUMNS if col not in features.columns]
if missing:
raise ValueError(f"Feature output missing required columns: {missing}")
out = features.copy()
out["date"] = pd.to_datetime(out["date"])
if out.duplicated(FEATURE_KEY_COLUMNS).any():
raise ValueError("Feature output has duplicate symbol_id,date rows")
feature_cols = [col for col in out.columns if col not in FEATURE_KEY_COLUMNS]
if not feature_cols:
raise ValueError("Feature output must include at least one feature column")
non_numeric = [col for col in feature_cols if not is_numeric_dtype(out[col])]
if non_numeric:
raise ValueError(f"Feature columns must be numeric: {non_numeric}")
out = out[FEATURE_KEY_COLUMNS + feature_cols].copy()
return out.sort_values(FEATURE_KEY_COLUMNS).reset_index(drop=True)
def compute_feature(
minute: pd.DataFrame,
feature_type: str,
daily: pd.DataFrame | None = None,
**params,
) -> pd.DataFrame:
"""Compute one registered feature from raw minute bars."""
feature = get_feature(feature_type, **params)
result = validate_feature_frame(feature.compute(minute=minute, daily=daily))
feature_cols = [col for col in result.columns if col not in FEATURE_KEY_COLUMNS]
logger.info(
"Feature '%s' (%r): %d symbols × %d dates, columns=%s",
feature_type,
feature,
result["symbol_id"].nunique(),
result["date"].nunique(),
feature_cols,
)
return result
def read_feature_frames(feature_paths: Iterable[str | Path]) -> list[pd.DataFrame]:
"""Read and validate feature parquet files."""
return [
validate_feature_frame(pd.read_parquet(path))
for path in feature_paths
]