FX NOOKAI CODE LAB
Loading

How to Create an EA in MQL5: Code, Compile, Test

By FXNook Lab team

To create an EA in MQL5, make an Expert Advisor file in MetaEditor, add inputs and decision rules, handle those rules in OnTick, compile the program, and test it in the MT5 Strategy Tester. An EA can monitor market data and send or manage trade requests; an indicator calculates or displays information but does not place trades by itself. The example below is a starting point for learning the structure, not a claim about trading performance.

What an MQL5 EA needs to do

An Expert Advisor is a program attached to a chart or run in the Strategy Tester. MT5 calls event functions such as OnInit when the EA starts, OnDeinit when it stops, and OnTick when a new price tick arrives. The EA uses those events to initialize resources, check conditions, and submit or manage trade requests.

A useful first version has four clear parts: configurable inputs, any indicators or data it needs, signal conditions, and trade execution. Keeping these parts separate makes it easier to see whether a problem is in the rule, the order request, or the platform setup. Start with simple rules and add complexity only after you can test each part.

Create the EA file in MetaEditor

  1. Open MetaTrader 5 and launch MetaEditor with F4, or use the MetaEditor button in MT5.
  2. In MetaEditor, choose File > New, select Expert Advisor (template), and continue through the wizard.
  3. Give the file a clear name, such as MovingAverageStarter. Finish the wizard and open the generated .mq5 source file.
  4. Find the generated OnInit, OnDeinit, and OnTick functions. Initialization belongs in OnInit, cleanup belongs in OnDeinit, and the EA's repeated decision process normally starts in OnTick.

The template is a valid shell, not a finished trading system. You can add your own code to it or replace the template contents with the complete scaffold below. Save the file in the MetaEditor project before compiling so that the source and compiled program stay together.

Add inputs and organize the decision logic

Inputs let you change a setting from the EA properties or Strategy Tester without editing the source each time. For example, a moving-average crossover EA might expose the fast and slow periods, trade volume, stop-loss distance, and target distance. Inputs should have clear names and units; a value in points is not the same thing as a price value or a percentage.

Separate the signal from the order request. In the example, the signal compares two exponential moving averages on completed bars: a buy signal occurs when the faster average was at or below the slower one on the earlier closed bar and is above it on the latest closed bar. The reverse is the sell signal. This illustrates how to structure a rule; it does not establish that the rule is suitable or profitable.

In MQL5, iMA creates an indicator handle. The EA then reads indicator values with CopyBuffer. This differs from the direct-value indicator calls commonly used in MQL4, so copying MT4 indicator code into an MQL5 EA without adapting it is a common source of errors.

Connect the rule to MQL5 trade functions

This MQL5 example uses the standard CTrade class from Trade/Trade.mqh. It checks for a new bar before reading signals, uses the two most recently completed bars, and sends at most one order per symbol while any position is open on that symbol. The latter is a deliberate simple policy: it does not distinguish a position opened by this EA from one opened manually or by another EA.

#property strict
#include <Trade/Trade.mqh>

input int FastPeriod = 10;
input int SlowPeriod = 30;
input double Lots = 0.10;
input int StopLossPoints = 300;
input int TakeProfitPoints = 600;
input ulong MagicNumber = 26001;
input ulong MaxDeviationPoints = 20;

CTrade trade;
int fastHandle = INVALID_HANDLE;
int slowHandle = INVALID_HANDLE;
datetime lastBarTime = 0;

int OnInit()
{
   if(FastPeriod < 1 || SlowPeriod <= FastPeriod || Lots <= 0.0 ||
      StopLossPoints <= 0 || TakeProfitPoints <= 0)
   {
      Print("Invalid input values.");
      return INIT_PARAMETERS_INCORRECT;
   }

   fastHandle = iMA(_Symbol, _Period, FastPeriod, 0, MODE_EMA, PRICE_CLOSE);
   slowHandle = iMA(_Symbol, _Period, SlowPeriod, 0, MODE_EMA, PRICE_CLOSE);
   if(fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE)
   {
      Print("Could not create moving-average handles.");
      return INIT_FAILED;
   }

   trade.SetExpertMagicNumber(MagicNumber);
   trade.SetDeviationInPoints(MaxDeviationPoints);
   if(!trade.SetTypeFillingBySymbol(_Symbol))
   {
      Print("Could not set the symbol filling mode.");
      return INIT_FAILED;
   }

   lastBarTime = iTime(_Symbol, _Period, 0);
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   if(fastHandle != INVALID_HANDLE)
      IndicatorRelease(fastHandle);
   if(slowHandle != INVALID_HANDLE)
      IndicatorRelease(slowHandle);
}

