Easy Stock Market Trading with MetaTrader

Shashev Sergei | 18 February, 2010

Introduction

After an easy development of trading robots for MetaTrader 4, you may be unpleasantly surprised about the challenges one will face with developing bots for the stock market in Russia. QUIK is one of the most popular programs among Russian brokers. The program has a built-in programming language QPILE, which allows making simple scripts, but doesn't have enough capacity to develop full-fledged trading robots, let alone provide testing facilities. Therefore, we have to find an alternative way.


QUIK Features

To exchange information between QUIK and an external program, fixed structure text files are being used:

The scheme of interaction between the programs is the following:

  1. An external program generates a transaction with specified parameters and writes it as a new line in a .tri file. Transactions are identified by an additional integer parameter TRANS_ID that contains a unique number.
  2. QUIK system scans the .tri file with transaction parameters and transmits previously unprocessed transactions to the trading system. If a transaction description does not match the required format, it is rejected.
  3. The operations' result is recorded in the .tro file in a format suitable for reading an external program. Each line of the file contains information about the processing of individual transactions, which differ by TRANS_ID parameter.

NOTE: Before the first .tri file reading, QUIK refers to the .tro file and reads the processed orders. Orders contained in the .tro file are considered as processed, and the lines in the .tri file with the same TRANS_ID parameter are being ignored. If at every launch the external program starts renumbering orders, then before starting it you need to remove the .tro file from the working directory.

This way, if we write a line corresponding to the QUIK format in the transaction file, the trading operation will be executed. You can write an order line from any external program. The most important fields in the order line are listed in the Table 1.

Value Description
CLASSCODE Code of a class for executing a transaction, for example, EQBR. Required parameter
SECCODE Code of an instrument for executing a transaction, e.g. EESR
ACTION Transaction type that has one of the following values:
• «NEW_ORDER» - for a new order,
• «NEW_STOP_ORDER» - for a new stop-order,
• «KILL_ORDER» - to remove an order,
• «KILL_STOP_ORDER» - to remove a stop-order,
• «KILL_ALL_ORDERS» - to remove all orders from a trading system,
• «KILL_ALL_STOP_ORDERS» - to remove all stop-orders
ACCOUNT Trader's account number, required parameter
CLIENT_CODE 20-symbol composite field, may contain a client code and a text comment with the same separator as when entering orders manually.
TYPE Order type, optional parameter. Values: «L» - limited, «M» - market.
OPERATION Direction of an order, required parameter. Values: «S» - sell, «B» - buy.
QUANTITY Number of lots in an order, required parameter
PRICE Order price, per unit of an instrument, required parameter.
STOPPRICE Stop-price, per unit of an instrument. Used only when «ACTION» = «NEW_STOP_ORDER»
STOP_ORDER_KIND Stop-order type.
• «SIMPLE_STOP_ORDER» - stop-limit,
• «WITH_LINKED_LIMIT_ORDER» - with an associated order,
• etc.
EXPIRY_DATE Validity of a stop order. Possible values:
• «GTC» - until canceled.
• Date in format "yyyymmdd", where "yyyy" - year, "mm" - month, "dd" - date.
TRANS_ID Unique identification number of an order

 

For more information about order formats in QUIK program, please download the attached file. There are many solutions available for the development of the robot's "brain". We will look now into some of them.


Programs for Trading Robot Development

Imagine, that you are given a task to develop a trading robot linked to QUIK. Let's look at the most popular programs for a development of trading systems currently existing.

MetaStock

MetaStock program is the oldest program used for technical analysis, its first version appeared in 1986. Internal tools for creating trading robots are imperfect due to a weakness of the programming language. These tests may considerably differ from the actual ones due to the multiple loopholes for "peeking" into the future. In addition to that, MetaStock can be very fussy, as it won't have a stable operation on every computer. It is common for the program to crash 1-2 times a day. Another disadvantage is a high fee for the licensed program.

Omega Research

Omega Research TradeStation 2000i is a functionally complete research platform offering a wide range of possibilities. The program is based on three basic components:

The language for TradeStation development is visually similar to Visual Basic, however, its capabilities are considerably lower. Nevertheless, in Omega you can create very decent, in terms of complexity, trading robots, and test data is very realistic. Among disadvantages - the platform's cumbersomeness, complex installation, weak language functionality, and high costs involved.

