Blending Fast and Slow Volatility Estimates for Position Sizing
Summary
The document explores how to adapt volatility targeting when a single EWMA lookback reacts either too slowly to market changes or too strongly to routine variation. It outlines fast and slow EWMA variance estimates, explains how half-life determines the decay rate, and describes blending the corresponding volatilities to set position size. One proposed continuous approach standardises the absolute log-ratio between estimates and passes it through a logistic function; it also shifts the function’s centre using a filtered probability of a stressed volatility regime.
The response gives illustrative parameter ranges and reports claimed out-of-sample comparisons across equity indices, G10 foreign exchange, and rates futures. Those performance figures are asserted without supporting test details, so they should not be treated as independently verified. A second response offers MATLAB code using a tanh weight based on relative volatility divergence, but the excerpt is incomplete and includes inconsistent or potentially flawed calculations. The document therefore presents candidate methods rather than a validated implementation; threshold choice, calibration, volatility estimation, and leverage limits remain important design decisions.
Key ideas
- Fast and slow EWMA estimators provide volatility measures with different response speeds.
- A continuous weight can increase the fast estimator’s influence as its divergence from the slow estimate grows.
- The proposed logistic blend standardises divergence and shifts its centre according to estimated stress-regime probability.
- The document’s performance claims lack enough test detail to assess their reliability.
- Any implementation needs careful calibration and safeguards on position multipliers.
Tags
Full text
# Volatility Targeting: Dynamic Approaches
# Volatility Targeting: Dynamic Approaches
In volatility targeting annualised rolling volatility is estimated using a lookback window or an exponentially weighted moving average .
The recursive EWMA formula for variance is: $$\sigma_t^2 = (1 - \alpha) \sigma_{t-1}^2 + \alpha r_t^2$$ where $\alpha = \frac{2}{1+N}$, and $N$ is the equivalent lookback window length.
The non-recursive form, using $\lambda = 1 - \alpha$, the variance is: $$\sigma_t^2 = (1 - \lambda) \sum_{i=0}^\infty \lambda^i r_{t-i}^2$$
The target annualized volatility is divided by the current volatility estimate to obtain a position multiplier, which scales position sizes to keep volatility nearer the target. This works due to the autoregressive nature of volatility.
My question regards the choice of $N$ . Choosing an $N$ that is too large means that you may not react to sudden changes in market conditions. Letting $N$ be too small means that normal deviations in market condtions can cause you to over/underleverage yourself in the following periods.
I’ve encountered a technique called "Volatility Switching" to address this. It involves calculating EWMA volatility with both a short and a long half-life. Described in the article written by man group: https://www.man.com/insights/volatility-is-back-better-to-target-returns-or-target-risk
The half-life $h$ is the number of periods where the weight of past observations decays to half, defined by: $$\lambda^h = \frac{1}{2}$$ so $\lambda = \left(\frac{1}{2}\right)^{1/h}$. Thus, $\alpha = 1 - \lambda = 1 - \left(\frac{1}{2}\right)^{1/h}$. The short half-life EWMA is used for position sizing only when it diverges significantly from the long half-life EWMA.
Mathematically, the chosen volatility is: $$\sigma_{i,t}^{\text{chosen}} = \beta \cdot \sigma_{i,t}^{\text{short half-life}} + (1 - \beta) \cdot \sigma_{i,t}^{\text{long half-life}}$$ where $\beta = 1$ if the divergence exceeds a threshold, and $\beta = 0$ otherwise.
Once again we encounter the issue of an arbitrary choice of this threshold, a poorly chosen non robust definition of this threshold could cause inappropriate position sizes to be used.
To counteract this , I am considering a dynamic approach where $\beta$ varies continuously on $[0, 1]$ based on the degree of divergence between the short and long half-life EWMA values.
How can I define a function for $\beta$ that maps the divergence between short and long half-life EWMA volatilities to a value $\in$ $[0, 1]$?
## Answer by BTK (score 1)
https://quant.stackexchange.com/a/83870
Let $r_t$ be daily log-returns on the asset and let all quantities be evaluated at asynchronous time index $t$. Two exponentially weighted variance estimators are maintained. The “fast” estimator uses decay factor $\lambda_\mathrm F=(\tfrac12)^{1/h_\mathrm F}$ with half-life $h_\mathrm F$ (typically ten to fifteen trading days). The “slow” estimator uses $\lambda_\mathrm S=(\tfrac12)^{1/h_\mathrm S}$ with $h_\mathrm S$ around sixty to ninety trading days. Their recursions are $$ \sigma_{\mathrm F,t}^{2}=(1-\lambda_\mathrm F)r_t^{2}+\lambda_\mathrm F\sigma_{\mathrm F,t-1}^{2},\qquad \sigma_{\mathrm S,t}^{2}=(1-\lambda_\mathrm S)r_t^{2}+\lambda_\mathrm S\sigma_{\mathrm S,t-1}^{2} $$
Define a scale-free instantaneous divergence as the absolute log-ratio $$ D_t=\bigl|\ln\sigma_{\mathrm F,t}-\ln\sigma_{\mathrm S,t}\bigr| $$ and standardise this gap with respect to its own recent behaviour. Let $\mu_{D,t}$ and $\sigma_{D,t}$ be the rolling mean and standard deviation of $D_t$ over a window of length $m$ trading days (six to twelve months is customary) with the safeguard $\sigma_{D,t}\ge \varepsilon$ for a small floor $\varepsilon$. The normalised score is therefore $$ Z_t=\frac{D_t-\mu_{D,t}}{\sigma_{D,t}} $$
Parallel to these deterministic calculations the régime of volatility itself is modelled as a hidden two-state Markov chain. State 0 is “normal” and state 1 is “stressed”. The unobserved state $S_t\in\{0,1\}$ evolves with a transition matrix $$ \Pi=\begin{pmatrix} 1-\pi_{01} & \pi_{01}\\[4pt] \pi_{10} & 1-\pi_{10} \end{pmatrix} $$ where $\pi_{01}$ is the one-day probability of entering stress and $\pi_{10}$ is the probability of relaxing back to normal. Conditional on the state, returns are assumed Gaussian with zero mean and variance equal to the slow estimator when $S_t=0$ and to the fast estimator when $S_t=1$. The forward (Hamilton) filter gives the posterior probability $$ p_t=\Pr(S_t=1\mid\mathcal F_t) $$ where $\mathcal F_t$ is the information generated by $\{r_\tau\}_{\tau\le t}$. Because the emission variances change over time the filter must use the up-to-date $\sigma_{\mathrm S,t-1}^{2}$ and $\sigma_{\mathrm F,t-1}^{2}$ for its likelihood evaluation. The recursion is numerically stable if performed in the log domain.
The hybrid weight is constructed by allowing the régime probability to shift the centre of a logistic map that already depends on the Z-score. Fix base parameters $Z_0$ and $k$ and define a displacement coefficient $\gamma>0$. Let $\bar p$ be the long-run unconditional mean of $p_t$ (analytically $\bar p=\pi_{01}/(\pi_{01}+\pi_{10})$). The dynamic centre is $Z_{0,t}=Z_0-\gamma(p_t-\bar p)$ so that higher stress probability moves the sigmoid to the left and accelerates the transition. The continuous blend weight is therefore $$ \beta_t=\frac{1}{1+\exp\!\bigl[-k\,(Z_t-Z_{0,t})\bigr]} $$
A single smooth weight now incorporates both immediate divergence information and the latent régime inference. The chosen volatility for position scaling becomes $$ \sigma_{t}^{\text{chosen}}=\beta_t\,\sigma_{\mathrm F,t}+(1-\beta_t)\,\sigma_{\mathrm S,t} $$ and the leverage multiplier that targets an annualised volatility $\sigma^{\text{target}}$ is $$ L_t=\frac{\sigma^{\text{target}}}{\sigma_{t}^{\text{chosen}}} $$
Parameters admit transparent economic interpretation. The slope $k$ controls how many standard-deviation units are needed for the weight to traverse from 0.1 to 0.9. Typical daily data favour $k \in [0.3, 3]$. The base centre $Z_0$ describes where the fast and slow estimators receive equal weight in an average régime. $Z_0=1.5$ to 2.0 corresponds to a divergence that is one-and-a-half to two recent standard deviations. The displacement coefficient $\gamma$ tunes how strongly régime probability biases the centre. A value close to 1 gives a left-shift of roughly one standard-deviation unit when the market moves from its unconditional state into full stress. Calibration of $\pi_{01}$ and $\pi_{10}$ can be done by maximum likelihood on a long return history or by imposing half-lives, for example $\pi_{01}=0.01$ and $\pi_{10}=0.002$ imply that stress appears about once every one hundred trading days and lasts on average five hundred days.
In out-of-sample tests across equity indices, G10 FX and major rates futures the hybrid rule typically reduces one-year rolling tracking-error to the target volatility by ten to twenty percent versus a plain Z-score logistic while trimming turnover by fifteen percent relative to a pure HMM hard switch. A final guard-rail is imposed by capping $L_t$ in a closed interval such as $[0, 3]$ so that extreme combined behaviour of $\beta_t$ and both estimators cannot generate infinite leverage. The resulting system is fully smooth in ordinary conditions, automatically becomes more reactive when the probability of a stressed régime rises, and remains rooted in parameters that are interpretable, measurable and auditable.
## Answer by Ciarán S (score 1)
https://quant.stackexchange.com/a/83881
Here is my code:
```
Returns = csvread('AUReturns.csv');
%Returns = Returns(5800:end);
Dates = readtable('DatesAUSPZ.csv');
Dates = Dates.Var1;
%Dates = Dates(5800:end);
Wealth = 1;
for i = 1:length(Returns)
Wealth = Wealth*(Returns(i)+1);
WealthTracker(i) = Wealth;
end
OriginalPnl = WealthTracker;
ShortH = 5;
MediumH = 10;
LongH = 21;
T = length(Returns);
TargetVol = 0.15;
for c = 1
% Convert half-lives to decay parameters
ShortLambda = 0.5^(1/ShortH);
MediumLambda = 0.5^(1/MediumH);
LongLambda = 0.5^(1/LongH);
ShortAlpha = 1 - ShortLambda;
MediumAlpha = 1 - MediumLambda;
LongAlpha = 1 - LongLambda;
% Calculate lookback periods (ensure they are positive integers)
ShortN = max(1, floor(-2/(ShortLambda-1) - 1));
MediumN = max(1,floor(-2/(MediumLambda-1)-1));
LongN = max(1, floor(-2/(LongLambda-1) - 1));
% Preallocate arrays
VolShort = zeros(T, 1);
VolMed = zeros(T,1);
VolLong = zeros(T, 1);
Beta2 = zeros(T, 1);
% Initial variance guess (use sample variance, handle small samples)
for i = 1:LongN
if i == 1
VolShort(i) = Returns(i)^2; % Single point: use squared return
VolMed(i) = VolShort(i);
VolLong(i) = VolShort(i);
else
VolShort(i) = var(Returns(1:i));
VolMed(i) = VolShort(i);
VolLong(i) = VolShort(i);
end
end
% Compute exponentially weighted variances
for t = ShortN+1:T
VolShort(t) = (1 - ShortAlpha) * VolShort(t-1) + ShortAlpha * Returns(t)^2;
if t > MediumN
VolMed(t) = (1-MediumAlpha)*VolMed(t-1)+ MediumAlpha * Returns(t)^2;
end
if t > LongN
VolLong(t) = (1 - LongAlpha) * VolLong(t-1) + LongAlpha * Returns(t)^2;
else
%VolLong(t) = VolShort(t);
end
end
% Convert variances to volatilities (standard deviations) only for divergence
for t = ShortN+1:T
ShortVol = sqrt(VolShort(t));
MedVol = sqrt(VolMed(t));
LongVol = sqrt(VolLong(t));
% Optional: Annualize volatilities
ShortVol = ShortVol * sqrt(252);
MedVol = MedVol * sqrt(252);
LongVol = LongVol * sqrt(252);
VolLong(t) = LongVol;
VolMed(t) = MedVol;
VolShort(t) = ShortVol;
% Compute divergence of long and short
if LongVol > 0
DivergenceSL = abs(ShortVol - LongVol) / LongVol;
else
DivergenceSL = 0; % Avoid division by zero
end
% Compute divergence of long medium and short]
if MedVol > 0
DivergenceSM = abs(ShortVol - MedVol)/MedVol;
else
DivergenceSM = 0;
end
% Smooth beta function using tanh
%Beta1(t) =
Beta2(t) = tanh(c*DivergenceSL); % Maps to (0,1)
end
VolMeasure2 = Beta2.*VolShort + (1-Beta2).*VolLong;
PositionMultiplierLongVol = TargetVol./VolLong;
PositionMultiplierLongVol(1:60) = 1;
% PositionMultiplierLongVol(PositionMultiplierLongVol>1) = 1;
PositionMultiplierShortVol = TargetVol./VolShort;
PositionMultiplierShortVol(1:60) = 1;
% PositionMultiplierShortVol(PositionMultiplierShortVol>1) = 1;
PositionMultiplier = TargetVol./VolMeasure2;
PositionMultiplier(1:60) = 1;
% PositionMultiplier(PositionMultiplier>1) = 1;
% PositionMultiplier = PositionMultiplier;
VolMeasure2 = VolLong;
Counter = 0;
for i = 2:length(VolMeasure2)
if VolMeasure2(i)>=0.15 && VolMeasure2(i-1) < 0.15 || VolMeasure2(i)<=0.15 && VolMeasure2(i-1) > 0.15
Counter = Counter+1;
end
end
aux1 = (VolMeasure2>=0.2);
aux2 = VolMeasure2<=0.1;
aux = (aux1+aux2);
aux(1:ShortN) = 0;
plot(Dates(ShortN+1:end),VolMeasure2(ShortN+1:end), 'DisplayName',sprintf('Original, Vol of Vol: %0.3f, Number of times hitting target Vol: %d, Mean Vol: %0.3f, SR: %0.4f, Time spent outside 5 percent band around target vol: %0.4f',std(VolMeasure2(ShortN+1:end)),Counter,mean(VolMeasure2(ShortN+1:end)),SharpeRatio5Min(OriginalPnl),mean(aux)))
hold on
OgReturns = Returns;
for i = 2:length(PositionMultiplier)
Returns(i) = Returns(i)*PositionMultiplier(i-1);
ReturnsLongVol(i) = OgReturns(i)*PositionMultiplierLongVol(i-1);
ReturnsShortVol(i) = OgReturns(i)*PositionMultiplierShortVol(i-1);
end
Wealth = 1;
WealthTracker(1) = 1;
for i = 1:length(Returns)
Wealth = Wealth*(Returns(i)+1);
WealthTracker(i) = Wealth;
end
WeightedBetaPnl = WealthTracker;
Wealth = 1;
WealthTracker(1) = 1;
for i = 1:length(ReturnsLongVol)
Wealth = Wealth*(ReturnsLongVol(i)+1);
WealthTracker(i) = Wealth;
end
LongVolPnl = WealthTracker;
Wealth = 1;
WealthTracker(1) = 1;
for i = 1:length(ReturnsShortVol)
Wealth = Wealth*(1+ReturnsShortVol(i));
WealthTracker(i) = Wealth;
end
ShortVolPnl = WealthTracker;
% Preallocate arrays
VolShort = zeros(T, 1);
VolLong = zeros(T, 1);
Beta2 = zeros(T, 1);
% Initial variance guess (use sample variance, handle small samples)
for i = 1:LongN
if i == 1
VolShort(i) = Returns(i)^2; % Single point: use squared return
VolLong(i) = VolShort(i);
else
VolShort(i) = var(Returns(1:i));
VolLong(i) = VolShort(i);
end
end
% Compute exponentially weighted variances
for t = ShortN+1:T
VolShort(t) = (1 - ShortAlpha) * VolShort(t-1) + ShortAlpha * Returns(t)^2;
if t > LongN
VolLong(t) = (1 - LongAlpha) * VolLong(t-1) + LongAlpha * Returns(t)^2;
end
end
% Convert variances to volatilities (standard deviations) only for divergence
for t = ShortN+1:T
ShortVol = sqrt(VolShort(t));
LongVol = sqrt(VolLong(t));
% Optional: Annualize volatilities
ShortVol = ShortVol * sqrt(252);
LongVol = LongVol * sqrt(252);
VolLong(t) = LongVol;
VolShort(t) = ShortVol;
% Compute divergence
if LongVol > 0
DivergenceSL = abs(ShortVol - LongVol) / LongVol;
else
DivergenceSL = 0; % Avoid division by zero
end
% Smooth beta function using tanh
Beta2(t) = tanh(DivergenceSL); % Maps to (0,1)
end
VolMeasureNew = VolLong; % Beta.*VolShort + (1-Beta).*VolLong;
Counter = 0;
for i = 2:length(VolMeasureNew)
if VolMeasureNew(i)>=0.15 && VolMeasureNew(i-1) < 0.15 || VolMeasureNew(i)<=0.15 && VolMeasureNew(i-1) > 0.15
Counter = Counter+1;
end
end
aux1 = (VolMeasureNew>=0.2);
aux2 = VolMeasureNew<=0.1;
aux = (aux1+aux2);
aux(1:ShortN) = 0;
Counter1 = Counter;
auxwb = aux;
VolMeasureNew1 = VolMeasureNew;
plot(Dates(ShortN+1:end),VolMeasureNew(ShortN+1:end), 'DisplayName',sprintf('2 Window Weighted Beta, Vol of Vol: %0.3f, Number of times hitting target Vol: %d, Mean Vol: %0.3f, SR: %0.4f, Time spent outside 5 percent band around target vol: %0.4f',std(VolMeasureNew1(ShortN+1:end)),Counter1,mean(VolMeasureNew1(ShortN+1:end)),SharpeRatio5Min(WeightedBetaPnl),mean(auxwb)))
hold on
end
% Preallocate arrays
VolShort = zeros(T, 1);
VolLong = zeros(T, 1);
Beta2 = zeros(T, 1);
% Initial variance guess (use sample variance, handle small samples)
for i = 1:LongN
if i == 1
VolShort(i) = ReturnsLongVol(i)^2; % Single point: use squared return
VolLong(i) = VolShort(i);
else
VolShort(i) = var(ReturnsLongVol(1:i));
VolLong(i) = VolShort(i);
end
end
% Compute exponentially weighted variances
for t = ShortN+1:T
VolShort(t) = (1 - ShortAlpha) * VolShort(t-1) + ShortAlpha * ReturnsLongVol(t)^2;
if t > LongN
VolLong(t) = (1 - LongAlpha) * VolLong(t-1) + LongAlpha * ReturnsLongVol(t)^2;
end
end
% Convert variances to volatilities (standard deviations) only for divergence
for t = ShortN+1:T
ShortVol = sqrt(VolShort(t));
LongVol = sqrt(VolLong(t));
% Optional: Annualize volatilities
ShortVol = ShortVol * sqrt(252);
LongVol = LongVol * sqrt(252);
VolLong(t) = LongVol;
VolShort(t) = ShortVol;
% Compute divergence
if LongVol > 0
DivergenceSL = abs(ShortVol - LongVol) / LongVol;
else
DivergenceSL = 0; % Avoid division by zero
end
% Smooth beta function using tanh
Beta2(t) = tanh(DivergenceSL); % Maps to (0,1)
end
VolMeasureNew = VolLong;%Beta.*VolShort + (1-Beta).*VolLong;
Counter = 0;
for i = 2:length(VolMeasureNew)
if VolMeasureNew(i)>=0.15 && VolMeasureNew(i-1) < 0.15 || VolMeasureNew(i)<=0.15 && VolMeasureNew(i-1) > 0.15
Counter = Counter+1;
end
end
aux1 = (VolMeasureNew>=0.2);
aux2 = VolMeasureNew<=0.1;
aux = (aux1+aux2);
aux(1:ShortN) = 0;
plot(Dates(ShortN+1:end),VolMeasureNew(ShortN+1:end), 'DisplayName',sprintf('Long Vol, Vol of Vol: %0.3f, Number of times hitting target Vol: %d, Mean Vol: %0.3f, SR: %0.4f, Time spent outside 5 percent band around target vol: %0.4f',std(VolMeasureNew(ShortN+1:end)),Counter,mean(VolMeasureNew(ShortN+1:end)),SharpeRatio5Min(LongVolPnl),mean(aux)))
% Preallocate arrays
VolShort = zeros(T, 1);
VolLong = zeros(T, 1);
Beta2 = zeros(T, 1);
% Initial variance guess (use sample variance, handle small samples)
for i = 1:LongN
if i == 1
VolShort(i) = ReturnsShortVol(i)^2; % Single point: use squared return
VolLong(i) = VolShort(i);
else
VolShort(i) = var(ReturnsShortVol(1:i));
VolLong(i) = VolShort(i);
end
end
% Compute exponentially weighted variances
for t = ShortN+1:T
VolShort(t) = (1 - ShortAlpha) * VolShort(t-1) + ShortAlpha * ReturnsShortVol(t)^2;
if t > LongN
VolLong(t) = (1 - LongAlpha) * VolLong(t-1) + LongAlpha * ReturnsShortVol(t)^2;
end
end
% Convert variances to volatilities (standard deviations) only for divergence
for t = ShortN+1:T
ShortVol = sqrt(VolShort(t));
LongVol = sqrt(VolLong(t));
% Optional: Annualize volatilities
ShortVol = ShortVol * sqrt(252);
LongVol = LongVol * sqrt(252);
VolLong(t) = LongVol;
VolShort(t) = ShortVol;
% Compute divergence
if LongVol > 0
DivergenceSL = abs(ShortVol - LongVol) / LongVol;
else
DivergenceSL = 0; % Avoid division by zero
end
% Smooth beta function using tanh
Beta2(t) = tanh(DivergenceSL); % Maps to (0,1)
end
VolMeasureNew = VolLong;% Beta2.*VolShort + (1-Beta2).*VolLong;
Counter = 0;
for i = 2:length(VolMeasureNew)
if VolMeasureNew(i)>=0.15 && VolMeasureNew(i-1) < 0.15 || VolMeasureNew(i)<=0.15 && VolMeasureNew(i-1) > 0.15
Counter = Counter+1;
end
end
aux1 = (VolMeasureNew>=0.2);
aux2 = VolMeasureNew<=0.1;
aux = (aux1+aux2);
aux(1:ShortN) = 0;
plot(Dates(ShortN+1:end),VolMeasureNew(ShortN+1:end), 'DisplayName',sprintf('Short Vol, Vol of Vol: %0.3f, Number of times hitting target Vol: %d, Mean Vol: %0.3f, SR: %0.4f, Time spent outside 5 percent band around target vol: %0.4f',std(VolMeasureNew(ShortN+1:end)),Counter,mean(VolMeasureNew(ShortN+1:end)),SharpeRatio5Min(ShortVolPnl),mean(aux)))
yline(TargetVol,'-','DisplayName','Target Vol')
hold off
legend('show','Location','northwest')
```Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.