A time exit is one of the simplest rules an EA can have: if a trade has not reached its stop or target after N bars, close it. It frees margin from trades that are going nowhere, it limits swap on positions that drift, and it is a useful research tool, because replacing the real exits with a plain time exit shows whether the entry has any edge on its own.

It is also easy to get slightly wrong. Counting ticks instead of bars, counting weekend hours as bars, or closing the same position twice on a busy tick are all common. This tutorial builds the rule in three small parts.

Step 1: the inputs

#include <Trade\Trade.mqh> input int MaxBarsHeld = 20; // close after this many bars of the EA's timeframe input long MagicNumber = 20261104; // only positions opened by this EA CTrade trade; int OnInit() { if(MaxBarsHeld < 1) { Print("MaxBarsHeld must be at least 1"); return INIT_PARAMETERS_INCORRECT; } trade.SetExpertMagicNumber(MagicNumber); return INIT_SUCCEEDED; } 

Step 2: how many bars has a position been open?

POSITION_TIME is the time the position was opened. iBarShift turns a time into a bar index on a timeframe, where index 0 is the current bar. So the shift of the open time is the number of completed bars since the bar in which the trade was opened.

// Bars since the position was opened, or -1 if history is not available yet. int BarsHeld(const datetime openTime) { int shift = iBarShift(_Symbol, _Period, openTime, false); return shift; } 

With exact set to false, iBarShift returns the bar that contains the time, which is what we want. It returns -1 if the history is not loaded yet, and the caller treats that as unknown and does nothing.

Counting bars rather than hours has a nice side effect: the weekend does not count. A position opened on Friday afternoon on H1 has not used up its time over Saturday and Sunday, because there are no bars there. If you want calendar time instead, compare TimeCurrent() with the open time in seconds, but decide that on purpose.

Two details about POSITION_TIME. A partial close does not change it, so the clock keeps running for the remaining volume. On a netting account, adding volume to an existing position also keeps the original open time, while POSITION_TIME_UPDATE moves. If your EA scales in on a netting account, decide which time the rule should use.

Step 3: check once per bar and close

The rule only changes when a new bar opens, so there is no reason to scan positions on every tick. A new bar check inside its own function keeps the work small without blocking the rest of OnTick. The last bar time is stored only after every close succeeded, so a failed close is tried again on the next tick instead of waiting a whole bar.

datetime lastCheckedBar = 0; void CheckTimeExits() { datetime barTime = iTime(_Symbol, _Period, 0); if(barTime == 0 || barTime == lastCheckedBar) return; bool allDone = true; 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) != MagicNumber) continue; int held = BarsHeld((datetime)PositionGetInteger(POSITION_TIME)); if(held < 0) { allDone = false; // history not ready, look again on the next tick continue; } if(held < MaxBarsHeld) continue; if(!trade.PositionClose(ticket) || trade.ResultRetcode() != TRADE_RETCODE_DONE) { PrintFormat("Time exit failed for %I64u, retcode %u", ticket, trade.ResultRetcode()); allDone = false; continue; } PrintFormat("Time exit: closed %I64u after %d bars", ticket, held); } if(allDone) lastCheckedBar = barTime; } void OnTick() { CheckTimeExits(); // the EA's normal entry and exit logic follows } 

The loop runs backwards because closing a position changes the position list, and walking from the end keeps the remaining indexes valid.

PositionClose on a hedging account closes that exact ticket. On a netting account there is only one position per symbol, so the same call closes it.

Testing it

  1. Run the EA in the Strategy Tester in visual mode with MaxBarsHeld = 3 on a fast timeframe and confirm in the journal that each position closes exactly three bars after the bar it opened in.
  2. Check a trade opened on a Friday and confirm it closes after the right number of bars on Monday, not at the first tick of the week.
  3. Compare the result of the strategy with its normal exits and with time exits of 5, 10 and 20 bars. If the time exits alone lose money, the profit lives in the stop and target levels, and those deserve a closer look.

A time exit is a few lines of code. It is worth having in every EA, even if only as a switch for testing.