◆ New — Strategy Validator, Code Fixer & Prop Compliance Checker are live. Try one free →
Home / Learn / How to Convert a MetaTrader EA to TradingView Pine Script
MetaTrader

How to Convert a MetaTrader EA to TradingView Pine Script

A developer-facing guide to porting a working MQL4/MQL5 Expert Advisor into a clean, non-repainting Pine v6 strategy: what maps cleanly, what cannot be faithfully converted (hedging, multi-symbol trading, tick scalping, event callbacks, DLL imports), a worked EMA-cross example, and how to validate the result signal-for-signal. This is educational material only, not financial advice; automated trading carries a genuine risk of loss, and any converted strategy should be studied and backtested before it goes anywhere near live capital.

Why this is a re-implementation, not a line-by-line port

When traders ask how to convert a MetaTrader EA to Pine Script, they usually picture a line-by-line translation. In practice the two platforms model time and price in fundamentally different ways, and that mismatch is where most converted strategies quietly go wrong. MQL is event-driven and imperative; Pine is declarative and series-based. So the real task is not translating syntax; it is re-implementing the execution model faithfully.

Consider what an MQL EA actually does. OnTick() fires on every incoming tick, so the first thing a careful EA does is hand-roll a new-bar gate — comparing iTime(_Symbol,_Period,0) against a stored timestamp — to avoid acting many times inside one candle. It tracks its own trades with a magic number, loops through OrdersTotal() or calls PositionSelect() to check what it already holds, and sizes, sends, and verifies each order by hand. The programmer owns all the bookkeeping.

Pine inverts almost all of that. A Pine strategy re-evaluates the whole script once per bar (on the confirmed close by default), and the strategy.* engine owns position and fill bookkeeping for you. There is no per-tick callback to gate, no magic number to reconcile, no manual order loop. strategy.position_size tells you your net exposure; strategy.entry and strategy.exit handle the orders. Whole categories of MQL code — the new-bar gate, the position-scanning loop, the fill-result check — simply have no Pine counterpart because the platform already does that job.

That is the mental shift. You are not translating the plumbing; you are extracting the strategy — the entry condition, the exit rule, the risk distance — and re-expressing it in a model where the platform handles the plumbing. Get that framing right and the conversion is clean. Treat it as a literal transliteration and you will fight the language the whole way. Educational context only; no conversion makes a strategy profitable, and all trading carries risk of loss.

What maps cleanly across

A good portion of a well-written EA translates almost mechanically, because both platforms compute the same standard indicators and both express "a signal happened." Knowing which pieces map cleanly lets you spend your attention on the pieces that do not.

Indicators map one-to-one. MQL4's direct calls and MQL5's handle-plus-CopyBuffer pattern both resolve to a single Pine built-in:

  • iMA(...,MODE_EMA,...) / iMA handle → ta.ema(close, len)
  • iRSI(...)ta.rsi(close, len)
  • iATR(...)ta.atr(len)
  • CopyBuffer(handle, 0, 1, 1, buf) reading shift 1 → just mySeries[1] in Pine

Cross tests map cleanly too. The idiomatic MQL check for a fresh cross reads two closed bars — fast above slow on shift 1, fast below slow on shift 2. In Pine that entire two-bar comparison collapses into ta.crossover(fast, slow) (and ta.crossunder for the other side), which is true only on the bar the cross first occurs.

Orders map by intent. OrderSend(..., OP_BUY, ...) or trade.Buy(...) becomes strategy.entry("Long", strategy.long). A stop or take-profit expressed as a point distance in MQL — sl = ask - InpStopPts * _Point — becomes a price handed to strategy.exit(stop=, limit=), with syminfo.mintick playing the role of _Point: stopDist = stopPts * syminfo.mintick.

Inputs map directly: input int / input double / extern become input.int(...) and input.float(...).

One discipline carries over unchanged. In MQL you read signals from the last closed bar (shift 1) to avoid acting on a forming candle. In Pine you get that for free on bar close, but if a signal also feeds an alert or a plotted marker, gate it with and barstate.isconfirmed so it cannot repaint. Same principle, different mechanism. This is educational information only, not a recommendation to trade any of these signals.

What cannot be faithfully converted — and the honest alternative

Some EA features have no faithful Pine equivalent. Pretending otherwise is how converted strategies mislead. Here is each one and the honest alternative.

Hedging (simultaneous long and short). A Pine strategy holds one net position per symbol, exactly like an MQL5 netting account. If your EA relied on MQL4-style independent tickets — a long and a short open at once — Pine cannot reproduce it in one script. The honest alternative is two separate scripts, one long-only and one short-only, run side by side.

Multi-symbol trading from one EA. An MT5 EA can open trades on other symbols. Pine cannot. request.security() reads another symbol's data but cannot place an order on it — a strategy only ever trades the chart's own symbol. You can read EURUSD to inform a GBPUSD trade; you cannot trade both from one script.

Tick / sub-bar scalping. Pine is bar-based. Logic that acts on individual ticks or fine sub-bar structure has no equivalent; the finest honest granularity is the bar.

