candlestick-signal-alerts/mql5/Indicators/CandlestickSignalAlerts/Cdl.mqh
Allan Niñal Allan Niñal dd87e0da4f Free source regenerated: Pro-only code removed from the engine, debug log added
Generated by the publish tool from the private source; all publish gates pass, compile 0 errors 0 warnings on MT5 and MT4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 08:12:02 +08:00
159 lines
6.8 KiB
MQL5
//+------------------------------------------------------------------+
//| Cdl.mqh |
//| Copyright 2026, Allan Niñal |
//| https://www.allanninal.dev/mql5 |
//+------------------------------------------------------------------+
//| Candlestick engine, one CLOSED bar per step (no repaint), shared |
//| by Candlestick Signal Alerts. |
//| 21 patterns; the exact rules are tests/reference.py (this file |
//| is checked against it bar by bar). Trend before a pattern: close |
//| vs EMA(20). Averages: body and range of the 10 bars before. |
//| Statistics: the move N bars later in ATR(14) units, signed by |
//| the pattern's direction, and the same for every bar (baseline). |
//+------------------------------------------------------------------+
// SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0 (see LICENSE)
#ifndef CDL_MQH
#define CDL_MQH
#define CD_NP 13
#define CD_N 64 // ring of recent bars (forward bars + 4 must stay below it)
#define CD_EMA 20
#define CD_AVG 10
string CDName(const int p)
{
string n[CD_NP]={"Bullish Engulfing","Bearish Engulfing","Hammer","Hanging Man","Inverted Hammer","Shooting Star",
"Doji","Morning Star","Evening Star","Bullish Harami","Bearish Harami","Piercing Line",
"Dark Cloud Cover"
};
return(p>=0 && p<CD_NP ? n[p] : "?");
}
string CDShort(const int p)
{
string n[CD_NP]={"ENG","ENG","HAM","HANG","IHAM","STAR","DOJI","MSTAR","ESTAR","HAR","HAR","PIER","DCC"
};
return(p>=0 && p<CD_NP ? n[p] : "?");
}
struct CDState
{
int cnt;
double o[CD_N],h[CD_N],l[CD_N],c[CD_N],ema[CD_N];
bool has_ema[CD_N];
double ema_sum;
int ema_cnt;
//--- the last bar
int ev_mask;
int ev_dir[CD_NP];
};
void CDInit(CDState &s)
{
s.cnt=0; s.ema_sum=0; s.ema_cnt=0;
s.ev_mask=0;
for(int i=0; i<CD_NP; i++)
s.ev_dir[i]=0;
}
int CDi(const int k) { return(k%CD_N); }
double CDBody(const CDState &s,const int k) { return(MathAbs(s.c[CDi(k)]-s.o[CDi(k)])); }
double CDRange(const CDState &s,const int k) { return(s.h[CDi(k)]-s.l[CDi(k)]); }
double CDUp(const CDState &s,const int k) { return(s.h[CDi(k)]-MathMax(s.o[CDi(k)],s.c[CDi(k)])); }
double CDLo(const CDState &s,const int k) { return(MathMin(s.o[CDi(k)],s.c[CDi(k)])-s.l[CDi(k)]); }
bool CDBull(const CDState &s,const int k) { return(s.c[CDi(k)]>s.o[CDi(k)]); }
bool CDBear(const CDState &s,const int k) { return(s.c[CDi(k)]<s.o[CDi(k)]); }
double CDO(const CDState &s,const int k) { return(s.o[CDi(k)]); }
double CDH(const CDState &s,const int k) { return(s.h[CDi(k)]); }
double CDL(const CDState &s,const int k) { return(s.l[CDi(k)]); }
double CDC(const CDState &s,const int k) { return(s.c[CDi(k)]); }
//--- comparisons with the tie tolerance of the bar being decided
double g_cd_tol=0;
bool GE(const double x,const double y) { return(x>=y-g_cd_tol); }
bool LE(const double x,const double y) { return(x<=y+g_cd_tol); }
bool GT(const double x,const double y) { return(x>y+g_cd_tol); }
bool LT(const double x,const double y) { return(x<y-g_cd_tol); }
int CDTrendT(const CDState &s,const int j)
{
if(j<0 || !s.has_ema[CDi(j)])
return(0);
double c=s.c[CDi(j)],e=s.ema[CDi(j)];
return(GT(c,e) ? 1 : (LT(c,e) ? -1 : 0));
}
void CDAdd(CDState &s,const int pat,const int d,const bool &use[])
{
if(!use[pat])
return;
s.ev_mask|=(1<<pat);
s.ev_dir[pat]=d;
}
//--- feed one CLOSED bar; true when it completed at least one pattern (s.ev_mask)
bool CDFeed(CDState &s,const bool &use[],const double o,const double h,const double l,const double c)
{
int k=s.cnt,ki=CDi(k);
s.o[ki]=o; s.h[ki]=h; s.l[ki]=l; s.c[ki]=c;
s.cnt++;
//--- EMA(20), SMA-seeded
s.ema_cnt++;
s.has_ema[ki]=false;
if(s.ema_cnt<CD_EMA)
s.ema_sum+=c;
else
if(s.ema_cnt==CD_EMA)
{ s.ema_sum+=c; s.ema[ki]=s.ema_sum/CD_EMA; s.has_ema[ki]=true; }
else
{ s.ema[ki]=s.ema[CDi(k-1)]+2.0/(CD_EMA+1)*(c-s.ema[CDi(k-1)]); s.has_ema[ki]=true; }
//--- patterns
s.ev_mask=0;
for(int i=0; i<CD_NP; i++)
s.ev_dir[i]=0;
if(k>=CD_AVG && k>=3)
{
double ab=0,ar=0;
for(int j=k-CD_AVG; j<k; j++)
{ ab+=CDBody(s,j); ar+=CDRange(s,j); }
ab/=CD_AVG; ar/=CD_AVG;
double b=CDBody(s,k),r=CDRange(s,k),u=CDUp(s,k),w=CDLo(s,k);
g_cd_tol=1e-9*MathAbs(c); // ties within 1e-9 x price count as equal (as tests/reference.py)
int t1=CDTrendT(s,k-1),t2=CDTrendT(s,k-2),t3=CDTrendT(s,k-3);
int p=k-1,q=k-2;
if(CDBear(s,p) && CDBull(s,k) && LE(o,CDC(s,p)) && GE(c,CDO(s,p)) && GT(b,CDBody(s,p)) && t2<0) CDAdd(s,0,1,use);
if(CDBull(s,p) && CDBear(s,k) && GE(o,CDC(s,p)) && LE(c,CDO(s,p)) && GT(b,CDBody(s,p)) && t2>0) CDAdd(s,1,-1,use);
if(GT(r,0))
{
bool hammer=(GE(w,2*b) && LE(u,0.1*r) && GE(w,0.6*r));
bool inv=(GE(u,2*b) && LE(w,0.1*r) && GE(u,0.6*r));
if(hammer && t1<0) CDAdd(s,2,1,use);
if(hammer && t1>0) CDAdd(s,3,-1,use);
if(inv && t1<0) CDAdd(s,4,1,use);
if(inv && t1>0) CDAdd(s,5,-1,use);
if(LE(b,0.05*r) && GE(r,0.5*ar) && t1!=0) CDAdd(s,6,-t1,use);
}
double midq=(CDO(s,q)+CDC(s,q))/2,midp=(CDO(s,p)+CDC(s,p))/2;
if(CDBear(s,q) && GE(CDBody(s,q),ab) && LE(CDBody(s,p),0.3*CDBody(s,q)) && LE(MathMin(CDO(s,p),CDC(s,p)),CDC(s,q)) &&
CDBull(s,k) && GT(c,midq) && t3<0) CDAdd(s,7,1,use);
if(CDBull(s,q) && GE(CDBody(s,q),ab) && LE(CDBody(s,p),0.3*CDBody(s,q)) && GE(MathMax(CDO(s,p),CDC(s,p)),CDC(s,q)) &&
CDBear(s,k) && LT(c,midq) && t3>0) CDAdd(s,8,-1,use);
if(CDBear(s,p) && GE(CDBody(s,p),ab) && CDBull(s,k) && GE(o,CDC(s,p)) && LE(c,CDO(s,p)) && LT(b,CDBody(s,p)) && t2<0) CDAdd(s,9,1,use);
if(CDBull(s,p) && GE(CDBody(s,p),ab) && CDBear(s,k) && LE(o,CDC(s,p)) && GE(c,CDO(s,p)) && LT(b,CDBody(s,p)) && t2>0) CDAdd(s,10,-1,use);
if(CDBear(s,p) && GE(CDBody(s,p),ab) && CDBull(s,k) && LE(o,CDC(s,p)) && GT(c,midp) && LT(c,CDO(s,p)) && t2<0) CDAdd(s,11,1,use);
if(CDBull(s,p) && GE(CDBody(s,p),ab) && CDBear(s,k) && GE(o,CDC(s,p)) && LT(c,midp) && GT(c,CDO(s,p)) && t2>0) CDAdd(s,12,-1,use);
}
//--- Free: the events of this bar are in ev_mask / ev_dir
return(s.ev_mask!=0);
}
//--- "Bullish Engulfing, Doji" from a mask (only the given direction when dir != 0)
string CDNames(const CDState &s,const int mask,const int dir)
{
string r="";
for(int i=0; i<CD_NP; i++)
if((mask&(1<<i))!=0 && (dir==0 || s.ev_dir[i]==dir))
r+=(r=="" ? "" : ", ")+CDName(i);
return(r);
}
#endif
//+------------------------------------------------------------------+