Complete Suite: all 12 tools. 6 Premium desks inside.All 12 tools $606.98 one by one

$606.98 one by one $97 once Save $509.98Get the SuiteGet it

Pine Script to MQL5 port planner

Paste a Pine script or open a .pine file, and see what the built-in calls it recognises become in MQL5 and which porting traps apply.

Updated 1 November 2026. Runs in your browser; nothing you type is uploaded.

Example: a session breakout strategySize M: 7 of 9 traps apply
Example: Our example for this page, not a tested strategy.

Read in your browser: nothing is uploaded, stored or put in a link. It plans the port and names the traps; it doesn't write the EA.

M, 7 of 9 traps apply

Port size

Ma medium port: several calls need care

  • Pine v6
  • strategy
  • 40 lines
  • Becomes an Expert Advisor (OnInit, OnTick and CTrade)

2 direct11 need care2 to rebuild7 of 9 traps apply

The plan carries the map, the traps with their fixes, the MetaQuotes and TradingView links, and your script at the end.

Traps in this script 7 of 9

Reading shift 0 instead of the bar that just closed

Lines

  • As a strategy, it calculates after each bar closes, so its orders come from closed bars.

Why the results differ. A Pine strategy calculates after a bar closes by default, and barstate.isconfirmed is true only on a bar's closing update, so both decide once per closed bar. (An indicator without barstate.isconfirmed recalculates on every realtime update, and its live signals can come and go too.) An EA's OnTick runs on every tick, and shift 0 is the bar still forming, so a signal read there can appear mid-bar, send its order, and be gone by the close.

The fix. Check for a new bar (iTime at shift 0 changed), then read shift 1 and 2 for a cross. You act on the next bar's first tick, the closest an EA gets to Pine's fill at the next open.

MQL5 sketch: An EMA cross read once per bar, on the bar that just closedcompiled 1 Nov 2026
// Pine: buy = ta.crossover(ta.ema(close, 9), ta.ema(close, 21)) and barstate.isconfirmed
#include <Trade\Trade.mqh>

input int    InpFast = 9;      // fast EMA length
input int    InpSlow = 21;     // slow EMA length
input double InpLots = 0.10;   // volume, in lots

CTrade   trade;
int      fastHandle = INVALID_HANDLE;
int      slowHandle = INVALID_HANDLE;
datetime lastBar    = 0;
int      closesAbove = 0;      // Pine: var int closesAbove = 0

int OnInit()
  {
   // handles once, here: their values take time to calculate
   fastHandle = iMA(_Symbol, _Period, InpFast, 0, MODE_EMA, PRICE_CLOSE);
   slowHandle = iMA(_Symbol, _Period, InpSlow, 0, MODE_EMA, PRICE_CLOSE);
   if(fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE)
      return(INIT_FAILED);
   return(INIT_SUCCEEDED);
  }

void OnDeinit(const int reason)
  {
   IndicatorRelease(fastHandle);
   IndicatorRelease(slowHandle);
  }

void OnTick()
  {
   datetime bar0 = iTime(_Symbol, _Period, 0);
   if(bar0 == lastBar)
      return;                                // same bar: nothing to decide

   double fast[], slow[];
   ArraySetAsSeries(fast, true);             // [0] is the newest value, as in Pine
   ArraySetAsSeries(slow, true);
   // from shift 1: [0] is the bar that just closed, [1] the one before it
   if(CopyBuffer(fastHandle, 0, 1, 2, fast) != 2 || CopyBuffer(slowHandle, 0, 1, 2, slow) != 2)
      return;                                // not calculated yet: try again on the next tick
   lastBar = bar0;

   // state changes once per closed bar, never per tick
   closesAbove = (iClose(_Symbol, _Period, 1) > slow[0]) ? closesAbove + 1 : 0;

   bool crossUp = fast[0] > slow[0] && fast[1] <= slow[1];   // Pine: ta.crossover(fast, slow)
   if(crossUp && !PositionSelect(_Symbol))
     {
      if(!trade.Buy(InpLots, _Symbol))
         PrintFormat("Buy failed: retcode %u", trade.ResultRetcode());
     }
  }
Arrays read in the wrong direction (ArraySetAsSeries)

Line

  • History references such as x[1] (line 19).

Why the results differ. Pine's x[1] always means one bar back. MQL5 arrays index left to right by default, CopyBuffer stores the oldest value first whatever the flag, and a buffer bound with SetIndexBuffer goes back to common indexing. buf[1] can be the second-oldest bar, not the previous one.

The fix. Call ArraySetAsSeries(arr, true) on every array you read as Pine reads a series, after SetIndexBuffer for buffers, and set the direction of OnCalculate's arrays yourself rather than trust the default.

MQL5 sketch: An EMA cross read once per bar, on the bar that just closedcompiled 1 Nov 2026
// Pine: buy = ta.crossover(ta.ema(close, 9), ta.ema(close, 21)) and barstate.isconfirmed
#include <Trade\Trade.mqh>

input int    InpFast = 9;      // fast EMA length
input int    InpSlow = 21;     // slow EMA length
input double InpLots = 0.10;   // volume, in lots

CTrade   trade;
int      fastHandle = INVALID_HANDLE;
int      slowHandle = INVALID_HANDLE;
datetime lastBar    = 0;
int      closesAbove = 0;      // Pine: var int closesAbove = 0

int OnInit()
  {
   // handles once, here: their values take time to calculate
   fastHandle = iMA(_Symbol, _Period, InpFast, 0, MODE_EMA, PRICE_CLOSE);
   slowHandle = iMA(_Symbol, _Period, InpSlow, 0, MODE_EMA, PRICE_CLOSE);
   if(fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE)
      return(INIT_FAILED);
   return(INIT_SUCCEEDED);
  }

void OnDeinit(const int reason)
  {
   IndicatorRelease(fastHandle);
   IndicatorRelease(slowHandle);
  }

void OnTick()
  {
   datetime bar0 = iTime(_Symbol, _Period, 0);
   if(bar0 == lastBar)
      return;                                // same bar: nothing to decide

   double fast[], slow[];
   ArraySetAsSeries(fast, true);             // [0] is the newest value, as in Pine
   ArraySetAsSeries(slow, true);
   // from shift 1: [0] is the bar that just closed, [1] the one before it
   if(CopyBuffer(fastHandle, 0, 1, 2, fast) != 2 || CopyBuffer(slowHandle, 0, 1, 2, slow) != 2)
      return;                                // not calculated yet: try again on the next tick
   lastBar = bar0;

   // state changes once per closed bar, never per tick
   closesAbove = (iClose(_Symbol, _Period, 1) > slow[0]) ? closesAbove + 1 : 0;

   bool crossUp = fast[0] > slow[0] && fast[1] <= slow[1];   // Pine: ta.crossover(fast, slow)
   if(crossUp && !PositionSelect(_Symbol))
     {
      if(!trade.Buy(InpLots, _Symbol))
         PrintFormat("Buy failed: retcode %u", trade.ResultRetcode());
     }
  }
MQL5 sketch: Pine's na in an indicator buffercompiled 1 Nov 2026
// Pine: plot(close > open ? high : na, style = plot.style_linebr)
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrTeal
#property indicator_label1  "High of up bars"

double Level[];

int OnInit()
  {
   SetIndexBuffer(0, Level, INDICATOR_DATA);
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);   // nothing is drawn where Pine had na
   return(INIT_SUCCEEDED);
  }

int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[],
                const double &open[], const double &high[], const double &low[], const double &close[],
                const long &tick_volume[], const long &volume[], const int &spread[])
  {
   // set the direction rather than trust the default: oldest first, like the buffer
   ArraySetAsSeries(Level, false);
   ArraySetAsSeries(open, false);
   ArraySetAsSeries(high, false);
   ArraySetAsSeries(close, false);
   int start = (prev_calculated > 0) ? prev_calculated - 1 : 0;
   for(int i = start; i < rates_total; i++)
      Level[i] = (close[i] > open[i]) ? high[i] : EMPTY_VALUE;
   return(rates_total);
  }
Counters that count ticks, not bars

Lines+4

  • var hi (line 15) changes on lines 20 and 24.
  • var lo (line 16) changes on lines 21 and 25.
  • var tradesToday (line 17) changes on lines 22, 32 and 36.

Why the results differ. A Pine var changed on a realtime bar is rolled back until the bar closes, so `count += 1` adds one per bar. An MQL5 global keeps every change: the same line in OnTick adds one per tick it handles, and OnTick doesn't even see every tick.

The fix. Update counters and running state only inside the new-bar block, from the closed bar's values. Pine's varip is the exception: it already behaves like an MQL5 global.

MQL5 sketch: An EMA cross read once per bar, on the bar that just closedcompiled 1 Nov 2026
// Pine: buy = ta.crossover(ta.ema(close, 9), ta.ema(close, 21)) and barstate.isconfirmed
#include <Trade\Trade.mqh>

input int    InpFast = 9;      // fast EMA length
input int    InpSlow = 21;     // slow EMA length
input double InpLots = 0.10;   // volume, in lots