bool IsNewBar()
{
   datetime barTime = iTime(_Symbol, _Period, 0);
   if(barTime == 0 || barTime == lastBarTime)
      return false;

   lastBarTime = barTime;
   return true;
}

bool ReadSignals(bool &buySignal, bool &sellSignal)
{
   double fast[2];
   double slow[2];

   if(CopyBuffer(fastHandle, 0, 1, 2, fast) != 2 ||
      CopyBuffer(slowHandle, 0, 1, 2, slow) != 2)
      return false;

   // With start position 1 and count 2, index 0 is bar 2
   // and index 1 is the latest completed bar, bar 1.
   buySignal = (fast[0] <= slow[0] && fast[1] > slow[1]);
   sellSignal = (fast[0] >= slow[0] && fast[1] < slow[1]);
   return true;
}

void SendMarketOrder(const bool isBuy)
{
   double point = 0.0;
   double ask = 0.0;
   double bid = 0.0;
   if(!SymbolInfoDouble(_Symbol, SYMBOL_POINT, point) || point <= 0.0 ||
      !SymbolInfoDouble(_Symbol, SYMBOL_ASK, ask) ||
      !SymbolInfoDouble(_Symbol, SYMBOL_BID, bid))
   {
      Print("Could not read the symbol price or point size.");
      return;
   }

   int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   double entry = isBuy ? ask : bid;
   double sl = isBuy ? entry - StopLossPoints * point
                     : entry + StopLossPoints * point;
   double tp = isBuy ? entry + TakeProfitPoints * point
                     : entry - TakeProfitPoints * point;
   sl = NormalizeDouble(sl, digits);
   tp = NormalizeDouble(tp, digits);

   bool sent;
   if(isBuy)
      sent = trade.Buy(Lots, _Symbol, 0.0, sl, tp, "MA cross buy");
   else
      sent = trade.Sell(Lots, _Symbol, 0.0, sl, tp, "MA cross sell");

   uint code = trade.ResultRetcode();
   bool accepted = sent && (code == TRADE_RETCODE_DONE ||
                             code == TRADE_RETCODE_DONE_PARTIAL ||
                             code == TRADE_RETCODE_PLACED);
   if(!accepted)
      PrintFormat("Trade request failed: %u, %s", code,
                  trade.ResultRetcodeDescription());
   else
      PrintFormat("Trade request accepted: %s, order %I64u, deal %I64u",
                  trade.ResultRetcodeDescription(), trade.ResultOrder(),
                  trade.ResultDeal());
}

void OnTick()
{
   if(!IsNewBar())
      return;

   if(PositionSelect(_Symbol))
   {
      Print("A position already exists on this symbol; no new order sent.");
      return;
   }

   bool buySignal = false;
   bool sellSignal = false;
   if(!ReadSignals(buySignal, sellSignal))
   {
      Print("Indicator data is not ready.");
      return;
   }

   if(buySignal)
      SendMarketOrder(true);
   else if(sellSignal)
      SendMarketOrder(false);
}

The check in IsNewBar prevents a crossover from triggering an order on every tick of the same candle. The signals use closed bars rather than the still-forming current bar, which avoids treating a temporary intrabar movement as a completed crossover. If the EA has no new signal, it does nothing.

CTrade.Buy and CTrade.Sell submit market requests. Their Boolean return value alone is not proof that the broker executed the trade, so the example also reads ResultRetcode and prints its description. A rejection can relate to symbol trading permissions, volume limits, filling rules, price changes, or stop distances. The scaffold sets a filling mode for the symbol, but its stop distances and volume still need to be checked against the broker's symbol settings.

The sample's stop and target are calculated in points using SYMBOL_POINT and normalized to the symbol's digits. It does not calculate position size from account risk, manage an open position, or adapt its stops to every broker's minimum-distance rules. Those are separate requirements to specify and implement before treating the code as a complete EA.

Compile and fix errors in MetaEditor

  1. Save the source file, then press F7 or choose Compile in MetaEditor.
  2. Read the errors and warnings in the bottom Toolbox panel. Double-click an error to jump to the relevant line, and read the message as well as the line number.
  3. Fix the first real error, compile again, and repeat. One missing include or misspelled identifier can cause several later messages that are only follow-on errors.
  4. When compilation succeeds, note the reported error and warning counts. Fix warnings you do not understand rather than assuming they are harmless.

