Volatility Targeting Explained

Fixed position sizes ignore market conditions. Vol targeting adjusts how much you put on based on how volatile the market currently is.

Last updated 2026-09
Key Takeaways
  • Vol targeting scales position size inversely with realized volatility: when the market is noisier than your target you put on less, when it is calmer you put on more — up to a cap.
  • Two steps: take the rolling standard deviation of log returns and annualize it to get realized volatility; then multiply the signal by target_vol ÷ realized_vol and clip it at vol_cap.
  • The defaults are target_vol=0.30, vol_cap=2.0, lookback=720, periods_per_year=8760, calibrated for a 1h strategy — change the interval and you have to recompute them.
  • Rule of thumb: target_vol ≈ acceptable MDD ÷ 2. The default 30% corresponds to a typical MDD of about −60%.
  • It adjusts position size, not the direction of the signal; when the signal is wrong, it only decides how wrong you are.

The same strategy is boring enough through the quiet months that you want to switch it off; then, in the week the market starts moving violently, it draws down far deeper than the backtest average led you to expect. The signal did not change, the market did — and the size of every bet you place stayed exactly the same.

What volatility targeting is

Vol targeting means scaling position size inversely with the market's realized volatility: when volatility is above your target you put on less, when it is below you put on more — up to a cap.

A basic Type A strategy outputs a signal of 1.0 (long) or 0.0 (flat). The runner deploys the full allocated capital when the signal is 1.0. This works, but it has a hidden problem: the strategy takes the same-sized bet regardless of whether the market is calm or chaotic.

In a low-volatility period, a 1% daily move is large. In a high-volatility period, a 1% move is noise. If your strategy enters with the same position size in both environments, your actual dollar risk swings dramatically — and your drawdowns in volatile periods will be far larger than what your backtest average suggests.

The signal says "buy", it does not say "how much".
A fixed position assumes every day's market is equally noisy — the same order is two different risks in a calm period and in a crisis.

The formula

Blave Agent implements vol targeting in two steps:

Step 1 — Measure realized volatility

log_return = log(Close / Close.shift(1))
realized_vol = log_return.rolling(lookback).std() * sqrt(periods_per_year)

This computes the rolling standard deviation of log returns, then annualizes it by multiplying by sqrt(periods_per_year). The result is annualized realized volatility — the same unit as Sharpe ratio volatility.

Defaults: lookback=720 bars, periods_per_year=8760. These are calibrated for a 1h interval strategy: 720 bars = 30 days × 24 hours; 8760 = 24 × 365.

Step 2 — Scale the signal

scale = (target_vol / realized_vol).clip(upper=vol_cap)
scaled_signal = signal * scale

The signal (1.0 = full long) is multiplied by the volatility ratio, capped at vol_cap. The result is a fractional position size.

Defaults: target_vol=0.30 (30% annualized), vol_cap=2.0 (maximum 2× leverage).

Concrete examples

Realized vol (ann.)Target volRaw scaleAfter capPosition size
10%30%3.0×2.0× (capped)200% of allocation
20%30%1.5×1.5×150% of allocation
30%30%1.0×1.0×100% — baseline
60%30%0.5×0.5×50% of allocation
90%30%0.33×0.33×33% of allocation

At 30% realized vol the strategy runs at full size. During a crisis when realized vol spikes to 90%, the strategy automatically trims to one-third position. In a calm period at 10% vol, it scales up to the cap of 2×.

Adjusting for different intervals

The defaults (lookback=720, periods_per_year=8760) are for a 1h strategy. For other intervals, both values must be recalculated:

Intervalperiods_per_yearlookback (30-day window)
5min105,120 (12 × 24 × 365)8,640 bars
1h (default)8,760720 bars
4h2,190180 bars
8h1,09590 bars
1d36530 bars
For daily strategies, a 30-bar lookback is short. Consider using a longer lookback like 252 bars (one year) to capture a more stable volatility estimate and avoid over-reacting to short-term spikes.

How to add it to a strategy