CTrade   trade;
int      fastHandle = INVALID_HANDLE;
int      slowHandle = INVALID_HANDLE;
datetime lastBar    = 0;
int      closesAbove = 0;      // Pine: var int closesAbove = 0

int OnInit()
  {
   // handles once, here: their values take time to calculate
   fastHandle = iMA(_Symbol, _Period, InpFast, 0, MODE_EMA, PRICE_CLOSE);
   slowHandle = iMA(_Symbol, _Period, InpSlow, 0, MODE_EMA, PRICE_CLOSE);
   if(fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE)
      return(INIT_FAILED);
   return(INIT_SUCCEEDED);
  }

void OnDeinit(const int reason)
  {
   IndicatorRelease(fastHandle);
   IndicatorRelease(slowHandle);
  }

void OnTick()
  {
   datetime bar0 = iTime(_Symbol, _Period, 0);
   if(bar0 == lastBar)
      return;                                // same bar: nothing to decide

   double fast[], slow[];
   ArraySetAsSeries(fast, true);             // [0] is the newest value, as in Pine
   ArraySetAsSeries(slow, true);
   // from shift 1: [0] is the bar that just closed, [1] the one before it
   if(CopyBuffer(fastHandle, 0, 1, 2, fast) != 2 || CopyBuffer(slowHandle, 0, 1, 2, slow) != 2)
      return;                                // not calculated yet: try again on the next tick
   lastBar = bar0;

   // state changes once per closed bar, never per tick
   closesAbove = (iClose(_Symbol, _Period, 1) > slow[0]) ? closesAbove + 1 : 0;

   bool crossUp = fast[0] > slow[0] && fast[1] <= slow[1];   // Pine: ta.crossover(fast, slow)
   if(crossUp && !PositionSelect(_Symbol))
     {
      if(!trade.Buy(InpLots, _Symbol))
         PrintFormat("Buy failed: retcode %u", trade.ResultRetcode());
     }
  }
Sessions on the broker's server clock, not the exchange's

Lines

  • A session window (lines 7 and 12), read in the zone the script names.
  • It names America/New_York: measure your server's offset from it for a winter and a summer week.

Why the results differ. Pine reads a session in the zone the script names, or the exchange's. MT5 charts are stamped in the broker's server time, and in the Strategy Tester TimeGMT() equals the simulated server time, so it can't tell you the offset there. A fixed offset is right only while the server and the exchange change their clocks on the same dates.

The fix. Measure the server's offset from the exchange for a winter and a summer week, convert bar times before the session test, and run the weeks when the two clocks differ.

MQL5 sketch: A New York session window on a server-time chartcompiled 1 Nov 2026
// Pine: inRange = not na(time(timeframe.period, "0930-1000", "America/New_York"))
input int InpNyMinusServer = -420;   // New York minus server time, in minutes: an EXAMPLE, measure yours
input int InpStart = 570;            // 09:30 New York, in minutes after midnight
input int InpEnd   = 600;            // 10:00

// A fixed offset is right only while both clocks change on the same dates: test the weeks they don't.
int NewYorkMinute(const datetime serverTime)
  {
   MqlDateTime t;
   TimeToStruct(serverTime + InpNyMinusServer * 60, t);
   return(t.hour * 60 + t.min);
  }

bool InRange(const int shift)
  {
   int m = NewYorkMinute(iTime(_Symbol, _Period, shift));
   return(m >= InpStart && m < InpEnd);
  }

void OnTick()
  {
   Comment(InRange(1) ? "The last closed bar is inside the window" : "Outside the window");
  }
na is not EMPTY_VALUE

Lines

  • na, nz() or fixnan() on lines 12, 15, 16, 27 and 40.
  • A plot that shows na draws nothing on that bar: in MQL5 that's EMPTY_VALUE with PLOT_EMPTY_VALUE set.

Why the results differ. In Pine every comparison with na is false: `value > level` is false while value is na (though `not (value > level)` is then true). MQL5 marks a missing buffer value with an empty value: EMPTY_VALUE, which is DBL_MAX, unless the indicator sets its own with PLOT_EMPTY_VALUE (0.0, for example). DBL_MAX compared as a number is above every price, so `value > level` is true.

The fix. Write EMPTY_VALUE where Pine plots na (set PLOT_EMPTY_VALUE), and test the value against the empty value before every comparison on a value that can be missing. When you read another indicator with CopyBuffer, check which empty value it uses.

MQL5 sketch: Pine's na in an indicator buffercompiled 1 Nov 2026
// Pine: plot(close > open ? high : na, style = plot.style_linebr)
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrTeal
#property indicator_label1  "High of up bars"

double Level[];

int OnInit()
  {
   SetIndexBuffer(0, Level, INDICATOR_DATA);
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);   // nothing is drawn where Pine had na
   return(INIT_SUCCEEDED);
  }

int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[],
                const double &open[], const double &high[], const double &low[], const double &close[],
                const long &tick_volume[], const long &volume[], const int &spread[])
  {
   // set the direction rather than trust the default: oldest first, like the buffer
   ArraySetAsSeries(Level, false);
   ArraySetAsSeries(open, false);
   ArraySetAsSeries(high, false);
   ArraySetAsSeries(close, false);
   int start = (prev_calculated > 0) ? prev_calculated - 1 : 0;
   for(int i = start; i < rates_total; i++)
      Level[i] = (close[i] > open[i]) ? high[i] : EMPTY_VALUE;
   return(rates_total);
  }
MQL5 sketch: Reading a value that can be missingcompiled 1 Nov 2026
// EMPTY_VALUE is DBL_MAX: compared as a number it is above every price, where Pine's na compares false
input string InpIndicator = "MyPort";   // the ported indicator's name in MQL5\Indicators

int handle = INVALID_HANDLE;

int OnInit()
  {
   handle = iCustom(_Symbol, _Period, InpIndicator);
   if(handle == INVALID_HANDLE)
      return(INIT_FAILED);
   return(INIT_SUCCEEDED);
  }

void OnTick()
  {
   double value[];
   if(CopyBuffer(handle, 0, 1, 1, value) != 1)
      return;
   bool above = value[0] != EMPTY_VALUE && value[0] > iClose(_Symbol, _Period, 1);
   Comment(above ? "Level above the last close" : "No level, or below");
  }
Plots, buffers and what an EA can draw

Lines

  • A strategy with 2 plot calls: an EA has no buffers, so draw objects or move the plots into an indicator attached with iCustom.
  • bgcolor (line 40) has no drawing style: rectangles instead.

Why the results differ. Each MQL5 drawing style needs one to four data buffers, plus a colour buffer for a colour per bar; fill() becomes DRAW_FILLING with two more, and a strategy's plots have nowhere to go, because only custom indicators run OnCalculate.

The fix. Count buffers and plots before you write the header (#property indicator_buffers, indicator_plots); for a strategy, put the plots in a separate indicator and attach it with iCustom and ChartIndicatorAdd, or draw objects.

MQL5 sketch: A line and an arrow: one buffer eachcompiled 1 Nov 2026
// Pine: plot(fast) and plotshape(ta.crossover(fast, slow) and barstate.isconfirmed, style = shape.triangleup)
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrTeal
#property indicator_label1  "Fast EMA"
#property indicator_type2   DRAW_ARROW
#property indicator_color2  clrTeal
#property indicator_label2  "Buy"

input int InpFast = 9;
input int InpSlow = 21;

double FastLine[];
double BuyArrow[];
int    fastHandle = INVALID_HANDLE;
int    slowHandle = INVALID_HANDLE;

int OnInit()
  {
   SetIndexBuffer(0, FastLine, INDICATOR_DATA);
   SetIndexBuffer(1, BuyArrow, INDICATOR_DATA);
   PlotIndexSetInteger(1, PLOT_ARROW, 233);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   fastHandle = iMA(_Symbol, _Period, InpFast, 0, MODE_EMA, PRICE_CLOSE);
   slowHandle = iMA(_Symbol, _Period, InpSlow, 0, MODE_EMA, PRICE_CLOSE);
   if(fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE)
      return(INIT_FAILED);
   return(INIT_SUCCEEDED);
  }

int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[],
                const double &open[], const double &high[], const double &low[], const double &close[],
                const long &tick_volume[], const long &volume[], const int &spread[])
  {
   double slow[];
   ArraySetAsSeries(FastLine, false);
   ArraySetAsSeries(BuyArrow, false);
   ArraySetAsSeries(slow, false);
   ArraySetAsSeries(low, false);
   if(CopyBuffer(fastHandle, 0, 0, rates_total, FastLine) != rates_total)
      return(0);
   if(CopyBuffer(slowHandle, 0, 0, rates_total, slow) != rates_total)
      return(0);
   // from bar 0 on the first run, so every bar without a shape gets EMPTY_VALUE
   int start = (prev_calculated > 0) ? prev_calculated - 1 : 0;
   for(int i = start; i < rates_total; i++)
     {
      bool closed = i < rates_total - 1;      // the last bar is still forming: no arrow on it
      bool cross  = i > 0 && FastLine[i] > slow[i] && FastLine[i - 1] <= slow[i - 1];
      BuyArrow[i] = (closed && cross) ? low[i] : EMPTY_VALUE;
     }
   return(rates_total);
  }
