//+------------------------------------------------------------------+ //| Gann.mqh | //| Copyright 2026, Allan NiƱal | //| https://www.allanninal.dev/mql5 | //+------------------------------------------------------------------+ //| Gann engine, one CLOSED bar per step, used by Gann HiLo Swing. | //| Same rules as the earlier | //| GannSwingSignals indicator (mt5-gann-trader), without its stop | //| and target levels: | //| HiLo Activator: trend up when the close is above the average | //| of the last N highs (as of the bar before), down when below | //| the average of the last N lows; a close equal to it changes | //| nothing. The line is the lows' average in an uptrend, the | //| highs' average in a downtrend. | //| 2-bar swing chart: an upswing starts after two higher highs in | //| a row, a downswing after two lower lows (both: the close | //| decides). When the swing turns up, the lowest low of the | //| downswing becomes a swing-low pivot (and mirrored). | //| Side: the activator trend, or (swing confirmation on) the side | //| both agree on. A side change is a new trend. | //| Pivots are confirmed on the bar the swing turns; they are kept | //| (bar number and price) for the Square of 9 anchor, the fan and | //| the box. Tested against tests/reference.py. | //+------------------------------------------------------------------+ // SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0 (see LICENSE) #ifndef GANN_MQH #define GANN_MQH #define GN_RING 64 // recent highs and lows for the HiLo averages #define GN_PIV 1 // only the newest pivot is marked struct GNParams { int hilo; // HiLo activator period (Gann used 3) bool swing_confirm; // side needs the activator and the swing to agree }; struct GNState { int cnt; // bars fed; bar k = k-th fed bar (0-based) double hi[GN_RING],lo[GN_RING],cl[GN_RING]; double hi_prev,lo_prev; // HiLo averages as of the bar before int trend; // activator trend +1/-1/0 double line; // activator line on the last bar (EMPTY_VALUE before warm-up) int sd; // swing direction +1/-1/0 double cur_min,cur_max; // extremes of the swing in progress ... int cur_min_bar,cur_max_bar; // ... and where they are int side; // +1/-1/0 int ev; // +1/-1 when the last bar started a new trend, else 0 int last_ev_bar; // bar of the last new trend, -1 if none //--- confirmed swing pivots, newest first: price, bar, +1 = swing high, -1 = swing low double piv_px[GN_PIV]; int piv_bar[GN_PIV]; int piv_type[GN_PIV]; int npiv; int piv_new; // the last bar confirmed a pivot (+1 high, -1 low, 0 none) }; int GNi(const int k) { return(k%GN_RING); } void GNInit(GNState &s) { s.cnt=0; ArrayInitialize(s.hi,0); ArrayInitialize(s.lo,0); ArrayInitialize(s.cl,0); s.hi_prev=EMPTY_VALUE; s.lo_prev=EMPTY_VALUE; s.trend=0; s.line=EMPTY_VALUE; s.sd=0; s.cur_min=0; s.cur_max=0; s.cur_min_bar=0; s.cur_max_bar=0; s.side=0; s.ev=0; s.last_ev_bar=-1; ArrayInitialize(s.piv_px,0); ArrayInitialize(s.piv_bar,-1); ArrayInitialize(s.piv_type,0); s.npiv=0; s.piv_new=0; } void GNPivot(GNState &s,const double px,const int bar,const int type) { for(int i=GN_PIV-1; i>0; i--) { s.piv_px[i]=s.piv_px[i-1]; s.piv_bar[i]=s.piv_bar[i-1]; s.piv_type[i]=s.piv_type[i-1]; } s.piv_px[0]=px; s.piv_bar[0]=bar; s.piv_type[0]=type; s.npiv=MathMin(s.npiv+1,GN_PIV); s.piv_new=type; } //--- feed one CLOSED bar; true when it started a new trend (s.ev) bool GNFeed(GNState &s,const GNParams &p,const double h,const double l,const double c) { int k=s.cnt; s.hi[GNi(k)]=h; s.lo[GNi(k)]=l; s.cl[GNi(k)]=c; s.ev=0; s.piv_new=0; //--- HiLo activator (as GannSwingSignals HiLoTrend) int t=s.trend; if(s.hi_prev!=EMPTY_VALUE) { double eps=1e-9*MathAbs(c); if(c-s.hi_prev>eps) t=1; else if(s.lo_prev-c>eps) t=-1; } s.trend=t; double hi=EMPTY_VALUE,lo=EMPTY_VALUE; if(k>=p.hilo-1) { double sh=0,sl=0; for(int j=k-p.hilo+1; j<=k; j++) { sh+=s.hi[GNi(j)]; sl+=s.lo[GNi(j)]; } hi=sh/p.hilo; lo=sl/p.hilo; } s.line=(t>0 ? lo : (t<0 ? hi : EMPTY_VALUE)); s.hi_prev=hi; s.lo_prev=lo; //--- 2-bar swing chart if(k==0) { s.cur_min=l; s.cur_max=h; s.cur_min_bar=0; s.cur_max_bar=0; } if(k>=2) { bool up=h>s.hi[GNi(k-1)] && s.hi[GNi(k-1)]>s.hi[GNi(k-2)]; bool dn=ls.cl[GNi(k-1)] ? 1 : -1); else if(up) nw=1; else if(dn) nw=-1; if(nw!=s.sd) { if(nw==1) { //--- the downswing ended: its lowest low (this bar included) is a swing low double m=s.cur_min; int mb=s.cur_min_bar; if(lm) { m=h; mb=k; } if(s.sd!=0) GNPivot(s,m,mb,1); s.cur_min=l; s.cur_min_bar=k; } s.sd=nw; } else { if(ls.cur_max) { s.cur_max=h; s.cur_max_bar=k; } } } //--- side and a new trend int prev=s.side; if(!p.swing_confirm) s.side=t; else if(t==1 && s.sd==1) s.side=1; else if(t==-1 && s.sd==-1) s.side=-1; s.cnt++; if(s.side!=prev && s.side!=0 && k>=p.hilo+2 && prev!=0) { s.ev=s.side; s.last_ev_bar=k; return(true); } return(false); } #endif //+------------------------------------------------------------------+