MQL5 Magic Numbers: Safely Manage Your EA Positions

MQL5 Magic Numbers: Safely Manage Your EA Positions

By HorizonAI Team · 12 min read · Intermediate

A magic number is the boundary between an EA managing its own trade and an EA accidentally managing somebody else's. The risk shows up the moment you run a trend EA beside a mean-reversion EA, or place a manual position in the same MT5 account.

Short answer: set an input ulong MagicNumber, call trade.SetExpertMagicNumber(MagicNumber) before sending orders, then check POSITION_MAGIC before every position modification or close. Use the same magic number for one EA instance or strategy variant, and never manage a position merely because its symbol matches.

A complete implementation also filters by symbol, loops through open positions by ticket, and sends the ticket to CTrade when it closes or modifies a position. That ownership check matters far more than the entry rule. The EMA-crossover EA below uses it on every exit.

What an MQL5 magic number actually identifies

A magic number is an unsigned integer attached to an EA-originated trade request. Think of it as an ownership label, not a unique ID for one individual order. Your EA should assign its label before opening an order, then use the stored position magic property to decide whether it may touch that position.

That distinction prevents a common failure. Symbol-only management looks harmless while one EA runs on EURUSD. Add a second EA on EURUSD, or open a discretionary EURUSD trade, and a symbol-only exit function can close the wrong position.

CTrade.SetExpertMagicNumber() sets the expert magic number used by the CTrade object’s operations. MetaQuotes documents the method in the standard-library reference: CTrade::SetExpertMagicNumber. Set it during initialization, before Buy(), Sell(), or pending-order calls.

A practical numbering scheme is simple:

EA or instanceExample magic numberWhy it is distinct
EURUSD EMA crossover26001Base strategy on EURUSD
EURUSD EMA crossover, H126002Separate timeframe instance
GBPUSD EMA crossover26101Separate symbol instance
EURUSD RSI mean reversion27001Separate strategy family

The number itself has no trading meaning. Pick a stable convention, record it in the EA inputs, and avoid reusing a number for unrelated systems that can trade the same account.

Set the magic number before the EA can trade

Start with an input, not a hard-coded literal. That lets you attach multiple copies of the EA to charts without editing source code.

  1. Add input ulong MagicNumber = 26001; near the other configuration inputs.
  2. Create one CTrade object at global scope.
  3. In OnInit(), call trade.SetExpertMagicNumber(MagicNumber).
  4. Leave the magic number unchanged while the EA has positions to manage.
  5. Gate every close or modification with POSITION_MAGIC == MagicNumber.

The fourth step is easy to miss. If you change an EA input from 26001 to 26002 while a 26001 position remains open, the new instance will correctly refuse to manage that older trade. Either restore the original input until it is flat, or use a deliberate migration routine.

The code below is a compact, full EA rather than an isolated magic-number fragment. It opens on a confirmed 9/21 EMA crossover, places a 300-point stop and 600-point target, and closes only an opposite-direction position with this EA’s magic number. It processes a signal once per completed bar, so it does not repeatedly send a market request on every tick.

#property strict
#property version   "1.00"

#include <Trade/Trade.mqh>

input ulong  MagicNumber       = 26001;
input double Volume            = 0.10;
input int    StopLossPoints    = 300;
input int    TakeProfitPoints  = 600;
input int    DeviationPoints   = 20;
input string SymbolFilter      = "";
input int    FastEMAPeriod     = 9;
input int    SlowEMAPeriod     = 21;

CTrade trade;

string   tradeSymbol;
int      fastEMAHandle = INVALID_HANDLE;
int      slowEMAHandle = INVALID_HANDLE;
datetime lastBarTime   = 0;

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

   lastBarTime = currentBarTime;
   return true;
}

bool IsOurPosition(const ulong ticket)
{
   if(!PositionSelectByTicket(ticket))
      return false;

   string positionSymbol = PositionGetString(POSITION_SYMBOL);
   ulong positionMagic   = (ulong)PositionGetInteger(POSITION_MAGIC);

   return positionSymbol == tradeSymbol && positionMagic == MagicNumber;
}

bool HasOurPosition(const ENUM_POSITION_TYPE positionType)
{
   for(int index = PositionsTotal() - 1; index >= 0; index--)
   {
      ulong ticket = PositionGetTicket(index);
      if(ticket == 0 || !IsOurPosition(ticket))
         continue;

      if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == positionType)
         return true;
   }

   return false;
}

