Note
Go to the end to download the full example code or to run this example in your browser via JupyterLite.
Lo-MacKinlay variance ratio#
If log prices follow a random walk, the variance of \(q\)-week returns is \(q\) times that of one-week returns. Andrew Lo and Craig MacKinlay (1988) turned this into a test,
with a heteroskedasticity-consistent standard error so that volatility clustering alone cannot cause a rejection. Weekly returns on U.S. stock indices from 1962 to 1985 gave \(VR(2) \approx 1.3\), strongly rejecting the random walk, mostly through the positive autocorrelation of small-stock portfolios.
import matplotlib.pyplot as plt
import numpy as np
from scipy.signal import lfilter
from financialkit.time_series import GJRGARCH, variance_ratio
from financialkit.time_series.visualizers import plot_variance_ratio
Robustness to volatility clustering#
Under GJR-GARCH increments the random walk holds, but the homoskedastic \(z(2)\) is too dispersed; the robust \(z^*(2)\) stays close to standard normal.
m = GJRGARCH(omega=0.02, alpha=0.05, gamma=0.15, beta=0.85)
z, zr = [], []
for _ in range(300):
res = variance_ratio(np.cumsum(m.simulate(1000, burn=200, rng=rng).returns), q=2)
z.append(res.z[0])
zr.append(res.z_robust[0])
fig, ax = plt.subplots()
ax.hist([z, zr], bins=25, density=True, histtype="step", lw=1.5, label=["z (homoskedastic)", "z* (robust)"])
g = np.linspace(-5, 5, 200)
ax.plot(g, np.exp(-(g**2) / 2) / np.sqrt(2 * np.pi), "k", label="N(0, 1)")
ax.set_xlabel("statistic under the null")
ax.legend()
print(f"rejection rate at 5%: z {np.mean(np.abs(z) > 1.96):.2f}, z* {np.mean(np.abs(zr) > 1.96):.2f}")
plt.show()

rejection rate at 5%: z 0.20, z* 0.04
Total running time of the script: (0 minutes 0.703 seconds)
