MT5 Lab

Build and Run Your First MQL5 Expert Advisor on an MT5 Demo Account

Download, compile, attach, and inspect a small MQL5 Expert Advisor in MetaTrader 5 while learning permissions, inputs, logs, and demo-only safety limits.

한국어 원문 읽기

Key takeaways

The goal of a first EA run is to prove the source-to-log workflow, not to demonstrate a profitable strategy.

Compile the provided `.mq5` source in MetaEditor, attach the resulting EA to a demo chart, and verify its permissions and input units before enabling Algo Trading.

Experts and Journal logs are the primary evidence: they reveal initialization, permission failures, invalid stops, insufficient margin, and closed-market conditions.

Build and run your first MQL5 Expert Advisor on a demo account

Three key takeaways

  • This exercise is successful when the EA compiles, initializes, and leaves understandable logs—not when it makes money.
  • Attach the EA while platform-wide Algo Trading is disabled, inspect every input, and enable execution only on a demo account you have identified clearly.
  • The example is intentionally limited and is not suitable for live trading; read the limitation notes before you run it.

An Expert Advisor (EA) turns MQL5 source code into a program that MetaTrader 5 can execute repeatedly. That power deserves a controlled first exercise. We will use a transparent, deliberately simple EA to learn the full path:

source file → compile → Navigator → demo chart → permissions → Experts/Journal logs

If MetaTrader, MetaEditor, or Strategy Tester is still unfamiliar, begin with the MetaTrader 5 and MQL5 starting guide. This tutorial assumes desktop MT5 is installed and a demo account is connected. It does not require a particular broker, instrument, or funded account.

What this exercise verifies—and what it does not

ScopeThis exercise verifiesThis exercise does not verify
Technical flowThe source compiles, the EA attaches, and logs are understandableA strategy's profitability or long-term reliability
Execution setupPermissions, inputs, and symbol-rule errors can be inspected on demoLive fills, slippage, fees, or broker conditions
EvidenceTerminal build, demo server and account mode, symbol, timeframe, EA version, and inputsThe same outcome in every environment
PrivacyScreenshots and logs can be shared without credentials or account identifiersA broker's complete privacy practices

Treat the example as a controlled functional check. Do not publish account numbers, passwords, access tokens, or personally identifying screenshots while recording your result.

Set up a controlled demo environment

Before opening the source file, verify the environment. Broker servers can use different symbol names, contract sizes, minimum volumes, stop distances, spreads, sessions, and account modes.

CheckWhere to lookWhat to record
Account is demoNavigator and account detailsServer, account currency, leverage, netting or hedging mode
Terminal buildHelp → AboutFull MT5 build and architecture
Symbol rulesMarket Watch → right-click symbol → SpecificationExact symbol, digits, point size, minimum/step volume, stop level, sessions
Platform permissionTop toolbarKeep Algo Trading disabled during setup
Existing exposureToolbox → TradeUse a clean demo symbol with no manual or EA-managed position

A demo account removes direct financial loss from the exercise, but it does not reproduce live liquidity, slippage, latency, or every rejection rule. Demo success is a functional check, not evidence of expected returns.

What the example EA does

The learning rule evaluates one completed candle whenever a new bar starts:

Previous completed candleRequested action
Close is above openClose a selected sell position, then request a buy if no position is selected
Close is below openClose a selected buy position, then request a sell if no position is selected
Close equals openDo nothing

It also requests a fixed volume, stop-loss distance, and take-profit distance. This is not a strategy recommendation. A candle's direction by itself is not an investment thesis, and no performance claim is attached to this example.

Download the complete MQL5 source

Use the checked-in source rather than copying fragments from screenshots:

Download the complete Permact First Candle EA source

The source is reproduced below so you can review exactly what will run.

//+------------------------------------------------------------------+
//|                                      Permact_First_Candle_EA.mq5 |
//|                                      Copyright 2026, Permact     |
//|                                             https://permact.com  |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Permact"
#property link      "https://permact.com"
#property version   "1.10"

#include <Trade/Trade.mqh>

CTrade trade;

input double Lots                = 0.01;
input int    StopLossPoints      = 300;
input int    TakeProfitPoints    = 600;
input int    MinStopBufferPoints = 50;
input ulong  MagicNumber         = 20260708;

datetime lastBarTime = 0;

int OnInit()
  {
   trade.SetExpertMagicNumber(MagicNumber);
   trade.SetAsyncMode(false);
   if (!trade.SetTypeFillingBySymbol(_Symbol))
     {
      Print("Initialization failed: unable to select the filling policy for ", _Symbol);
      return(INIT_FAILED);
     }
   Print("Permact first candle EA started");
   return(INIT_SUCCEEDED);
  }

