Download MetaTrader 5

Creating a trading robot for Moscow Exchange. Where to start?

28 June 2016, 08:16
MetaQuotes Software Corp.
0
10 689

Many traders on Moscow Exchange would like to automate their trading algorithms, but they do not know where to start. The MQL5 language offers a huge range of trading functions, and it additionally provides ready classes that help users to make their first steps in algo trading. In this article we will discuss native tools provided by the MQL5 trading strategy language, from which MOEX traders can benefit.

Two Types of Trade Orders on Moscow Exchange (MOEX)

  Moscow Exchange supports two types of trade orders: market and limit.
  • Market orders are immediately delivered to the Exchange and are executed at the best available price. These orders can be sent using MQL5 market orders of the ORDER_TYPE_BUY and ORDER_TYPE_SELL types.
  • Limit orders are stored on the Exchange server and can be executed as soon as a suitable opposite order appears on the Exchange. In the MQL5 language, these orders are represented by two types: ORDER_TYPE_BUY_LIMIT and ORDER_TYPE_SELL_LIMIT.

A market order can guarantee trade execution (provided there is enough liquidity), but it cannot guarantee the price. This means that the price of the executed trade may significantly differ from the current offer. A limit order can guarantee the price at which a trade will be executed, but it does not guarantee the execution of the trade. As a result, your order can fail to be executed.

All other types of orders available for brokers on Moscow Exchange are part of the software system, through which traders interact with the Exchange. In other words, all other orders are algorithmic. These orders are stored and processed outside of Moscow Exchange, and are sent to the Exchange in the form of market or limit orders as a result of internal processing.

The MetaTrader 5 platform provides the following types of trading orders, which traders can use for trading on Moscow Exchange:

ID Description Storage and execution

ORDER_TYPE_BUY

A market Buy order

The order is sent to the Exchange in the form of a market Buy order at the best selling price available

ORDER_TYPE_SELL

A market Sell order

The order is sent to the Exchange in the form of a market Sell order at the best buying price available

ORDER_TYPE_BUY_LIMIT

A pending Buy Limit order

The order is sent to the Exchange in the form of a limit Buy order. The order will be executed as soon as a Sell offer with the specified or better price appears in the market.

ORDER_TYPE_SELL_LIMIT

A pending Sell Limit order

The order is sent to the Exchange in the form of a limit Sell order. The order will be executed as soon as a Buy offer with the specified or better price appears in the market.

ORDER_TYPE_BUY_STOP

A pending Buy Stop order

The order is stored on the MetaTrader 5 server. Once the order triggers, it is sent to the Exchange:
  • for the FX and Securities Markets, it is sent as a market Buy order
  • for FORTS, the order is sent as a limit Buy order at the worst channel threshold price

ORDER_TYPE_SELL_STOP

A pending Sell Stop order

The order is stored on the MetaTrader 5 server. Once the order triggers, it is sent to the Exchange:
  • for the FX and Securities Markets, it is sent as a market Sell order
  • for FORTS, the order is sent as a limit Sell order at the worst channel threshold price

ORDER_TYPE_BUY_STOP_LIMIT

Pending BUY STOP LIMIT

The order is stored on the MetaTrader 5 server. Once the order triggers, it is sent to the Exchange in the form of a limit Buy order

ORDER_TYPE_SELL_STOP_LIMIT

Pending SELL STOP LIMIT

The order is stored on the MetaTrader 5 server. Once the order triggers, it is sent to the Exchange in the form of a limit Sell order

The MetaTrader 5 platform allows setting TakeProfit and StopLoss levels for open positions. The levels are stored on the MetaTrader 5 trade server and trigger automatically, even if the trading account is not connected:

  • The TakeProfit level sets a price to close the position in the profit direction. Once a TakeProfit triggers, a limit order at the TakeProfit price is sent to the Exchange;
  • The StopLoss level allows implementing a protective stop order if the position is making loss. Once a StopLoss triggers, a market order at the StopLoss price is sent to the Exchange when trading in the FX and Securities markets, and a limit order at the worst channel threshold price is sent when trading FORTS;

In addition, the MetaTrader 5 platform allows traders to set and modify StopLoss/TakeProfit levels for pending orders, as well as to modify trigger levels of all pending orders.

Trading Operations in MetaTrader 5

MetaTrader 5 provides basic types of trading operations, which you may want to use in your trading robot:

  • buying/selling at the current price
  • placing a pending order to buy/sell under a certain condition
  • modifying/deleting a pending order
  • closing/increasing/reducing/reversing a position

All trading operations in MQL5 are implemented using the OrderSend() function that returns control to the program as soon as a trade order is successfully sent to Moscow Exchange. The order state then changes to ORDER_STATE_PLACED, which however does not mean that your order will be successfully executed (that order state will change to ORDER_STATE_FILLED or ORDER_STATE_PARTIAL). The result of your trade order execution depends on the current market, and the order may be rejected by the Exchange (state ORDER_STATE_REJECTED) for different reasons.

An asynchronous version of the function is also available — OrderSendAsync(), which performs much faster than OrderSend(), because it does not wait for the order to be sent to the trading system of the Exchange. A response to this function is sent immediately, as soon as the MetaTrader 5 terminal sends the order outside. This means that your trade order has passed the basic verification in the terminal, and is now sent for processing to the MetaTrader 5 trade server. The time required to add your order to the queue on the Exchange, as well as when the order will be executed or rejected depend on how busy the Exchange is and how fast your Internet connection is.

The wide variety of trade operations is described in the MqlTradeRequest structure, which contains the description of a trade request. Therefore the main task here is to correctly fill in the MqlTradeRequest structure and process the request execution result.

