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.
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.
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_test4. 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.
Share Card & OpenGraph Preview
Modeling asymmetric financial volatility with EGARCH(1,1) and leak-free chronological Random Forest splits