AlgoPro UniversityCourse home
Part 9 · Lesson

Building a moving-average-cross EA

The full strategy skeleton: fast/slow MA inputs, both handles created in OnInit, once-per-bar cross detection in OnTick — the complete signal engine, ready for orders.

Time to build a real strategy. The moving-average cross is the "hello world" of trend following, and it is a genuinely deployable idea: when a fast average crosses *above* a slow one, momentum has turned up → go long; when it crosses *below*, momentum has turned down → go short. We built the concept in Python back in Part 4; now we build it as an EA.

The cross that fires a signal
Fast EMA (violet) crossing the slow EMA (blue). The up-cross around bar 9 is a long signal; the down-cross near bar 16 is a short signal.

Detect the cross, not the state

The signal is not "fast is above slow" — that is true for dozens of bars in a row and would fire endlessly. The signal is the moment of crossing, and to see a moment you need *two* snapshots: the last closed bar and the one before it.

Act once per bar, not once per tick

Because OnTick runs constantly, we gate the whole strategy behind a new-bar check: remember the open time of the last bar we processed, and only run the logic when a fresh bar appears. Everything else on the tick is a no-op. This is the standard MQL5 idiom for a bar-based strategy.

mql5The new-bar gate
datetime lastBarTime = class="n">0;   class="c">// global: open time of the bar we last handled

class="c">// Returns true exactly once per newly-opened bar.
bool IsNewBar()
{
   datetime t = iTime(_Symbol, _Period, class="n">0);   class="c">// open time of the current bar
   if(t == lastBarTime)
      return(false);
   lastBarTime = t;
   return(true);
}

The complete signal EA

Here is the full strategy engine — inputs, both handles built in OnInit, the new-bar gate, and cross detection in OnTick. It does not place orders yet: it prints the signal so you can verify the logic in isolation before wiring in real trades next lesson. Building the signal and the execution separately is exactly the discipline from Part 1's five-component blueprint.

mql5MaCrossSignal.mq5 — complete, compiles, prints signals
class="c">#property copyright class="s">"AlgoPro University"
class="c">#property version   class="s">"class="n">1.00"
class="c">#property description class="s">"MA-cross signal engine — demo/testing, not financial advice."

input int              FastPeriod = class="n">12;             class="c">// Fast EMA period
input int              SlowPeriod = class="n">26;             class="c">// Slow EMA period
input ENUM_MA_METHOD   MaMethod   = MODE_EMA;       class="c">// MA method
input ENUM_APPLIED_PRICE MaPrice  = PRICE_CLOSE;    class="c">// Applied price

int      fastHandle = INVALID_HANDLE;
int      slowHandle = INVALID_HANDLE;
datetime lastBarTime = class="n">0;

int OnInit()
{
   if(FastPeriod >= SlowPeriod)
   {
      Print(class="s">"FastPeriod must be smaller than SlowPeriod.");
      return(INIT_PARAMETERS_INCORRECT);
   }

   fastHandle = iMA(_Symbol, _Period, FastPeriod, class="n">0, MaMethod, MaPrice);
   slowHandle = iMA(_Symbol, _Period, SlowPeriod, class="n">0, MaMethod, MaPrice);

   if(fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE)
   {
      Print(class="s">"Could not create MA handles: ", GetLastError());
      return(INIT_FAILED);
   }
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   IndicatorRelease(fastHandle);   class="c">// free the indicator resources
   IndicatorRelease(slowHandle);
}

class="c">// Fills fast[] and slow[] with the last two CLOSED bar values.
class="c">// Returns false if data is not ready.
bool ReadMAs(double &fast[], double &slow[])
{
   ArraySetAsSeries(fast, true);
   ArraySetAsSeries(slow, true);
   class="c">// Start at index class="n">1 (last closed bar), copy class="n">2 values -> [class="n">1] and [class="n">2].
   if(CopyBuffer(fastHandle, class="n">0, class="n">1, class="n">2, fast) < class="n">2) return(false);
   if(CopyBuffer(slowHandle, class="n">0, class="n">1, class="n">2, slow) < class="n">2) return(false);
   return(true);
}

void OnTick()
{
   datetime t = iTime(_Symbol, _Period, class="n">0);
   if(t == lastBarTime) return;   class="c">// only act on a new bar
   lastBarTime = t;

   double fast[], slow[];
   if(!ReadMAs(fast, slow)) return;

   double fastPrev = fast[class="n">1], fastLast = fast[class="n">0];   class="c">// [class="n">0]=last closed, [class="n">1]=one before
   double slowPrev = slow[class="n">1], slowLast = slow[class="n">0];

   bool crossUp   = (fastPrev <= slowPrev) && (fastLast > slowLast);
   bool crossDown = (fastPrev >= slowPrev) && (fastLast < slowLast);

   if(crossUp)   Print(class="s">"BULLISH cross on ", _Symbol, class="s">" — long signal");
   if(crossDown) Print(class="s">"BEARISH cross on ", _Symbol, class="s">" — short signal");
}

Compile this, attach it to a demo chart, and watch the Experts log print BULLISH/BEARISH as crosses occur. The signal engine works. Now we give it hands: the next lesson turns each printed signal into a real order with the CTrade class.