void OnDeinit(const int reason)
  {
   Print("Permact first candle EA stopped. Reason: ", reason);
  }

void OnTick()
  {
   datetime currentBarTime = iTime(_Symbol, _Period, 0);
   if (currentBarTime == 0 || currentBarTime == lastBarTime)
      return;

   lastBarTime = currentBarTime;

   if (Bars(_Symbol, _Period) < 10)
      return;

   double previousOpen  = iOpen(_Symbol, _Period, 1);
   double previousClose = iClose(_Symbol, _Period, 1);

   if (previousOpen == 0 || previousClose == 0)
      return;

   if (previousClose > previousOpen)
     {
      CloseSellPosition();
      if (!PositionSelect(_Symbol))
         OpenBuy();
     }

   if (previousClose < previousOpen)
     {
      CloseBuyPosition();
      if (!PositionSelect(_Symbol))
         OpenSell();
     }
  }

void OnTrade()
  {
  }

void OpenBuy()
  {
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   if (bid <= 0 || ask <= 0)
      return;

   int slPoints = EffectiveInitialStopPoints(StopLossPoints);
   int tpPoints = EffectiveInitialStopPoints(TakeProfitPoints);

   double sl = 0.0;
   double tp = 0.0;

   if (slPoints > 0)
      sl = AlignPriceDown(bid - slPoints * _Point);

   if (tpPoints > 0)
      tp = AlignPriceUp(ask + tpPoints * _Point);

   bool requestBuilt = trade.Buy(Lots, _Symbol, 0.0, sl, tp, "Permact candle buy");
   TradeRequestCompleted(requestBuilt, "Buy");
  }

void OpenSell()
  {
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   if (bid <= 0 || ask <= 0)
      return;

   int slPoints = EffectiveInitialStopPoints(StopLossPoints);
   int tpPoints = EffectiveInitialStopPoints(TakeProfitPoints);

   double sl = 0.0;
   double tp = 0.0;

   if (slPoints > 0)
      sl = AlignPriceUp(ask + slPoints * _Point);

   if (tpPoints > 0)
      tp = AlignPriceDown(bid - tpPoints * _Point);

   bool requestBuilt = trade.Sell(Lots, _Symbol, 0.0, sl, tp, "Permact candle sell");
   TradeRequestCompleted(requestBuilt, "Sell");
  }

int EffectiveInitialStopPoints(const int requestedPoints)
  {
   if (requestedPoints <= 0)
      return 0;

   int stopLevel = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
   int bufferPoints = MinStopBufferPoints > 0 ? MinStopBufferPoints : 0;
   int minimumPoints = stopLevel + bufferPoints;

   if (requestedPoints < minimumPoints)
      return minimumPoints;

   return requestedPoints;
  }

double AlignPriceDown(const double price)
  {
   double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   if (tickSize <= 0.0)
      tickSize = _Point;

   return NormalizeDouble(MathFloor(price / tickSize) * tickSize, _Digits);
  }

double AlignPriceUp(const double price)
  {
   double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   if (tickSize <= 0.0)
      tickSize = _Point;

   return NormalizeDouble(MathCeil(price / tickSize) * tickSize, _Digits);
  }

bool TradeRequestCompleted(const bool requestBuilt, const string action)
  {
   uint retcode = trade.ResultRetcode();
   ulong deal = trade.ResultDeal();
   ulong order = trade.ResultOrder();

   if (requestBuilt && retcode == TRADE_RETCODE_DONE && deal > 0)
      return true;

   if (retcode == TRADE_RETCODE_DONE_PARTIAL)
     {
      Print(action, " partially filled and requires reconciliation. Retcode: ", retcode, " ",
            trade.ResultRetcodeDescription(), ", deal: ", deal, ", order: ", order);
      return false;
     }

   if (retcode == TRADE_RETCODE_PLACED)
     {
      Print(action, " was placed but is not final; reconciliation is required. Retcode: ", retcode, " ",
            trade.ResultRetcodeDescription(), ", deal: ", deal, ", order: ", order);
      return false;
     }

   if (requestBuilt && retcode == TRADE_RETCODE_DONE && deal == 0)
     {
      Print(action, " returned DONE without a deal ticket; reconciliation is required. Order: ", order);
      return false;
     }

   Print(action, " failed. Retcode: ", retcode, " ", trade.ResultRetcodeDescription(),
         ", deal: ", deal, ", order: ", order);
   return false;
  }