Strategy fills and the tester's price model

Lines

  • process_orders_on_close = true: Pine fills at the signal bar's close. An EA can't know a bar's last tick, so it fills on the next bar's first tick.
  • strategy.exit (lines 31 and 35): Pine fills stops and limits by its bar-path rule, MT5 on ticks.
  • Entries both ways: Pine reverses the position. In MT5 the EA must close the other side, then enter: an opposite deal of the same volume only closes the position on a netting account and opens a second one on a hedging account.

Why the results differ. Pine's broker emulator fills at the next bar's open by default (at the signal close with process_orders_on_close), guesses the path inside a bar from its open, high, low and close, and reverses a position on an opposite entry. MT5's Strategy Tester fills on ticks, real or generated by the mode you pick. An opposite deal of the same volume only closes the position on a netting account, and opens a second position on a hedging account.

The fix. Expect fills to differ even when the signals agree. Test MT5 on real ticks where the broker has them, and before an opposite entry close the other side first, on either account type, then enter. Compare signals bar by bar before you compare results.

MQL5 sketch: An opposite entry that ends long, on either account typecompiled 1 Nov 2026
// Pine's strategy.entry("Long", strategy.long) closes a short and leaves the strategy long. In MT5 an opposite
// deal of the same volume only closes a short on a netting account, and opens a second position on a hedging
// one, so close every short on the symbol first, then buy.
#include <Trade\Trade.mqh>

input double InpLots = 0.10;   // volume, in lots

CTrade trade;

bool IsHedging()
  {
   return((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
  }

bool EnterLong(const double lots)
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);    // selects the position too
      if(ticket == 0 || PositionGetString(POSITION_SYMBOL) != _Symbol)
         continue;                            // real code: filter by your magic number as well
      if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) != POSITION_TYPE_SELL)
         continue;
      if(!trade.PositionClose(ticket))
        {
         PrintFormat("Close failed: retcode %u", trade.ResultRetcode());
         return(false);
        }
     }
   if(!trade.Buy(lots, _Symbol))
     {
      PrintFormat("Buy failed: retcode %u", trade.ResultRetcode());
      return(false);
     }
   return(true);
  }

int OnInit()
  {
   PrintFormat("Position accounting: %s", IsHedging() ? "hedging" : "netting or exchange");
   return(INIT_SUCCEEDED);
  }

void OnTick()
  {
   // your new-bar check and entry rule go here; when it says long: EnterLong(InpLots);
  }
Checked and not in this script (2)
  • Indicator handles created in OnTick. No ta.* call here needs an MQL5 indicator handle.
  • request.security: lookahead and higher-timeframe bars. No request.security(): everything is on the chart's own timeframe.

Call by call 15 constructs; open one for where it goes, why and the docs

What the script is

strategy()Needs careAn Expert Advisor (.mq5): OnInit, OnTick, and CTrade for orders
Line

Goes in: OnInit + OnTick

A strategy decides on closed bars by default and fills at the next bar's open; an EA runs OnTick on each tick it receives. Gate the logic on a new bar (first trap).

MQL5: OnTickCTradePine: strategy()Strategies: broker emulator

Inputs

input.int(), input.float(), input.price(), input()Directinput int / input double
Lines

Goes in: Global scope

MQL5 inputs are read-only inside the program; minval and maxval become checks in OnInit (return INIT_PARAMETERS_INCORRECT).

MQL5: Input VariablesPine: input.int()Inputs

input.string(), input.text_area(), input.symbol()Needs careinput string, or an enum input where Pine lists options
Line

Goes in: Global scope

An MQL5 string input's value can be at most 191 to 253 characters long, depending on the name's length; a list of options reads better as an enum.

MQL5: Input VariablesPine: input.string()

input.session()Needs careTwo inputs (start and end, e.g. minutes after midnight) compared with bar times
Line

Goes in: Global scope

Pine reads the session in the exchange's time zone; an MT5 chart is on the server's clock (see the sessions trap).

MQL5: Input VariablesMQL5 Book: Local and server timePine: input.session()Sessions

Indicators (ta.*)

ta.atr(length)No direct equivalent: rebuildAn RMA of true range computed in code (weight 1 / length, as ta.rma); iATR only where the Pine uses ta.sma(ta.tr, length)
Line

Goes in: OnCalculate or OnTick

iATR won't match ta.atr. TradingView's ATR is an RMA of true range; MetaQuotes' own ATR.mq5 is a sliding simple average of true range, so it matches ta.sma(ta.tr, length) instead. A stop at k × ATR lands elsewhere unless the port computes the RMA itself.

MQL5: MetaQuotes' ATR.mq5 (MQL5\Indicators\Examples), Copyright 2000-2025 MetaQuotes Ltd.Average True RangeiATRPine: ta.atr()TradingView help: Average True Rangeta.rma()

Plots and colours

plot() in a strategyNeeds careChart objects from the EA, or the plots in a separate indicator attached with iCustom + ChartIndicatorAdd
Lines

Goes in: OnInit (attach) or OnTick (objects)

OnCalculate runs only in custom indicators, so an EA has no buffers to draw with.

MQL5: Event Handling FunctionsiCustomChartIndicatorAddPine: plot()

bgcolor()No direct equivalent: rebuildRectangles (OBJ_RECTANGLE) spanning the bars to shade
Line

Goes in: OnCalculate or OnTick; delete in OnDeinit

None of MQL5's 18 drawing styles colours a bar's background: draw objects and clean them up.

MQL5: Drawing StylesObjectCreateObjectDeletePine: bgcolor()Backgrounds

Orders and positions

strategy.entry()Needs careCTrade: trade.Buy(volume, symbol, price, sl, tp) / trade.Sell(…); check ResultRetcode()
Lines

Goes in: OnTick, after the new-bar check

An opposite entry reverses a Pine position. In MT5 an opposite deal of the same volume only closes the position on a netting account, and opens a second one on a hedging account: close the other side first, then enter. Pine sizes in contracts or units, MT5 in lots.

MQL5: CTrade::BuyCTrade::SellPosition accountingPine: strategy.entry()Strategies: reversing positions

strategy.exit()Needs caresl and tp on trade.Buy/Sell, or trade.PositionModify(symbol, sl, tp) later
Lines

Goes in: OnTick

Pine's emulator fills exits inside a bar by its own price-path rule; MT5 fills SL and TP on ticks, real or generated.

MQL5: CTrade::PositionModifyStrategy TesterPine: strategy.exit()Strategies: broker emulator

strategy.position_size, strategy.position_avg_price …Needs carePositionSelect(symbol), then PositionGetDouble(POSITION_VOLUME / POSITION_PRICE_OPEN)
Line

Goes in: OnTick

On a hedging account PositionSelect picks the position with the lowest ticket: loop over positions and filter by your magic number.

MQL5: PositionSelectPositionGetDoublePine: strategy.position_size

State across bars

varNeeds careA global variable, or a static local
Lines

Goes in: Global scope

Pine drops changes a realtime bar makes before its close; MQL5 keeps every change. Update it once per closed bar (tick-counters trap).

MQL5: Global VariablesStatic VariablesPine: varVariable declarations

Time and sessions

time(timeframe, session, timezone), time_close()Needs careTimeToStruct(iTime(_Symbol, _Period, shift) + your offset) and a minutes-after-midnight test
Line

Goes in: OnCalculate or OnTick

Pine reads the session in the zone you pass, or the exchange's; MT5 bar times are server time. The offset changes when the two clocks change on different dates.

MQL5: MQL5 Book: Local and server timeTimeToStructiTimePine: time()Sessions

Missing values

na, na(), nz(), fixnan()Needs careEMPTY_VALUE in buffers (PLOT_EMPTY_VALUE for drawing), checked before any comparison
Lines

Goes in: Wherever a value may be missing

Pine's comparisons with na are false; EMPTY_VALUE is DBL_MAX, a number above every price, and an indicator can set its own empty value instead.

MQL5: Other ConstantsPine: nanz()Type system: na value

History and bar numbers

x[n] (the history operator)Needs careAn array read at shift n after ArraySetAsSeries(arr, true), or buf[i - n] in OnCalculate's left-to-right buffers
Line

Goes in: OnCalculate or OnTick

Pine keeps each series' past for you; in MQL5 you copy it (CopyBuffer, CopyRates) and pick the direction.

MQL5: CopyBufferArraySetAsSeriesPine: Execution modelLimitations: history references

math.max(), math.min(), math.abs() …DirectMathMax, MathMin, MathAbs …
Lines

Goes in: Wherever it's used

One to one for two arguments; Pine's take more.

MQL5: MathMaxPine: math.max()

