Author: Roberto Jacobs (3rjfx)
Discover how Newton’s Laws of Motion solve ZigZag repainting. Use the 30/70 momentum threshold to confirm trend reversals with precision in MQL5.
1. Introduction
In the world of technical analysis, the ZigZag indicator is legendary for its ability to filter market noise and highlight the true structure of the market—its peaks and troughs. However, every veteran trader knows its greatest weakness: it repaints. A confirmed low today can disappear tomorrow if the price continues to drop.
To solve this, I developed ZigZag Reverses, an indicator that doesn't just track price; it measures Momentum Energy based on the laws of physics.
2. The Newtonian Philosophy: Force and Inertia
My inspiration for this indicator comes from Sir Isaac Newton’s Laws of Motion. Imagine a ball being thrown against the floor. The initial force (the trend) creates a massive bounce. However, as gravity and friction take over, the energy dissipates.
In trading, when a price hits an extremum (High or Low), it possesses 100% of that trend's force. For a reversal to be mathematically and physically probable, that force must decay past a certain threshold. Through my observations, the 30% and 70% levels act as the Threshold of No Return.

3. How ZigZag Reverses Works
Unlike standard ZigZag, this indicator calculates two dynamic confirmation lines based on the most recent price swing:
- The 70% Fall Line (Highest to Fall): After a Peak is formed, we don't assume a reversal immediately. We wait for the price to drop below 70% of the previous swing's range. If the Close Price breaks this level, the bullish momentum is physically exhausted, confirming a Bearish Reversal.
- The 30% Rise Line (Lowest to Rise): Conversely, after a Trough, we wait for the price to climb above the 30% mark. Breaking this level confirms that the selling pressure has faded and a Bullish Reversal is in play.
4. Why Close Price Matters
The indicator is programmed to be objective. It creates a TrendLine on the chart to visualize these thresholds. As per my testing, if a bar closes beyond these lines, the probability of the trend continuing in the new direction increases significantly. It is no longer a guess—it is a confirmation of shifting market force.
5. Input Parameters Properties
6. Technical Specifications:
- Dynamic Thresholds: Customizable `updown` and `downup` percentages to suit different market volatilities.
- Visual Confirmation: Automatic horizontal lines marking the breakout zones.
- Clean Logic: Built with high-performance MQL5 code, ensuring zero lag in calculation.
7. Practical Strategy for Manual Traders
To maximize the effectiveness of the ZigZag Reverses indicator, manual traders can utilize a set-and-forget approach using Pending Orders. Once the ZigZag extremum is stabilized (no longer repainting), follow these steps:
- Bullish Confirmation: When a stable Extremum Low is identified, traders can place a BUY_STOP order exactly at the 30% Line. This ensures an entry only when the upward momentum is physically confirmed by the price breaking the threshold.
- Bearish Confirmation: When a stable Extremum High is identified, traders can place a SELL_STOP order exactly at the 70% Line. This ensures you are selling only when the price has officially "exhausted" its bullish force and crossed into the reversal zone.
8. Conclusion
Trading shouldn't be about catching falling knives; it should be about measuring the weight of the market. By applying the principles of Newtonian mechanics to the ZigZag structure, we transform a repainting tool into a reliable momentum gauge.
9. Vital Records
We hope this article and the ZigZag Reverses Indicator program will be useful for traders in learning and generating new ideas, thereby will be able improving your trading performance.
We hope you find our content useful and thank you for visiting the Forex Home Expert.
See you in the next article on Expert Advisor programs or indicators for MetaTrader 4, MetaTrader 5 or Python program and trading psychology.
Explore more algorithmic trading resources:
Note: This program is shared under a Copyleft/Creative Commons License (CCL) for educational purposes. There is no direct download link. Please follow the detailed steps below to install it manually in your MQL4/MQL5 MetaEditor.
📋 How to Install This Program (Manual Copy-Paste Guide)
Step-by-Step Instructions:
- Open your MQL5 MetaEditor (press F4 in MT5).
- Click "New" (or File > New) to create a new document.
- Select the program type:
- Choose Expert Advisor (template) if this is an EA.
- Choose Custom Indicator if this is an indicator.
- Enter the Name (e.g., ECN) and click "Next" twice.
- Click "Finish". A blank template page will be created.
- Scroll down and click the "Full Source Code Preview (CCL)" button below.
- Highlight the entire code in the preview box, right-click, and select Copy.
- Return to the blank MetaEditor page, Paste the code (Ctrl+V), replacing any default text.
- Click "Compile" (F7). Check the Toolbox panel: if there are 0 errors, your program is ready!
Summary: This section provides a manual copy-paste installation guide for MQL4/MQL5 Expert Advisors and Custom Indicators shared under CCL license. Users must open MetaEditor, create a new file, copy the source code from the preview section below, paste it into the editor, and compile the program to use it on MetaTrader 4/5 charts.
//+------------------------------------------------------------------+
//| ZigZag Reverses.mq5 |
//| Copyright 2026, Roberto Jacobs (3rjfx) ~ Date: 2026-01-30 |
//| https://www.mql5.com/en/users/3rjfx |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Roberto Jacobs (3rjfx) ~ Date: 2026-01-30"
#property link "https://www.mql5.com/en/users/3rjfx"
#property version "1.00"
#property indicator_chart_window
//--
//---
#property indicator_buffers 3
#property indicator_plots 3
//---
#property indicator_type1 DRAW_NONE
#property indicator_type2 DRAW_NONE
#property indicator_type3 DRAW_NONE
//--
//--- input parameters
input int ZDepth = 25; // Input Zigzag Depth
input int ZDeviation = 5; // Input Zigzag Deviation
input int ZBackstep = 3; // Input Zigzag Backstep
input double updown = 70.0; // Highest to Fall (in pct)
input double downup = 30.0; // Lowest to Rise (in pct)
input color contRise = clrWhite; // Color Line Rise Continue
input color contDown = clrWhite; // Color Line Down Continue
input color revsRise = clrRed; // Color Line Rise Reverses
input color revsDown = clrRed; // Color Line Down Reverses
//--
//--- indicator buffers
double ZZBuffer[];
double ZRBuffer[];
double ZDBuffer[];
//--
int hZ;
//--
string obnm="obzz_";
string iname="Examples\\ZigZag.ex5";
string indiname;
//--
#define RDown updown/100.0
#define DRise downup/100.0
#define DATA_LIMIT 369
//---------//
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//--- indicator buffers mapping
//--
SetIndexBuffer(0,ZZBuffer,INDICATOR_DATA);
SetIndexBuffer(1,ZRBuffer,INDICATOR_DATA);
SetIndexBuffer(2,ZDBuffer,INDICATOR_DATA);
//--
hZ=iCustom(Symbol(),0,iname,ZDepth,ZDeviation,ZBackstep); //-- Handle for the ZigZag indicator
//--
if(hZ==INVALID_HANDLE)
{
printf("Failed to create handle of the ZigZag indicator for ",Symbol());
return(INIT_FAILED);
}
//--
PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0);
indiname=StringFormat("ZZR(%d,%d,%d)",ZDepth,ZDeviation,ZBackstep);
IndicatorSetString(INDICATOR_SHORTNAME,indiname);
PlotIndexSetString(0,PLOT_LABEL,indiname);
PlotIndexSetString(1,PLOT_LABEL,"Level Rise");
PlotIndexSetString(2,PLOT_LABEL,"Level Down");
IndicatorSetInteger(INDICATOR_DIGITS,Digits());
//---
return(INIT_SUCCEEDED);
}
//---------//
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//----
Comment("");
//--
IndicatorRelease(hZ);
ObjectsDeleteAll(0,obnm,-1,-1);
//--
Print(getUninitReasonText(reason));
//--
if(reason==REASON_REMOVE)
{
PrintFormat("An indicator %s deletes from the chart..!!",indiname,__FILE__);
}
//--
//----
return;
}
//---------//
//+------------------------------------------------------------------+
//| Custom indicator iteration function |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
//---
int i,limit;
int zh=0,zl=0;
datetime tmi=0;
//---- insufficient data
if(rates_total<DATA_LIMIT)
return(0);
//--- last counted bar will be recounted
limit=rates_total-prev_calculated;
if(limit==0) limit=DATA_LIMIT;
if(prev_calculated>0) limit++;
//--
int calculated=BarsCalculated(hZ);
if(calculated<rates_total)
{
Print("Not all data of ZigZag Indicator is calculated (",calculated,"bars ). Error",GetLastError());
return(0);
}
//--
limit=DATA_LIMIT;
//--
ArrayResize(ZZBuffer,limit,limit);
ArrayResize(ZRBuffer,limit,limit);
ArrayResize(ZDBuffer,limit,limit);
//--
ArraySetAsSeries(ZZBuffer,true);
ArraySetAsSeries(ZRBuffer,true);
ArraySetAsSeries(ZDBuffer,true);
ArraySetAsSeries(open,true);
ArraySetAsSeries(high,true);
ArraySetAsSeries(low,true);
ArraySetAsSeries(close,true);
ArraySetAsSeries(time,true);
//--
//--- we can copy not all data
int to_copy;
int values_to_copy;
if(prev_calculated>rates_total || prev_calculated<0)
{
to_copy=values_to_copy=rates_total;
}
else
{
to_copy=values_to_copy=rates_total-prev_calculated;
if(prev_calculated>0) to_copy++; values_to_copy++;
}
//--- get ZigZag buffers
if(IsStopped()) return(0); //Checking for stop flag
if(CopyBuffer(hZ,0,0,to_copy,ZZBuffer)<0)
{
Print("Getting ZigZag indicator buffers is failed! Error",GetLastError());
return(0);
}
//--
for(i=limit-1; i>=0; i--)
{
if(ZZBuffer[i]==high[i]) { zh=i; tmi=time[i+3]; }
if(ZZBuffer[i]==low[i]) { zl=i; tmi=time[i+3]; }
ZDBuffer[i]=0.0;
ZRBuffer[i]=0.0;
//--
if(i==0)
{
if((zl<zh && zl>0) || (zl>zh && zh==0))
{
ZRBuffer[zl]=NormalizeDouble(low[zl]+((high[zh]-low[zl])*DRise),Digits());
CreateTrendLine(0,obnm+"Price Reverses from Down To Rise_"+DoubleToString(DRise,2)+"(%)",time[0],ZRBuffer[zl],tmi,ZRBuffer[zl],1,STYLE_SOLID,revsDown,false,true);
for(int x=0; x<zl; x++)
{
ZRBuffer[x]=ZRBuffer[zl];
ZDBuffer[x]=0.0;
}
CreateTrendLine(0,obnm+"ZigZag Bottom ~ Breakout continue to Down",time[0],ZZBuffer[zl],tmi,ZZBuffer[zl],1,STYLE_SOLID,contDown,false,true);
}
if((zh<zl && zh>0) || (zh>zl && zl==0))
{
ZDBuffer[zh]=NormalizeDouble(low[zl]+((high[zh]-low[zl])*RDown),Digits());
CreateTrendLine(0,obnm+"Price Reverses from Rise To Down_"+DoubleToString(RDown,2)+"(%)",time[0],ZDBuffer[zh],tmi,ZDBuffer[zh],1,STYLE_SOLID,revsRise,false,true);
for(int x=0; x<zh; x++)
{
ZDBuffer[x]=ZDBuffer[zh];
ZRBuffer[x]=0.0;
}
CreateTrendLine(0,obnm+"ZigZag Top ~ Breakout continue to Rise",time[0],ZZBuffer[zh],tmi,ZZBuffer[zh],1,STYLE_SOLID,contRise,false,true);
}
}
}
//--
//--- return value of prev_calculated for next call
return(rates_total);
}
//---------//
//+------------------------------------------------------------------+
bool CreateTrendLine(long chartid,
string line_name,
datetime line_time1,
double line_price1,
datetime line_time2,
double line_price2,
int line_width,
int line_style,
color line_color,
bool ray_right,
bool line_hidden)
{
//---
ObjectDelete(chartid,line_name);
//--
if(ObjectCreate(chartid,line_name,OBJ_TREND,0,line_time1,line_price1,line_time2,line_price2)) // create trend line
{
ObjectSetInteger(chartid,line_name,OBJPROP_WIDTH,line_width);
ObjectSetInteger(chartid,line_name,OBJPROP_STYLE,line_style);
ObjectSetInteger(chartid,line_name,OBJPROP_COLOR,line_color);
ObjectSetInteger(chartid,line_name,OBJPROP_RAY,ray_right);
ObjectSetInteger(chartid,line_name,OBJPROP_HIDDEN,line_hidden);
}
else
{Print("Failed to create the object OBJ_TREND ",line_name,", Error code = ", GetLastError()); return(false);}
//--
return(true);
//---
} //-end CreateTrendLine()
//---------//
string getUninitReasonText(int reasonCode)
{
//---
string text="";
//---
switch(reasonCode)
{
case REASON_PROGRAM:
text="The EA has stopped working calling by remove function."; break;
case REASON_REMOVE:
text="Program "+__FILE__+" was removed from chart"; break;
case REASON_RECOMPILE:
text="Program recompiled."; break;
case REASON_CHARTCHANGE:
text="Symbol or timeframe was changed"; break;
case REASON_CHARTCLOSE:
text="Chart was closed"; break;
case REASON_PARAMETERS:
text="Input-parameter was changed"; break;
case REASON_ACCOUNT:
text="Account was changed"; break;
case REASON_TEMPLATE:
text="New template was applied to chart"; break;
case REASON_INITFAILED:
text="The OnInit() handler returned a non-zero value."; break;
case REASON_CLOSE:
text="Terminal closed."; break;
default: text="Another reason"; break;
}
//--
return text;
//---
} //-end getUninitReasonText()
//---------//
***Copyright © 2026 3rjfx ~ For educational purposes only.***




No comments :
Post a Comment
Leave A Comment...