void CloseOurPositions(const ENUM_POSITION_TYPE positionType)
{
   for(int index = PositionsTotal() - 1; index >= 0; index--)
   {
      ulong ticket = PositionGetTicket(index);
      if(ticket == 0 || !IsOurPosition(ticket))
         continue;

      if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) != positionType)
         continue;

      if(!trade.PositionClose(ticket))
         PrintFormat("Could not close ticket %I64u. Retcode: %u", ticket, trade.ResultRetcode());
   }
}

bool OpenBuy()
{
   double point = SymbolInfoDouble(tradeSymbol, SYMBOL_POINT);
   int digits   = (int)SymbolInfoInteger(tradeSymbol, SYMBOL_DIGITS);
   double ask   = SymbolInfoDouble(tradeSymbol, SYMBOL_ASK);

   double stopLoss   = NormalizeDouble(ask - StopLossPoints * point, digits);
   double takeProfit = NormalizeDouble(ask + TakeProfitPoints * point, digits);

   if(!trade.Buy(Volume, tradeSymbol, 0.0, stopLoss, takeProfit, "EMA magic buy"))
   {
      PrintFormat("Buy failed. Retcode: %u", trade.ResultRetcode());
      return false;
   }

   return true;
}

bool OpenSell()
{
   double point = SymbolInfoDouble(tradeSymbol, SYMBOL_POINT);
   int digits   = (int)SymbolInfoInteger(tradeSymbol, SYMBOL_DIGITS);
   double bid   = SymbolInfoDouble(tradeSymbol, SYMBOL_BID);

   double stopLoss   = NormalizeDouble(bid + StopLossPoints * point, digits);
   double takeProfit = NormalizeDouble(bid - TakeProfitPoints * point, digits);

   if(!trade.Sell(Volume, tradeSymbol, 0.0, stopLoss, takeProfit, "EMA magic sell"))
   {
      PrintFormat("Sell failed. Retcode: %u", trade.ResultRetcode());
      return false;
   }

   return true;
}

int OnInit()
{
   tradeSymbol = SymbolFilter == "" ? _Symbol : SymbolFilter;

   if(!SymbolSelect(tradeSymbol, true))
   {
      Print("Cannot select symbol: ", tradeSymbol);
      return INIT_FAILED;
   }

   trade.SetExpertMagicNumber(MagicNumber);
   trade.SetDeviationInPoints(DeviationPoints);

   fastEMAHandle = iMA(tradeSymbol, _Period, FastEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
   slowEMAHandle = iMA(tradeSymbol, _Period, SlowEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);

   if(fastEMAHandle == INVALID_HANDLE || slowEMAHandle == INVALID_HANDLE)
   {
      Print("Could not create EMA handles.");
      return INIT_FAILED;
   }

   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   if(fastEMAHandle != INVALID_HANDLE)
      IndicatorRelease(fastEMAHandle);

   if(slowEMAHandle != INVALID_HANDLE)
      IndicatorRelease(slowEMAHandle);
}

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

   double fastEMA[];
   double slowEMA[];
   ArrayResize(fastEMA, 3);
   ArrayResize(slowEMA, 3);
   ArraySetAsSeries(fastEMA, true);
   ArraySetAsSeries(slowEMA, true);

   if(CopyBuffer(fastEMAHandle, 0, 0, 3, fastEMA) < 3 ||
      CopyBuffer(slowEMAHandle, 0, 0, 3, slowEMA) < 3)
   {
      Print("EMA data is not ready.");
      return;
   }

   bool bullishCross = fastEMA[1] > slowEMA[1] && fastEMA[2] <= slowEMA[2];
   bool bearishCross = fastEMA[1] < slowEMA[1] && fastEMA[2] >= slowEMA[2];

   if(bullishCross)
   {
      CloseOurPositions(POSITION_TYPE_SELL);
      if(!HasOurPosition(POSITION_TYPE_BUY))
         OpenBuy();
   }
   else if(bearishCross)
   {
      CloseOurPositions(POSITION_TYPE_BUY);
      if(!HasOurPosition(POSITION_TYPE_SELL))
         OpenSell();
   }
}

The code calls SetExpertMagicNumber() once in OnInit(). More importantly, IsOurPosition() calls PositionSelectByTicket(), then requires both the configured symbol and configured magic number to match. That same helper protects HasOurPosition() and CloseOurPositions().

Why use the ticket? A position ticket identifies the position instance that is currently being inspected or closed. It is safer than invoking a symbol-based close from a management loop because the loop has already selected a specific position.

