Bollinger Bands Strategy

A volatility-based strategy that trades breakouts and mean reversion using Bollinger Bands.

Strategy Overview

AspectDetails
TypeVolatility / Mean Reversion
IndicatorsBollinger Bands, RSI
Timeframes15m, 1h, 4h
MarketsAll (Spot & Futures)
DifficultyIntermediate

How It Works

Two modes of operation:

Squeeze Breakout Mode

  1. Wait for Bollinger Band squeeze (low volatility)
  2. Enter when price breaks out of squeeze
  3. Ride the momentum move

Mean Reversion Mode

  1. Buy when price touches lower band
  2. Sell when price touches upper band
  3. 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

ParameterTypeDefaultDescription
STRATEGY_MODEStringmean_reversion'mean_reversion', 'breakout', or 'both'
SQUEEZE_THRESHOLDNumber0.02BB width threshold for squeeze
USE_RSI_FILTERBooleantrueRequire RSI confirmation
RSI_OVERSOLDNumber30RSI level for oversold
RSI_OVERBOUGHTNumber70RSI level for overbought
STOP_LOSS_PCTNumber2Stop loss percentage
TAKE_PROFIT_PCTNumber4Take profit percentage
POSITION_SIZENumber100Position size (%)
ENABLE_SHORTSBooleanfalseAllow 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

  1. Mean reversion: Works best in ranging, choppy markets
  2. Breakout: Works best after periods of consolidation
  3. RSI filter: Reduces false signals at band touches
  4. Band width: Monitor for squeeze setups (width < 0.02)

Next Steps