Magic numbers in MQL5: managing multiple EAs on one account

Published 2026-09-17 · Ranar Algo

The terminal does not police magic numbers. It stores whatever integer you attach to an order and hands it back later, with no check that another expert has already claimed it. Treat that integer as a partition key rather than a label, and running several strategies on one account stops being a fight.

What the magic actually tags

Every order you send carries a magic, set through MqlTradeRequest.magic or, more conveniently, CTrade::SetExpertMagicNumber(). When the order fills, the resulting position inherits it, and every deal written to history carries it too. That last part matters more than the first: DEAL_MAGIC is what lets you reconstruct a single strategy's P&L months later, long after the positions are closed and forgotten.

A magic of 0 usually means "not set", which in practice means you clicked the button yourself. Keep it that way — it gives manual interventions a bucket of their own.

Netting accounts break the model

Worth saying plainly. On a netting account there is one position per symbol, so two EAs trading the same pair merge into a single position carrying whichever magic opened it first. The second EA's filter will not find its own trades, and its exit logic will either do nothing or close someone else's work. Magic-based separation only holds reliably on hedging accounts, or on netting accounts where each EA sticks to its own symbols. Check the account's margin mode before building anything on this.

Rules that hold up

1. One magic per running instance, exposed as an input. Never hardcode it inside the EA body.

2. Reserve 0 for manual trades and refuse to start if the input is 0.

3. Filter by symbol *and* magic. Magic alone collides across symbols if you ever reuse a block.

4. Never loop PositionsTotal() to count, modify or close without both filters. This is the most common way one EA wrecks another.

5. Run a collision check in OnInit. At start-up you own nothing, so any live position already carrying your magic belongs to somebody else.

6. Query history by magic rather than by time window alone when reporting.

7. Retire magics instead of recycling them. Reassigning an old number to a new strategy makes the history unreadable.

A filter that compiles

The functions below are the whole trick. PositionGetTicket(i) selects a position as a side effect, so every PositionGetString / PositionGetInteger call straight after it refers to that one position.

//+------------------------------------------------------------------+
//|                                              MagicScope.mq5      |
//|   Attribute every position to one EA and touch nothing else.     |
//+------------------------------------------------------------------+
#property version "1.00"

#include <Trade\Trade.mqh>

input long   InpMagic  = 20260101;  // unique per running instance
input double InpTarget = 50.0;      // close own basket at this profit
input string InpTag    = "ScopeDemo";

CTrade trade;

//--- how many positions this EA owns on this symbol
int OwnCount(const string symbol, const long magic)
  {
   int count = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0)
         continue;
      if(PositionGetString(POSITION_SYMBOL) != symbol)
         continue;
      if(PositionGetInteger(POSITION_MAGIC) != magic)
         continue;
      count++;
     }
   return count;
  }

//--- floating P/L of this EA's slice, swaps included
double OwnProfit(const string symbol, const long magic)
  {
   double profit = 0.0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0)
         continue;
      if(PositionGetString(POSITION_SYMBOL) != symbol)
         continue;
      if(PositionGetInteger(POSITION_MAGIC) != magic)
         continue;
      profit += PositionGetDouble(POSITION_PROFIT)
              + PositionGetDouble(POSITION_SWAP);
     }
   return profit;
  }

//--- close only what we own, ticket by ticket
void CloseOwn(const string symbol, const long magic)
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0)
         continue;
      if(PositionGetString(POSITION_SYMBOL) != symbol)
         continue;
      if(PositionGetInteger(POSITION_MAGIC) != magic)
         continue;
      if(!trade.PositionClose(ticket))
         PrintFormat("close failed ticket=%I64u ret=%d",
                     ticket, trade.ResultRetcode());
     }
  }

int OnInit()
  {
   trade.SetExpertMagicNumber(InpMagic);
   trade.SetTypeFillingBySymbol(_Symbol);
   trade.SetDeviationInPoints(20);

   if(InpMagic <= 0)
     {
      Print("Refusing to run: magic 0 is the manual-trading bucket.");
      return INIT_PARAMETERS_INCORRECT;
     }

   //--- we own nothing yet, so any match is a collision
   const int foreign = OwnCount(_Symbol, InpMagic);
   if(foreign > 0)
      PrintFormat("[%s] collision: %d position(s) on %s already use magic %I64d.",
                  InpTag, foreign, _Symbol, InpMagic);

   return INIT_SUCCEEDED;
  }

void OnTick()
  {
   //--- entries are omitted here; trade.Buy/Sell carry the magic for you
   if(OwnCount(_Symbol, InpMagic) == 0)
      return;

   if(OwnProfit(_Symbol, InpMagic) >= InpTarget)
      CloseOwn(_Symbol, InpMagic);
  }

Allocate blocks, not numbers

Once you run more than two or three experts, invent a scheme and write it down. A readable one: magic = strategy * 100000 + symbolIndex * 1000 + instance. Strategy 4 on symbol 2, instance 1 gives 402001. You can decode the components later, reserve ranges for future symbols, and you never have to remember which number was free. The field holds a long, so six digits cost nothing.

Hashing strategy name plus symbol also works, but then you can only match a magic, never read it.

Attribution is the payoff

With consistent magics, per-strategy P&L is a short loop: HistorySelect(from, to), walk HistoryDealsTotal(), read DEAL_MAGIC, DEAL_PROFIT, DEAL_SWAP and DEAL_COMMISSION, and accumulate into a map keyed by magic. That gives you a breakdown the account curve cannot. A strategy can look perfectly respectable on win rate and still be the thing draining the account — [see a real backtest](https://mql.ranartech.com/r/6V4RG96MUmMI), an AUDUSD M15 EA that won 60% of its 565 trades and finished $2,517 down. Blended into a three-EA account, that would just look like a bad month.

Where people slip

None of this is clever. It is just discipline applied to a single integer, and it is the difference between a portfolio of strategies and a pile of experts shouting over each other.

Build & backtest your own EA free →

Ranar Algo turns a plain-English strategy into a compiled MQL5 Expert Advisor with a real MetaTrader 5 backtest. You keep your IP. Past performance does not guarantee future results.