In accordance with the rules of your trading system, you can buy or sell at the market price (BUY or SELL), or place a pending order to buy/to sell at a distance from the current price:

  • BUY STOP, SELL STOP — buying or selling once the price breaks through the specified level (worse than the current price). Orders of this type are stored on the MetaTrader 5 trade server and are sent to Moscow Exchange as soon as the order triggers. They are sent in the form of a market (for FX and Securities) or a limit (for FORTS) order.
  • BUY LIMIT, SELL LIMIT — buying or selling when the price reaches the specified level (best than the current price). Such orders are sent to Moscow Exchange immediately in the form of a limit order. On Moscow Exchange, it is possible to specify a level inside spread or on the other side of spread. This helps to limit slippage during trade execution.
  • BUY STOP LIMIT, SELL STOP LIMIT — placing BUY LIMIT or SELL LIMIT once the specified price is reached. Orders of this type are stored on the MetaTrader 5 trade server. Once the specified conditions trigger, a limit order is sent to Moscow Exchange. The opening level of such a limit order can be either higher or lower than the order trigger level.

The below figure shows the principle of using BUY STOP, SELL STOP and BUY LIMIT, SELL LIMIT, as well as it suggests how to place these order from Market Depth.

In addition, you may need to modify or even delete a pending order. This can also be done by using the OrderSend()/OrderSendAsync() functions. Managing an open position is also easy, because management is performed through trading operations.

In this article we'll show you how to program buying and selling in MQL5, and we will demonstrate how to work with a trading account and symbol properties. This can be done by using trade classes of the Standard Library.

Exchange Trading Using a Robot is Easy

The MQL5 language natively supports all trading capabilities of the MetaTrader 5 platform: it contains many trading functions for working with orders, positions and trade requests. Operations are the same no mater what securities you are trading: futures, securities, options, bonds, etc.

Using MQL5 opportunities, you can create a trade order and send it to a server using the OrderSend() or OrderSendAsync() function, you can also receive the order execution result, view trading history, check out security contract specification, handle a trade event and access other useful information.

An important note for developers of trading robots: any trading operation, including opening of a position, setting of StopLoss or TakeProfit, or closure of a position by an opposite one, always consists of a set of transactions performed on the MetaTrader 5 server and on Moscow Exchange. You can view the process by running the TradeTransactionListener.mql5 Expert Advisor on your account. The EA listens to TradeTransaction events and displays brief information about the events:

//+------------------------------------------------------------------+
//|                                     TradeTransactionListener.mq5 |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   PrintFormat("LAST PING=%.f ms",
               TerminalInfoInteger(TERMINAL_PING_LAST)/1000.);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
  {
//---
   static int counter=0;   // Counter of OnTradeTransaction() calls
   static uint lasttime=0; // Time of the last call of OnTradeTransaction()
//---
   uint time=GetTickCount();
//--- If the last transaction was performed more than 1 seconds ago,
   if(time-lasttime>1000)
     {
      counter=0; // then this is a new trade operation, an the counter can be reset
      if(IS_DEBUG_MODE)
         Print(" New trade operation");
     }
   lasttime=time;
   counter++;
   Print(counter,". ",__FUNCTION__);
//--- Result of trade request execution
   ulong            lastOrderID   =trans.order;
   ENUM_ORDER_TYPE  lastOrderType =trans.order_type;
   ENUM_ORDER_STATE lastOrderState=trans.order_state;
//--- The name of the symbol for which a transaction was performed
   string trans_symbol=trans.symbol;
//--- type of transaction
   ENUM_TRADE_TRANSACTION_TYPE  trans_type=trans.type;
   switch(trans.type)
     {
      case  TRADE_TRANSACTION_POSITION:   // Position modification
        {
         ulong pos_ID=trans.position;
         PrintFormat("MqlTradeTransaction: Position  #%d %s modified: SL=%.5f TP=%.5f",
                     pos_ID,trans_symbol,trans.price_sl,trans.price_tp);
        }
      break;
      case TRADE_TRANSACTION_REQUEST:     // Sending a trade request
         PrintFormat("MqlTradeTransaction: TRADE_TRANSACTION_REQUEST");
         break;
      case TRADE_TRANSACTION_DEAL_ADD:    // Adding a trade
        {
         ulong          lastDealID   =trans.deal;
         ENUM_DEAL_TYPE lastDealType =trans.deal_type;
         double        lastDealVolume=trans.volume;
         //--- Trade ID in an internal system - a ticket assigned by Moscow Exchange
         string Exchange_ticket="";
         if(HistoryDealSelect(lastDealID))
            Exchange_ticket=HistoryDealGetString(lastDealID,DEAL_EXTERNAL_ID);
         if(Exchange_ticket!="")
            Exchange_ticket=StringFormat("(MOEX deal=%s)",Exchange_ticket);

         PrintFormat("MqlTradeTransaction: %s deal #%d %s %s %.2f lot   %s",EnumToString(trans_type),
                     lastDealID,EnumToString(lastDealType),trans_symbol,lastDealVolume,Exchange_ticket);
        }
      break;
      case TRADE_TRANSACTION_HISTORY_ADD: // Adding an order to the history
        {
         //--- Order ID in an internal system - a ticket assigned by Moscow Exchange
         string Exchange_ticket="";
         if(lastOrderState==ORDER_STATE_FILLED)
           {
            if(HistoryOrderSelect(lastOrderID))
               Exchange_ticket=HistoryOrderGetString(lastOrderID,ORDER_EXTERNAL_ID);
            if(Exchange_ticket!="")
               Exchange_ticket=StringFormat("(MOEX ticket=%s)",Exchange_ticket);
           }
         PrintFormat("MqlTradeTransaction: %s order #%d %s %s %s   %s",EnumToString(trans_type),
                     lastOrderID,EnumToString(lastOrderType),trans_symbol,EnumToString(lastOrderState),Exchange_ticket);
        }
      break;
      default: // other transactions
        {
         //--- Order ID in an internal system - a ticket assigned by Moscow Exchange
         string Exchange_ticket="";
         if(lastOrderState==ORDER_STATE_PLACED)
           {
            if(OrderSelect(lastOrderID))
               Exchange_ticket=OrderGetString(ORDER_EXTERNAL_ID);
            if(Exchange_ticket!="")
               Exchange_ticket=StringFormat("MOEX ticket=%s",Exchange_ticket);
           }
         PrintFormat("MqlTradeTransaction: %s order #%d %s %s   %s",EnumToString(trans_type),
                     lastOrderID,EnumToString(lastOrderType),EnumToString(lastOrderState),Exchange_ticket);
        }
      break;
     }
//--- order ticket    
   ulong orderID_result=result.order;
   string retcode_result=GetRetcodeID(result.retcode);
   if(orderID_result!=0)
      PrintFormat("MqlTradeResult: order #%d retcode=%s ",orderID_result,retcode_result);
//---   
  }
