Best Practices for Porting MT4 Experts to MT5: India 2026 Guide
Porting an MT4 EA to MT5 is a rewrite, not a conversion — covering the five recurring mistakes real migrations hit (leaked indicator handles, missing ArraySetAsSeries, stale rates, OrdersTotal confusion, enum mismatches) and why MQL5 can run up to 20x faster once done correctly.
Best Practices for Porting MT4 Experts to MT5: India 2026 Guide
TLDR
Porting an MT4 Expert Advisor to MT5 is a rewrite, not a conversion — MQL5 replaced MQL4's single-order model with a relational system of Orders, Deals, and net Positions, moved indicators from direct inline calls to a handle-based system the terminal caches, and MT5 defaults to netting mode rather than hedging. The five mistakes that show up most often in real migrations are memory leaks from indicator handles never released in OnDeinit(), missing ArraySetAsSeries() calls causing silent indexing errors, stale price data from skipping RefreshRates(), confusion between OrdersTotal() and PositionsTotal(), and enum value mismatches carried over from MQL4 assumptions. Done correctly, the payoff is real: MQL5 can execute up to 20x faster than equivalent MQL4 code.
Why This Isn't a Simple Conversion
OrderSend() and the rest of MQL4's trade function family simply do not compile in MT5 — MQL5 requires MqlTradeRequest structures or the higher-level CTrade class instead. Automated .mq4-to-.mq5 converters exist and can handle straightforward syntax translation, but the underlying architectural differences mean a mechanically converted EA rarely runs correctly without manual rework — treating this as a find-and-replace exercise is the single biggest planning mistake a migration can make.
If you want this handled by developers who do this migration regularly, Viprasol offers MQL5 development with MT4-to-MT5 porting experience.
The Core Architectural Shifts
Area | MQL4 | MQL5 |
|---|---|---|
Trade execution | OrderSend(), OrderClose() | MqlTradeRequest struct or CTrade class |
Order model | Single orders | Relational Orders, Deals, and net Positions |
Indicators | Inline calls, recalculated every time | Handles created once, cached by the terminal, read via CopyBuffer() |
Price/account data | Direct variables (Close[], Bid) | Function calls (iClose(), SymbolInfoDouble()) |
Position mode default | Hedging (multiple positions per symbol) | Netting by default; hedging requires an explicit account setting |
Order Management: The Biggest Rewrite
The fundamental difference in trade management is that MQL4 tracked a flat list of orders, while MQL5 relates Orders (instructions to trade), Deals (executed transactions), and net Positions (the account's current holding per symbol) to each other. An EA that opened multiple separate "orders" on the same symbol in MQL4 under hedging behavior needs to be rethought under MQL5's netting default, where those would combine into a single net position unless hedging mode is explicitly enabled in account settings. This is not a syntax change — it changes what the EA's own internal state tracking needs to represent.
Indicators: From Inline Calls to Handles
MQL4 recalculated an indicator value on every single call. MQL5's handle system lets the terminal cache indicator calculations — a handle is created once, typically in OnInit(), and subsequent reads pull from that cached calculation via CopyBuffer() rather than triggering a fresh recalculation each time. This is a meaningful performance improvement, but it introduces a new failure mode entirely absent in MQL4: handles that are created but never released in OnDeinit() leak memory over the EA's running lifetime, a bug that will not show up in a short backtest but degrades a live account running for weeks.
A Concrete Before/After: Placing an Order
The order execution rewrite is where the architectural shift is most visible in actual code:
// MQL4 — will not compile in MQL5 int ticket = OrderSend(Symbol(), OP_BUY, 0.1, Ask, 3, 0, 0, "Buy order", 12345, 0, clrGreen);// MQL5 — using the CTrade class #include <TradeTrade.mqh> CTrade trade;
trade.SetExpertMagicNumber(12345); if(!trade.Buy(0.1, Symbol(), SymbolInfoDouble(Symbol(), SYMBOL_ASK), 0, 0, "Buy order")) { Print("Order failed: ", trade.ResultRetcodeDescription()); }
Beyond the syntax, note what changed structurally: MQL4's OrderSend() takes stop-loss and take-profit directly as parameters and returns a simple ticket number. MQL5's CTrade.Buy() call omits them here (they can be set via separate parameters or a follow-up modify call), and — critically — the return value is a boolean success flag, with the actual result details (including any error) retrieved via trade.ResultRetcodeDescription() rather than inferred from the return value alone. Code that only checks "did OrderSend return a positive ticket" has no direct equivalent pattern to fall back on in MQL5 without this explicit result-checking step.
Porting Custom Indicators: Buffer and Timeseries Changes Beyond OnInit()
Custom indicators carry their own distinct set of porting traps beyond the EA-level order management rewrite. SetIndexBuffer() is the one indicator function name preserved from MQL4, but its signature changed: MQL5 requires an explicit third argument specifying the buffer's data type (commonly INDICATOR_DATA), where MQL4's version took only the index and the array. Code ported by search-and-replace on the function name alone will fail to compile the moment this third argument is missing — a useful signal that at least catches the problem at compile time rather than silently at runtime.
A subtler and more dangerous change is the default indexing direction. MQL4 indicator buffers defaulted to as-series ordering (index 0 is the most recent bar). MQL5 buffers default to standard array ordering (index 0 is the oldest bar in the calculation window) — the exact opposite. A ported indicator that reads buffer values with MQL4's indexing assumptions, without an explicit ArraySetAsSeries() call after binding the buffer, does not fail to compile — it silently reads calculations for the wrong bar, producing an indicator that looks plausible but is subtly and consistently wrong. This is the indicator-specific analog of the array-indexing mistake already common on the trading side, and needs the same explicit, deliberate fix rather than an assumption that old code "just works."
MQL5 also fully supports object-oriented buffer management, letting indicator arrays live as class members with encapsulated calculation logic rather than only as global-scope arrays — a structural option that did not really exist in MQL4's more limited OOP support. This is not a required migration step, but for a complex multi-buffer indicator, restructuring around classes during the port is often less error-prone than mechanically preserving MQL4's global-array structure inside MQL5's different indexing defaults.
How Position IDs Actually Link Orders, Deals, and Positions
The Orders/Deals/Positions relationship described above is not just conceptual — it is implemented through a specific identifier that MQL4 never had. Every position carries a POSITION_IDENTIFIER that never changes for the life of that position, and this same identifier appears as ORDER_POSITION_ID on every order and DEAL_POSITION_ID on every deal connected to it — this is the actual mechanism, not the ticket number, that links the three together. HistorySelectByPosition() uses this identifier to pull the complete history of orders and deals tied to one position, which is the correct way to reconstruct a position's full fill history rather than trying to infer it from ticket numbers, which is an MQL4-era mental model that does not map onto MQL5's structure.
This distinction becomes concrete with partial fills. A single 1.0-lot buy order that fills in two pieces produces one order, but two separate deals, both carrying the same DEAL_POSITION_ID — a 0.6-lot deal and a 0.4-lot deal, for example. Code ported from MQL4 that assumes "one order equals one fill equals one ticket to track" has no direct way to represent this, because MQL4's single-order model never needed to. A ported EA that logs or reconciles trades by ticket number alone, rather than by position ID with a proper deal-history query, will misrepresent exactly this kind of partially-filled position — recording it as a single atomic fill when it was actually two, with two different fill prices that a naive ticket-based average would blend incorrectly.
Worked Example: One Order, Two Deals, One Position
A 1.0-lot buy order on EURUSD placed by a ported EA fills against available liquidity in two pieces:
Object | Identifier | Detail |
|---|---|---|
Order | Order #4471 | 1.0 lot buy instruction; ORDER_POSITION_ID = 9001 |
Deal 1 | Deal #8801 | 0.6 lot filled at 1.0842; DEAL_POSITION_ID = 9001 |
Deal 2 | Deal #8802 | 0.4 lot filled at 1.0844; DEAL_POSITION_ID = 9001 |
Resulting position | Position ID 9001 | 1.0 lot net, volume-weighted average price 1.08428 |
A properly ported EA calls HistorySelectByPosition(9001) to retrieve both deals, computes the volume-weighted average fill price (0.6×1.0842 + 0.4×1.0844, divided by 1.0 = 1.08428), and logs the position accurately. A ported EA still thinking in MQL4's ticket-per-order terms has no natural place to represent deal #8802 at all — it may log only the first fill, silently understating the position's real size, or overwrite the first fill's price entirely rather than computing the correct weighted average.
The Five Most Common Migration Mistakes
Across real EA and indicator migrations, five specific code-level mistakes recur consistently:
Memory leaks from unreleased indicator handles. Every handle created in OnInit() needs a corresponding release in OnDeinit() — skipping this does not crash the EA immediately, making it easy to miss until a live account has been running for a while.
Missing ArraySetAsSeries() calls. Array indexing direction is not automatic in MQL5 the way it effectively was in MQL4 — forgetting this call causes silent, hard-to-diagnose indexing errors rather than a compile error.
Stale data from skipping RefreshRates(). Cached bid/ask values can go stale between reads if rates are not explicitly refreshed, leading to decisions made on outdated prices.
Confusing OrdersTotal() with PositionsTotal(). In MQL4, OrdersTotal() covered both open orders and positions together. In MQL5 these are separate concepts with separate counting functions — code that assumes MQL4's combined semantics undercounts or overcounts silently.
Enum value mismatches carried over from MQL4 assumptions. Enumerations that meant one thing in MQL4 do not always map directly to MQL5's equivalents, and an incorrect assumption here can misclassify order types or states without an obvious error.
Step-by-Step Migration Process
Do not start from an automated converter's output as the final code. Automated .mq4-to-.mq5 tools are useful for a rough first pass on syntax, but the architectural rewrite — order model, indicator handles — needs deliberate manual work regardless.
Rewrite the order management layer first. Decide explicitly whether the EA needs netting or hedging behavior in MQL5, and build the position-tracking logic around that decision rather than assuming MQL4's behavior carries over.
Convert indicator calls to the handle pattern, creating handles in OnInit(), reading via CopyBuffer(), and explicitly releasing every handle in OnDeinit().
Audit every array access for ArraySetAsSeries() and every price read for proper refresh handling, rather than assuming MQL4 patterns transfer directly.
Run the ported EA in Strategy Tester against the same historical period as the original MQL4 backtest, and compare results directly — a meaningfully different result on identical data signals a migration bug, not a market difference.
Forward-test on demo before funding a live or prop firm account, watching specifically for the five mistakes above rather than only checking overall profitability.
For any custom indicators, explicitly set buffer indexing direction with
ArraySetAsSeries()after binding each buffer — do not assume MQL4's as-series default carries over, since MQL5 defaults to the opposite ordering.Reconcile trade logging against position ID and deal history, not ticket numbers alone. Use
HistorySelectByPosition()to verify the EA's own trade records match the platform's actual deal history, especially for any position that could receive a partial fill.
Testing Before Going Live
The most reliable validation is a direct comparison: run the ported MQL5 version and the original MQL4 version against identical historical data and confirm they produce the same trades, not just similar aggregate statistics. Aggregate metrics like total return or win rate can look acceptably close while individual trade-level behavior has actually diverged due to one of the five common mistakes — comparing at the trade level catches discrepancies that portfolio-level comparison hides.
India-Specific Considerations
For EAs destined for prop firm evaluation, covered in Viprasol's guide to complying with prop firm rules for automated trading, confirm which platform — MT4 or MT5 — your target firm actually supports before investing in a full port, since not every firm supports both, and porting effort spent on the wrong platform is effort wasted. Where a firm supports both, MT5's netting-by-default behavior should be checked against that firm's specific rules, since some prop firm rule sets have assumptions baked in around position counting that behave differently under netting versus hedging.
The position-ID reconciliation practice covered above is worth treating as a hard requirement rather than a nice-to-have specifically for prop-firm-bound EAs, since firms may audit trade logs during a payout review — an EA whose internal trade log silently misrepresents a partially-filled position, because it was ported with MQL4's ticket-per-order assumptions still baked in, produces an audit trail that does not match the platform's actual deal history. That mismatch is exactly the kind of discrepancy a review process is designed to catch, and it originates from a porting bug rather than any dishonesty in the strategy itself — which does not make it any less costly to discover after the fact.
Common Process Mistakes When Porting
Treating an automated converter's output as production-ready. It handles syntax, not the architectural rewrite that order management and indicator handling genuinely require.
Skipping trade-level comparison testing. Aggregate performance metrics looking similar between MQL4 and MQL5 versions does not confirm the migration preserved actual behavior.
Not deciding netting vs hedging deliberately. Inheriting MT5's default without considering whether the strategy's logic assumed MQL4's hedging-style multiple positions can silently change behavior.
Porting to the wrong platform for the target account. Confirm the destination prop firm or broker actually needs MT5 before committing migration effort — some contexts still specifically require MT4.
Underestimating the timeline. Developers with significant migration experience still describe this as a genuine rewrite project, not a quick conversion task — planning it as the latter leads to rushed, under-tested code.
Assuming MQL4's indicator buffer indexing carries over unchanged. MQL5 buffers default to the opposite ordering direction from MQL4 — a ported indicator missing an explicit ArraySetAsSeries() call will compile and run, but silently read the wrong bar's calculation.
Tracking trades by ticket number instead of position ID. MQL5's partial-fill model means one order can produce multiple deals sharing one position ID — ticket-based reconciliation ported from MQL4's single-order model will misrepresent partially-filled positions.
Build vs Buy: When to Get a Developer
Use an automated converter as a starting point only if you plan to manually rework the order management and indicator handling afterward — never as the final deliverable.
Get a professional port if the EA is complex, destined for a funded or prop firm account where bugs have real financial consequences, or you want trade-level validation against the original MQL4 version done properly. See Viprasol's MQL5 developer services for MT4-to-MT5 migration.
Related Glossary Terms
For more definitions, visit the AI and software glossary.
Netting Mode: MT5's default position mode, where multiple trades on the same symbol combine into a single net position rather than existing as separate positions.
Hedging Mode: A position mode allowing multiple simultaneous positions on the same symbol in opposite directions, MT4's standard behavior and an explicit opt-in setting in MT5.
Indicator Handle: A reference to a cached indicator calculation in MQL5, created once and read repeatedly via CopyBuffer(), replacing MQL4's inline recalculation on every call.
MqlTradeRequest: The structure MQL5 uses to submit trade requests, replacing MQL4's OrderSend() function family.
Position ID: A permanent identifier assigned to each MQL5 position, appearing as ORDER_POSITION_ID and DEAL_POSITION_ID on every order and deal connected to it, used to reconstruct a position's full fill history.
Deal: The record of an actual executed transaction in MQL5; a single order can produce multiple deals when filled partially, all sharing the same position ID.
FAQ
Can I just use an automated MQ4 to MQ5 converter?
Automated converters can handle basic syntax translation, but the architectural differences — order model, indicator handles — require manual rework regardless. Treating converter output as production-ready is a common and costly mistake.
Why does my ported EA behave differently on MT5 than MT4?
The most common causes are the netting-vs-hedging default difference, unreleased indicator handles causing degraded behavior over time, missing ArraySetAsSeries() calls causing indexing errors, or confusion between OrdersTotal() and PositionsTotal() semantics.
Is MQL5 actually faster than MQL4?
Yes — MQL5 can execute operations up to 20x faster than equivalent MQL4 code, partly due to its more optimized architecture and the indicator handle caching system.
How should I test a ported EA before going live?
Run the MQL5 version against the same historical data as the original MQL4 backtest and compare at the trade level, not just aggregate statistics — individual trade divergence can hide behind similar-looking overall performance numbers.
Should I port to MT5 if my prop firm still uses MT4?
Confirm your target firm's platform support before investing porting effort — not every prop firm supports both platforms, and migration effort should follow the platform your actual target account requires.
Why does my ported indicator show correct values on recent bars but wrong values further back, or vice versa?
This is the signature of an indexing-direction mismatch: MQL5 indicator buffers default to oldest-first ordering, the opposite of MQL4's as-series default. Without an explicit ArraySetAsSeries() call after binding each buffer, the indicator compiles and runs but reads calculations against the wrong bar — it will not throw an error, only produce values that are subtly and consistently offset from what the same logic produced in MQL4.
How do I correctly track a position that filled in multiple partial deals?
Use the position's POSITION_IDENTIFIER together with HistorySelectByPosition() to pull every order and deal sharing that ID, rather than tracking by ticket number. A single order can produce multiple deals at different fill prices during a partial fill, and MQL4-style ticket-based tracking has no direct way to represent that MQL5 structure correctly.
Does a partially-filled position matter for prop firm audit trails?
Yes. If an EA's internal trade log misrepresents a partial fill because it was ported with MQL4's ticket-per-order assumptions, that log will not match the platform's actual deal history — exactly the kind of discrepancy a prop firm's payout review process is designed to catch, even though the underlying strategy did nothing wrong. Reconciling against position ID and deal history, not tickets, avoids creating this gap in the first place.
Need your MT4 EA ported to MT5 correctly, with trade-level validation before it goes live? Book a free 30-minute consultation to discuss your migration.
External Resources
About the Author
Viprasol Tech Team
Custom Software Development Specialists
The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 1000+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement.
Ready to Automate Your Trading?
Get a custom Expert Advisor built by professionals with verified MyFXBook results.
Free consultation • No commitment • Response within 24 hours
Need a custom EA or trading bot built?
We specialise in MT4/MT5 Expert Advisor development — prop-firm compliant, forward-tested before live, MyFXBook verifiable. 5.0★ Upwork, 100% Job Success, 1000+ projects shipped.