Save as MQL5/Experts/Lesson17CompleteEA.mq5. DEMO FIRST. Daily regime filter, EMA cross entry, ATR bracket, 1% risk sizing, full validation, and clean shutdown.
//+------------------------------------------------------------------+
//| Lesson 17 - Complete trend-following EA for CFD |
//| WARNING: sends real orders. Test on DEMO and understand the risk |
//| lesson before any live use. |
//+------------------------------------------------------------------+
#property copyright "Strategist Academy"
#property version "1.00"
#property strict
#include <Trade/Trade.mqh>
input group "Signal"
input int FastMAPeriod = 20; // Fast EMA
input int SlowMAPeriod = 50; // Slow EMA
input int TrendMAPeriod = 200; // Regime EMA (higher TF)
input group "Risk"
input double RiskPercent = 1.0; // Risk % of equity per trade
input double AtrSLMult = 2.0; // Stop = ATR x
input double AtrTPMult = 4.0; // Target = ATR x
input int AtrPeriod = 14; // ATR period
input int MaxSpreadPoints= 30; // Skip if spread wider than this
input group "Execution"
input ulong MagicNumber = 17091709; // Magic number
input int DeviationPoints = 10; // Slippage tolerance (points)
CTrade trade;
int fastHandle = INVALID_HANDLE;
int slowHandle = INVALID_HANDLE;
int trendHandle = INVALID_HANDLE;
int atrHandle = INVALID_HANDLE;
datetime lastBarTime = 0;
//+------------------------------------------------------------------+
int OnInit()
{
if(FastMAPeriod >= SlowMAPeriod)
{
Print("FastMA must be shorter than SlowMA");
return(INIT_FAILED);
}
// Create handles ONCE. Doing this in OnTick leaks one per tick.
fastHandle = iMA(_Symbol, _Period, FastMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
slowHandle = iMA(_Symbol, _Period, SlowMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
trendHandle = iMA(_Symbol, PERIOD_D1, TrendMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
atrHandle = iATR(_Symbol, _Period, AtrPeriod);
if(fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE ||
trendHandle == INVALID_HANDLE || atrHandle == INVALID_HANDLE)
{
Print("Indicator handle creation failed. Error: ", GetLastError());
return(INIT_FAILED);
}
trade.SetExpertMagicNumber(MagicNumber);
trade.SetDeviationInPoints(DeviationPoints);
trade.SetTypeFillingBySymbol(_Symbol);
Print("Complete EA ready on ", _Symbol, " / ", EnumToString((ENUM_TIMEFRAMES)_Period));
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(fastHandle != INVALID_HANDLE) IndicatorRelease(fastHandle);
if(slowHandle != INVALID_HANDLE) IndicatorRelease(slowHandle);
if(trendHandle != INVALID_HANDLE) IndicatorRelease(trendHandle);
if(atrHandle != INVALID_HANDLE) IndicatorRelease(atrHandle);
}
//+------------------------------------------------------------------+
//| Read one value from a handle at a given shift |
//+------------------------------------------------------------------+
bool ReadAt(const int handle, const int shift, double &out)
{
double buf[];
ArraySetAsSeries(buf, true);
if(CopyBuffer(handle, 0, 0, shift + 3, buf) < shift + 3)
return false;
double v = buf[shift];
if(v == EMPTY_VALUE || v <= 0.0)
return false;
out = v;
return true;
}
//+------------------------------------------------------------------+
//| Volume rounded DOWN onto the broker's step |
//+------------------------------------------------------------------+
double NormalizeVolumeDown(const string sym, double vol)
{
double vMin = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);
double vMax = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);
double vStep = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);
vol = MathMax(vMin, MathMin(vMax, vol));
if(vStep > 0)
vol = vMin + MathFloor((vol - vMin) / vStep + 0.0000001) * vStep;
return NormalizeDouble(vol, 2);
}
//+------------------------------------------------------------------+
//| Lots so a stop of stopPoints costs at most riskMoney |
//+------------------------------------------------------------------+
double CalcLots(const string sym, const double riskMoney, const double stopPoints)
{
double tickValue = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE);
if(tickValue <= 0 || tickSize <= 0 || stopPoints <= 0)
return 0.0;
double stopTicks = (stopPoints * _Point) / tickSize;
double riskPerLot = stopTicks * tickValue;
if(riskPerLot <= 0)
return 0.0;
return NormalizeVolumeDown(sym, riskMoney / riskPerLot);
}
//+------------------------------------------------------------------+
void OnTick()
{
// --- cheap checks first -------------------------------------------
datetime t = iTime(_Symbol, _Period, 0);
if(t == lastBarTime) return;
lastBarTime = t;
if(PositionSelect(_Symbol) &&
(ulong)PositionGetInteger(POSITION_MAGIC) == MagicNumber)
return; // already in our position
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if((ask - bid) / _Point > MaxSpreadPoints)
{
Print("Spread too wide - skipping. Spread=",
DoubleToString((ask - bid) / _Point, 1), " points");
return;
}
// --- read indicators (shift 1 = last closed bar) -------------------
double fast[1], slow[1], fastPrev[1], slowPrev[1], trend[1], atr[1];
if(!ReadAt(fastHandle, 1, fast[0]) || !ReadAt(slowHandle, 1, slow[0]) ||
!ReadAt(fastHandle, 2, fastPrev[0]) || !ReadAt(slowHandle, 2, slowPrev[0]) ||
!ReadAt(trendHandle, 1, trend[0]) || !ReadAt(atrHandle, 1, atr[0]))
return; // not enough data yet
// --- the signal ----------------------------------------------------
bool bullRegime = (bid > trend[0]); // daily trend agrees
bool crossUp = (fastPrev[0] <= slowPrev[0] && fast[0] > slow[0]);
if(!(bullRegime && crossUp))
return;
// --- levels --------------------------------------------------------
double atrVal = atr[0];
double stopPrice = NormalizeDouble(bid - atrVal * AtrSLMult, _Digits);
double tpPrice = NormalizeDouble(ask + atrVal * AtrTPMult, _Digits);
double stopPts = (bid - stopPrice) / _Point;
long stopsLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
if(stopPts < (double)stopsLevel)
{
Print("Stop ", DoubleToString(stopPts, 0), " pts is inside broker minimum ",
stopsLevel, " - skipping rather than widening the stop.");
return;
}
// --- sizing --------------------------------------------------------
double riskMoney = AccountInfoDouble(ACCOUNT_EQUITY) * RiskPercent / 100.0;
double lots = CalcLots(_Symbol, riskMoney, stopPts);
if(lots <= 0)
{
Print("Calculated size is zero - symbol limits make this unriskable at ",
DoubleToString(riskMoney, 2));
return;
}
double margin = 0.0;
if(OrderCalcMargin(ORDER_TYPE_BUY, _Symbol, lots, ask, margin))
{
if(margin > AccountInfoDouble(ACCOUNT_MARGIN_FREE))
{
Print("Not enough free margin. Need ", DoubleToString(margin, 2),
" have ", DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_FREE), 2));
return;
}
}
// --- send ----------------------------------------------------------
bool ok = trade.Buy(lots, _Symbol, 0.0, stopPrice, tpPrice, "lesson17");
Print("BUY ", ok ? "sent" : "FAILED",
" lots=", DoubleToString(lots, 2),
" sl=", DoubleToString(stopPrice, _Digits),
" tp=", DoubleToString(tpPrice, _Digits),
" retcode=", trade.ResultRetcode(),
" (", trade.ResultRetcodeDescription(), ")");
}