How to code an EA
16 minute read
In this article, we’ll introduce how to program an EA (Expert Advisor, i.e. trading robot) for MetaTrader 4 or 5 (MT4 or MT5) from a business-logic and requirements-management perspective. This is not another ‘what are variables and arrays’ tutorial.
There are plenty of MQL4 and MQL5 programming tutorials out there, teaching you how to hack up specific types of EAs. Those tutorials are usually great for learning the MQL syntax, covering topics like: ‘what is a variable’ or ‘how to use OrderSend
’.
They may also give you examples of how to script a particular task, like: ‘how to create a trailing stop’.
However, after some practice hacking up EA scripts, you may soon find yourself drowning in collections of disparate EA code files, unable to figure out how to extend one or create one yourself.
If you want to take your thinking to the next level – from a scripter to a developer or even a solution engineer – then it’ll pay off to approach coding with a few higher goals in mind:
- Code in a way that mirrors high-level business logic – the closer your code is to the natural language of high-level requirements, the more efficient and accountable your code is.
- Code as if you’re developing a library of reusable assets, not just a one-off script – so that you don’t reinvent the wheel, and your productivity can scale.
These points are especially important if you’re going to provide EA development services. There are at least two ways to think about what you provide as a developer:
- You provide your dedicated time and labor to research and use MQL to translate natural-language requirements into code (selling your time); and
- You provide your knowledge and assets that have been built through your expertise and experience over a long time (selling your code or assets)*.
* Note: In this conversation, we’ll leave out the situation where you’re employed and all your intellectual property automatically belongs to the employer during your time with them, but they also have responsibilities for your welfare and superannuation – this is not a common setup in this field. Most likely, you’re paid for a single gig.
If you think of yourself as selling your time, or usually deal with customers who request you to code everything from scratch, then you’re incentivized to work by the hour and script the bare minimum requirements – to reinvent the wheel. And as part and parcel, this scripting mentality incentivizes you to make code as direct and simple for them as possible, placing low-level calls like iCustom
and OrderSend
inline, in the same place as business-logic.
Whereas, if you work with clients who require reliable automation and understand the value of assets and not reinventing the wheel, then you can use all the assets you’ve built up over time to engineer a robust, battle-tested and reliable EA. Your client will benefit from knowing their automation needs are backed by a reliable engine with an ever-improving infrastructure.
The rest of the development world outside this niche has known the value of a reliable engine since the beginning. For example, using libraries of code for operations in security and math is faster, more reliable and recommended over coding from scratch, because details have been deeply thought out and ‘silly mistakes’ have been ironed out. It sometimes takes a lot of coincidences over history for code to become robust, and it’s not always easy to explain all the reasons why certain patches were made to a beginner looking for the most understandable version of code.
Nevertheless, regardless of your goals in learning how to code an EA – to be able to code a quick hack for a one-time purpose; or to build assets that become better and better with every EA made – you’ll learn better how to code EAs in general if you see a well-organized, high-level program structure. This is a foundation that shapes the way you design all EAs. Once you approach programming an EA in a top-down way, engineering it from a requirements or business-logic level, then filling in the syntax details becomes boilerplate that you can routinely find on StackExchange or the MQL language reference guide.
The trading model
A higher way to think about coding is to understand that our role as engineers is to bridge the language between high-level business logic and machine. When we write code in MQL, if we can make utilities for ourselves that closely resemble natural language, we will be more efficient.
EAs are programs that automate a trader’s procedure. A trader, if trading manually, would routinely do the following:
- Check if s/he should make an Entry. If so, Enter the market by opening a position.
- Modify ongoing positions if required (such as changing the Stop Loss).
- Check if s/he should make an Exit. If so, Exit the market by closing the position.
This is the basis for an EA’s main function OnTick
, which will almost never change:
void OnTick(void) {
CheckAndDoExits();
CheckAndDoModifies();
CheckAndDoEntries();
}
The reason why it’s listed backwards is because a trader would generally exit any old position before entering a new one.
This is as far as the OnTick
function needs to go, for now.
Checking and doing entry, modifications and exit
The next step is to write each of the business logic functions: CheckAndDoExits
, CheckAndDoModifies
and CheckAndDoEntries
.
These functions follow clear business logic inside them as well.
CheckAndDoExits
Check if conditions for exit are true. If so, execute the exit. The code looks like this:
void CheckAndDoExits(void) {
if (Conditions_Exit(true)) Exit(true);
if (Conditions_Exit(false)) Exit(false);
}
Here, a boolean (true/false) switch is used to represent Buy and Sell situations. That is, the code says:
- If Exit Conditions for Buys apply, then perform an Exit of Buys.
- If Exit Conditions for Sells apply, then perform an Exit of Sells.
Trading is unique in the way that we have the Buy and Sell scenarios, usually with symmetric but opposite conditions and procedures. For example: “Buy if MACD is above 0. Sell if MACD is below 0”. We want to code as efficiently as possible, so we’ll code once and use twice – therefore, you’ll see us use this technique often, where we’ll use a boolean switch to flip operations within a single function.
CheckAndDoModifies
This function can hold several custom functions that do unique modifications to positions that are still open.
For now, we’ll leave it empty – we won’t have any modification steps in our strategy.
void CheckAndDoModifies(void) {
// Nothing to do
}
But remember, at least we’re building out an infrastructure. By this function being empty, it tells us at first glance that this strategy does not perform any deliberate modifications on ongoing orders. This is good implicit documentation.
A common example for the future might be to insert a trailing stop:
void CheckAndDoModifies(void) {
Modify_TrailingStop();
}
In Modify_TrailingStop
, you would loop through all live tickets and check if their Stop Losses should be modified.
Another common example is to set Stop Losses at break even. This idea can coexist with a trailing stop:
void CheckAndDoModifies(void) {
Modify_TrailingStop();
Modify_BreakEven();
}
In Modify_BreakEven
, you would loop through all live tickets and check if their Stop Losses should be moved to break-even. Depending on your client’s definition, this idea can sometimes co-exist with a Trailing Stop idea. If not, you would place a logical switch within the function to decide if it should be switched off under certain circumstances.
However, for the scope of this article, we’ll leave CheckAndDoModifies
empty. The takeaway is that the EA has a clear business logic layer, where any position modification modules could be inserted in the future.
CheckAndDoEntries
Check if conditions for entry are true – if so, execute the entry. The code looks like this:
void CheckAndDoEntries(void) {
if (Conditions_Entry(true)) Entry(true);
if (Conditions_Entry(false)) Entry(false);
}
Here, a boolean (true/false) switch is used to represent Buy and Sell situations. That is, the code says:
- If Entry Conditions for a Buy apply, then perform an Entry for a Buy.
- If Entry Conditions for a Sell apply, then perform an Entry for a Sell.
So far, the code reflects how a trader does things – that’s good.
Executing Entry and Exit
Now, we’ll code the Entry
and Exit
functions.
Remember, the reason why we don’t call the MQL built-in functions OrderSend
and OrderClose
directly is because these are low-level functions; they aren’t nice enough for us to use at the strategy level, and here’s why.
Our Entry
and Exit
functions represent business logic layers. For example, Entry
will encapsulate what happens when a trader enters a position: requesting the broker to open a position, then performing any bookkeeping tasks that the trader might keep. Think in terms of how you would naturally describe what you do as a trader sitting at a desk.
Whereas, OrderSend
and OrderClose
deal with the request protocol, which is too low-levelled for this layer. Cleaning data to package inside these functions in preparation to transmit to the broker, and evaluating the request response and error codes, are low-level operations that a trader doesn’t think about in a strategy; the strategy usually just says: “enter a buy position, then do…”. Therefore, the dirty details of these request transmission protocols can be encapsulated in deeper layers of the code infrastructure, for your own sanity and scalability.
Executing Entry
When a trader executes Entry, s/he’ll open either a buy or sell order (for most strategies).
They’ll send a request to the broker to open the order. If it fails, the order won’t open. if it succeeds, an order will be opened and there’ll be a ticket number. The trader might sometimes do something else after successful entry.
Here’s what the code structure looks like, if an Entry means making one new order:
void Entry(const bool buyorsell) {
const int ticket = OpenOrder(buyorsell);
if (ticket <= -1) return;
// Do things after the Entry is successful here.
}
Most of the time, nothing needs to be done after successful entry, so the above code doesn’t change. One possible example of something done after successful entry is closing all opposite positions – for some clients, they specify to do this only once the opposite Entry is opened successfully; in which case the code will look like this:
void Entry(const bool buyorsell) {
const int ticket = OpenOrder(buyorsell);
if (ticket <= -1) return;
// Do things after the Entry is successful here.
Exit(!buyorsell);
}
Another example of something that might happen after a successful entry is maintaining a strategy-specific variable in reaction to a new entry. For example:
void Entry(const bool buyorsell) {
const int ticket = OpenOrder(buyorsell);
if (ticket <= -1) return;
// Do things after the Entry is successful here.
Exit(!buyorsell);
HiddenOrderBook.Add(ticket);
}
In the above example, it is registering the newly opened ticket in HiddenOrderBook
, which tracks hidden stop losses.
OpenOrder
OpenOrder
is yet another business logic function. We code it for ourselves so that it can be used easily: all we need to know is that by calling OpenOrder
, an order will or will not open. If successful, the ticket number will be returned greater than -1.
This is a great business logic utility, because in terms of strategy flow, that’s the essential thing that allows or blocks the flow of the strategy. So, OpenOrder
fits nicely into code that very closely resembles business logic and requirements dialogue.
Inside OpenOrder
, it calls the final MQL4 OrderSend
keyword, checks for the return code, logs errors in a nice way, and logs the details of the request attempt nicely:
int OpenOrder(const bool buyorsell) {
Print("INFO: Attempting to open an order...");
const int ticket = OrderSend(...);
const int err = GetLastError();
if (ticket <= -1) {
Print("WARNING: Couldn't open order. Error number: ", err);
}
else {
Print("INFO: Opened ticket ", ticket);
}
return ticket;
}
In reality, the function that MQL4Solutions uses in bespoke services is much more robust than this. For example, in this function it would be appropriate to code re-attempts at failed requests, do logging in more detail, clean and normalize data, do a final check of Symbol specifications before OrderSend
, and handle return errors more nicely.
Nevertheless, the takeaway is to code with business logic in mind – doing these error checks in Entry
would not be nice, because it would detract from the way that strategies are usually expressed in natural language when a trader ‘makes Entry’. That’s why it’s encapsulated deeper in the EA infrastructure, not in Entry
.
Executing Exit
When a trader executes Exit, s/he’ll usually close all buys, or close all sells. There may be variations, but we’ll leave that for an extension in the future, because the infrastructure we’ll focus on now is a basis that can be scaled.
When the trader seeks to perform an Exit, they’ll send a request to the broker to close all open buy or sell tickets.
Here’s what the code structure looks like:
void Exit(const bool buyorsell) {
const bool success = CloseOrders(buyorsell);
if (!success) return;
// Do things after the Exit is successful here.
}
Most of the time, nothing needs to be done after successful exit, so the above code doesn’t change. There are advanced cases where it’s important to know if closing all orders was successful, such as maintaining strategy-specific bookkeeping variables – if so, the place to insert that logic is available here.
CloseOrders
CloseOrders
is yet another business logic function. It’s so common in strategies to say “close buys” or “close sells”, that it makes sense to encapsulate this into a utility. The utility is made to be used as simply as a saying the strategy in natural language: “close all buys or sells, and let us know if it was successful”.
Inside CloseOrders:
bool CloseOrders(const bool buyorsell) {
Print("INFO: Attempting to close orders...");
// Collect all live open buy or sell tickets
int tickets[];
for (int i = 0; i < OrdersTotal() && !IsStopped(); ++i) {
if (!OrderSelect(i, SELECT_BY_POS)) continue;
if (OrderType() == (buyorsell ? OP_BUY : OP_SELL)) {} else continue;
ArrayAdd(tickets, OrderTicket());
}
// Close each one
bool something_not_closed = false;
for (int i = 0; i < ArraySize(tickets) && !IsStopped(); ++i) {
if (!CloseOrder(tickets[i])) something_not_closed = true;
}
if (something_not_closed) Print("WARNING: Not all tickets closed");
return (!something_not_closed);
}
The above function collects a list of live tickets, then attempts to close each one. It’s worth noting that the line
if (!OrderSelect(i, SELECT_BY_POS)) continue;
is simplified for this article. This example will collect all tickets on the account, even tickets that this EA is not supposed to be managing. In reality, MQL4Solutions Bespoke MT4 development uses another utility to collect live tickets that belong to this EA, so that this EA does not interfere with manual trading or other EAs that are running alongside this one.
The above function calls yet another business logic utility CloseOrder
. It’s also so common to close only one particular order, that it’s worth abstracting out into its own utility. Here’s how it looks in code:
bool CloseOrder(const int ticket) {
const bool success = OrderClose(...);
const int err = GetLastError();
if (!success) {
Print("WARNING: Couldn't close ticket #", ticket, ". Error number: ", err);
}
else {
Print("INFO: Closed ticket #", ticket);
}
return success;
}
In reality, the function that MQL4Solutions uses in bespoke MQL development services is much more robust than this. For example, it may be appropriate to code re-attempts in this area, do logging in more detail, and handle return errors more nicely.
As well, you should enhance the above function to check first if the requested ticket is already closed; if so, return true and don’t attempt an OrderClose
. Depending on your internal design, returning true may or may not be meaningful; in our case, it’s meaningful to return true, because we want to know on the high level if the order is closed.
The takeaway is to code with business logic in mind – doing these error checks in Exit
would not be nice, because it would detract from the way that strategies are usually expressed in natural language when a trader describes ‘making an Exit’.
Conditions of Entry and Exit
Now that we’ve set up the execution infrastructure, we’re left with the Conditions of Entry and Exit to code.
We’ll go through how to code the Conditions of Entry in this article.
Here’s the code:
bool Conditions_Entry(const bool buyorsell) {
return (
Condition_1_Entry(buyorsell) &&
Condition_2_Entry(buyorsell) &&
Condition_3_Entry(buyorsell)
);
}
The above code says to return true if all conditions in the list pass. You should encapsulate each condition, or each criterion for entry, to match the requirements or business logic. For example:
bool Condition_1_Entry(const bool buyorsell) {
// MA1 must be above MA2
}
bool Condition_2_Entry(const bool buyorsell) {
// An arrow must be shown from a custom indicator
}
bool Condition_3_Entry(const bool buyorsell) {
// It is within 30 minutes before high impact news.
}
This part of the program file is one of the parts that can vary significantly for each EA, by design. Under the safety of this well-organized area for storing entry rules, experiment as much as you like.
The overall code
The overall code structure should now look something like this:
// Infrastructure (changes sometimes): describes high-level trader behavior
void OnTick(void) {...}
void CheckAndDoExits(void) {...}
void CheckAndDoModifies(void) {...}
void CheckAndDoEntries(void) {...}
void Exit(const bool buyorsell) {...}
void Entry(const bool buyorsell) {...}
// Infrastructure (never changes, only improves): utilities
int OpenOrder(const bool buyorsell) {...}
bool CloseOrders(const bool buyorsell) {...}
bool CloseOrder(const int ticket) {...}
// Infrastructure (changes slightly): models the decision-making process
bool Conditions_Entry(const bool buyorsell) {...}
bool Conditions_Exit(const bool buyorsell) {...}
// Changes often: strategy-specific rules
bool Condition_1_Entry(const bool buyorsell) {...}
bool Condition_2_Entry(const bool buyorsell) {...}
bool Condition_3_Entry(const bool buyorsell) {...}
bool Condition_1_Exit(const bool buyorsell) {...}
bool Condition_2_Exit(const bool buyorsell) {...}
The above style of code management will allow you to see your strategy at a glance and extend it. If you need to change your entry or exit rules, then experiment within the safe Conditions area, keeping well clear of the OnTick
function and any other critical parts of the engine; and also clearing your mind.
The above fundamentals should also help you learn EA programming skills. If you find a tutorial teaching you how to code a particular type of EA, and most of the code is inside the OnTick
function or in larger functions, practice thinking about how to take those pieces and insert them into this business-logic infrastructure, by asking yourself some questions about the code you’re being shown:
- Where’s the trader making a decision to act?
- Where’s the trader acting?
Decision and action are significant parts of a strategy, so they deserve different areas of code. You’ll find that you can usually refactor their code into our article’s infrastructure, which will ultimately make the algorithm more controllable. If you can keep code organized this way, you’ll ultimately create better EAs.
Building a library
To build a library of reusable code, and not ‘reinvent the wheel’, you can use ‘include’ files to store away parts of code infrastructure.
For example, after establishing the OpenOrder
, CloseOrders
and CloseOrder
functions, their code can be pasted into separate MQH files for better organization.
Then for every new EA you work on, you don’t need to clutter the main strategy code with boilerplate code. Just include the boilerplate code using MQL’s include facility:
#include "OpenOrder.mqh"
#include "CloseOrders.mqh"
#include "CloseOrder.mqh"
Now, you’ll inevitably find ways to improve these utilities over time – whether it be to handle more cases, or fit into a more robust infrastructure. When you do make improvements, it makes sense to use the very best version of these utilities you’ve built over the years for all new EAs.
However, this of course depends on your clients and what they value, as we mentioned in the intro to this article. If you’re selling your time to clients who are seeking to obtain source code intellectual property, or helping someone to debug their own code, then you may be incentivized to ‘code from scratch’ for them, or just adjust their code until it gets unstuck, rather than build from the ground up using the most robust foundations you have.
Moreover, if your client expects you to deliver source code for a low price, then you’ll be incentivized to prioritize script that can be understood plainly and directly for the purpose asked, rather than hand over robust code that you’ve taken years to make optimal or universal.
On the other hand, if your client values your partnership and experience, is looking for reliable automation solutions and is not asking for your source code assets (for free), then you’re much more likely to build using foundations that are robust and well beyond the minimal expectations of a ‘code from scratch’ job.
This way of thinking might anger a lot of developers who believe code should always be delivered; but don’t worry about that – it’s just because they work based on that style, selling time and debugging code. By all means, choose a business model that enables you to provide the best for your type of clients: Some developers sell debugging expertise, whereas other B2B businesses provide outsourced bespoke infrastructure – for example the highest custom tier on cloud services. You can adjust as appropriate and of course you’re free to give to people what you choose to.
That’s what MQL4Solutions Bespoke MT4 development (or MT5 development) does – we’re able to use the best and most robust version of our code infrastructure for clients whenever we build an indicator or EA for them. And, we constantly optimize and improve, meaning that output efficiency and core reliability scales upwards and upwards. It means that new projects get supported by better foundations, and never regresses to the ‘coded from scratch’ quality level in core reliability.
However, it does mean that even a very basic EA we build is backed by dozens of modules containing battle-tested components that take care of all parts of the trading cycle in data-cleaned detail.
Summary
You deserve to be taught a very clear structure for coding your very first EA – an EA structure that allows you to see how algorithmic trading is done, and to be able to see how to extend it.
Learning syntax is one thing, but translating business logic is what it’s all about. One of the very first things to learn in development is to break down a program into understandable bite-sized tasks. This is learnt in school in pseudo-code even before real syntax is used – and it’s essential for you to understand what an EA algorithm looks like in the big picture.
We’ve shown in this article that an EA should mirror a trader’s process for managing entries and exits, with a very high-level and simple OnTick
function:
- Check conditions for a new Entry; if so, then make a new Entry.
- Modify ongoing live positions if conditions require it.
- Check conditions for Exit; if it applies, then Exit.
This is a very simple but powerful process structure that is repeated indefinitely as the chart progresses.
We then break down each of these steps into lower-level logics, but shield ourselves from dealing with the low-level components in the future by abstracting logic as much as possible into reusable business logic wrappers.
The key takeaway is to develop code layers that match natural language. And, to grow code in a way that does not detract from the high-level business logic view.
In the future, we might go deeper into condition checking to show how we can use great code management to create abstract utilities for describing complex entry and exit conditions, in a way that is closer to natural language, and therefore much more efficient, bug-free and accountable.
To learn how to code EAs, it’s essential for you to see a good high-level EA program structure that matches how a trader works. At MQL4Solutions we’ve developed a thousand or more EAs since 2016; not just one-offs, but Expert Advisors that have grown into their 100th version sustainably. We hope this article has given you a bit of insight into structuring your first EA in a way that will boost your EA programming potential.
From here, there’s only a few more steps to insert a simple strategy and create a fully running EA.