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-08

The problem with fixed position sizes

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.

Vol targeting solves this by scaling position size inversely with realized volatility: when the market is more volatile than your target, you put on less; when it's calmer, you put on more — up to a cap.

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

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

This same relationship is used in the portfolio manager when sizing the overall portfolio: leverage = target_vol / portfolio_realized_vol. Setting your individual strategy's TARGET_VOL to be consistent with the manager's target_vol_pct avoids double-counting risk when strategies are combined.