MACD Crossover Strategy

A trend-following strategy that trades MACD line and signal line crossovers.

Strategy Overview

AspectDetails
TypeTrend Following
IndicatorsMACD, Optional ADX
Timeframes1h, 4h, 1d
MarketsAll (Spot & Futures)
DifficultyIntermediate

How It Works

  1. Entry: Buy when MACD histogram crosses from negative to positive (bullish crossover)
  2. Exit: Close when MACD histogram crosses from positive to negative (bearish crossover)
  3. Filter: Optionally require ADX to confirm trend strength

Complete Code

// ============================================
// MACD CROSSOVER STRATEGY
// ============================================
// Trades MACD crossovers with optional trend
// strength confirmation using ADX.
// ============================================

// ============================================
// PARAMETERS
// ============================================
const USE_ADX_FILTER = td.config.getBoolean('USE_ADX_FILTER', true);
const ADX_THRESHOLD = td.config.getNumber('ADX_THRESHOLD', 25);
const STOP_LOSS_ATR_MULT = td.config.getNumber('STOP_LOSS_ATR_MULT', 2);
const TAKE_PROFIT_ATR_MULT = td.config.getNumber('TAKE_PROFIT_ATR_MULT', 4);
const POSITION_SIZE = td.config.getNumber('POSITION_SIZE', 100);
const ENABLE_SHORTS = td.config.getBoolean('ENABLE_SHORTS', false);

// ============================================
// VALIDATION
// ============================================
if (td.market.candles.length < 50) {
    td.utils.log('Waiting for sufficient candle data');
    return;
}

// ============================================
// CALCULATE INDICATORS
// ============================================
const macd = td.indicators.macd;
const adx = td.indicators.adx;
const atr = td.indicators.atr;
const price = td.market.price;

// Get previous histogram for crossover detection
const prevHistogram = td.state.get('prevHistogram', macd.histogram);

// Detect crossovers
const bullishCrossover = prevHistogram <= 0 && macd.histogram > 0;
const bearishCrossover = prevHistogram >= 0 && macd.histogram < 0;

// ============================================
// ADX TREND FILTER
// ============================================
let trendStrong = true;
let trendDirection = 'neutral';

if (USE_ADX_FILTER) {
    trendStrong = adx.value >= ADX_THRESHOLD;

    if (adx.diPlus > adx.diMinus) {
        trendDirection = 'bullish';
    } else if (adx.diMinus > adx.diPlus) {
        trendDirection = 'bearish';
    }
}

// ============================================
// ENTRY LOGIC
// ============================================
if (!td.position.hasPosition) {
    // LONG ENTRY
    if (bullishCrossover) {
        // Check trend filter
        if (USE_ADX_FILTER && (!trendStrong || trendDirection === 'bearish')) {
            td.utils.log('Bullish crossover filtered by ADX', {
                adxValue: adx.value,
                trendDirection: trendDirection
            });
        } else {
            const stopLoss = td.utils.round(price - (atr * STOP_LOSS_ATR_MULT), 2);
            const takeProfit = td.utils.round(price + (atr * TAKE_PROFIT_ATR_MULT), 2);

            td.trade.buy({
                amountPercent: POSITION_SIZE,
                stopLoss: stopLoss,
                takeProfit: takeProfit,
                reason: 'MACD bullish crossover'
            });

            td.utils.log('LONG ENTRY', {
                histogram: macd.histogram.toFixed(4),
                adx: adx.value.toFixed(2),
                atr: atr.toFixed(2),
                stopLoss: stopLoss,
                takeProfit: takeProfit
            });
        }
    }

    // SHORT ENTRY (if enabled)
    if (ENABLE_SHORTS && bearishCrossover) {
        if (USE_ADX_FILTER && (!trendStrong || trendDirection === 'bullish')) {
            td.utils.log('Bearish crossover filtered by ADX');
        } else {
            const stopLoss = td.utils.round(price + (atr * STOP_LOSS_ATR_MULT), 2);
            const takeProfit = td.utils.round(price - (atr * TAKE_PROFIT_ATR_MULT), 2);

            td.trade.sell({
                amountPercent: POSITION_SIZE,
                stopLoss: stopLoss,
                takeProfit: takeProfit,
                reason: 'MACD bearish crossover'
            });

            td.utils.log('SHORT ENTRY', {
                histogram: macd.histogram.toFixed(4),
                stopLoss: stopLoss,
                takeProfit: takeProfit
            });
        }
    }
}

