0

I am trying to reproduce ZigZag mql4 code from MetaTrader 4 into Python 3.7 using pandas functions and numpy and also plotting it in order to check if it is a good result or not.

This is the mql4 code I'm talking about:

//+------------------------------------------------------------------+
//|                                                       ZigZag.mq4 |
//|                   Copyright 2006-2014, MetaQuotes Software Corp. |
//|                                              http://www.mql4.com |
//+------------------------------------------------------------------+
#property copyright "2006-2014, MetaQuotes Software Corp."
#property link      "http://www.mql4.com"
#property strict

#property indicator_chart_window
#property indicator_buffers 1
#property indicator_color1  Red
//---- indicator parameters
input int InpDepth=12;     // Depth
input int InpDeviation=5;  // Deviation
input int InpBackstep=3;   // Backstep
//---- indicator buffers
double ExtZigzagBuffer[];
double ExtHighBuffer[];
double ExtLowBuffer[];
//--- globals
int    ExtLevel=3; // recounting's depth of extremums
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(InpBackstep>=InpDepth)
     {
      Print("Backstep cannot be greater or equal to Depth");
      return(INIT_FAILED);
     }
//--- 2 additional buffers
   IndicatorBuffers(3);
//---- drawing settings
   SetIndexStyle(0,DRAW_SECTION);
//---- indicator buffers
   SetIndexBuffer(0,ExtZigzagBuffer);
   SetIndexBuffer(1,ExtHighBuffer);
   SetIndexBuffer(2,ExtLowBuffer);
   SetIndexEmptyValue(0,0.0);
//---- indicator short name
   IndicatorShortName("ZigZag("+string(InpDepth)+","+string(InpDeviation)+","+string(InpBackstep)+")");
//---- initialization done
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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,counterZ,whatlookfor=0;
   int    back,pos,lasthighpos=0,lastlowpos=0;
   double extremum;
   double curlow=0.0,curhigh=0.0,lasthigh=0.0,lastlow=0.0;
//--- check for history and inputs
   if(rates_total<InpDepth || InpBackstep>=InpDepth)
      return(0);
//--- first calculations
   if(prev_calculated==0)
      limit=InitializeAll();
   else 
     {
      //--- find first extremum in the depth ExtLevel or 100 last bars
      i=counterZ=0;
      while(counterZ<ExtLevel && i<100)
        {
         if(ExtZigzagBuffer[i]!=0.0)
            counterZ++;
         i++;
        }
      //--- no extremum found - recounting all from begin
      if(counterZ==0)
         limit=InitializeAll();
      else
        {
         //--- set start position to found extremum position
         limit=i-1;
         //--- what kind of extremum?
         if(ExtLowBuffer[i]!=0.0) 
           {
            //--- low extremum
            curlow=ExtLowBuffer[i];
            //--- will look for the next high extremum
            whatlookfor=1;
           }
         else
           {
            //--- high extremum
            curhigh=ExtHighBuffer[i];
            //--- will look for the next low extremum
            whatlookfor=-1;
           }
         //--- clear the rest data
         for(i=limit-1; i>=0; i--)  
           {
            ExtZigzagBuffer[i]=0.0;  
            ExtLowBuffer[i]=0.0;
            ExtHighBuffer[i]=0.0;
           }
        }
     }