Wealth Lab

Wealth-Lab Developer is a program that provides a complete environment for creating and testing trading systems for all financial markets.

The program has already installed trading systems by default. They are described in ChartScripts. ChartScript may contain rules and trading strategies (trade regulations) and instructions to display indicators and graphical objects in the diagram. The programming language is very similar to Pascal, where students get acquainted with programming.

The program's weakness is its unstable online operation, that leads to high technical risks of robot operation as well as contributes to the cost.

MetaTrader 4

We will look into MetaTrader now. Its language is the first thing that catches your eye. C-oriented MQL4 is head and shoulders above the programming languages ​​used on other platforms. The program is free of charge, and if for an individual client this may be more of a philosophical question, then for legal entities this is an important factor to consider when choosing a program. In addition to that, the program is easier to install, it weighs less and operates smoothly on any device.

There is just one disadvantage - the data itself. In MetaStock, Omega and Wealth-Lab data is exported online directly from QUIK, whereas in MetaTrader - from your own data source. The price differences between real data and MetaTrader data are insignificant, therefore, normal non-pip strategies will also apply well to MetaTrader data.

Leapfrog situations may occur during holidays, when data doesn't reach MetaTrader, when trading is actually happening. To address this shortcoming, it is sufficient to choose the right data provider, which can be any DC offering CFD trading on Russian stocks.


Options for Writing Orders in the File

Field analysis for an order-line indicates that some of them will be repeated for each transaction - they will be referred to global variables. Please bear in mind, that leverage is provided only when buying, not when selling. Therefore, the maximum number of buying and selling shares will be different. The author created a robot for a brokerage company, therefore an opportunity to work with multiple client accounts is provided. The basic functions - DEAL, Stop-Limit and KillOrders, will be used the following way:

//ORDER// 
bool DEAL   (string TYPE,string ACTION,string OPERATION,string PRICE,string POSITION)
{ 
  string CLIENT_CODE;   
  int QUANTITY_BUY;   
  int QUANTITY_SELL;   
  int QUANTITY;     
   // Open file    
   HandleFileRequests=FileOpen(FileRequests,FILE_CSV|FILE_READ|FILE_WRITE,';'); 
   if(HandleFileRequests<1)   
   { Print("File to import transactions is not found, last error". GetLastError());     
     return(false);   }     
   // Write file   
   for(int i=0;i<COUNT_CLIENT;i++)   
   { CLIENT_CODE=CLIENT_CODES[i];      
     QUANTITY_BUY=CLIENT[i];      
     QUANTITY_SELL=CLIENT[i];      
     if (OPERATION=="B" && POSITION=="OPEN")         QUANTITY=QUANTITY_BUY;      
     if (OPERATION=="B" && POSITION=="CLOSE")        QUANTITY=QUANTITY_SELL;      
     if (OPERATION=="S" && POSITION=="OPEN")         QUANTITY=QUANTITY_SELL;      
     if (OPERATION=="S" && POSITION=="CLOSE")        QUANTITY=QUANTITY_BUY;      
     TRANS_ID++;           
     FileSeek(HandleFileRequests,0,SEEK_END);           
     FileWrite(HandleFileRequests, "TRANS_ID="+TRANS_ID, ACCOUNT,  "CLIENT_CODE="+CLIENT_CODE,  
           CLASSCODE, SECCODE, "TYPE="+TYPE, "ACTION="+ACTION, "OPERATION="+OPERATION,  
           "PRICE="+PRICE, "QUANTITY="+QUANTITY+";" );
   }   
   GlobalVariableSet(ID,TRANS_ID);  
   FileClose(HandleFileRequests);
}
 
