Insights/Insights & Articles/Elliott Wave + DeMark: Combining Classic and Modern Technical Analysis in Code

Elliott Wave + DeMark: Combining Classic and Modern Technical Analysis in Code

Elliott Wave is from the 1930s. DeMark showed up sixty years later. Completely different philosophies – one counts waves, the other counts sequential price exhaustion. They shouldn't work together. But they do. Absurdly well, actually.

Mar 18, 2026

Elliott Wave + DeMark: Combining Classic and Modern Technical Analysis in Code

Elliott Wave + DeMark: Combining Classic and Modern Technical Analysis in Code

Elliott Wave is from the 1930s. DeMark showed up sixty years later. Completely different philosophies – one counts waves, the other counts sequential price exhaustion. They shouldn't work together.

But they do. Absurdly well, actually.

We spent three months wiring these two systems together, and the conflict resolution logic alone cut our false entry rate nearly in half during backtesting. That's not marketing – that's what the equity curve showed us. Here's the full production EA, with MQL4 source and a Pine Script port.

The Architecture

Dead simple principle at the core: two independent signals must agree before entry.

ENTRY CONDITION:

  • (Elliott Wave bias = LONG OR DeMark signal = BUY) AND
  • NOT (conflicting opposite signal is active)

One signal alone can trigger a trade – but only if the other system isn't screaming the opposite direction. We call it "agree or sit on your hands" logic. And it's wrapped inside a risk management shell that's honestly stricter than most traders would tolerate.

Signal 1: ZigZag-Based Elliott Wave Detection

Elliott Wave traders who just eyeball it drive us crazy. We didn't avoid it – we got practical. Instead of encoding Ralph Nelson Elliott's complete rule set, we use a simplified pivot analysis via the ZigZag indicator. It's not purist. It works.

Extracting Pivots from ZigZag

int GetZigZagPivots(double &prices[], int &times[], int maxPivots)
{
    ArraySetAsSeries(prices, false);
    ArraySetAsSeries(times, false);
    int scanned = MathMin(200, Bars - 1);
    int count = 0;
 
    for (int i = scanned; i >= 1 && count < maxPivots; i--)
    {
        double zLow  = iCustom(NULL, 0, "ZigZag",
                               ZigZagDepth, ZigZagDeviation, ZigZagBackstep,
                               1, i);
        double zHigh = iCustom(NULL, 0, "ZigZag",
                               ZigZagDepth, ZigZagDeviation, ZigZagBackstep,
                               0, i);
        double val = (zLow == 0 ? zHigh : zLow);
        if (val != 0)
        {
            ArrayResize(prices, count + 1);
            ArrayResize(times, count + 1);
            prices[count] = val;
            times[count]  = (int)Time[i];
            count++;
        }
    }
    ReverseArrayD(prices, count);
    ReverseArrayI(times, count);
    return count;
}

ZigZag defaults that took us weeks: Depth = 12, Deviation = 5, Backstep = 3.

From Pivots to Wave Count

int DetectElliottApprox()
{
    double pivPrice[];
    int pivTime[];
    int pivCount = GetZigZagPivots(pivPrice, pivTime, 10);
    if (pivCount < 5) return 0;
 
    int last = pivCount - 1;
    int upDownCount = 0;
    for (int i = last; i > last - 4; i--)
    {
        double diff = pivPrice[i] - pivPrice[i - 1];
        if (diff > 0) upDownCount++;
        else          upDownCount--;
    }
    if (upDownCount >= 4)  return +1;  // long bias
    if (upDownCount <= -4) return -1;  // short bias
    return 0;
}

Grab the last 5 ZigZag pivots → 4 price movements. If all 4 swing the same direction? Strong impulse wave. That's your Elliott approximation.

Signal 2: DeMark via iCustom

We pull the DeMark signal from an external custom indicator through iCustom() – probably the most underrated function in MQL4:

int DeMarkSignal()
{
    double val = iCustom(Symbol(), 0, DeMarkIndicatorName,
                         DeMarkBufferIndex, 0);
    if (val != EMPTY_VALUE && val != 0)
    {
        if (val > 0.5)   return +1;   // buy signal
        if (val < -0.5)  return -1;   // sell signal
    }
    return 0;
}