//--- main loop      
   for(i=limit; i>=0; i--)
     {
      //--- find lowest low in depth of bars
      extremum=low[iLowest(NULL,0,MODE_LOW,InpDepth,i)];
      //--- this lowest has been found previously
      if(extremum==lastlow)
         extremum=0.0;
      else 
        { 
         //--- new last low
         lastlow=extremum; 
         //--- discard extremum if current low is too high
         if(low[i]-extremum>InpDeviation*Point)
            extremum=0.0;
         else
           {
            //--- clear previous extremums in backstep bars
            for(back=1; back<=InpBackstep; back++)
              {
               pos=i+back;
               if(ExtLowBuffer[pos]!=0 && ExtLowBuffer[pos]>extremum)
                  ExtLowBuffer[pos]=0.0; 
              }
           }
        } 
      //--- found extremum is current low
      if(low[i]==extremum)
         ExtLowBuffer[i]=extremum;
      else
         ExtLowBuffer[i]=0.0;
      //--- find highest high in depth of bars
      extremum=high[iHighest(NULL,0,MODE_HIGH,InpDepth,i)];
      //--- this highest has been found previously
      if(extremum==lasthigh)
         extremum=0.0;
      else 
        {
         //--- new last high
         lasthigh=extremum;
         //--- discard extremum if current high is too low
         if(extremum-high[i]>InpDeviation*Point)
            extremum=0.0;
         else
           {
            //--- clear previous extremums in backstep bars
            for(back=1; back<=InpBackstep; back++)
              {
               pos=i+back;
               if(ExtHighBuffer[pos]!=0 && ExtHighBuffer[pos]<extremum)
                  ExtHighBuffer[pos]=0.0; 
              } 
           }
        }
      //--- found extremum is current high
      if(high[i]==extremum)
         ExtHighBuffer[i]=extremum;
      else
         ExtHighBuffer[i]=0.0;
     }
//--- final cutting 
   if(whatlookfor==0)
     {
      lastlow=0.0;
      lasthigh=0.0;  
     }
   else
     {
      lastlow=curlow;
      lasthigh=curhigh;
     }
   for(i=limit; i>=0; i--)
     {
      switch(whatlookfor)
        {
         case 0: // look for peak or lawn 
            if(lastlow==0.0 && lasthigh==0.0)
              {
               if(ExtHighBuffer[i]!=0.0)
                 {
                  lasthigh=High[i];
                  lasthighpos=i;
                  whatlookfor=-1;
                  ExtZigzagBuffer[i]=lasthigh;
                 }
               if(ExtLowBuffer[i]!=0.0)
                 {
                  lastlow=Low[i];
                  lastlowpos=i;
                  whatlookfor=1;
                  ExtZigzagBuffer[i]=lastlow;
                 }
              }
             break;  
         case 1: // look for peak
            if(ExtLowBuffer[i]!=0.0 && ExtLowBuffer[i]<lastlow && ExtHighBuffer[i]==0.0)
              {
               ExtZigzagBuffer[lastlowpos]=0.0;
               lastlowpos=i;
               lastlow=ExtLowBuffer[i];
               ExtZigzagBuffer[i]=lastlow;
              }
            if(ExtHighBuffer[i]!=0.0 && ExtLowBuffer[i]==0.0)
              {
               lasthigh=ExtHighBuffer[i];
               lasthighpos=i;
               ExtZigzagBuffer[i]=lasthigh;
               whatlookfor=-1;
              }   
            break;               
         case -1: // look for lawn
            if(ExtHighBuffer[i]!=0.0 && ExtHighBuffer[i]>lasthigh && ExtLowBuffer[i]==0.0)
              {
               ExtZigzagBuffer[lasthighpos]=0.0;
               lasthighpos=i;
               lasthigh=ExtHighBuffer[i];
               ExtZigzagBuffer[i]=lasthigh;
              }
            if(ExtLowBuffer[i]!=0.0 && ExtHighBuffer[i]==0.0)
              {
               lastlow=ExtLowBuffer[i];
               lastlowpos=i;
               ExtZigzagBuffer[i]=lastlow;
               whatlookfor=1;
              }   
            break;               
        }
     }
//--- done
   return(rates_total);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int InitializeAll()
  {
   ArrayInitialize(ExtZigzagBuffer,0.0);
   ArrayInitialize(ExtHighBuffer,0.0);
   ArrayInitialize(ExtLowBuffer,0.0);
//--- first counting position
   return(Bars-InpDepth);
  }
//+------------------------------------------------------------------+

