449 lines
13 KiB
Plaintext
449 lines
13 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| SuperEMA.mq5 |
|
|
//| EMA + CCI + MACD histogram — trend filter, momentum confirmation |
|
|
//+------------------------------------------------------------------+
|
|
#property strict
|
|
#property version "1.01"
|
|
|
|
#include <Trade/Trade.mqh>
|
|
|
|
enum ENUM_ENTRY_STYLE
|
|
{
|
|
ENTRY_CCIZERO_MACD = 0, // EMA trend + CCI crosses zero + MACD histogram agrees
|
|
ENTRY_LAMBERT = 1, // EMA trend + CCI crosses ±100 + MACD histogram agrees
|
|
ENTRY_PULLBACK = 2 // Uptrend: pullback to fast EMA + CCI was oversold + CCI crosses up through 0 + MACD > 0 (mirror for sells)
|
|
};
|
|
|
|
input group "=== Market ==="
|
|
input string InpSymbol = "";
|
|
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M15;
|
|
input double InpLots = 0.01;
|
|
input int InpSlippagePoints = 55;
|
|
input int InpMagic = 940001;
|
|
|
|
input group "=== EMA (trend & structure) ==="
|
|
input int InpEmaFast = 40;
|
|
input int InpEmaMid = 180;
|
|
input int InpEmaSlow = 125;
|
|
input int InpEmaTrendBars = 3; // closed bar shift for EMA reads
|
|
|
|
input group "=== CCI ==="
|
|
input int InpCciPeriod = 17;
|
|
input double InpCciOverbought = 80.0;
|
|
input double InpCciOversold = -140.0;
|
|
input int InpPullbackCciLookback = 20; // bars to check prior CCI oversold/overbought
|
|
|
|
input group "=== MACD (histogram = main - signal) ==="
|
|
input int InpMacdFast = 14;
|
|
input int InpMacdSlow = 38;
|
|
input int InpMacdSignal = 9;
|
|
|
|
input group "=== Strategy ==="
|
|
input ENUM_ENTRY_STYLE InpEntryStyle = ENTRY_LAMBERT;
|
|
input bool InpOneTradeOnly = true;
|
|
input bool InpUseStructuralSL = false;
|
|
input double InpSlBufferPoints = 110;
|
|
|
|
input group "=== Exits (so trades do not run forever) ==="
|
|
input bool InpExitOnTrendFlip = false; // close when price vs slow EMA flips against position
|
|
input bool InpExitOnMacdFlip = false; // close when MACD histogram flips against position
|
|
input bool InpExitOnCciZeroCross = true; // long: CCI crosses below 0; short: CCI crosses above 0
|
|
input int InpMaxHoldingBars = 168; // 0 = disabled (e.g. ~8 days M15)
|
|
input bool InpExitBelowMidEma = false; // long: close if close < mid EMA (invalidation)
|
|
|
|
input group "=== Debug ==="
|
|
input bool InpDebugLogs = false;
|
|
|
|
CTrade trade;
|
|
datetime g_lastBarTime = 0;
|
|
|
|
string WorkSymbol()
|
|
{
|
|
return (InpSymbol == "" || InpSymbol == NULL) ? _Symbol : InpSymbol;
|
|
}
|
|
|
|
void Log(const string s)
|
|
{
|
|
if(InpDebugLogs)
|
|
Print("[SuperEMA] ", s);
|
|
}
|
|
|
|
bool IsNewBar(const string sym, const ENUM_TIMEFRAMES tf)
|
|
{
|
|
datetime t = iTime(sym, tf, 0);
|
|
if(t <= 0 || t == g_lastBarTime)
|
|
return false;
|
|
g_lastBarTime = t;
|
|
return true;
|
|
}
|
|
|
|
double EmaAt(const string sym, const ENUM_TIMEFRAMES tf, const int period, const int shift)
|
|
{
|
|
int h = iMA(sym, tf, period, 0, MODE_EMA, PRICE_CLOSE);
|
|
if(h == INVALID_HANDLE)
|
|
return 0.0;
|
|
double b[1];
|
|
if(CopyBuffer(h, 0, shift, 1, b) <= 0)
|
|
{
|
|
IndicatorRelease(h);
|
|
return 0.0;
|
|
}
|
|
IndicatorRelease(h);
|
|
return b[0];
|
|
}
|
|
|
|
double CciAt(const string sym, const ENUM_TIMEFRAMES tf, const int period, const int shift)
|
|
{
|
|
int h = iCCI(sym, tf, period, PRICE_TYPICAL);
|
|
if(h == INVALID_HANDLE)
|
|
return 0.0;
|
|
double b[1];
|
|
if(CopyBuffer(h, 0, shift, 1, b) <= 0)
|
|
{
|
|
IndicatorRelease(h);
|
|
return 0.0;
|
|
}
|
|
IndicatorRelease(h);
|
|
return b[0];
|
|
}
|
|
|
|
bool MacdHistAt(const string sym, const ENUM_TIMEFRAMES tf, const int fast, const int slow, const int signal, const int shift, double &hist)
|
|
{
|
|
int h = iMACD(sym, tf, fast, slow, signal, PRICE_CLOSE);
|
|
if(h == INVALID_HANDLE)
|
|
return false;
|
|
double mainLine[1], sigLine[1];
|
|
if(CopyBuffer(h, 0, shift, 1, mainLine) <= 0 || CopyBuffer(h, 1, shift, 1, sigLine) <= 0)
|
|
{
|
|
IndicatorRelease(h);
|
|
return false;
|
|
}
|
|
IndicatorRelease(h);
|
|
hist = mainLine[0] - sigLine[0];
|
|
return true;
|
|
}
|
|
|
|
bool TrendUp(const string sym, const int sh)
|
|
{
|
|
double c = iClose(sym, InpTimeframe, sh);
|
|
double emaS = EmaAt(sym, InpTimeframe, InpEmaSlow, sh);
|
|
return (emaS > 0.0 && c > emaS);
|
|
}
|
|
|
|
bool TrendDown(const string sym, const int sh)
|
|
{
|
|
double c = iClose(sym, InpTimeframe, sh);
|
|
double emaS = EmaAt(sym, InpTimeframe, InpEmaSlow, sh);
|
|
return (emaS > 0.0 && c < emaS);
|
|
}
|
|
|
|
bool CciCrossAboveZero(const string sym)
|
|
{
|
|
double c1 = CciAt(sym, InpTimeframe, InpCciPeriod, 1);
|
|
double c2 = CciAt(sym, InpTimeframe, InpCciPeriod, 2);
|
|
return (c2 <= 0.0 && c1 > 0.0);
|
|
}
|
|
|
|
bool CciCrossBelowZero(const string sym)
|
|
{
|
|
double c1 = CciAt(sym, InpTimeframe, InpCciPeriod, 1);
|
|
double c2 = CciAt(sym, InpTimeframe, InpCciPeriod, 2);
|
|
return (c2 >= 0.0 && c1 < 0.0);
|
|
}
|
|
|
|
bool CciCrossAbove100(const string sym)
|
|
{
|
|
double c1 = CciAt(sym, InpTimeframe, InpCciPeriod, 1);
|
|
double c2 = CciAt(sym, InpTimeframe, InpCciPeriod, 2);
|
|
return (c2 < InpCciOverbought && c1 > InpCciOverbought);
|
|
}
|
|
|
|
bool CciCrossBelowMinus100(const string sym)
|
|
{
|
|
double c1 = CciAt(sym, InpTimeframe, InpCciPeriod, 1);
|
|
double c2 = CciAt(sym, InpTimeframe, InpCciPeriod, 2);
|
|
return (c2 > InpCciOversold && c1 < InpCciOversold);
|
|
}
|
|
|
|
bool HadCciOversoldRecently(const string sym)
|
|
{
|
|
for(int i = 2; i <= InpPullbackCciLookback + 1; i++)
|
|
{
|
|
double v = CciAt(sym, InpTimeframe, InpCciPeriod, i);
|
|
if(v <= InpCciOversold)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool HadCciOverboughtRecently(const string sym)
|
|
{
|
|
for(int i = 2; i <= InpPullbackCciLookback + 1; i++)
|
|
{
|
|
double v = CciAt(sym, InpTimeframe, InpCciPeriod, i);
|
|
if(v >= InpCciOverbought)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool PullbackNearFastEmaLong(const string sym)
|
|
{
|
|
double emaF = EmaAt(sym, InpTimeframe, InpEmaFast, 1);
|
|
double lo = iLow(sym, InpTimeframe, 1);
|
|
if(emaF <= 0.0)
|
|
return false;
|
|
return (lo <= emaF + InpSlBufferPoints * _Point * 3.0);
|
|
}
|
|
|
|
bool PullbackNearFastEmaShort(const string sym)
|
|
{
|
|
double emaF = EmaAt(sym, InpTimeframe, InpEmaFast, 1);
|
|
double hi = iHigh(sym, InpTimeframe, 1);
|
|
if(emaF <= 0.0)
|
|
return false;
|
|
return (hi >= emaF - InpSlBufferPoints * _Point * 3.0);
|
|
}
|
|
|
|
int PositionsByMagic(const string sym, const int magic)
|
|
{
|
|
int n = 0;
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong t = PositionGetTicket(i);
|
|
if(t == 0)
|
|
continue;
|
|
if(PositionGetString(POSITION_SYMBOL) == sym && (int)PositionGetInteger(POSITION_MAGIC) == magic)
|
|
n++;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
void ComputeSLTP(const bool isBuy, const double entry, double &sl, double &tp)
|
|
{
|
|
const string sym = WorkSymbol();
|
|
sl = 0.0;
|
|
tp = 0.0;
|
|
if(!InpUseStructuralSL)
|
|
return;
|
|
double emaM = EmaAt(sym, InpTimeframe, InpEmaMid, InpEmaTrendBars);
|
|
double buf = InpSlBufferPoints * _Point;
|
|
if(isBuy)
|
|
sl = emaM - buf;
|
|
else
|
|
sl = emaM + buf;
|
|
}
|
|
|
|
int BarsSinceOpen(const string sym, const datetime openTime)
|
|
{
|
|
if(openTime <= 0)
|
|
return 0;
|
|
int sh = iBarShift(sym, InpTimeframe, openTime, false);
|
|
if(sh < 0)
|
|
return 999999;
|
|
return sh;
|
|
}
|
|
|
|
void ClosePositionTicket(const ulong ticket, const string reason)
|
|
{
|
|
trade.SetExpertMagicNumber(InpMagic);
|
|
if(trade.PositionClose(ticket))
|
|
Log("Close: " + reason);
|
|
}
|
|
|
|
void ManageSuperEMAExits(const string sym)
|
|
{
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(ticket == 0)
|
|
continue;
|
|
if(!PositionSelectByTicket(ticket))
|
|
continue;
|
|
if(PositionGetString(POSITION_SYMBOL) != sym)
|
|
continue;
|
|
if((int)PositionGetInteger(POSITION_MAGIC) != InpMagic)
|
|
continue;
|
|
|
|
ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
datetime openTime = (datetime)PositionGetInteger(POSITION_TIME);
|
|
|
|
double h1 = 0.0;
|
|
if(!MacdHistAt(sym, InpTimeframe, InpMacdFast, InpMacdSlow, InpMacdSignal, 1, h1))
|
|
continue;
|
|
|
|
bool closeLong = false;
|
|
bool closeShort = false;
|
|
string reason = "";
|
|
|
|
if(InpMaxHoldingBars > 0)
|
|
{
|
|
int held = BarsSinceOpen(sym, openTime);
|
|
if(held >= InpMaxHoldingBars)
|
|
{
|
|
if(ptype == POSITION_TYPE_BUY)
|
|
closeLong = true;
|
|
else
|
|
closeShort = true;
|
|
reason = "time stop (max bars)";
|
|
}
|
|
}
|
|
|
|
if(ptype == POSITION_TYPE_BUY)
|
|
{
|
|
if(InpExitOnTrendFlip && TrendDown(sym, InpEmaTrendBars))
|
|
{
|
|
closeLong = true;
|
|
reason = "trend flip (below slow EMA)";
|
|
}
|
|
if(InpExitOnMacdFlip && h1 < 0.0)
|
|
{
|
|
closeLong = true;
|
|
reason = "MACD histogram < 0";
|
|
}
|
|
if(InpExitOnCciZeroCross && CciCrossBelowZero(sym))
|
|
{
|
|
closeLong = true;
|
|
reason = "CCI crossed below zero";
|
|
}
|
|
if(InpExitBelowMidEma)
|
|
{
|
|
double c = iClose(sym, InpTimeframe, 1);
|
|
double emaM = EmaAt(sym, InpTimeframe, InpEmaMid, 1);
|
|
if(emaM > 0.0 && c < emaM)
|
|
{
|
|
closeLong = true;
|
|
reason = "close below mid EMA";
|
|
}
|
|
}
|
|
if(closeLong)
|
|
ClosePositionTicket(ticket, reason);
|
|
}
|
|
else if(ptype == POSITION_TYPE_SELL)
|
|
{
|
|
if(InpExitOnTrendFlip && TrendUp(sym, InpEmaTrendBars))
|
|
{
|
|
closeShort = true;
|
|
reason = "trend flip (above slow EMA)";
|
|
}
|
|
if(InpExitOnMacdFlip && h1 > 0.0)
|
|
{
|
|
closeShort = true;
|
|
reason = "MACD histogram > 0";
|
|
}
|
|
if(InpExitOnCciZeroCross && CciCrossAboveZero(sym))
|
|
{
|
|
closeShort = true;
|
|
reason = "CCI crossed above zero";
|
|
}
|
|
if(InpExitBelowMidEma)
|
|
{
|
|
double c = iClose(sym, InpTimeframe, 1);
|
|
double emaM = EmaAt(sym, InpTimeframe, InpEmaMid, 1);
|
|
if(emaM > 0.0 && c > emaM)
|
|
{
|
|
closeShort = true;
|
|
reason = "close above mid EMA";
|
|
}
|
|
}
|
|
if(closeShort)
|
|
ClosePositionTicket(ticket, reason);
|
|
}
|
|
}
|
|
}
|
|
|
|
int OnInit()
|
|
{
|
|
string sym = WorkSymbol();
|
|
if(!SymbolSelect(sym, true))
|
|
{
|
|
Print("SuperEMA: cannot select symbol ", sym);
|
|
return INIT_FAILED;
|
|
}
|
|
trade.SetExpertMagicNumber(InpMagic);
|
|
trade.SetDeviationInPoints(InpSlippagePoints);
|
|
return INIT_SUCCEEDED;
|
|
}
|
|
|
|
void OnTick()
|
|
{
|
|
string sym = WorkSymbol();
|
|
if(_Symbol != sym)
|
|
{
|
|
static datetime lastLog = 0;
|
|
datetime tb = iTime(_Symbol, PERIOD_M1, 0);
|
|
if(tb != lastLog && InpDebugLogs)
|
|
{
|
|
lastLog = tb;
|
|
Log("Chart symbol differs from WorkSymbol; attach to " + sym + " or set InpSymbol empty.");
|
|
}
|
|
return;
|
|
}
|
|
|
|
if(!IsNewBar(sym, InpTimeframe))
|
|
return;
|
|
|
|
// Exits must run every bar; do not skip when a position exists (otherwise trades never close with SL=0/TP=0).
|
|
ManageSuperEMAExits(sym);
|
|
|
|
if(InpOneTradeOnly && PositionsByMagic(sym, InpMagic) > 0)
|
|
return;
|
|
|
|
const int sh = InpEmaTrendBars;
|
|
double h1 = 0.0, h2 = 0.0;
|
|
if(!MacdHistAt(sym, InpTimeframe, InpMacdFast, InpMacdSlow, InpMacdSignal, 1, h1) ||
|
|
!MacdHistAt(sym, InpTimeframe, InpMacdFast, InpMacdSlow, InpMacdSignal, 2, h2))
|
|
return;
|
|
|
|
bool up = TrendUp(sym, sh);
|
|
bool dn = TrendDown(sym, sh);
|
|
|
|
bool wantBuy = false;
|
|
bool wantSell = false;
|
|
|
|
switch(InpEntryStyle)
|
|
{
|
|
case ENTRY_CCIZERO_MACD:
|
|
if(up && CciCrossAboveZero(sym) && h1 > 0.0)
|
|
wantBuy = true;
|
|
if(dn && CciCrossBelowZero(sym) && h1 < 0.0)
|
|
wantSell = true;
|
|
break;
|
|
|
|
case ENTRY_LAMBERT:
|
|
if(up && CciCrossAbove100(sym) && h1 > 0.0)
|
|
wantBuy = true;
|
|
if(dn && CciCrossBelowMinus100(sym) && h1 < 0.0)
|
|
wantSell = true;
|
|
break;
|
|
|
|
case ENTRY_PULLBACK:
|
|
if(up && HadCciOversoldRecently(sym) && CciCrossAboveZero(sym) && h1 > 0.0 && PullbackNearFastEmaLong(sym))
|
|
wantBuy = true;
|
|
if(dn && HadCciOverboughtRecently(sym) && CciCrossBelowZero(sym) && h1 < 0.0 && PullbackNearFastEmaShort(sym))
|
|
wantSell = true;
|
|
break;
|
|
}
|
|
|
|
MqlTick tick;
|
|
if(!SymbolInfoTick(sym, tick))
|
|
return;
|
|
|
|
double sl = 0.0, tp = 0.0;
|
|
|
|
if(wantBuy && !wantSell)
|
|
{
|
|
ComputeSLTP(true, tick.ask, sl, tp);
|
|
if(trade.Buy(InpLots, sym, tick.ask, sl, tp, "SuperEMA long"))
|
|
Log(StringFormat("BUY ask=%.5f sl=%.5f cci=%.2f macdHist=%.5f", tick.ask, sl,
|
|
CciAt(sym, InpTimeframe, InpCciPeriod, 1), h1));
|
|
}
|
|
else if(wantSell && !wantBuy)
|
|
{
|
|
ComputeSLTP(false, tick.bid, sl, tp);
|
|
if(trade.Sell(InpLots, sym, tick.bid, sl, tp, "SuperEMA short"))
|
|
Log(StringFormat("SELL bid=%.5f sl=%.5f cci=%.2f macdHist=%.5f", tick.bid, sl,
|
|
CciAt(sym, InpTimeframe, InpCciPeriod, 1), h1));
|
|
}
|
|
}
|