A text reading of the script, not a compiler: it doesn't follow your own functions or imported libraries. Every MQL5 fact links MetaQuotes' page; the sketches are labelled compiled in MetaEditor on 1 Nov 2026.

The math: ta.ema(close, 21) → iMA(…, MODE_EMA, …) handle in OnInit, CopyBuffer at shift 1 · size = 1 × direct + 2 × care + 4 × rebuild

Shift 0 or the closed bar: one rule, two EAsBuy when the close crosses above its 5-bar average. One EA reads shift 0 on every tick; the other waits for a new bar and reads shift 1, as the Pine strategy decides. Step through the live bar's 8 ticks.
100100.5101101.5102102.5103103.5tick 3/85-bar averageshift 0: Buy sent
The live bar
EA on shift 0 (every tick):
bought at tick 3, 101.6
EA on the closed bar:
waits for the close

Tick 3 of 8: price 101.6, average 101.44. The shift-0 EA has sent its Buy.

It reads the bar still forming, so the cross counts the moment it appears. The closed-bar EA and the Pine strategy wait: the bar has 5 ticks to go.

Made-up prices for the drill, not market data and not a result. Shift 0 is the bar still forming; shift 1 is the bar that just closed (MQL5's indexing direction page).

What each Pine construct becomes in MQL5

The planner's whole map: 92 rows, each linked to the MetaQuotes and TradingView pages it was read on, 1 November 2026.

What the script is (5)
PineMQL5, and where it goesHow close
indicator()A custom indicator (.mq5): #property indicator_chart_window when overlay = true, indicator_separate_window otherwise
File header (#property). Its plots become indicator buffers; its calculation moves into OnCalculate. MQL5 Reference: Program Properties (#property), MQL5 Reference: Event Handling Functions
Direct
strategy()An Expert Advisor (.mq5): OnInit, OnTick, and CTrade for orders
OnInit + OnTick. A strategy decides on closed bars by default and fills at the next bar's open; an EA runs OnTick on each tick it receives. Gate the logic on a new bar (first trap). MQL5 Reference: OnTick, MQL5 Reference: CTrade
Needs care
library()An include file (.mqh) pulled in with #include, or a compiled module called through #import
Separate file. Port the library's exported functions first; every script that imports it depends on them. MQL5 Reference: Importing Functions (#import)
Needs care
import user/library/1The library's MQL5 port, included with #include
File header. MQL5 can't load a Pine library: its source has to be ported too, or the calls rebuilt in place. MQL5 Reference: Importing Functions (#import)
No direct equivalent: rebuild
type MyTypeA struct (data only) or a class (data and methods)
Global scope. Pine objects are references; copy MQL5 structs by value or use a class with care. MQL5 Reference: Structures, Classes and Interfaces
Needs care
Inputs (9)
PineMQL5, and where it goesHow close
input.int(), input.float(), input.price(), input()input int / input double
Global scope. MQL5 inputs are read-only inside the program; minval and maxval become checks in OnInit (return INIT_PARAMETERS_INCORRECT). MQL5 Reference: Input Variables
Direct
input.bool()input bool
Global scope. One to one. MQL5 Reference: Input Variables
Direct
input.string(), input.text_area(), input.symbol()input string, or an enum input where Pine lists options
Global scope. An MQL5 string input's value can be at most 191 to 253 characters long, depending on the name's length; a list of options reads better as an enum. MQL5 Reference: Input Variables
Needs care
input.color()input color
Global scope. Pine's colour carries transparency; MQL5's input color doesn't, so set it on the plot or object. MQL5 Reference: Input Variables
Direct
input.timeframe()input ENUM_TIMEFRAMES (PERIOD_H1, PERIOD_D1 …)
Global scope. Pine returns a string such as "60" or "D"; MQL5 functions take an ENUM_TIMEFRAMES value, where 0 means the chart's own. MQL5 Reference: Input Variables, MQL5 Reference: iMA
Needs care
input.session()Two inputs (start and end, e.g. minutes after midnight) compared with bar times
Global scope. Pine reads the session in the exchange's time zone; an MT5 chart is on the server's clock (see the sessions trap). MQL5 Reference: Input Variables, MQL5 Book: Local and server time
Needs care
input.source()input ENUM_APPLIED_PRICE, passed as applied_price
Global scope. Pine accepts any series, even another script's plot; applied_price takes a price constant or another indicator's handle. MQL5 Reference: iMA
Needs care
input.time()input datetime
Global scope. Pine's value is in milliseconds; MQL5's datetime is in seconds. MQL5 Reference: Input Variables, MQL5 Reference: TimeToStruct
Direct
input.enum()enum MyChoice { … }; input MyChoice
Global scope. One to one. MQL5 Reference: Input Variables
Direct
Indicators (ta.*) (31)
PineMQL5, and where it goesHow close
ta.sma(source, length)iMA(symbol, period, length, 0, MODE_SMA, applied_price), then CopyBuffer
Handle in OnInit; CopyBuffer in OnCalculate (indicator) or OnTick (EA). Create the handle once and read it with CopyBuffer: shift 0 is the bar still forming, shift 1 the last closed bar. MQL5 Reference: iMA, MQL5 Reference: Technical Indicator Functions
Direct
ta.ema(source, length)iMA(symbol, period, length, 0, MODE_EMA, applied_price), then CopyBuffer
Handle in OnInit; CopyBuffer in OnCalculate (indicator) or OnTick (EA). Same recursion on both sides. TradingView documents its first value (the source itself); MetaTrader's help doesn't state its seed, so compare values after a warm-up of several lengths. MQL5 Reference: iMA, MetaTrader 5 help: Moving Average, MQL5 Reference: Technical Indicator Functions
Direct
ta.rma(source, length)iMA(symbol, period, length, 0, MODE_SMMA, applied_price), then CopyBuffer
Handle in OnInit; CopyBuffer in OnCalculate (indicator) or OnTick (EA). Both docs give the same recursion (weight 1 / length) seeded with a simple average. MQL5 Reference: iMA, MetaTrader 5 help: Moving Average
Direct
ta.wma(source, length)iMA(symbol, period, length, 0, MODE_LWMA, applied_price), then CopyBuffer
Handle in OnInit; CopyBuffer in OnCalculate (indicator) or OnTick (EA). Both weight the bars in arithmetical progression, newest heaviest. MQL5 Reference: iMA, MetaTrader 5 help: Moving Average
Direct
ta.rsi(source, length)iRSI(symbol, period, length, applied_price), then CopyBuffer
Handle in OnInit; CopyBuffer in OnCalculate (indicator) or OnTick (EA). Pine smooths the up and down moves with ta.rma(); MetaTrader's help gives RSI = 100 - 100 / (1 + U / D) without naming the average. Compare a few bars before trusting a level. MQL5 Reference: iRSI, MetaTrader 5 help: Relative Strength Index
Needs care
ta.atr(length)An RMA of true range computed in code (weight 1 / length, as ta.rma); iATR only where the Pine uses ta.sma(ta.tr, length)
OnCalculate or OnTick. iATR won't match ta.atr. TradingView's ATR is an RMA of true range; MetaQuotes' own ATR.mq5 is a sliding simple average of true range, so it matches ta.sma(ta.tr, length) instead. A stop at k × ATR lands elsewhere unless the port computes the RMA itself. MetaQuotes' ATR.mq5 (MQL5\Indicators\Examples), Copyright 2000-2025 MetaQuotes Ltd., MetaTrader 5 help: Average True Range, MQL5 Reference: iATR
No direct equivalent: rebuild
ta.macd(source, fast, slow, signal)iMACD(symbol, period, fast, slow, signal, applied_price): buffer 0 MAIN_LINE, 1 SIGNAL_LINE
Handle in OnInit; CopyBuffer in OnCalculate (indicator) or OnTick (EA). iMACD has no histogram buffer: take main minus signal. MetaTrader's help gives SIGNAL = SMA(MACD, 9), and TradingView's reference doesn't name its signal average, so compare the signal line. MQL5 Reference: iMACD, MetaTrader 5 help: MACD
Needs care
ta.bb(series, length, mult)iBands(symbol, period, length, 0, mult, applied_price): 0 BASE_LINE, 1 UPPER_BAND, 2 LOWER_BAND
Handle in OnInit; CopyBuffer in OnCalculate (indicator) or OnTick (EA). Pine returns [middle, upper, lower]; iBands numbers its buffers base, upper, lower. MetaTrader's deviation divides by N; compare one band on a few bars. MQL5 Reference: iBands, MetaTrader 5 help: Bollinger Bands
Needs care
ta.stoch(source, high, low, length)iStochastic(symbol, period, Kperiod, Dperiod, slowing, ma_method, price_field): 0 MAIN_LINE, 1 SIGNAL_LINE
Handle in OnInit; CopyBuffer in OnCalculate (indicator) or OnTick (EA). Pine's ta.stoch is the raw %K, which a script smooths itself; iStochastic smooths %K by `slowing` (1 is the raw, fast one) and adds %D. MQL5 Reference: iStochastic, MetaTrader 5 help: Stochastic Oscillator
Needs care
ta.cci(source, length)iCCI(symbol, period, length, applied_price), then CopyBuffer
Handle in OnInit; CopyBuffer in OnCalculate (indicator) or OnTick (EA). MetaTrader's help builds CCI on the typical price (high + low + close) / 3; Pine takes the source you pass. Feed both the same price. MQL5 Reference: iCCI, MetaTrader 5 help: Commodity Channel Index
Needs care
ta.mfi(series, length)iMFI(symbol, period, length, applied_volume), then CopyBuffer
Handle in OnInit; CopyBuffer in OnCalculate (indicator) or OnTick (EA). iMFI asks which volume to use (ENUM_APPLIED_VOLUME); match the volume TradingView shows for the symbol. MQL5 Reference: iMFI
Needs care
ta.dmi(diLength, adxSmoothing)iADXWilder(symbol, period, length): 0 MAIN_LINE (ADX), 1 PLUSDI_LINE, 2 MINUSDI_LINE
Handle in OnInit; CopyBuffer in OnCalculate (indicator) or OnTick (EA). Pine returns [+DI, -DI, ADX], a different order, and takes two lengths where iADXWilder and iADX take one. Compare both MQL5 versions against the Pine values. MQL5 Reference: iADXWilder, MQL5 Reference: iADX
Needs care
ta.sar(start, inc, max)iSAR(symbol, period, step, maximum), then CopyBuffer
Handle in OnInit; CopyBuffer in OnCalculate (indicator) or OnTick (EA). Pine takes a start and an increment; iSAR takes one step. It maps directly only when start equals the increment. MQL5 Reference: iSAR
Needs care
ta.mom(source, length)Compute source[shift] - source[shift + length] (iMomentum is a different formula)
OnCalculate or OnTick. Pine's momentum is a difference; MetaTrader's Momentum is CLOSE(i) / CLOSE(i - n) × 100, a ratio. Don't reuse its levels. MQL5 Reference: iMomentum, MetaTrader 5 help: Momentum
Needs care
ta.stdev(source, length, biased)iStdDev(symbol, period, length, 0, MODE_SMA, applied_price), then CopyBuffer
Handle in OnInit; CopyBuffer in OnCalculate (indicator) or OnTick (EA). Pine's `biased` (default true) picks the population or the sample estimate; check which one the MQL5 value matches. MQL5 Reference: iStdDev
Needs care
ta.wpr(length)iWPR(symbol, period, length), then CopyBuffer
Handle in OnInit; CopyBuffer in OnCalculate (indicator) or OnTick (EA). Create the handle once and read it with CopyBuffer: shift 0 is the bar still forming, shift 1 the last closed bar. MQL5 Reference: iWPR
Direct
ta.obviOBV(symbol, period, applied_volume), then CopyBuffer
Handle in OnInit; CopyBuffer in OnCalculate (indicator) or OnTick (EA). OBV sums from the first bar each platform has, so its level differs; compare its changes, not its value. MQL5 Reference: iOBV
Needs care
ta.highest(source, length)On a price: iHighest(symbol, period, MODE_HIGH, length, start), then iHigh at that shift. On a computed series (an RSI): ArrayMaximum on its buffer
OnCalculate or OnTick. iHighest searches a timeseries (ENUM_SERIESMODE: high, low, close …), not your own values. start = 0 includes the bar still forming, as Pine does on the live bar; start = 1 reads closed bars only. MQL5 Reference: iHighest, MQL5 Reference: ArrayMaximum
Needs care
ta.lowest(source, length)On a price: iLowest(symbol, period, MODE_LOW, length, start), then iLow at that shift. On a computed series: ArrayMinimum on its buffer
OnCalculate or OnTick. iLowest searches a timeseries (ENUM_SERIESMODE), not your own values. start = 0 includes the bar still forming; start = 1 reads closed bars only. MQL5 Reference: iLowest, MQL5 Reference: iHighest, MQL5 Reference: ArrayMaximum
Needs care
ta.crossover(a, b), ta.crossunder(a, b), ta.cross(a, b)Two comparisons on two shifts: a[1] > b[1] && a[2] <= b[2] for the bar that just closed
OnCalculate or OnTick. Which shifts you compare decides whether the signal can vanish: a cross read on shift 0 and 1 can appear and disappear while the bar forms. MQL5 Reference: CopyBuffer, MQL5 Reference: Indexing Direction in Arrays, Buffers and Timeseries
Needs care
ta.change(source, length)value[shift] - value[shift + length]
OnCalculate or OnTick. A subtraction on two shifts. MQL5 Reference: CopyBuffer
Direct
ta.trMathMax(high - low, MathMax(MathAbs(high - prevClose), MathAbs(low - prevClose)))
OnCalculate or OnTick. The same formula. On the first bar, where there is no previous close, the variable ta.tr is na; ta.tr(true) (and ta.atr) use high - low there. MQL5 Reference: MathMax
Direct
ta.roc(source, length)100 * (value[shift] - value[shift + length]) / value[shift + length]
OnCalculate or OnTick. Arithmetic on two shifts. MQL5 Reference: CopyBuffer
Direct
ta.pivothigh(), ta.pivotlow()A loop over leftbars and rightbars around a candidate bar
OnCalculate or OnTick. A pivot is only known rightbars bars after it; Pine's own example plots it with offset = -rightbars, back in time. Port the delay, not the drawing. MQL5 Reference: iHighest
No direct equivalent: rebuild
ta.vwapA running sum of price × volume over volume, reset when the day changes
OnCalculate or OnTick. Pine resets on a new day by default, in the exchange's time zone; an MT5 day is a server-clock day (see the sessions trap). MQL5 Book: Local and server time
No direct equivalent: rebuild
ta.hma(source, length)Weighted averages in code (or two iMA LWMA handles, then a weighted average of their difference)
OnCalculate or OnTick. MQL5's indicator list has no Hull average; rebuild it as HMA = WMA(2 × WMA(n/2) − WMA(n), √n) and compare. MQL5 Reference: iMA, MetaTrader 5 help: Moving Average
No direct equivalent: rebuild
ta.vwma(source, length)sum(price × volume) / sum(volume) over length bars
OnCalculate or OnTick. TradingView defines it as sma(source × volume, length) / sma(volume, length); use the same volume on both sides. MQL5 Reference: CopyRates
No direct equivalent: rebuild
ta.supertrend(factor, atrPeriod)A state machine on ATR bands, with its ATR computed as Pine's RMA of true range
OnCalculate or OnTick. It inherits the ATR difference (ta.atr row) and flips on closes: port it on closed bars. MQL5 Reference: iATR, MetaTrader 5 help: Average True Range
No direct equivalent: rebuild
ta.valuewhen(), ta.barssince()Globals updated once per closed bar: the last value and bar time when the condition was true
Global scope + OnCalculate or OnTick. Keep the state yourself, and update it only on a new bar (tick-counters trap). MQL5 Reference: Global Variables
No direct equivalent: rebuild
ta.cum(source)A running total in a global, added once per closed bar
Global scope + OnCalculate or OnTick. It starts from the first bar each platform loads, so the level differs between them. MQL5 Reference: Global Variables
Needs care
Another ta.* functionIts formula, in code, unless MQL5's indicator list has a built-in with the same definition
OnCalculate or OnTick. Rebuild it from TradingView's definition and compare values bar by bar. MQL5 Reference: Technical Indicator Functions
No direct equivalent: rebuild
Other timeframes (5)
PineMQL5, and where it goesHow close
request.security(symbol, timeframe, expression)The same handle on that timeframe (e.g. iMA(_Symbol, PERIOD_D1, …)) or iClose/iHigh/iLow(_Symbol, PERIOD_D1, shift)
Handles in OnInit; reads in OnCalculate or OnTick. Shift 0 is the higher-timeframe bar still forming. For Pine's [1] with lookahead_on, take the higher-timeframe bar before the one that holds the chart bar you decide on: iBarShift(_Symbol, tf, that bar's time) + 1 (see the lookahead trap). MQL5 Reference: Indexing Direction in Arrays, Buffers and Timeseries, MQL5 Reference: iBarShift, MQL5 Reference: CopyBuffer
Needs care
request.security_lower_tf()CopyRates on the lower timeframe for the chart bar's time range
OnCalculate or OnTick. Pine hands you an array per chart bar; MQL5 makes you fetch the lower bars and group them by time. MQL5 Reference: CopyRates, MQL5 Reference: iBarShift
No direct equivalent: rebuild
request.currency_rate()The close of your broker's own currency-pair symbol, e.g. iClose(pair, PERIOD_D1, 1)
OnCalculate or OnTick. Pine's is a daily rate from TradingView's data; in MT5 the rate comes from your broker's quotes, under the symbol name the broker uses. MQL5 Reference: iClose, MQL5 Reference: Organizing Data Access
Needs care
request.economic()MQL5's Economic Calendar functions (CalendarValueHistory and the rest), by country or currency
OnInit or OnTimer. Pine requests an economic series for a country (GDP, inflation rate); the MQL5 calendar returns event values. Map the series to the calendar's events and check that the values agree. MQL5 Reference: Economic Calendar
No direct equivalent: rebuild
request.financial(), request.earnings(), request.dividends() …Another source for the data, or drop the condition
—. These are TradingView's data sets; MetaTrader's prices come from your broker's trade server. MQL5 Reference: Organizing Data Access
No direct equivalent: rebuild
Plots and colours (8)
PineMQL5, and where it goesHow close
plot()An indicator buffer: double Buf[]; SetIndexBuffer in OnInit; #property indicator_typeN DRAW_LINE
Global buffer + OnInit + OnCalculate. Count #property indicator_buffers and indicator_plots as you go (buffers trap). MQL5 Reference: SetIndexBuffer, MQL5 Reference: Program Properties (#property)
Direct
plot() in a strategyChart objects from the EA, or the plots in a separate indicator attached with iCustom + ChartIndicatorAdd
OnInit (attach) or OnTick (objects). OnCalculate runs only in custom indicators, so an EA has no buffers to draw with. MQL5 Reference: Event Handling Functions, MQL5 Reference: iCustom, MQL5 Reference: ChartIndicatorAdd
Needs care
plotshape(), plotchar(), plotarrow()A DRAW_ARROW buffer (PLOT_ARROW picks the symbol); EMPTY_VALUE where nothing shows
Global buffer + OnInit + OnCalculate. Write EMPTY_VALUE on bars without a shape, or every bar gets one. MQL5 Reference: Drawing Styles
Needs care
plotcandle(), plotbar()DRAW_CANDLES or DRAW_BARS: 4 buffers (open, high, low, close)
Global buffers + OnInit + OnCalculate. Four buffers per call, five with a colour per bar. MQL5 Reference: Drawing Styles
Needs care
hline()#property indicator_levelN in a separate window, or an OBJ_HLINE object
File header, or OnInit. A fixed level needs no buffer. MQL5 Reference: Program Properties (#property), MQL5 Reference: Object Types
Direct
fill()DRAW_FILLING: two buffers, the area between them filled
Global buffers + OnInit + OnCalculate. Two more buffers on top of the plots it fills between. MQL5 Reference: Drawing Styles
Needs care
bgcolor()Rectangles (OBJ_RECTANGLE) spanning the bars to shade
OnCalculate or OnTick; delete in OnDeinit. None of MQL5's 18 drawing styles colours a bar's background: draw objects and clean them up. MQL5 Reference: Drawing Styles, MQL5 Reference: ObjectCreate, MQL5 Reference: ObjectDelete
No direct equivalent: rebuild
barcolor()DRAW_COLOR_CANDLES: 4 data buffers and 1 colour buffer, drawn over the chart's own candles
Global buffers + OnInit + OnCalculate. MQL5 recolours by drawing candles of its own. MQL5 Reference: Drawing Styles
Needs care
Drawings (5)
PineMQL5, and where it goesHow close
line.new()ObjectCreate(chart_id, name, OBJ_TREND, 0, time1, price1, time2, price2)
OnCalculate or OnTick; ObjectDelete in OnDeinit. Name every object; calling ObjectCreate again on the same name moves it. Pine keeps a limited number of drawings; MQL5 keeps what you don't delete. MQL5 Reference: ObjectCreate, MQL5 Reference: Object Types, MQL5 Reference: ObjectDelete
Needs care
label.new()OBJ_TEXT (anchored to a time and price) or OBJ_LABEL (fixed on the screen)
OnCalculate or OnTick; ObjectDelete in OnDeinit. Same naming and clean-up as lines. MQL5 Reference: ObjectCreate, MQL5 Reference: Object Types, MQL5 Reference: ObjectDelete
Needs care
box.new()OBJ_RECTANGLE with two anchor points
OnCalculate or OnTick; ObjectDelete in OnDeinit. Same naming and clean-up as lines. MQL5 Reference: ObjectCreate, MQL5 Reference: Object Types, MQL5 Reference: ObjectDelete
Needs care
table.new()OBJ_RECTANGLE_LABEL and OBJ_LABEL objects laid out by hand, or Comment() for plain text
OnInit + OnCalculate or OnTick. There's no table object: position each cell yourself. MQL5 Reference: Object Types, MQL5 Reference: Comment
No direct equivalent: rebuild
polyline.new(), linefill.new()Segments of OBJ_TREND, or a filled shape built from objects
OnCalculate or OnTick; ObjectDelete in OnDeinit. Draw it piece by piece. MQL5 Reference: ObjectCreate, MQL5 Reference: Object Types
No direct equivalent: rebuild
Alerts (2)
PineMQL5, and where it goesHow close
alertcondition()Alert() or SendNotification() called by your code when the condition is true
OnCalculate or OnTick, once per closed bar. Pine only offers the condition to TradingView's alert dialog; in MQL5 the code sends the alert. SendNotification allows 2 calls a second and 10 a minute, and neither works in the Strategy Tester. MQL5 Reference: Alert, MQL5 Reference: SendNotification
Needs care
alert()Alert(), SendNotification(), or WebRequest() for a webhook (EA or script only)
OnTick (EA), once per closed bar. alert() defaults to the first call on each realtime bar, which can fire mid-bar. WebRequest needs the URL allowed in the terminal's options and doesn't run in the Strategy Tester. MQL5 Reference: Alert, MQL5 Reference: SendNotification, MQL5 Reference: WebRequest
Needs care
Orders and positions (7)
PineMQL5, and where it goesHow close
strategy.entry()CTrade: trade.Buy(volume, symbol, price, sl, tp) / trade.Sell(…); check ResultRetcode()
OnTick, after the new-bar check. An opposite entry reverses a Pine position. In MT5 an opposite deal of the same volume only closes the position on a netting account, and opens a second one on a hedging account: close the other side first, then enter. Pine sizes in contracts or units, MT5 in lots. MQL5 Reference: CTrade::Buy, MQL5 Reference: CTrade::Sell, MetaTrader 5 help: Position accounting
Needs care
strategy.exit()sl and tp on trade.Buy/Sell, or trade.PositionModify(symbol, sl, tp) later
OnTick. Pine's emulator fills exits inside a bar by its own price-path rule; MT5 fills SL and TP on ticks, real or generated. MQL5 Reference: CTrade::PositionModify, MetaTrader 5 help: Strategy Tester
Needs care
strategy.close(), strategy.close_all()trade.PositionClose(symbol) or PositionClose(ticket)
OnTick. strategy.close closes the entries with that ID, and like other market orders fills at the next bar's open by default; PositionClose sends its order when the EA calls it. On a hedging account close each position by ticket. MQL5 Reference: CTrade::PositionClose, MetaTrader 5 help: Position accounting
Needs care
strategy.order()trade.Buy/Sell, or pending orders (trade.BuyLimit, BuyStop …)
OnTick. strategy.order doesn't reverse a position automatically, as strategy.entry does; it adds to or reduces the position by its size. In MT5 a netting account nets an opposite order against the position, and a hedging account opens a second position. MQL5 Reference: CTrade::BuyLimit, MetaTrader 5 help: Position accounting
Needs care
strategy.cancel(), strategy.cancel_all()trade.OrderDelete(ticket)
OnTick. Keep the pending order's ticket when you place it. MQL5 Reference: CTrade::OrderDelete
Direct
strategy.position_size, strategy.position_avg_price …PositionSelect(symbol), then PositionGetDouble(POSITION_VOLUME / POSITION_PRICE_OPEN)
OnTick. On a hedging account PositionSelect picks the position with the lowest ticket: loop over positions and filter by your magic number. MQL5 Reference: PositionSelect, MQL5 Reference: PositionGetDouble
Needs care
strategy.risk.*()Your own checks before each order
OnTick. MQL5 has no strategy-level risk rules; write them. MQL5 Reference: CTrade
No direct equivalent: rebuild
State across bars (2)
PineMQL5, and where it goesHow close
varA global variable, or a static local
Global scope. Pine drops changes a realtime bar makes before its close; MQL5 keeps every change. Update it once per closed bar (tick-counters trap). MQL5 Reference: Global Variables, MQL5 Reference: Static Variables
Needs care
varipA plain global variable
Global scope. varip keeps every tick's change, which is what an MQL5 global does. MQL5 Reference: Global Variables
Direct
Arrays, maps and matrices (3)
PineMQL5, and where it goesHow close
array.new<…>(), array.*A dynamic array sized with ArrayResize
Global scope (if it persists) + where it's used. Mind the direction: ArraySetAsSeries flips indexing, and Pine's array.get(id, -1) (the last element) has no MQL5 shorthand. MQL5 Reference: ArrayResize, MQL5 Reference: ArraySetAsSeries
Needs care
map.new<…>(), map.*CHashMap<TKey,TValue> from #include <Generic\HashMap.mqh>
Global scope + where it's used. Pine keeps a map's insertion order: map.keys() and map.values() return keys and values in the order they were put. CHashMap stores unordered pairs, so a port that walks the keys must keep its own order, for example a parallel array of keys. MQL5 Reference: CHashMap<TKey,TValue>
Needs care
matrix.new<…>(), matrix.*matrix (of double), or a two-dimensional array for other types
Wherever it's used. An MQL5 matrix holds doubles; a Pine matrix can hold any one type. MQL5 Reference: Matrices and vectors
Needs care
Time and sessions (7)
PineMQL5, and where it goesHow close
time(timeframe, session, timezone), time_close()TimeToStruct(iTime(_Symbol, _Period, shift) + your offset) and a minutes-after-midnight test
OnCalculate or OnTick. Pine reads the session in the zone you pass, or the exchange's; MT5 bar times are server time. The offset changes when the two clocks change on different dates. MQL5 Book: Local and server time, MQL5 Reference: TimeToStruct, MQL5 Reference: iTime
Needs care
hour, minute, dayofweek, dayofmonth, month, yearTimeToStruct(iTime(_Symbol, _Period, shift), t): t.hour, t.min, t.day_of_week …
OnCalculate or OnTick. Pine's are in the exchange's time zone, MT5's in the server's. MQL5 Reference: TimeToStruct, MQL5 Book: Local and server time
Needs care
timestamp()A datetime on the server clock (seconds, not milliseconds)
Wherever it's used. Convert the zone and the unit. MQL5 Reference: TimeToStruct, MQL5 Book: Local and server time
Needs care
timenowTimeCurrent() (last quote's server time) or TimeTradeServer() (computed)
Wherever it's used. Pine's timenow is milliseconds since 1970 in UTC; TimeCurrent() is the server's time in seconds (a datetime). In the Strategy Tester TimeGMT() equals the simulated server time. MQL5 Reference: TimeCurrent, MQL5 Reference: TimeToStruct, MQL5 Reference: TimeTradeServer, MQL5 Reference: TimeGMT
Needs care
session.ismarket, session.isfirstbar …SymbolInfoSessionTrade for the symbol's sessions, compared with bar times
OnInit + OnCalculate or OnTick. Pine knows the exchange's regular hours; in MT5 you read the broker's trading sessions per weekday. MQL5 Reference: SymbolInfoSessionTrade
No direct equivalent: rebuild
syminfo.timezoneYour measured offset between the server clock and the exchange's
Input. TimeGMT() comes from your computer's clock, and in the Strategy Tester it equals the simulated server time, so it can't give you the offset there. MQL5 Reference: TimeGMT, MQL5 Book: Local and server time
No direct equivalent: rebuild
timeframe.change(timeframe)A new bar on that timeframe: iTime(_Symbol, PERIOD_D1, 0) differs from the last one seen
OnCalculate or OnTick. A D1 bar in MT5 opens at the server's midnight. MQL5 Reference: iTime, MQL5 Book: Local and server time
Needs care
Bar state (4)
PineMQL5, and where it goesHow close
barstate.isconfirmedA new-bar check (iTime at shift 0 changed), then read shift 1
OnTick (EA) or the closed bars in OnCalculate. An EA can't know which tick is a bar's last, so it acts on the next bar's first tick. MQL5 Reference: iTime, MQL5 Reference: Indexing Direction in Arrays, Buffers and Timeseries
Needs care
barstate.isnewThe same new-bar check
OnTick or OnCalculate. iTime at shift 0 differs from the last value you stored. MQL5 Reference: iTime
Direct
barstate.islast, barstate.isrealtime, barstate.ishistoryIn OnCalculate, the bar at index rates_total - 1 (left to right) is the one forming; an EA only sees live ticks (or the tester's)
OnCalculate or OnTick. History versus live isn't a flag in MQL5: decide from the bar's index. MQL5 Reference: Indexing Direction in Arrays, Buffers and Timeseries, MQL5 Reference: OnTick
Needs care
barstate.isfirstprev_calculated == 0 in OnCalculate, or OnInit for one-time setup
OnCalculate or OnInit. prev_calculated also drops to 0 when the terminal reloads history, so a first-bar block can run again. MQL5 Reference: OnCalculate
Needs care
Missing values (1)
PineMQL5, and where it goesHow close
na, na(), nz(), fixnan()EMPTY_VALUE in buffers (PLOT_EMPTY_VALUE for drawing), checked before any comparison
Wherever a value may be missing. Pine's comparisons with na are false; EMPTY_VALUE is DBL_MAX, a number above every price, and an indicator can set its own empty value instead. MQL5 Reference: Other Constants
Needs care
History and bar numbers (3)
PineMQL5, and where it goesHow close
x[n] (the history operator)An array read at shift n after ArraySetAsSeries(arr, true), or buf[i - n] in OnCalculate's left-to-right buffers
OnCalculate or OnTick. Pine keeps each series' past for you; in MQL5 you copy it (CopyBuffer, CopyRates) and pick the direction. MQL5 Reference: CopyBuffer, MQL5 Reference: ArraySetAsSeries
Needs care
bar_indexThe loop index i in OnCalculate's left-to-right arrays; rates_total - 1 - shift in timeseries order
OnCalculate. Both count from the oldest loaded bar, and each platform loads a different amount of history. MQL5 Reference: Indexing Direction in Arrays, Buffers and Timeseries
Needs care
math.max(), math.min(), math.abs() …MathMax, MathMin, MathAbs …
Wherever it's used. One to one for two arguments; Pine's take more. MQL5 Reference: MathMax
Direct

The nine traps it checks

The planner names a trap only when your script has what causes it, and says why the others don't apply.

Reading shift 0 instead of the bar that just closed

A Pine strategy calculates after a bar closes by default, and barstate.isconfirmed is true only on a bar's closing update, so both decide once per closed bar. (An indicator without barstate.isconfirmed recalculates on every realtime update, and its live signals can come and go too.) An EA's OnTick runs on every tick, and shift 0 is the bar still forming, so a signal read there can appear mid-bar, send its order, and be gone by the close.

The fix: Check for a new bar (iTime at shift 0 changed), then read shift 1 and 2 for a cross. You act on the next bar's first tick, the closest an EA gets to Pine's fill at the next open.

Sources: Pine User Manual: Strategies: broker emulator, Pine User Manual: Bar states, Pine User Manual: Execution model, MQL5 Reference: OnTick, MQL5 Reference: Indexing Direction in Arrays, Buffers and Timeseries

Indicator handles created in OnTick

In Pine, ta.ema() is a function you call on every bar. In MQL5, iMA() creates an indicator and returns a handle; its values take time to calculate, so a handle made where it's used may have nothing to give yet.

The fix: Create each handle once in OnInit (fail with INIT_FAILED if it's INVALID_HANDLE), read it with CopyBuffer, test the count it returns, and release it in OnDeinit.

Sources: Pine v6 reference: ta.ema(), MQL5 Reference: Technical Indicator Functions, MQL5 Reference: OnInit, MQL5 Reference: IndicatorRelease

Arrays read in the wrong direction (ArraySetAsSeries)

Pine's x[1] always means one bar back. MQL5 arrays index left to right by default, CopyBuffer stores the oldest value first whatever the flag, and a buffer bound with SetIndexBuffer goes back to common indexing. buf[1] can be the second-oldest bar, not the previous one.

The fix: Call ArraySetAsSeries(arr, true) on every array you read as Pine reads a series, after SetIndexBuffer for buffers, and set the direction of OnCalculate's arrays yourself rather than trust the default.

Sources: Pine User Manual: Execution model, MQL5 Reference: Indexing Direction in Arrays, Buffers and Timeseries, MQL5 Reference: CopyBuffer, MQL5 Reference: SetIndexBuffer, MQL5 Reference: OnCalculate

Counters that count ticks, not bars

A Pine var changed on a realtime bar is rolled back until the bar closes, so `count += 1` adds one per bar. An MQL5 global keeps every change: the same line in OnTick adds one per tick it handles, and OnTick doesn't even see every tick.

The fix: Update counters and running state only inside the new-bar block, from the closed bar's values. Pine's varip is the exception: it already behaves like an MQL5 global.

Sources: Pine User Manual: Variable declarations, Pine User Manual: Execution model, MQL5 Reference: Global Variables, MQL5 Reference: OnTick

Sessions on the broker's server clock, not the exchange's

Pine reads a session in the zone the script names, or the exchange's. MT5 charts are stamped in the broker's server time, and in the Strategy Tester TimeGMT() equals the simulated server time, so it can't tell you the offset there. A fixed offset is right only while the server and the exchange change their clocks on the same dates.

The fix: Measure the server's offset from the exchange for a winter and a summer week, convert bar times before the session test, and run the weeks when the two clocks differ.

Sources: Pine User Manual: Sessions, Pine User Manual: Time, MQL5 Book: Local and server time, MQL5 Reference: TimeGMT

request.security: lookahead and higher-timeframe bars

Without lookahead, Pine's history gets each higher-timeframe value at the end of its period, and live bars show the forming value, which repaints on reload. With lookahead_on and no [1], history sees the finished bar early: a future leak that a live EA, or a Strategy Tester run, can't reproduce, because the finished bar doesn't exist yet when they run. Don't port the leak. In MT5, shift 0 on another timeframe is the bar still forming, and a D1 bar runs on the server's day.

The fix: Port the confirmed pattern (expression[1] with lookahead_on) as Pine reads it: find the higher-timeframe bar that holds the chart bar being decided, then take the one before it. In an EA deciding on the bar that just closed, that is iBarShift(_Symbol, tf, iTime(_Symbol, _Period, 1)) + 1; in an indicator drawing past bars, iBarShift(_Symbol, tf, time[i]) + 1. Plain shift 1 on the higher timeframe reads the wrong day on each day's last chart bar. Check iBarShift for -1, the price for 0 (iClose and iHigh return 0 on an error), and CopyBuffer's count (-1 while that history loads). Compare daily highs and lows on both platforms before the signals.

Sources: Pine User Manual: Other timeframes and data: lookahead, Pine v6 reference: request.security(), Pine User Manual: Other timeframes and data, MQL5 Reference: Indexing Direction in Arrays, Buffers and Timeseries, MQL5 Reference: iBarShift, MQL5 Reference: iClose, MQL5 Reference: CopyBuffer

na is not EMPTY_VALUE

In Pine every comparison with na is false: `value > level` is false while value is na (though `not (value > level)` is then true). MQL5 marks a missing buffer value with an empty value: EMPTY_VALUE, which is DBL_MAX, unless the indicator sets its own with PLOT_EMPTY_VALUE (0.0, for example). DBL_MAX compared as a number is above every price, so `value > level` is true.

The fix: Write EMPTY_VALUE where Pine plots na (set PLOT_EMPTY_VALUE), and test the value against the empty value before every comparison on a value that can be missing. When you read another indicator with CopyBuffer, check which empty value it uses.

Sources: Pine User Manual: Type system: na value, MQL5 Reference: Other Constants, Pine User Manual: Other timeframes and data: gaps

Plots, buffers and what an EA can draw

Each MQL5 drawing style needs one to four data buffers, plus a colour buffer for a colour per bar; fill() becomes DRAW_FILLING with two more, and a strategy's plots have nowhere to go, because only custom indicators run OnCalculate.

The fix: Count buffers and plots before you write the header (#property indicator_buffers, indicator_plots); for a strategy, put the plots in a separate indicator and attach it with iCustom and ChartIndicatorAdd, or draw objects.

Sources: Pine User Manual: Limitations: plot counts, MQL5 Reference: Drawing Styles, MQL5 Reference: Technical Indicator Functions, MQL5 Reference: Event Handling Functions

Strategy fills and the tester's price model

Pine's broker emulator fills at the next bar's open by default (at the signal close with process_orders_on_close), guesses the path inside a bar from its open, high, low and close, and reverses a position on an opposite entry. MT5's Strategy Tester fills on ticks, real or generated by the mode you pick. An opposite deal of the same volume only closes the position on a netting account, and opens a second position on a hedging account.

The fix: Expect fills to differ even when the signals agree. Test MT5 on real ticks where the broker has them, and before an opposite entry close the other side first, on either account type, then enter. Compare signals bar by bar before you compare results.

Sources: Pine User Manual: Strategies: broker emulator, Pine User Manual: Strategies: process_orders_on_close, Pine User Manual: Strategies: broker emulator, Pine User Manual: Strategies: reversing positions, MetaTrader 5 help: Strategy Tester, MQL5 Reference: CTrade::Buy, MetaTrader 5 help: Position accounting

What the planner doesn't do

It plans the port and names the traps; the port, the compile and the comparison are yours.

No EA written, nothing compiled, nothing run
  • It doesn't write MQL5. The rows name the calls and where they go; the sketches under the traps show one fix each and say whether MetaEditor has compiled them.
  • It reads text, not a compiled script: your own functions, imported libraries and what a variable holds at run time stay unread. A line it doesn't recognise is simply not on the map.
  • It can't see your broker: the server's clock, the account's position system (netting or hedging) and the symbol's volume are yours to check.
  • The size is a count of what the script uses, not a quote or a time estimate.

The porting post walks one rule from our Opening Range Desk through the clock, the bar and the fills: Pine Script to MQL5. A port is done when both versions agree bar by bar on the same data, or when you can name the reason for every bar where they don't.

Method and privacy

Method: The planner reads the script in your browser: the version and declaration as the .pine file opener reads them, then each construct with comments and strings blanked out, so a call inside a comment never counts. Each construct maps to a row read on 1 November 2026 on MetaQuotes' MQL5 Reference, the MetaTrader 5 help and TradingView's Pine v6 manuals, each linked; where a page doesn't say how two calculations compare, the row says to compare them rather than guessing. The size counts the constructs found: 1 point for a direct call, 2 for one that needs care, 4 for one to rebuild, 4 more for a strategy and 1 per 100 lines (S up to 14, M up to 36). It is a text reading, not a compiler: it doesn't follow your own functions or imported libraries, and it doesn't write or compile MQL5.

Calculations run entirely in your browser — nothing you type is uploaded. Your last numbers stay in this browser so you can pick up where you left off; Forget them clears them. A link with your numbers carries them after the # sign, which browsers do not send to servers, and the page takes them out of the address before any analytics loads. Copy and download actions save your calculation locally. The site may record coarse tool actions under its privacy policy, without inputs, results, file contents or filenames.

Common questions

Is there a free Pine Script to MQL5 converter?

Converters and AI chats will write MQL5 from Pine, and a draft can be a useful start, but a draft still has to be compared with the original bar by bar. This planner doesn't write the EA: it reads your script, maps the built-in calls it recognises to their MQL5 equivalents with MetaQuotes' page for each, and names the traps that apply, so you or your AI port it knowing where the two versions will disagree. A port is done when both agree bar by bar on the same data, or when you can name the reason for every bar where they don't.

Pine Script to MQL5: where the two run differently, and a worked port

Can Pine Script run on MetaTrader 5?

No. MetaTrader 5 runs programs written in MQL5: Expert Advisors, which handle each new tick in OnTick, and custom indicators, which calculate in OnCalculate. A Pine script has to be rewritten in MQL5: an indicator() usually becomes a custom indicator and a strategy() an Expert Advisor.

What is the MQL5 equivalent of ta.ema?

An iMA handle with MODE_EMA, created once in OnInit, then read with CopyBuffer: shift 0 is the bar still forming and shift 1 the last closed bar. MetaQuotes recommends creating indicator handles in OnInit because their values take time to calculate. TradingView documents its EMA's first value and MetaTrader's help doesn't state its seed, so compare the two after a warm-up.

Why does my MT5 EA take trades the TradingView strategy never took?

Usually because the EA reads shift 0, the bar still forming, on every tick. A Pine strategy decides after each bar closes by default, so a cross that shows mid-bar and is gone by the close never trades in Pine, while the EA has already sent its order. Check for a new bar in the EA and read shift 1; the drill on this page plays it tick by tick.

How do I port request.security() without repainting?

Port the confirmed pattern, the expression offset by [1] with lookahead = barmerge.lookahead_on, as Pine reads it: find the higher-timeframe bar that holds the chart bar being decided, then take the one before it. In an EA deciding on the bar that just closed, that is iBarShift(_Symbol, PERIOD_D1, iTime(_Symbol, _Period, 1)) + 1; reading D1 shift 1 directly gets the wrong day on each day's last chart bar. A Pine request with lookahead_on and no [1] sees the finished higher-timeframe bar early on history; a live EA or a Strategy Tester run can't reproduce that, and a port shouldn't copy it. iBarShift can return -1 and iClose or iHigh 0 while the other timeframe's history loads, so check both.

Is my Pine script uploaded?

No. The planner reads the script in your browser; it isn't uploaded, stored or put in the page's link, and the copy and download buttons work on your device. The page records only which steps were used (a paste, a file opened, a button), never what the script holds.

Does the MQL5 in the plan compile?

The plan maps calls and links MetaQuotes' documentation; it doesn't write your EA. The short MQL5 sketches under the traps compiled in MetaEditor with 0 errors and 0 warnings on 1 November 2026; none of them was run against a trade server, since each shows the fix for one trap, not a finished EA. Compile your own port in MetaEditor and run it in the Strategy Tester and on a demo account before it goes near real money.

Does it work for MT4 and MQL4?

It maps to MQL5, for MetaTrader 5. MQL4 is a different language with its own calls, so the call map doesn't carry over to MT4; check each call against MQL4's own reference.

Goes deeper

Limits and trading risk

These tools calculate from your inputs, supported historical records or explicitly labelled teaching examples. They do not verify a live account, predict future returns or confirm that a broker or prop-firm rule has been met. Missing costs, floating losses and changed market conditions can materially change outcomes. Trading can lose the capital committed to it; check the stated method and your account specifications.

Read the risk disclosure