EMA + Bollinger Bands Strategy for TradingView:
Working PineScript Code + Backtest
This strategy identifies periods of low volatility (Bollinger Band squeeze) and enters long when price breaks above the upper band while the EMA(20) is rising. The squeeze is detected when Bollinger Band width falls below a threshold, indicating consolidation. When the squeeze releases upward with EMA confirmation, it signals strong momentum. Exits occur when price closes below the EMA or when RSI reaches overbought levels as a secondary filter.
What is Exponential Moving Average with Bollinger Band Squeeze?
This strategy combines the Exponential Moving Average (EMA) with Bollinger Bands to identify momentum breakouts following periods of low volatility. The Bollinger Band squeeze occurs when the bands narrow, indicating consolidation. When price breaks out of the squeeze above the EMA, it signals a potential momentum move. The EMA(20) acts as a trend filter while Bollinger Bands(20,2) define the volatility envelope. This combination is particularly effective for capturing the start of new trending moves in equity indices.
| Parameter | Value |
|---|---|
| upper Band | SMA(20) + 2*StdDev |
| lower Band | SMA(20) - 2*StdDev |
| ema Trend | EMA(20) |
EMA + Bollinger Band Squeeze Breakout: The Setup
Entry Rules
- ▶Bollinger Band width must be below its 120-period SMA (squeeze detected)
- ▶Price closes above the upper Bollinger Band (breakout)
- ▶EMA(20) is rising (current EMA > EMA 2 bars ago)
- ▶Enter long on next bar open after conditions are met
Exit Rules
- ■Exit when price closes below the EMA(20)
- ■Exit when Bollinger Band width expands above 2x its average (momentum exhaustion)
- ■Stop-loss at the lower Bollinger Band at time of entry
Working PineScript Code
The script calculates EMA(20) and Bollinger Bands(20,2) using built-in functions. Bollinger Band width is normalized by dividing the band range by the middle band. A squeeze is detected when the current width is below its 120-period simple moving average. The entry triggers when price closes above the upper band during a squeeze while the EMA is rising (compared to 2 bars ago). Exits are managed through either a close below the EMA or when band width expands beyond 2x its average, signaling momentum exhaustion. A stop-loss is placed at the lower Bollinger Band at entry time.
//@version=5
strategy("EMA + BB Squeeze Breakout", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100)
// === INPUTS ===
emaLength = input.int(20, "EMA Length", minval=1)
bbLength = input.int(20, "BB Length", minval=1)
bbMult = input.float(2.0, "BB StdDev Multiplier", minval=0.5, step=0.1)
squeezeLookback = input.int(120, "Squeeze Lookback", minval=20)
exhaustionMult = input.float(2.0, "Exhaustion Width Multiplier", minval=1.0, step=0.1)
// === CALCULATIONS ===
emaValue = ta.ema(close, emaLength)
[bbMiddle, bbUpper, bbLower] = ta.bb(close, bbLength, bbMult)
// Bollinger Band Width
bbWidth = (bbUpper - bbLower) / bbMiddle
avgWidth = ta.sma(bbWidth, squeezeLookback)
// Squeeze detection
isSqueeze = bbWidth < avgWidth
emaRising = emaValue > emaValue[2]
// === ENTRY CONDITIONS ===
breakoutUp = close > bbUpper and isSqueeze[1] and emaRising
// === EXIT CONDITIONS ===
exitCondition = close < emaValue or bbWidth > avgWidth * exhaustionMult
// === STRATEGY EXECUTION ===
if breakoutUp
strategy.entry("BB Breakout", strategy.long)
strategy.exit("Stop Loss", "BB Breakout", stop=bbLower)
if exitCondition
strategy.close("BB Breakout")
// === PLOTTING ===
plot(emaValue, "EMA", color=color.orange, linewidth=2)
plot(bbUpper, "Upper BB", color=color.blue)
plot(bbMiddle, "Middle BB", color=color.gray, linestyle=line.style_dashed)
plot(bbLower, "Lower BB", color=color.blue)
bgcolor(isSqueeze ? color.new(color.yellow, 90) : na, title="Squeeze")
plotshape(breakoutUp, title="Breakout", location=location.belowbar, style=shape.triangleup, color=color.green, size=size.small)Backtest Results: QQQ (1D, 2020-2025)
Total Return
+42.3%
Annualized
+7.3%
Win Rate
55%
Max Drawdown
-18.1%
Sharpe Ratio
0.92
Total Trades
38
Profit Factor
1.45
Past performance does not guarantee future results. Backtest results may not reflect actual trading conditions including slippage, commissions, and liquidity constraints.
Optimization Tips
Add a volume filter requiring volume to be above its 20-period average on breakout bars. Squeeze breakouts on high volume are significantly more reliable. Use `volume > ta.sma(volume, 20)` as an additional entry condition.
Test different squeeze detection thresholds. Instead of comparing to the full average, try requiring width to be in the bottom 20th percentile of its lookback range using `ta.percentrank(bbWidth, squeezeLookback) < 20`.
Implement a trailing stop using ATR(14) instead of the fixed lower band stop. After price moves 1 ATR in profit, trail the stop at 1.5 ATR below the highest close. This protects profits on strong momentum moves.
Consider adding MACD histogram as a momentum confirmation. Require MACD histogram to be positive and increasing when entering. This filters out false breakouts that lack underlying momentum.
Adjust the EMA length based on the asset's volatility profile. For higher-volatility assets like leveraged ETFs (TQQQ), use a shorter EMA (10-12) to capture faster moves.
Common Mistakes with EMA + Bollinger Bands on TradingView
Entering during every Bollinger Band breakout without waiting for a proper squeeze. Not all upper band touches are breakouts — the squeeze condition is critical for filtering low-quality signals that occur in choppy markets.
Using the same parameters across all timeframes. Bollinger Band squeeze characteristics differ between daily and intraday charts. The squeeze lookback period should be scaled: 120 bars for daily, 240+ for hourly charts.
Ignoring the overall market regime. BB squeeze breakouts work best in trending markets. In prolonged sideways markets, many squeezes resolve with false breakouts. Consider adding a longer-term trend filter like the 200 SMA.
Setting the stop-loss too tight. The lower Bollinger Band stop works well for daily charts but may be too wide for intraday. For faster timeframes, consider using 1-1.5 ATR stops instead.
Build This Strategy in 60 Seconds with SpendDock
Instead of copying and debugging this code, describe your strategy in plain English. SpendDock generates the PineScript code, backtests it against real market data, and lets you iterate until it is right.
Try SpendDock Free