//+------------------------------------------------------------------+
//| Converts numeric response codes to string mnemonics              |
//+------------------------------------------------------------------+
string GetRetcodeID(int retcode)
  {
   switch(retcode)
     {
      case 10004: return("TRADE_RETCODE_REQUOTE");             break;
      case 10006: return("TRADE_RETCODE_REJECT");              break;
      case 10007: return("TRADE_RETCODE_CANCEL");              break;
      case 10008: return("TRADE_RETCODE_PLACED");              break;
      case 10009: return("TRADE_RETCODE_DONE");                break;
      case 10010: return("TRADE_RETCODE_DONE_PARTIAL");        break;
      case 10011: return("TRADE_RETCODE_ERROR");               break;
      case 10012: return("TRADE_RETCODE_TIMEOUT");             break;
      case 10013: return("TRADE_RETCODE_INVALID");             break;
      case 10014: return("TRADE_RETCODE_INVALID_VOLUME");      break;
      case 10015: return("TRADE_RETCODE_INVALID_PRICE");       break;
      case 10016: return("TRADE_RETCODE_INVALID_STOPS");       break;
      case 10017: return("TRADE_RETCODE_TRADE_DISABLED");      break;
      case 10018: return("TRADE_RETCODE_MARKET_CLOSED");       break;
      case 10019: return("TRADE_RETCODE_NO_MONEY");            break;
      case 10020: return("TRADE_RETCODE_PRICE_CHANGED");       break;
      case 10021: return("TRADE_RETCODE_PRICE_OFF");           break;
      case 10022: return("TRADE_RETCODE_INVALID_EXPIRATION");  break;
      case 10023: return("TRADE_RETCODE_ORDER_CHANGED");       break;
      case 10024: return("TRADE_RETCODE_TOO_MANY_REQUESTS");   break;
      case 10025: return("TRADE_RETCODE_NO_CHANGES");          break;
      case 10026: return("TRADE_RETCODE_SERVER_DISABLES_AT");  break;
      case 10027: return("TRADE_RETCODE_CLIENT_DISABLES_AT");  break;
      case 10028: return("TRADE_RETCODE_LOCKED");              break;
      case 10029: return("TRADE_RETCODE_FROZEN");              break;
      case 10030: return("TRADE_RETCODE_INVALID_FILL");        break;
      case 10031: return("TRADE_RETCODE_CONNECTION");          break;
      case 10032: return("TRADE_RETCODE_ONLY_REAL");           break;
      case 10033: return("TRADE_RETCODE_LIMIT_ORDERS");        break;
      case 10034: return("TRADE_RETCODE_LIMIT_VOLUME");        break;
      case 10035: return("TRADE_RETCODE_INVALID_ORDER");       break;
      case 10036: return("TRADE_RETCODE_POSITION_CLOSED");     break;
      default:
         return("TRADE_RETCODE_UNKNOWN="+IntegerToString(retcode));
         break;
     }
//---
  }
//+------------------------------------------------------------------+


Example of the Listener operation:

2016.06.09 14:51:19.763 TradeTransactionListener (Si-6.16,M15)  LAST PING=14 ms
Buying
2016.06.09 14:51:24.856 TradeTransactionListener (Si-6.16,M15)  1. OnTradeTransaction
2016.06.09 14:51:24.856 TradeTransactionListener (Si-6.16,M15)  MqlTradeTransaction: TRADE_TRANSACTION_ORDER_ADD order #49118594 ORDER_TYPE_BUY ORDER_STATE_STARTED   
2016.06.09 14:51:24.859 TradeTransactionListener (Si-6.16,M15)  2. OnTradeTransaction
2016.06.09 14:51:24.859 TradeTransactionListener (Si-6.16,M15)  MqlTradeTransaction: TRADE_TRANSACTION_REQUEST
2016.06.09 14:51:24.859 TradeTransactionListener (Si-6.16,M15)  MqlTradeResult: order #49118594 retcode=TRADE_RETCODE_PLACED 
2016.06.09 14:51:24.859 TradeTransactionListener (Si-6.16,M15)  3. OnTradeTransaction
2016.06.09 14:51:24.859 TradeTransactionListener (Si-6.16,M15)  MqlTradeTransaction: TRADE_TRANSACTION_ORDER_UPDATE order #49118594 ORDER_TYPE_BUY ORDER_STATE_REQUEST_ADD   
2016.06.09 14:51:24.881 TradeTransactionListener (Si-6.16,M15)  4. OnTradeTransaction
2016.06.09 14:51:24.881 TradeTransactionListener (Si-6.16,M15)  MqlTradeTransaction: TRADE_TRANSACTION_ORDER_UPDATE order #49118594 ORDER_TYPE_BUY ORDER_STATE_PLACED   
2016.06.09 14:51:24.881 TradeTransactionListener (Si-6.16,M15)  5. OnTradeTransaction
2016.06.09 14:51:24.881 TradeTransactionListener (Si-6.16,M15)  MqlTradeTransaction: TRADE_TRANSACTION_ORDER_DELETE order #49118594 ORDER_TYPE_BUY ORDER_STATE_PLACED   
2016.06.09 14:51:24.884 TradeTransactionListener (Si-6.16,M15)  6. OnTradeTransaction
2016.06.09 14:51:24.884 TradeTransactionListener (Si-6.16,M15)  MqlTradeTransaction: TRADE_TRANSACTION_HISTORY_ADD order #49118594 ORDER_TYPE_BUY Si-6.16 ORDER_STATE_FILLED   (MOEX ticket=3377179723)
2016.06.09 14:51:24.884 TradeTransactionListener (Si-6.16,M15)  7. OnTradeTransaction
2016.06.09 14:51:24.885 TradeTransactionListener (Si-6.16,M15)  MqlTradeTransaction: TRADE_TRANSACTION_DEAL_ADD deal #6945344 DEAL_TYPE_BUY Si-6.16 1.00 lot   (MOEX deal=185290434)
Setting SL/TP
2016.06.09 14:51:50.872 TradeTransactionListener (Si-6.16,M15)  1. OnTradeTransaction
2016.06.09 14:51:50.872 TradeTransactionListener (Si-6.16,M15)  MqlTradeTransaction: TRADE_TRANSACTION_REQUEST
2016.06.09 14:51:50.872 TradeTransactionListener (Si-6.16,M15)  2. OnTradeTransaction
2016.06.09 14:51:50.872 TradeTransactionListener (Si-6.16,M15)  MqlTradeTransaction: Position  #0 Si-6.16 modified: SL=62000.00000 TP=67000.00000
Closing the position (selling)
2016.06.09 14:52:24.063 TradeTransactionListener (Si-6.16,M15)  1. OnTradeTransaction
2016.06.09 14:52:24.063 TradeTransactionListener (Si-6.16,M15)  MqlTradeTransaction: TRADE_TRANSACTION_ORDER_ADD order #49118750 ORDER_TYPE_SELL ORDER_STATE_STARTED   
2016.06.09 14:52:24.067 TradeTransactionListener (Si-6.16,M15)  2. OnTradeTransaction
2016.06.09 14:52:24.067 TradeTransactionListener (Si-6.16,M15)  MqlTradeTransaction: TRADE_TRANSACTION_REQUEST
2016.06.09 14:52:24.067 TradeTransactionListener (Si-6.16,M15)  MqlTradeResult: order #49118750 retcode=TRADE_RETCODE_PLACED 
2016.06.09 14:52:24.067 TradeTransactionListener (Si-6.16,M15)  3. OnTradeTransaction
2016.06.09 14:52:24.067 TradeTransactionListener (Si-6.16,M15)  MqlTradeTransaction: TRADE_TRANSACTION_ORDER_UPDATE order #49118750 ORDER_TYPE_SELL ORDER_STATE_REQUEST_ADD   
2016.06.09 14:52:24.071 TradeTransactionListener (Si-6.16,M15)  4. OnTradeTransaction
2016.06.09 14:52:24.071 TradeTransactionListener (Si-6.16,M15)  MqlTradeTransaction: TRADE_TRANSACTION_ORDER_UPDATE order #49118750 ORDER_TYPE_SELL ORDER_STATE_PLACED   
2016.06.09 14:52:24.073 TradeTransactionListener (Si-6.16,M15)  5. OnTradeTransaction
2016.06.09 14:52:24.073 TradeTransactionListener (Si-6.16,M15)  MqlTradeTransaction: TRADE_TRANSACTION_DEAL_ADD deal #6945378 DEAL_TYPE_SELL Si-6.16 1.00 lot   (MOEX deal=185290646)
2016.06.09 14:52:24.075 TradeTransactionListener (Si-6.16,M15)  6. OnTradeTransaction
2016.06.09 14:52:24.075 TradeTransactionListener (Si-6.16,M15)  MqlTradeTransaction: TRADE_TRANSACTION_ORDER_DELETE order #49118750 ORDER_TYPE_SELL ORDER_STATE_PLACED   
2016.06.09 14:52:24.077 TradeTransactionListener (Si-6.16,M15)  7. OnTradeTransaction
2016.06.09 14:52:24.077 TradeTransactionListener (Si-6.16,M15)  MqlTradeTransaction: TRADE_TRANSACTION_HISTORY_ADD order #49118750 ORDER_TYPE_SELL Si-6.16 ORDER_STATE_FILLED   (MOEX ticket=3377182821)

Now it is time to review source code examples.


Working with a Trading Account

At the very start of the trading robot, it is necessary to obtain information about the trading account, on which the robot is to trade.

Account details can be accessed using the special CAccountInfo class. Let's include the use of the AccountInfo.mqh file into our code and declare the account variable of this class:

#include <Trade\AccountInfo.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- An object for working with the account
CAccountInfo account;
//--- Get the number of the account the EA is running on
   long login=account.Login();
   Print("Login=",login);
//--- Print the account currency    
   Print("Account currency: ",account.Currency());   
//--- Print balance and current5 profit on the account
   Print("Balance=",account.Balance(),"  Profit=",account.Profit(),"   Equity=",account.Equity());
//--- Print account type    
   Print("Account type: ",account.TradeModeDescription());
//--- Find out whether trading is allowed on this account
   if(account.TradeAllowed())
      Print("Trading on the account is allowed");
   else
      Print("Trading on the account is not allowed: probably connected in with an investor password");
//--- Margin calculation mode
   Print("Margin calculation mode: ",account.MarginModeDescription());
//--- Check if trading using Expert Advisors is allowed on the account
   if(account.TradeExpert())
      Print("Automate trading on the account is allowed");
   else
      Print("Automated trading using Expert Advisors or scripts is not allowed");