// STOP-LIMIT    
bool Stop   ( string ACTION, string OPERATION, string STOPPRICE, string PRICE )
{   
   string CLIENT_CODE;   
   int QUANTITY_BUY;   
   int QUANTITY_SELL;   
   int QUANTITY;    
   // Open file    
   HandleFileRequests=FileOpen(FileRequests,FILE_CSV|FILE_READ|FILE_WRITE,';');   
   if(HandleFileRequests<1)   
   { Print("File to import transactions is not found, last error". GetLastError());      
     return(false);   }
   // Write file   
   for(int i=0;i<COUNT_CLIENT;i++)   
   {      
      CLIENT_CODE=CLIENT_CODES[i]; 
      QUANTITY_BUY=CLIENT[i]; 
      QUANTITY_SELL=CLIENT[i]; 
      if (OPERATION=="S")  QUANTITY=QUANTITY_BUY;  
      if (OPERATION=="B")  QUANTITY=QUANTITY_SELL; 
      TRANS_ID++;        
      FileSeek(HandleFileRequests,0,SEEK_END);  
      FileWrite(HandleFileRequests, "TRANS_ID="+TRANS_ID,  ACCOUNT, "CLIENT_CODE="+CLIENT_CODE, 
            CLASSCODE, SECCODE, "ACTION="+ACTION, "OPERATION="+OPERATION, "QUANTITY="+QUANTITY, 
            "PRICE="+PRICE, "STOPPRICE="+STOPPRICE,"EXPIRY_DATE=GTC;" );
   }
   GlobalVariableSet(ID,TRANS_ID);   
   FileClose(HandleFileRequests);}
    
// Remove all orders    
bool KillOrders   ( string ACTION )
{   string CLIENT_CODE;      
   // Open file   
   HandleFileRequests=FileOpen(FileRequests,FILE_CSV|FILE_READ|FILE_WRITE,';'); 
   if(HandleFileRequests<1)   
   {Print("File to import transactions is not found, last error". GetLastError());      
          return(false);   }        
 // Write file   
   for(int i=0;i<COUNT_CLIENT;i++)   
   {      
      CLIENT_CODE=CLIENT_CODES[i]; 
      TRANS_ID++;        
      FileSeek(HandleFileRequests,0,SEEK_END);        
      FileWrite(HandleFileRequests,  "TRANS_ID="+TRANS_ID, CLASSCODE, SECCODE, "ACTION="+ACTION, 
                        "CLIENT_CODE="+CLIENT_CODE+";" );
   }
   GlobalVariableSet(ID,TRANS_ID);   FileClose(HandleFileRequests);
}

TRANS_ID is marked as a separate global variable, because when the terminal is turned off, the variable must be maintained, otherwise the value of the transaction counter is reset, and new orders will not be executed, since their TRANS_ID will match the already implemented ones, stored in the file Trans.tro.

The variable FileRequests holds the file name, where line-orders are written.

The result of completing each function is the creation of lines in the trans.tri file, which are then processed by QUIK and become actual transactions.


Simple Trading Robot

After writing functions that are interacting with the transaction file, we can proceed with developing a trading robot.

As an input signal generator, for example, we use smoothed prices - exponential moving averages (EMA) with long (EMA Long) and short (EMA Short) periods. Rules for operating a trading robot are the following:

Algorithmic transfer of two-parts stop-order. First, previous orders are removed, followed by the new orders being placed. As a result, the trailing function will be the following:

// Trailing stop
bool Trailing()
  {
   string Stop_Price;
   string Price;   
   int N=OrdersTotal();
   for(int i = N - 1; i >= 0 ; i--)
      {
      if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false)        break;
      if( OrderSymbol()!=Symbol()) continue;
         if(OrderType() == OP_BUY)            
            if(TrailingStop > 0)
               if((Bid-OrderOpenPrice()) > (Point*TrailingStop))
                  if((OrderStopLoss()+TrailingStop*Point) < (Bid-Point*TrailingStop))
                     {
                     OrderModify(OrderTicket(),OrderOpenPrice(),Bid-Point*TrailingStop,
                                                       OrderTakeProfit(),0,GreenYellow);
                     // Write trailing in file
                     KillOrders("KILL_ALL_STOP_ORDERS");
                     Stop_Price=DoubleToStr(Bid-Point*TrailingStop,2);                     
                     Price=DoubleToStr(Bid-Point*TrailingStop-Bid*0.0006,2);
                     Stop("NEW_STOP_ORDER","S",Stop_Price,Price);                     
                     return(0);
                     }

            if(OrderType() == OP_SELL)                                                                       
               if(TrailingStop > 0)
                  if(OrderOpenPrice()-Ask>Point*TrailingStop)
                     if(OrderStopLoss()-TrailingStop*Point>Ask+Point*TrailingStop)
                        {
                        OrderModify(OrderTicket(),OrderOpenPrice(),Ask+Point*TrailingStop,
                                                                  OrderTakeProfit(),0,Red);
                        // Write trailing in file
                        KillOrders("KILL_ALL_STOP_ORDERS");
                        Stop_Price=DoubleToStr(Ask+Point*TrailingStop,2);                     
                        Price=DoubleToStr(Ask+Point*TrailingStop+Ask*0.0006,2);
                        Stop("NEW_STOP_ORDER","B",Stop_Price,Price);                                                  
                        return(0);              
                        }
      }
  }

