Learning how to code a fully functional MA Expert Advisor will help traders program their trading EA in this complete MQL4 Expert Advisor Example article.

The moving average cross (MACross) is frequently used as an example in the few manuals and guides that discuss the development of an expert advisor. It is the most popular indicator-based strategy available, and it is much easier to teach new coding concepts when using a trading concept that most people are already familiar with.

If traders have never programmed before, the above code may appear arcane and intimidating. One way to overcome the intimidation factor is to focus on the big picture rather than the details.

Try not to spend time figuring out how every piece of the language works and wondering what is going on behind the scenes, at the processor level, and accept that it works. Traders do not have to get bogged down in the nuances of the language and its structure. They do not have to be concerned with the nuts and bolts to comprehend and build an EA. At this point, all they need to know is how the puzzle pieces fit together and which of the essential pieces can be manipulated to develop new strategies.

This article will help traders put the puzzle together and point them in the right direction for the essential pieces. This article has numbered and labeled each section of the EA to make cross-referencing easier as it walks traders through each of the parts.

What is Expert Advisor?

Expert Advisors (experts) are terminal programs written in MetaQuotes Language 4 (MQL4) and used to automate analytical and trading processes. They enable a quick technical analysis of price data and management of trading activities based on signals received. Experts can handle the entire routine work of technical analysis and trading. An expert can perform analytical and trading operations on any symbols or periods, regardless of whether the corresponding chart is open or closed.

MQL4 Expert Advisor Example

Section1: Preprocessor Directives, External and Internal Variables


Any line that starts with / is considered free text and is ignored by the program.

This article includes comments even though the computer ignores them to help explain the meaning of the programming statements in plain English. Yes, programming languages can be challenging to grasp, and adding comments as traders write their code can help make their lives easier.

Preprocessor Directives