void CloseBuyPosition()
  {
   if (!PositionSelect(_Symbol))
      return;

   long currentType = PositionGetInteger(POSITION_TYPE);
   long currentMagic = PositionGetInteger(POSITION_MAGIC);

   if (currentType != POSITION_TYPE_BUY)
      return;

   if (currentMagic != (long)MagicNumber)
     {
      Print("Skip close: buy position belongs to another strategy. Magic: ", currentMagic);
      return;
     }

   ulong ticket = (ulong)PositionGetInteger(POSITION_TICKET);
   bool requestBuilt = trade.PositionClose(ticket);
   if (TradeRequestCompleted(requestBuilt, "Close buy") && PositionSelectByTicket(ticket))
      Print("Close buy returned a deal but the position remains open; reconciliation is required. Ticket: ", ticket);
  }

void CloseSellPosition()
  {
   if (!PositionSelect(_Symbol))
      return;

   long currentType = PositionGetInteger(POSITION_TYPE);
   long currentMagic = PositionGetInteger(POSITION_MAGIC);

   if (currentType != POSITION_TYPE_SELL)
      return;

   if (currentMagic != (long)MagicNumber)
     {
      Print("Skip close: sell position belongs to another strategy. Magic: ", currentMagic);
      return;
     }

   ulong ticket = (ulong)PositionGetInteger(POSITION_TICKET);
   bool requestBuilt = trade.PositionClose(ticket);
   if (TradeRequestCompleted(requestBuilt, "Close sell") && PositionSelectByTicket(ticket))
      Print("Close sell returned a deal but the position remains open; reconciliation is required. Ticket: ", ticket);
  }
//+------------------------------------------------------------------+

The example uses the standard-library CTrade helper for Buy, Sell, and PositionClose. See the official MQL5 CTrade reference opens in a new tab for the class API and return-code methods.

Read the limitations before compiling

This small program is useful precisely because its assumptions are visible. It is demo-only and must not be treated as production code.

  • It refuses to close a symbol-selected position when its POSITION_MAGIC differs from this EA's MagicNumber.
  • It still assumes a simple one-position-per-symbol learning setup. Hedging accounts can hold multiple positions, which requires ticket-level enumeration and ownership checks.
  • Lots = 0.01 is not universally valid. The symbol may require a different minimum or volume step.
  • “Points” means multiples of the symbol's _Point value. It is not automatically the same as a pip, tick, percentage, or fixed currency amount.
  • Initial protective prices use the advertised stops level plus the configured buffer and are aligned outward to SYMBOL_TRADE_TICK_SIZE. Freeze level is not folded into initial placement; later modify, cancel, or close operations on existing orders and positions must inspect the server rules and TRADE_RETCODE_FROZEN separately.
  • Every open and close request checks the immediate server return code and deal ticket. PLACED, partial fills, and DONE without a deal are logged as states that require reconciliation rather than completed success; full recovery is still not implemented.
  • It does not implement spread limits, session filters, daily loss limits, equity stops, reconnect recovery, persistent state, or complete trade-result reconciliation.

Use a clean demo symbol with no existing position. Production-quality code must establish position ownership, normalize volume and prices from the live symbol specification, validate every trade result, and define a fail-safe state.

Create the EA file in MetaEditor

  1. In desktop MT5, press F4 to open MetaEditor.
  2. Choose File → New → Expert Advisor (template).
  3. Name the file Permact_First_Candle_EA.
  4. Replace the generated template with the complete downloaded source.
  5. Save the file under the terminal's MQL5/Experts directory.
  6. Press F7 to compile.
  7. Confirm that the Errors pane reports 0 errors, 0 warnings.
MetaEditor showing the first-candle EA after a successful compile with zero errors and zero warnings
A successful compile creates the executable EA, but does not validate its trading logic

MetaEditor compiles the editable .mq5 source into an .ex5 program that MT5 can run. If compilation fails, use the first error rather than changing several things at once.

Error patternFirst check
Trade/Trade.mqh not found or related include errorConfirm the include line is exactly #include <Trade/Trade.mqh> and the standard library is installed
Unexpected token, brace, or end of programReplace the entire file from the download; a line may have been omitted while copying
Semicolon expectedOpen the first reported line and compare it with the source above
Compile succeeds but EA is absent in MT5Confirm the file is under MQL5/Experts, then refresh Navigator

The official MT5 help for trading robots and indicators opens in a new tab explains how compiled programs appear in Navigator and are attached to charts.

Find the compiled EA in Navigator

Return to MT5 and open Navigator → Expert Advisors. If Permact_First_Candle_EA is not visible, right-click Expert Advisors and choose Refresh. If it remains absent, return to MetaEditor and use the file's context menu to verify its data-folder location.

Do not copy an .ex5 file from an unknown source merely because it appears in Navigator. The advantage of this exercise is that you possess and can inspect the exact .mq5 source.

