Blog
Why Pine Script Strategy Differs on MQL5 & How to Fix
Why Your Pine Script Strategy Gives Different Results on MQL5 (And How to Fix It)
Quick Answer
A Pine Script strategy can give different results on MQL5 because the platforms handle bar and tick execution, unconfirmed and higher-timeframe data, indicator values, and order fills differently. Compare signals on matching, confirmed bars first; then check indicator buffers, execution timing, spread, commission, and slippage before comparing backtest performance.
Key Facts
| Area | Pine Script | MQL5 |
|---|---|---|
| Execution | Evaluates chart bars; realtime behavior depends on strategy settings | Event-driven EA commonly evaluates on each OnTick() event |
| Current candle | Realtime values may change until the bar is confirmed | Shift 0 is forming; shift 1 is the last closed bar |
| RSI access | ta.rsi() returns a series value | iRSI() creates a handle; CopyBuffer() reads its values |
| Higher timeframe | request.security() has lookahead and confirmation behavior | Read the selected timeframe's series/indicator and align by bar time |
| Backtest results | Depend on TradingView data and broker-emulator settings | Depend on MT5 history, Strategy Tester settings, and execution assumptions |
Your TradingView strategy looks profitable, but the MetaTrader 5 version enters late, misses trades, or produces a different equity curve. That does not automatically mean the conversion is broken: Pine Script and MQL5 process data and orders differently, and their backtests may use different prices and fill assumptions. Start by comparing signals on the same confirmed bars, then check indicator values, order timing, and trading costs. This makes it easier to find the actual source of the mismatch before risking money.
When I first tested my RSI strategy on MT5, I spent 3 hours debugging an
iRSIhandle error. That's exactly why I built CodeFlowOS to verify the output before you even download it.
Why Pine Script and MQL5 Can Disagree
A Pine strategy runs within TradingView’s chart and broker emulator. An MQL5 Expert Advisor (EA) runs inside MetaTrader and responds to terminal events and broker data. Translating an entry condition alone does not reproduce every setting around it.
For a fair comparison, use the same symbol specification, timeframe, date range, signal timing, and position-sizing rules. Then review spread, commission, slippage, sessions, and order-fill assumptions. First compare when each system says “buy” or “sell”; compare profit and drawdown only after the signals align.
Bar-by-Bar Pine Logic vs. MQL5 OnTick()
Pine evaluates a script across chart bars. Historical bars are evaluated using their available data; a realtime bar can be recalculated as new price updates arrive. Strategies normally calculate at bar close, unless options such as calc_on_every_tick change realtime calculation behavior. By default, a market order created by a strategy is generally filled on the next available tick, not retroactively at the signal bar’s close.
An MQL5 EA is event-driven. OnTick() runs when the terminal receives a tick for the chart symbol. If the EA evaluates an entry condition on every tick, it may see an intrabar condition that Pine’s default bar-close strategy would not act on. Conversely, a new-bar-only check can miss behavior the Pine strategy was configured to evaluate intrabar.
To imitate a bar-close rule, many EAs detect the first tick of a new bar and read the last completed candle. Make the bar choice explicit: in MQL5, shift 0 is the forming bar, while shift 1 is the most recently closed bar. Use the same completed candle in both systems before investigating order execution.
Repainting and request.security() Can Create False Parity
Realtime values are not always final. A signal based on the current candle can appear and disappear before that candle closes. Higher-timeframe data introduces another risk: request.security() may return values from an unconfirmed higher-timeframe candle that later change. A script using lookahead incorrectly can also expose future higher-timeframe values on historical bars, creating signals that could not have been known live.
For a confirmed higher-timeframe value in Pine, a common pattern is to request the prior completed higher-timeframe bar:
confirmedHtfClose = request.security(
syminfo.tickerid,
"60",
close[1],
lookahead = barmerge.lookahead_on
)
Do not translate this into “use the latest H1 close” without checking when that value becomes available. In MT5, higher-timeframe data is typically read using the appropriate symbol/timeframe series or indicator handle, then aligned to the chart bars by time. Differences in bar boundaries, missing history, sessions, and data feeds can shift the apparent signal. Compare timestamps and confirm whether both versions use completed or still-forming higher-timeframe bars.
ta.rsi() vs. iRSI() and CopyBuffer()
Pine’s ta.rsi(source, length) returns an RSI series that can be used directly in expressions. MQL5’s iRSI() creates an indicator handle; the EA then requests calculated values with CopyBuffer(). The handle must be valid, and the buffer may not be ready immediately, so check the number of values copied rather than assuming the request succeeded.
The paired examples below calculate RSI and detect a cross above 30 using completed candles. They illustrate signal logic, not a complete trading system: risk sizing, existing positions, and order handling must be implemented separately.
Pine Script (v6):
//@version=6
strategy("RSI signal example", overlay = false)
rsiValue = ta.rsi(close, 14)
longSignal = ta.crossover(rsiValue, 30)
if longSignal
strategy.entry("Long", strategy.long)
MQL5 (EA signal check):
int rsiHandle = INVALID_HANDLE;
datetime lastBarTime = 0;
int OnInit()
{
rsiHandle = iRSI(_Symbol, _Period, 14, PRICE_CLOSE);
return rsiHandle == INVALID_HANDLE ? INIT_FAILED : INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if (rsiHandle != INVALID_HANDLE)
IndicatorRelease(rsiHandle);
}
void OnTick()
{
datetime barTime = iTime(_Symbol, _Period, 0);
if (barTime == 0 || barTime == lastBarTime)
return;
double rsiValues[];
ArraySetAsSeries(rsiValues, true);
if (CopyBuffer(rsiHandle, 0, 0, 3, rsiValues) != 3)
return;
lastBarTime = barTime;
bool longSignal = rsiValues[2] <= 30.0 && rsiValues[1] > 30.0;
if (longSignal)
Print("RSI crossed above 30 on the last closed bar.");
}
With ArraySetAsSeries() enabled, this example reads index 1 as the last closed bar and index 2 as the closed bar before it; it deliberately avoids using the still-forming bar at index 0 for the crossover. If CopyBuffer() has not returned all requested values, the EA waits for a later tick. When adapting the example, check the indicator’s symbol, timeframe, warm-up history, and the exact bar shifts. A valid handle or a successful compile alone does not prove the values match Pine.
Moving Average Crossover: Pine Script vs. MQL5
These examples use 10- and 30-period exponential moving averages and detect a crossover using closed bars. They print or submit no orders; they demonstrate how to align the signal calculation, not a complete trading system.
Pine Script (v6):
//@version=6
strategy("EMA crossover example", overlay = true)
fastEma = ta.ema(close, 10)
slowEma = ta.ema(close, 30)
longSignal = ta.crossover(fastEma, slowEma)
shortSignal = ta.crossunder(fastEma, slowEma)
if longSignal
strategy.entry("Long", strategy.long)
if shortSignal
strategy.entry("Short", strategy.short)
MQL5 (EA signal check):
int fastHandle = INVALID_HANDLE;
int slowHandle = INVALID_HANDLE;
datetime lastBarTime = 0;
int OnInit()
{
fastHandle = iMA(_Symbol, _Period, 10, 0, MODE_EMA, PRICE_CLOSE);
slowHandle = iMA(_Symbol, _Period, 30, 0, MODE_EMA, PRICE_CLOSE);
if (fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE)
return INIT_FAILED;
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if (fastHandle != INVALID_HANDLE) IndicatorRelease(fastHandle);
if (slowHandle != INVALID_HANDLE) IndicatorRelease(slowHandle);
}
void OnTick()
{
datetime barTime = iTime(_Symbol, _Period, 0);
if (barTime == 0 || barTime == lastBarTime)
return;
double fast[], slow[];
ArraySetAsSeries(fast, true);
ArraySetAsSeries(slow, true);
if (CopyBuffer(fastHandle, 0, 0, 3, fast) != 3 ||
CopyBuffer(slowHandle, 0, 0, 3, slow) != 3)
return;
lastBarTime = barTime;
bool longSignal = fast[2] <= slow[2] && fast[1] > slow[1];
bool shortSignal = fast[2] >= slow[2] && fast[1] < slow[1];
if (longSignal) Print("EMA crossed up on the last closed bar.");
if (shortSignal) Print("EMA crossed down on the last closed bar.");
}
Both versions express a crossover, but the MQL5 EA must create indicator handles and retrieve their buffers. As with the RSI example, verify array shifts, available history, and signal timestamps in your own data before comparing trades.
A Practical Backtest-Parity Checklist
- Match the inputs: symbol, timeframe, date range, indicator lengths, price source, and strategy settings.
- Compare signals first: log each signal’s bar time and direction in both platforms. Investigate the first bar where they differ.
- Confirm candle state: determine whether each condition uses a forming candle, a closed candle, or a confirmed higher-timeframe value.
- Review fills and costs: align order timing where possible, then account for spread, commissions, slippage, and each platform’s fill model.
- Check account behavior: lot-size rounding, minimum volume, netting versus hedging, and position-closing rules can change results even when entries match.
- Test out of sample: use additional market periods and a demo/forward test. Historical parity cannot guarantee future performance or live fills.
For prop firm traders, also verify the EA’s volume limits and any account-specific daily loss or drawdown controls independently. A signal match does not establish that a system complies with a firm’s rules.
Frequently Asked Questions
Can I use request.security() in MQL5?
Not directly: request.security() is a Pine function. In MQL5, request the relevant symbol/timeframe data or create an indicator handle for that timeframe, then align its values to the chart bars. Preserve the original confirmed-bar, lookahead, and gap behavior deliberately.
How do I test backtest parity?
Use matching inputs and compare timestamped entry signals before comparing trades or profit. Then investigate order timing, data differences, spread, commission, slippage, and position rules. Test the EA in MT5 Strategy Tester and forward-test on a demo account before considering live use.
Does a successful MQL5 compile mean the conversion is correct?
No. Compilation checks MQL5 syntax and types; it does not prove that the EA reproduces Pine’s signal timing, data alignment, order behavior, or risk rules. Review the code and compare its signals against the original strategy.
Convert, Then Verify
Different results usually become easier to diagnose when you separate signal logic from execution and compare the same confirmed bars. CodeFlowOS can help translate Pine Script into compiler-verified MQL5 output, giving you a starting point to inspect. You should still review the generated logic, compare signals, and backtest with realistic settings before considering live trading.
For a step-by-step conversion walkthrough, see the Pine Script to MQL5 guide, or visit CodeFlowOS pricing.