Trading Strategystockscryptofutures
Squeeze Momentum Strategy
BB inside Keltner = squeeze; enter long/short when momentum turns positive/negative. Stop at band midline; exit on momentum reversal.
What is Squeeze Momentum?
Bollinger Bands inside Keltner = squeeze; when momentum histogram turns positive (from neutral/negative) enter long; when it turns negative enter short. Stop near band midline; exit on histogram reversal or ATR trail. Uses linear regression slope as momentum. Works on stocks, crypto, and futures.
Strategy Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| bbPeriod | number | 20 | Bollinger Bands period |
| bbStdDev | number | 2 | BB standard deviation |
| keltnerPeriod | number | 20 | Keltner period |
| keltnerMult | number | 1.5 | Keltner ATR multiplier |
| atrPeriod | number | 14 | ATR period |
| momPeriod | number | 12 | Momentum (linear regression) period |
Use Cases
- ✓Squeeze (BB inside Keltner) then momentum
- ✓Momentum histogram direction
- ✓Stop at band midline
- ✓Stocks, crypto, futures
Strategy Script (JavaScript)
This strategy runs in VaultCharts using the built-in strategy engine. Below is the full script in a readable format. You can copy it or run it directly in VaultCharts.
strategy.jsVaultCharts built-in
module.exports = {
meta: {
name: "Squeeze Momentum",
params: {
bbPeriod: { type: "number", default: 20 },
bbStdDev: { type: "number", default: 2 },
keltnerPeriod: { type: "number", default: 20 },
keltnerMult: { type: "number", default: 1.5 },
atrPeriod: { type: "number", default: 14 },
momPeriod: { type: "number", default: 12 }
}
},
compute: (data, params, utils) => {
const cleanData = data.filter(d =>
d && Number.isFinite(d.high) && Number.isFinite(d.low) && Number.isFinite(d.close) &&
Number.isFinite(d.open) && Number.isFinite(d.time) && d.high >= d.low && d.close > 0
);
if (!cleanData || cleanData.length < 80) return { signals: [] };
const { technicalindicators: TI } = utils;
if (!TI || !TI.BollingerBands || !TI.ATR) return { signals: [] };
const bbPeriod = params?.bbPeriod ?? 20;
const bbStdDev = params?.bbStdDev ?? 2;
const kPeriod = params?.keltnerPeriod ?? 20;
const kMult = params?.keltnerMult ?? 1.5;
const atrPeriod = params?.atrPeriod ?? 14;
const momPeriod = params?.momPeriod ?? 12;
const closes = cleanData.map(d => d.close);
const highs = cleanData.map(d => d.high);
const lows = cleanData.map(d => d.low);
const bb = TI.BollingerBands.calculate({ period: bbPeriod, values: closes, stdDev: bbStdDev });
const atr = TI.ATR.calculate({ high: highs, low: lows, close: closes, period: atrPeriod });
const ema = [];
const keltnerUpper = [];
const keltnerLower = [];
const alpha = 2 / (kPeriod + 1);
let emaVal = closes[0];
for (let i = 0; i < cleanData.length; i++) {
emaVal = i === 0 ? closes[0] : alpha * closes[i] + (1 - alpha) * emaVal;
ema.push(emaVal);
const atrIdx = Math.min(i, atr.length - 1);
const atrVal = Number.isFinite(atr[atrIdx]) ? atr[atrIdx] : 0;
keltnerUpper.push(emaVal + kMult * atrVal);
keltnerLower.push(emaVal - kMult * atrVal);
}
const momentum = [];
for (let i = 0; i < cleanData.length; i++) {
if (i < momPeriod) {
momentum.push(0);
} else {
const linReg = (function(x, y) {
let n = x.length, sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
for (let j = 0; j < n; j++) {
sumX += x[j]; sumY += y[j]; sumXY += x[j]*y[j]; sumX2 += x[j]*x[j];
}
const slope = (n*sumXY - sumX*sumY) / (n*sumX2 - sumX*sumX);
return slope;
})(Array.from({length: momPeriod}, (_, j) => j), closes.slice(i - momPeriod, i));
momentum.push(linReg);
}
}
const signals = [];
let position = null;
let wasInSqueeze = false;
for (let i = Math.max(bbPeriod, kPeriod, atrPeriod, momPeriod); i < cleanData.length; i++) {
const candle = cleanData[i];
const bbIdx = i - (bbPeriod - 1);
if (bbIdx < 0 || bbIdx >= bb.length) continue;
const bbCurr = bb[bbIdx];
if (!bbCurr || !Number.isFinite(bbCurr.upper) || !Number.isFinite(bbCurr.lower)) continue;
const bbInsideKeltner = bbCurr.upper <= keltnerUpper[i] && bbCurr.lower >= keltnerLower[i];
const inSqueeze = bbInsideKeltner;
const momCurr = momentum[i];
const momPrev = i > 0 ? momentum[i - 1] : 0;
const momTurnedGreen = Number.isFinite(momCurr) && Number.isFinite(momPrev) && momCurr > 0 && momPrev <= 0;
const momTurnedRed = Number.isFinite(momCurr) && Number.isFinite(momPrev) && momCurr < 0 && momPrev >= 0;
const mid = (bbCurr.upper + bbCurr.lower) / 2;
if (position === 'long') {
if (momTurnedRed || candle.close < mid * 0.998) {
signals.push({ type: "exit", direction: "long", time: candle.time, price: candle.close, index: i });
position = null;
}
continue;
}
if (position === 'short') {
if (momTurnedGreen || candle.close > mid * 1.002) {
signals.push({ type: "exit", direction: "short", time: candle.time, price: candle.close, index: i });
position = null;
}
continue;
}
if (wasInSqueeze && momTurnedGreen && candle.close > mid) {
signals.push({ type: "entry", direction: "long", time: candle.time, price: candle.close, index: i });
position = 'long';
}
if (wasInSqueeze && momTurnedRed && candle.close < mid) {
signals.push({ type: "entry", direction: "short", time: candle.time, price: candle.close, index: i });
position = 'short';
}
wasInSqueeze = inSqueeze;
}
return { signals };
}
};Run Squeeze Momentum in VaultCharts
VaultCharts includes this strategy as a built-in option. Backtest it, adjust parameters, and use it on your own data—all stored locally on your device.