// ============================================
// EXIT LOGIC
// ============================================
if (td.position.hasPosition) {
    const isLong = td.position.side === 'long';
    const isShort = td.position.side === 'short';

    // Exit long on bearish crossover
    if (isLong && bearishCrossover) {
        td.trade.close('MACD bearish crossover - exit long');
        td.utils.log('EXIT LONG', {
            pnl: td.position.pnl,
            pnlPercent: td.position.pnlPercent.toFixed(2) + '%'
        });
    }

    // Exit short on bullish crossover
    if (isShort && bullishCrossover) {
        td.trade.close('MACD bullish crossover - exit short');
        td.utils.log('EXIT SHORT', {
            pnl: td.position.pnl,
            pnlPercent: td.position.pnlPercent.toFixed(2) + '%'
        });
    }
}

// ============================================
// STATE UPDATE
// ============================================
td.state.set('prevHistogram', macd.histogram);

// ============================================
// DEBUG LOGGING
// ============================================
const DEBUG = td.config.getBoolean('DEBUG_MODE', false);
if (DEBUG) {
    td.utils.log('MACD State', {
        line: macd.line.toFixed(4),
        signal: macd.signal.toFixed(4),
        histogram: macd.histogram.toFixed(4),
        prevHistogram: prevHistogram.toFixed(4),
        bullishCrossover: bullishCrossover,
        bearishCrossover: bearishCrossover,
        adx: adx.value.toFixed(2),
        trendDirection: trendDirection
    });
}

Parameters

ParameterTypeDefaultDescription
USE_ADX_FILTERBooleantrueRequire ADX confirmation
ADX_THRESHOLDNumber25Minimum ADX for trend strength
STOP_LOSS_ATR_MULTNumber2Stop loss = ATR × multiplier
TAKE_PROFIT_ATR_MULTNumber4Take profit = ATR × multiplier
POSITION_SIZENumber100Position size (% of balance)
ENABLE_SHORTSBooleanfalseAllow short positions
DEBUG_MODEBooleanfalseEnable verbose logging

MACD Components

Understanding the MACD indicator:

MACD Line    = 12 EMA - 26 EMA
Signal Line  = 9 EMA of MACD Line
Histogram    = MACD Line - Signal Line
  • Histogram > 0: MACD above signal (bullish)
  • Histogram < 0: MACD below signal (bearish)
  • Crossover: When histogram changes sign

Recommended Settings

Conservative (Trend Following)

USE_ADX_FILTER: true
ADX_THRESHOLD: 30
STOP_LOSS_ATR_MULT: 2
TAKE_PROFIT_ATR_MULT: 4
ENABLE_SHORTS: false

Moderate

USE_ADX_FILTER: true
ADX_THRESHOLD: 25
STOP_LOSS_ATR_MULT: 1.5
TAKE_PROFIT_ATR_MULT: 3
ENABLE_SHORTS: false

Aggressive (Both Directions)

USE_ADX_FILTER: false
STOP_LOSS_ATR_MULT: 1
TAKE_PROFIT_ATR_MULT: 2
ENABLE_SHORTS: true

Performance Tips

  1. Higher timeframes: 4h and daily charts produce fewer but higher-quality signals
  2. ADX filter: Significantly reduces false signals in ranging markets
  3. ATR stops: Adapts to current market volatility
  4. Avoid choppy markets: MACD works best in trending conditions

Variations

With EMA Trend Filter

// Helper to get custom indicator value safely
function getIndicator(key) {
    const val = td.indicators.custom[key];
    if (val === undefined || val === null) return 0;
    if (Array.isArray(val)) return val[val.length - 1];
    return val;
}

// Only trade in direction of major trend
const ema50 = getIndicator('ema_50');

if (bullishCrossover && price > ema50) {
    // Long only above EMA
    td.trade.buy({ reason: 'MACD cross above EMA50' });
}

if (bearishCrossover && price < ema50) {
    // Short only below EMA
    td.trade.sell({ reason: 'MACD cross below EMA50' });
}

Histogram Divergence

// Detect divergence between price and MACD
const priceHigh = td.state.get('recentPriceHigh', price);
const macdHigh = td.state.get('recentMacdHigh', macd.histogram);

// Bearish divergence: price higher high, MACD lower high
if (price > priceHigh && macd.histogram < macdHigh) {
    td.utils.log('Bearish divergence detected');
    // Consider reducing position or tightening stops
}

// Update highs
if (price > priceHigh) td.state.set('recentPriceHigh', price);
if (macd.histogram > macdHigh) td.state.set('recentMacdHigh', macd.histogram);

Next Steps