"""Full V5 Feature Engineering — mirrors cleaned notebook Part A (pandas version)""" import pandas as pd import numpy as np from ta.momentum import RSIIndicator # type: ignore from ta.trend import MACD # type: ignore def add_v5_features(df: pd.DataFrame) -> tuple[pd.DataFrame, list]: df = df.copy() if not isinstance(df.index, pd.DatetimeIndex): df = df.set_index('timestamp').sort_index() c, h, l, v, o = df['close'], df['high'], df['low'], df['volume'], df['open'] # === Core V5 blocks from notebook === for lag in [1, 2, 4, 6, 12, 24, 48, 168]: df[f'return_{lag}h'] = c.pct_change(lag) df['candle_body_ratio'] = (c - o) / (h - l + 1e-10) df['range_pct'] = (h - l) / (c + 1e-10) df['close_location_value'] = (c - l) / (h - l + 1e-10) for w in [4, 12, 24, 48, 168]: df[f'sma_{w}h'] = c.rolling(w).mean() df[f'vol_{w}h'] = c.rolling(w).std() df[f'vol_avg_{w}h'] = v.rolling(w).mean() df[f'price_vs_sma_{w}h'] = c / df[f'sma_{w}h'] - 1 for period in [6, 14, 24]: df[f'rsi_{period}h'] = RSIIndicator(c, window=period).rsi() macd = MACD(c, window_slow=26, window_fast=12, window_sign=9) df['macd_h'] = macd.macd() df['macd_signal_h'] = macd.macd_signal() df['macd_hist_h'] = macd.macd_diff() df['vol_ratio_24h'] = v / (v.rolling(24).mean() + 1e-10) df['vol_ratio_168h'] = v / (v.rolling(168).mean() + 1e-10) # On-chain momentum for col in ['tx_count', 'active_senders', 'active_receivers', 'total_eth_transferred', 'total_gas_used']: if col in df.columns: df[f'{col}_change_24h'] = df[col] / (df[col].shift(24) + 1e-10) - 1 df[f'{col}_ma24h'] = df[col].rolling(24).mean() # Cyclical df['hour_sin'] = np.sin(2 * np.pi * df.index.hour / 24) # type: ignore df['hour_cos'] = np.cos(2 * np.pi * df.index.hour / 24) # type: ignore df['dow_sin'] = np.sin(2 * np.pi * df.index.dayofweek / 7) # type: ignore df['dow_cos'] = np.cos(2 * np.pi * df.index.dayofweek / 7) # type: ignore # 30d regime features (used by filter & routing) df['ret_30d'] = c.pct_change(30*24) # ≈30d on 1h bars df['vol_30d'] = df['return'].rolling(30*24).std() df = df.dropna() feature_cols = [col for col in df.columns if col not in {'open','high','low','close','volume','timestamp'}] print(f"✅ Built {len(feature_cols)} V5 features (matches notebook Part A)") return df, feature_cols