Filter positions safely with POSITION_MAGIC

PositionsTotal() tells you how many open positions the terminal currently exposes. PositionGetTicket(index) retrieves a position ticket by index and selects it for subsequent property reads. From there, PositionGetInteger(POSITION_MAGIC) reads the magic value and PositionGetString(POSITION_SYMBOL) confirms the symbol.

MetaQuotes lists POSITION_MAGIC among the integer position properties, and its PositionGetInteger reference explains the property-read call. The position-properties reference identifies POSITION_MAGIC as the expert ID associated with the position.

Use this filter pattern whenever management code does any of the following:

  • Moves a stop to break-even.
  • Trails a stop behind an EMA or ATR value.
  • Partially closes volume.
  • Closes at an opposite signal.
  • Cancels pending orders placed by the EA.
  • Counts open exposure before allowing a new entry.

For a break-even example, the ownership gate belongs before the condition that compares bid or ask with the trigger. That prevents an EA’s break-even module from modifying a discretionary trade. The fuller implementation is covered in our MQL5 break-even stop EA guide.

Orders, deals, positions, and magic numbers are different

MT5 records several related objects. Mixing them up is why a magic-number check sometimes appears to “not work.”

ObjectWhat it representsWhat to use it for
OrderAn instruction to buy, sell, or place a pending orderInspecting or canceling pending instructions
DealAn executed transaction or fillAuditing execution history
PositionThe current open market exposureMoving stops, closing exposure, checking POSITION_MAGIC
Position ticketThe selected position’s identifierTargeting a specific current position
Magic numberAn EA ownership labelSeparating one EA’s management from another’s

An order can generate one or more deals, and deals can create, increase, reduce, or close a position. Do not try to manage an open trade by treating the magic number as a ticket. 26001 is your EA’s label. The ticket is a terminal-assigned identifier, such as the ticket retrieved inside the loop.

For live position management, inspect position properties. For pending-order cleanup, inspect order properties and use the analogous order magic property. For historical reporting, query deal or order history rather than assuming an open position will still exist.

Handle netting accounts before you run multiple systems

The ownership model is cleanest on a hedging account, where several positions can coexist for the same symbol. A buy position from one EA and a sell position from another can appear separately, subject to the broker’s account rules.

A netting account holds only one aggregate position per symbol. New trades on that symbol can increase, reduce, reverse, or flatten the shared exposure. That means two EAs and manual trades should not independently manage the same symbol in a netting account, even if each has a different magic number.

This is the operational rule:

✅ Do this: On a netting account, assign one owner to each symbol. If the EMA EA owns EURUSD, do not run a manual EURUSD strategy or another EURUSD EA that expects isolated stops and exits.

❌ Avoid this: Assume a different magic number creates separate EURUSD positions on a netting account. It labels trade activity, but it cannot override the account’s one-position-per-symbol model.

If you need independent systems on the same instrument, use a hedging account if your broker offers it, or separate the systems across accounts. Confirm the account mode in MT5 before designing the portfolio. The MQL5 PositionsTotal documentation also notes the distinction between netting and hedging behavior.

Debug the magic number from the Experts log

When an EA seems to ignore a position, print what it sees before changing any trade-management logic. Add a temporary diagnostic function, then call it inside your positions loop:

void LogPositionOwnership(const ulong ticket)
{
   if(!PositionSelectByTicket(ticket))
      return;

   PrintFormat("Ticket=%I64u Symbol=%s Magic=%I64u Type=%d",
               ticket,
               PositionGetString(POSITION_SYMBOL),
               (ulong)PositionGetInteger(POSITION_MAGIC),
               (int)PositionGetInteger(POSITION_TYPE));
}

Run it in the Strategy Tester or on a demo chart, then check the Experts tab. You want to see the ticket, symbol, magic number, and position type you expect. If the magic prints as 0, it commonly indicates a manually opened position. If it prints as another value, another EA owns it, and your filter should leave it alone.

Also log the result after each trade request. A CTrade method returning true means the request structure passed its initial checks, not necessarily that the trade server accepted the operation. trade.ResultRetcode() is the value to inspect when an open or close fails. For broader entry failures, use this MT5 EA not opening trades checklist rather than removing the ownership filter.

Common magic-number mistakes that close the wrong trade

❌ Mistake: Filtering only by _Symbol. if(PositionGetString(POSITION_SYMBOL) == _Symbol) gives every EA on that symbol permission to manage the same position.

