MACD Crossover Strategy
A trend-following strategy that trades MACD line and signal line crossovers.
Strategy Overview
| Aspect | Details |
|---|---|
| Type | Trend Following |
| Indicators | MACD, Optional ADX |
| Timeframes | 1h, 4h, 1d |
| Markets | All (Spot & Futures) |
| Difficulty | Intermediate |
How It Works
- Entry: Buy when MACD histogram crosses from negative to positive (bullish crossover)
- Exit: Close when MACD histogram crosses from positive to negative (bearish crossover)
- 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
| Parameter | Type | Default | Description |
|---|---|---|---|
| USE_ADX_FILTER | Boolean | true | Require ADX confirmation |
| ADX_THRESHOLD | Number | 25 | Minimum ADX for trend strength |
| STOP_LOSS_ATR_MULT | Number | 2 | Stop loss = ATR × multiplier |
| TAKE_PROFIT_ATR_MULT | Number | 4 | Take profit = ATR × multiplier |
| POSITION_SIZE | Number | 100 | Position size (% of balance) |
| ENABLE_SHORTS | Boolean | false | Allow short positions |
| DEBUG_MODE | Boolean | false | Enable 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
- Higher timeframes: 4h and daily charts produce fewer but higher-quality signals
- ADX filter: Significantly reduces false signals in ranging markets
- ATR stops: Adapts to current market volatility
- 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
- Bollinger Bands Strategy - Volatility-based approach
- Multi-Indicator Strategy - Combined signals
- Best Practices - Strategy guidelines