← Back to Blog
February 10, 2026programming

Top 5 Volatility Indicators Every Crypto Trader Should Know

By APIndicators

Most crypto traders obsess over direction: will the price go up or down? But the traders who consistently profit focus just as much on volatility: how much will the price move, regardless of direction? Volatility determines your position size, your stop-loss distance, your expected profit target, and whether you should be trading at all.

In crypto markets, volatility is not just a risk metric. It is the primary feature that creates opportunity. Bitcoin can move 3% in an hour while the S&P 500 takes a month to make the same move. Understanding and measuring this volatility gives you a structural advantage over traders who only think about direction.

This guide covers five volatility indicators that every crypto trader should understand, from the foundational ATR to more sophisticated tools like Keltner Channel squeezes. For each one, we explain what it measures, how to calculate it, and how to use it in practice.

1. Average True Range (ATR)

ATR is the foundational volatility indicator. It measures the average range of price movement over a specified period, accounting for gaps between candles.

What it measures: The typical size of a candle, including any gap from the previous close. In 24/7 crypto markets, gaps are less common than in stocks, but ATR still captures intrabar volatility more accurately than simple high-low range.

import pandas as pd

def calculate_atr(df: pd.DataFrame, period: int = 14) -> pd.Series:
    high_low = df["high"] - df["low"]
    high_close = (df["high"] - df["close"].shift()).abs()
    low_close = (df["low"] - df["close"].shift()).abs()

    true_range = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
    return true_range.rolling(window=period).mean()

How to use ATR in practice:

  • Stop-loss placement: Place stops at 1.5-2x ATR from your entry. This gives the trade room to breathe within normal volatility while protecting against adverse moves.
  • Position sizing: If ATR is $2,000 on BTC and you want to risk 1% of a $10,000 account ($100), your maximum position size at a 1.5 ATR stop ($3,000) is $100 / $3,000 = 0.033 BTC.
  • Trailing stops: Move your stop by 1x ATR for every ATR the price moves in your favor.
  • Entry filter: When ATR is at its 90th percentile over the last 100 candles, volatility is extreme. Consider reducing position size or waiting for conditions to normalize.

ATR Ratio: Raw ATR changes as price changes (ATR on BTC at $100K is naturally larger than at $50K). Normalize it by dividing current ATR by the rolling average ATR to get a ratio that works across price levels:

def atr_ratio(df: pd.DataFrame, atr_period: int = 14, avg_period: int = 50) -> pd.Series:
    atr = calculate_atr(df, atr_period)
    return atr / atr.rolling(window=avg_period).mean()

An ATR ratio above 1.5 means current volatility is 50% higher than the recent average. Above 2.0 means you are in an extreme volatility environment.

2. Bollinger Band Width

Bollinger Band Width measures the distance between the upper and lower Bollinger Bands as a percentage of the middle band. It captures the cyclical nature of volatility better than any other indicator.

What it measures: How wide the price distribution has been relative to its moving average. Narrow bands mean low volatility (a squeeze). Wide bands mean high volatility (expansion).

def bollinger_bandwidth(prices: pd.Series, period: int = 20, num_std: float = 2.0) -> pd.Series:
    sma = prices.rolling(window=period).mean()
    std = prices.rolling(window=period).std()
    upper = sma + (num_std * std)
    lower = sma - (num_std * std)
    return (upper - lower) / sma * 100

How to use Bollinger Bandwidth in practice:

  • Squeeze detection: When bandwidth drops to its lowest level in 50+ candles, a large move is imminent. The squeeze does not predict direction, but it reliably predicts that volatility will expand.
  • Expansion alerts: When bandwidth reaches the 90th percentile, the volatile move is likely nearing exhaustion. This is the wrong time to initiate new trend-following positions.
  • Regime classification: Bandwidth below the 25th percentile = consolidation (use mean-reversion). Bandwidth above the 75th percentile = trending or volatile (use trend-following or sit out).

The key insight is that volatility is cyclical. Low volatility leads to high volatility and vice versa. Bollinger Bandwidth quantifies exactly where you are in this cycle.

3. Historical Volatility (HV)

Historical volatility measures the annualized standard deviation of log returns. It is the most statistically rigorous volatility measure and the one used in options pricing.

What it measures: The dispersion of returns over a lookback window, scaled to an annual figure. An HV of 80% means the asset's returns have exhibited enough variance that a one-standard-deviation annual move would be 80%.

import numpy as np

def historical_volatility(prices: pd.Series, window: int = 30, trading_periods: int = 365) -> pd.Series:
    log_returns = np.log(prices / prices.shift(1))
    return log_returns.rolling(window=window).std() * np.sqrt(trading_periods) * 100

Note: We use 365 trading periods for crypto (24/7 markets) instead of the 252 used for stocks.

How to use Historical Volatility in practice:

  • Relative comparison: Compare HV across different timeframes. If 10-day HV is much higher than 30-day HV, short-term volatility is spiking relative to the longer-term norm. This often happens during news events and tends to revert.
  • Volatility term structure: Plot 10-day, 20-day, and 60-day HV together. When short-term HV crosses above long-term HV, the market is in a volatility expansion. When short-term drops below long-term, conditions are normalizing.
  • Position sizing baseline: Use 30-day HV as the basis for position sizing across different assets. An altcoin with 120% HV needs position sizes roughly 1.5x smaller than BTC with 80% HV to achieve the same dollar risk.

