Learning Center

Turn TradingView strategy.* logic into an MQL5 Expert Advisor

How strategy.entry, strategy.close and pyramiding map to positions, CTrade calls and a once-per-bar decision loop.

Two different execution models

A Pine strategy is evaluated on each bar close by default, and the broker emulator fills orders on the next bar open. Positions are tracked for you: strategy.position_size tells you where you are.

An MQL5 Expert Advisor receives OnTick on every price change. Nothing remembers your position unless you ask the terminal. To reproduce Pine behaviour you need two things: run your decision once per new bar, and read the open position from PositionSelect before acting.

The Pine Script original

A long and short reversal strategy with no pyramiding (the default pyramiding=0 allows only one entry per direction).

Pine Script

//@version=5
strategy("Reversal", overlay=true)
fast = ta.sma(close, 10)
slow = ta.sma(close, 30)
if ta.crossover(fast, slow)
    strategy.entry("L", strategy.long)
if ta.crossunder(fast, slow)
    strategy.entry("S", strategy.short)

The MQL5 structure

strategy.entry in the opposite direction closes the existing position and opens a new one. In MQL5 on a netting account you can send an order for the full reverse volume, but closing first then opening is clearer and also works on hedging accounts.

MQL5

#include <Trade/Trade.mqh>
CTrade trade;
input double Lots = 0.10;
int hFast, hSlow;
datetime lastBar = 0;

int OnInit(){
   hFast = iMA(_Symbol, _Period, 10, 0, MODE_SMA, PRICE_CLOSE);
   hSlow = iMA(_Symbol, _Period, 30, 0, MODE_SMA, PRICE_CLOSE);
   return (hFast == INVALID_HANDLE || hSlow == INVALID_HANDLE) ? INIT_FAILED : INIT_SUCCEEDED;
}
void OnDeinit(const int r){ IndicatorRelease(hFast); IndicatorRelease(hSlow); }

int PositionDir(){
   if(!PositionSelect(_Symbol)) return 0;
   return PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY ? 1 : -1;
}

void OnTick(){
   datetime t = iTime(_Symbol, _Period, 0);
   if(t == lastBar) return;          // once per new bar, like Pine on bar close
   lastBar = t;
   double f[3], s[3];
   if(CopyBuffer(hFast, 0, 1, 2, f) != 2 || CopyBuffer(hSlow, 0, 1, 2, s) != 2) return;
   // f[1] = last closed bar, f[0] = the bar before it (non-series copy)
   bool crossUp   = f[0] <= s[0] && f[1] > s[1];
   bool crossDown = f[0] >= s[0] && f[1] < s[1];
   int dir = PositionDir();
   if(crossUp && dir != 1){ if(dir == -1) trade.PositionClose(_Symbol); trade.Buy(Lots); }
   if(crossDown && dir != -1){ if(dir == 1) trade.PositionClose(_Symbol); trade.Sell(Lots); }
}

Pyramiding, strategy.exit and other details

If the Pine script sets pyramiding=3, replace the PositionSelect check with a count of positions carrying your magic number (trade.SetExpertMagicNumber) and allow entries until the count reaches 3. On a netting account multiple entries merge into one position, so pyramiding needs a hedging account to behave the same.

strategy.exit with stop and limit maps to SL and TP prices on the order itself; see the stop loss and position sizing tutorial. process_orders_on_close=true in Pine means fills at the bar close, which in MQL5 is roughly the first tick of the next bar, so expect small price differences.

Verify it

Compile, run the Strategy Tester on the same symbol and timeframe as TradingView, and compare the list of trades by date. A matching entry date with a slightly different price is normal; a missing or extra trade usually means the cross is read on the wrong bar index.

Related code snippets