A pound sign (#) precedes each directive. Many advanced directives, such as #import and #include, but this article was only going to use the most basic of them all, the #property copyright preprocessor directive, which identifies the code as "ours." Aside from that, it is unimportant and does nothing fancy.

The external variables come next, and they are crucial. The external variable (which is preceded by the word extern) is significant because it displays its parameters outside of the program in the Expert Dialog box for the user to manipulate easily.

Many of the external variables in the basic EA shown above are self-explanatory. When traders get to the syntax of the OrderSend function in Section 3E, OrderPlacement, this article will talk about EAName, MagicNumber, Lotsize, TakeProfit, and Stoploss. These variables mainly refer to that function.

Moving average parameter variables (particularly MAPeriod), OppositeClose, and EnterOpenBar are the most interesting external variables in this section.

Moving Average Parameter Variables

Traders will notice that this article made all of the moving average parameters values external variables. It is not required. It could have made the most important variable, the MAPeriod, an external variable and left the rest of the parameters in their defaulted values within the indicators. Most of the parameters have been declared as external variables if it wants to optimize any of them later. For the time being, this article probably optimizes the MAPeriod, but it might be helpful to optimize some of the others in the future.

True/False Bool (Boolean) Variables: OppositeClose, and EnterOpenBar

When traders see a bool in a variable, it is a type that is used for truth values. The type bool is derived from the surname of the inventor of the logical calculus, Boole. Take a look at the bool OppositeClose.


The external bool for this variable allows traders to toggle the oppositeclose condition on and off. When oppositeclose is mentioned in the code, it will be set to true by default, indicating that it wants it to be enabled. It will be turned off if set to false. Traders can also use 0 for false and 1 for true instead of true or false.

The OppositeClose bool denotes the ability to close order on an opposite signal. What exactly does this mean? If traders currently in a long position and a short entry order is triggered, the short entry order closes out their current long before entering a short trade. The opposite signal that closes the current long is the short entry signal (and vice versa). It set oppositeclose to true by default because it wants it enabled. If traders had selected false, that is, deactivated the oppositeclose, the short entry signal would not have closed out their previous long trade, and their long trade would have remained open until it was closed by hitting the stop-loss or take-profit. In general, having the oppositeclose set to true and activated is a good idea.


The EnterOpenBar bool denotes the idea of only entering at the start of each new bar rather than interbar or close. When developing new strategies based on indicators, it is preferred to set the EnterOpenBar to true to see how the strategy backtests quickly. Backtest modes in the strategy tester are divided into three categories: everytick, control points, and open prices only. Every tick is more precise but slower than the previous ones. Open prices are less precise, but they are faster than the others. Control points fall somewhere in the middle of the two in terms of accuracy and speed. However, if EnterOpenBar is set to true, traders can safely backtest on open prices only mode, significantly increasing their backtesting speed while maintaining very similar accuracy and results to the everytick mode. Aside from increasing the backtesting speed, it is noticed that setting enteronopenbar to true improves the overall performance and reliability of the system, primarily if it is based on standard indicators. It encourages traders to experiment with setting the enteronopenbar to true and false to see how the results differ.

Finally, in this section, it declared a few internal variables (also known as Global Variables), such as
  • double ticket, number, vPoint;
Please take note of how it does not declare a specific value for each identifier. Without a declared value, each indentifier is set to 0 and will be determined later. When it has completed its determination, it returns to 0. Notice how it lists the identifiers after the double, one after the other, separated by commas until the statement ends with a semicolon. It is possible because none of them have a globally distinct value. This article could have declared these in the start () function rather than here, but having them here allows traders to reference them globally from any code function. It is beneficial and eliminates the need for unnecessary repetition.

Tip:Remember that there is a quick way to find their matching counterparts whenever traders come across specific indentifiers, and it is difficult to see what part of the code they refer to. Copy and paste the identifier (for example, ExpertName) into the find field (Cnt+F) to quickly jump down to the matching identifier located elsewhere in the code. Unless traders enjoy playing ISPY with words, they will probably find themselves doing this frequently to match up the various parts of the code.

Section 2: Initialization

This section contains the code for setting the point value relative to traders broker's currency digits (brokers use either a 4 digit or a 5 digit quoting system):
  • If (Digits==3 || Digits==5), then
vSlippage=Slippage*10; vPoint=Point*10;
Otherwise, vPoint=Point, vSlippage=Slippage.

Translation:If the traders' currency pair is quoted in digits of 3 or 5, point value equals Point*10; otherwise (such as 2 or 4), point value remains as point value without a multiple.

See also: Wide range of InstaForex technical indicators.

A valuable item is the inclusion of code to automatically detect and adjust for fractional 3 or 5 digit brokers.

Understand syntax, language, and structure.Take note of how the condition is enclosed in parenthesis () and the statements enclosed in braces. That is the typical structure of an if condition, which is followed by its statements. In this case, the if condition is if(Digits==3 || Digits==5), with the double equal sign (==) representing equals and the double vertical lines (||) representing "or." Yes, traders must be aware of how their language is translated into machine language: while it would be convenient to say "and" or "or," the program will not understand traders if they use these words. Instead, use double vertical lines (||) for "or" and double ampersands (&&) for "and."

Note:While it is simple to type the double ampersand (&&) for "and," it is difficult to type the double vertical lines (||) for "or," so a quick shortcut is to copy and paste it.

Finally, the first statement enclosed in brackets, vPoint=Point*10; vSlippage=Slippage*10; contains two statements separated by a semicolon: one defining what vPoint and the other one defining what vSlippage means. Whenever the condition was never met, the else function interlocks and points to a compound statement in brackets. vSlippage=Slippage; vPoint=Point;.

Section 3: The Function Start

It is the most essential and longest section, so it is best to divide it into separate chunks, which have lettered as 3A, 3B, and more.

This article included the following lines at the start of this start () function:
  • If (Bars100) is true, then Print(Less than 100 bars); return(0);
Translation:If the number of bars is less than 100, do not trade and print the number of bars on the screen. It is a helpful code to prevent a trade from taking place when there are not enough bars loaded onto the chart.

Understand syntax, language, and structure:Another if condition (Bars 100) is set within parenthesis after "if." If the expression that follows the if condition contains two or more compound statements, it must be set within braces, and a semicolon must separate each statement within the braces. Following the if condition, this article has two statements in this example. Print is a resident function in the first statement that requires a description enclosed in quotes and surrounded by parenthesis. When the condition is met, it will display that description on the screen. The semicolon completes that expression. In the second statement, return (0) indicates that no trades will occur if there are fewer than 100 bars.

Note:Because every left-brace must be followed by a matching right bracket or the code will fail to compile, we close the two statements with a right brace.

Section 3A: Identify Short Tags to Standard Trading Functions

This section defined several short tags that represented common trading functions and configured them to work with MagicNumbers.

Why would traders want MagicNumbers to work with their trading functions?

The MagicNumber is traders EA's fingerprint, allowing the program to tell it apart from other EAs (or trades) in the same currency and timeframe. For example, if traders only want the program to track their open buy positions for this EA and not the platform's open buy positions. As a result, whenever they refer to any of their trade information functions, they would like them to be associated with the MagicNumber.

Translation:If there are any open or pending trades with the MagicNumber, the following tag names will represent common trading functions.

Lean Syntax, Language and Structure:Traders will see some variation of the OrderSelect function in various EAs, often in the first lines of any custom function. The OrderSelect function selects an order for further processing, returning true if successful and false if unsuccessful. Because the OrderSelect() function is important.

By using the OrderSelect function to filter by trades, mode trades (which means open and pending orders), and MagicNumber, the traders want the trading functions to encapsulate to work with open and pending orders magicnumber. The third line, specifically the part that says OrderMagicNumber() == MagicNumber, represents the condition for incorporating the trading functions into the MagicNumber.

This article will also point out the significance of the first line of code in that code block.

It is known as an operator, and it is used to loop through a block of code a set number of times. The first expression, int = Counter =1, sets the Counter variable to 1. The second expression, Counter =OrdersTotal(), is a condition that, if true, will execute the code between the braces (if there were 3 open orders, it will execute the loop three-time). Counter++, the third expression, means "increase the value of Counter by one." The example, the counter is incremented by one every time the loop completes until all open orders are accounted for.

Section 3B: Indicator Calling

In this section, the article will declare four different moving averages.

Each one refers to the moving average indicator built into MT4 and has its syntax. Traders like to think of the structure in parenthesis that follows the iMA indentifier as a bus with a set number of seats. Each seat on the bus is denoted by a comma and is referred to as a parameter. There are seven parameters in the iMA indicator. Each parameter has a default value that can be changed (or personalized to keep with the bus metaphor). It is helpful to understand what each parameter does, what the default values are for each parameter, how they can be customized, and what parameters drive the bus.

The information below describes each of the Moving Average parameters:
  • Symbol-A trading symbol, such as EURUSD. The currency chart's pair is represented by the symbol ().
  • TimeFrame-The period of the chart to which the moving average is applied, which is usually set to 0, indicating the chart's symbol to which the EA is attached.
  • MAPeriod-The moving average's look-back period. It is the most crucial variable.
  • Try Forex indicators in MT4 with $1000 No Deposit Bonus now!

  • MAShift-In bars, the forward shift of the moving average line is usually set to 0.
  • MAMethod-The moving average calculation method, with simple, exponential, smoothed, or linear weighted options. The second most crucial variable.
  • MAPrice-The price array to use when computing the moving average can be close, open, high, low, or some other type of average. Typically, the default value of 0 or close to it is used.
  • Shift-The backward shift of the bar for which the calculation is to be returned. A value of 0 returns the current bar's indicator value, while 3 returns the indicator value from three bars ago. As traders will see, this is the third most crucial variable.
For the time being, traders will use the default parameter values. The most important parameter for this purpose is the MAPeriod, the moving average length, set to 2 for FastMAPeriod and 30 for SlowMAPeriod. The bus is driven by the MAPeriod, which distinguishes between fast and slow-moving averages.

MAMethod is also important, especially the Simple (Integer=0) and Exponential (Integer=1) methods. Traders have defaulted to 0 or Simple for the fast-moving average and one or Exponential for the slow-moving average. In order to trigger a buy signal, the 30-period exponential moving average must cross the 2-period simple moving average.

MAShift and MAprice are typically set to zero, and changing these values has little effect.

The final parameter, shift, has no relationship to the fourth parameter, MAShift, so do not get them mixed up. This last parameter is critical for locating the moving average in time. It is essential for distinguishing the previous bar from the current bar, which is essential for our entry and exit logic.

Remember that all of these parameters have been placed as external variables to be easily modified or optimized at a later stage.

Section 3C: Logic of Entry

The EnterOpenBar logic that was alluded to earlier will be coded up in the first part of the entry logic. Surprisingly, this is a short but critical piece of code that many expert advisors overlook.

How does the program determine the opening of a new bar? It must locate the first tick that occurs on the new bar. Thus, in the preceding code, traders check for volume and delay the trade entry until it detects the first tick of the newly discovered bar.

The first, "if (enteronopenbar)," refers to the bool variable, which was previously set to true. If true, it advances to the next conditional statement, "if (iVolume(NULL,0,0)>1)." The second is condition checks to see if the volume is one, in which case openbar becomes true (anything greater than one is false) because it has discovered the first tick of the new bar. Because checking for openbar is a simple but critical component of any new system.

Then traders get to the EA's brains, the strategy conditions for entry and exit.

The following are the buy and sell conditions that plan to code:

1st Buy Condition

If the three-period moving average crosses above the thirty-period moving average, buy at the market (also close any open sell positions).

1st Sell Condition

If the three-period moving average falls below the thirty-period moving average, sell at market (also close open buy position).

How do these two conditions get translated into MQL4 code?

There are numerous ways to code up crossover conditions, but this article will use the simplest for the sake of simplicity. Because MT4 lacks a built-in crossover function, this article will devise a two-step workaround. The buy crossover condition will be indicated by observing whether the previous bar's fast-moving average was previously below the slow moving average, and now the current bar's moving average is above the slow-moving average. As a result, it has crossed over. Perhaps traders now have a better understanding of the following code.

When declaring an if condition, traders must put the logic within braces, mainly if it contains two or more statements. It uses the > sign, which is known as an operator, to suggest that FastMACurrent must be greater than SlowMACurrent. If the previous bar's 20 period moving average was lower than the 200 periods moving average, and the current bar's 20 period moving average is now higher than the current bar's 200 periods moving average, then buy at the market. Because operators are so crucial to trading conditions.

Following the condition, two statements are enclosed in braces and separated by semicolons: the first statement, which is simple enough to understand, sets OpenBuy as true if the moving average conditions are met. The second statement is a little more complex. It sets CloseSell to true if the initial moving average condition is met, and it also sets the extern bool OppositeClose to true. Take note of how it requests its own internal if condition, in this case, the OppositeClose bool = true or false, before activating its CloseSell or CloseBuy bools. It is nice to think of these internal if-bool conditions as key and lock mechanisms, with the end-user can quickly turn on and off the mechanism (in this case, OppositeClose) from the expert properties tab.

Section 3D: Close Conditions

This section begins with the while operator. It is a simple looping method in MQL4 similar to the for loop discussed above, but it is better if the number of iterations is unknown.

The while loop looks something like this:
  • while (true) / code for a loop
Following that, it set the conditions for closing the buy and sell orders:
  • close (OP BUY); / Close Buy return; if (OType==0 && CloseBuy==true)
OType is a variable that stands for the trade information function OderType(), and each of the trade order types has an integer corresponding to it. OrderType == 0 refers to the buy position OP BUY, while OrderType = 1 refers to the sell position OP SELL.

If there is an active buy position (OType==1) and the bool CloseBuy is (==) true, then the custom close function, close (OP BUY), can be called.

This section concludes with a break operator. The operator 'break' terminates the execution of the nearest external operator of the 'while,' 'for,' or switch types. The 'break' operator executes by passing control outside of the compound operator of the type 'while,' 'for,' or switch to the nearest following operator.

Section 3E: Placement of Orders

After the previous section's break operator, this section will begin with another "while" operator. The previous section's break has transferred control or flow to this second while loop.

The program will execute its buy and sell orders in this section.

OrdersTotalMagicOpen() is a custom program that traders will look at shortly that counts the number of Open Orders that fall within the EA's magic number. They can proceed if the order total is 0 (==0). Moreover, (&&) they can proceed if the bool OpenBuy is true.

Traders are using two if-else sequenced statements back to back in this case. Remember that an else condition evaluates an alternative condition if the previous if statements are false. Traders combine "else" and "if" to create alternative conditions that will be executed if they are true. In this case, traders are saying that if StopLoss > 0, they can calculate the StopLoss value (Bid-StopLoss *vPoint). If the StopLoss is 0 instead, they do not determine the value of the StopLoss, and the alternative scenario is that the StopLoss remains 0. The logic is the same for the TakeProfit. The values determined by these if-else conditions appear in the OrderSend() function's StopLoss and TakeProfit parameters.

Next, traders want to keep refreshing rates and pushing orders through until they are filled.

They say that if the ticket is 0 (order not yet filled) and the number of attempts to fill is less than 100, they will keep refreshing rates and attempting to fill. If there are requotes in a fast-moving market and traders want to be filled regardless, this is a helpful code to include. The "while" operator used when more to loop through tickets and the number of attempts to place the order.

Section 4A: Close Function

The close code begins with the for operator and loops through the code block indefinitely to calculate the closing conditions.

The OrderSelect() function searches the pool of open orders for the matching symbol and the order type (OP BUY or OP SELL).

The final and most important condition for this block of code is to keep an eye out for the exit strategy conditions, which in the example above are the MACD exit conditions.

Once the open position symbol and order types have been identified and the MACD exit conditions, the OrderClose() function, the parameters of which are discussed in the table above, can be used.

Customizable Close Function

Working with a custom close function can be beneficial because traders can easily invoke it from within their start () function whenever they need to close an order based on any set of conditions. Their custom close function should go through the pool of open orders, identifying their order types as well as their magic numbers.

Section 4B: OrdersTotalMagicOpen Function

OrdersTotalMagicOpen is the name people gave to the order counting function (). It will return an integer value indicating how many orders are currently open on the specified chart symbol that corresponds to the magic number we passed as a function argument.

It begins by declaring the OrderCount variable, whose initial value is 0. The operator is used to loop through the code block, and the OrderSelect() function examines the pool of currently open positions, looking for ones that match the order symbol and magic number.

It can be sure that the EA placed this order if OrderMagicNumber() matches the MagicNumber. It should be noted that doubts about EA identification would arise if the user ran two EAs on the same currency symbol with the same magic number.

If OrderMagicNumber() does not match, use the!= sign, which means "not equals," and the program can continue, discarding unaffiliated trades and moving on to trades that do.

The order is then checked to see if it matches the order types OP BUY or (||) OP SELL. The constant OP BUY denotes a buy market order. Replace OP BUY, or OP SELL with the appropriate order type constant to count other types.

If the order matches both the magic number, chart symbol, and order type, the OrderCount value will increase. Return the value of OrderCount to the calling function after looping all of the orders in the order pool.