//--- Is the maximum number of orders specified or not
   int orders_limit=account.LimitOrders();
   if(orders_limit!=0)Print("The maximum allowable number of actual pending orders: ",orders_limit);
//--- Print the name of the company and the name of the server
   Print(account.Company(),": server ",account.Server());
   Print(__FUNCTION__,"  completed"); //---   
  }

As can be seen from the above code, by using the account variable in the OnInit() function, you can obtain a lot of useful information. You can add this code into your Expert Advisor, and it will be much easier to parse the logs while analyzing the EA operation.

The below figure shows the result of running the script.



Obtaining Properties of a Financial Instrument

We have obtained information about the account. However, in order to be able to perform trading operations, we also need to know the properties of the financial asset we are going to trade. For this purpose, we will use another convenient class CSymbolInfo with a large number of methods. The below examples features some of them.

#include<Trade\SymbolInfo.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- An object for receiving symbol properties
CSymbolInfo symbol_info;
//--- Setting the name of the symbol, for which we want to obtain information
   symbol_info.Name(_Symbol);
//--- Receiving current quotes and printing them
   symbol_info.RefreshRates();
   Print(symbol_info.Name()," (",symbol_info.Description(),")",
         "  Bid=",symbol_info.Bid(),"   Ask=",symbol_info.Ask());
//--- Receiving the number of decimal places and the point value
   Print("Digits=",symbol_info.Digits(),
         ", Point=",DoubleToString(symbol_info.Point(),symbol_info.Digits()));
//--- Requesting order execution type, check restrictions
   Print("Restrictions on trading operations: ",EnumToString(symbol_info.TradeMode()),
         " (",symbol_info.TradeModeDescription(),")");
//--- Checking trade execution modes
   Print("Trade execution mode: ",EnumToString(symbol_info.TradeExecution()),
         " (",symbol_info.TradeExecutionDescription(),")");
//--- Finding out hoe the value of contracts is calculated
   Print("Calculating contract value: ",EnumToString(symbol_info.TradeCalcMode()),
         " (",symbol_info.TradeCalcModeDescription(),")");
//--- Contract size
   Print("Standard contract size: ",symbol_info.ContractSize());
//--- Value of initial margin per 1 contract
   Print("Initial margin for a standard contract: ",symbol_info.MarginInitial()," ",symbol_info.CurrencyBase());
//--- Minimum and maximum volume size in trading operations
   Print("Volume info: LotsMin=",symbol_info.LotsMin(),"  LotsMax=",symbol_info.LotsMax(),
         "  LotsStep=",symbol_info.LotsStep());
//--- 
   Print(__FUNCTION__,"  completed");   
  }

The below figure shows the properties of symbol Si-6.16 traded on the FORTS market of Moscow Exchange. Now you are ready to move on to trading.



Programming Trading Operations

Trading orders can be sent using two MQL5 functions: OrderSend() and OrderSendAsync(). In fact, these are two implementations of the same function. OrderSend() sends a trade order and waits for its execution result, while the asynchronous OrderSendAsync() function only sends a request and allows the program to continue running without waiting for the trade server response. Thus it is really easy to trade with MQL5, where you only need to use one function for all trading operations.

Both functions receive the MqlTradeRequest structure as the first parameter, which contains over a dozen fields. The set of required fields depends on the trading operation type, therefore not all fields need to be filled in. If a value is incorrect or a required field is missing, the request will fail to pass verification in the terminal and will not be sent to the server. In five of the fields, you will need to correctly specify values from predefined enumerations.

The trade request contains so many fields, because you need to describe a lot of properties of an order, which may change depending on the fill policy, expiry and other parameters. No need to memorize all these details, simply use the ready CTrade class. Here is how the use of the class inside a trading robot may look like:

#include<Trade\Trade.mqh>
//--- An object for performing trading operations
CTrade  trade;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Setting MagicNumber to identify EA's orders
   int MagicNumber=123456;
   trade.SetExpertMagicNumber(MagicNumber);
//--- Setting allowable slippage in points for buying/selling
   int deviation=10;
   trade.SetDeviationInPoints(deviation);
//--- Order filling mode, use the mode that is allowed by the server
   trade.SetTypeFilling(ORDER_FILLING_RETURN);
//--- Logging mode: it is advisable not to call this method as the class will set the optimal mode by itself
   trade.LogLevel(1); 
//--- the function to be used for trading: true - OrderSendAsync(), false - OrderSend()
   trade.SetAsyncMode(true);
//---
   return(0);
  }

As a rule, the ORDER_FILLING_RETURN mode is used for Exchange trading. Description from the language reference:

This mode is only used in the Market and Exchange Execution modes: for market (ORDER_TYPE_BUY and ORDER_TYPE_SELL), limit and stop-limit orders (ORDER_TYPE_BUY_LIMIT, ORDER_TYPE_SELL_LIMIT, ORDER_TYPE_BUY_STOP_LIMIT and ORDER_TYPE_SELL_STOP_LIMIT). In case of partial filling, a market or limit order with remaining volume is not canceled but processed further.

During activation of the ORDER_TYPE_BUY_STOP_LIMIT and ORDER_TYPE_SELL_STOP_LIMIT orders, an appropriate limit order ORDER_TYPE_BUY_LIMIT/ORDER_TYPE_SELL_LIMIT with the ORDER_FILLING_RETURN type is created.

Now it is time to discuss how CTrade helps in trading operations.

Buying/Selling at the Current Price

Often in trading strategies we need to buy or to sell an asset immediately at the current price. CTrade is familiar with this situation, and it only needs to know the required volume of the trading operation. All other parameters, including the Open price, symbol name, Stop Loss and Take Profit levels, as well as the comment to the order can be omitted.

