Interactive exploration of Dynamic Conditional Correlation (DCC) models for time-varying correlations between asset returns
The Dynamic Conditional Correlation (DCC) model (Engle 2002; Christoffersen 2012, chap. 7) decomposes the covariance matrix into separate volatility and correlation components:
where \(D_{t+1}\) is the diagonal matrix of GARCH volatilities and \(\Upsilon_{t+1}\) is the correlation matrix with time-varying entries. This decomposition allows separate modeling of volatilities (asset-specific GARCH parameters) and correlations (common DCC persistence parameters).
The correlation dynamics are driven by standardized returns \(z_{i,t} = R_{i,t}/\sigma_{i,t}\), whose conditional covariance equals the conditional correlation:
The correlation is obtained by normalization: \(\rho_{ij,t+1} = q_{ij,t+1} / \sqrt{q_{ii,t+1}\, q_{jj,t+1}}\), ensuring \(-1 \leq \rho_{ij,t+1} \leq 1\).
Note
Common persistence parameters. The parameters \(\lambda\) (or \(\alpha, \beta\)) must be identical across all asset pairs. Allowing pair-specific persistence parameters could cause the covariance matrix to lose positive semidefiniteness, producing nonsensical negative portfolio variances. The level of correlation, governed by \(\bar{\rho}_{ij}\), does vary across pairs.
Note
Parameter constraints. For the mean-reverting DCC, the parameters must satisfy \(\alpha \geq 0\), \(\beta \geq 0\), and \(\alpha + \beta < 1\). The last condition ensures stationarity: the correlation mean-reverts to its long-run level \(\bar{\rho}_{ij}\). When \(\alpha + \beta = 1\), the model reduces to the exponential smoother (no mean-reversion). If \(\alpha + \beta \geq 1\), the process becomes non-stationary or explosive. For the exponential smoother, the constraint is simply \(0 < \lambda < 1\).
rng_utils = {functionmulberry32(seed) {returnfunction() { seed |=0; seed = seed +0x6D2B79F5|0let t =Math.imul(seed ^ seed >>>15,1| seed) t = t +Math.imul(t ^ t >>>7,61| t) ^ treturn ((t ^ t >>>14) >>>0) /4294967296 } }functionboxMuller(rng) {const u1 =rng(), u2 =rng()returnMath.sqrt(-2*Math.log(u1)) *Math.cos(2*Math.PI* u2) }functiongammaSample(rng, shape) {if (shape <1) returngammaSample(rng, shape +1) *Math.pow(rng(),1/ shape)const d = shape -1/3, c =1/Math.sqrt(9* d)while (true) {let x, vdo { x =boxMuller(rng); v =1+ c * x } while (v <=0) v = v * v * vconst u =rng()if (u <1-0.0331* (x * x) * (x * x)) return d * vif (Math.log(u) <0.5* x * x + d * (1- v +Math.log(v))) return d * v } }functionstdTSample(rng, d) {const normal =boxMuller(rng)const chi2 =2*gammaSample(rng, d /2)return normal /Math.sqrt(chi2 / d) *Math.sqrt((d -2) / d) }return { mulberry32, boxMuller, stdTSample }}
fmt = (x, d) => x ===undefined||isNaN(x) ?"N/A": x.toFixed(d)
This simulation generates two correlated GARCH(1,1) return series and runs the DCC recursion to compute the dynamic conditional correlation. Observe how the correlation fluctuates around its long-run level and responds to joint large moves.
Tip
How to experiment
Start with the mean-reverting DCC and default parameters. Notice how the correlation fluctuates around \(\bar{\rho}\). Then increase \(\alpha\) to see faster response to shocks, or increase \(\beta\) for more persistence. Switch to the exponential smoother and observe that without mean-reversion, the correlation can drift further from its long-run level. Check the “Stress periods” tab to see how joint negative shocks drive correlations upward.
{if (dccType ==="Mean-reverting DCC"&& dccAlpha + dccBeta >=1) {returnhtml`<div style="background:#fff3cd;border:1px solid #ffc107;border-radius:6px;padding:8px 12px;margin-bottom:8px;font-size:0.85rem;color:#856404;"><strong>Warning:</strong> α + β = ${fmt(dccAlpha + dccBeta,2)} ≥ 1. The mean-reverting DCC requires α + β < 1 for stationarity. The correlation process will not mean-revert to ρ̄ and may become explosive.</div>` }returnhtml`<span></span>`}
html`<p style="color:#666;font-size:0.85rem;">The <span style="color:#2f71d5;font-weight:700;">DCC correlation</span> responds to market shocks through the cross-product z₁z₂. The <span style="color:#aaa;">rolling 60-day correlation</span> lags behind. The <span style="color:#d62728;font-weight:700;">dashed line</span> shows the unconditional correlation ρ̄ toward which the mean-reverting DCC gravitates.</p>`
{const stressData = dccPlotData.filter(d => d.bothNeg)const nStress = stressData.lengthconst marks = [ Plot.ruleY([dccRhoBar], { stroke:"#d62728",strokeWidth:1,strokeDasharray:"6 3" }), Plot.line(dccPlotData, { x:"t",y:"rho",stroke:"#2f71d5",strokeWidth:1.5 }) ]if (nStress >0) { marks.push(Plot.dot(stressData, { x:"t",y:"rho",r:4,fill:"#d62728",fillOpacity:0.7 })) }const plot = Plot.plot({height:400,marginLeft:60,x: { label:"Day",grid:false },y: { label:"Correlation ρ₁₂,t",grid:true,domain: [-1,1] }, marks })const cpPlot = Plot.plot({height:200,marginLeft:60,marginTop:5,x: { label:"Day",grid:false },y: { label:"Cross-product z₁z₂",grid:true },marks: [ Plot.ruleY([0], { stroke:"#ddd" }), Plot.line(dccPlotData, { x:"t",y:"crossProd",stroke:"#999",strokeWidth:0.5 }),...(nStress >0? [Plot.dot(stressData, { x:"t",y:"crossProd",r:3,fill:"#d62728",fillOpacity:0.7 })] : []) ] })returnhtml`<div>${plot}${cpPlot} <p style="color:#666;font-size:0.85rem;"><strong>Top:</strong> DCC correlation with <span style="color:#d62728;font-weight:700;">red dots</span> marking days when both assets had large <em>negative</em> standardized returns (z₁ < −1.5 and z₂ < −1.5). ${nStress} stress days detected in this sample. <strong>Bottom:</strong> Cross-product z₁z₂ that drives the DCC update. Note that large positive cross-products also arise from joint <em>positive</em> returns (both assets rallying), not only from joint crashes; only the latter are highlighted as stress events. Also note that the highest cross-product spikes do not necessarily coincide with the highest correlations above: because the DCC update is an exponentially weighted average (with weight β ≈ ${dccType ==="Mean-reverting DCC"?fmt(dccBeta,2) :fmt(dccLambda,2)} on the previous q-value), a single large shock only moves the correlation modestly. The highest correlations tend to follow <em>sustained</em> periods of positive cross-products rather than isolated spikes.</p> </div>`}
{const el =html`<p style="color:#666;font-size:0.85rem;">The three q-values that drive the DCC model. <span style="color:#e67e22;font-weight:700;">q₁₁</span> and <span style="color:#2e7d32;font-weight:700;">q₂₂</span> track the "unnormalized variance" of each asset's standardized returns (they hover around 1). <span style="color:#2f71d5;font-weight:700;">q₁₂</span> tracks the unnormalized covariance (hovering around ρ̄). The DCC correlation is obtained by normalizing: \\(\\rho_{12} = q_{12} / \\sqrt{q_{11} \\times q_{22}}\\).</p>`if (window.MathJax&& MathJax.typesetPromise) MathJax.typesetPromise([el])return el}
html`<p style="color:#666;font-size:0.85rem;">Scatter of standardized returns colored by the DCC correlation at each point. The dashed red box highlights the "stress quadrant" where both assets have large negative returns. Points in this region tend to be colored red (high correlation), illustrating the crisis-correlation phenomenon.</p>`
References
Christoffersen, Peter F. 2012. Elements of Financial Risk Management. 2nd ed. Academic Press.
Engle, Robert. 2002. “Dynamic Conditional Correlation: A Simple Class of Multivariate Generalized Autoregressive Conditional Heteroskedasticity Models.”Journal of Business & Economic Statistics 20 (3): 339–50.