Spaces:
Running on Zero
Running on Zero
shindeaditya2313-spec
deploy: clean snapshot for HuggingFace Spaces - premium UI redesign + Created by aDi
6114f21 Download src/data/indicators.py from Adityashinde0/Stock_Prediction_Model_With_RL-ML: direct link, hf CLI and curl.
- Browser
- Download file 10 kB
-
https://huggingface.co/spaces/Adityashinde0/Stock_Prediction_Model_With_RL-ML/resolve/main/src/data/indicators.py
- Command line
-
hf download hf://spaces/Adityashinde0/Stock_Prediction_Model_With_RL-ML/src/data/indicators.py
-
curl -L -o indicators.py https://huggingface.co/spaces/Adityashinde0/Stock_Prediction_Model_With_RL-ML/resolve/main/src/data/indicators.py
10 kB
| """ | |
| Technical Indicator Engineering Module | |
| Calculates Momentum, Trend, Volatility, Volume, and Seasonality features | |
| for ML/RL/DL model feature vectors. | |
| Feature set (21 total): | |
| Trend : EMA7, EMA21, EMA50, EMA200 | |
| Momentum : MACD_line, MACD_signal, MACD_diff, RSI | |
| Volume : OBV | |
| Volatility : BBH, BBL, BB_width, RealizedVol, ATR_norm | |
| Return : Return, LogReturn | |
| Regime : DistEMA200 | |
| Seasonality: DayOfWeek_sin, DayOfWeek_cos, Month_sin, Month_cos | |
| """ | |
| import logging | |
| import numpy as np | |
| import pandas as pd | |
| import ta | |
| from src.config import INDICATOR_PARAMS | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger("TechnicalIndicators") | |
| class TechnicalIndicatorEngine: | |
| """ | |
| Transforms raw OHLCV market data into rich feature vectors for model inference. | |
| """ | |
| def add_all_indicators(df: pd.DataFrame) -> pd.DataFrame: | |
| """ | |
| Computes all 21 institutional quantitative features and returns a | |
| cleaned DataFrame with warm-up NaN rows removed. | |
| New features vs. original: | |
| LogReturn : ln(P_t / P_{t-1}) β cleaner distributional properties | |
| RealizedVol : 20-day rolling std(LogReturn) Γ β252 β annualised | |
| DistEMA200 : (Adj Close - EMA200) / EMA200 β regime distance | |
| ATR_norm : ATR(14) / Close β normalised intraday bar expansion | |
| DayOfWeek_sin/cos : sine/cosine of ISO day-of-week (0=Mon β¦ 6=Sun) | |
| Month_sin/cos : sine/cosine of calendar month (1β12) | |
| Cyclical features are extracted from the DataFrame's DatetimeIndex | |
| BEFORE reset_index(drop=True) to avoid losing timestamp information. | |
| """ | |
| if df.empty or len(df) < 50: | |
| logger.warning("DataFrame is too small to calculate technical indicators.") | |
| return df | |
| data = df.copy() | |
| try: | |
| # ββ 1. Exponential Moving Averages βββββββββββββββββββββββββββββββ | |
| data["EMA7"] = ta.trend.EMAIndicator( | |
| close=data["Adj Close"], window=INDICATOR_PARAMS["ema_fast"], fillna=False | |
| ).ema_indicator() | |
| data["EMA21"] = ta.trend.EMAIndicator( | |
| close=data["Adj Close"], window=INDICATOR_PARAMS["ema_slow"], fillna=False | |
| ).ema_indicator() | |
| data["EMA50"] = ta.trend.EMAIndicator( | |
| close=data["Adj Close"], window=50, fillna=False | |
| ).ema_indicator() | |
| data["EMA200"] = ta.trend.EMAIndicator( | |
| close=data["Adj Close"], window=INDICATOR_PARAMS["ema_trend"], fillna=False | |
| ).ema_indicator() | |
| # ββ 2. MACD ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| macd = ta.trend.MACD( | |
| close=data["Adj Close"], | |
| window_slow=INDICATOR_PARAMS["macd_slow"], | |
| window_fast=INDICATOR_PARAMS["macd_fast"], | |
| window_sign=INDICATOR_PARAMS["macd_signal"], | |
| fillna=False, | |
| ) | |
| data["MACD_line"] = macd.macd() | |
| data["MACD_signal"] = macd.macd_signal() | |
| data["MACD_diff"] = macd.macd_diff() | |
| # ββ 3. RSI βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| data["RSI"] = ta.momentum.RSIIndicator( | |
| close=data["Adj Close"], | |
| window=INDICATOR_PARAMS["rsi_period"], | |
| fillna=False, | |
| ).rsi() | |
| # ββ 4. On-Balance Volume βββββββββββββββββββββββββββββββββββββββββ | |
| data["OBV"] = ta.volume.OnBalanceVolumeIndicator( | |
| close=data["Adj Close"], volume=data["Volume"], fillna=False | |
| ).on_balance_volume() | |
| # ββ 5. Bollinger Bands βββββββββββββββββββββββββββββββββββββββββββ | |
| bb = ta.volatility.BollingerBands( | |
| close=data["Adj Close"], | |
| window=INDICATOR_PARAMS["bollinger_window"], | |
| window_dev=INDICATOR_PARAMS["bollinger_std"], | |
| fillna=False, | |
| ) | |
| data["BBH"] = bb.bollinger_hband_indicator() | |
| data["BBL"] = bb.bollinger_lband_indicator() | |
| data["BB_width"] = bb.bollinger_wband() | |
| # ββ 6. Simple Percentage Return ββββββββββββββββββββββββββββββββββ | |
| data["Return"] = data["Adj Close"].pct_change() | |
| # ββ 7. Log Return ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # ln(P_t / P_{t-1}) β better normality, additive across periods | |
| data["LogReturn"] = np.log( | |
| data["Adj Close"] / data["Adj Close"].shift(1) | |
| ) | |
| # ββ 8. Historical Realized Volatility (annualised) βββββββββββββββ | |
| # 20-day rolling Ο of log returns Γ β252 | |
| data["RealizedVol"] = ( | |
| data["LogReturn"].rolling(window=20).std() * np.sqrt(252) | |
| ) | |
| # ββ 9. Distance from EMA200 (regime indicator) βββββββββββββββββββ | |
| # Positive β price above trend; negative β below trend | |
| data["DistEMA200"] = (data["Adj Close"] - data["EMA200"]) / data["EMA200"] | |
| # ββ 10. Normalised Average True Range ββββββββββββββββββββββββββββ | |
| # ATR(14) / Close β dimensionless intraday volatility measure | |
| atr = ta.volatility.AverageTrueRange( | |
| high=data["High"], | |
| low=data["Low"], | |
| close=data["Close"], | |
| window=14, | |
| fillna=False, | |
| ) | |
| data["ATR"] = atr.average_true_range() | |
| data["ATR_norm"] = data["ATR"] / data["Close"] | |
| # ββ 11. Cyclical Date Features (from DatetimeIndex) ββββββββββββββ | |
| # Extracted BEFORE reset_index(drop=True) to retain timestamp info. | |
| # Sine/cosine encoding avoids discontinuities at day/month boundaries. | |
| if isinstance(data.index, pd.DatetimeIndex): | |
| dow = data.index.dayofweek.astype(float) # 0=Mon β¦ 6=Sun | |
| month = data.index.month.astype(float) # 1 β¦ 12 | |
| data["DayOfWeek_sin"] = np.sin(2.0 * np.pi * dow / 7.0) | |
| data["DayOfWeek_cos"] = np.cos(2.0 * np.pi * dow / 7.0) | |
| data["Month_sin"] = np.sin(2.0 * np.pi * month / 12.0) | |
| data["Month_cos"] = np.cos(2.0 * np.pi * month / 12.0) | |
| elif "Date" in data.columns: | |
| dt = pd.to_datetime(data["Date"]) | |
| dow = dt.dt.dayofweek.astype(float) | |
| month = dt.dt.month.astype(float) | |
| data["DayOfWeek_sin"] = np.sin(2.0 * np.pi * dow / 7.0) | |
| data["DayOfWeek_cos"] = np.cos(2.0 * np.pi * dow / 7.0) | |
| data["Month_sin"] = np.sin(2.0 * np.pi * month / 12.0) | |
| data["Month_cos"] = np.cos(2.0 * np.pi * month / 12.0) | |
| else: | |
| # Fallback: zero-fill if no datetime information available | |
| logger.warning( | |
| "No DatetimeIndex or Date column found β " | |
| "cyclical date features set to 0." | |
| ) | |
| for col in ["DayOfWeek_sin", "DayOfWeek_cos", "Month_sin", "Month_cos"]: | |
| data[col] = 0.0 | |
| # ββ 12. Clean warm-up NaN rows (long-window indicators: EMA200, RealizedVol) | |
| clean_data = data.dropna().reset_index(drop=True) | |
| # Drop the intermediate ATR column (ATR_norm is the normalised form used) | |
| if "ATR" in clean_data.columns: | |
| clean_data = clean_data.drop(columns=["ATR"]) | |
| logger.info( | |
| f"Feature Engineering Complete. Shape after cleaning: {clean_data.shape}" | |
| ) | |
| return clean_data | |
| except Exception as exc: | |
| logger.error(f"Error computing technical indicators: {exc}") | |
| return df | |
| # ββ Standalone Verification Block ββββββββββββββββββββββββββββββββββββββββββββ | |
| if __name__ == "__main__": | |
| from src.data.fetcher import MarketDataFetcher | |
| raw_df = MarketDataFetcher.fetch_data(ticker="BTC-USD", period="2y", interval="1d") | |
| processed_df = TechnicalIndicatorEngine.add_all_indicators(raw_df) | |
| NEW_COLS = [ | |
| "LogReturn", "RealizedVol", "DistEMA200", | |
| "ATR_norm", | |
| "DayOfWeek_sin", "DayOfWeek_cos", | |
| "Month_sin", "Month_cos", | |
| ] | |
| print("\n--- Technical Indicator Engine Verification ---") | |
| print(f"Total columns : {len(processed_df.columns)}") | |
| print(f"Total rows : {len(processed_df)}") | |
| print(f"New feature cols : {NEW_COLS}") | |
| print() | |
| print(processed_df[["Adj Close", "LogReturn", "RealizedVol", "DistEMA200", "ATR_norm"]].tail(3)) | |
| print() | |
| print(processed_df[["DayOfWeek_sin", "DayOfWeek_cos", "Month_sin", "Month_cos"]].tail(3)) | |
| # Sanity: all new cols present and non-null in last row | |
| for col in NEW_COLS: | |
| assert col in processed_df.columns, f"MISSING: {col}" | |
| assert not pd.isna(processed_df[col].iloc[-1]), f"NaN in last row: {col}" | |
| print("\nAll new feature sanity checks PASSED.") |