Common errors include a missing #include <Trade/Trade.mqh> for CTrade, an undeclared variable caused by a spelling or scope problem, and a function call with the wrong number or type of parameters. MQL5 also requires the correct event-function signatures: for example, use void OnTick() and int OnInit(). A clean compile means the source passed the compiler; it does not prove that the EA's decisions or trade requests behave correctly.

Message or symptomTypical causeNext action
CTrade or a trade method is undeclaredThe standard trade class include is missing or misspelled.Add #include <Trade/Trade.mqh> and compile again.
An identifier is undeclaredThe name was misspelled, declared in another scope, or never declared.Compare the exact identifier at the error and its declaration.
Wrong parameters or no matching functionThe call does not match the MQL5 function signature.Check the function name, argument order, and argument types in the code and documentation.
Compilation passes but no order appearsThe signal may not occur, indicator data may not be ready, or an order may be rejected.Use Strategy Tester logs and trade results to distinguish these cases.

Test the EA in Strategy Tester

  1. In MT5, open Strategy Tester, commonly with Ctrl+R, and select the compiled EA.
  2. Choose the symbol and timeframe the rules are intended to use, then set a historical date range.
  3. Choose a test mode appropriate to the logic. For rules that depend on intrabar price movement, use a tick-based mode; where available, Every tick based on real ticks uses real tick history for the test period. Use visual mode when you need to see where signals occur on the chart.
  4. Set the EA inputs and starting test conditions, then run the test.
  5. Inspect the Journal and trade results. Match the EA's printed signal or request messages to the entries, exits, and any retcodes shown by the tester.

The first question is not whether the result looks attractive. Check whether the EA followed the written rule: did it use the intended timeframe, evaluate the completed bars, avoid repeated entries on one candle, and send an order only when the signal condition was true? If an order was rejected, use the retcode and Journal message to identify the reason rather than changing the signal rule at random.

Test more than one condition when relevant. For example, compare a visual run with a non-visual run, and test the symbol settings and timeframe you expect to use. Historical results depend on the test data and assumptions; past or tested results do not guarantee future results. For more setup detail, see MT5 EA Source Code: Build and Test a Working Example and MT5 Strategy Tester Not Working: Causes and Fixes.

Check settings before attaching the EA to a chart

Use a demo environment or the Strategy Tester while checking behavior. An EA written for one account or execution setup may need changes for another, especially where position handling, symbol constraints, and order filling differ. Keep a copy of the source before making changes so that you can compare behavior between versions.

Build a custom EA from a written strategy

If you want to automate your own rules, write them precisely before coding. State what market data and timeframe the EA uses, what exactly triggers a buy or sell, and whether a signal is checked on a completed bar or during the current bar. Define what cancels a pending entry and what happens if the EA restarts while a position is open.

Specify exits and limits just as clearly as entries. Include how stop loss and target prices are set, whether there is a trailing or break-even rule, how position size is calculated, and how many positions or orders may exist at once. Add any trading hours, spread limits, day-of-week filters, or daily loss rules, including what the EA should do when a condition is reached. Avoid phrases such as “strong trend” unless you can describe how the EA measures that condition.

The FXNook AI EA builder lets you describe rules or design them step by step, then asks for missing details and shows a strategy review before building. It builds MT4 or MT5 EAs from the confirmed specification and compiles and checks the result. Whatever method you use, compare the finished EA against the written rules in Strategy Tester before considering any further use.

Next step

Create the template, compile a small version, and test one behavior at a time. If you want help turning a defined strategy into an EA, start with the FXNook AI EA builder; for answers about how the Lab works, see the FXNook Lab FAQ.

Questions

Do I need to know C++ to create an EA in MQL5?

No. MQL5 has a syntax and concepts that will feel familiar to C++ programmers, but you can build a basic EA by learning MQL5 event functions, inputs, indicator data, and trade classes. Understanding variables, conditions, and functions is a useful starting point.

How do I compile an MQL5 EA in MetaEditor?

Open the EA's `.mq5` source file in MetaEditor, save it, and press F7 or choose Compile. Read any messages in the Toolbox panel, correct the errors, and compile again; a successful build creates the compiled EA file.

How can I test an MQL5 EA before using it on a live account?

Select the EA in MT5 Strategy Tester, choose a symbol, timeframe, date range, and suitable test mode, then run it and inspect the Journal and trade results. A demo account can also help you observe platform behavior, but neither historical nor demo results guarantee future trading results.