What is Historical Volatility (HV)?
Historical Volatility (HV) is a statistical measure that quantifies the degree of variation of a security's price returns over a specific past period. Unlike indicators like Average True Range (ATR) which measure average price movement, HV specifically uses standard deviation of logarithmic returns to represent volatility as a percentage. It indicates how much price has deviated from its average over time, effectively showing how "jumpy" or "calm" an asset has been.
HV is crucial for risk management, particularly in options trading (where it's compared to implied volatility), and for understanding different market phases. High HV suggests unpredictable, large price swings, while low HV suggests stable, smaller price movements.
In Pine Script, calculating and interpreting Historical Volatility allows traders and investors to adapt their strategies based on the current market's inherent risk and opportunity profile.
Components and Calculation
The calculation of Historical Volatility involves several statistical steps, typically annualized for consistency across different assets and timeframes:
- Logarithmic Returns: For each period, calculate the natural logarithm of the current closing price divided by the previous closing price. This normalizes percentage changes.
`Log Return = math.log(close / close[1])` - Standard Deviation of Log Returns: Calculate the standard deviation of these logarithmic returns over a specified `length` (e.g., 20 periods for 20 trading days).
`StdDev_Log_Returns = ta.stdev(Log Return, length)` - Annualization: To compare volatility across different assets or timeframes, the daily (or per-period) standard deviation is annualized. This involves multiplying by the square root of the number of periods in a year.
* For daily data, typically `math.sqrt(252)` (representing 252 trading days in a year). * For weekly data, `math.sqrt(52)`. * For monthly data, `math.sqrt(12)`.
`Annualized HV = StdDev_Log_Returns * math.sqrt(Annualization Factor)` - Convert to Percentage: The result is often multiplied by 100 to express it as a percentage.
`Historical Volatility (%) = Annualized HV * 100`
A common `length` for daily charts is 20 periods, representing roughly one trading month.
Basic Historical Volatility (HV) Implementation in Pine Script
Pine Script v5 requires a custom function to calculate Historical Volatility, as `ta.hvol()` is not a direct built-in in its most common annualized form. We'll leverage `math.log` and `ta.stdev`.
//@version=5
indicator("My Historical Volatility (HV)", overlay=false, format=format.percent) // overlay=false to plot in a separate pane
// Input for HV length (number of periods for standard deviation)
length = input.int(20, title="HV Length", minval=2) // Minval 2 needed for log return calculation
// Input for annualization factor
// 252 for daily trading days, 365 for calendar days (if using daily candles)
// 52 for weekly, 12 for monthly
annualizationFactor = input.int(252, title="Annualization Factor (e.g., 252 for daily)", minval=1)
// Calculate Logarithmic Returns
// Handle first bar case (close[1] will be NaN)
logReturn = math.log(close / close[1])
// Calculate Standard Deviation of Log Returns
// Use `fixnan` to handle the first `length-1` bars where stdev would be NaN
stdevLogReturns = ta.stdev(logReturn, length)
// Annualize the Standard Deviation
annualizedHV = stdevLogReturns * math.sqrt(annualizationFactor)
// Convert to percentage
historicalVolatility = annualizedHV * 100
// Plot the Historical Volatility line
plot(historicalVolatility, title="Historical Volatility (%)", color=color.blue, linewidth=2)
// Optional: Add moving average of HV to identify trends in volatility
hvMA = ta.sma(historicalVolatility, 20) // 20-period SMA of HV
plot(hvMA, title="HV SMA", color=color.gray, linewidth=1, style=plot.style_line)
// Highlight background when HV is significantly higher or lower than its average
bgcolor(historicalVolatility > hvMA * 1.2 ? color.new(color.red, 90) : na, title="High HV")
bgcolor(historicalVolatility < hvMA * 0.8 ? color.new(color.green, 90) : na, title="Low HV")
Practical Historical Volatility (HV) Trading Strategies
1. Volatility Regimes (Mean Reversion of Volatility)
Volatility often exhibits mean-reverting behavior: periods of high volatility tend to be followed by periods of low volatility, and vice versa. HV helps identify these shifts.
- High HV (Contracting Volatility): When HV is significantly high (e.g., above its long-term average or a historical high threshold), it suggests volatility is stretched and might soon contract. This often occurs after sharp price movements. Traders might look for a reversal or a period of consolidation to follow.
- Low HV (Expanding Volatility): When HV is significantly low (e.g., below its long-term average or a historical low threshold), it suggests volatility is compressed and might soon expand. This often occurs during quiet, range-bound periods. Traders might anticipate an impending price breakout or the start of a strong trend.
//@version=5
strategy("HV Volatility Regimes", overlay=true)
length = input.int(20, title="HV Length", minval=2)
annualizationFactor = input.int(252, title="Annualization Factor", minval=1)
hvUpperThreshold = input.float(30.0, title="High HV Threshold (%)", minval=10.0, step=1.0)
hvLowerThreshold = input.float(15.0, title="Low HV Threshold (%)", minval=5.0, step=1.0)
logReturn = math.log(close / close[1])
stdevLogReturns = ta.stdev(logReturn, length)
historicalVolatility = stdevLogReturns * math.sqrt(annualizationFactor) * 100
plot(historicalVolatility, "Historical Volatility", color.blue, display=display.pane_only)
hline(hvUpperThreshold, "High Vol Threshold", color.red, linestyle=hline.style_dashed, display=display.pane_only)
hline(hvLowerThreshold, "Low Vol Threshold", color.green, linestyle=hline.style_dashed, display=display.pane_only)
// Conditions for Volatility Expansion/Contraction
isHighHV = historicalVolatility > hvUpperThreshold
isLowHV = historicalVolatility < hvLowerThreshold
// Strategy Entry/Exit based on volatility shifts
// Example: Enter long when volatility is low and breaks out bullishly
// And short when volatility is high and reverses bearishly
maFast = ta.ema(close, 10)
maSlow = ta.ema(close, 20)
// Anticipate bullish breakout after low volatility
if (isLowHV[1] and ta.crossover(maFast, maSlow))
strategy.entry("Long (Vol Expand)", strategy.long)
// Anticipate bearish reversal after high volatility
if (isHighHV[1] and ta.crossunder(maFast, maSlow))
strategy.entry("Short (Vol Contract)", strategy.short)
// Basic exit logic
strategy.close("Long (Vol Expand)", when=ta.crossunder(maFast, maSlow))
strategy.close("Short (Vol Contract)", when=ta.crossover(maFast, maSlow))
2. Options Trading (Implied Volatility vs. Historical Volatility)
While Pine Script does not directly provide implied volatility data from options chains, traders often compare an option's implied volatility (IV) to the underlying asset's Historical Volatility (HV) to assess if options are relatively cheap or expensive.
- IV > HV: Options are expensive (premium is high relative to actual past price movement). Opportunity for option sellers (e.g., sell covered calls, put credit spreads).
- IV < HV: Options are cheap (premium is low relative to actual past price movement). Opportunity for option buyers (e.g., buy calls, buy puts).
This strategy is conceptual within Pine Script, as it requires external IV data, but HV itself is a core component.
//@version=5
indicator("HV for Options Context", overlay=false, format=format.percent)
length = input.int(20, title="HV Length", minval=2)
annualizationFactor = input.int(252, title="Annualization Factor", minval=1)
logReturn = math.log(close / close[1])
stdevLogReturns = ta.stdev(logReturn, length)
historicalVolatility = stdevLogReturns * math.sqrt(annualizationFactor) * 100
plot(historicalVolatility, "Historical Volatility (%)", color=color.blue, linewidth=2)
// To compare with Implied Volatility (IV), you would need to input IV manually or via external data.
// For demonstration, let's assume a hypothetical IV input.
// Note: This 'impliedVol' input is purely for illustration; real IV comes from option chain data.
impliedVol = input.float(na, title="Implied Volatility (%) (Manual Input for Demo)", group="Options Context")
// Plot the hypothetical Implied Volatility for visual comparison
plot(not na(impliedVol) ? impliedVol : na, "Implied Volatility", color=color.orange, linewidth=2)
// Determine if options are relatively expensive or cheap based on IV vs HV
areOptionsExpensive = historicalVolatility > 0 and impliedVol > historicalVolatility * 1.2 // IV is 20% higher than HV
areOptionsCheap = historicalVolatility > 0 and impliedVol < historicalVolatility * 0.8 // IV is 20% lower than HV
bgcolor(areOptionsExpensive ? color.new(color.red, 90) : na, title="Options Potentially Expensive")
bgcolor(areOptionsCheap ? color.new(color.green, 90) : na, title="Options Potentially Cheap")
alertcondition(areOptionsExpensive, "Options Expensive", "Implied Volatility is high relative to Historical Volatility.")
alertcondition(areOptionsCheap, "Options Cheap", "Implied Volatility is low relative to Historical Volatility.")
3. Dynamic Stop Loss & Position Sizing
Similar to ATR, Historical Volatility can be used to dynamically adjust risk management parameters, but on a percentage basis.
- Dynamic Stop Loss: For a long position, place a stop loss `X%` below your entry, where `X` is a multiple of the current HV. For a short position, `X%` above your entry.
`Stop Loss Price = Entry Price * (1 - (HV * Multiplier) / 100)` for longs. - Dynamic Position Sizing: Adjust your position size so that your maximum dollar risk per trade remains constant. When HV is high, reduce position size; when HV is low, increase position size.
`Shares = Risk Per Trade / (Entry Price * HV)`
//@version=5
strategy("HV Dynamic Risk Management", overlay=true)
length = input.int(20, title="HV Length", minval=2)
annualizationFactor = input.int(252, title="Annualization Factor", minval=1)
hvStopLossMultiplier = input.float(1.5, title="HV Stop Loss Multiplier", minval=0.1, step=0.1) // e.g., 1.5x HV
accountRiskPerTrade = input.float(1000.0, title="Max $ Risk Per Trade", minval=10)
logReturn = math.log(close / close[1])
stdevLogReturns = ta.stdev(logReturn, length)
historicalVolatility = stdevLogReturns * math.sqrt(annualizationFactor) // Not converting to % for calculation
// Simple entry: crossing a 20-period SMA
maLength = input.int(20, "Entry MA Length", minval=1)
maValue = ta.sma(close, maLength)
longCondition = ta.crossover(close, maValue)
shortCondition = ta.crossunder(close, maValue)
// Calculate dynamic stop loss
// Ensure historicalVolatility is not zero to prevent division by zero
longStopLossPercentage = historicalVolatility * hvStopLossMultiplier
shortStopLossPercentage = historicalVolatility * hvStopLossMultiplier
longStopPrice = strategy.position_avg_price * (1 - longStopLossPercentage)
shortStopPrice = strategy.position_avg_price * (1 + shortStopLossPercentage)
// Calculate dynamic position size
// Risk_per_share = Entry Price * HV * Multiplier
// Qty = Account Risk / Risk_per_share
qtyToTrade = 0
if (historicalVolatility > 0)
riskPerShare = close * historicalVolatility * hvStopLossMultiplier
qtyToTrade := math.floor(accountRiskPerTrade / riskPerShare)
qtyToTrade := math.max(1, qtyToTrade) // Ensure at least 1 share
if (longCondition)
strategy.entry("Long HV", strategy.long, qty=qtyToTrade)
if (shortCondition)
strategy.entry("Short HV", strategy.short, qty=qtyToTrade)
// Apply dynamic stop losses
strategy.exit("Long Exit", from_entry="Long HV", stop=longStopPrice)
strategy.exit("Short Exit", from_entry="Short HV", profit=na, stop=shortStopPrice) // Added profit=na for clarity based on previous exits; adjust if needed
plot(historicalVolatility * 100, "HV %", color=color.purple, display=display.pane_only) // Plot as percentage for viewing
plot(strategy.position_avg_price > 0 ? longStopPrice : na, "Long Stop", color=color.red, style=plot.style_linebr)
plot(strategy.position_avg_price < 0 ? shortStopPrice : na, "Short Stop", color=color.green, style=plot.style_linebr)
Optimizing Historical Volatility Performance
To get the most from Historical Volatility in Pine Script:
- Parameter Tuning:
- `Length`: The period for calculating standard deviation. Common values are 20 (for daily charts, representing a month) or 252 (for annual volatility). Shorter lengths are more reactive to recent price swings, longer lengths provide a smoother, broader view.
- `Annualization Factor`: Crucial for consistency. Ensure it matches your chart's timeframe (e.g., 252 for daily, 52 for weekly).
- Combine with Directional Indicators: HV tells you *how much* prices are moving, not *where*. Always combine it with trend-following or momentum indicators (e.g., moving averages, MACD, ADX) to confirm the direction of the market before acting on HV signals.
- Contextual Interpretation: HV levels are relative to the asset. What's "high" volatility for a stable large-cap stock might be "low" for a highly speculative cryptocurrency. Analyze historical HV ranges for the specific asset.
- Volatility Spikes and Contractions: The cyclical nature of volatility (expansion followed by contraction, and vice-versa) is a powerful concept. Look for HV reaching historical extremes as potential turning points for volatility itself, which can precede price reversals or strong breakouts.
- Options Trading Integration (External): For serious options trading, compare HV with implied volatility data from an options provider. HV helps determine if current market prices for options are cheap or expensive.
Common Historical Volatility Pitfalls
- Not a Directional Signal: The most critical mistake is using HV to predict future price direction. It measures past price *movement*, not future direction.
- Lagging Indicator: HV is based on past data and calculations, making it a lagging indicator. It reflects what has *already happened*.
- Sensitivity to Outliers: Extreme price swings (e.g., flash crashes, news events) can cause temporary spikes in HV that might not be representative of the underlying volatility regime.
- Parameter Dependency: The HV value can change significantly depending on the `length` and `annualizationFactor` chosen, requiring careful selection and consistency.
- Not a Standalone Indicator: HV is a powerful analytical tool but should always be part of a comprehensive trading system that includes trend analysis, risk management, and confirmation from other indicators.
Conclusion
Historical Volatility (HV) is a fundamental statistical measure in Pine Script for TradingView, providing a quantifiable understanding of an asset's price variability. By measuring the annualized standard deviation of logarithmic returns, it offers valuable insights into market behavior, aiding in crucial aspects like risk management, position sizing, and adapting strategies to changing volatility regimes. While it does not predict price direction, its ability to signal periods of volatility expansion (preceding breakouts) or contraction (preceding reversals/consolidation) is invaluable. By understanding its calculation, thoughtfully tuning its parameters, and integrating it strategically with directional indicators and robust risk management practices, you can leverage Historical Volatility to enhance your trading decisions and navigate various market environments with greater precision.
Enhance Your Trading
Get a high-performance Pine Script analysis tool for actionable market insights, designed for traders on the move.
This strategy runs in live mode on TradingView, helping you identify potential opportunities.
Subscribe now @ $99/month