thinkingEverytime/QuantOracle
1
1"""Feature engineering (EOD) - minimal but useful."""2 3from __future__ import annotations4 5import pandas as pd6 7 8def build_features(h: pd.DataFrame) -> pd.DataFrame:9 """Return per-date features. Expects OHLCV with Date index and Close column."""10 if h is None or h.empty or "Close" not in h:11 return pd.DataFrame()12 13 close = h["Close"].astype(float)14 ret1 = close.pct_change(1)15 ret5 = close.pct_change(5)16 ret20 = close.pct_change(20)17 vol20 = close.pct_change().rolling(20).std()18 19 sma20 = close.rolling(20).mean()20 sma50 = close.rolling(50).mean()21 delta = close.diff()22 gain = delta.where(delta > 0, 0.0)23 loss = (-delta).where(delta < 0, 0.0)24 avg_gain = gain.ewm(alpha=1 / 14, min_periods=14, adjust=False).mean()25 avg_loss = loss.ewm(alpha=1 / 14, min_periods=14, adjust=False).mean()26 rs = avg_gain / avg_loss27 rsi14 = 100 - (100 / (1 + rs))28 29 out = pd.DataFrame(30 {31 "ret_1d": ret1,32 "ret_5d": ret5,33 "ret_20d": ret20,34 "vol_20d": vol20,35 "price_sma20": close / sma20 - 1,36 "price_sma50": close / sma50 - 1,37 "rsi_14": rsi14,38 },39 index=h.index,40 )41 return out.dropna()42 43 44def build_targets(close: pd.Series, horizon: int = 5) -> pd.Series:45 """Forward return over horizon."""46 close = close.astype(float)47 return close.shift(-horizon) / close - 1.048 