Skip to main content
MDX Engineering Note · Market Sentiment & Financial Volatility Analyzer··6 min read

Modeling asymmetric financial volatility with EGARCH(1,1) and leak-free chronological Random Forest splits

Architectural notes from my 8th-semester CSE major project analyzing 4,236+ trading days of Nifty 50, Sensex, and Bank Nifty returns.

4,236+ Trading Days · EGARCH(1,1) + Chronological RFSource: /content/writing/egarch-volatility-chronological-random-forest.mdxPython 3.12EGARCH(1,1)EconometricsRandom ForestTime-SeriesStreamlit

1. Why Standard GARCH Fails on Asymmetric Market Panics

In equity markets, a negative news shock triggers significantly higher subsequent volatility than a positive rally of the same magnitude—a phenomenon known in financial econometrics as the leverage effect.

Standard symmetric GARCH(1,1) squares the lagged innovation, erasing the sign of the shock. In my 8th-semester major project (investor-sentiment-streamlit), we modeled 4,236+ trading days (2007–2024) across Nifty 50, BSE Sensex, and Bank Nifty using Nelson's exponential EGARCH(1,1) formulation in log-variance space.

Because the left-hand side models log-variance, conditional variance is guaranteed to remain strictly positive without artificial parameter constraints, and the asymmetry parameter gamma directly measures how much stronger negative sentiment shocks amplify volatility.

2. Fitting EGARCH(1,1) & Verifying Stationarity in Python

Before fitting the volatility model or running Granger causality tests against daily sentiment scores (derived from a 3-tier GDELT v2, Google Trends PCA, and VADER cascade), we verify that log-returns are stationary via the Augmented Dickey-Fuller (ADF) test.

src/models/volatility_engine.py(python)
import numpy as np
import pandas as pd
from arch import arch_model
from statsmodels.tsa.stattools import adfuller

def fit_asymmetric_egarch(close_prices: pd.Series) -> dict:
    log_returns = 100.0 * np.log(close_prices / close_prices.shift(1)).dropna()
    adf_stat, adf_pvalue, *_ = adfuller(log_returns, autolag="AIC")

    model = arch_model(
        log_returns,
        vol="EGARCH",
        p=1,
        o=1,
        q=1,
        dist="t",
    )
    res = model.fit(disp="off")
    return {
        "adf_pvalue": float(adf_pvalue),
        "omega": float(res.params.get("omega", 0.0)),
        "alpha": float(res.params.get("alpha[1]", 0.0)),
        "gamma_asymmetry": float(res.params.get("gamma[1]", 0.0)),
        "beta_persistence": float(res.params.get("beta[1]", 0.0)),
        "conditional_volatility": res.conditional_volatility,
    }

3. Eliminating Lookahead Leakage with Chronological 80/20 Splits

A frequent pitfall in time-series machine learning is passing rolling features into train_test_split(..., shuffle=True) or random K-fold cross-validation. Because volatility regimes cluster in time, random shuffling leaks future conditional variance and rolling sentiment averages into the training set.

To prevent lookahead bias, our Random Forest regime classifier enforces a strict chronological 80/20 boundary (shuffle=False), fitting PCA and feature scalers exclusively on the historical 80% training window before evaluating on the out-of-sample 20% test horizon.

src/models/regime_classifier.py(python)
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler

def train_chronological_regime_classifier(
    features_df: pd.DataFrame,
    target_regime: pd.Series,
    train_ratio: float = 0.80,
):
    split_idx = int(len(features_df) * train_ratio)

    X_train, X_test = features_df.iloc[:split_idx], features_df.iloc[split_idx:]
    y_train, y_test = target_regime.iloc[:split_idx], target_regime.iloc[split_idx:]

    scaler = StandardScaler()
    X_train_scaled = scaler.fit_transform(X_train)
    X_test_scaled = scaler.transform(X_test)  # Never fit_transform on test window

    clf = RandomForestClassifier(
        n_estimators=200,
        max_depth=8,
        min_samples_leaf=5,
        random_state=42,
    )
    clf.fit(X_train_scaled, y_train)
    return clf, scaler, X_test_scaled, y_test

4. Resilient 3-Tier Data Failover in Production

When deployed to Streamlit Cloud, upstream news and search APIs can rate-limit or time out. The pipeline uses a deterministic 3-tier ingestion cascade (GDELT v2 tone -> Google Trends PCA fear index -> curated VADER headlines) with local caching so econometric diagnostics remain available even during upstream outages.

Academic milestone: Completed as the 8th-semester B.Tech CSE major project at KIIT University (contributing to a 9.20 Semester 8 SGPA).

Share Card & OpenGraph Preview

1200×630 Dynamic OG Card
RAHUL ROY · ENGINEERING NOTE · MARKET SENTIMENT & FINANCIAL VOLATILITY ANALYZER6 min read

Modeling asymmetric financial volatility with EGARCH(1,1) and leak-free chronological Random Forest splits

4,236+ Trading Days · EGARCH(1,1) + Chronological RFgithub.com/Rahul03ll