Trading Strategycryptostocksforex
Volatility Expansion Breakout Strategy
Trades volatility expansion after prolonged compression with trend confirmation. Works well on crypto, equities, and forex.
What is Volatility Expansion Breakout?
Trades volatility expansion after prolonged compression with trend confirmation. Waits for compression (narrow Bollinger Bands + low ATR) then trades the breakout in the direction of the EMA trend. Exits when price crosses back to band middle or trend flips.
Strategy Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| bbPeriod | number | 20 | Bollinger Bands period |
| emaPeriod | number | 100 | EMA period for trend |
| atrPeriod | number | 14 | ATR period |
Use Cases
- ✓Consolidation breakouts
- ✓Trend-aligned expansion
- ✓BB width + ATR compression
- ✓EMA trend filter
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: "Volatility Expansion",
params: {
bbPeriod: { type: "number", default: 20 },
emaPeriod: { type: "number", default: 100 },
atrPeriod: { type: "number", default: 14 }
}
},
compute: (data, params, utils) => {
// Data sanitization
const cleanData = data.filter(d =>
d &&
Number.isFinite(d.high) &&
Number.isFinite(d.low) &&
Number.isFinite(d.close) &&
Number.isFinite(d.time) &&
d.high >= d.low &&
d.close > 0
);
if (!cleanData || cleanData.length < 150) {
console.warn('[Volatility Expansion] Insufficient data:', cleanData?.length || 0);
return { signals: [] };
}
const { technicalindicators: TI } = utils;
if (!TI || !TI.EMA || !TI.ATR) {
console.warn('[Volatility Expansion] Technical indicators not available');
return { signals: [] };
}
const bbPeriod = params?.bbPeriod ?? 20;
const emaPeriod = params?.emaPeriod ?? 100;
const atrPeriod = params?.atrPeriod ?? 14;
const closes = cleanData.map(d => d.close);
const highs = cleanData.map(d => d.high);
const lows = cleanData.map(d => d.low);
// BollingerBands - try multiple API forms
let bb;
try {
if (TI.BollingerBands && TI.BollingerBands.calculate) {
bb = TI.BollingerBands.calculate({ period: bbPeriod, values: closes, stdDev: 2 });
} else if (TI.BollingerBands && typeof TI.BollingerBands === 'function') {
const bbIndicator = new TI.BollingerBands({ period: bbPeriod, values: closes, stdDev: 2 });
bb = bbIndicator.getResult();
} else if (TI.BBANDS) {
bb = TI.BBANDS.calculate({ period: bbPeriod, values: closes, stdDev: 2 });
} else {
console.warn('[Volatility Expansion] BollingerBands not available');
return { signals: [] };
}
} catch (err) {
console.error('[Volatility Expansion] BollingerBands error:', err);
return { signals: [] };
}
const ema = TI.EMA.calculate({ period: emaPeriod, values: closes });
const atr = TI.ATR.calculate({ high: highs, low: lows, close: closes, period: atrPeriod });
console.log('[Volatility Expansion] Indicator lengths:', {
dataLength: cleanData.length,
bbLength: bb.length,
emaLength: ema.length,
atrLength: atr.length
});
const signals = [];
// Use EMA as base since it's the slowest indicator (100)
const emaStartOffset = emaPeriod - 1;
let checkedCount = 0;
let skippedCount = 0;
// Iterate over EMA array indices
for (let i = 1; i < ema.length; i++) {
const dataIdx = i + emaStartOffset; // Derive data index from EMA
if (dataIdx >= cleanData.length) break;
const candle = cleanData[dataIdx];
if (!candle || candle.time === undefined) continue;
// Align other indicators to this dataIdx
const bbIdx = dataIdx - (bbPeriod - 1);
const atrIdx = dataIdx - (atrPeriod - 1);
const emaIdx = i; // Already aligned (EMA base)
// Bounds check
if (bbIdx < 1 || atrIdx < 1 || emaIdx < 1) {
skippedCount++;
continue;
}
if (bbIdx >= bb.length || atrIdx >= atr.length || emaIdx >= ema.length) {
skippedCount++;
continue;
}
const bbCurr = bb[bbIdx];
const bbPrev = bb[bbIdx - 1];
if (!bbCurr || !bbPrev ||
!Number.isFinite(bbCurr.upper) || !Number.isFinite(bbCurr.lower) || !Number.isFinite(bbCurr.middle)) {
skippedCount++;
continue;
}
const atrCurr = atr[atrIdx];
const atrPrev = atr[atrIdx - 1];
if (!Number.isFinite(atrCurr) || !Number.isFinite(atrPrev)) {
skippedCount++;
continue;
}
const emaCurr = ema[emaIdx];
const emaPrev = ema[emaIdx - 1];
if (!Number.isFinite(emaCurr) || !Number.isFinite(emaPrev)) {
skippedCount++;
continue;
}
checkedCount++;
const width = bbCurr.upper - bbCurr.lower;
const prevWidth = bbPrev.upper - bbPrev.lower;
const compression = width < prevWidth && atrCurr < atrPrev;
const bullishBias = emaCurr > emaPrev;
const bearishBias = emaCurr < emaPrev;
const breakoutUp = candle.close > bbCurr.upper;
const breakoutDown = candle.close < bbCurr.lower;
// Get position state
const lastSignal = signals.length > 0 ? signals[signals.length - 1] : null;
const inLongPosition = lastSignal && lastSignal.type === 'entry' && lastSignal.direction === 'long';
const inShortPosition = lastSignal && lastSignal.type === 'entry' && lastSignal.direction === 'short';
// Entry signals
if (!inLongPosition && !inShortPosition && bullishBias && (compression || breakoutUp)) {
signals.push({ type: "entry", direction: "long", time: candle.time, price: candle.close, index: dataIdx });
}
if (!inLongPosition && !inShortPosition && bearishBias && (compression || breakoutDown)) {
signals.push({ type: "entry", direction: "short", time: candle.time, price: candle.close, index: dataIdx });
}
// Exit signals
if (inLongPosition && (candle.close < bbCurr.middle || !bullishBias)) {
signals.push({ type: "exit", direction: "long", time: candle.time, price: candle.close, index: dataIdx });
}
if (inShortPosition && (candle.close > bbCurr.middle || !bearishBias)) {
signals.push({ type: "exit", direction: "short", time: candle.time, price: candle.close, index: dataIdx });
}
}
console.log('[Volatility Expansion] Analysis:', {
checkedCount,
skippedCount,
signalsGenerated: signals.length
});
return { signals };
}
};Run Volatility Expansion Breakout 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.