Keltner Channels
I. What is the Keltner Channels Indicator
Keltner Channels was originally introduced by American grain trader Chester W. Keltner in 1960 in his book “How to Make Money in Commodities.” Later, renowned trader Linda Bradford Raschke modernized the indicator in the 1980s by replacing the original simple moving average with an Exponential Moving Average (EMA) and substituting the Average True Range (ATR) for the original high-low average range, making it more responsive and practical.
Keltner Channels belongs to the Volatility / Bands overlay indicator category and consists of three lines:
- Middle Line: EMA of the closing price
- Upper Band: Middle Line + Multiplier x ATR
- Lower Band: Middle Line - Multiplier x ATR
The default parameters are EMA period = 20, ATR period = 10, and ATR multiplier = 1.5.
Keltner Channels and Bollinger Bands are frequently used together to detect the “Bollinger Squeeze” — when Bollinger Bands contract inside the Keltner Channels, it indicates extreme volatility compression and the market is about to make a directional move. This is the well-known strategy popularized by John Carter in “Mastering the Trade.”
II. Mathematical Principles and Calculation
2.1 Core Formulas
Middle Line:
where the EMA recursive formula is:
True Range (TR):
Average True Range (ATR):
In the modern version of Keltner Channels, the ATR uses EMA smoothing (some versions use RMA / Wilder smoothing). The default smoothing method may differ across platforms, so verify before use.
Upper Band:
Lower Band:
where is the ATR multiplier, default 1.5.
2.2 Original Version (Chester Keltner)
The original version used different formulas:
- Middle Line = SMA(Typical Price, 10), where Typical Price
- Upper Band = Middle Line + SMA(, 10)
- Lower Band = Middle Line - SMA(, 10)
The modern version is more widely used because EMA is more responsive and ATR better reflects true volatility than the simple high-low range.
2.3 Calculation Steps
- Calculate the EMA of closing prices (period ) -> middle line
- Calculate the daily True Range (TR)
- Smooth TR with EMA (period ) -> ATR
- Upper Band = Middle Line + ATR
- Lower Band = Middle Line - ATR
III. Python Implementation
import numpy as np
import pandas as pd
def true_range(high: pd.Series, low: pd.Series, close: pd.Series) -> pd.Series:
"""Calculate True Range (TR)."""
prev_close = close.shift(1)
tr1 = high - low
tr2 = (high - prev_close).abs()
tr3 = (low - prev_close).abs()
return pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
def keltner_channels(high: pd.Series, low: pd.Series, close: pd.Series,
ema_period: int = 20, atr_period: int = 10,
multiplier: float = 1.5) -> pd.DataFrame:
"""
Calculate Keltner Channels — modern version.
Parameters:
high : high price series
low : low price series
close : closing price series
ema_period : EMA period, default 20
atr_period : ATR period, default 10
multiplier : ATR multiplier, default 1.5
Returns:
DataFrame with columns: middle, upper, lower, atr
"""
# Middle line: EMA of closing prices
middle = close.ewm(span=ema_period, adjust=False).mean()
# ATR: EMA of True Range
tr = true_range(high, low, close)
atr = tr.ewm(span=atr_period, adjust=False).mean()
# Upper and lower bands
upper = middle + multiplier * atr
lower = middle - multiplier * atr
return pd.DataFrame({
'middle': middle,
'upper': upper,
'lower': lower,
'atr': atr
})
def keltner_bb_squeeze(high: pd.Series, low: pd.Series, close: pd.Series,
bb_period: int = 20, bb_std: float = 2.0,
kc_ema: int = 20, kc_atr: int = 10,
kc_mult: float = 1.5) -> pd.Series:
"""
Detect Bollinger Band - Keltner Channel squeeze signals.
Returns True when Bollinger Bands are entirely inside Keltner Channels.
"""
# Bollinger Bands
bb_mid = close.rolling(window=bb_period).mean()
bb_std_val = close.rolling(window=bb_period).std(ddof=0)
bb_upper = bb_mid + bb_std * bb_std_val
bb_lower = bb_mid - bb_std * bb_std_val
# Keltner Channels
kc = keltner_channels(high, low, close, kc_ema, kc_atr, kc_mult)
# Squeeze condition: BB upper < KC upper AND BB lower > KC lower
squeeze = (bb_upper < kc['upper']) & (bb_lower > kc['lower'])
return squeeze
# ============ Usage Example ============
if __name__ == '__main__':
np.random.seed(42)
n_days = 120
# Generate simulated OHLCV data
base_price = 50 + np.cumsum(np.random.randn(n_days) * 0.5)
df = pd.DataFrame({
'open': base_price + np.random.randn(n_days) * 0.2,
'high': base_price + np.abs(np.random.randn(n_days) * 0.8),
'low': base_price - np.abs(np.random.randn(n_days) * 0.8),
'close': base_price,
'volume': np.random.randint(1000, 10000, n_days)
})
# Calculate Keltner Channels
kc = keltner_channels(df['high'], df['low'], df['close'],
ema_period=20, atr_period=10, multiplier=1.5)
result = pd.concat([df[['close']], kc], axis=1)
print("=== Keltner Channels Results (last 10 rows) ===")
print(result.tail(10).to_string())
# Detect squeeze signals
squeeze = keltner_bb_squeeze(df['high'], df['low'], df['close'])
squeeze_count = squeeze.sum()
print(f"\n=== Bollinger Squeeze Detection ===")
print(f"Total trading days: {len(df)}, Squeeze days: {squeeze_count}, "
f"Percentage: {squeeze_count/len(df)*100:.1f}%")
# Generate trend signals
result['trend'] = np.where(df['close'] > kc['upper'], 'Uptrend',
np.where(df['close'] < kc['lower'], 'Downtrend', 'In Channel'))
print("\n=== Trend Signal Statistics ===")
print(result['trend'].value_counts())
IV. Problems the Indicator Solves
4.1 Trend Direction Identification
Keltner Channels provides a clear framework for trend assessment:
- Price above the upper band: Strong uptrend
- Price below the lower band: Strong downtrend
- Price inside the channel: No clear trend or range-bound
- Middle line direction: The slope of the middle line reflects the overall trend direction
4.2 Breakout Trading Signals
- Closing price breaks above the upper band: Long signal
- Closing price breaks below the lower band: Short signal
- Price returns inside the channel: Can serve as an exit or position reduction signal
4.3 Bollinger Band Squeeze Detection
This is one of the most valuable applications of Keltner Channels:
- Calculate both Bollinger Bands and Keltner Channels simultaneously
- When Bollinger Bands fully contract inside the Keltner Channels -> Squeeze state
- When Bollinger Bands expand back outside the Keltner Channels -> Squeeze release
- The direction of the squeeze release indicates the trading direction
The squeeze signal only tells you that “volatility is about to expand,” but it does not directly tell you the direction. You need to combine it with price momentum (such as the MACD histogram or momentum indicators) to determine the breakout direction.
4.4 Dynamic Support / Resistance
- The upper band acts as dynamic resistance
- The lower band acts as dynamic support
- In trending markets, the middle line often serves as support/resistance during pullbacks
V. Advantages, Disadvantages, and Use Cases
Advantages
| Advantage | Description |
|---|---|
| More robust with ATR | ATR is less sensitive to extreme values than standard deviation, resulting in a smoother channel |
| Faster EMA response | Reflects price changes more quickly than SMA |
| Squeeze detection | Combined with Bollinger Bands, it produces high-value squeeze signals |
| Broadly applicable | Works for stocks, futures, forex, and cryptocurrencies |
Disadvantages
| Disadvantage | Description |
|---|---|
| More parameters | Three parameters (EMA period, ATR period, multiplier) increase tuning complexity |
| Trend lag | Based on EMA and ATR, it is still inherently a lagging indicator |
| False signals in ranging markets | May generate frequent false breakouts in sideways markets |
| Inconsistent implementations | ATR smoothing methods (EMA vs. RMA) may differ across platforms |
Use Cases
- Best suited for: Squeeze detection in combination with Bollinger Bands; trend direction identification
- Moderately suited for: As a filter in trend-following systems
- Not suited for: Standalone short-term trading entry signals
Comparison with Similar Indicators
| Indicator | Middle Line | Channel Width Basis | Characteristics |
|---|---|---|---|
| Keltner Channels | EMA | ATR | Smooth, stable |
| Bollinger Bands | SMA | Standard Deviation | More sensitive to volatility changes |
| Donchian Channels | Extreme value average | High/Low extremes | Simplest, staircase-shaped |
Parameter tuning tips:
- Default parameters (20, 10, 1.5) work well for most situations
- Increasing the ATR multiplier (e.g., 2.0) reduces false signals but delays entries
- Shortening the EMA period (e.g., 10) increases sensitivity but adds noise
- For squeeze detection, it is recommended to use the same middle line period (20) for both Bollinger Bands and Keltner Channels to ensure comparability