//--- 1. An example of buying the current symbol
   if(!trade.Buy(1))
     {
      //--- Report the failure
      Print("The Buy() method has failed. Return code=",trade.ResultRetcode(),
            ". Code description: ",trade.ResultRetcodeDescription());
     }
   else
     {
      Print("The Buy() method has been successfully performed. Return code=",trade.ResultRetcode(),
            " (",trade.ResultRetcodeDescription(),")");
     }

By default, if the symbol name is not specified, CTrade will use the name of the symbol, on whose chart it is running. This is great for simple strategies. If an Expert Advisor trades multiple symbols at the same time, the symbol of a trade operation should be specified for each operation.

//--- 2. An example of buying the specified symbol
   if(!trade.Buy(1,"Si-6.16"))
     {
      //--- Report the failure
      Print("The Buy() method has failed. Return code=",trade.ResultRetcode(),
            ". Code description: ",trade.ResultRetcodeDescription());
     }
   else
     {
      Print("The Buy() method has been successfully performed. Return code=",trade.ResultRetcode(),
            " (",trade.ResultRetcodeDescription(),")");
     }

You can specify all the parameters of an order: Stop Loss/Take Profit, open price and comment.

//--- 3. An example of buying the specified symbol with the preset SL and TP
   double volume=1;           // Specifying the volume of the trading operation
   string symbol="Si-6.16";   // Specifying the symbol of the trading operation
   int    digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); // Number of decimal places
   double point=SymbolInfoDouble(symbol,SYMBOL_POINT);         // Point
   double bid=SymbolInfoDouble(symbol,SYMBOL_BID);             // Current price to close LONG
   double SL=bid-100*point;                                    // Unnormalized value of SL
   SL=NormalizeDouble(SL,digits);                              // Normalizing Stop Loss
   double TP=bid+100*point;                                    // Unnormalized value of TP
   TP=NormalizeDouble(TP,digits);                              // Normalizing Take Profit
//--- Getting the current open price for LONG positions
   double open_price=SymbolInfoDouble(symbol,SYMBOL_ASK);
   string comment=StringFormat("Buy %s %G lots at %s, SL=%s TP=%s",
                               symbol,volume,
                               DoubleToString(open_price,digits),
                               DoubleToString(SL,digits),
                               DoubleToString(TP,digits));
   if(!trade.Buy(volume,symbol,open_price,SL,TP,comment))
     {
      //--- Report the failure
      Print("The Buy() method has failed. Return code=",trade.ResultRetcode(),
            ". Code description: ",trade.ResultRetcodeDescription());
     }
   else
     {
      Print("The Buy() method has been successfully performed. Return code=",trade.ResultRetcode(),
            " (",trade.ResultRetcodeDescription(),")");
     }

MagicNumber and allowable slippage were specified during the initialization of the CTrade instance, so we do not need to set them now. Although they can also be set directly before each trading operation, if necessary.

Placing a Limit Order

A limit order can be sent using the appropriate method of the class: BuyLimit() or SellLimit(). A brief version, where you only specify the open price and the volume, will be enough in most situations. The open price for BuyLimit must be below the current price, and it must be above the current price for SellLimit. These orders are used to enter the market at a better price, for example, in strategies that expect price to roll back from the support level. The symbol on which the EA is running is used in this case:

//--- 1. An example of placing a pending BuyLimit order
   string symbol="Si-6.16";                                    // Specifying the symbol of the order
   int    digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); // Number of decimal places
   double point=SymbolInfoDouble(symbol,SYMBOL_POINT);         // Point
   double ask=SymbolInfoDouble(symbol,SYMBOL_ASK);             // Current buying price
   double price=ask-100*point;                                 // Unnormalized opening price
   price=NormalizeDouble(price,digits);                        // Normalizing the opening price
//--- Everything is ready, sending the pending Buy Limit order to the server
   if(!trade.BuyLimit(1,price))
     {
      //--- Report the failure
      Print("The BuyLimit() method has failed. Return code=",trade.ResultRetcode(),
            ". Code description: ",trade.ResultRetcodeDescription());
     }
   else
     {
      Print("The BuyLimit() method has been successfully performed. Return code=",trade.ResultRetcode(),
            " (",trade.ResultRetcodeDescription(),")");
     }

You can also use a more detailed version, where you need to specify all parameters: SL/TP, expiry, the name of the symbol and a comment to the order.

//--- 2. An example of placing a pending BuyLimit order with all parameters
   double volume=1;
   string symbol="Si-6.16";                                    // Specifying the symbol of the order
   int    digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); // Number of decimal places
   double point=SymbolInfoDouble(symbol,SYMBOL_POINT);         // Point
   double ask=SymbolInfoDouble(symbol,SYMBOL_ASK);             // Current buying price
   double price=ask-100*point;                                 // Unnormalized opening price
   price=NormalizeDouble(price,digits);                        // Normalizing the opening price
   int SL_pips=100;                                            // Stop Loss in points
   int TP_pips=100;                                            // Take Profit in points
   double SL=price-SL_pips*point;                              // Unnormalized value of SL
   SL=NormalizeDouble(SL,digits);                              // Normalizing Stop Loss
   double TP=price+TP_pips*point;                              // Unnormalized value of TP
   TP=NormalizeDouble(TP,digits);                              // Normalizing Take Profit
   datetime expiration=TimeTradeServer()+PeriodSeconds(PERIOD_D1);
   string comment=StringFormat("Buy Limit %s %G lots at %s, SL=%s TP=%s",
                               symbol,volume,
                               DoubleToString(price,digits),
                               DoubleToString(SL,digits),
                               DoubleToString(TP,digits));