Event callbacks and external code. OnTradeTransaction, OnTimer, OnChartEvent, and #import / DLL calls have no Pine counterpart at all. Pine is sandboxed and bar-driven — there is no timer event and no way to call external libraries.

Tick-granular trailing stops. An MQL EA can trail a stop tick-by-tick with OrderModify. Pine's strategy.exit(trail_points=...) trails on bar granularity only, so the fills will not match a tick-level trail.

Grids and martingale. These lean on stacking many independent orders. Pine offers only limited pyramiding, and it nets rather than holding separate tickets, so a faithful grid or martingale is not reproducible.

A good converter should flag these rather than silently emit code that looks equivalent but is not. That is the honest bar. Educational only; note that grid and martingale structures in particular can carry severe, open-ended loss — all trading carries risk of loss.

Worked example: an EMA-cross EA to a clean Pine strategy

Take the common MQL5 shape: create EMA handles in OnInit, gate to a new bar in OnTick, read the fast and slow EMA from the last closed bar via CopyBuffer, and if there is no open position for this magic, trade.Buy with a stop a fixed point distance below the ask.

5
// MQL5 core of the strategy (bookkeeping omitted for brevity)
double f[1], s[1];
CopyBuffer(g_hFast, 0, 1, 1, f);   // fast EMA, shift 1 (closed bar)
CopyBuffer(g_hSlow, 0, 1, 1, s);   // slow EMA, shift 1
bool crossUp = f[0] > s[0];        // combined with a shift-2 check for a fresh cross
if(crossUp && !PositionSelect(_Symbol))
   trade.Buy(InpLots, _Symbol, ask, ask - InpStopPts * _Point, 0.0);

Now strip it to the strategy and let Pine own the plumbing. The new-bar gate becomes bar-close evaluation; the magic-number position check becomes strategy.position_size == 0; the two-bar cross test becomes ta.crossover; _Point becomes syminfo.mintick. The stop input is wired straight into strategy.exit so it is a real, live risk control — not a dead parameter.

//@version=6
// Educational only — validate before trading; not financial advice.
strategy("EMA Cross (converted from MQL5)", overlay = true, pyramiding = 0)

fastLen = input.int(20, "Fast EMA")
slowLen = input.int(50, "Slow EMA")
stopPts = input.int(300, "Stop distance (points)")

fast = ta.ema(close, fastLen)
slow = ta.ema(close, slowLen)

crossUp = ta.crossover(fast, slow)

if crossUp and barstate.isconfirmed and strategy.position_size == 0
    strategy.entry("Long", strategy.long)

stopDist = stopPts * syminfo.mintick
if strategy.position_size > 0
    strategy.exit("Stop", "Long", stop = strategy.position_avg_price - stopDist)

Notice what vanished: no OnInit handle creation, no g_lastBar gate, no PositionSelect loop, no fill-result check. The and barstate.isconfirmed gate makes the entry non-repainting, and strategy.position_avg_price gives the stop its reference without you tracking the entry price by hand. This is a faithful long-only port; if the EA also sold on a downward cross, that belongs in a second script. Educational only — this is not a strategy recommendation, and it can lose money.

Validate the conversion signal-for-signal

A conversion that compiles is not a conversion that matches. The whole point of porting is to preserve behaviour, so verify it explicitly rather than trusting that similar code produces similar trades.

Start by aligning the ground truth. Put the original EA in the MetaTrader Strategy Tester on a specific symbol and date range, and log every entry and exit with its bar timestamp and price. Then run the Pine strategy on the same symbol and range in TradingView and read its List of Trades. You are comparing two ordered lists of (time, side, price) events. They should line up bar for bar.

Watch for the predictable sources of divergence:

  • Off-by-one from the forming bar. If the EA read the live shift-0 candle while Pine waits for confirmation, entries will differ by a bar. The fix is to make both read the closed bar — in Pine, that is the barstate.isconfirmed gate.
  • Point vs pip. _Point on a 5-digit broker is a tenth of a pip. If your stopPts distance looks ten times too tight or too loose, this is usually why — syminfo.mintick must correspond to the same unit the EA used.
  • Spread, fills, and session data. MetaTrader fills at bid/ask with a real spread; Pine fills at the modelled price. Small price differences on otherwise-matching trades are expected. Whole missing or extra trades are the real red flags — chase those, not the pennies.
  • Netting vs hedging. If the EA held overlapping positions, a single Pine strategy cannot match its trade count. That is a design mismatch telling you to split the script, not a bug to patch.

ForexCodes has an MQL → Pine converter at /metatrader#reverse that does exactly this reverse translation and automatically flags the features that cannot be faithfully converted — hedging, multi-symbol trades, tick logic, event callbacks, DLL imports. Treat its output as a reviewed first draft, not magic: read the flags, run the signal-for-signal comparison above, and backtest on a demo account. Educational and software only, not financial advice; no conversion or validation removes the risk of loss.

Key takeaways

Educational only — not financial advice. Trading involves substantial risk of loss.

Catch the bug that compiles.Run auditGet Pro