Blog
Pine Script ta.crossover in MQL5: Detect an MA Cross in an EA
Quick Answer
In Pine Script, ta.crossover(a, b) is true when a is above b on the current bar and a was at or below b on the previous bar. MQL5 has no built-in for this. You read both values on two bars and compare them yourself.
When your EA runs once per new bar, the "current bar" in Pine is MQL5 shift 1, and the "previous bar" is shift 2. A bullish cross is fast1 > slow1 && fast2 <= slow2, where the number is the shift.
Key Facts
| Pine Script | Meaning | MQL5 condition (shift 1 and 2) |
|---|---|---|
| ta.crossover(fast, slow) | fast crosses above slow | fast1 > slow1 && fast2 <= slow2 |
| ta.crossunder(fast, slow) | fast crosses below slow | fast1 < slow1 && fast2 >= slow2 |
| ta.cross(fast, slow) | A cross in either direction | Either of the two conditions above |
| Current bar | Bar the script is calculating | Shift 1, when the EA runs on a new bar |
| Previous bar | [1] | Shift 2 |
The Pine Script Version
fast = ta.ema(close, 10)
slow = ta.ema(close, 30)
longSignal = ta.crossover(fast, slow)
shortSignal = ta.crossunder(fast, slow)
longSignal is true on the single bar where fast moves from at-or-below slow to above it. It is not true on every bar where fast is above slow. That one-bar behavior is the part that has to be rebuilt in MQL5.
How the Shifts Map
A Pine strategy usually evaluates a bar after it closes. That bar is "current" for Pine. In MQL5, once a new bar opens, the bar that just closed sits at shift 1. So:
- Pine
faston the current bar is the indicator value at shift 1. - Pine
fast[1]is the indicator value at shift 2.
The same logic applies to slow. If you want the full explanation of this off-by-one rule, read Pine Script close[1] in MQL5 first.
Full MQL5 Example
This example creates two EMA handles, reads two closed bars from each, and prints a message when a cross happens. It does not place trades. You can add your own entry logic where the messages are.
input int FastPeriod = 10;
input int SlowPeriod = 30;
int fastHandle = INVALID_HANDLE;
int slowHandle = INVALID_HANDLE;
enum CrossSignal
{
SIG_NONE,
SIG_CROSS_UP,
SIG_CROSS_DOWN
};
int OnInit()
{
if(FastPeriod >= SlowPeriod)
{
Print("FastPeriod must be smaller than SlowPeriod.");
return INIT_PARAMETERS_INCORRECT;
}
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)
{
Print("Failed to create MA handles. Error: ", GetLastError());
return INIT_FAILED;
}
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if(fastHandle != INVALID_HANDLE) IndicatorRelease(fastHandle);
if(slowHandle != INVALID_HANDLE) IndicatorRelease(slowHandle);
}
// Reads the values at shift 1 and shift 2
bool ReadTwoValues(int handle, double &shift1, double &shift2)
{
if(BarsCalculated(handle) < 3)
return false;
double buf[];
ArraySetAsSeries(buf, true);
// start_pos = 1, count = 2: buf[0] is shift 1 and buf[1] is shift 2
if(CopyBuffer(handle, 0, 1, 2, buf) != 2)
return false;
shift1 = buf[0];
shift2 = buf[1];
return true;
}
CrossSignal GetCross()
{
double fast1, fast2, slow1, slow2;
if(!ReadTwoValues(fastHandle, fast1, fast2)) return SIG_NONE;
if(!ReadTwoValues(slowHandle, slow1, slow2)) return SIG_NONE;
if(fast1 > slow1 && fast2 <= slow2)
return SIG_CROSS_UP; // ta.crossover
if(fast1 < slow1 && fast2 >= slow2)
return SIG_CROSS_DOWN; // ta.crossunder
return SIG_NONE;
}
bool IsNewBar()
{
static datetime lastBarTime = 0;
datetime currentBarTime = iTime(_Symbol, _Period, 0);
if(currentBarTime == 0)
return false;
if(currentBarTime == lastBarTime)
return false;
lastBarTime = currentBarTime;
return true;
}
void OnTick()
{
if(!IsNewBar())
return;
CrossSignal sig = GetCross();
if(sig == SIG_CROSS_UP)
Print("Crossover: fast crossed above slow.");
else if(sig == SIG_CROSS_DOWN)
Print("Crossunder: fast crossed below slow.");
}
The same CopyBuffer safety checks (return value, BarsCalculated, array size) are explained in the MQL5 CopyBuffer guide. Skipping them is a common cause of an array out of range error.
Why the Previous-Bar Check Matters
Look at the two conditions again:
fast1 > slow1 && fast2 <= slow2
The first part says fast is above slow now. The second part says it was not above slow one bar earlier. Without the second part, the EA would report a signal on every new bar while fast stays above slow, and any entry logic attached to it would fire again each time. The previous-bar check makes the signal fire once, on the bar where the cross happens.
Common Mistakes
- Reading shift 0. The forming bar changes on every tick, so a cross can appear and then disappear before the bar closes.
- Using shifts 0 and 1 instead of 1 and 2. The signal comes one bar earlier than in Pine.
- Dropping the previous-bar condition. The EA signals on every bar that stays above or below.
- Not checking
CopyBuffer. If it copies fewer values than requested, the array read can fail. - Using a different average than Pine.
ta.emaandta.smaneedMODE_EMAandMODE_SMA, and the price source has to match. Small value differences are still possible, because the two platforms start their calculations differently and use different data. See why a Pine Script strategy gives different results in MQL5. - Running on every tick. Without a new-bar check, the same cross can be handled many times.
Turning the Signal Into an Order
Once the cross is detected, the next steps are entry, stop loss and take profit. For the entry mapping, see strategy.entry() to MQL5 CTrade. Test the full EA in the MT5 Strategy Tester before you use it on a live account.
FAQ
What is ta.crossover in MQL5?
MQL5 has no function with that name. You compare two values on two bars: the cross is true when the first series is above the second on the last closed bar, and was at or below it on the bar before.
How do I detect a crossunder in MQL5?
Use the opposite condition: fast1 < slow1 && fast2 >= slow2, with shift 1 and shift 2.
Why does my EA signal on a different bar than TradingView?
The shifts are probably off by one. In an EA that runs on a new bar, the last closed bar is shift 1, so use shifts 1 and 2 for the current and previous values. Shifts 0 and 1 make the signal early and unstable, and shifts 2 and 3 make it one bar late.
Does the order of the two series matter?
Yes. ta.crossover(fast, slow) and ta.crossover(slow, fast) describe opposite events. Keep the same order in your MQL5 condition.
Convert Signals Without Counting Shifts
Every ta.crossover, ta.crossunder and [1] in a Pine script has to become the right shift in MQL5, and one mistake delays or repeats a signal. CodeFlowOS translates Pine Script into MQL5 and lets you review the generated code before you test it in the MT5 Strategy Tester. You can try it on the converter page.
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.