Experience of Actual Operation

The resulting robot model is shown on Figure 1.


Let's analyze the advantages and disadvantages of this model:

Pros:

  1. You can implement any trading system thanks to MQL4 possibilities
  2. Operates without glitch

Cons:

  1. MetaTrader data can slightly differ from the actual data
  2. MetaTrader data may not pass due to the technical issues with servers
  3. Synchronization control is required between MetaTrader transactions and actual transactions
  4. Change of lots is required approximately once every 2 weeks, as the capital and stock prices are constantly changing

Another issue is a human factor, which implies situations when traders at least once a month are closing trades manually using MetaTrader or QUIK, which can lead to a mismatch between actual and theoretical positions. As a solution to this problem, you can use semaphores - global variables in MetaTrader, which will change their value only during the automatic operation. Their regulation would also temporary allow to prohibit robot's transactions without shutting down the terminal.

When operating with numerous accounts, additional problems can be triggered. Constantly adding new clients requires a recompilation of EAs, herein, open positions and their parameters are "forgotten". Semaphores can be of assistance in this particular issue. However, within terms of MetaTrader, the problems of adding/removing clients, changing fund volumes for each client (money input and output, changes in share prices, adding new robots) are not so easily solved.


Comprehensive Account Management Module

For multiple accounts management our model has to be rebuilt by introducing a program, that would allow us to add/remove a client account, as well as add new shares and robots quickly. In addition to that, the program should be capable of reallocating client funds between robots and shares in a short period of time. The new model would look accordingly:


The new scheme has an added database that stores information about share prices, client funds, share capital for each robot as well as other useful information. Robots in MQL4 are slightly amended - the line orders are no longer formed in MQL4, but in RoboTrader program (authoring). The robots' structure of orders to the account management module can be different, for example (for order Deal):

//ORDER
 bool DEAL(string TYPE,string ACTION,string OPERATION,string PRICE,string POSITION   )
 {     
  // Open file    
   HandleFileRequests=FileOpen(FileRequests,FILE_CSV|FILE_READ|FILE_WRITE,';');   
   if(HandleFileRequests<1)   
   { Print("File to import transactions is not found, last error". GetLastError());      
       return(false);   }     
  // Write file          
  FileSeek(HandleFileRequests,0,SEEK_END);      
  FileWrite(HandleFileRequests,"WITH STOP");      
  FileWrite(HandleFileRequests,RobotName);      
  FileWrite(HandleFileRequests,Paper);                
  FileWrite(HandleFileRequests,ACCOUNT,CLASSCODE,SECCODE,"TYPE="+TYPE,"ACTION="+ACTION,
                                                "OPERATION="+OPERATION,"PRICE="+PRICE+";");                                
  FileClose(HandleFileRequests);
  }

As you can see, the function has barely changed, apart from the client codes and lots that have disappeared, and are currently stored in the database.


Conclusion

The problem of automated trading on the stock market that we faced, is now solved. The attached robot file can be used as a template when creating trading robots for MICEX (some alterations have to be made for operating with futures for RTS futures).

Using MetaTrader enables creating trading robots of different levels of complexity for operating on the stock market. In addition to that, the actual operation experience showed that the combination of MetaTrader 4 + QUIK offers the highest stability of all.

An additional advantage of MetaTrader is its ability to work with global variables which allow you to stop or resume trading without violating any processes (file cleaning, export of transactions).