Quick Answer
ta.barssince(condition) returns how many bars ago the condition was last true. It returns 0 on the bar where the condition is true, and na if the condition has not been true yet. MQL5 has no built-in for it. You write a small function that scans the closed bars backwards until the condition is true, and returns the number of bars it had to step back.
Two details cause most bugs. Pine's na has no number, so you must pick a value for "never happened", and the usual choice is -1. And in Pine, a comparison with na is false, but in MQL5, -1 <= 3 is true. Always check that the result is 0 or more before you compare it.
Key Facts
| Pine Script | MQL5 |
|---|---|
| ta.barssince(cond) | A function that scans back from a shift until cond is true |
| Result 0 | The condition is true on the bar at the shift you evaluate |
| Result na (never true) | Return -1, and check bs >= 0 before comparing |
| ta.barssince(cond) <= 3 | bs >= 0 && bs <= 3 |
| na(ta.barssince(cond)) | bs < 0 |
| Searches all loaded history | Searches only maxBars bars. Choose the limit on purpose |
| Counter kept with var | A global counter updated once per new bar. It resets on restart |
| Shift 0 is the live bar | Evaluate on shift 1, the last closed bar |
The Pine Script Version
//@version=5
strategy("Cross then candle", overlay = true)
fast = ta.ema(close, 10)
slow = ta.ema(close, 30)
crossUp = ta.crossover(fast, slow)
barsSince = ta.barssince(crossUp)
if barsSince <= 3 and close > open
strategy.entry("Long", strategy.long)
The strategy enters long on a green candle that appears at most 3 bars after the fast EMA crossed above the slow EMA. When there was no cross yet, barsSince is na, and the comparison is false.
Scan Back for the Condition
The condition here is a cross of two EMAs. Copy the EMA values for the bars you want to scan once, and then loop over the arrays:
#define BARS_NONE -1 // Pine: na (condition never true within maxBars)
#define BARS_ERROR -2 // data was not ready
input int FastPeriod = 10;
input int SlowPeriod = 30;
int fastHandle = INVALID_HANDLE;
int slowHandle = INVALID_HANDLE;
// Like ta.barssince(ta.crossover(fast, slow)), evaluated on the bar at 'shift'.
// Returns 0 if the cross happened on that bar, 1 if on the bar before it, and so on.
int BarsSinceCrossUp(int shift, int maxBars)
{
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 BARS_ERROR;
if(CopyBuffer(slowHandle, 0, shift, count, slow) != count) return BARS_ERROR;
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])
return i;
}
return BARS_NONE;
}
The scan is stateless: it reads the history each time, so it is correct after a restart, and it does not depend on the EA having been running when the cross happened. For the handles and CopyBuffer, see MQL5 CopyBuffer guide, and for the cross rule, see Pine Script ta.crossover in MQL5.
The na Trap
In Pine, barsSince <= 3 is false when barsSince is na. In MQL5 there is no na, and the "never" value is -1, so this line is wrong:
if(bs <= 3) { ... } // wrong: -1 <= 3 is true, so it fires when there was no cross
if(bs >= 0 && bs <= 3) { ... } // correct
Do the same for the error code. Check for a result of 0 or more, and then compare.
Option B: A Counter Updated Once Per Bar
If you prefer the same style as a Pine var counter, keep a global and update it once per new bar:
int g_barsSinceCross = BARS_NONE;
// Call once per new bar, with the result of the condition on the last closed bar
void UpdateBarsSince(bool conditionOnClosedBar)
{
if(conditionOnClosedBar)
g_barsSinceCross = 0;
else if(g_barsSinceCross >= 0)
g_barsSinceCross++;
}
It is cheaper, but it only knows what happened while the EA was running. After a restart or a change of inputs, it starts again from BARS_NONE, even if a cross happened two bars ago. The scan in the previous section does not have this problem. The restart problem is explained in Pine Script var and varip in MQL5.
Full MQL5 Example
This EA checks the rule from the Pine strategy once per closed bar and prints it. It does not place trades.
#define BARS_NONE -1
#define BARS_ERROR -2
input int FastPeriod = 10;
input int SlowPeriod = 30;
input int MaxScan = 50; // how many bars back to look for the cross
input int MaxBarsAfterCross = 3;
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;
}
int BarsSinceCrossUp(int shift, int maxBars)
{
int count = maxBars + 2;
double fast[], slow[];
ArraySetAsSeries(fast, true);
ArraySetAsSeries(slow, true);
if(CopyBuffer(fastHandle, 0, shift, count, fast) != count) return BARS_ERROR;
if(CopyBuffer(slowHandle, 0, shift, count, slow) != count) return BARS_ERROR;
for(int i = 0; i <= maxBars; i++)
{
if(fast[i] > slow[i] && fast[i + 1] <= slow[i + 1])
return i;
}
return BARS_NONE;
}
void OnTick()
{
if(!IsNewBar())
return;
int barsSince = BarsSinceCrossUp(1, MaxScan); // evaluated on the last closed bar
if(barsSince < 0)
return; // no cross found, or data not ready
double open1 = iOpen(_Symbol, _Period, 1);
double close1 = iClose(_Symbol, _Period, 1);
if(open1 == 0.0 || close1 == 0.0)
return;
// barsSince <= 3 and close > open, on the closed bar
if(barsSince <= MaxBarsAfterCross && close1 > open1)
PrintFormat("Green candle %d bar(s) after the fast EMA crossed above the slow EMA", barsSince);
}
The new-bar check and the shift 1 rule are explained in Pine Script close[1] in MQL5, and the once-per-bar pattern in Pine Script barstate.isnew in MQL5.
Common Mistakes
- Comparing -1 as a number.
-1 <= 3is true. Checkbs >= 0first. - Searching too few bars. Pine looks through all loaded history. If
maxBarsis shorter than the usual gap between signals, you get "none" where Pine finds a signal. - Counting on every tick. A counter that increases on every tick grows many times per bar. Count once per new bar.
- 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 counter after a restart. A global counter starts empty. Scan the history, or rebuild the counter.
- Looping with
iClosefor every bar and indicator. Copy the buffers once and loop over the arrays.
FAQ
Related Guides
- Pine Script ta.crossover in MQL5: the cross rule used as the condition.
- Pine Script ta.valuewhen in MQL5: get the value from the bar of the last signal, with the same kind of scan.
- Pine Script var and varip in MQL5: counters, state and restarts.
- MQL5 CopyBuffer guide: read indicator buffers safely.
- Pine Script close[1] in MQL5: closed-bar values and the new-bar check.
- Pine Script barstate.isnew in MQL5: run logic once per bar.
- Why a Pine Script Strategy Gives Different Results in MQL5:
nahandling is one of the causes.
Convert Signal Logic Without Surprises
A missing na check can turn "no signal" into "signal". 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.