✅ Do this: Require both symbol and magic number. In the example, return positionSymbol == tradeSymbol && positionMagic == MagicNumber; is the rule that establishes ownership.

❌ Mistake: Calling PositionClose(_Symbol) in a multi-position workflow. It does not express which selected position from your loop you intend to close, and it is especially risky once your account can hold more than one position on a symbol.

✅ Do this: Retrieve a ticket, reselect it with PositionSelectByTicket(ticket), check POSITION_MAGIC, and call trade.PositionClose(ticket) only after that check.

❌ Mistake: Changing MagicNumber while trades are still open. The EA will stop recognizing the old position, then traders often remove filters to “fix” it.

✅ Do this: Keep the original magic input until the strategy is flat. Reserve a new number for the next instance or version.

❌ Mistake: Reusing 10001 for every EA. The filter works exactly as written, but all those EAs claim the same trades.

✅ Do this: Give each strategy-symbol-timeframe combination its own documented number, such as 26001 for EURUSD M15 and 26002 for EURUSD H1.

❌ Mistake: Treating netting as isolated position storage. Separate magic numbers do not convert a netting account into a hedging account.

✅ Do this: Give each netting symbol one decision-maker, including stop management. Coordinate all entry and exit rules inside that EA.

Pro tips for magic-number management

Put the magic number in every EA input set. When you save an .set file for a tester run, the ownership value travels with the rest of the configuration. That makes historical test results easier to reproduce.

Use an intentional range. A range such as 26000–26099 for crossover systems and 27000–27099 for mean-reversion systems makes logs readable. Avoid deriving the magic number from a price, timestamp, or an unstable hash because you need the same EA to recognize its open exposure after a restart.

Check ownership before modification too. Closing is not the only dangerous action. A trailing-stop function that skips the magic check can silently replace the stop on a manual position. If you are extending the EA with a volatility stop, ATR explained is a useful reference for selecting the distance, but retain the same IsOurPosition() gate.

Make pending orders part of the design. This EA uses market orders, so its position filter is enough for exits. A breakout EA that places buy stops and sell stops needs a second filter for orders, using its order magic value before deleting or replacing a pending order.

Generating this ownership layer without writing it yourself

You can ask HorizonAI to generate a compile-checked MQL5 EA or add the ownership layer to an existing one. Be explicit about account mode, symbols, and exactly which positions the EA may manage.

“Create an MQL5 Expert Advisor using CTrade. Add input ulong MagicNumber = 26001, Volume 0.10, StopLossPoints 300, TakeProfitPoints 600, DeviationPoints 20, and SymbolFilter. Trade a confirmed 9/21 EMA crossover once per new bar. Call trade.SetExpertMagicNumber(MagicNumber) in OnInit. Before closing or modifying any position, select it by ticket and require both POSITION_SYMBOL equals SymbolFilter and POSITION_MAGIC equals MagicNumber. Close only this EA’s opposite position, then open the new direction. Include ResultRetcode logging and make it compile in MQL5.”

HorizonAI returns MQL5 code you can edit in chat and validates it with a real compiler. It writes the EA code, but it doesn't connect to a broker or execute trades for you. Try it free →

FAQs

How do I set a magic number in MQL5?

Declare an input ulong MagicNumber, create a CTrade object, and call trade.SetExpertMagicNumber(MagicNumber) in OnInit() before the EA submits orders. Filter positions later with PositionGetInteger(POSITION_MAGIC).

What magic number should I use for my EA?

Any positive ulong value is suitable if it is unique among systems that may manage the same account and symbol. A documented range such as 26001 for one EA instance and 26002 for another is easier to audit than random values.

Is a magic number the same as a position ticket?

No. A magic number is an EA-defined ownership label that can be shared by many positions or orders. A position ticket is a terminal-assigned identifier for one current position.

Can two MQL5 EAs trade the same symbol with different magic numbers?

They can on a hedging account, provided each EA filters by magic number before management. On a netting account, they should not independently manage the same symbol because the account maintains one aggregate position per symbol.

Final thoughts

A magic number is not decorative metadata. It is the permission check that keeps an EA from closing, resizing, or moving the stop on a trade it did not create. Set it before the first order, pair it with a symbol check, and use tickets for each individual management action.

The practical pro tip is to test with a manual position and a second EA position on the same symbol before going live. Your EA should log both positions, recognize only its own magic number, and leave the other one untouched.

Related articles

Questions about MQL5 magic numbers? Join our Discord to discuss with other traders!