| HV Range (Crypto) | Interpretation | Trading Implication | |-------------------|---------------|---------------------| | 20-40% | Very low (rare) | Squeeze building, reduce position sizing initially | | 40-70% | Normal | Standard position sizes | | 70-100% | Elevated | Reduce position sizes by 25-50% | | 100%+ | Extreme | Minimum position sizes or no new trades |

4. Keltner Channels

Keltner Channels plot bands around an EMA using ATR instead of standard deviation (which Bollinger Bands use). This makes them smoother and less reactive to individual large candles.

What they measure: A volatility envelope around the trend, based on actual price range rather than statistical dispersion. The key difference from Bollinger Bands is that Keltner Channels use ATR (a measure of range) while Bollinger Bands use standard deviation (a measure of return dispersion).

def keltner_channels(df: pd.DataFrame, ema_period: int = 20, atr_period: int = 14, multiplier: float = 2.0) -> pd.DataFrame:
    ema = df["close"].ewm(span=ema_period, adjust=False).mean()
    atr = calculate_atr(df, atr_period)
    upper = ema + (multiplier * atr)
    lower = ema - (multiplier * atr)

    return pd.DataFrame({
        "upper": upper,
        "middle": ema,
        "lower": lower,
    })

How to use Keltner Channels in practice:

  • Trend strength: When price closes above the upper Keltner Channel, the trend is strong. When it closes below the lower channel, the downtrend is strong. This is a more reliable trend strength indicator than Bollinger Bands because it is less prone to whipsaws.
  • TTM Squeeze: The TTM Squeeze, one of the most popular volatility setups, occurs when the Bollinger Bands move inside the Keltner Channels. This happens when standard-deviation-based volatility is lower than range-based volatility, indicating extreme compression.
def ttm_squeeze(df: pd.DataFrame) -> pd.Series:
    bb_upper = df["close"].rolling(20).mean() + 2 * df["close"].rolling(20).std()
    bb_lower = df["close"].rolling(20).mean() - 2 * df["close"].rolling(20).std()

    kc = keltner_channels(df, 20, 14, 1.5)

    squeeze_on = (bb_lower > kc["lower"]) & (bb_upper < kc["upper"])
    return squeeze_on

When the squeeze fires (Bollinger inside Keltner), prepare for a large move. When the squeeze releases (Bollinger expands outside Keltner), enter in the direction of the breakout.

5. Crypto Volatility Index and Fear/Greed Metrics

While the previous indicators are calculated from price data, market-wide sentiment indicators provide a macro volatility context. The Crypto Fear and Greed Index aggregates multiple data sources into a 0-100 score.

What it measures: Aggregate market sentiment derived from volatility (25%), market momentum/volume (25%), social media (15%), surveys (15%), Bitcoin dominance (10%), and Google trends (10%).

How to use sentiment-based volatility in practice:

  • Extreme fear (0-25): Historically, these are the best times to enter long positions. Volatility is typically at its peak, so use tight position sizes but be ready for a reversal.
  • Extreme greed (75-100): The market is complacent. This does not mean an immediate crash, but it means the next large volatility event is more likely to be downward. Tighten stops on longs.
  • Transition zones (25-75): Less actionable on their own. Combine with technical volatility indicators for better signals.

The most powerful signal from sentiment data is divergence: when price makes a new high but sentiment fails to reach extreme greed, or when price makes a new low but fear does not reach extreme levels. These divergences suggest the crowd's emotional response is fading, which often precedes reversals.

Building a Volatility Dashboard

Combine these five indicators into a single volatility assessment:

def volatility_assessment(df: pd.DataFrame) -> dict:
    atr = calculate_atr(df)
    atr_r = atr.iloc[-1] / atr.rolling(50).mean().iloc[-1]

    bb_bw = bollinger_bandwidth(df["close"])
    bb_pct = bb_bw.rank(pct=True).iloc[-1]

    hv_short = historical_volatility(df["close"], window=10).iloc[-1]
    hv_long = historical_volatility(df["close"], window=30).iloc[-1]

    squeeze = ttm_squeeze(df).iloc[-1]

    if atr_r > 2.0 or bb_pct > 0.9:
        regime = "EXTREME"
    elif atr_r > 1.3 or bb_pct > 0.75:
        regime = "HIGH"
    elif atr_r < 0.7 or bb_pct < 0.25:
        regime = "LOW"
    else:
        regime = "NORMAL"

    return {
        "atr_ratio": round(atr_r, 2),
        "bb_percentile": round(bb_pct * 100, 1),
        "hv_short": round(hv_short, 1),
        "hv_long": round(hv_long, 1),
        "hv_term_structure": "contango" if hv_short > hv_long else "backwardation",
        "ttm_squeeze": squeeze,
        "regime": regime,
    }

This assessment gives you a single snapshot of the volatility environment before you make any trading decisions. The regime classification directly informs position sizing: full size in NORMAL, reduced in HIGH, minimum or no new positions in EXTREME, and squeeze awareness in LOW.

Conclusion

Volatility indicators do not tell you which direction to trade. They tell you how much to trade and whether to trade at all. In crypto markets, where a single day can bring more volatility than a month in traditional markets, this information is at least as valuable as directional signals.

The five indicators covered here work at different levels: ATR for individual trade management, Bollinger Bandwidth for volatility cycles, Historical Volatility for statistical context, Keltner Channels for squeeze detection, and sentiment metrics for macro awareness. Together, they give you a complete picture of the volatility landscape. The traders who manage volatility well survive long enough for their directional edge to compound. The ones who ignore it eventually blow up, no matter how good their signals are.