//--- Everything is ready, sending the pending Buy Limit order to the server
   if(!trade.BuyLimit(volume,price,symbol,SL,TP,ORDER_TIME_DAY,expiration,comment))
     {
      //--- Report the failure
      Print("The BuyLimit() method has failed. Return code=",trade.ResultRetcode(),
            ". Code description: ",trade.ResultRetcodeDescription());
     }
   else
     {
      Print("The BuyLimit() method has been successfully performed. Return code=",trade.ResultRetcode(),
            " (",trade.ResultRetcodeDescription(),")");
     }

In the second variant it is necessary to correctly specify the SL and TP levels. Note that when you buy, Take Profit must be above the open price, and Stop Loss must be below the open price. Mirrored for SellLimit orders. If a level is set incorrectly, you will easily find it out while backtesting your robot, because the CTrade class automatically prints logs in such cases (if you do not call the LogLevel function).

Placing a Stop Order

Stop orders can be sent using BuyStop() and SellStop() methods. The open price for Buy Stop must be above the current price, and below for SellStop. Stop orders are used in strategies that enter the market once the price breaks a resistance level, as well as they can help to limit losses. Simple version:

//--- 1. An example of placing a pending Buy Stop order
   string symbol="RTS-6.16";    // Specifying the symbol of the order
   int    digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); // Number of decimal places
   double point=SymbolInfoDouble(symbol,SYMBOL_POINT);         // Point
   double ask=SymbolInfoDouble(symbol,SYMBOL_ASK);             // Current buying price
   double price=ask+100*point;                                 // Unnormalized opening price
   price=NormalizeDouble(price,digits);                        // Normalizing the opening price
//--- Everything is ready, sending the pending Buy Stop order to the server 
   if(!trade.BuyStop(1,price))
     {
      //--- Report the failure
      Print("The BuyStop() method has failed. Return code=",trade.ResultRetcode(),
            ". Code description: ",trade.ResultRetcodeDescription());
     }
   else
     {
      Print("The BuyStop() method has been successfully performed. Return code=",trade.ResultRetcode(),
            " (",trade.ResultRetcodeDescription(),")");
     }

Here is a more detailed version when you need to specify more parameters for the pending BuyStop order:

//--- 2. An example of placing a pending Buy Stop order with all parameters
   double volume=1;
   string symbol="RTS-6.16";    // Specifying the symbol of the order
   int    digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); // Number of decimal places
   double point=SymbolInfoDouble(symbol,SYMBOL_POINT);         // Point
   double ask=SymbolInfoDouble(symbol,SYMBOL_ASK);             // Current buying price
   double price=ask+100*point;                                 // Unnormalized opening price
   price=NormalizeDouble(price,digits);                        // Normalizing the opening price
   int SL_pips=100;                                            // Stop Loss in points
   int TP_pips=100;                                            // Take Profit in points
   double SL=price-SL_pips*point;                              // Unnormalized value of SL
   SL=NormalizeDouble(SL,digits);                              // Normalizing Stop Loss
   double TP=price+TP_pips*point;                              // Unnormalized value of TP
   TP=NormalizeDouble(TP,digits);                              // Normalizing Take Profit
   datetime expiration=TimeTradeServer()+PeriodSeconds(PERIOD_D1);
   string comment=StringFormat("Buy Stop %s %G lots at %s, SL=%s TP=%s",
                               symbol,volume,
                               DoubleToString(price,digits),
                               DoubleToString(SL,digits),
                               DoubleToString(TP,digits));
//--- Everything is ready, sending the pending Buy Stop order to the server 
   if(!trade.BuyStop(volume,price,symbol,SL,TP,ORDER_TIME_DAY,expiration,comment))
     {
      //--- Report the failure
      Print("The BuyStop() method has failed. Return code=",trade.ResultRetcode(),
            ". Code description: ",trade.ResultRetcodeDescription());
     }
   else
     {
      Print("The BuyStop() method has been successfully performed. Return code=",trade.ResultRetcode(),
            " (",trade.ResultRetcodeDescription(),")");
     }

A SellStop order can be sent using the appropriate method of the CTrade class. In this case it is important to correctly specify the price.

Working with a Position

Instead of using the Buy() and Sell() methods, you can use methods for opening a position. However, in this case you will need to specify more details:

//--- Number of decimal places
   int    digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
//--- Point value
   double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
//--- Getting the buying price
   double price=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
//--- Calculating and normalizing the SL and TP levels
   double SL=NormalizeDouble(price-100*point,digits);
   double TP=NormalizeDouble(price+100*point,digits);
//--- Adding a comment
   string comment="Buy "+_Symbol+" 1 at "+DoubleToString(price,digits);
//--- Everything is ready, trying to open a Buy position
   if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,1,price,SL,TP,comment))
     {
      //--- Report the failure
      Print("The PositionOpen() method has failed. Return code=",trade.ResultRetcode(),
            ". Code description: ",trade.ResultRetcodeDescription());
     }
   else
     {
      Print("The PositionOpen() method has been successfully performed. Return code=",trade.ResultRetcode(),
            " (",trade.ResultRetcodeDescription(),")");
     }

In order to close a position, we only need to specify the name of the symbol, and the CTrade class will do the rest.

//--- Closing the position on the current symbol
   if(!trade.PositionClose(_Symbol))
     {
      //--- Report the failure
      Print("The PositionClose() method has failed. Return code=",trade.ResultRetcode(),
            ". Code description: ",trade.ResultRetcodeDescription());
     }
   else
     {
      Print("The PositionClose() method has been successfully performed. Return code=",trade.ResultRetcode(),
            " (",trade.ResultRetcodeDescription(),")");
     }

It is possible to change the StopLoss and TakeProfit levels of an open position. This can be done using the ModifyPosition() method.

//--- Number of decimal places
   int    digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
//--- Point value
   double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
//--- Getting the current Bid price
   double price=SymbolInfoDouble(_Symbol,SYMBOL_BID);
