Quick Answer
ta.valuewhen(condition, source, occurrence) returns the value of source on the bar where condition was true for the occurrence-th time, counting back from now. Occurrence 0 is the most recent signal, 1 is the one before it, and so on. If there are not enough signals, it returns na.
MQL5 has no built-in for it. You write a function that scans the closed bars backwards, counts the bars where the condition is true, and reads the source value at the bar of the requested occurrence. The value comes from the bar of the signal, not from the current bar. That is the whole point of the function, and the first thing to get right.
Key Facts
| Pine Script | MQL5 |
|---|---|
| ta.valuewhen(cond, low, 0) | A function that scans back and returns iLow at the most recent bar where cond is true |
| ta.valuewhen(cond, low, 1) | The same scan, but it returns the value at the second most recent signal |
| na (not enough signals) | Return EMPTY_VALUE, and check it before you compare |
| ta.valuewhen(cond, close, 0) where cond is true now | The value of the bar you evaluate, because the scan starts at that bar |
| Searches all loaded history | Searches only maxBars bars. Choose the limit on purpose |
| Related: ta.barssince(cond) | How many bars ago the signal was, from the same kind of scan |
| Shift 0 is the live bar | Evaluate on shift 1, the last closed bar |
The Pine Script Version
//@version=5
strategy("Cross level", overlay = true)
fast = ta.ema(close, 10)
slow = ta.ema(close, 30)
crossUp = ta.crossover(fast, slow)
crossLow = ta.valuewhen(crossUp, low, 0)
if crossUp
strategy.entry("Long", strategy.long)
if strategy.position_size > 0 and close < crossLow
strategy.close("Long")
On every bar, crossLow holds the low of the bar where the fast EMA last crossed above the slow EMA. The strategy closes the long when the close drops below that level. Before the first cross, crossLow is na, and close < na is false in Pine.
Scan Back and Return the Value
Copy the EMA values once, loop over the bars until you find the requested occurrence, and then read the source at that bar:
int fastHandle = INVALID_HANDLE;
int slowHandle = INVALID_HANDLE;
// Like ta.valuewhen(ta.crossover(fast, slow), low, occurrence), evaluated on the bar at 'shift'.
// Returns EMPTY_VALUE when there are not enough signals within maxBars.
double LowWhenCrossUp(int shift, int maxBars, int occurrence)
{
int count = maxBars + 2; // one extra bar, because a cross compares a bar with the previous one
double fast[], slow[];
ArraySetAsSeries(fast, true); // index 0 = the bar at 'shift'
ArraySetAsSeries(slow, true);
if(CopyBuffer(fastHandle, 0, shift, count, fast) != count) return EMPTY_VALUE;
if(CopyBuffer(slowHandle, 0, shift, count, slow) != count) return EMPTY_VALUE;
int found = 0;
for(int i = 0; i <= maxBars; i++)
{
// ta.crossover(fast, slow) on the bar at index i
if(fast[i] > slow[i] && fast[i + 1] <= slow[i + 1])
{
if(found == occurrence)
{
double v = iLow(_Symbol, _Period, shift + i); // the low of the SIGNAL bar
return (v == 0.0) ? EMPTY_VALUE : v;
}
found++;
}
}
return EMPTY_VALUE;
}
The index i counts bars back from shift, so the source bar is at shift + i. For the handles and CopyBuffer, see MQL5 CopyBuffer guide, and for the cross rule, see Pine Script ta.crossover in MQL5.
The na Trap, Again
In Pine, close < crossLow is false when crossLow is na. In MQL5, EMPTY_VALUE is a very large number (DBL_MAX), so this line is wrong:
if(close1 < level) { ... } // wrong: close1 < DBL_MAX is true
if(level != EMPTY_VALUE && close1 < level) { ... } // correct
The mistake is quiet: the code compiles, runs and closes positions when there was never a signal. Always compare against EMPTY_VALUE first. The same problem exists for the "never happened" value of ta.barssince, which is covered in Pine Script ta.barssince in MQL5.
Option B: Store the Value When the Signal Happens
If you want the same style as a Pine var variable, keep the value in a global and update it once per new bar:
double g_crossLow = EMPTY_VALUE;
// Call once per new bar, with the condition result and the low of the last closed bar
void UpdateCrossLow(bool crossOnClosedBar, double lowOfClosedBar)
{
if(crossOnClosedBar)
g_crossLow = lowOfClosedBar;
}
It is short and cheap, but it only knows the signals that happened while the EA was running. After a restart or a change of inputs, g_crossLow is EMPTY_VALUE again, even if a cross happened two bars ago. The scan does not have this problem, and it is also the only simple way to get occurrence 1 or 2. The restart problem is explained in Pine Script var and varip in MQL5.
Full MQL5 Example
This EA checks the exit rule from the Pine strategy once per closed bar and prints it. It does not place trades.
input int FastPeriod = 10;
input int SlowPeriod = 30;
input int MaxScan = 100; // how many bars back to look for the cross
int fastHandle = INVALID_HANDLE;
int slowHandle = INVALID_HANDLE;
datetime g_lastBarTime = 0;
int OnInit()
{
fastHandle = iMA(_Symbol, _Period, FastPeriod, 0, MODE_EMA, PRICE_CLOSE);
slowHandle = iMA(_Symbol, _Period, SlowPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE)
return INIT_FAILED;
g_lastBarTime = iTime(_Symbol, _Period, 0);
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if(fastHandle != INVALID_HANDLE) IndicatorRelease(fastHandle);
if(slowHandle != INVALID_HANDLE) IndicatorRelease(slowHandle);
}
bool IsNewBar()
{
datetime currentBarTime = iTime(_Symbol, _Period, 0);
if(currentBarTime == 0 || currentBarTime == g_lastBarTime)
return false;
g_lastBarTime = currentBarTime;
return true;
}
double LowWhenCrossUp(int shift, int maxBars, int occurrence)
{
int count = maxBars + 2;
double fast[], slow[];
ArraySetAsSeries(fast, true);
ArraySetAsSeries(slow, true);
if(CopyBuffer(fastHandle, 0, shift, count, fast) != count) return EMPTY_VALUE;
if(CopyBuffer(slowHandle, 0, shift, count, slow) != count) return EMPTY_VALUE;
int found = 0;
for(int i = 0; i <= maxBars; i++)
{
if(fast[i] > slow[i] && fast[i + 1] <= slow[i + 1])
{
if(found == occurrence)
{
double v = iLow(_Symbol, _Period, shift + i);
return (v == 0.0) ? EMPTY_VALUE : v;
}
found++;
}
}
return EMPTY_VALUE;
}
void OnTick()
{
if(!IsNewBar())
return;
double crossLow = LowWhenCrossUp(1, MaxScan, 0); // evaluated on the last closed bar
if(crossLow == EMPTY_VALUE)
return; // no cross found, or data not ready
double close1 = iClose(_Symbol, _Period, 1);
if(close1 == 0.0)
return;
// close < ta.valuewhen(crossUp, low, 0), on the closed bar
if(close1 < crossLow)
PrintFormat("Close %.5f is below the low of the last cross bar (%.5f)", close1, crossLow);
}
The new-bar check and the shift 1 rule are explained in Pine Script close[1] in MQL5. To turn such a level into a stop order, see Pine Script strategy.exit to MQL5 SL and TP.
Common Mistakes
- Returning the current value.
valuewhengives the value from the bar of the signal. Read the source atshift + i, not atshift. - Comparing
EMPTY_VALUEas a number.close < DBL_MAXis true. Check forEMPTY_VALUEfirst. - Mixing up the occurrence number. 0 is the most recent signal, 1 is the one before it.
- Searching too few bars. Pine looks through all loaded history. If
maxBarsis shorter than the gap between signals, you getnawhere Pine finds a value. - Evaluating on the live bar. The condition on shift 0 can change before the bar closes. Use shift 1.
- Off by one in the cross. A cross compares a bar with the previous one, so copy one extra bar.
- Trusting a stored value after a restart. A global starts empty. Scan the history, or rebuild the value.
FAQ
Related Guides
- Pine Script ta.barssince in MQL5: count the bars since the last signal, with the same kind of scan.
- Pine Script ta.crossover in MQL5: the cross rule used as the condition.
- Pine Script var and varip in MQL5: stored values, state and restarts.
- MQL5 CopyBuffer guide: read indicator buffers safely.
- Pine Script strategy.exit to MQL5 SL and TP: turn a signal level into a stop.
- Why a Pine Script Strategy Gives Different Results in MQL5:
nahandling is one of the causes.
Convert Signal Logic Without Surprises
A level taken from the wrong bar, or a missing na check, can close a trade that should stay open. CodeFlowOS translates Pine Script into MQL5 and lets you review the generated code before you test it in the MT5 Strategy Tester. The platform compile check is optional, and outputs are not guaranteed, so always review the result. For the full process, see the Pine Script to MQL5 guide, or try the converter page. If the code does not compile, the Error Fixer can suggest a fix, and the free early-bird Code Review checks an EA for common mistakes.
This article is educational. It does not describe trading performance and is not financial advice. Always compile and test code in MetaEditor and the Strategy Tester before using it on a live account.