Attach it safely and inspect the inputs

Keep the toolbar's Algo Trading control disabled while you attach and inspect the EA.

  1. Confirm the Navigator account is the intended demo account.
  2. Open a clean chart for the exact symbol you recorded earlier.
  3. Select a timeframe. H1 is practical for observing a new-bar example without excessive event frequency.
  4. Drag Permact_First_Candle_EA from Navigator onto the chart.
  5. In Common, review the EA's algorithmic-trading permission.
  6. In Inputs, compare every value with the symbol specification.
  7. Click OK only after confirming the chart, account, and inputs.
InputMeaningValidation step
LotsRequested trade volumeMust meet the symbol's minimum, maximum, and step; 0.01 is only a placeholder
StopLossPointsRequested stop-loss distance in symbol pointsConvert with the symbol's _Point; do not read it automatically as pips
TakeProfitPointsRequested take-profit distance in symbol pointsCheck the resulting price distance against the current quote and specification
MinStopBufferPointsExtra points added beyond the advertised initial stops levelA troubleshooting aid, not a guarantee against rejections
MagicNumberIdentifier assigned to new trade requestsClose helpers skip a selected position when its magic number differs

Enable Algo Trading intentionally

MT5 can require permission at more than one level:

ControlScopeSymptom when disabled
Toolbar Algo TradingPlatform-wideEAs may calculate but trade requests are blocked
EA Common settingsThis attached EAThis EA cannot trade even if the toolbar control is enabled
Tools → Options → Expert AdvisorsPlatform policyGlobal settings can block algorithmic execution
Account/profile change protectionsSession safetyTrading may be disabled after switching account or profile
The MT5 toolbar with the Algo Trading control used to permit or block EA trade requests
Enable Algo Trading only after verifying the demo account, chart symbol, EA, and inputs

After verifying all four, enable Algo Trading for this controlled demo run. The EA evaluates only when it observes a new bar, so an immediate order is not a success requirement. Permission to trade also does not mean the EA's logic has been approved or validated.

Use Experts and Journal as your evidence

Open the Toolbox at the bottom of MT5 and watch both Experts and Journal. The first useful evidence is the initialization message:

Permact first candle EA started

Common messages should lead to a specific check:

Log message or symptomLikely meaningNext action
EA start message is absentEA did not initialize or is attached elsewhereCheck chart label, Navigator, and Experts log around attachment time
auto trading disabled by clientOne or more permission layers are offRecheck toolbar, EA Common settings, and platform options—on demo only
invalid stopsRequested protective prices violate current symbol/server rulesRecord bid/ask and symbol stop level, then follow the invalid-stops diagnostic guide
not enough moneyRequested volume exceeds available demo marginVerify symbol contract size and reduce the demo volume to a valid step
market closedThe symbol is outside its trading sessionRead the symbol's session schedule; do not repeatedly retry
No order and no error before a new barThe EA has not reached its evaluation boundaryWait for a new bar or use Strategy Tester for controlled replay

Record the timestamp, symbol, timeframe, inputs, bid/ask, and complete return-code description when a request fails. “It did not trade” is not enough information to reproduce a defect.

Stop and clean up the exercise

When you have observed initialization and at least one decision boundary:

  1. Disable Algo Trading.
  2. Save the Experts and Journal entries you need.
  3. Remove the EA from the chart.
  4. Review and close any remaining demo position deliberately; disabling an EA does not automatically flatten the account.
  5. Save the source version, input values, terminal build, account mode, symbol specification, and observation period together.

Do not move this EA to a live chart. Its incomplete ownership and trade-reconciliation safeguards are intentional teaching constraints, not production-ready design choices.

Completion checklist

The exercise is complete when you can check every item below:

  • [ ] The exact .mq5 source is saved in MQL5/Experts.
  • [ ] MetaEditor reports 0 errors, 0 warnings.
  • [ ] The EA appears in MT5 Navigator.
  • [ ] You attached it to the intended demo chart with Algo Trading initially disabled.
  • [ ] You validated volume and point-based inputs against the symbol specification.
  • [ ] Experts contains the initialization message.
  • [ ] You can explain the account-mode, magic-number, and position-ownership limitations.
  • [ ] You disabled the EA and reconciled any remaining demo position after the exercise.

Profit, win rate, and account balance are deliberately absent from this checklist. They do not validate the installation or the code path.

Up next

Next, run this same source through the MT5 Strategy Tester backtesting workflow. You will lock the symbol, timeframe, date range, model, deposit, leverage, and inputs; inspect Visual mode and logs; and save a test package that someone else can reproduce.