Source: https://www.mql5.com/en/code/viewcode/7796/130170/zigzag.mq4 and this: https://www.mql5.com/en/code/7796

I want to create a function in order to be used with pandas dataframe like this: df.rolling(23).apply(ZigZag_function) instead the indicator itself word by word.

I need to reproduce exactly this mql4 version of ZigZag indcators that is using 12, 5 and 5 as input parameters and no other ZigZag modded indicators. This is for study purpose.

I have found also a Pine Script ZigZag indicator where its author is saying that it is 100% copy of the one from mql4 but ported into Tradingview pines cript and I tried to look also at both codes in order to recreate one into python.

Here is the pine script version:

// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © LucemAnb

//@version=4
//@author=lucemanb

//
//       THIS CODE IS BASED FROM THE MT4 ZIGZAG INDICATOR
//       THE ZIGZAG SETTINGS FOR THE MAIN ONE ON TRADINGVIEW DO NOT WORK THE SAME AS MT4
//       I HOPE U LOVE IT
//

// Ping me for donations, Collaborations or Support


////////
// Seek Menu
study("ZigZag++", "ZigZag++ Lucem Anb", true, format.price, max_labels_count=51, max_lines_count=50)


////////
// Fetch Ingredients 
// [
Depth       = input(12, "Depth", input.integer, minval=1, step=1)
Deviation   = input(5, "Deviation", input.integer, minval=1, step=1)
Backstep    = input(2, "Backstep", input.integer, minval=2, step=1)
line_thick  = input(2, "Line Thickness", input.integer, minval=1, maxval=4)
upcolor     = input(color.lime, "Bull Color")
dncolor     = input(color.red, "Bear Color")
repaint     = input(true, "Repaint Levels")
// ]


//////// 
// Bake
// [
var last_h = 1, last_h := last_h + 1
var last_l = 1, last_l := last_l + 1
var lw = 1, var hg = 1
lw := lw + 1, hg := hg + 1
p_lw = -lowestbars(Depth),
p_hg = -highestbars(Depth)

lowing = lw == p_lw or low - low[p_lw] > Deviation*syminfo.mintick
highing = hg == p_hg or high[p_hg] - high > Deviation*syminfo.mintick
lh = barssince(not highing[1]),
ll = barssince(not lowing[1])
down = barssince(not (lh > ll)) >= Backstep,

lower = low[lw] > low[p_lw],
higher = high[hg] < high[p_hg]

if lw != p_lw and (not down[1] or lower)
    lw := p_lw < hg ? p_lw : 0
if hg != p_hg and (down[1] or higher)
    hg := p_hg < lw ? p_hg : 0

line zz = na
label point = na
x1 = down ? lw : hg
y1 = down ? low[lw] : high[hg]

if down == down[1]
    if repaint
        label.delete(point[1])
        line.delete(zz[1])
    down
if down != down[1]
    if down
        last_h := hg
    else
        last_l := lw
    if not repaint
        nx = down?last_h:last_l
        zz := line.new(bar_index-nx, down ? high[nx] : low[nx], bar_index-(down?last_l:last_h), down ? low[last_l] : high[last_h], width=line_thick, color=down?upcolor:dncolor)
        point := label.new(bar_index-nx, down ? high[nx] : low[nx], down ? (high[nx] > high[last_h[1]]?"HH":"LH") : (low[nx] < low[last_l[1]] ? "LL" : "HL"), style=down?label.style_label_down:label.style_label_up, size=size.tiny, color=down?dncolor:upcolor, textcolor=color.black, tooltip = down ? (high[nx] > high[last_h[1]]?"Higher High":"Lower High") : (low[nx] < low[last_l[1]] ? "Lower Low" : "Higher Low"))
    down
