VaultCharts
Trading Strategystocksforexcrypto

Regime Trend Momentum Strategy

Trades strong trends using ADX regime filtering, EMA trend bias, and MACD momentum confirmation.

What is Regime Trend Momentum?

Institutional trend logic using ADX regime filtering, EMA trend bias, and MACD momentum confirmation. Only trades when market is trending (ADX > 20) and momentum confirms continuation. Long on MACD histogram cross up with fast EMA > slow EMA; short on cross down with bearish EMA alignment. Exits on MACD reversal or trend flip.

Strategy Parameters

ParameterTypeDefaultDescription
adxPeriodnumber14ADX period
emaFastnumber50Fast EMA
emaSlownumber200Slow EMA

Use Cases

  • Trending markets only (ADX > 20)
  • MACD + EMA alignment
  • Avoid ranging markets
  • Stocks, forex, crypto

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: "Regime Trend Momentum",
    params: {
      adxPeriod: { type: "number", default: 14 },
      emaFast: { type: "number", default: 50 },
      emaSlow: { type: "number", default: 200 }
    }
  },
  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 < 250) {
      console.warn('[Regime Trend Momentum] Insufficient data:', cleanData?.length || 0);
      return { signals: [] };
    }
    
    const { technicalindicators: TI } = utils;
    if (!TI || !TI.EMA || !TI.ADX || !TI.MACD) {
      console.warn('[Regime Trend Momentum] Technical indicators not available');
      return { signals: [] };
    }
    
    const adxPeriod = params?.adxPeriod ?? 14;
    const emaFast = params?.emaFast ?? 50;
    const emaSlow = params?.emaSlow ?? 200;
    
    const closes = cleanData.map(d => d.close);
    const highs = cleanData.map(d => d.high);
    const lows = cleanData.map(d => d.low);
    
    const emaFastValues = TI.EMA.calculate({ period: emaFast, values: closes });
    const emaSlowValues = TI.EMA.calculate({ period: emaSlow, values: closes });
    const adxValues = TI.ADX.calculate({ high: highs, low: lows, close: closes, period: adxPeriod });
    const macdResult = TI.MACD.calculate({ 
      values: closes, 
      fastPeriod: 12, 
      slowPeriod: 26, 
      signalPeriod: 9, 
      SimpleMAOscillator: false, 
      SimpleMASignal: false 
    });
    
    // Calculate MACD histogram if not present
    const macdWithHistogram = macdResult.map(m => {
      if (m && typeof m === 'object') {
        const macdVal = m.MACD !== undefined ? m.MACD : (m.macd !== undefined ? m.macd : null);
        const signalVal = m.signal !== undefined ? m.signal : (m.Signal !== undefined ? m.Signal : null);
        const hist = m.histogram !== undefined ? m.histogram : (m.Histogram !== undefined ? m.Histogram : null);
        // Calculate histogram if missing: MACD - signal
        const calculatedHist = (macdVal !== null && signalVal !== null && Number.isFinite(macdVal) && Number.isFinite(signalVal)) 
          ? (macdVal - signalVal) 
          : hist;
        return { MACD: macdVal, signal: signalVal, histogram: calculatedHist };
      }
      return m;
    });
    
    console.log('[Regime Trend Momentum] Indicator lengths:', {
      dataLength: cleanData.length,
      emaFastLength: emaFastValues.length,
      emaSlowLength: emaSlowValues.length,
      adxLength: adxValues.length,
      macdLength: macdWithHistogram.length
    });
    
    const signals = [];
    
    // Use MACD as base since it's most restrictive for signals (needs histogram)
    const macdStartOffset = 26 - 1; // MACD slow period is 26
    
    // Iterate over MACD array indices
    let checkedCount = 0;
    let skippedCount = 0;
    
    for (let i = 1; i < macdWithHistogram.length; i++) {
      const dataIdx = i + macdStartOffset; // Derive data index from MACD
      
      if (dataIdx >= cleanData.length) break;
      
      const candle = cleanData[dataIdx];
      if (!candle || candle.time === undefined) continue;
      
      // Align other indicators to this dataIdx
      const adxIdx = dataIdx - (adxPeriod - 1);
      const emaFastIdx = dataIdx - (emaFast - 1);
      const emaSlowIdx = dataIdx - (emaSlow - 1);
      const macdIdx = i; // Already aligned (MACD base)
      
      // Bounds check
      if (adxIdx < 0 || emaFastIdx < 0 || emaSlowIdx < 0 || macdIdx < 1) {
        skippedCount++;
        continue;
      }
      if (adxIdx >= adxValues.length || emaFastIdx >= emaFastValues.length || 
          emaSlowIdx >= emaSlowValues.length || macdIdx >= macdWithHistogram.length) {
        skippedCount++;
        continue;
      }
      
      // Validate ADX
      const adxValue = adxValues[adxIdx];
      let adxNum = null;
      if (adxValue) {
        if (typeof adxValue === 'object' && 'adx' in adxValue) {
          adxNum = Number.isFinite(adxValue.adx) ? adxValue.adx : null;
        } else if (typeof adxValue === 'number') {
          adxNum = Number.isFinite(adxValue) ? adxValue : null;
        }
      }
      if (adxNum === null) {
        skippedCount++;
        continue;
      }
      
      // Validate EMA values
      const emaFastVal = emaFastValues[emaFastIdx];
      const emaSlowVal = emaSlowValues[emaSlowIdx];
      if (!Number.isFinite(emaFastVal) || !Number.isFinite(emaSlowVal)) {
        skippedCount++;
        continue;
      }
      
      // Validate MACD histogram
      const macdPrev = macdWithHistogram[macdIdx - 1];
      const macdCurr = macdWithHistogram[macdIdx];
      if (!macdPrev || !macdCurr || 
          !Number.isFinite(macdPrev.histogram) || !Number.isFinite(macdCurr.histogram)) {
        skippedCount++;
        continue;
      }
      
      checkedCount++;
      
      const trending = adxNum > 20;
      const bullishTrend = emaFastVal > emaSlowVal;
      const bearishTrend = emaFastVal < emaSlowVal;
      
      const histPrev = macdPrev.histogram;
      const histCurr = macdCurr.histogram;
      
      const macdCrossUp = histPrev <= 0 && histCurr > 0;
      const macdCrossDown = histPrev >= 0 && histCurr < 0;
      
      // 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 && trending && bullishTrend && macdCrossUp) {
        signals.push({ type: "entry", direction: "long", time: candle.time, price: candle.close, index: dataIdx });
      }
      if (!inLongPosition && !inShortPosition && trending && bearishTrend && macdCrossDown) {
        signals.push({ type: "entry", direction: "short", time: candle.time, price: candle.close, index: dataIdx });
      }
      
      // Exit signals
      if (inLongPosition && (!bullishTrend || macdCrossDown)) {
        signals.push({ type: "exit", direction: "long", time: candle.time, price: candle.close, index: dataIdx });
      }
      if (inShortPosition && (!bearishTrend || macdCrossUp)) {
        signals.push({ type: "exit", direction: "short", time: candle.time, price: candle.close, index: dataIdx });
      }
    }
    
    console.log('[Regime Trend Momentum] Analysis:', {
      checkedCount,
      skippedCount,
      signalsGenerated: signals.length
    });
    
    return { signals };
  }
};

Run Regime Trend Momentum 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.

Related Strategies

Explore More