A Pine Script strategy and its MQL5 version can use the same indicators and still trade on different bars. Here are the three timing bugs behind most of that, and a small EA to check for them.
1. Reading the wrong bar
In Pine, close[1] is the last closed bar. In MQL5 you choose the index yourself. In OnTick(), shift 0 is the bar that is still forming. A signal that is calculated on the close should read shift 1 and 2.
2. Running once per tick instead of once per bar
TradingView runs a strategy once per bar by default. OnTick() runs on every tick, so without a new-bar check the same signal can fire many times. Save the time of the last processed bar and return early if it has not changed. Save it only after your data reads succeeded, so another tick can retry if the indicator is not ready.
3. Missing the rules that strategy.entry() applies for you
strategy.entry() reverses an opposite position and stops at the pyramiding limit. In MQL5, CTrade only sends what you tell it to. Close the opposite side, count your open positions and check the return code of every request.
A signal-only EA to check the first two
This EA reads the last two closed bars of a 10 and 30 period EMA and prints a cross. It places no trades, so you can compare it with the chart first:
int fastHandle = INVALID_HANDLE;
int slowHandle = INVALID_HANDLE;
datetime lastProcessedBar = 0;
int OnInit()
{
fastHandle = iMA(_Symbol, _Period, 10, 0, MODE_EMA, PRICE_CLOSE);
slowHandle = iMA(_Symbol, _Period, 30, 0, MODE_EMA, PRICE_CLOSE);
if(fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE)
return INIT_FAILED;
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if(fastHandle != INVALID_HANDLE) IndicatorRelease(fastHandle);
if(slowHandle != INVALID_HANDLE) IndicatorRelease(slowHandle);
}
void OnTick()
{
const datetime currentBar = iTime(_Symbol, _Period, 0);
if(currentBar == 0 || currentBar == lastProcessedBar)
return;
double fast[2], slow[2];
if(CopyBuffer(fastHandle, 0, 1, 2, fast) != 2 ||
CopyBuffer(slowHandle, 0, 1, 2, slow) != 2)
return;
lastProcessedBar = currentBar;
if(fast[0] <= slow[0] && fast[1] > slow[1]) Print("Closed-bar EMA cross up.");
if(fast[0] >= slow[0] && fast[1] < slow[1]) Print("Closed-bar EMA cross down.");
}
CopyBuffer() starts at shift 1, so index 0 is shift 2 and index 1 is shift 1. Run it on a chart, put the same EMAs on the same symbol and timeframe in TradingView, and compare every printed cross with the cross on the chart. If one is a bar early or late, fix the shift before you add any orders.
The complete file, with input parameters, is on GitHub: https://github.com/karanpixorce-prog/pine-to-mql5-signal-ea
Where to go next
The full conversion guide has a mapping table, a list of what does not convert and a verification checklist: https://codeflowos.com/blog/pine-script-to-mql5-guide
I built CodeFlowOS, a tool that produces a first-draft MQL5 file from Pine Script: https://codeflowos.com/pine-script-to-mql5. Review and test its output, like any converted code.
This is educational, not financial advice.
Top comments (0)