DeMark fires when a trend is exhausted. Elliott reads trend continuation. When both say "go long"? You're catching a pullback that's done pulling back. That's the sweet spot.

The Conflict Resolution Engine

When you mash two independent signal systems together, you get four possible states. How you handle conflicts matters more than the signals themselves:

ElliottDeMarkAction
LongBuyStrong Buy – both agree
LongNoneBuy (single signal, no conflict)
NoneBuyBuy (single signal, no conflict)
LongSellNo Trade – conflict
ShortBuyNo Trade – conflict
ShortSellStrong Sell – both agree
bool ConflictingShortSignal(bool deMarkShort, bool elliottShort)
{
    if (deMarkShort && elliottShort) return true;
    if (deMarkShort && UseDeMark && UseElliott && !elliottShort) return true;
    return false;
}

The EA is more afraid of conflict than it is hungry for confirmation. One opposing signal? Veto. Hard stop. No trade. The veto logic isn't conservative. It's survival.

The Risk Management Shell

Before any order fires, the EA runs six pre-checks. Fail any single one and the bar gets skipped:

void OnTick()
{
    static datetime lastTickTime = 0;
    if (Time[0] == lastTickTime) return;
    lastTickTime = Time[0];
 
    if (!IsTradingHour())     return;
    if (!IsAllowedWeekday())  return;
    if (SpreadInPoints() > MaxSpreadPoints) return;
    if (AccountFreeMargin() < MinimumAccountFreeMargin) return;
    if (ExceededDrawdown())   return;
    if (CountSymbolTradesToday() >= MaxTradesPerDay) return;
 
    // ... then compute signals and enter
}

ATR-Based Position Sizing

SL = ATR × 1.5, TP = SL × 2.0 – 1:2 risk-reward. Lot size from risk % so volatile markets → wider stops → smaller lots automatically.

double LotsByRisk(double riskPct, double slPrice)
{
    double riskMoney = AccountBalance() * riskPct / 100.0;
    if (Bid == 0 || Ask == 0)
        return MarketInfo(Symbol(), MODE_MINLOT);
    double pipValue = MarketInfo(Symbol(), MODE_TICKVALUE);
    double slPips   = MathAbs((Ask - slPrice) / Point);
    if (pipValue <= 0) pipValue = 1.0;
    double lots = riskMoney / (slPips * pipValue);
    double step = MarketInfo(Symbol(), MODE_LOTSTEP);
    int steps = (int)(lots / step);
    return NormalizeDouble(steps * step, 2);
}

Tuning the Parameters

ParameterDefaultPurposeTuning Guidance
ZigZagDepth12Pivot sensitivityLower = more pivots, noisier. Higher = fewer, smoother
ATRPeriod14Volatility window14 for H1, 20 for H4, 10 for M15
ATRMultiplierSL1.5Stop width1.0 = tight. 2.0 = wide
TP_Multiplier2.0Risk:reward1.5 = more wins. 3.0 = larger payoff
MaxTradesPerDay3Overtrading limitLower in chop, raise in trending
MaxDailyDrawdownPct10%Circuit breaker5% conservative, 15% aggressive
RiskPercent1.0%Per-trade risk0.5% beginners, 2% max experienced

What Makes This Strategy Work

  1. Signal diversity – Elliott (trend continuation) + DeMark (exhaustion). When both agree, you're catching the moment a pullback ends and the trend resumes.
  2. Asymmetric conflict resolution – Any conflict = no trade. You'll miss valid entries. Accounts don't die from missed winners; they die from false signals.
  3. Volatility-adaptive risk – ATR stops + % lot sizing = the system tunes itself.
  4. Defense in depth – Six pre-checks before any signal evaluation: trading hours, weekday, spread, margin, drawdown, daily trade count.

Build a Multi-Signal Strategy

Elliott + DeMark is just one combination. The architecture pattern – independent signals, conflict resolution, risk shell – works with almost any pair of indicators. RSI + Bollinger. MACD + Ichimoku.

Design Your Multi-Signal System →

Algorithmic Trading Insights by Quantumcona.

2of4