【Copy and Paste Friendly】How to Create a Forex Automated Trading Tool (EA) Using MQL4

MT4

The basic outline for designing a simple Cost Averaging Martingale EA is quite straightforward, as follows:

  • Setting up the input
  • Loop processing
    • Checking positions
    • Placing new entry orders
    • Placing additional entry (cost averaging) orders
    • Placing position close orders

By copying and pasting the content of this article, you can create the simplest Cost Averaging Martingale EA.

We’ve tried to explain the meaning of the code as thoroughly as possible for beginners, so even if you’re new to this, feel free to take on the challenge of creating your own EA.

At the end of this article, we have made an .mq4 file available for download that compiles all the code introduced here. If you have difficulty compiling the code by copying and pasting in order, or if you find it cumbersome to copy and paste one by one, please make use of the file.

    EA Logic Overview

    The logic of the EA we’re creating here is as simplified as possible, with complex conditions kept to a minimum.

    Initial lot 0.01 lot
    Entry Takes a new position immediately if within the active hours
    Cost averaging width Price width of 2.00 for a minimum price change of 0.01
    Martingale multiplier Martingale method (1.5 times multiplier)
    Take-profit target 1.00USD per position
    Operating hours Full operation during trading hours
    Forced close None (positions carried over to the next day)

    Setting up the Input

    This section mainly determines the logic of the EA. It is also the part that can be configured externally when running the EA in MT4.

    input double first_lot = 0.01; // Initial lot size
    input double nanpin_range = 200; // Cost averaging width
    input double profit_target = 1.00; // Profit target
    input int magic_number = 10001; // Magic number
    static int ticket_number; // Ticket number
    double slippage = 10; // Slippage

    Initial Lot (first_lot)

    This setting is for the lot size when taking the first position. The minimum is 0.01, so we’ll set it to 0.01 for now.

    Cost Averaging Width (nanpin_range)

    This setting determines the price range after the first position, at which the next position will be taken. A setting of 200, in the case of GOLD, means a price range of 2.00. (Strictly speaking, the unit will be determined later with *Point.)

    Profit Target (profit_target)

    Set the profit target in monetary terms. Here, the unit is in yen, and the setting is 1.00USD. When an unrealized profit of 1.00USD per position occurs, a close order will be executed based on this logic.

    Magic Number (magic_number)

    This is like an identification number that exists in every EA. It doesn’t matter what number you use, as long as it doesn’t conflict with another EA running simultaneously. It has no bearing on the logic. Here, we have arbitrarily set it to 10001.

    Ticket Number (ticket_number)

    In the case of MT4, each order has a ticket number (similar to an order number). Just think of this as a placeholder for the number. It has no bearing on the logic.

    Slippage (slippage)

    In MT4, you can set an acceptable range for slippage when placing orders. However, some brokers may ignore this setting. XMTrading is one of them; even if you set an acceptable slippage range when placing an order, it will be ignored. However, as the official explanation below states, slippage rarely occurs.

    Does slippage occur?
    With our company, slippage rarely occurs. However, especially during important economic news announcements, rapid market price movements may cause your order to be executed at a rate different from the one requested.

    www.xmtrading.com

    We will set it to 10 for now.

    Loop Processing

    Now that the initial settings are done, we’ll move on to the loop processing.

    void OnTick()

    void OnTick()
     {

    This is one of the event functions called when the EA receives a new tick.
    To put it more simply, it is a process that is repeated each time the price changes.

    Therefore, most of the main logic will be written inside this function.

    From here on, we’ll be discussing content to be written within void OnTick().

    Variable Definition

    First, let’s define the variables that will be used within the loop processing.

    int cnt;
    int current_buy_position;//most recent buy position
    int current_sell_position;//most recent sell position
    int buy_position;//number of buy positions
    int sell_position;//number of sell positions
    double buy_profit;//unrealized profit and loss for buy positions
    double sell_profit;//unrealized profit and loss for sell positions

    current_buy_position, current_sell_position

    current_buy_position is a variable to record the most recent buy position. For example, if you have three buy positions, it would represent the third buy position. current_sell_position works similarly.

    buy_position, sell_position

    buy_position represents the number of buy positions. It is used to determine how many buy positions are held. sell_position works similarly.

    buy_profit, sell_profit

    buy_profit represents the total unrealized profit and loss for buy positions. If multiple buy positions are held, this value represents the cumulative unrealized profit and loss across all positions. It is used to determine whether to execute a close order. sell_profit works similarly.

    Checking Positions

    Here, we perform the process of checking positions.
    Specifically, we obtain the number of buy positions and sell positions, as well as the latest position number (cnt), which will be used for the next order.

    buy_position=0; // Initialize the number of buy positions
    sell_position=0; // Initialize the number of sell positions
    current_buy_position=-1; // Initialize the latest buy position
    current_sell_position=-1; // Initialize the latest sell position
    for(cnt=0;cnt<OrdersTotal();cnt++) // Checking positions
    {
      if(OrderSelect(cnt,SELECT_BY_POS)==false)continue;
      if(OrderMagicNumber()!=magic_number)continue;
      if(OrderType()==OP_BUY)
      {
        current_buy_position=cnt;
        buy_position+=1;
        buy_profit=buy_profit+OrderProfit();
      };
    
      if(OrderType()==OP_SELL)
      {
        current_sell_position=cnt;
        sell_position+=1;
        sell_profit=sell_profit+OrderProfit();
      };
    
    }
    

    To briefly explain the content, we check all positions in order and,

    if it’s a buy position,

    • Record the position number (cnt) in current_buy_position.
    • Increase buy_position by 1.
    • Add the profit of that position to buy_profit.

    if it’s a sell position,

    • Record the position number (cnt) in current_sell_position.
    • Increase sell_position by 1.
    • Add the profit of that position to sell_profit.

        This is the process being performed.

        New Entry Orders

        Next, we have new entry orders. The entry conditions are simply “when no position is held.”

        if(buy_position==0) // If no buy positions are held
             {
              ticket_number=OrderSend(
                               Symbol(), // Currency pair
                               OP_BUY,  // buy:OP_BUY, sell:OP_SELL
                               first_lot, // Lot size
                               Ask, // Order price
                               slippage, // Slippage
                               0,  // Stop loss
                               0,  // Take profit
                               "first_buy",  // Order comment
                               magic_number,  // Magic number
                               0,  // Order expiration
                               Blue  // Arrow color
                            );
             }
        if(sell_position==0) // If no sell positions are held
             {
              ticket_number=OrderSend(
                               Symbol(), // Currency pair
                               OP_SELL,  // buy:OP_BUY, sell:OP_SELL
                               first_lot, // Lot size
                               Bid, // Order price
                               slippage, // Slippage
                               0,  // Stop loss
                               0,  // Take profit
                               "first_sell",  // Order comment
                               magic_number,  // Magic number
                               0,  // Order expiration
                               Red  // Arrow color
                            );
             }

        Here, we are simply using the OrderSend function.

        This function is used to send new entry orders. If there are no buy positions, a new buy order is sent, and if there are no sell positions, a new sell order is sent. The parameters used in the function include the currency pair, order type (buy or sell), lot size, order price, slippage, stop loss, take profit, order comment, magic number, order expiration, and arrow color.

        OP_BUY and OP_SELL represent market orders in trading. The order prices, Ask and Bid, indicate the current prices at the time of the order.

        In other words, if you don’t have an existing buy or sell position, you would immediately place a market order at the current price (even though it’s a market order, you still specify the price). The lot size for this order is set by the initial input, called “first_lot” (initial lot).

        Order comments are displayed when you confirm an order in MT4. You don’t have to set them, but for this example, we’ll use “first_buy” and “first_sell” as the comments.

        Arrows refer to the color of the trade history displayed on MT4 charts. In this example, we will set buy orders to blue (Blue) and sell orders to red (Red). There’s no problem if you don’t specify these colors.

        Additional Entry (Martingale) Orders

        Now let’s move on to the crucial part, setting up Martingale orders.
        That being said, there’s nothing particularly difficult about it. The only differences from a regular entry order are:

        • Orders are only placed when the conditions for the Martingale range are met
        • Lot size is increased by 1.5 times the previous lot size

          That’s about it.

          if(buy_position>0) //If holding one or more buy positions
               {
                OrderSelect(current_buy_position,SELECT_BY_POS); //Select the latest buy position
                if(Ask<(OrderOpenPrice()-nanpin_range*Point)) //Check if the current price has reached the Martingale range   
                  {
                   ticket_number=OrderSend(
                                    Symbol(), //Currency pair
                                    OP_BUY,  //Buy:OP_BUY, Sell:OP_SELL
                                    round(OrderLots()*1.5*100)/100, //Lot size
                                    Ask, //Order price
                                    slippage, //Slippage
                                    0,  //Stop-loss
                                    0,  //Take-profit
                                    "nanpin_buy",  //Order comment
                                    magic_number,  //Magic number
                                    0,  //Order expiration time
                                    Blue  //Arrow color
                                 );
                  }
               }
          if(sell_position>0) //If holding one or more sell positions
               {
                OrderSelect(current_sell_position,SELECT_BY_POS); //Select the latest sell position
                if(Bid>(OrderOpenPrice()+nanpin_range*Point)) //Check if the current price has reached the Martingale range
                  {
                   ticket_number=OrderSend(
                                    Symbol(), //Currency pair
                                    OP_SELL, //Buy:OP_BUY, Sell:OP_SELL
                                    round(OrderLots()*1.5*100)/100, //Lot size
                                    Bid, //Order price
                                    slippage, //Slippage
                                    0, //Stop-loss
                                    0, //Take-profit
                                    "nanpin_sell", //Order comment
                                    magic_number, //Magic number
                                    0, //Order expiration time
                                    Red //Arrow color
                                 );
                  }
          
               }

          Selecting the Latest Buy Position

          OrderSelect(current_buy_position,SELECT_BY_POS); //Select the latest buy position

          We will use a function called OrderSelect to obtain information about the latest position.
          For example, suppose you have two buy positions as follows:

          • Lot size: 0.01, Price: 1742.21 (buy position)
          • Lot size: 0.02, Price: 1740.21 (buy position)

            In this case, the latest position refers to the second position obtained later. Here, we use the number (cnt) obtained during the position confirmation with current_buy_position.

            Has the Current Price Reached the Averaging Down Range?

            if(Ask<(OrderOpenPrice()-nanpin_range*Point))  //Check if the current price has reached the averaging down range

            We use the OrderOpenPrice function to obtain the price of the selected latest position. In the previous example, this would mean obtaining the price of 1740.21.
            nanpin_range is the averaging down range of 200 set by Input. Point is the minimum unit determined for each currency pair (trading target). In the case of XMTrading’s GOLD, it means 0.01.

            In other words, this if statement represents the condition:

            “If the current price (Ask) falls below 1740.21 – 200 × 0.01 = 1738.21”

            Position Close Order

            Finally, we have the position close order.

            The code snippet below is quite simple, but it utilizes two functions called buyClose and sellClose, which will be defined separately later:

            if(buy_position>0&&buy_profit>profit_target*buy_position)
            {
              buyClose(Green);//Close all buy positions
            }
            if(sell_position>0&&sell_profit>profit_target*sell_position)
            {
            sellClose(Yellow);//Close all sell positions
            }

            Position Close Conditions

            The conditions for placing a close order for buy positions are met when the following two conditions are satisfied simultaneously:

            • Having one or more buy positions
            • The total unrealized profit of buy positions exceeds the profit target multiplied by the number of buy positions

              This is represented by the formula above. Specifically, for example, if the number of buy positions is 3, and the profit target (profit_target) is set at 143 JPY, when the total unrealized profit of buy positions exceeds 429 JPY, which is three times the profit target, a close order for buy positions will be placed.

              Functions to Close Positions

              Define the following separately outside of “void OnTick()”. Use these custom functions to call the position close orders mentioned earlier.

              buyClose Function

              This function closes all buy positions held.
              It closes all buy positions with the same magic number.

              void buyClose(color clr)
                {
                 int i;
                 for(i=OrdersTotal()-1; i>=0; i--)
                   {
                    if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false)
                       continue;
                    if(OrderSymbol()!=Symbol()||OrderMagicNumber()!=magic_number)
                       continue;
                    if(OrderType()==OP_BUY)
                       OrderClose(OrderTicket(),OrderLots(),Bid,NormalizeDouble(slippage,0),clr);
                   }
                }

              sellClose Function

              This is a function to close all sell positions you currently hold.
              It closes all sell positions with the same magic number.

              void sellClose(color clr)
                {
                 int i;
                 for(i=OrdersTotal()-1; i>=0; i--)
                   {
                    if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false)
                       continue;
                    if(OrderSymbol()!=Symbol()||OrderMagicNumber()!=magic_number)
                       continue;
                    if(OrderType()==OP_SELL)
                       OrderClose(OrderTicket(),OrderLots(),Ask,NormalizeDouble(slippage,0),clr);
                   }
                }

              Conclusion

              By combining what we have seen so far, a basic averaging down Martingale EA is complete.
              How did you find it? You may have realized that its structure is simpler than you thought.

              Download the .mq4 file

              You can download the code that I’ve introduced in this post from the link below:

              In general, I believe it should be usable as is, simply by compiling it. However, please note that I cannot guarantee its functionality or profitability. Use it at your own discretion and judgement.

              Comments

              Copied title and URL