モジュール 1 / 7

Your First EA Places No Trades

Keisuke Kurosawa · 公開: 2026-09-16

Most people's first Expert Advisor places a trade. That is the wrong place to start, and it is why so many first EAs lose money in ways their author never sees coming.

The EA you are going to build in this module places no trades at all. It reads the market, decides what it thinks, and publishes that decision. Nothing else. You can attach it to a live account on a Monday morning and the worst it can do is be wrong in a text file.

That constraint is not a training-wheels exercise. The real one is running right now — it is what fills the signals page, and its source is Signals/D1_Signal_Trend.mq5 in the public repository. By the end of this module you will understand every decision in it, including four that are invisible until they cost you something.

The rule that separates working EAs from broken ones

Here is the most important thing in the whole file, and it is four characters wide:

bool GetCloseAtShift(const string sym, const int shift, double &val)
{
   double buf[];
   ArraySetAsSeries(buf, true);
   if(CopyClose(sym, InpTf, shift, 1, buf) != 1) return false;
   val = buf[0];
   return true;
}

That shift is how many bars back from the current one. Shift 0 is the bar forming right now. Shift 1 is the last bar that has closed.

Every read in the EA passes 1:

   if(!GetCloseAtShift(sym, 1, close1)) return false;
   if(!GetBufAtShift(g_hEmaFast[i], 0, 1, emaFast1)) return false;
   if(!GetBufAtShift(g_hEmaSlow[i], 0, 1, emaSlow1)) return false;

   // iADX buffer 0 is ADX in MQL5
   if(!GetBufAtShift(g_hAdx[i], 0, 1, adx1)) return false;

Do not let the zeros confuse you: in GetBufAtShift the first number is the indicator buffer and the second is the shift. An MQL5 indicator can expose several buffers — iADX publishes ADX on buffer 0, +DI on 1 and −DI on 2 — so buffer 0, shift 1 means "the ADX value of the last closed bar". Shift 0 appears nowhere in the file.

The reason is that the current bar lies. Its close is not a close, it is just the last price that happened to print. An EMA calculated on it moves every tick. A condition like "EMA20 crossed above EMA50" can be true at 14:03, false at 14:07, and true again at 14:58 — the same bar, the same indicator, three different answers. An EA reading shift 0 will fire on the first of those and never learn that the bar closed the other way.

This is also the single biggest reason a backtest and a live account disagree. In the strategy tester, depending on your modelling mode, that flicker may not exist at all: the EA sees clean bar closes and looks decisive. Live, it sees every tremor. The strategy did not change. The data it was reading did.

Once a bar is closed it is finished. It will read the same in five minutes, next week, and in a backtest run three years from now. Read closed bars only, and your live behaviour and your backtest are describing the same thing.

OnTick is the wrong heartbeat

Every MQL5 tutorial puts the logic in OnTick(). Here is what this EA does instead:

void OnTick()  { Poll(); }
void OnTimer() { Poll(); }

Both handlers call the same function, and the timer is what actually drives it:

input int InpTimerSeconds = 5;    // poll interval

...

EventSetTimer(MathMax(1, InpTimerSeconds));

The reason is in the header comment: "Timer driven, so it does not depend on the chart symbol ticking."

This EA scans ten pairs from one chart. If it lived in OnTick, it would only wake when the chart's symbol ticked. Attach it to EURGBP on a quiet Friday afternoon and USDJPY could roll over into a new day without the EA noticing for minutes. Worse, the behaviour would depend on which chart you happened to attach it to — a bug that never reproduces on your machine.

OnTick stays because a tick is a free excuse to poll, and polling is idempotent here. But nothing depends on it.

Three failures that only show up in production

The logic above is maybe twenty lines. The rest of the file is the part that separates an EA that runs for a year from one that quietly stops working on a Tuesday.

1. Starting up is not a signal

