Momentum Squeeze Breakout Trend Tracking Strategy

Author: ChaoZhang, Date: 2023-11-13 17:46:01



This strategy is based on LazyBear’s Squeeze Momentum indicator, combining Bollinger Bands and Keltner Channels to identify price breakouts from channel compression and expansion to determine potential trend direction of prices, and adopts a trend following approach to decide entry direction. The advantage of this strategy is making full use of momentum indicator’s ability to identify potential trends, and setting multiple condition filters to control signal quality which can effectively filter out uncertain signals and avoid over-trading during ranging markets.

Strategy Logic

  1. Calculate middle band, upper band and lower band of Bollinger Bands. Middle band is n-day simple moving average of close price, upper and lower bands are middle band plus/minus m times n-day standard deviation of close price.

  2. Calculate middle line, upper line and lower line of Keltner Channels. Middle line is n-day simple moving average of close price, upper and lower lines are middle line plus/minus m times n-day simple moving average of true range.

  3. Determine if price breaks through upper or lower band of Bollinger Bands and Keltner Channels to form compression and expansion patterns. Compression forms when price breaks down through lower band, while expansion forms when price breaks up through upper band.

  4. Calculate value of Linear Regression curve as momentum indicator. Upcrossing 0 is buy signal while downcrossing 0 is sell signal.

  5. Combine compression/expansion patterns, momentum direction, mean filtering and other conditions to determine final trading signals. Signals are only triggered when all conditions are met to avoid bad trades.

Advantages of the Strategy

  1. Using double filtration of Bollinger Bands and Keltner Channels to identify quality compression and expansion patterns.

  2. Momentum indicator can timely capture price trend reversals, complementing channel indicators.

  3. Allow early entry to increase profit opportunities.

  4. Adopt multiple condition判断 to avoid over-trading during ranging markets.

  5. Technical indicator parameters are customizable, adapting to different products and parameter combinations.

  6. Backtest time frame can be set to optimize over specific periods.

Risks of the Strategy

  1. Trend following strategies are prone to losses when trend reverses.

  2. Improper parameter settings may lead to over-trading or poor signal quality.

  3. Reliance on historical data cannot guarantee stable future returns.

  4. Unable to handle market turbulence and drastic price swings caused by black swan events.

  5. Improper backtest time window settings may lead to overfitting.

Optimization Directions

  1. Optimize parameters of Bollinger Bands and Keltner Channels to find best combination.

  2. Test adding trailing stop loss to control maximum loss per trade.

  3. Attempt further optimizations for specific products and period/parameter combinations.

  4. Explore integrating machine learning models to 判断 trend reversals.

  5. Test different entry sequencing and position sizing strategies.

  6. Research how to identify trend reversal signals and exit in time.


This strategy integrates multiple technical indicators to 判断 price trend direction and follow the trend, having relatively strong adaptability. By customizing parameters and using multiple condition filters, it can effectively control trading frequency and improve signal quality. But reversal trades and black swan events should still be watched out for. Further exploring trend reversal signals and risk control mechanisms can be done to make the strategy more robust.

start: 2022-11-06 00:00:00
end: 2023-11-12 00:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT"}]

//Strategy based on LazyBear Squeeze Momentum Indicator
//I added some custom feature and filters
// @author LazyBear
// List of all my indicators:
// v2 - fixed a typo, where BB multipler was always stuck at 1.5. [Thanks @ucsgears]
strategy(shorttitle = "SQZMOM_LB", title="Strategy for Squeeze Momentum Indicator [LazyBear]", overlay=false, calc_on_every_tick=true, pyramiding=0,default_qty_type=strategy.percent_of_equity,default_qty_value=100,currency=currency.USD)

length = input(14, title="BB Length")
mult = input(2.0,title="BB MultFactor")
lengthKC=input(16, title="KC Length")
multKC = input(1.5, title="KC MultFactor")
useTrueRange = input(true, title="Use TrueRange (KC)", type=bool)

