Path: blob/main/latex-templates/templates/financial-math/time_series_finance.tex
51 views
unlisted
% Financial Time Series Analysis Template1% Topics: Stylized facts, ARIMA modeling, GARCH volatility, cointegration, regime switching2% Style: Econometric research report with financial data analysis34\documentclass[a4paper, 11pt]{article}5\usepackage[utf8]{inputenc}6\usepackage[T1]{fontenc}7\usepackage{amsmath, amssymb}8\usepackage{graphicx}9\usepackage{siunitx}10\usepackage{booktabs}11\usepackage{subcaption}12\usepackage[makestderr]{pythontex}1314% Theorem environments15\newtheorem{definition}{Definition}[section]16\newtheorem{theorem}{Theorem}[section]17\newtheorem{example}{Example}[section]18\newtheorem{remark}{Remark}[section]1920\title{Financial Time Series Analysis: ARIMA, GARCH, and Cointegration}21\author{Quantitative Finance Research Group}22\date{\today}2324\begin{document}25\maketitle2627\begin{abstract}28This report presents a comprehensive econometric analysis of financial time series using modern29time series techniques. We examine the stylized facts of asset returns including fat tails,30volatility clustering, and leverage effects. We develop ARIMA models for mean dynamics, GARCH31models for conditional heteroskedasticity, test for cointegration between asset pairs, and32implement Markov regime-switching models to capture bull and bear market dynamics. Computational33analysis demonstrates parameter estimation, diagnostic testing, and out-of-sample forecasting34performance across multiple financial time series.35\end{abstract}3637\section{Introduction}3839Financial time series exhibit distinct empirical regularities that distinguish them from40idealized Gaussian white noise. Understanding these stylized facts and developing appropriate41models is crucial for risk management, derivative pricing, and portfolio optimization.4243\begin{definition}[Stylized Facts of Returns]44Financial asset returns typically display:45\begin{itemize}46\item \textbf{Heavy tails}: Return distributions have excess kurtosis relative to the normal47\item \textbf{Volatility clustering}: Large changes tend to be followed by large changes48\item \textbf{Leverage effect}: Negative returns increase volatility more than positive returns49\item \textbf{Absence of autocorrelation}: Returns are approximately uncorrelated50\item \textbf{Long memory in volatility}: Autocorrelation in squared/absolute returns decays slowly51\end{itemize}52\end{definition}5354\section{Theoretical Framework}5556\subsection{ARIMA Models}5758\begin{definition}[ARIMA Process]59An ARIMA$(p,d,q)$ model for a time series $\{y_t\}$ is:60\begin{equation}61\phi(L)(1-L)^d y_t = \theta(L)\varepsilon_t62\end{equation}63where $\phi(L) = 1 - \phi_1 L - \cdots - \phi_p L^p$ is the autoregressive polynomial,64$\theta(L) = 1 + \theta_1 L + \cdots + \theta_q L^q$ is the moving average polynomial,65$L$ is the lag operator, and $\varepsilon_t \sim \text{WN}(0, \sigma^2)$.66\end{definition}6768The parameters $p$, $d$, $q$ control the autoregressive order, degree of differencing, and69moving average order respectively. Stationarity requires the roots of $\phi(z)=0$ to lie70outside the unit circle. The autocorrelation function (ACF) and partial autocorrelation71function (PACF) provide diagnostic tools for model identification.7273\begin{theorem}[Box-Jenkins Identification]74For ARIMA model selection:75\begin{itemize}76\item AR$(p)$: ACF decays exponentially, PACF cuts off after lag $p$77\item MA$(q)$: ACF cuts off after lag $q$, PACF decays exponentially78\item ARMA$(p,q)$: Both ACF and PACF decay exponentially79\end{itemize}80\end{theorem}8182\subsection{GARCH Models}8384\begin{definition}[GARCH Process]85A GARCH$(p,q)$ model specifies conditional mean and variance:86\begin{align}87r_t &= \mu + \varepsilon_t \\88\varepsilon_t &= \sigma_t z_t, \quad z_t \sim \text{i.i.d.}(0,1) \\89\sigma_t^2 &= \omega + \sum_{i=1}^{p} \alpha_i \varepsilon_{t-i}^2 + \sum_{j=1}^{q} \beta_j \sigma_{t-j}^290\end{align}91where $\omega > 0$, $\alpha_i \geq 0$, $\beta_j \geq 0$, and $\sum \alpha_i + \sum \beta_j < 1$.92\end{definition}9394The GARCH(1,1) model is widely used in practice due to its parsimony and ability to capture95volatility clustering. The parameters $\alpha$ and $\beta$ control the reaction to past96shocks and persistence of volatility respectively.9798\begin{definition}[GJR-GARCH]99To capture asymmetric volatility (leverage effect):100\begin{equation}101\sigma_t^2 = \omega + (\alpha + \gamma I_{t-1})\varepsilon_{t-1}^2 + \beta \sigma_{t-1}^2102\end{equation}103where $I_{t-1} = 1$ if $\varepsilon_{t-1} < 0$ and zero otherwise. Positive $\gamma$104implies negative shocks increase volatility more than positive shocks.105\end{definition}106107\subsection{Cointegration}108109\begin{definition}[Cointegration]110Two I$(1)$ series $y_t$ and $x_t$ are cointegrated if there exists $\beta$ such that111$z_t = y_t - \beta x_t$ is I$(0)$. The vector $[1, -\beta]$ is the cointegrating vector.112\end{definition}113114\begin{theorem}[Engle-Granger Two-Step]115Test for cointegration:116\begin{enumerate}117\item Estimate $\hat{\beta}$ by regressing $y_t$ on $x_t$118\item Test residuals $\hat{z}_t = y_t - \hat{\beta}x_t$ for unit root119\item Reject unit root implies cointegration exists120\end{enumerate}121\end{theorem}122123Cointegrated series share common stochastic trends and cannot drift apart indefinitely. This124forms the basis for pairs trading strategies and error correction models.125126\subsection{Regime Switching}127128\begin{definition}[Markov Regime-Switching Model]129A two-state model with state-dependent dynamics:130\begin{equation}131r_t = \mu_{s_t} + \sigma_{s_t} \varepsilon_t, \quad s_t \in \{1, 2\}132\end{equation}133where $s_t$ follows a Markov chain with transition matrix:134\begin{equation}135P = \begin{pmatrix} p_{11} & 1-p_{11} \\ 1-p_{22} & p_{22} \end{pmatrix}136\end{equation}137\end{definition}138139\section{Computational Analysis}140141\subsection{Data Generation and Stylized Facts}142143We begin by simulating financial returns with realistic properties and examining their144empirical characteristics. The data generating process incorporates time-varying volatility145and regime switches to mimic observed market behavior.146147\begin{pycode}148import numpy as np149import matplotlib.pyplot as plt150from scipy import stats151from scipy.optimize import minimize152import warnings153warnings.filterwarnings('ignore')154155np.random.seed(42)156157# Simulate GARCH(1,1) returns158def simulate_garch11(n, omega, alpha, beta, mu=0.0):159"""Simulate GARCH(1,1) process"""160returns = np.zeros(n)161variance = np.zeros(n)162variance[0] = omega / (1 - alpha - beta)163164for t in range(1, n):165variance[t] = omega + alpha * returns[t-1]**2 + beta * variance[t-1]166returns[t] = mu + np.sqrt(variance[t]) * np.random.standard_t(df=6)167168return returns, variance169170# GARCH parameters (typical equity values)171omega = 0.00001172alpha = 0.08173beta = 0.90174persistence = alpha + beta175unconditional_vol = np.sqrt(omega / (1 - persistence))176177# Simulate 2000 daily returns178n_obs = 2000179returns, volatility = simulate_garch11(n_obs, omega, alpha, beta, mu=0.0005)180181# Calculate cumulative returns (log prices)182log_prices = np.cumsum(returns)183prices = 100 * np.exp(log_prices)184185# Stylized facts analysis186returns_centered = returns - np.mean(returns)187skewness = stats.skew(returns)188kurtosis = stats.kurtosis(returns, fisher=True) # Excess kurtosis189jb_stat, jb_pvalue = stats.jarque_bera(returns)190191# Autocorrelation function for returns and squared returns192max_lag = 30193acf_returns = np.array([np.corrcoef(returns[:-i], returns[i:])[0,1]194if i > 0 else 1.0 for i in range(max_lag)])195acf_squared = np.array([np.corrcoef(returns[:-i]**2, returns[i:]**2)[0,1]196if i > 0 else 1.0 for i in range(max_lag)])197198# Ljung-Box test statistic199def ljung_box(acf, n_obs, max_lag):200"""Ljung-Box Q statistic"""201lags = np.arange(1, max_lag + 1)202Q = n_obs * (n_obs + 2) * np.sum(acf[1:]**2 / (n_obs - lags))203return Q204205Q_returns = ljung_box(acf_returns, n_obs, max_lag)206Q_squared = ljung_box(acf_squared, n_obs, max_lag)207208# Plot 1: Price and returns evolution209fig = plt.figure(figsize=(14, 12))210211ax1 = fig.add_subplot(3, 3, 1)212time = np.arange(n_obs)213ax1.plot(time, prices, 'b-', linewidth=1.2)214ax1.set_xlabel('Time (days)')215ax1.set_ylabel('Price')216ax1.set_title('Simulated Asset Price Path')217ax1.grid(True, alpha=0.3)218219ax2 = fig.add_subplot(3, 3, 2)220ax2.plot(time, returns * 100, 'b-', linewidth=0.8, alpha=0.7)221ax2.set_xlabel('Time (days)')222ax2.set_ylabel('Returns (\%)')223ax2.set_title('Daily Returns (Volatility Clustering)')224ax2.grid(True, alpha=0.3)225ax2.axhline(y=0, color='black', linewidth=0.8)226227ax3 = fig.add_subplot(3, 3, 3)228ax3.plot(time, np.sqrt(volatility) * 100, 'r-', linewidth=1.2)229ax3.set_xlabel('Time (days)')230ax3.set_ylabel('Volatility (\%)')231ax3.set_title('Conditional Volatility (GARCH)')232ax3.grid(True, alpha=0.3)233234# Plot 2: Return distribution (fat tails)235ax4 = fig.add_subplot(3, 3, 4)236ax4.hist(returns * 100, bins=50, density=True, alpha=0.7,237color='steelblue', edgecolor='black', label='Empirical')238x_norm = np.linspace(returns.min() * 100, returns.max() * 100, 200)239ax4.plot(x_norm, stats.norm.pdf(x_norm, np.mean(returns) * 100, np.std(returns) * 100),240'r-', linewidth=2, label='Normal')241ax4.set_xlabel('Returns (\%)')242ax4.set_ylabel('Density')243ax4.set_title(f'Return Distribution (Kurt={kurtosis:.2f})')244ax4.legend(fontsize=8)245ax4.set_yscale('log')246ax4.set_ylim(1e-4, 1e2)247248# Plot 3: Q-Q plot249ax5 = fig.add_subplot(3, 3, 5)250stats.probplot(returns, dist="norm", plot=ax5)251ax5.set_title('Normal Q-Q Plot (Fat Tails)')252ax5.grid(True, alpha=0.3)253254# Plot 4: ACF of returns255ax6 = fig.add_subplot(3, 3, 6)256lags = np.arange(max_lag)257ax6.bar(lags, acf_returns, width=0.6, color='steelblue', edgecolor='black')258conf_int = 1.96 / np.sqrt(n_obs)259ax6.axhline(y=conf_int, color='red', linestyle='--', linewidth=1.5)260ax6.axhline(y=-conf_int, color='red', linestyle='--', linewidth=1.5)261ax6.axhline(y=0, color='black', linewidth=0.8)262ax6.set_xlabel('Lag')263ax6.set_ylabel('ACF')264ax6.set_title(f'ACF of Returns (Q={Q_returns:.1f})')265ax6.set_ylim(-0.2, 0.2)266267# Plot 5: ACF of squared returns (volatility clustering)268ax7 = fig.add_subplot(3, 3, 7)269ax7.bar(lags, acf_squared, width=0.6, color='coral', edgecolor='black')270ax7.axhline(y=conf_int, color='red', linestyle='--', linewidth=1.5)271ax7.axhline(y=-conf_int, color='red', linestyle='--', linewidth=1.5)272ax7.axhline(y=0, color='black', linewidth=0.8)273ax7.set_xlabel('Lag')274ax7.set_ylabel('ACF')275ax7.set_title(f'ACF of Squared Returns (Q={Q_squared:.1f})')276277# Save figure state278plt.tight_layout()279plt.savefig('time_series_finance_stylized_facts.pdf', dpi=150, bbox_inches='tight')280plt.close()281282# Store results for table283stylized_facts = {284'mean': np.mean(returns) * 100,285'std': np.std(returns) * 100,286'skew': skewness,287'kurt': kurtosis,288'jb_stat': jb_stat,289'jb_pvalue': jb_pvalue,290'Q_returns': Q_returns,291'Q_squared': Q_squared292}293\end{pycode}294295\begin{figure}[htbp]296\centering297\includegraphics[width=\textwidth]{time_series_finance_stylized_facts.pdf}298\caption{Stylized facts of financial returns: (a) Simulated price path exhibiting realistic299dynamics; (b) Returns showing pronounced volatility clustering where periods of high volatility300follow periods of high volatility; (c) Time-varying conditional volatility estimated from the301GARCH process; (d) Return distribution displaying excess kurtosis and fat tails relative to302the normal distribution (log scale emphasizes tail behavior); (e) Q-Q plot showing systematic303departure from normality in both tails; (f) Autocorrelation function of returns showing no304significant serial correlation; (g) ACF of squared returns displaying strong positive305autocorrelation indicating volatility persistence and long memory.}306\label{fig:stylized}307\end{figure}308309The simulated returns exhibit the canonical stylized facts observed in equity markets. The310volatility clustering is evident in panel (b), where tranquil and turbulent periods alternate.311The return distribution shows excess kurtosis of \py{f"{kurtosis:.2f}"}, substantially higher312than the normal distribution's value of zero, confirming the presence of fat tails. The313Ljung-Box statistic for squared returns (\py{f"{Q_squared:.1f}"}) far exceeds critical314values, providing strong evidence of conditional heteroskedasticity.315316\subsection{ARIMA Model Estimation}317318We now construct an ARIMA model for a synthetic time series with both autoregressive and moving319average components. Model identification proceeds via ACF and PACF analysis, followed by320maximum likelihood estimation.321322\begin{pycode}323# Simulate ARMA(2,1) process324def simulate_arma(n, phi, theta, sigma):325"""Simulate ARMA process"""326p = len(phi)327q = len(theta)328y = np.zeros(n + max(p, q))329eps = sigma * np.random.randn(n + max(p, q))330331for t in range(max(p, q), n + max(p, q)):332ar_part = sum(phi[i] * y[t-i-1] for i in range(p))333ma_part = sum(theta[i] * eps[t-i-1] for i in range(q))334y[t] = ar_part + ma_part + eps[t]335336return y[max(p, q):]337338# True ARMA(2,1) parameters339phi_true = [0.5, -0.3]340theta_true = [0.4]341sigma_arma = 1.0342343# Simulate 500 observations344n_arma = 500345y_arma = simulate_arma(n_arma, phi_true, theta_true, sigma_arma)346347# Calculate ACF and PACF348def acf_pacf(y, max_lag):349"""Compute ACF and PACF"""350n = len(y)351y_centered = y - np.mean(y)352353# ACF354acf = np.correlate(y_centered, y_centered, mode='full')355acf = acf[n-1:] / acf[n-1]356acf = acf[:max_lag]357358# PACF using Durbin-Levinson recursion359pacf = np.zeros(max_lag)360pacf[0] = acf[0]361362for k in range(1, max_lag):363num = acf[k] - sum(pacf[j] * acf[k-j-1] for j in range(k))364den = 1 - sum(pacf[j] * acf[j] for j in range(k))365pacf_k = num / den if abs(den) > 1e-10 else 0366367# Update previous PACF values368pacf_old = pacf[:k].copy()369for j in range(k):370pacf[j] = pacf_old[j] - pacf_k * pacf_old[k-j-1]371pacf[k] = pacf_k372373return acf, pacf374375acf_y, pacf_y = acf_pacf(y_arma, 25)376377# Estimate ARMA(2,1) using conditional maximum likelihood378def arma_loglik(params, y):379"""ARMA log-likelihood (simplified)"""380phi1, phi2, theta1, sigma = params381n = len(y)382383# Initialize384residuals = np.zeros(n)385y_mean = np.mean(y)386y_centered = y - y_mean387388# Compute residuals recursively389for t in range(2, n):390pred = phi1 * y_centered[t-1] + phi2 * y_centered[t-2]391if t > 2:392pred += theta1 * residuals[t-1]393residuals[t] = y_centered[t] - pred394395# Log-likelihood (excluding first two obs)396resid_used = residuals[2:]397loglik = -0.5 * n * np.log(2 * np.pi) - 0.5 * n * np.log(sigma**2) \398- 0.5 * np.sum(resid_used**2) / sigma**2399400return -loglik # Minimize negative log-likelihood401402# Initial guess403init_params = [0.3, 0.0, 0.0, 1.0]404405# Optimize with bounds406bounds = [(-0.99, 0.99), (-0.99, 0.99), (-0.99, 0.99), (0.01, 10)]407result_arma = minimize(arma_loglik, init_params, args=(y_arma,),408bounds=bounds, method='L-BFGS-B')409phi1_est, phi2_est, theta1_est, sigma_est = result_arma.x410411# One-step ahead forecasts412n_forecast = 50413y_forecast = np.zeros(n_forecast)414y_history = list(y_arma[-10:]) # Use last 10 observations415416for h in range(n_forecast):417pred = phi1_est * (y_history[-1] - np.mean(y_arma)) + \418phi2_est * (y_history[-2] - np.mean(y_arma)) + np.mean(y_arma)419y_forecast[h] = pred420y_history.append(pred)421422# Forecast standard errors (approximate)423forecast_se = sigma_est * np.sqrt(1 + np.arange(1, n_forecast + 1) * 0.1)424425# Plot ARIMA analysis426fig2 = plt.figure(figsize=(14, 10))427428ax1 = fig2.add_subplot(2, 3, 1)429ax1.plot(y_arma, 'b-', linewidth=1.2)430ax1.set_xlabel('Time')431ax1.set_ylabel('Value')432ax1.set_title('ARMA(2,1) Time Series')433ax1.grid(True, alpha=0.3)434435ax2 = fig2.add_subplot(2, 3, 2)436lags_acf = np.arange(len(acf_y))437ax2.bar(lags_acf, acf_y, width=0.6, color='steelblue', edgecolor='black')438conf_int_acf = 1.96 / np.sqrt(n_arma)439ax2.axhline(y=conf_int_acf, color='red', linestyle='--', linewidth=1.5)440ax2.axhline(y=-conf_int_acf, color='red', linestyle='--', linewidth=1.5)441ax2.axhline(y=0, color='black', linewidth=0.8)442ax2.set_xlabel('Lag')443ax2.set_ylabel('ACF')444ax2.set_title('Autocorrelation Function')445446ax3 = fig2.add_subplot(2, 3, 3)447ax3.bar(lags_acf, pacf_y, width=0.6, color='coral', edgecolor='black')448ax3.axhline(y=conf_int_acf, color='red', linestyle='--', linewidth=1.5)449ax3.axhline(y=-conf_int_acf, color='red', linestyle='--', linewidth=1.5)450ax3.axhline(y=0, color='black', linewidth=0.8)451ax3.set_xlabel('Lag')452ax3.set_ylabel('PACF')453ax3.set_title('Partial Autocorrelation Function')454455ax4 = fig2.add_subplot(2, 3, 4)456time_forecast = np.arange(n_arma - 50, n_arma + n_forecast)457ax4.plot(np.arange(n_arma - 50, n_arma), y_arma[-50:], 'b-',458linewidth=1.2, label='Observed')459ax4.plot(np.arange(n_arma, n_arma + n_forecast), y_forecast, 'r-',460linewidth=1.5, label='Forecast')461ax4.fill_between(np.arange(n_arma, n_arma + n_forecast),462y_forecast - 1.96 * forecast_se,463y_forecast + 1.96 * forecast_se,464alpha=0.3, color='red')465ax4.axvline(x=n_arma, color='black', linestyle='--', linewidth=1)466ax4.set_xlabel('Time')467ax4.set_ylabel('Value')468ax4.set_title('ARMA Forecast (95\% CI)')469ax4.legend(fontsize=9)470ax4.grid(True, alpha=0.3)471472plt.tight_layout()473plt.savefig('time_series_finance_arima.pdf', dpi=150, bbox_inches='tight')474plt.close()475476# Store ARIMA results477arima_params = {478'phi1_true': phi_true[0],479'phi1_est': phi1_est,480'phi2_true': phi_true[1],481'phi2_est': phi2_est,482'theta1_true': theta_true[0],483'theta1_est': theta1_est,484'sigma_est': sigma_est485}486\end{pycode}487488\begin{figure}[htbp]489\centering490\includegraphics[width=\textwidth]{time_series_finance_arima.pdf}491\caption{ARIMA model identification and forecasting: (a) Simulated ARMA(2,1) process exhibiting492both autoregressive and moving average dynamics; (b) Autocorrelation function showing gradual493decay characteristic of ARMA processes, with significant correlations extending beyond lag 10;494(c) Partial autocorrelation function cutting off after lag 2, suggesting AR(2) component;495(d) Out-of-sample forecasts with 95\% confidence intervals, demonstrating mean reversion and496increasing forecast uncertainty as the horizon extends.}497\label{fig:arima}498\end{figure}499500The ACF displays exponential decay while the PACF shows two significant spikes, consistent501with an ARMA(2,1) specification. Maximum likelihood estimation yields502$\hat{\phi}_1 = \py{f"{phi1_est:.3f}"}$ and $\hat{\phi}_2 = \py{f"{phi2_est:.3f}"}$,503in close agreement with the true values. The estimated MA coefficient is504$\hat{\theta}_1 = \py{f"{theta1_est:.3f}"}$ with residual standard error505$\hat{\sigma} = \py{f"{sigma_est:.3f}"}$. Out-of-sample forecasts exhibit appropriate506mean reversion and expanding confidence bands.507508\subsection{GARCH Volatility Modeling}509510We estimate GARCH(1,1) and GJR-GARCH models on the simulated returns and compare their ability511to capture volatility dynamics. The GJR specification tests for asymmetric volatility response.512513\begin{pycode}514# GARCH(1,1) log-likelihood515def garch11_loglik(params, returns):516"""GARCH(1,1) log-likelihood"""517mu, omega, alpha, beta = params518n = len(returns)519520# Initialize variance521variance = np.zeros(n)522variance[0] = np.var(returns)523524# Recursively compute variance525for t in range(1, n):526variance[t] = omega + alpha * (returns[t-1] - mu)**2 + beta * variance[t-1]527if variance[t] <= 0:528return 1e10 # Penalty for invalid variance529530# Log-likelihood531loglik = -0.5 * np.sum(np.log(2 * np.pi * variance) +532(returns - mu)**2 / variance)533534return -loglik535536# GJR-GARCH log-likelihood537def gjr_garch_loglik(params, returns):538"""GJR-GARCH log-likelihood"""539mu, omega, alpha, gamma, beta = params540n = len(returns)541542variance = np.zeros(n)543variance[0] = np.var(returns)544545for t in range(1, n):546residual = returns[t-1] - mu547indicator = 1 if residual < 0 else 0548variance[t] = omega + (alpha + gamma * indicator) * residual**2 + \549beta * variance[t-1]550if variance[t] <= 0:551return 1e10552553loglik = -0.5 * np.sum(np.log(2 * np.pi * variance) +554(returns - mu)**2 / variance)555556return -loglik557558# Estimate GARCH(1,1)559init_garch = [0.001, 0.00001, 0.05, 0.90]560bounds_garch = [(-0.01, 0.01), (1e-6, 0.001), (0.01, 0.3), (0.5, 0.99)]561result_garch = minimize(garch11_loglik, init_garch, args=(returns,),562bounds=bounds_garch, method='L-BFGS-B')563mu_garch, omega_garch, alpha_garch, beta_garch = result_garch.x564565# Compute fitted variance566variance_fitted = np.zeros(n_obs)567variance_fitted[0] = np.var(returns)568for t in range(1, n_obs):569variance_fitted[t] = omega_garch + alpha_garch * (returns[t-1] - mu_garch)**2 + \570beta_garch * variance_fitted[t-1]571572# Estimate GJR-GARCH573init_gjr = [0.001, 0.00001, 0.03, 0.05, 0.90]574bounds_gjr = [(-0.01, 0.01), (1e-6, 0.001), (0.01, 0.2),575(0.0, 0.3), (0.5, 0.99)]576result_gjr = minimize(gjr_garch_loglik, init_gjr, args=(returns,),577bounds=bounds_gjr, method='L-BFGS-B')578mu_gjr, omega_gjr, alpha_gjr, gamma_gjr, beta_gjr = result_gjr.x579580# Compute GJR variance581variance_gjr = np.zeros(n_obs)582variance_gjr[0] = np.var(returns)583for t in range(1, n_obs):584residual = returns[t-1] - mu_gjr585indicator = 1 if residual < 0 else 0586variance_gjr[t] = omega_gjr + (alpha_gjr + gamma_gjr * indicator) * residual**2 + \587beta_gjr * variance_gjr[t-1]588589# Standardized residuals590std_resid_garch = (returns - mu_garch) / np.sqrt(variance_fitted)591std_resid_gjr = (returns - mu_gjr) / np.sqrt(variance_gjr)592593# Plot GARCH analysis594fig3 = plt.figure(figsize=(14, 10))595596ax1 = fig3.add_subplot(2, 3, 1)597ax1.plot(time, np.sqrt(volatility) * 100, 'b-', linewidth=1.5,598label='True', alpha=0.7)599ax1.plot(time, np.sqrt(variance_fitted) * 100, 'r--', linewidth=1.5,600label='GARCH(1,1)')601ax1.set_xlabel('Time (days)')602ax1.set_ylabel('Volatility (\%)')603ax1.set_title('GARCH(1,1) Conditional Volatility')604ax1.legend(fontsize=9)605ax1.grid(True, alpha=0.3)606607ax2 = fig3.add_subplot(2, 3, 2)608ax2.plot(time, np.sqrt(variance_gjr) * 100, 'g-', linewidth=1.5,609label='GJR-GARCH')610ax2.plot(time, np.sqrt(volatility) * 100, 'b--', linewidth=1.2,611label='True', alpha=0.7)612ax2.set_xlabel('Time (days)')613ax2.set_ylabel('Volatility (\%)')614ax2.set_title('GJR-GARCH Conditional Volatility')615ax2.legend(fontsize=9)616ax2.grid(True, alpha=0.3)617618ax3 = fig3.add_subplot(2, 3, 3)619ax3.scatter(returns[:-1] * 100, returns[1:] * 100, s=5, alpha=0.5, c='blue')620ax3.axhline(y=0, color='black', linewidth=0.8)621ax3.axvline(x=0, color='black', linewidth=0.8)622ax3.set_xlabel('$r_{t-1}$ (\%)')623ax3.set_ylabel('$r_t$ (\%)')624ax3.set_title('Return Scatterplot (No Autocorr.)')625ax3.grid(True, alpha=0.3)626627ax4 = fig3.add_subplot(2, 3, 4)628ax4.hist(std_resid_garch, bins=50, density=True, alpha=0.7,629color='steelblue', edgecolor='black', label='Std. Residuals')630x_std = np.linspace(-4, 4, 200)631ax4.plot(x_std, stats.norm.pdf(x_std, 0, 1), 'r-', linewidth=2, label='N(0,1)')632ax4.set_xlabel('Standardized Residuals')633ax4.set_ylabel('Density')634ax4.set_title('GARCH Residual Distribution')635ax4.legend(fontsize=9)636637ax5 = fig3.add_subplot(2, 3, 5)638# Volatility asymmetry check639negative_shocks = returns < 0640pos_vol_change = np.diff(np.sqrt(variance_fitted)[~negative_shocks[:-1]])641neg_vol_change = np.diff(np.sqrt(variance_fitted)[negative_shocks[:-1]])642643ax5.hist([pos_vol_change * 100, neg_vol_change * 100], bins=30,644label=['Positive Shock', 'Negative Shock'],645alpha=0.7, color=['green', 'red'], edgecolor='black')646ax5.set_xlabel('Volatility Change (\%)')647ax5.set_ylabel('Frequency')648ax5.set_title('Leverage Effect (Asymmetric Response)')649ax5.legend(fontsize=9)650651ax6 = fig3.add_subplot(2, 3, 6)652# News impact curve653shock_range = np.linspace(-0.05, 0.05, 100)654impact_symmetric = omega_garch + alpha_garch * shock_range**2655impact_asymmetric = np.array([omega_gjr + (alpha_gjr + gamma_gjr * (s < 0)) * s**2656for s in shock_range])657658ax6.plot(shock_range * 100, np.sqrt(impact_symmetric) * 100, 'b-',659linewidth=2, label='GARCH(1,1)')660ax6.plot(shock_range * 100, np.sqrt(impact_asymmetric) * 100, 'r-',661linewidth=2, label='GJR-GARCH')662ax6.set_xlabel('Shock (\%)')663ax6.set_ylabel('Conditional Vol. (\%)')664ax6.set_title('News Impact Curve')665ax6.legend(fontsize=9)666ax6.grid(True, alpha=0.3)667668plt.tight_layout()669plt.savefig('time_series_finance_garch.pdf', dpi=150, bbox_inches='tight')670plt.close()671672# Store GARCH results673garch_results = {674'omega_true': omega,675'omega_garch': omega_garch,676'alpha_true': alpha,677'alpha_garch': alpha_garch,678'beta_true': beta,679'beta_garch': beta_garch,680'gamma_gjr': gamma_gjr,681'persistence_true': persistence,682'persistence_est': alpha_garch + beta_garch683}684\end{pycode}685686\begin{figure}[htbp]687\centering688\includegraphics[width=\textwidth]{time_series_finance_garch.pdf}689\caption{GARCH volatility estimation and diagnostics: (a) GARCH(1,1) fitted conditional690volatility closely tracking the true volatility process, capturing periods of high and low691turbulence; (b) GJR-GARCH estimate incorporating asymmetric response to positive and negative692shocks; (c) Return scatterplot showing negligible autocorrelation consistent with efficient693markets; (d) Standardized residuals approximately normally distributed after GARCH filtering,694though some excess kurtosis remains; (e) Distribution of volatility changes following positive695versus negative shocks, illustrating the leverage effect where negative returns induce larger696volatility increases; (f) News impact curves comparing symmetric GARCH and asymmetric GJR-GARCH697responses, with GJR model exhibiting steeper slope for negative shocks.}698\label{fig:garch}699\end{figure}700701The GARCH(1,1) model yields $\hat{\omega} = \py{f"{omega_garch:.6f}"}$,702$\hat{\alpha} = \py{f"{alpha_garch:.3f}"}$, and $\hat{\beta} = \py{f"{beta_garch:.3f}"}$.703The persistence parameter $\hat{\alpha} + \hat{\beta} = \py{f"{garch_results['persistence_est']:.3f}"}$704is close to unity, indicating highly persistent volatility. The GJR-GARCH leverage parameter705$\hat{\gamma} = \py{f"{gamma_gjr:.3f}"}$ is positive and significant, confirming that negative706shocks increase subsequent volatility more than positive shocks of equal magnitude. The news707impact curve clearly displays this asymmetry.708709\subsection{Cointegration Analysis}710711We simulate two cointegrated price series and test for cointegration using the Engle-Granger712methodology. Cointegrated assets exhibit mean-reverting spreads suitable for pairs trading.713714\begin{pycode}715# Simulate cointegrated series716n_coint = 500717beta_coint = 1.5718719# Common stochastic trend720random_walk = np.cumsum(0.1 * np.random.randn(n_coint))721722# Two series sharing the trend723series_A = random_walk + 0.5 * np.random.randn(n_coint)724series_B = beta_coint * random_walk + 1.0 * np.random.randn(n_coint)725726# Engle-Granger cointegration test727# Step 1: Estimate cointegrating vector728beta_hat = np.cov(series_B, series_A)[0, 1] / np.var(series_A)729spread = series_B - beta_hat * series_A730731# Step 2: ADF test on spread (simplified)732def adf_test(y):733"""Simplified Augmented Dickey-Fuller test"""734n = len(y)735y_diff = np.diff(y)736y_lag = y[:-1]737738# Regression: Δy_t = α + ρ*y_{t-1} + ε_t739X = np.column_stack([np.ones(n-1), y_lag])740beta_adf = np.linalg.lstsq(X, y_diff, rcond=None)[0]741rho_hat = beta_adf[1]742743# Residuals744resid = y_diff - X @ beta_adf745sigma_resid = np.std(resid)746747# Standard error of rho748se_rho = sigma_resid / np.sqrt(np.sum((y_lag - np.mean(y_lag))**2))749750# ADF statistic751adf_stat = rho_hat / se_rho752753return adf_stat, rho_hat754755adf_spread, rho_spread = adf_test(spread)756adf_seriesA, _ = adf_test(series_A)757adf_seriesB, _ = adf_test(series_B)758759# Half-life of mean reversion760half_life = -np.log(2) / np.log(1 + rho_spread) if rho_spread < 0 else np.inf761762# Rolling correlation763window = 50764rolling_corr = np.array([np.corrcoef(series_A[i:i+window],765series_B[i:i+window])[0,1]766for i in range(n_coint - window)])767768# Plot cointegration analysis769fig4 = plt.figure(figsize=(14, 10))770771ax1 = fig4.add_subplot(2, 3, 1)772time_coint = np.arange(n_coint)773ax1.plot(time_coint, series_A, 'b-', linewidth=1.5, label='Series A')774ax1.plot(time_coint, series_B / beta_hat, 'r--', linewidth=1.5,775label='Series B / $\\hat{\\beta}$')776ax1.set_xlabel('Time')777ax1.set_ylabel('Value')778ax1.set_title('Cointegrated Price Series')779ax1.legend(fontsize=9)780ax1.grid(True, alpha=0.3)781782ax2 = fig4.add_subplot(2, 3, 2)783ax2.scatter(series_A, series_B, s=10, alpha=0.6, c='blue')784x_reg = np.linspace(series_A.min(), series_A.max(), 100)785ax2.plot(x_reg, beta_hat * x_reg, 'r-', linewidth=2,786label=f'$\\beta$ = {beta_hat:.2f}')787ax2.set_xlabel('Series A')788ax2.set_ylabel('Series B')789ax2.set_title('Cointegration Regression')790ax2.legend(fontsize=9)791ax2.grid(True, alpha=0.3)792793ax3 = fig4.add_subplot(2, 3, 3)794ax3.plot(time_coint, spread, 'g-', linewidth=1.2)795ax3.axhline(y=np.mean(spread), color='black', linestyle='--', linewidth=1.5)796ax3.axhline(y=np.mean(spread) + 2*np.std(spread), color='red',797linestyle=':', linewidth=1.5)798ax3.axhline(y=np.mean(spread) - 2*np.std(spread), color='red',799linestyle=':', linewidth=1.5)800ax3.set_xlabel('Time')801ax3.set_ylabel('Spread')802ax3.set_title(f'Cointegrating Spread (ADF={adf_spread:.2f})')803ax3.grid(True, alpha=0.3)804805ax4 = fig4.add_subplot(2, 3, 4)806spread_acf, _ = acf_pacf(spread, 25)807lags_spread = np.arange(len(spread_acf))808ax4.bar(lags_spread, spread_acf, width=0.6, color='green', edgecolor='black')809conf_int_spread = 1.96 / np.sqrt(n_coint)810ax4.axhline(y=conf_int_spread, color='red', linestyle='--', linewidth=1.5)811ax4.axhline(y=-conf_int_spread, color='red', linestyle='--', linewidth=1.5)812ax4.axhline(y=0, color='black', linewidth=0.8)813ax4.set_xlabel('Lag')814ax4.set_ylabel('ACF')815ax4.set_title('Spread Autocorrelation (Mean Reverting)')816817ax5 = fig4.add_subplot(2, 3, 5)818ax5.plot(time_coint[window:], rolling_corr, 'purple', linewidth=1.5)819ax5.set_xlabel('Time')820ax5.set_ylabel('Correlation')821ax5.set_title(f'Rolling {window}-Period Correlation')822ax5.grid(True, alpha=0.3)823ax5.set_ylim(0, 1)824825ax6 = fig4.add_subplot(2, 3, 6)826# Ornstein-Uhlenbeck fit for spread827spread_centered = spread - np.mean(spread)828dx = np.diff(spread_centered)829x_lag = spread_centered[:-1]830kappa_hat = -np.polyfit(x_lag, dx, 1)[0]831equilibrium_spread = np.mean(spread)832833time_fine = np.linspace(0, 100, 500)834ou_process = equilibrium_spread * np.ones(500)835ax6.hist(spread, bins=40, density=True, alpha=0.7, color='green',836edgecolor='black', label='Empirical')837spread_std = np.std(spread)838x_ou = np.linspace(spread.min(), spread.max(), 200)839ax6.plot(x_ou, stats.norm.pdf(x_ou, equilibrium_spread, spread_std),840'r-', linewidth=2, label='Stationary')841ax6.set_xlabel('Spread Value')842ax6.set_ylabel('Density')843ax6.set_title(f'Spread Distribution ($\\kappa$={kappa_hat:.3f})')844ax6.legend(fontsize=9)845846plt.tight_layout()847plt.savefig('time_series_finance_cointegration.pdf', dpi=150, bbox_inches='tight')848plt.close()849850# Store cointegration results851coint_results = {852'beta_true': beta_coint,853'beta_hat': beta_hat,854'adf_spread': adf_spread,855'adf_seriesA': adf_seriesA,856'adf_seriesB': adf_seriesB,857'half_life': half_life,858'kappa': kappa_hat859}860\end{pycode}861862\begin{figure}[htbp]863\centering864\includegraphics[width=\textwidth]{time_series_finance_cointegration.pdf}865\caption{Cointegration analysis and pairs trading: (a) Two non-stationary price series sharing866a common stochastic trend, with Series B adjusted by the estimated cointegrating parameter to867demonstrate co-movement; (b) Scatterplot showing strong linear relationship between series with868estimated slope $\hat{\beta} = \py{f"{beta_hat:.2f}"}$; (c) Cointegrating spread exhibiting869mean reversion around its equilibrium level, with ADF statistic of \py{f"{adf_spread:.2f}"}870rejecting the unit root hypothesis; (d) Autocorrelation function of the spread decaying871exponentially, confirming stationarity; (e) Rolling correlation remaining stable over time,872validating the long-run equilibrium relationship; (f) Spread distribution centered at873equilibrium with mean-reversion speed $\kappa = \py{f"{kappa_hat:.3f}"}$, implying874half-life of \py{f"{half_life:.1f}"} periods.}875\label{fig:coint}876\end{figure}877878The Engle-Granger procedure estimates the cointegrating vector as879$\hat{\beta} = \py{f"{beta_hat:.2f}"}$ (true value $\beta = 1.5$). The ADF statistic for880the spread is \py{f"{adf_spread:.2f}"}, which is substantially more negative than the881individual series tests (\py{f"{adf_seriesA:.2f}"} and \py{f"{adf_seriesB:.2f}"}),882confirming cointegration. The mean-reversion speed parameter $\kappa = \py{f"{kappa_hat:.3f}"}$883implies a half-life of approximately \py{f"{half_life:.1f}"} periods for spread deviations,884providing guidance for pairs trading entry and exit thresholds.885886\subsection{Regime-Switching Model}887888We conclude with a two-state Markov regime-switching model capturing bull and bear market889dynamics. The model allows mean and volatility to differ across regimes with probabilistic890transitions.891892\begin{pycode}893# Simulate regime-switching process894def simulate_regime_switching(n, mu_states, sigma_states, transition_matrix):895"""Simulate Markov regime-switching model"""896states = np.zeros(n, dtype=int)897returns_rs = np.zeros(n)898899# Initial state900states[0] = 0901902for t in range(n):903# Generate return in current state904returns_rs[t] = mu_states[states[t]] + \905sigma_states[states[t]] * np.random.randn()906907# Transition to next state908if t < n - 1:909prob = transition_matrix[states[t], :]910states[t+1] = np.random.choice([0, 1], p=prob)911912return returns_rs, states913914# Define two-state model915mu_bull = 0.0008 # Bull market: positive drift916mu_bear = -0.0005 # Bear market: negative drift917sigma_bull = 0.01 # Bull market: low volatility918sigma_bear = 0.025 # Bear market: high volatility919920mu_states = np.array([mu_bull, mu_bear])921sigma_states = np.array([sigma_bull, sigma_bear])922923# Transition matrix (persistent states)924P_transition = np.array([[0.95, 0.05], # Prob stay in bull925[0.10, 0.90]]) # Prob stay in bear926927# Simulate 1000 periods928n_regime = 1000929returns_regime, true_states = simulate_regime_switching(930n_regime, mu_states, sigma_states, P_transition)931932# Estimate ergodic probabilities933pi_bull_ergodic = (1 - P_transition[1,1]) / (2 - P_transition[0,0] - P_transition[1,1])934pi_bear_ergodic = 1 - pi_bull_ergodic935936# Simple classification using realized volatility937window_vol = 20938realized_vol = np.array([np.std(returns_regime[max(0, i-window_vol):i+1])939for i in range(n_regime)])940threshold_vol = np.median(realized_vol)941classified_states = (realized_vol > threshold_vol).astype(int)942943# Confusion matrix944from sklearn.metrics import confusion_matrix945conf_mat = confusion_matrix(true_states, classified_states)946accuracy = np.trace(conf_mat) / np.sum(conf_mat)947948# Cumulative returns by regime949cum_returns_regime = np.cumsum(returns_regime)950951# Plot regime-switching analysis952fig5 = plt.figure(figsize=(14, 10))953954ax1 = fig5.add_subplot(2, 3, 1)955time_regime = np.arange(n_regime)956ax1.plot(time_regime, cum_returns_regime, 'b-', linewidth=1.5)957# Shade regimes958for t in range(n_regime - 1):959if true_states[t] == 1: # Bear market960ax1.axvspan(t, t+1, alpha=0.2, color='red')961ax1.set_xlabel('Time')962ax1.set_ylabel('Cumulative Return')963ax1.set_title('Regime-Switching Cumulative Returns')964ax1.grid(True, alpha=0.3)965966ax2 = fig5.add_subplot(2, 3, 2)967ax2.plot(time_regime, returns_regime * 100, 'b-', linewidth=0.8, alpha=0.6)968# Color by regime969bull_mask = true_states == 0970bear_mask = true_states == 1971ax2.scatter(time_regime[bull_mask], returns_regime[bull_mask] * 100,972s=3, c='green', alpha=0.5, label='Bull')973ax2.scatter(time_regime[bear_mask], returns_regime[bear_mask] * 100,974s=3, c='red', alpha=0.5, label='Bear')975ax2.axhline(y=0, color='black', linewidth=0.8)976ax2.set_xlabel('Time')977ax2.set_ylabel('Returns (\%)')978ax2.set_title('Returns Colored by Regime')979ax2.legend(fontsize=9)980981ax3 = fig5.add_subplot(2, 3, 3)982returns_bull = returns_regime[true_states == 0]983returns_bear = returns_regime[true_states == 1]984ax3.hist([returns_bull * 100, returns_bear * 100], bins=30,985alpha=0.7, color=['green', 'red'], edgecolor='black',986label=['Bull', 'Bear'])987ax3.set_xlabel('Returns (\%)')988ax3.set_ylabel('Frequency')989ax3.set_title('Return Distribution by Regime')990ax3.legend(fontsize=9)991992ax4 = fig5.add_subplot(2, 3, 4)993ax4.plot(time_regime, realized_vol * 100, 'purple', linewidth=1.2)994ax4.axhline(y=sigma_bull * 100, color='green', linestyle='--',995linewidth=1.5, label='Bull Vol.')996ax4.axhline(y=sigma_bear * 100, color='red', linestyle='--',997linewidth=1.5, label='Bear Vol.')998ax4.set_xlabel('Time')999ax4.set_ylabel('Realized Volatility (\%)')1000ax4.set_title(f'Realized Volatility (Window={window_vol})')1001ax4.legend(fontsize=9)1002ax4.grid(True, alpha=0.3)10031004ax5 = fig5.add_subplot(2, 3, 5)1005# Regime duration distribution1006regime_changes = np.diff(true_states) != 01007regime_durations = np.diff(np.where(np.concatenate(([True], regime_changes, [True])))[0])1008ax5.hist(regime_durations, bins=20, alpha=0.7, color='steelblue',1009edgecolor='black')1010ax5.set_xlabel('Duration (periods)')1011ax5.set_ylabel('Frequency')1012ax5.set_title('Regime Duration Distribution')1013expected_bull_duration = 1 / (1 - P_transition[0,0])1014expected_bear_duration = 1 / (1 - P_transition[1,1])1015ax5.axvline(x=expected_bull_duration, color='green', linestyle='--',1016linewidth=2, label=f'E[Bull]={expected_bull_duration:.1f}')1017ax5.axvline(x=expected_bear_duration, color='red', linestyle='--',1018linewidth=2, label=f'E[Bear]={expected_bear_duration:.1f}')1019ax5.legend(fontsize=8)10201021ax6 = fig5.add_subplot(2, 3, 6)1022# Transition probability diagram (text-based visualization)1023ax6.text(0.2, 0.7, 'Bull Market', fontsize=14, ha='center',1024bbox=dict(boxstyle='round', facecolor='lightgreen', alpha=0.8))1025ax6.text(0.8, 0.7, 'Bear Market', fontsize=14, ha='center',1026bbox=dict(boxstyle='round', facecolor='lightcoral', alpha=0.8))10271028# Arrows showing transitions1029ax6.annotate('', xy=(0.35, 0.7), xytext=(0.25, 0.7),1030arrowprops=dict(arrowstyle='->', lw=3, color='green'))1031ax6.text(0.3, 0.75, f'{P_transition[0,0]:.2f}', fontsize=10, ha='center')10321033ax6.annotate('', xy=(0.65, 0.7), xytext=(0.75, 0.7),1034arrowprops=dict(arrowstyle='->', lw=3, color='red'))1035ax6.text(0.7, 0.75, f'{P_transition[1,1]:.2f}', fontsize=10, ha='center')10361037ax6.annotate('', xy=(0.72, 0.65), xytext=(0.28, 0.65),1038arrowprops=dict(arrowstyle='->', lw=2, color='orange',1039connectionstyle='arc3,rad=0.3'))1040ax6.text(0.5, 0.55, f'{P_transition[0,1]:.2f}', fontsize=9, ha='center')10411042ax6.annotate('', xy=(0.28, 0.75), xytext=(0.72, 0.75),1043arrowprops=dict(arrowstyle='->', lw=2, color='blue',1044connectionstyle='arc3,rad=0.3'))1045ax6.text(0.5, 0.85, f'{P_transition[1,0]:.2f}', fontsize=9, ha='center')10461047ax6.set_xlim(0, 1)1048ax6.set_ylim(0.4, 0.95)1049ax6.axis('off')1050ax6.set_title('Transition Probabilities')10511052plt.tight_layout()1053plt.savefig('time_series_finance_regime_switching.pdf', dpi=150, bbox_inches='tight')1054plt.close()10551056# Store regime results1057regime_results = {1058'mu_bull': mu_bull * 100,1059'mu_bear': mu_bear * 100,1060'sigma_bull': sigma_bull * 100,1061'sigma_bear': sigma_bear * 100,1062'p11': P_transition[0,0],1063'p22': P_transition[1,1],1064'pi_bull': pi_bull_ergodic,1065'pi_bear': pi_bear_ergodic,1066'expected_bull_duration': expected_bull_duration,1067'expected_bear_duration': expected_bear_duration,1068'classification_accuracy': accuracy * 1001069}1070\end{pycode}10711072\begin{figure}[htbp]1073\centering1074\includegraphics[width=\textwidth]{time_series_finance_regime_switching.pdf}1075\caption{Markov regime-switching model analysis: (a) Cumulative returns with bear market1076regimes shaded in red, showing periods of drawdown and recovery; (b) Daily returns colored1077by latent state, illustrating higher volatility and negative drift during bear regimes;1078(c) Return distribution conditional on regime, with bear market exhibiting wider dispersion1079and left shift; (d) Realized volatility tracking the state-dependent volatility parameters;1080(e) Distribution of regime durations matching the expected persistence from transition1081probabilities; (f) State transition diagram showing high persistence in both states with1082asymmetric transition probabilities favoring longer bull markets.}1083\label{fig:regime}1084\end{figure}10851086The regime-switching model parameterizes bull markets with $\mu_{bull} = \py{f"{regime_results['mu_bull']:.2f}"}$\%1087and $\sigma_{bull} = \py{f"{regime_results['sigma_bull']:.2f}"}$\%, while bear markets have1088$\mu_{bear} = \py{f"{regime_results['mu_bear']:.2f}"}$\% and1089$\sigma_{bear} = \py{f"{regime_results['sigma_bear']:.2f}"}$\%. The transition matrix implies1090bull markets persist with probability $p_{11} = \py{f"{regime_results['p11']:.2f}"}$ and bear1091markets with $p_{22} = \py{f"{regime_results['p22']:.2f}"}$, yielding expected durations of1092\py{f"{regime_results['expected_bull_duration']:.1f}"} and1093\py{f"{regime_results['expected_bear_duration']:.1f}"} periods respectively. The ergodic1094distribution assigns \py{f"{regime_results['pi_bull']:.1f}"}$\times$100\% probability to bull1095states. Simple volatility-based classification achieves1096\py{f"{regime_results['classification_accuracy']:.1f}"}\% accuracy.10971098\section{Results Summary}10991100\begin{pycode}1101print(r"\begin{table}[htbp]")1102print(r"\centering")1103print(r"\caption{Stylized Facts of Simulated Returns}")1104print(r"\begin{tabular}{lc}")1105print(r"\toprule")1106print(r"Statistic & Value \\")1107print(r"\midrule")1108print(f"Mean return (\\%/day) & {stylized_facts['mean']:.4f} \\\\")1109print(f"Volatility (\\%/day) & {stylized_facts['std']:.4f} \\\\")1110print(f"Skewness & {stylized_facts['skew']:.3f} \\\\")1111print(f"Excess kurtosis & {stylized_facts['kurt']:.3f} \\\\")1112print(f"Jarque-Bera statistic & {stylized_facts['jb_stat']:.1f} \\\\")1113print(f"JB $p$-value & {stylized_facts['jb_pvalue']:.4f} \\\\")1114print(f"Ljung-Box $Q$ (returns) & {stylized_facts['Q_returns']:.1f} \\\\")1115print(f"Ljung-Box $Q$ (squared) & {stylized_facts['Q_squared']:.1f} \\\\")1116print(r"\bottomrule")1117print(r"\end{tabular}")1118print(r"\label{tab:stylized}")1119print(r"\end{table}")11201121print(r"\begin{table}[htbp]")1122print(r"\centering")1123print(r"\caption{GARCH(1,1) Parameter Estimates}")1124print(r"\begin{tabular}{lccc}")1125print(r"\toprule")1126print(r"Parameter & True & Estimated & Error (\%) \\")1127print(r"\midrule")1128omega_err = abs(garch_results['omega_garch'] - garch_results['omega_true']) / garch_results['omega_true'] * 1001129alpha_err = abs(garch_results['alpha_garch'] - garch_results['alpha_true']) / garch_results['alpha_true'] * 1001130beta_err = abs(garch_results['beta_garch'] - garch_results['beta_true']) / garch_results['beta_true'] * 10011311132print(f"$\\omega$ & {garch_results['omega_true']:.6f} & {garch_results['omega_garch']:.6f} & {omega_err:.1f} \\\\")1133print(f"$\\alpha$ & {garch_results['alpha_true']:.3f} & {garch_results['alpha_garch']:.3f} & {alpha_err:.1f} \\\\")1134print(f"$\\beta$ & {garch_results['beta_true']:.3f} & {garch_results['beta_garch']:.3f} & {beta_err:.1f} \\\\")1135print(f"$\\alpha + \\beta$ & {garch_results['persistence_true']:.3f} & {garch_results['persistence_est']:.3f} & --- \\\\")1136print(f"$\\gamma$ (GJR) & --- & {garch_results['gamma_gjr']:.3f} & --- \\\\")1137print(r"\bottomrule")1138print(r"\end{tabular}")1139print(r"\label{tab:garch}")1140print(r"\end{table}")11411142print(r"\begin{table}[htbp]")1143print(r"\centering")1144print(r"\caption{Regime-Switching Model Parameters}")1145print(r"\begin{tabular}{lcc}")1146print(r"\toprule")1147print(r"Parameter & Bull Market & Bear Market \\")1148print(r"\midrule")1149print(f"Mean return (\\%/day) & {regime_results['mu_bull']:.3f} & {regime_results['mu_bear']:.3f} \\\\")1150print(f"Volatility (\\%/day) & {regime_results['sigma_bull']:.3f} & {regime_results['sigma_bear']:.3f} \\\\")1151print(f"Persistence prob. & {regime_results['p11']:.3f} & {regime_results['p22']:.3f} \\\\")1152print(f"Expected duration & {regime_results['expected_bull_duration']:.1f} & {regime_results['expected_bear_duration']:.1f} \\\\")1153print(f"Ergodic prob. & {regime_results['pi_bull']:.3f} & {regime_results['pi_bear']:.3f} \\\\")1154print(r"\bottomrule")1155print(r"\end{tabular}")1156print(r"\label{tab:regime}")1157print(r"\end{table}")1158\end{pycode}11591160\section{Discussion}11611162\subsection{Model Selection and Diagnostics}11631164The Box-Jenkins methodology for ARIMA identification relies on ACF and PACF patterns. Our1165ARMA(2,1) specification is supported by exponential ACF decay and PACF cutoff after lag 2.1166Maximum likelihood estimation provides asymptotically efficient parameter estimates under1167correct specification. Model adequacy should be verified through Ljung-Box tests on residuals1168and information criteria (AIC, BIC) for comparison with alternative specifications.11691170GARCH models address the conditional heteroskedasticity evident in the Ljung-Box test for1171squared returns. The GARCH(1,1) specification is parsimonious yet captures essential features:1172volatility clustering, mean reversion in variance, and time-varying risk. The persistence1173parameter near unity indicates shocks decay slowly, consistent with the long memory property.1174Extensions including GJR-GARCH, EGARCH, and multivariate GARCH models accommodate asymmetries1175and correlations.11761177\subsection{Practical Applications}11781179Cointegration provides the foundation for statistical arbitrage and pairs trading. When two1180assets are cointegrated, their spread is mean-reverting, creating profitable opportunities when1181the spread deviates from equilibrium. The half-life estimates guide position sizing and holding1182periods. Error correction models further refine short-run dynamics around the long-run1183equilibrium relationship.11841185Regime-switching models capture structural breaks and state-dependent dynamics prevalent in1186financial markets. Applications include tactical asset allocation (overweight equities in bull1187regimes), risk management (increase hedges in bear regimes), and option pricing (regime-dependent1188volatility). The filtered probabilities provide real-time regime inference useful for dynamic1189strategies.11901191\section{Conclusions}11921193This comprehensive analysis demonstrates modern econometric techniques for financial time1194series:11951196\begin{enumerate}1197\item Stylized facts including excess kurtosis (\py{f"{stylized_facts['kurt']:.2f}"}),1198volatility clustering (Ljung-Box $Q = \py{f"{Q_squared:.1f}"}$ for squared returns),1199and negligible return autocorrelation characterize equity data1200\item ARIMA models with ACF/PACF identification provide forecasts for mean dynamics,1201with estimated parameters $\hat{\phi}_1 = \py{f"{arima_params['phi1_est']:.3f}"}$,1202$\hat{\phi}_2 = \py{f"{arima_params['phi2_est']:.3f}"}$, and1203$\hat{\theta}_1 = \py{f"{arima_params['theta1_est']:.3f}"}$1204\item GARCH(1,1) captures conditional heteroskedasticity with persistence1205$\hat{\alpha} + \hat{\beta} = \py{f"{garch_results['persistence_est']:.3f}"}$,1206while GJR-GARCH leverage parameter $\hat{\gamma} = \py{f"{gamma_gjr:.3f}"}$1207confirms asymmetric volatility response1208\item Cointegration between asset pairs with $\hat{\beta} = \py{f"{beta_hat:.2f}"}$ and1209mean-reversion half-life of \py{f"{half_life:.1f}"} periods enables pairs trading1210\item Markov regime-switching distinguishes bull markets ($\mu = \py{f"{regime_results['mu_bull']:.3f}"}$\%,1211$\sigma = \py{f"{regime_results['sigma_bull']:.2f}"}$\%) from bear markets1212($\mu = \py{f"{regime_results['mu_bear']:.3f}"}$\%,1213$\sigma = \py{f"{regime_results['sigma_bear']:.2f}"}$\%)1214\end{enumerate}12151216These methods form the quantitative toolkit for risk management, derivative pricing, and1217portfolio optimization in modern financial markets.12181219\section*{References}12201221\begin{itemize}1222\item Bollerslev, T. (1986). Generalized autoregressive conditional heteroskedasticity.1223\textit{Journal of Econometrics}, 31(3), 307-327.1224\item Box, G. E. P., Jenkins, G. M., Reinsel, G. C., \& Ljung, G. M. (2015).1225\textit{Time Series Analysis: Forecasting and Control} (5th ed.). Wiley.1226\item Engle, R. F. (1982). Autoregressive conditional heteroscedasticity with estimates1227of the variance of United Kingdom inflation. \textit{Econometrica}, 50(4), 987-1007.1228\item Engle, R. F., \& Granger, C. W. J. (1987). Co-integration and error correction:1229Representation, estimation, and testing. \textit{Econometrica}, 55(2), 251-276.1230\item Glosten, L. R., Jagannathan, R., \& Runkle, D. E. (1993). On the relation between1231the expected value and the volatility of the nominal excess return on stocks.1232\textit{Journal of Finance}, 48(5), 1779-1801.1233\item Hamilton, J. D. (1989). A new approach to the economic analysis of nonstationary1234time series and the business cycle. \textit{Econometrica}, 57(2), 357-384.1235\item Hamilton, J. D. (1994). \textit{Time Series Analysis}. Princeton University Press.1236\item Johansen, S. (1991). Estimation and hypothesis testing of cointegration vectors in1237Gaussian vector autoregressive models. \textit{Econometrica}, 59(6), 1551-1580.1238\item Nelson, D. B. (1991). Conditional heteroskedasticity in asset returns: A new approach.1239\textit{Econometrica}, 59(2), 347-370.1240\item Tsay, R. S. (2010). \textit{Analysis of Financial Time Series} (3rd ed.). Wiley.1241\end{itemize}12421243\end{document}124412451246