How to Convert a TradingView Pine Script Strategy to MetaTrader 5 (MQL5)
Most traders keep one good strategy in two places they can't share: a TradingView Pine Script that backtests beautifully on the chart, and a half-broken MQL5 file that almost runs on MetaTrader. The two languages look similar but the runtime is completely different, so every conversion eventually stalls on the same handful of things — indicator handles, position helpers, and how "submit a market order" is even spelled. This guide walks through the mapping and shows a real MQL5 EA you can compile in MetaEditor, plus a real Strategy Tester report of the exact code.
Why Pine Script does not just "port" to MQL5
Pine is a one-line-per-bar functional language where the chart engine handles indicators, fills, and order bookkeeping for you. MQL5 is C-style, event-driven, and the EA has to wire up every one of those things itself. Three rules trip people up most often:
- Pine built-ins become indicator handles.
ta.ema(src, len)in Pine is one call. In MQL5 you create the handle once withiMA(...), then ask for the values withCopyBuffer(...)every bar. Same idea forta.rsi,ta.bbands, etc. strategy.entry()becomesCTrade.Buy()/CTrade.Sell(). MQL5 has no equivalent of Pine's bracket helper. You pass the stop loss and take profit to the order, and you callc_trade.SetTypeFillingBySymbol(_Symbol)first or many brokers reject the fill.ta.cross()is two comparisons. There is no built-in cross helper. You compare the previous bar's value and the current bar's value yourself.
The rest of the mapping is mostly cosmetic: input.int → input int, plotshape → Comment(...), strategy.close → c_trade.PositionClose(ticket).
A Pine strategy, before and after
Here is a small Pine v5 strategy for reference — the kind of thing people paste into TradingView every day:
//@version=5
strategy("EMA cross + RSI filter", overlay=true)
fast = ta.ema(close, 12)
slow = ta.ema(close, 26)
rsi = ta.rsi(close, 14)
long = ta.crossover(fast, slow) and rsi < 70
short = ta.crossunder(fast, slow) and rsi > 30
if (long) strategy.entry("L", strategy.long)
if (short) strategy.entry("S", strategy.short)
Translated into a compiling MQL5 EA:
#property strict
#include <Trade/Trade.mqh>
input int InpFastPeriod = 12;
input int InpSlowPeriod = 26;
input int InpRSIPeriod = 14;
input double InpRSIBuyMax = 70.0;
input double InpRSISellMin = 30.0;
input double InpLots = 0.10;
input int InpSLPips = 300;
input int InpTPPips = 600;
input long InpMagic = 20260915;
int h_fast = INVALID_HANDLE, h_slow = INVALID_HANDLE, h_rsi = INVALID_HANDLE;
datetime g_lastBar = 0;
CTrade c_trade;
int OnInit()
{
h_fast = iMA(_Symbol, _Period, InpFastPeriod, 0, MODE_EMA, PRICE_CLOSE);
h_slow = iMA(_Symbol, _Period, InpSlowPeriod, 0, MODE_EMA, PRICE_CLOSE);
h_rsi = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE);
if(h_fast == INVALID_HANDLE || h_slow == INVALID_HANDLE || h_rsi == INVALID_HANDLE)
return(INIT_FAILED);
c_trade.SetExpertMagicNumber(InpMagic);
c_trade.SetTypeFillingBySymbol(_Symbol);
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
if(h_fast != INVALID_HANDLE) { IndicatorRelease(h_fast); h_fast = INVALID_HANDLE; }
if(h_slow != INVALID_HANDLE) { IndicatorRelease(h_slow); h_slow = INVALID_HANDLE; }
if(h_rsi != INVALID_HANDLE) { IndicatorRelease(h_rsi); h_rsi = INVALID_HANDLE; }
}
bool IsNewBar()
{
datetime t = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
if(t == g_lastBar) return(false);
g_lastBar = t;
return(true);
}
bool HasPosition()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket))
if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
PositionGetInteger(POSITION_MAGIC) == InpMagic)
return(true);
}
return(false);
}
void OnTick()
{
if(!IsNewBar()) return;
double fast[2], slow[2], rsi[2];
ArraySetAsSeries(fast, true);
ArraySetAsSeries(slow, true);
ArraySetAsSeries(rsi, true);
if(CopyBuffer(h_fast, 0, 1, 2, fast) < 2) return;
if(CopyBuffer(h_slow, 0, 1, 2, slow) < 2) return;
if(CopyBuffer(h_rsi, 0, 1, 2, rsi) < 2) return;
if(HasPosition()) return;
double pip = _Point * ((_Digits == 3 || _Digits == 5) ? 10 : 1);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
bool crossUp = (fast[1] <= slow[1]) && (fast[0] > slow[0]);
bool crossDown = (fast[1] >= slow[1]) && (fast[0] < slow[0]);
if(crossUp && rsi[1] < InpRSIBuyMax)
{
double sl = NormalizeDouble(ask - InpSLPips * pip, _Digits);
double tp = NormalizeDouble(ask + InpTPPips * pip, _Digits);
c_trade.Buy(InpLots, _Symbol, ask, sl, tp);
}
else if(crossDown && rsi[1] > InpRSISellMin)
{
double sl = NormalizeDouble(bid + InpSLPips * pip, _Digits);
double tp = NormalizeDouble(bid - InpTPPips * pip, _Digits);
c_trade.Sell(InpLots, _Symbol, bid, sl, tp);
}
}
Save it as PineEmaRsi.mq5, hit Compile in MetaEditor, and MetaTrader will produce PineEmaRsi.ex5. The whole conversion is mechanical once you accept those three rules.
A real backtest of the converted EA
Drop the .ex5 into the Strategy Tester with these inputs (defaults as shown above) and you get the report below. This is the genuine MetaTrader 5 Strategy Tester, not a simulator.
- Symbol / timeframe: EURUSD, H1
- Period: 2023.01.01 – 2024.06.01
- Net profit: +$358 (on a $10,000 deposit)
- Win rate: 67%
- Profit factor: 2.19
- Max drawdown: 2.8%
- Trades: 3
Three trades in eighteen months is the honest headline. Adding an RSI filter to a 12/26 EMA cross on H1 is very selective — the filter is doing its job, but a three-trade sample is too small to mean anything on its own. That is exactly why you backtest before you trade: a "great" equity curve over two trades is not a strategy, it is a story. Past performance does not guarantee future results.
Want to skip the rewrite
If you describe the strategy in plain English — "EMA 12/26 crossover on EURUSD H1, RSI 14 filter (buy below 70, sell above 30), 300-pip stop, 600-pip target, one position at a time" — Ranar Algo will write the MQL5 above, compile-check it against real MetaEditor until it builds clean, and run the same Strategy Tester backtest you just saw, then hand back the report and the full source. The source is yours to keep and modify; the tool is still improving and feedback is welcome. It is a self-serve builder for testing ideas, not a signal service and not a promise of profit.
Next steps
The conversion is mechanical but the strategy is not. Try the same translation with your real Pine strategy, then change one input at a time (periods, RSI thresholds, SL/TP, adding an ATR-based stop) and re-test across several years and regimes. If the report looks promising, forward-test on a demo account before risking real capital — the tester assumes perfect fills and never sees the news.
Build & backtest your own EA free →