useExtremeOrders  = input(false, title="Early entry on momentum change", type=bool)
useMomAverage = input(false, title="Filter for Momenutum value", type=bool)
MomentumMin = input(20, title="Min for momentum")

// Calculate BB
src = close
basis = sma(src, length)
dev = mult * stdev(src, length)
upperBB = basis + dev
lowerBB = basis - dev
// Calculate KC
ma = sma(src, lengthKC)
range = useTrueRange ? tr : (high - low)
rangema = sma(range, lengthKC)
upperKC = ma + rangema * multKC
lowerKC = ma - rangema * multKC
sqzOn  = (lowerBB > lowerKC) and (upperBB < upperKC)
sqzOff = (lowerBB < lowerKC) and (upperBB > upperKC)
noSqz  = (sqzOn == false) and (sqzOff == false)
val = linreg(src  -  avg(avg(highest(high, lengthKC), lowest(low, lengthKC)),sma(close,lengthKC)), lengthKC,0)
bcolor = iff( val > 0,            iff( val > nz(val[1]), lime, green),            iff( val < nz(val[1]), red, maroon))
scolor = noSqz ? blue : sqzOn ? black : aqua
plot(val, color=bcolor, style=histogram, linewidth=4)
plot(0, color=scolor, style=cross, linewidth=2)

//momentum filter

//standard condition
longCondition = scolor[1]!=aqua and scolor==aqua and bcolor==lime and filterMom
exitLongCondition = bcolor==green and not useExtremeOrders
shortCondition = scolor[1]!=aqua and scolor==aqua and bcolor==red and filterMom
exitShortCondition = bcolor==maroon and not useExtremeOrders

//early entry
extremeLong= useExtremeOrders and scolor==aqua and bcolor==maroon and bcolor[1]!=bcolor[0] and filterMom
exitExtLong = scolor==black or bcolor==red
extremeShort = useExtremeOrders and scolor==aqua and bcolor==green and bcolor[1]!=bcolor[0] and filterMom
exitExtShort = scolor==black or bcolor==lime


strategy.entry("SQ_Long", strategy.long, when = longCondition)
strategy.close("SQ_Long",when = exitLongCondition )

strategy.entry("SQ_Long_Ext", strategy.long, when = extremeLong)
strategy.close("SQ_Long_Ext",when = exitExtLong)
//strategy.exit("exit Long", "SQ_Long", when = exitLongCondition)

strategy.entry("SQ_Short", strategy.short, when = shortCondition)
strategy.close("SQ_Short",when = exitShortCondition)

strategy.entry("SQ_Short_Ext", strategy.short, when = extremeShort)
strategy.close("SQ_Short_Ext",when = exitExtShort)
//strategy.exit("exit Short", "SQ_Short", when = exitShortCondition)

// // === Backtesting Dates === thanks to Trost

// testPeriodSwitch = input(true, "Custom Backtesting Dates")
// testStartYear = input(2018, "Backtest Start Year")
// testStartMonth = input(1, "Backtest Start Month")
// testStartDay = input(1, "Backtest Start Day")
// testStartHour = input(0, "Backtest Start Hour")
// testPeriodStart = timestamp(testStartYear,testStartMonth,testStartDay,testStartHour,0)
// testStopYear = input(2018, "Backtest Stop Year")
// testStopMonth = input(12, "Backtest Stop Month")
// testStopDay = input(14, "Backtest Stop Day")
// testStopHour = input(23, "Backtest Stop Hour")
// testPeriodStop = timestamp(testStopYear,testStopMonth,testStopDay,testStopHour,0)
// testPeriod() =>
//     time >= testPeriodStart and time <= testPeriodStop ? true : false
// isPeriod = testPeriodSwitch == true ? testPeriod() : true
// // === /END

// if not isPeriod
//     strategy.cancel_all()
//     strategy.close_all()