Bollinger Bands Strategy
A volatility-based strategy that trades breakouts and mean reversion using Bollinger Bands.
Strategy Overview
| Aspect | Details |
|---|---|
| Type | Volatility / Mean Reversion |
| Indicators | Bollinger Bands, RSI |
| Timeframes | 15m, 1h, 4h |
| Markets | All (Spot & Futures) |
| Difficulty | Intermediate |
How It Works
Two modes of operation:
Squeeze Breakout Mode
- Wait for Bollinger Band squeeze (low volatility)
- Enter when price breaks out of squeeze
- Ride the momentum move
Mean Reversion Mode
- Buy when price touches lower band
- Sell when price touches upper band
- Profit from price returning to mean
Complete Code
// ============================================
// BOLLINGER BANDS STRATEGY
// ============================================
// Trades volatility squeezes and mean reversion
// using Bollinger Bands with RSI confirmation.
// ============================================
// ============================================
// PARAMETERS
// ============================================
const STRATEGY_MODE = td.config.get('STRATEGY_MODE', 'mean_reversion');
const SQUEEZE_THRESHOLD = td.config.getNumber('SQUEEZE_THRESHOLD', 0.02);
const USE_RSI_FILTER = td.config.getBoolean('USE_RSI_FILTER', true);
const RSI_OVERSOLD = td.config.getNumber('RSI_OVERSOLD', 30);
const RSI_OVERBOUGHT = td.config.getNumber('RSI_OVERBOUGHT', 70);
const STOP_LOSS_PCT = td.config.getNumber('STOP_LOSS_PCT', 2);
const TAKE_PROFIT_PCT = td.config.getNumber('TAKE_PROFIT_PCT', 4);
const POSITION_SIZE = td.config.getNumber('POSITION_SIZE', 100);
// ============================================
// VALIDATION
// ============================================
const validModes = ['mean_reversion', 'breakout', 'both'];
if (!validModes.includes(STRATEGY_MODE)) {
td.utils.log('ERROR: Invalid STRATEGY_MODE');
return;
}
if (td.market.candles.length < 30) {
td.utils.log('Waiting for sufficient candle data');
return;
}
// ============================================
// CALCULATE INDICATORS
// ============================================
const bb = td.indicators.bbands;
const rsi = td.indicators.rsi;
const price = td.market.price;
// Track squeeze state
const wasInSqueeze = td.state.get('inSqueeze', false);
const inSqueeze = bb.width < SQUEEZE_THRESHOLD;
// ============================================
// HELPER FUNCTIONS
// ============================================
function calculateTrade(side) {
const stopLoss = side === 'long'
? price * (1 - STOP_LOSS_PCT / 100)
: price * (1 + STOP_LOSS_PCT / 100);
const takeProfit = side === 'long'
? price * (1 + TAKE_PROFIT_PCT / 100)
: price * (1 - TAKE_PROFIT_PCT / 100);
return {
amountPercent: POSITION_SIZE,
stopLoss: td.utils.round(stopLoss, 2),
takeProfit: td.utils.round(takeProfit, 2)
openGraph: { title: 'Bollinger Bands Example', description: 'Example Bollinger Bands custom trading strategy.' },
};
}
// ============================================
// MEAN REVERSION LOGIC
// ============================================
function meanReversionLogic() {
if (td.position.hasPosition) return;
// Buy at lower band
if (price <= bb.lower) {
// Optional RSI confirmation
if (USE_RSI_FILTER && rsi > RSI_OVERSOLD) {
td.utils.log('Lower band touch filtered - RSI not oversold');
return;
}
const trade = calculateTrade('long');
trade.reason = `Mean reversion: price at lower BB (percentB: ${bb.percentB.toFixed(3)})`;
td.trade.buy(trade);
td.utils.log('MEAN REVERSION LONG', {
price: price,
lowerBand: bb.lower,
rsi: rsi.toFixed(2)
});
}
// Sell at upper band (if shorts enabled)
if (price >= bb.upper && td.config.getBoolean('ENABLE_SHORTS', false)) {
if (USE_RSI_FILTER && rsi < RSI_OVERBOUGHT) {
td.utils.log('Upper band touch filtered - RSI not overbought');
return;
}
const trade = calculateTrade('short');
trade.reason = `Mean reversion: price at upper BB`;
td.trade.sell(trade);
td.utils.log('MEAN REVERSION SHORT', {
price: price,
upperBand: bb.upper,
rsi: rsi.toFixed(2)
});
}
}
// ============================================
// BREAKOUT LOGIC
// ============================================
function breakoutLogic() {
if (td.position.hasPosition) return;
// Breakout from squeeze
if (wasInSqueeze && !inSqueeze) {
td.utils.log('Squeeze breakout detected', {
prevWidth: td.state.get('prevWidth'),
currentWidth: bb.width
});
// Determine breakout direction
if (price > bb.upper) {
const trade = calculateTrade('long');
trade.reason = 'Squeeze breakout UP';
td.trade.buy(trade);
td.utils.log('BREAKOUT LONG', { price: price, upperBand: bb.upper });
} else if (price < bb.lower && td.config.getBoolean('ENABLE_SHORTS', false)) {
const trade = calculateTrade('short');
trade.reason = 'Squeeze breakout DOWN';
td.trade.sell(trade);
td.utils.log('BREAKOUT SHORT', { price: price, lowerBand: bb.lower });
}
}
}
// ============================================
// EXIT LOGIC
// ============================================
function exitLogic() {
if (!td.position.hasPosition) return;
const isLong = td.position.side === 'long';
// Mean reversion exits at middle band
if (STRATEGY_MODE === 'mean_reversion' || STRATEGY_MODE === 'both') {
if (isLong && price >= bb.middle) {
td.trade.close('Price returned to middle band');
td.utils.log('EXIT at middle band', { pnl: td.position.pnl });
}
}
// Breakout exits when price re-enters bands
if (STRATEGY_MODE === 'breakout' || STRATEGY_MODE === 'both') {
if (isLong && bb.percentB < 0.8 && td.position.pnlPercent > 1) {
td.trade.close('Breakout momentum fading');
td.utils.log('EXIT momentum fade', { percentB: bb.percentB });
}
}
}
// ============================================
// MAIN STRATEGY
// ============================================
if (STRATEGY_MODE === 'mean_reversion' || STRATEGY_MODE === 'both') {
meanReversionLogic();
}
if (STRATEGY_MODE === 'breakout' || STRATEGY_MODE === 'both') {
breakoutLogic();
}
exitLogic();
// ============================================
// STATE UPDATE
// ============================================
td.state.set('inSqueeze', inSqueeze);
td.state.set('prevWidth', bb.width);
// ============================================
// DEBUG LOGGING
// ============================================
const DEBUG = td.config.getBoolean('DEBUG_MODE', false);
if (DEBUG) {
td.utils.log('BB State', {
upper: bb.upper.toFixed(2),
middle: bb.middle.toFixed(2),
lower: bb.lower.toFixed(2),
width: bb.width.toFixed(4),
percentB: bb.percentB.toFixed(3),
inSqueeze: inSqueeze,
price: price
});
}
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| STRATEGY_MODE | String | mean_reversion | 'mean_reversion', 'breakout', or 'both' |
| SQUEEZE_THRESHOLD | Number | 0.02 | BB width threshold for squeeze |
| USE_RSI_FILTER | Boolean | true | Require RSI confirmation |
| RSI_OVERSOLD | Number | 30 | RSI level for oversold |
| RSI_OVERBOUGHT | Number | 70 | RSI level for overbought |
| STOP_LOSS_PCT | Number | 2 | Stop loss percentage |
| TAKE_PROFIT_PCT | Number | 4 | Take profit percentage |
| POSITION_SIZE | Number | 100 | Position size (%) |
| ENABLE_SHORTS | Boolean | false | Allow short positions |
Bollinger Bands Components
Upper Band = SMA(20) + (2 × StdDev)
Middle Band = SMA(20)
Lower Band = SMA(20) - (2 × StdDev)
Band Width = (Upper - Lower) / Middle
%B = (Price - Lower) / (Upper - Lower)
- %B > 1: Price above upper band
- %B < 0: Price below lower band
- %B = 0.5: Price at middle band
Recommended Settings
Mean Reversion (Conservative)
STRATEGY_MODE: mean_reversion
USE_RSI_FILTER: true
RSI_OVERSOLD: 25
STOP_LOSS_PCT: 1.5
ENABLE_SHORTS: false
Breakout (Momentum)
STRATEGY_MODE: breakout
SQUEEZE_THRESHOLD: 0.015
USE_RSI_FILTER: false
STOP_LOSS_PCT: 2
TAKE_PROFIT_PCT: 6
Combined
STRATEGY_MODE: both
USE_RSI_FILTER: true
SQUEEZE_THRESHOLD: 0.02
Performance Tips
- Mean reversion: Works best in ranging, choppy markets
- Breakout: Works best after periods of consolidation
- RSI filter: Reduces false signals at band touches
- Band width: Monitor for squeeze setups (width < 0.02)
Next Steps
- Multi-Indicator Strategy - Combined approach
- Advanced Patterns - Complex strategies
- Best Practices - Guidelines