//--- Calculating and normalizing the SL and TP levels
   double SL=NormalizeDouble(price-100*point,digits);
   double TP=NormalizeDouble(price+100*point,digits);
//--- Everything is ready, trying to modify a Buy position
   if(!trade.PositionModify(_Symbol,SL,TP))
     {
      //--- Report the failure
      Print("The PositionModify() method has failed. Return code=",trade.ResultRetcode(),
            ". Code description: ",trade.ResultRetcodeDescription());
     }
   else
     {
      Print("The PositionModify() method has been successfully performed. Return code=",trade.ResultRetcode(),
            " (",trade.ResultRetcodeDescription(),")");
     }

Modifying and Deleting an Order

For modifying parameters of a pending order, the CTrade class provides the OrderModify() method, to which the required parameters should be passed.

//--- Checking if the order exists  
   if(!OrderSelect(ticket))
     {
      Print("Order #",ticket," not found");
      return;

     }
//--- Symbol 
   string symbol=OrderGetString(ORDER_SYMBOL);
//--- Number of decimal places
   int    digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
//--- Point value
   double point=SymbolInfoDouble(symbol,SYMBOL_POINT);
//--- Getting the opening price
   double price=OrderGetDouble(ORDER_PRICE_OPEN);
//--- Calculating and normalizing the SL and TP levels
   double SL=NormalizeDouble(price-200*point,digits);
   double TP=NormalizeDouble(price+200*point,digits);
//--- Everything is ready, trying to modify the order 
   if(!trade.OrderModify(ticket,price,SL,TP,ORDER_TIME_DAY,0))
     {
      //--- Report the failure
      Print("The OrderModify() method has failed. Return code=",trade.ResultRetcode(),
            ". Code description: ",trade.ResultRetcodeDescription());
     }
   else
     {
      Print("The OrderModify() method has been successfully performed. Return code=",trade.ResultRetcode(),
            " (",trade.ResultRetcodeDescription(),")");
     }

You need to obtain the ticket of the order that you want to modify, and to specify correct StopLoss and TakeProfit levels depending on the order type. In addition, the new opening price must also be correct in relation to the current price.

If you want to delete an order, you only need to know its ticket:

//--- Checking if the order exists  
   if(!OrderSelect(ticket))
     {
      Print("Order #",ticket," not found");
      return;
     }
//--- Everything is ready, trying to delete the order
   if(!trade.OrderDelete(ticket))
     {
      //--- Report the failure
      Print("The OrderDelete() method has failed. Return code=",trade.ResultRetcode(),
            ". Code description: ",trade.ResultRetcodeDescription());
     }
   else
     {
      Print("The OrderDelete() method has been successfully performed. Return code=",trade.ResultRetcode(),
            " (",trade.ResultRetcodeDescription(),")");
     }

The class also contains the universal OrderOpen() method, which can place pending orders of any type. Unlike the specialized BuyLimit, BuyStop, SellLimit and SellStop methods, OrderOpen() contains more required parameters. So you may choose what method to use.


What's More in Trade Classes

In this article we have analyzed simple techniques for programming trading Buy and Sell operations, as well as for programming operations with pending orders. However, the Trade Classes section contains some more convenient assistants for MQL5 robot developers:

Use of these classes will help you focus on the trading side of your strategy while maximally solving technical issues. In addition, the CTrade class can be used to study trade requests, for example using the debugger. Then after some time you will be able to create your own classes based on CTrade, and implement the required logic of processing trade request execution results in this class.

Start your journey to algorithmic trading with simple scripts

The methods of MQL5 robot development described in this article are mainly intended for beginners, although experienced developers may also find something new and useful here. Try to execute the simple scripts described in this article, and you will understand that creating a trading robot is easier than you might have thought.

If you want to study the topic further, here are two more related articles:

  1. Principles of Exchange Pricing through the Example of Moscow Exchange's Derivatives Market
  2. How to Secure Yourself and Your Expert Advisor While Trading on the Moscow Exchange

Translated from Russian by MetaQuotes Software Corp.
Original article: https://www.mql5.com/ru/articles/2513

Attached files |
accountinfo.mq5 (4.6 KB)
symbolinfo.mq5 (4.91 KB)
expert_sample.mq5 (37.09 KB)
limit_sample.mq5 (12.86 KB)
Step on New Rails: Custom Indicators in MQL5 Step on New Rails: Custom Indicators in MQL5

I will not list all of the new possibilities and features of the new terminal and language. They are numerous, and some novelties are worth the discussion in a separate article. Also there is no code here, written with object-oriented programming, it is a too serous topic to be simply mentioned in a context as additional advantages for developers. In this article we will consider the indicators, their structure, drawing, types and their programming details, as compared to MQL4. I hope that this article will be useful both for beginners and experienced developers, maybe some of them will find something new.

Here Comes the New MetaTrader 5 and MQL5 Here Comes the New MetaTrader 5 and MQL5

This is just a brief review of MetaTrader 5. I can't describe all the system's new features for such a short time period - the testing started on 2009.09.09. This is a symbolical date, and I am sure it will be a lucky number. A few days have passed since I got the beta version of the MetaTrader 5 terminal and MQL5. I haven't managed to try all its features, but I am already impressed.

False trigger protection for Trading Robot False trigger protection for Trading Robot

Profitability of trading systems is defined not only by logic and precision of analyzing the financial instrument dynamics, but also by the quality of the performance algorithm of this logic. False trigger is typical for low quality performance of the main logic of a trading robot. Ways of solving the specified problem are considered in this article.

Using text files for storing input parameters of Expert Advisors, indicators and scripts Using text files for storing input parameters of Expert Advisors, indicators and scripts

The article describes the application of text files for storing dynamic objects, arrays and other variables used as properties of Expert Advisors, indicators and scripts. The files serve as a convenient addition to the functionality of standard tools offered by MQL languages.