if repaint
    zz := line.new(bar_index-(down?last_h:last_l), down ? high[last_h] : low[last_l], bar_index-x1, y1, width=line_thick, color=down?dncolor:upcolor)
    point := label.new(bar_index-x1, y1, down ? (low[x1] < low[last_l] ? "LL" : "HL") : (high[x1] > high[last_h]?"HH":"LH"), style=down?label.style_label_up:label.style_label_down, size=size.tiny, color=down?upcolor:dncolor, textcolor=color.black, tooltip = down ? (low[x1] < low[last_l] ? "Lower Low" : "Higher Low") : (high[x1] > high[last_h]?"Higher High":"Lower High"))
// ]


////////
// Let it Cool And Serve
bear = down
bgcolor(bear?color.red:color.lime, title="Scanning Direction")


////////
// Declare Meal Was Sweet By Force
alertcondition(bear!=bear[1], "Direction Changed", 'Zigzag on {{ticker}} direction changed at {{time}}')
alertcondition(bear!=bear[1] and not bear, "Bullish Direction", 'Zigzag on {{ticker}} bullish direction at {{time}}')
alertcondition(bear!=bear[1] and bear, "Bearish Direction", 'Zigzag on {{ticker}} bearish direction at {{time}}')

And finally this is my Python version:

from finta import TA
import pandas_ta as p_ta
import yfinance as yf
import pandas as pd
import numpy as np
import math
import statistics

ohlc = yf.download('EURUSD=X', start='2020-11-12', interval='1d')

InpDepth=12
InpDeviation=5
InpBackstep=3
Point = 0.01

limit = len(ohlc)-InpDepth
ohlc['extremum'] = pd.Series([0.0 for x in range(len(ohlc.index))], index=ohlc.index)

ohlc['ExtLowBuffer'] = pd.Series([np.NaN for x in range(len(ohlc.index))], index=ohlc.index)
ohlc['ExtHighBuffer'] = pd.Series([np.NaN for x in range(len(ohlc.index))], index=ohlc.index)
ohlc['ExtZigzagBuffer'] = pd.Series([np.NaN for x in range(len(ohlc.index))], index=ohlc.index)

ohlc['lastlow'] = pd.Series([0.0 for x in range(len(ohlc.index))], index=ohlc.index)
ohlc['lasthigh'] = pd.Series([0.0 for x in range(len(ohlc.index))], index=ohlc.index)


for i in range(limit, -1, -1):
    #! find lowest low in depth of bars:
    # extremum=low[iLowest(NULL,0,MODE_LOW,InpDepth,i)];
    ohlc['extremum'].iloc[i] = ohlc['Low'].rolling(InpDepth).min().iloc[i]
    # this lowest has been found previously:
    if(ohlc['extremum'].iloc[i]==ohlc['lastlow'].iloc[i]):
        ohlc['extremum'].iloc[i]=0.0
    else:
        # new last low
        ohlc['lastlow'].iloc[i]=ohlc['extremum'].iloc[i]
        # discard extremum if current low is too high
        if((ohlc['Low'].iloc[i]-ohlc['extremum'].iloc[i])>InpDeviation*Point):
            ohlc['extremum'].iloc[i]=0.0
        else:
            # clear previous extremums in backstep bars:
            for back in range(1, InpBackstep+1):
                pos=i+back
                if((ohlc['ExtLowBuffer'].iloc[pos]!=0) and (ohlc['ExtLowBuffer'].iloc[pos]>ohlc['extremum'].iloc[i])):
                    ohlc['ExtLowBuffer'].iloc[pos]=0.0
    
    # found extremum is current low:
    if(ohlc['Low'].iloc[i]==ohlc['extremum'].iloc[i]):
        ohlc['ExtLowBuffer'].iloc[i]=ohlc['extremum'].iloc[i]
    else:
        ohlc['ExtLowBuffer'].iloc[i]=0.0
    
    #! find highest high in depth of bars:
    # extremum=high[iHighest(NULL,0,MODE_HIGH,InpDepth,i)];
    ohlc['extremum'].iloc[i] = ohlc['High'].rolling(InpDepth).max().iloc[i]
    # this highest has been found previously:
    if(ohlc['extremum'].iloc[i]==ohlc['lasthigh'].iloc[i]):
        ohlc['extremum'].iloc[i]=0.0
    else:
        #  new last high
        ohlc['lasthigh'].iloc[i]=ohlc['extremum'].iloc[i]
        # discard extremum if current high is too low:
        if((ohlc['extremum'].iloc[i]-ohlc['High'].iloc[i])>(InpDeviation*Point)):
            ohlc['extremum'].iloc[i]=0.0
        else:
            # clear previous extremums in backstep bars:
            for back in range(1, InpBackstep+1):
                pos=i+back
                if((ohlc['ExtHighBuffer'].iloc[pos]!=0) and (ohlc['ExtHighBuffer'].iloc[pos]<ohlc['extremum'].iloc[i])):
                    ohlc['ExtHighBuffer'].iloc[pos]=0.0
    # found extremum is current high:
    if(ohlc['High'].iloc[i]==ohlc['extremum'].iloc[i]):
        ohlc['ExtHighBuffer'].iloc[i]=ohlc['extremum'].iloc[i]
    else:
        ohlc['ExtHighBuffer'].iloc[i]=0.0