VOL_TARGETING    = True
TARGET_VOL       = 0.30   # 30% annualized target
VOL_CAP          = 2.0    # max 2× leverage
VOL_LOOKBACK     = 720    # bars in lookback window
PERIODS_PER_YEAR = 8760   # for 1h interval

def fetch_data(hdrs):
    from lib.data import fetch_kline
    from lib.strategy import add_realized_vol
    df = fetch_kline(SYMBOL, INTERVAL, START, END, hdrs)
    if VOL_TARGETING:
        add_realized_vol(df, VOL_LOOKBACK, PERIODS_PER_YEAR)
    return df

def compute_signals(df):
    from lib.strategy import apply_vol_scaling
    signal = pd.Series(np.nan, index=df.index)
    # ... your signal logic here ...
    if VOL_TARGETING:
        return apply_vol_scaling(signal, df, TARGET_VOL, VOL_CAP)
    return signal
ParameterDefaultMeaningWhen to change it
TARGET_VOL0.3030% annualized target volatilitySet it to the MDD you can accept ÷ 2
VOL_CAP2.0At most 2× leverageSet 1.0 if you want no leverage at all
VOL_LOOKBACK720Length of the volatility estimation window (in bars)Recompute when you change the interval — see the table above
PERIODS_PER_YEAR8760Periods per year used for annualizing (1h interval)Recompute when you change the interval — see the table above

Note that add_realized_vol must be called in fetch_data (before signals are computed), because apply_vol_scaling reads df['realized_vol'] which is set in-place by the first function.

The vol_cap: why 2× and not higher

The cap exists because vol targeting can produce extreme leverage in persistently low-volatility environments. If realized vol drops to 5%, the uncapped scale would be 6× — three times the allocation. On a 1-day adverse move, this can cause a catastrophic drawdown even with a fundamentally sound signal.

The default cap of 2× limits the strategy to deploying at most double the allocated capital. For strategies where you want to avoid any leverage at all, set vol_cap=1.0. This turns vol targeting into pure vol-adjusted position sizing with no leverage — position size only shrinks in high-vol, never expands past full size.

Target vol and implied MDD

A useful rule of thumb: target_vol ≈ acceptable MDD ÷ 2. This comes from the empirical relationship between annualized volatility and maximum drawdown in trend-following strategies.

target_volImplied typical MDDUse case
10%≈ −20%Conservative, capital preservation priority
20%≈ −40%Moderate
30% (default)≈ −60%Aggressive — full risk budget

How to tell your parameters are set right

1

periods_per_year matches your interval? The default 8760 is for 1h. It is 105,120 for 5min, 2,190 for 4h, 1,095 for 8h and 365 for 1d. Get it wrong and the annualization step is wrong, which makes every cell after it wrong.

2

lookback still 30 days? 720 bars = 1h × 30 days. Change it with the interval: 8,640 bars for 5min, 180 for 4h, 90 for 8h, 30 for 1d. On daily bars 30 is short — consider 252 (one year) for a steadier estimate.

3

target_vol matches the MDD you can accept? The rule of thumb is target_vol ≈ acceptable MDD ÷ 2: 10% for about −20%, 20% for about −40%, and the default 30% for about −60%. (For how to read MDD, see How to Read a Backtest.)

4

vol_cap gives you the leverage you want? The default 2.0 means up to twice the allocated capital. Set it to 1.0 and it will only shrink the position when volatility is high, never scale above the full allocation when volatility is low.

5

Your individual strategy's TARGET_VOL is consistent with the portfolio manager's target_vol_pct? The manager sizes overall leverage with the same logic (leverage = target_vol ÷ portfolio realized volatility). If the two disagree, combining strategies double-counts risk.

What volatility targeting will not do for you

  • It adjusts position size, not whether the signal is right. The scaling multiplies the signal — when the signal is wrong, it only decides how wrong you are.
  • target_vol ≈ acceptable MDD ÷ 2 is an empirical relationship observed in trend-following strategies, not a guarantee. The table says "typical" MDD, not maximum.
  • Realized volatility is computed from the past lookback bars: a longer window is a steadier estimate and a slower one, a shorter window over-reacts to a single short-lived spike. Neither lets you know in advance that volatility is about to jump.