Chapter 6 · Time Series Models for Trading and Risk
Section 6.6 · Chapter 6 · Learning Statistics with Python
Time Series Models for Trading and Risk
Prof. Xuhu Wan
ISOM, HKUST Business School · 2026 Edition
One extra parameter lets the model react differently to losses and gains. You will test whether it is needed, stack GARCH on an ARMA mean, and then do what every systematic fund does with \(\hat\sigma_t\): not forecast direction, but scale the position — and see what that does to a drawdown.
Symmetric GARCH cannot tell them apart: \(\varepsilon_{t-1}^2\) is the same. Glosten, Jagannathan and Runkle (1993) add an indicator:
\[\sigma_t^2 = \omega + \left(\alpha + \gamma\,\mathbf 1[\varepsilon_{t-1}<0]\right)\varepsilon_{t-1}^2 + \beta\sigma_{t-1}^2 .\]
For a stock index, the sign of \(\gamma\) is expected to be:
Same likelihood, one more parameter. Setting the bound on \(\gamma\) to \((0,0)\) gives plain GARCH(1,1) — so one function fits both and the likelihood-ratio test is free.
\(LR = 2(\ell_{GJR} - \ell_{GARCH})\), \(\chi^2_1\) under \(H_0: \gamma=0\) (5 % critical value 3.84). Log-likelihoods −1815.05 and −1836.45. What is \(LR\)?
42.8
\(\hat\gamma = 0.254\), \(LR = 42.8\), \(p = 6\times10^{-11}\). A −2 % day feeds 0.335 of its square into tomorrow’s variance; a +2 % day only 0.081 — a 4.2× asymmetry. Persistence is unchanged at 0.974. The S&P 500 gives the same story (\(\hat\gamma = 0.272\), \(LR = 38.9\)).
Plot tomorrow’s variance as a function of today’s shock, holding \(\sigma_{t-1}^2\) at its long-run level.
After −3 %: variance 4.11; after +3 %: 1.82. The symmetric model splits the difference and is wrong on both sides — too calm after crashes, too nervous after rallies.
Everything so far assumed \(r_t = \mu + \varepsilon_t\). If the mean has ARMA structure, fit it first and hand the residuals to GARCH.
Two-step ARMA+GARCH means:
\(\hat\phi = -0.195\) (se 0.008) — but look at the standardised residuals: ACF of \(z\) at lag 1 is +0.151. The AR(1) was pulled by March 2020’s giant reversals; once each day is weighted by \(1/\sigma_t\) the mean dynamics look different. ACF of \(z^2\) (0.03, −0.01, 0.02) is clean — the variance is done. That mismatch is why joint estimation (armagarch, weighted by \(\sigma_t\)) is preferred when the mean matters.
Apple 2020–2024, 1 258 days. Fit GARCH(1,1), sort days into quartiles of the forecast \(\hat\sigma_t\) (known at the close of \(t-1\)), and compare next-day returns.
Compared with the calmest quartile, the most volatile quartile will show:
Calm quartile: mean 0.104 %/day, sd 1.35; stormy: mean 0.049, sd 2.91. Return per unit of daily risk falls from 0.077 to 0.017. Apple’s \(\alpha=0.086\), \(\beta=0.885\) — more persistent than the Dow.
Position \(=1\) when \(\hat\sigma_t <\) thr, else cash. The starter has thr = 100 — always invested, i.e. buy-and-hold. Set a real threshold (try 2.0; the median \(\hat\sigma\) is 1.70).
Change thr so the rule is in the market on fewer than 99 % of days, then read off cumulative return, annualised Sharpe and maximum drawdown versus buy-and-hold.
At thr = 2.0: in the market 73 % of days; cumulative 95 % vs 123 % for buy-and-hold, Sharpe 0.89 vs 0.78, max drawdown −25.5 % vs −37.7 %. Less money, better risk-adjusted money. An on/off switch is crude, though — the industry version is continuous.
Instead of in-or-out, hold \(w_t = \min\!\left(\dfrac{\sigma^\ast}{\hat\sigma_t},\, w_{\max}\right)\) of the index: more when it is calm, less when it is stormy, so that realised volatility stays near \(\sigma^\ast\). This is how CTAs, risk-parity funds and most systematic books scale every position.
Target \(\sigma^\ast = 1\) % a day, cap \(w_{\max} = 1.5\). Buy-and-hold over these five years realised 1.35 % a day with a −41 % drawdown.
Compared with buy-and-hold, the vol-targeted book should show…
Realised sd 0.994 % against the 1 % target (buy-and-hold 1.350). Vol-target: cumulative 60.7 %, Sharpe 0.77, max drawdown -22.6 %; buy-and-hold 59.9 %, 0.56, -41.4 %. Same money, half the drawdown. The weight ran from 0.13 in March 2020 to the 1.5 cap in quiet months. Nothing here forecasts direction — the whole edge is the variance forecast from §6.5.
Warning
From September 2011 the Swiss National Bank held EUR/CHF above 1.20, and for three years the exchange rate behaved like a textbook stationary series pinned to its floor: ADF rejected, KPSS did not, the spread to 1.20 mean-reverted within days. Traders and retail brokers treated the floor as a cointegrating rope between the franc and the euro, ran carry and short-volatility positions against it, and sized them on a variance estimated from those three quiet years.
On 15 January 2015 the SNB abandoned the floor without warning. EUR/CHF fell from 1.20 to 0.85 within minutes — a move of roughly 30 %, about 60 standard deviations of the pre-announcement daily change. FXCM, then the largest US retail FX broker, faced US$225 million of client losses in excess of collateral and needed a US$300 million rescue loan the next day; Alpari UK entered insolvency; Everest Capital’s US$830 million Global fund was wiped out.
Lesson for this chapter: a stationarity test tells you about the sample you fed it. A regime held in place by a policy is stationary until the policy changes, and no ADF statistic, GARCH \(\hat\sigma_t\) or smoothed regime probability can see the meeting at which it does. Test the spread, then ask who is holding the rope.
To: Head of Systematic Trading From: <Your name>, quant research Subject: Where to deploy the time-series budget: volatility-targeting overlay vs. a pairs book Date: 2026-03-02
Recommendation: Deploy the volatility-targeting overlay on the index book now; run the pairs book as a capped pilot (≤ 5 % of risk) until it survives two more formation windows.
Evidence: - Overlay, S&P 500 2020–24, target 1 %/day, cap 1.5×: Sharpe 0.77 vs 0.56, max drawdown −22.6 % vs −41.4 %, cumulative return unchanged (60.7 % vs 59.9 %). Regime filter alternative: Sharpe 0.81, drawdown −11.3 %, but gives up a quarter of the return. - Pairs, GOOG/S&P 2016–20, walk-forward hedge ratio, entry 2 / exit 0.5: Sharpe 0.75, drawdown −11.6 %, in a trade 35 % of days, 52 crossings before costs. Full-sample
cointp = 0.014 — but p = 0.24 and 0.11 on the formation windows a trader would actually have had.Caveats: the overlay depends only on variance persistence (\(\alpha+\beta \approx 0.97\)), which has held in every equity market since 1987; the pairs book depends on a rope whose in-sample evidence is stronger than its out-of-sample evidence, and its Sharpe doubles or halves with one threshold. Neither is net of costs or slippage.
Next step: overlay — add GJR (bad-news \(\gamma\) = 0.25) and refit weekly. Pairs — extend to 20 candidate pairs, require formation-window p < 0.05 and a half-life under 30 days, and stop trading any pair whose rolling ADF p exceeds 0.10.
arch_model(r) on decimal returns — the stalled fit in the notebook (alpha=0.1000, beta=0.8800). “Scale returns to percent first, and tell me the optimiser’s convergence flag.”coint p = 0.01 and then back-tests a z-score rule on the same window has used the future twice. Ask it to state the formation window, the trading window, and the hedge ratio it used on each day — and to refuse a Sharpe on any spread whose formation-window test did not reject.Pitfall: when a copilot reports a regime model or a Kalman beta, ask whether it used the smoothed (two-sided) or filtered (one-sided) estimate in the back-test. The smoothed one knows the future.
| Role | Concept | Tool |
|---|---|---|
| Foundation | Stationarity, unit roots, decision table | kpss (null: stationary), adfuller (null: unit root), "c" / "ct" |
| Foundation | Memory diagnostics, ARIMA baseline | plot_acf, plot_pacf, ±\(2/\sqrt n\); ARIMA(y, order).fit().aic, .get_forecast, .apply |
| ML base | Forecast scoring | MAE / RMSE / DA on the same rows; the zero forecast |
| ML base | Features without leakage, walk-forward | .shift(1), .rolling(w), expanding yearly refit of HistGradientBoostingRegressor |
| Structural arb | Cointegration and pairs | coint(a, b); formation vs trading window; rolling hedge ratio; z-score entry/exit |
| Structural arb | Hidden states | UnobservedComponents(level="local level"); 8-line time-varying beta; MarkovRegression(k_regimes=2, switching_variance=True) |
| Volatility | ARCH/GARCH, forecasts, VaR | het_arch; numpy recursion + optimize.minimize; \(\hat\sigma^2_{T+h}\) decay path; stats.norm.ppf / stats.t.ppf; Kupiec |
| Volatility | Leverage, vol targeting | GJR indicator + LR test; ARIMA(...).resid → GARCH; \(w_t = \min(\sigma^\ast/\hat\sigma_t, w_{\max})\) |
The message, once more: the classical models lost the mean-forecasting job to ML — and kept the variance, the spread, the regime, and the pipeline.
The walk-forward GBM on the S&P 500 scored DA 0.494 and a Sharpe below buy-and-hold, yet Chapter 4.5’s cross-sectional model earned a positive spread. What, precisely, is different about the cross-section that makes the same algorithm work? Name two things a single series cannot provide.
GOOG/S&P 500 passed the Engle–Granger test on the full sample (p = 0.014) but not on either formation window (p = 0.24, 0.11). A colleague proposes trading it anyway because “the rope was there all along”. Write the two-sentence reply, with numbers, and say what evidence would change your mind.
The regime filter and the vol-targeting overlay both cut the S&P 500 drawdown by more than half, using only a variance model. Under what return-generating process would either of them raise the total return, not just the Sharpe ratio — and does the quartile table on Apple support that process?
The Kalman beta tracked NVDA’s 2024 swing (2.7 → 3.2 → 1.7) a year before annual OLS could, yet the hedge error was the same to two decimals. When does timeliness in a hedge ratio pay, and when is it just noise? Propose one experiment on the NVDA/SPY data that would tell the two apart.
Prof. Xuhu Wan · HKUST ISOM · Learning Statistics with Python