ohlc['lastlow'] = pd.Series([0.0 for x in range(len(ohlc.index))], index=ohlc.index)
ohlc['lasthigh'] = pd.Series([0.0 for x in range(len(ohlc.index))], index=ohlc.index)

ohlc['lasthighpos'] = pd.Series([0.0 for x in range(len(ohlc.index))], index=ohlc.index)
ohlc['lastlowpos'] = pd.Series([0.0 for x in range(len(ohlc.index))], index=ohlc.index)

for i in range(limit, -1, -1):
    # look for peak or lawn:
    if((ohlc['lastlow'].iloc[i]==0.0) and (ohlc['lasthigh'].iloc[i]==0.0)):
            if(ohlc['ExtHighBuffer'].iloc[i]!=0.0):
                ohlc['lasthigh'].iloc[i]=ohlc['High'].iloc[i]
                ohlc['ExtZigzagBuffer'].iloc[i]=ohlc['lasthigh'].iloc[i]
            if(ohlc['ExtLowBuffer'].iloc[i]!=0.0):
                ohlc['lastlow'].iloc[i]=ohlc['Low'].iloc[i]
                ohlc['lastlowpos'].iloc[i]=i
                ohlc['ExtZigzagBuffer'].iloc[i]=ohlc['lastlow'].iloc[i]

When I'm trying to plot that ZigZag indicator it looks like this:

import plotly.graph_objects as go

df = ohlc.copy()

# plot the candlesticks
fig = go.Figure(data=[go.Candlestick(x=df.index,open=df['Open'], high=df['High'],low=df['Low'],close=df['Close']), 
                    # go.Scatter(x=df.index, y=df['ExtLowBuffer'], line=dict(color='green', width=1)),
                    go.Scatter(x=df.index, y=df['ExtZigzagBuffer'], line=dict(color='red', width=1))])
fig.update_layout(
    title='Chart test for ZigZag',
    yaxis_title='EURO/USD chart',
)
fig.show()

enter image description here

What is the problem? I did everything exactly the same. How to create a ZigZag function in order to be applied like this df.rolling(35).apply(ZigZag_function)? I know the plotting is wrong but I checked the values and also them are wrong.

Thank you advance!

P.S. I have found also some other topics, some with of them have answers and some don't but none is looking for a mql4 version and most of them are looking only to plot just Close column while mql4 version take 3 inputs and is taking care of Low and also of High columns and not of Close.

Here they are if you want t take a look:

ZigZag indicator in Python

Python ZigZag indicator function logic issue

ZigZag Indicator Metastock Formula to Python

Is it possible to improve my zigzag indicator loop?

Zigzag Line in Python

Python Zig Zag algorithm function not returning expected results

Pandas: Zigzag segmentation of data based on local minima-maxima

YoYoYo
  • 439
  • 2
  • 11

0 Answers0