// First successful read of this symbol: adopt the current bar as
// already-seen, so startup is not mistaken for a fresh close.
if(g_lastBar[i] == 0)
{
   g_lastBar[i] = t1;
   ...
}

The EA publishes when it sees a bar it has not seen before. On attach, it has seen nothing — so every bar is new, and it would republish the entire watchlist every time MetaTrader restarts, every time you recompile, every time you change an input.

Adopting the current bar on first sight fixes it. Restarting becomes a no-op, which is what restarting should be.

2. Advance only on success

         g_barTries[i]++;
         if(ok)
         {
            // Advance ONLY on success, so a failed publish is retried
            // instead of being silently lost for the day.
            g_lastBar[i]  = t1;
            g_barTries[i] = 0;
            Print("Published new D1 bar. sym=", g_sym[i], " dir=", g_dir[i],
                  " str=", g_strength[i]);
         }
         else if(g_barTries[i] >= 12)
         {
            g_lastBar[i]  = t1;   // give up on this bar rather than hammer the API
            g_barTries[i] = 0;
            Print("Publish FAILED after retries, skipping bar. sym=", g_sym[i]);
         }

The obvious way to write this is to mark the bar as done as soon as you have tried. Then one dropped network request loses that day's signal for that pair, silently, and you find out a week later when the chart has a hole in it.

Marking it done only on success means a failure is retried on the next timer tick. And the counter means that a genuinely broken endpoint gets abandoned after twelve attempts instead of being hammered every five seconds for the rest of the day. Retry, but give up. Both halves matter.

3. The rollover stampede

input int InpMaxSendsPerCycle = 3;    // spread the rollover burst across cycles

...
// Spread the rollover burst so one timer callback never blocks long.
if(sentThisCycle >= InpMaxSendsPerCycle) continue;

At the daily close, all ten pairs roll over within the same second. Each publish is a blocking WebRequest. Ten of them back-to-back inside one timer callback locks the terminal for as long as the slowest one takes.

Three per cycle, five seconds apart, clears ten pairs in under twenty seconds and never blocks. Nobody is waiting on those twenty seconds — the bar has closed, the information is not going anywhere.

Run it

Open Signals/D1_Signal_Trend.mq5 from the repository in MetaEditor and compile it. Attach it to one chart — any symbol, the EA does not care. Then:

  • Set InpSignalEnable = false for your first run. The EA computes everything and publishes nothing. The status panel in the top-left of the chart shows what it would have sent.
  • Set InpSendOnInit = true if you want it to publish the last closed bar immediately instead of waiting until tomorrow's close. Useful for checking the pipeline end to end.
  • Make sure InpSymbols matches your broker's symbol names exactly. Some brokers append suffixes — USDJPY.pro and USDJPY are different symbols as far as MT5 is concerned.

The API key lives in Helpers/KurosawaSecrets.mqh, which is not in the repository. Copy KurosawaSecrets.example.mqh next to it and put your own value in. Keeping the key in its own gitignored header — rather than as an input default you will eventually screenshot — is the entire reason the rest of the suite can be public.

What the output actually means

The EA emits a direction and a strength from 0 to 100. It is tempting to read LONG, strength 82 as "buy this". Do not.

We rebuilt all three of our daily analyzers over ten years and ten pairs and measured what happened next. The forward returns are negative on every strategy, and the stronger the reading, the worse they get. A strong LONG marks a move that is already extended — historically it has come back more often than it has carried on.

So the number is a measurement of how far price has gone, not a forecast of where it goes. That is a genuinely useful thing to have; it is just not the thing the label suggests. The study, with the numbers, is here.

This matters for the course as much as for the signal. You are going to build something that produces numbers. Knowing what your numbers do not mean is the difference between a system and a slot machine.

What you should have now

A compiled EA on a chart, a status panel listing ten pairs with a direction and a strength each, and a clear idea of why every read in it is shift 1.

Next module: the same logic, split into a strategy module that decides and an engine that acts — and why that split is the thing that makes a strategy testable at all.