Hướng dẫn điều khiển mt5: backtest cùng lúc nhiều chế độ

Trong phần mềm mt5 hiện tại khi test có 5 model: Every tick , Every tick based on real ticks ,1 minute OHLC ,Open prices only ,Math calculations . Trong đó model Math caculations không dùng để test chiến lược mà dùng để tính toán các vấn đề xung quanh. Do đó chỉ quan tâm đến 4 model đầu. Câu hỏi đặt ra là khi kiểm tra model nào là tốt nhất trong 4 lựa chọn trên. Trong bài viết này tôi sẽ hướng dẫn chạy cả 4 model cùng một lúc trên 4 máy chủ mt5 khác nhau để dễ dàng so sánh kết quả.

Bài viết hướng dẫn chạy cùng một ea được test trên terminal chính (Master) cùng lúc với 4 terminal phụ (Slave terminal) đánh số từ #1 đến #4 lần lượt với các model sau:

  • terminal #1 — “Every tick based on real ticks”;
  • terminal #2 —  “Every tick”;
  • terminal #3 — “1 Minute OHLC”;
  • terminal #4 — “Opening prices only”

1 Cơ bản

Giới hạn khi sử dụng:

  • Các terminal không được khởi động bằng cách dùng /portable key
  • Ít nhất năm Terminal MetaTrader 5 phải được cài đặt.
  • Phải đăng nhập cùng một tài khoản trên Master Terminal và 4 Slave Terminal.
  • Tắt các chương trình khác khi chạy như game online , media player ….

Các hàm sử dụng:

  • CopyFileW – sao chép tệp vào “sandbox” từ “sandbox” của MQL5.
  • FindClose – đóng các điều khiển tìm kiếm.
  • FindFirstFileW – tìm kiếm thư mục tệp hoặc thư mục con có tên khớp với tên tệp đã chỉ định.
  • FindNextFileW – tiếp tục tìm kiếm tệp từ lần gọi trước tới hàm FindFirstFile.
  • GetOpenFileNameW – gọi hộp thoại hệ thống để mở tệp:
  • ShellExecuteW — Dùng để khởi chạy Slave Terminal

2 Input

Các đường dẫn “thư mục cài đặt MetaTrader # Х” là các đường dẫn đến các thư mục cài đặt Slave terminal. Khi chỉ định đường dẫn trong mã mq5, cần phải viết dấu gạch chéo kép  như đoạn dưới :

//--- input parameters                                 
input string   ExtInstallationPathTerminal_1="C:\\Program Files\\MetaTrader 5 1\\";    // folder of the MetaTrader#1 installation
input string   ExtInstallationPathTerminal_2="D:\\MetaTrader 5 2\\";                   // folder of the MetaTrader#2 installation
input string   ExtInstallationPathTerminal_3="D:\\MetaTrader 5 3\\";                   // folder of the MetaTrader#3 installation
input string   ExtInstallationPathTerminal_4="D:\\MetaTrader 5 4\\";                   // folder of the MetaTrader#4 installation
input string   ExtTerminalName="terminal64.exe"; 

Liên kết giữa thư mục cài đặt và dữ liệu :

Liên kết này thực hiện thông qua biến TERMINAL_DATA_PATH sẽ được mt5 tự tạo ra khi chạy

Nếu mt5 được chạy theo cách thông thường , nó sẽ trả về biến giá trị như sau

TERMINAL_PATH = C: \ Program Files \ MetaTrader 5 
TERMINAL_DATA_PATH = C: \ Program Files \ MetaTrader 5 
TERMINAL_COMMONDATA_PATH = C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common

Nếu mt5 chạy theo từ khóa /portable , nó sẽ trả về biến như sau:

TERMINAL_PATH = C:\Program Files\MetaTrader 5
TERMINAL_DATA_PATH = C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962
TERMINAL_COMMONDATA_PATH = C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common

Vậy là sẽ có sự khác nhau ở biến “TERMINAL_DATA_PATH” theo 2 kiểu chạy trên

3. Khớp thư mục cài đặt với thư mục chứa dữ liệu ở các Slave Terminal

Ea sẽ chạy Slave Terminal theo config file. Mỗi Slave cần 1 file config khác nhau. Các file này đều bắt đầu bằng cú pháp [Tester] …

...
[Tester]
Expert=test
...

ở dòng Expert=test ta sẽ ghi đường dẫn vào file EA vidu như “C: \ Users \ KVN \ AppData \ Roaming \ MetaQuotes \ Terminal \ 038C9E8FAFF9EA373522ECC6D5159962 \ MQL5 \ Experts”

3.1 Oginin file

Bên trong mỗi thư mục chữa dữ liệu đều có file Oginin.txt trong file này sẽ chỉ ra thư mục cài đặt terminal liên kết với thư mục dữ liệu này.

3.2  FindFirstFileW, FindNextFileW

FindFirstFileW – tìm kiếm thư mục cho tệp hoặc thư mục con có tên trùng với tên nhất định (hoặc một phần của tên, nếu các ký tự đặc biệt được sử dụng).

HANDLE  FindFirstFileW(
   string           lpFileName,         //
   WIN32_FIND_DATA  &lpFindFileData     //
   ); 

lpFileName :

 Thư mục hoặc đường dẫn và tên của tệp, có thể bao gồm các ký tự đại diện như dấu hoa thị (*) hoặc dấu hỏi (?).

lpFindFileData

[in] [out] Trỏ đến cấu trúc WIN32_FIND_DATA, cấu trúc này nhận thông tin về tệp hoặc thư mục được tìm thấy. 

Giá trị trả về

Nếu hàm thành công, giá trị trả về sẽ là bộ xử lý tìm kiếm (search handle) được sử dụng trong hàm FindNextFile  hoặc  FindClose và  tham số lpFindFileData chứa thông tin về tệp hoặc thư mục đầu tiên được tìm thấy.

Nếu hàm không thành công hoặc không thể tìm thấy tệp từ chuỗi tìm kiếm trong tham số lpFileName , nó sẽ trả về INVALID_HANDLE_VALUE và nội dung của  lpFindFileData  sẽ không được xác định. Để biết thêm thông tin về lỗi, hãy gọi  hàm GetLastError .

Nếu hàm không kích hoạt vì không tìm thấy tệp tương ứng, thì hàm GetLastError trả về ERROR_FILE_NOT_FOUND .

FindNextFileW – tiếp tục tìm kiếm tệp từ lần gọi trước và truyền nó tới hàm FindFirstFile ,  FindFirstFileEx hoặc FindFirstFileTransacted .

bool  FindNextFileW(
   HANDLE           FindFile,           //
   WIN32_FIND_DATA  &lpFindFileData     //
   );

Thông số

FindFile

[in] Xử lý tìm kiếm được trả về bởi lệnh gọi trước đến hàm FindFirstFile  hoặc  FindFirstFileEx . 

lpFindFileData

[in] [out] Con trỏ đến cấu trúc WIN32_FIND_DATA, cấu trúc này nhận thông tin về tệp hoặc thư mục được tìm thấy. 

Giá trị trả về

Nếu hàm thành công thì giá trị trả về khác 0, tham số lpFindFileData sẽ chứa thông tin về tệp hoặc thư mục tiếp theo được tìm thấy.

Nếu hàm lỗi, giá trị trả về bằng 0 và nội dung của  lpFindFileData  sẽ không được xác định. Để biết thêm thông tin về lỗi, hãy gọi  hàm GetLastError .

Ví dụ về khai báo các hàm FindFirstFileW và FindNextFileW của Win API (mã được lấy từ tệp ListingFilesDirectory.mqh đi kèm):

#define MAX_PATH                 0x00000104  //
#define FILE_ATTRIBUTE_DIRECTORY 0x00000010  //
#define ERROR_NO_MORE_FILES      0x00000012  //there are no more files
#define ERROR_FILE_NOT_FOUND     0x00000002  //the system cannot find the file specified
//+------------------------------------------------------------------+
//| FILETIME structure                                               |
//+------------------------------------------------------------------+
struct FILETIME
  {
   uint              dwLowDateTime;
   uint              dwHighDateTime;
  };
//+------------------------------------------------------------------+
//| WIN32_FIND_DATA structure                                        |
//+------------------------------------------------------------------+
struct WIN32_FIND_DATA
  {
   uint              dwFileAttributes;
   FILETIME          ftCreationTime;
   FILETIME          ftLastAccessTime;
   FILETIME          ftLastWriteTime;
   uint              nFileSizeHigh;
   uint              nFileSizeLow;
   uint              dwReserved0;
   uint              dwReserved1;
   ushort            cFileName[MAX_PATH];
   ushort            cAlternateFileName[14];
  };

#import "kernel32.dll"
int      GetLastError();
long     FindFirstFileW(string lpFileName,WIN32_FIND_DATA  &lpFindFileData);
int      FindNextFileW(long FindFile,WIN32_FIND_DATA &lpFindFileData);
int      FindClose(long hFindFile);
int      FindNextFileW(int FindFile,WIN32_FIND_DATA &lpFindFileData);
int      FindClose(int hFindFile);
int      CopyFileW(string lpExistingFileName,string lpNewFileName,bool bFailIfExists);
#import

bool WinAPI_FindClose(long hFindFile)
  {
   bool res;
   if(_IsX64)
      res=FindClose(hFindFile)!=0;      
   else
      res=FindClose((int)hFindFile)!=0;      
//---
   return(res);
  }
  
bool WinAPI_FindNextFile(long hFindFile,WIN32_FIND_DATA &lpFindFileData)
  {
   bool res;
   if(_IsX64)
      res=FindNextFileW(hFindFile,lpFindFileData)!=0;      
   else
      res=FindNextFileW((int)hFindFile,lpFindFileData)!=0;      
//---
   return(res);
  }

3.3. Ví dụ về việc sử dụng FindFirstFileW, FindNextFileW

Script ” ListingFilesDirectory.mq5 ” đồng thời là ví dụ và thực tế là bản sao đầy đủ của mã làm việc cho EA. Nói cách khác, mã này càng gần với thực tế càng tốt.

Mục tiêu là lấy tên của thư mục chứa TERMINAL_COMMONDATA_PATH – “Common”. 

Ví dụ: đường dẫn TERMINAL_COMMONDATA_PATH trên máy tính trả về “C: \ Users \ KVN \ AppData \ Roaming \ MetaQuotes \ Terminal \ Common”. Vì vậy, nếu “Common” bị cắt khỏi đường dẫn này, bạn có thể lấy đường dẫn “C: \ Users \ KVN \ AppData \ Roaming \ MetaQuotes \ Terminal \”:

Thông thường, mặt nạ tìm kiếm “*. *” Được sử dụng để tìm tất cả các tệp. Vì vậy, cần phải thực hiện hai thao tác với các chuỗi sau: cắt từ “Common” và thêm mặt nạ “*. *”:

string common_data_path = TerminalInfoString ( TERMINAL_COMMONDATA_PATH );
   int pos = StringFind (common_data_path, "Common" , 0 );
   if (pos! = - 1 )
     {
      common_data_path = StringSubstr (common_data_path, 0 , pos- 1 );
     }
   khác 
      trở lại ;

   string path_addition = "\\ *. *" ;
   string mask_path = common_data_path + path_addition;
   printf ( "mask_path =% s" , mask_path);

hàm printf trả về mask_path = C: \ Users \ KVN \ AppData \ Roaming \ MetaQuotes \ Terminal \ *. * có nghĩa là ta làm đúng

Tiếp theo: khởi tạo search handle “hFind” và gọi hàm FindFirstFileW của Win API:

   hFind=-100;
   hFind=FindFirstFileW(mask_path,ffd);
   if(hFind==INVALID_HANDLE)
     {
      PrintFormat("Failed FindFirstFile (hFind) with error: %x",kernel32::GetLastError());
      return;
     }

Nếu một lệnh gọi đến FindFirstFileW không thành công, xử lý tìm kiếm “hFind” sẽ bằng “INVALID_HANDLE” và tập lệnh sẽ bị kết thúc.

Trong trường hợp của một cuộc gọi thành công đến chức năng FindFirstFileW, tạo ra một lvòng lặp do while, mà có được các tập tin hoặc thư mục tên , và ở phần cuối của vòng lặp các chức năng FindNextFileW của API Win sẽ được gọi là:

// List all the files in the directory with some info about them
   PrintFormat("hFind=%d",hFind);
   bool rezult=0;
   do
     {
      string name="";
      for(int i=0;i<MAX_PATH;i++)
        {
         name+=ShortToString(ffd.cFileName[i]);
        }
      
      Print("\"",name,"\", File Attribute Constants (dec): ",ffd.dwFileAttributes);
      //---
      ArrayInitialize(ffd.cFileName,0);
      ArrayInitialize(ffd.cAlternateFileName,0);
      ffd.dwFileAttributes=-100;
      ResetLastError();
      rezult=WinAPI_FindNextFile(hFind,ffd);
     }
   while(rezult!=0);
   if(kernel32::GetLastError()!=ERROR_NO_MORE_FILES)
      PrintFormat("Failed FindNextFileW (hFind) with error: %x",kernel32::GetLastError());
   WinAPI_FindClose(hFind);

Vòng lặp ‘do while’ sẽ tiếp tục miễn là hàm FindNextFileW của Win API trả về giá trị khác 0. Nếu một lệnh gọi đến hàm FindNextFileW của Win API trả về 0 và lỗi không bằng “ERROR_NO_MORE_FILES” – thì có nghĩa là đã xảy ra lỗi. Các search handle được đóng vào cuối đoạn bởi hàm WinAPI_FindClose (hFind);

Kết quả của thao tác tập lệnh ” ListingFilesDirectory.mq5 “:

mask_path=C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\*.*
hFind=-847293552
".", File Attribute Constants (dec): 16
"..", File Attribute Constants (dec): 16
"038C9E8FAFF9EA373522ECC6D5159962", File Attribute Constants (dec): 16
"0C46DDCEB43080B0EC647E0C66170465", File Attribute Constants (dec): 16
"2A6A33B25AA0984C6AB9D7F28665B88E", File Attribute Constants (dec): 16
"50CA3DFB510CC5A8F28B48D1BF2A5702", File Attribute Constants (dec): 16
"BC11041F9347CD71C5F8926F53AA908A", File Attribute Constants (dec): 16
"Common", File Attribute Constants (dec): 16
"Community", File Attribute Constants (dec): 16
"D0E8209F77C8CF37AD8BF550E51FF075", File Attribute Constants (dec): 16
"D3852169A6E781B7F35488A051432620", File Attribute Constants (dec): 16
"EE57F715BA53F2E183D6731C9376293D", File Attribute Constants (dec): 16
"Help", File Attribute Constants (dec): 16

3.4. Bên trong các thư mục terminal

Như vậy chúng ta đã tìm đến các thư mục con nằm trong “C: \ Users \ KVN \ AppData \ Roaming \ MetaQuotes \ Terminal \”. Sau đó chúng ta thực hiện tìm kiếm ở mức 2: theo đường dẫn sau: “C: \ Users \ KVN \ AppData \ Roaming \ MetaQuotes \ Terminal \” + tên của thư mục được tìm thấy ở bước trước + “origin.txt”.

Do đó, tìm kiếm chính của hàm FindFirstFileW sẽ chỉ tìm kiếm một tệp duy nhất trong thư mục con – “origin.txt”.

//+------------------------------------------------------------------+
//| Find and read the origin.txt                                     |
//+------------------------------------------------------------------+
void FindDataPath(string &array[][2])
  {
//---
   WIN32_FIND_DATA ffd;
   long            hFirstFind_0,hFirstFind_1;

   ArrayInitialize(ffd.cFileName,0);
   ArrayInitialize(ffd.cAlternateFileName,0);
//+------------------------------------------------------------------+
//| Get common path for all of the terminals installed on a computer.|
//| The common path on my computer:                                  |
//| C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common          |
//+------------------------------------------------------------------+
   string common_data_path=TerminalInfoString(TERMINAL_COMMONDATA_PATH);
   int pos=StringFind(common_data_path,"Common",0);
   if(pos!=-1)
     {
      //+------------------------------------------------------------------+
      //| Cuts "Common" ... and we get:                                    |
      //| C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal                 |
      //+------------------------------------------------------------------+
      common_data_path=StringSubstr(common_data_path,0,pos-1);
     }
   else
      return;

//--- stage Search №0. 
   string filter_0=common_data_path+"\\*.*"; // filter_0==C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\*.*

   hFirstFind_0=FindFirstFileW(filter_0,ffd);
//---
   string str_handle="";
   if(hFirstFind_0==INVALID_HANDLE)
      str_handle="INVALID_HANDLE";
   else
      str_handle=IntegerToString(hFirstFind_0);
   Print("filter_0: \"",filter_0,"\", handle hFirstFind_0: ",str_handle);
//---
   if(hFirstFind_0==INVALID_HANDLE)
     {
      PrintFormat("Failed FindFirstFile (hFirstFind_0) with error: %x",kernel32::GetLastError());
      return;
     }

//--- list all the files in the directory with some info about them
   bool rezult=0;
   do
     {
      if((ffd.dwFileAttributes  &FILE_ATTRIBUTE_DIRECTORY)==FILE_ATTRIBUTE_DIRECTORY)
        {
         string name_0="";
         for(int i=0;i<MAX_PATH;i++)
           {
            name_0+=ShortToString(ffd.cFileName[i]);
           }
         if(name_0!="." && name_0!="..")
           {
            ArrayInitialize(ffd.cFileName,0);
            ArrayInitialize(ffd.cAlternateFileName,0);
            //--- stage Search №1. search origin.txt file in the folder
            string filter_1=common_data_path+"\\"+name_0+"\\origin.txt";
            ResetLastError();
            hFirstFind_1=FindFirstFileW(filter_1,ffd);
            //---
            if(hFirstFind_1==INVALID_HANDLE)
               str_handle="INVALID_HANDLE";
            else
               str_handle=IntegerToString(hFirstFind_1);
            Print("   filter_1: \"",filter_1,"\", handle hFirstFind_1: ",str_handle);
            //---
            if(hFirstFind_1==INVALID_HANDLE)
              {
               if(kernel32::GetLastError()!=ERROR_FILE_NOT_FOUND)
                 {
                  PrintFormat("Failed FindFirstFile (hFirstFind_1) with error: %x",kernel32::GetLastError());
                  break;
                 }
               WinAPI_FindClose(hFirstFind_1);
               ArrayInitialize(ffd.cFileName,0);
               ArrayInitialize(ffd.cAlternateFileName,0);
               ResetLastError();
               rezult=WinAPI_FindNextFile(hFirstFind_0,ffd);
               continue;
              }
            //--- origin.txt file in this folder is found
            bool rezultTwo=0;
            string name_1="";
            for(int i=0;i<MAX_PATH;i++)
              {
               name_1+=ShortToString(ffd.cFileName[i]);
              }
            string origin=CopiedAndReadFile(filter_1); //--- receiving a string of the file found origin.txt
            if(origin!=NULL)
              {
               //--- write a string into an array
               int size=ArrayRange(array,0);
               ArrayResize(array,size+1,0);
               array[size][0]=common_data_path+"\\"+name_0;
               //value array[][0]==C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962
               array[size][1]=origin;
               //value array[][1]==C:\Program Files\MetaTrader 5 1\
              }
            WinAPI_FindClose(hFirstFind_1);
           }
        }
      ArrayInitialize(ffd.cFileName,0);
      ArrayInitialize(ffd.cAlternateFileName,0);
      ResetLastError();
      rezult=WinAPI_FindNextFile(hFirstFind_0,ffd);
     }
   while(rezult!=0); //if(hFirstFind_1==INVALID_HANDLE), we appear here
   if(kernel32::GetLastError()!=ERROR_NO_MORE_FILES)
      PrintFormat("Failed FindNextFileW (hFirstFind_0) with error: %x",kernel32::GetLastError());
   else
      Print("filter_0: \"",filter_0,"\", handle hFirstFind_0: ",hFirstFind_0,", NO_MORE_FILES");
   WinAPI_FindClose(hFirstFind_0);
  }

Hàm FindDataPath () in ra thông tin sau:

filter_0: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\*.*", handle hFirstFind_0: 1901014212592
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962\origin.txt", handle hFirstFind_1: 1901014213744
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\0C46DDCEB43080B0EC647E0C66170465\origin.txt", handle hFirstFind_1: 1901014213840
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\2A6A33B25AA0984C6AB9D7F28665B88E\origin.txt", handle hFirstFind_1: INVALID_HANDLE
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\50CA3DFB510CC5A8F28B48D1BF2A5702\origin.txt", handle hFirstFind_1: 1901014218448
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\BC11041F9347CD71C5F8926F53AA908A\origin.txt", handle hFirstFind_1: 1901014213936
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common\origin.txt", handle hFirstFind_1: INVALID_HANDLE
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Community\origin.txt", handle hFirstFind_1: INVALID_HANDLE
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\origin.txt", handle hFirstFind_1: 1901014216720
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D3852169A6E781B7F35488A051432620\origin.txt", handle hFirstFind_1: 1901014217104
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\EE57F715BA53F2E183D6731C9376293D\origin.txt", handle hFirstFind_1: 1901014218640
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Help\origin.txt", handle hFirstFind_1: INVALID_HANDLE
filter_0: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\*.*", handle hFirstFind_0: 1901014212592, NO_MORE_FILES 

Giải thích về các dòng in: đầu tiên nó tạo bộ lọc “filter_0” của tìm kiếm cấp 1 (bộ lọc là “C: \ Users \ KVN \ AppData \ Roaming \ MetaQuotes \ Terminal \ *. *”) Và lấy được “hFirstFind_0” là Handle của tìm kiếm cấp 1,nó bằng 1901014212592. Vì giá trị của “hFirstFind_0” không phải là “INVALID_HANDLE” – thì bộ lọc “filter_0” của tìm kiếm chính được chuyển tới hàm FindFirstFileW (filter_0, ffd) của Win API là chính xác. Sau khi gọi thành công đến FindFirstFileW (filter_0, ffd), tên của thư mục đầu tiên nhận được: đó là thư mục “038C9E8FAFF9EA373522ECC6D5159962”.  Đoạn này ta đã làm lại các bước ở phần 3.3 bên trên.

Tiếp theo, cần phải tìm kiếm tệp “origin.txt” trong thư mục 038C9E8FAFF9EA373522ECC6D5159962 . Để làm điều này, hãy tạo mask bộ lọc. Ví dụ: đối với 038C9E8FAFF9EA373522ECC6D5159962 , mask sẽ trông như sau: “C: \ Users \ KVN \ AppData \ Roaming \ MetaQuotes \ Terminal \ 038C9E8FAFF9EA373522ECC6D5159962 \ origin.txt “. Nếu xử lý “hFirstFind_1” không bằng “INVALID_HANDLE” – thì thư mục được chỉ định ( 038C9E8FAFF9EA373522ECC6D5159962 ) có chứa tệp được chỉ định ( origin.txt ). 

Việc in ra cho thấy rõ ràng rằng tìm kiếm chính trong các thư mục con đôi khi trả về “INVALID_HANDLE”. Điều này có nghĩa là các thư mục được chỉ định không có tệp “origin.txt”. 

3.5. CopyFileW

CopyFileW – sao chép tệp hiện có sang tệp mới.

bool   CopyFileW (
    string lpExistingFileName, //
    string lpNewFileName, //
    bool bFailIfExists //
   );

Thông số

lpExistingFileName

[in] Tên của tệp hiện có.

Ở đây, giới hạn về độ dài tên – MAX_PATH ký tự được thực thi

Nếu tệp có tên  lpExistingFileName không tồn tại, hàm không thành công và GetLastError  trả về  ERROR_FILE_NOT_FOUND .

lpNewFileName

[in] Tên của tệp mới. 

bFailIfExists

[trong] Nếu tham số này là  TRUE và tệp mới được chỉ định trong  lpNewFileName  tồn tại, thì hàm không thành công. Nếu tham số này là  FALSE và tệp mới tồn tại, hàm sẽ ghi đè tệp hiện có và hoàn tất thành công.

Giá trị trả về

NẾU hàm thành công, giá trị trả về khác 0.

Nếu hàm hoàn thành với lỗi, giá trị trả về bằng không. Để biết thêm thông tin về lỗi, hãy gọi  hàm GetLastError .

Ví dụ về khai  báo  hàm CopyFileW của Win API (mã được lấy từ tệp ListingFilesDirectory.mqh đi kèm):

#import "kernel32.dll" 
int       GetLastError ();
bool      CopyFileW ( string lpExistingFileName, string lpNewFileName, bool bFailIfExists);
#import

3.6. Làm việc với tệp “origin.txt”

Mô tả cách làm việc với hàm ListingFilesDirectory.mqh :: CopiedAndReadFile (string full_file_name).

Đường dẫn đầy đủ cua file “origin.txt” có thể giống như sau: “C: \ Users \ KVN \ AppData \ Roaming \ MetaQuotes \ Terminal \ 038C9E8FAFF9EA373522ECC6D5159962 \ origin.txt” . Phải mở file “origin.txt” và đọc nó bằng MQL5, điều này đồng nghĩa với việc file này phải đặt trong “Sandbox”. Do đó, “origin.txt” phải được sao chép từ thư mục con vào “Sandbox” (trong trường hợp này là “Sandbox” trong tệp chung của tất cả các Terminal). Việc sao chép như vậy được thực hiện bằng cách gọi hàm CopyFileW của Win API.

Ghi đường dẫn đến tệp “origin.txt” trong “Sandbox” vào biến “new_path”:

//+------------------------------------------------------------------+
//| Copying to the Common Data Folder                                |
//| for all client terminals ***\Terminal\Common\Files               |
//+------------------------------------------------------------------+
string CopiedAndReadFile(string full_file_name)
  {
   string new_path=TerminalInfoString(TERMINAL_COMMONDATA_PATH)+"\\Files\\origin.txt";
// => new_path==C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common\Files\origin.txt
//--- Win API

và gọi hàm CopyFileW của Win API với tham số thứ ba được đặt thành false – cho phép ghi đè tệp “origin.txt” trong hộp cát:

//--- Win API
   if(!CopyFileW(full_file_name,new_path,false))
     {
      Print("Error CopyFile ",full_file_name," to ",new_path);
      return(NULL);
     }
//--- open the file using MQL5

Mở tệp “origin.txt” để đọc và đừng quên đặt cờ FILE_COMMON, vì tệp nằm trong thư mục tệp chung:

//--- open the file using MQL5
   string str;
   ResetLastError();
   int file_handle=FileOpen("origin.txt",FILE_READ|FILE_TXT|FILE_COMMON);
   if(file_handle!=INVALID_HANDLE)
     {
      //--- read a string using the MQL5 
      str=FileReadString(file_handle,-1)+"\\";
      //--- close the file using the MQL5
      FileClose(file_handle);
     }
   else
     {
      PrintFormat("File %s open failed , MQL5 error=%d","origin.txt",GetLastError());
      return(NULL);
     }
   return(str);
  }

Chỉ đọc một lần – một chuỗi, nối “\\” vào cuối và nó là kết quả trả về của hàm này CopiedAndReadFile(string full_file_name).

3.7. Kết thúc Stroke

Đường dẫn đến thư mục cài đặt cho bốn terminal được đặt trong các tham số đầu vào:

//---Tham số nhập vào                                
input string   ExtInstallationPathTerminal_1="C:\\Program Files\\MetaTrader 5 1\\";    // folder of the MetaTrader#1 installation
input string   ExtInstallationPathTerminal_2="D:\\MetaTrader 5 2\\";                   // folder of the MetaTrader#2 installation
input string   ExtInstallationPathTerminal_3="D:\\MetaTrader 5 3\\";                   // folder of the MetaTrader#3 installation
input string   ExtInstallationPathTerminal_4="D:\\MetaTrader 5 4\\";                   // folder of the MetaTrader#4 installation

Ngoài ra, bốn biến string khác và một mảng được khai báo trên phạm vi toàn cục:

string         slaveTerminalDataPath1=NULL;                                // the path to the Data Folder of the terminal #1
string         slaveTerminalDataPath2=NULL;                                // the path to the Data Folder of the terminal #2
string         slaveTerminalDataPath3=NULL;                                // the path to the Data Folder of the terminal #3
string         slaveTerminalDataPath4=NULL;                                // the path to the Data Folder of the terminal #4
//---
string         arr_path[][2];

Các đường dẫn đến thư mục terminal trong AppData cần được lưu trữ trong các biến đó và mảng hai chiều có thể giúp thực hiện điều đó. sơ đồ dưới đây vẽ một phác thảo chung về cách khớp các thư mục cài đặt của các terminal Slave với các thư mục của chúng trong AppData:

GetStatsFromAccounts_EA.mq5::OnInit() >call> GetStatsFromAccounts_EA.mq5::FindDataFolders(arr_path) 
>call> ListingFilesDirectory.mqh::FindDataPath(string &array[][2]) >call> CopiedAndReadFile(string full_file_name) 

string origin=CopiedAndReadFile(filter_1); //--- receiving a string of the file found origin.txt
            if(origin!=NULL)
              {
               //--- write a string into an array
               int size=ArrayRange(array,0);
               ArrayResize(array,size+1,0);
               array[size][0]=common_data_path+"\\"+name_0;
               //value array[][0]==C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962
               array[size][1]=origin;
               //value array[][1]==C:\Program Files\MetaTrader 5 1\
              }
            FindClose(hFirstFind_1);

Hàm ListingFilesDirectory.mqh :: FindDataPath (string & array [] [2]) gọi hàm CopiedAndReadFile (string full_file_name) khi tệp “origin.txt” được tìm thấy trong các thư mục con của Terminal. Phần tử “0” của mảng chứa đường dẫn đến AppData và phần tử “1” lưu đường đến dẫn cài đặt (xin nhắc lại, đường dẫn này được lấy từ tệp “origin.txt” được tìm thấy).

>return control to> GetStatsFromAccounts_EA.mq5::FindDataFolders(arr_path):

ở đây, các biến slaveTerminalDataPath1, slaveTerminalDataPath2, slaveTerminalDataPath3 và slaveTerminalDataPath4 được lấp đầy bằng cách chỉ cần lặp qua mảng hai chiều:

   FindDataPath(array);
   for(int i=0;i<ArrayRange(array,0);i++)
     {
      //Print("array[",i,"][0]: ",array[i][0]);
      //Print("array[",i,"][1]: ",array[i][1]);
      if(StringCompare(ExtInstallationPathTerminal_1,array[i][1],true)==0)
         slaveTerminalDataPath1=array[i][0];
      if(StringCompare(ExtInstallationPathTerminal_2,array[i][1],true)==0)
         slaveTerminalDataPath2=array[i][0];
      if(StringCompare(ExtInstallationPathTerminal_3,array[i][1],true)==0)
         slaveTerminalDataPath3=array[i][0];
      if(StringCompare(ExtInstallationPathTerminal_4,array[i][1],true)==0)
         slaveTerminalDataPath4=array[i][0];
     }
   if(slaveTerminalDataPath1==NULL || slaveTerminalDataPath2==NULL ||
      slaveTerminalDataPath3==NULL || slaveTerminalDataPath4==NULL)
     {
      Print("slaveTerminalDataPath1 ",slaveTerminalDataPath1,", slaveTerminalDataPath2 ",slaveTerminalDataPath2);
      Print("slaveTerminalDataPath3 ",slaveTerminalDataPath3,", slaveTerminalDataPath4 ",slaveTerminalDataPath4);
      return(false);
     }

Nếu đạt đến giai đoạn này, thì EA đã khớp các thư mục cài đặt và đường dẫn của chúng trong thư mục AppData. Trong trường hợp ít nhất một trong các đường dẫn đầu cuối trong AppData không được tìm thấy (tức là bằng NULL), thì tất cả các đường dẫn được in ở các dòng cuối cùng và EA sẽ kết thúc với một lỗi.

4. Lựa chọn EA để thử nghiệm

Tệp của EA đã thử nghiệm nên được chọn trước khi khởi chạy bốn terminal Slave. Chuyên gia này phải được biên dịch trước và đặt trong thư mục dữ liệu của Master Terminal.

4.1. GetOpenFileName

GetOpenFileName – tạo hộp thoại ” mở “, cho phép người dùng chỉ định ổ đĩa, thư mục và tên của tệp (hoặc một tập hợp tệp) sẽ được mở. Khai báo và thực hiện hộp thoại ” Mở ” có đầy đủ trong tệp GetOpenFileNameW.mqh đi kèm.

4.2. Chọn một EA bằng hộp thoại hệ thống “Mở tệp”

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   ArrayFree(arr_path);
   if(!FindDataFolders(arr_path))
      return(INIT_SUCCEEDED);
//---
   if(MessageBox("Ready?",NULL,MB_YESNO)==IDYES)
     {
      expert_name=OpenFileName();
      if(expert_name==NULL)
         return(INIT_FAILED);
      //--- editing and copying of the ini-file in the folder of the terminals

Sau đó GetOpenFileNameW.mqh :: OpenFileName (void) được gọi

//+------------------------------------------------------------------+
//| Creates an Open dialog box                                       |
//+------------------------------------------------------------------+
string OpenFileName(void)
  {
   string path=NULL;
   string filter=NULL;
   if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
      filter="Компилированный код";
   else
      filter="Compiled code";
   if(GetOpenFileName(path,filter+"\0*.ex5\0",TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\\Experts\\","Select source file"))
      return(path);
   else
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return(NULL);
     }
  }

Nếu lệnh gọi hàm GetOpenFileName của Win API thành công, biến “đường dẫn” sẽ chứa tên đầy đủ của tệp đã chọn, chẳng hạn như: “C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Experts\Examples\MACD\MACD Sample.ex5“.

Bộ lọc filter sẽ lọc ra các ea đã được compiled. Sau đó ký tư “\0*.ex5\0” được thêm vào để tạo ra chuuỗi “TerminalInfoString (TERMINAL_DATA_PATH) +” \\ MQL5 \\ Experts \\ “”

4.3. Tệp INI cấu hình

Để khởi chạy terminal để kiểm tra EA từ command line (hoặc sử dụng Win API), cần phải có tệp configuration INI phải chứa phần [Tester] và các tham số hướng dẫn như sau:

[Tester]
Expert=test             //the file name of the Expert Advisor that will automatically run in the testing mode.
Symbol=EURUSD           //the name of the symbol that will be used as the main testing symbol
Period=H1               //the period of the testing chart
Deposit=10000           //amount of the initial deposit for testing
Model=4                 //tick generation mode
Optimization=0          //enable/disable optimization and set its type
FromDate=2016.01.22     //testing start date
ToDate=2016.06.06       //testing end date
Report=TesterReport     //the name of the file to save the report on testing
ReplaceReport=1         //enable/disable overwriting of the report file 
UseLocal=1              //enable/disable the used of local agents for testing
Port=3000               //port of the testing agent
Visual=0                //enable/disable testing in the visual mode
ShutdownTerminal=0      //enable/disable platform shutdown after completion of testing 

Quy trình làm việc với tệp INI là:

  1. Nhận đường dẫn đầy đủ đến “common.ini” của Master terminal. Đường dẫn đầy đủ là một chuỗi có dạng:
    “C: \ Users \ KVN \ AppData \ Roaming \ MetaQuotes \ Terminal \ D0E8209F77C8CF37AD8BF550E51FF075 \ config \ common.ini “. (MQL5)
  2. Nhận đường dẫn mới đến tệp INI trong hộp cát “\ Files”. Đường dẫn mới là một chuỗi có dạng:
    “C: \ Users \ KVN \ AppData \ Roaming \ MetaQuotes \ Terminal \ D0E8209F77C8CF37AD8BF550E51FF075 \ MQL5 \ Files \ myconfiguration.ini ” cho Master terminal. (MQL5)
  3. Sao chép ” common.ini”  vào file ” myconfiguration.ini” . (Chức năng CopyFileW của Win API).
  4. Chỉnh sửa tệp “myconfiguration.ini” . (MQL5).
  5. Lấy đường dẫn mới đến tệp INI trong hộp cát của terminal Slave. Nó là một chuỗi có dạng (trong ví dụ về Slave terminal №1)
    “C: \ Users \ KVN \ AppData \ Roaming \ MetaQuotes \ Terminal \ 038C9E8FAFF9EA373522ECC6D5159962 \ MQL5 \ Files \ myconfiguration.ini “. (MQL5)
  6. Sao chép tệp INI “myconfiguration.ini ” đã chỉnh sửa từ hộp cát của Master terminal vào hộp cát của Slave terminal. (Chức năng CopyFileW của Win API).
  7. Xóa  tệp “myconfiguration.ini” khỏi hộp cát của Master terminal. (MQL5)

Quy trình này phải được lặp lại cho mỗi đầu cuối Slave. Mặc dù có chỗ cho việc tối ưu hóa, nhưng phần mô tả quá trình này không có trong bài viết này. 

Việc chỉnh sửa configuration INI bắt đầu sau khi đã chọn EA để kiểm tra, GetStatsFromAccounts_EA.mq5 :: OnInit ():

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   ArrayFree(arr_path);
   if(!FindDataFolders(arr_path))
      return(INIT_SUCCEEDED);
//---
   if(MessageBox("Ready?",NULL,MB_YESNO)==IDYES)
     {
      expert_name=OpenFileName();
      if(expert_name==NULL)
         return(INIT_FAILED);
      //--- editing and copying of the ini-file in the folder of the terminals
      if(!CopyCommonIni())
         return(INIT_FAILED);
      if(!CopyTerminalIni())
         return(INIT_FAILED);
      //--- copying an expert in the terminal folders

Quy trình làm việc với tệp INI, trong ví dụ về đầu cuối Slave №1, GetStatsFromAccounts_EA.mq5 :: CopyCommonIni ():

//+------------------------------------------------------------------+
//| Copying common.ini - file in a shared folder of client           |
//| terminals. Edit the ini-file and copy obtained                   |
//| ini-files into folders                                           |
//| ...\AppData\Roaming\MetaQuotes\Terminal\"id terminal"\MQL5\Files |
//+------------------------------------------------------------------+
bool CopyCommonIni()
  {
//0 — "Every tick", "1 — 1 minute OHLC", 2 — "Open price only"
//3 — "Math calculations", 4 — "Every tick based on real ticks" 
//--- path to Data Folder
   string terminal_data_path=TerminalInfoString(TERMINAL_DATA_PATH);
//--- path to Commomm Data Folder
   string common_data_path=TerminalInfoString(TERMINAL_COMMONDATA_PATH);
//---
   string existing_file_name=terminal_data_path+"\\config\\common.ini"; // full path to the ini-file                                                        
   string temp_name_ini=terminal_data_path+"\\MQL5\\Files\\"+common_file_name;
   string test=NULL;
//--- terminal #1
   if(!CopyFileW(existing_file_name,temp_name_ini,false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return(false);
     }
   EditCommonIniFile(common_file_name,3000,4);
   test=slaveTerminalDataPath1+"\\MQL5\\Files\\"+common_file_name;
   if(!CopyFileW(temp_name_ini,test,false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return(false);
     }
   ResetLastError();
   if(!FileDelete(common_file_name,0))
      Print("#1 file ",common_file_name," not deleted, an error ",GetLastError());
//--- terminal #2 

Lệnh gọi đến hàm EditCommonIniFile (common_file_name, 3000,4) được chuyển như sau:

common_file_name – tên nếu tệp INI được chỉnh sửa;

3000 – là số cổng của agent thử nghiệm. Mỗi terminal phải được khởi chạy trên Agent. Đánh số Agent bắt đầu từ 3000. Để xem số cổng của Agent kiểm tra: trong terminal MetaTrader 5, hãy chuyển đến phần strategy tester và nhấp chuột phải vào tab “Journal” của trình kiểm tra chiến lược. Đánh số các cổng Agent thử nghiệm có thể được nhìn thấy như hình:

4 – loại thử nghiệm: 

  • 0 — “Every tick”
  • 1 — “1 Minute OHLC”,
  • 2 — “Opening prices only”,
  • 3 — “Mathematical calculations”,
  • 4 — “Every tick based on real ticks”

Việc chỉnh sửa tệp cấu hình commom.ini được thực hiện trong hàm GetStatsFromAccounts_EA.mq5 :: EditCommonIniFile (string name, const int port, const int model) – các hoạt động mở tệp, đọc từ tệp và ghi vào tệp được thực hiện bằng MQL5 :

//+------------------------------------------------------------------+
//| Editing common.ini file                                          |
//+------------------------------------------------------------------+
bool EditCommonIniFile(string name,const int port,const int model)
  {
   bool tester=false;      // if false - means the section [Tester] not found
   int  count_tester=0;    // counter discoveries section [Tester]
//--- open file 
   ResetLastError();
   int file_handle=FileOpen(name,FILE_READ|FILE_WRITE|FILE_TXT);
   if(file_handle!=INVALID_HANDLE)
     {
      //--- auxiliary variable
      string str;
      //--- read data
      while(!FileIsEnding(file_handle))
        {
         //--- read line 
         str=FileReadString(file_handle,-1);
         //--- find [Tester]
         if(StringFind(str,"[Tester]",0)!=-1)
           {
            tester=true;
            count_tester++;
           }
        }
      if(!tester)
        {
         FileWriteString(file_handle,"[Tester]\n",-1);
         FileWriteString(file_handle,"Expert=test\n",-1);
         FileWriteString(file_handle,"Symbol=EURUSD\n",-1);
         FileWriteString(file_handle,"Period=H1\n",-1);
         FileWriteString(file_handle,"Deposit=10000\n",-1);
         //0 — "Every tick", "1 — 1 minute OHLC", 2 — "Open price only"
         //3 — "Math calculations", 4 — "Every tick based on real ticks" 
         FileWriteString(file_handle,"Model="+IntegerToString(model)+"\n",-1);
         FileWriteString(file_handle,"Optimization=0\n",-1);
         FileWriteString(file_handle,"FromDate=2016.01.22\n",-1);
         FileWriteString(file_handle,"ToDate=2016.06.06\n",-1);
         FileWriteString(file_handle,"Report=TesterReport\n",-1);
         FileWriteString(file_handle,"ReplaceReport=1\n",-1);
         FileWriteString(file_handle,"UseLocal=1\n",-1);
         FileWriteString(file_handle,"Port="+IntegerToString(port)+"\n",-1);
         FileWriteString(file_handle,"Visual=0\n",-1);
         FileWriteString(file_handle,"ShutdownTerminal=0\n",-1);
        }
      //--- close file
      FileClose(file_handle);
     }
   else
     {
      PrintFormat("Unable to open file %s, error = %d",name,GetLastError());
      return(false);
     }
   return(true);
  }

4.4. Secret №2

Trước khi tắt, terminal MetaTrader 5 lưu trữ vị trí của các cửa sổ và bảng điều khiển, cũng như kích thước của chúng trong tệp ” terminal.ini “. Bản thân tệp được lưu trữ trong danh mục dữ liệu đầu cuối, trong thư mục con “config”. Ví dụ: đường dẫn đầy đủ đến ” terminal.ini ” của terminal Slave №1 như sau:

“C: \ Users \ KVN \ AppData \ Roaming \ MetaQuotes \ Terminal \ 038C9E8FAFF9EA373522ECC6D5159962 \ config \ terminal.ini “.

Trong chính tệp ” terminal.ini “, chỉ có khối “[Window]” là được quan tâm. Khôi phục window  của terminal MetaTrader 5. terminal sẽ có các kích thước xấp xỉ sau:

Nếu terminal bị đóng, khối [Cửa sổ] trong tệp terminal.ini sẽ có dạng sau:

Arrange=1
[Window]
Fullscreen=0
Type=1
Left=412
Top=65
Right=1212
Bottom=665
LSave=412

Đó là, khối [Cửa sổ] lưu trữ tọa độ và trạng thái của terminal. 

4.5. Đặt kích thước đầu cuối (chiều rộng, chiều cao). Chèn các dòng vào giữa tệp

Thay đổi tọa độ trong tệp terminal.ini của các terminal Slave là cần thiết để sắp xếp tất cả bốn terminal Slave theo cách sau khi khởi động:

Như đã đề cập ở trên, tệp “terminal.ini” cần được chỉnh sửa cho mỗi terminal Slave. Xin lưu ý rằng các dòng cần phải được chèn không phải ở cuối mà ở giữa tệp “terminal.ini” . Dưới đây là các tính năng của quá trình này.

Đây là một ví dụ: có một tệp “test.txt” nằm trong “hộp cát” của terminal. Nội dung của tệp “test.txt”:

s=0
df=12
asf=3
g=3
n=0
param_f=123

Cần phải sửa đổi thông tin ở dòng thứ hai và thứ ba để nhận được những điều sau:

s = 0 
df = 12 56 
asf = 5 
g = 3 
n = 0 
param_f = 123

Điều này nên được thực hiện như sáu:

  • mở tệp để đọc và ghi, đọc dòng đầu tiên (thao tác này di chuyển con trỏ tệp về đầu dòng thứ hai);
  • ghi giá trị mới “df = 1256” vào dòng thứ hai;
  • ghi giá trị mới “asf = 5” vào dòng thứ ba;
  • đóng tệp.

Chúng ta hãy xem xét điều này trong ví dụ mã của tập lệnh ” InsertRowsMistakenly.mq5 “:

//+------------------------------------------------------------------+
//|                                         InsertRowsMistakenly.mq5 |
//|                              Copyright © 2016, Vladimir Karputov |
//|                                           http://wmua.ru/slesar/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2016, Vladimir Karputov"
#property link      "http://wmua.ru/slesar/"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- open file
   ResetLastError();
   string name="test.txt";
   int file_handle=FileOpen(name,FILE_READ|FILE_WRITE|FILE_TXT);
   if(file_handle!=INVALID_HANDLE)
     {
      FileReadString(file_handle,-1);
      FileWriteString(file_handle,"df=1256"+"\r\n",-1);
      FileWriteString(file_handle,"asf=5"+"\r\n",-1);
      //--- close file
      FileClose(file_handle);
     }
   else
     {
      PrintFormat("Unable to open file %s, error = %d",name,GetLastError());
      return;
     }
  }
//+------------------------------------------------------------------+

Nếu làm như trên ta sẽ nhận được một kết quả không mong muốn – trong dòng thứ tư, ký tự ” g = ” bị thiếu ở dòng 4:

s=0
df=12
asf=3
g=3
n=0
param_f=123
s=0
df=1256
asf=5
3
n=0
param_f=123

Tại sao nó xảy ra? Hãy tưởng tượng rằng tệp bao gồm một tập hợp các ô nối tiếp nhau. Mỗi ô chứa một ký tự. Vì vậy, khi một cái gì đó được ghi vào tệp từ vị trí giữa tệp, nó đã thực hiện việc ghi đè lên các ô. Nếu nhiều ký tự được thêm vào so với ban đầu (như trong ví dụ trên: ban đầu có “df = 12”, sau đó thêm hai ký tự được viết – “df = 1256”), thì các ký tự thêm vào này sẽ làm hỏng mã tiếp theo. Việc này được mô tả giống như hình dưới:

Để tránh làm hỏng thông tin khi chèn các dòng vào giữa tệp, hãy tiến hành theo cách sau .

  • Sao chép tệp “terminal.ini” từ terminal Slave vào hộp cát của terminal Chính vào tệp có tên “terminal_ext.ini” (Win API CopyFileW).
  • Tạo một tệp có tên “terminal.ini” trong hộp cát Master terminal, mở nó để ghi (MQL5).
  • Mở tệp “terminal_ext.ini” trong hộp cát Master terminal để ghi (MQL5).
  • Trong hộp cát Master terminal: đọc các dòng từ “terminal_ext.ini” và ghi chúng vào tệp “terminal.ini” (MQL5).
  • Khi giá trị đọc là “[Window]” – ghi tọa độ mới (sáu dòng) vào tệp “terminal.ini” và di chuyển con trỏ tệp trong “terminal_ext.ini” đến sáu dòng (MQL5).
  • Trong hộp cát Master terminal: đọc các dòng từ “terminal_ext.ini” và ghi chúng vào tệp “terminal.ini”, cho đến khi tìm thấy phần cuối của tệp “terminal_ext.ini” (MQL5).
  • Trong hộp cát Master terminal: đóng tệp “terminal.ini” và “terminal_ext.ini” (MQL5).
  • Sao chép “terminal.ini” từ hộp cát Master terminal sang terminal Slave, vào tệp “terminal.ini” (Win API CopyFileW).
  • Trong hộp cát Master terminal: xóa các tệp “terminal.ini” và “terminal_ext.ini” (MQL5).

Thứ tự của hàm gọi: GetStatsFromAccounts_EA.mq5::OnInit() >call> GetStatsFromAccounts_EA.mq5::CopyTerminalIni()

//+------------------------------------------------------------------+
//| Editing Files "terminal.ini"                                     |
//+------------------------------------------------------------------+
bool CopyTerminalIni()
  {
//--- path to the terminal data folder 
   string terminal_data_path=TerminalInfoString(TERMINAL_DATA_PATH);
//---
   string existing_file_name=NULL;
   string ext_ini=terminal_data_path+"\\MQL5\\Files\\terminal_ext.ini";
   string ini=terminal_data_path+"\\MQL5\\Files\\terminal.ini";
   int left=0;
   int top=0;
   int right=0;
   int bottom=0;
//---
   for(int i=1;i<5;i++)
     {
      switch(i)
        {
         case 1:
            existing_file_name=slaveTerminalDataPath1+"\\config\\terminal.ini";
            left=0; top=0; right=682; bottom=420;
            break;
         case 2:
            existing_file_name=slaveTerminalDataPath2+"\\config\\terminal.ini";
            left=682; top=0; right=1366; bottom=420;
            break;
         case 3:
            existing_file_name=slaveTerminalDataPath3+"\\config\\terminal.ini";
            left=0; top=738-413; right=682; bottom=738;
            break;
         case 4:
            existing_file_name=slaveTerminalDataPath4+"\\config\\terminal.ini";
            left=682; top=738-413; right=1366; bottom=738;
            break;
        }
      //---
      if(!CopyFileW(existing_file_name,ext_ini,false))
        {
         PrintFormat("Failed with error: %x",kernel32::GetLastError());
         return(false);
        }
      if(!EditTerminalIniFile("terminal_ext.ini",left,top,right,bottom))
         return(false);
      if(!CopyFileW(ini,existing_file_name,false))
        {
         PrintFormat("Failed with error: %x",kernel32::GetLastError());
         return(false);
        }
      ResetLastError();
      if(!FileDelete("terminal.ini",0))
         Print("#",i," file terminal.ini not deleted, an error ",GetLastError());
      ResetLastError();
      if(!FileDelete("terminal_ext.ini",0))
         Print("#",i," file terminal_ext.ini not deleted, an error ",GetLastError());
     }
//---
   return(true);
  }

 >call> GetStatsFromAccounts_EA.mq5::EditTerminalIniFile

//+------------------------------------------------------------------+
//| Editing terminal.ini file                                        |
//+------------------------------------------------------------------+
bool EditTerminalIniFile(string ext_name,const int Left=0,const int Top=0,const int Right=1366,const int Bottom=738)
  {
//--- creates and opens files
   string name="terminal.ini";
   ResetLastError();
   int terminal_ini_handle=FileOpen(name,FILE_WRITE|FILE_TXT);
   int terminal_ext_ini__handle=FileOpen(ext_name,FILE_READ|FILE_TXT);
   if(terminal_ini_handle==INVALID_HANDLE)
     {
      PrintFormat("Unable to open file %s, error = %d",name,GetLastError());
     }
   if(terminal_ext_ini__handle==INVALID_HANDLE)
     {
      PrintFormat("Unable to open file %s, error = %d",ext_name,GetLastError());
     }
   if(terminal_ini_handle==INVALID_HANDLE && terminal_ext_ini__handle==INVALID_HANDLE)
     {
      FileClose(terminal_ext_ini__handle);
      FileClose(terminal_ini_handle);
      return(false);
     }

//--- auxiliary variable
   string str=NULL;
//--- read data
   while(!FileIsEnding(terminal_ext_ini__handle))
     {
      //--- read line
      str=FileReadString(terminal_ext_ini__handle,-1);
      FileWriteString(terminal_ini_handle,str+"\r\n",-1);
      //--- find [Window]
      if(StringFind(str,"[Window]",0)!=-1)
        {
         FileReadString(terminal_ext_ini__handle,-1);
         FileWriteString(terminal_ini_handle,"Fullscreen=0\r\n",-1);

         FileReadString(terminal_ext_ini__handle,-1);
         FileWriteString(terminal_ini_handle,"Type=1\r\n",-1);

         FileReadString(terminal_ext_ini__handle,-1);
         FileWriteString(terminal_ini_handle,"Left="+IntegerToString(Left)+"\r\n",-1);

         FileReadString(terminal_ext_ini__handle,-1);
         FileWriteString(terminal_ini_handle,"Top="+IntegerToString(Top)+"\r\n",-1);

         FileReadString(terminal_ext_ini__handle,-1);
         FileWriteString(terminal_ini_handle,"Right="+IntegerToString(Right)+"\r\n",-1);

         FileReadString(terminal_ext_ini__handle,-1);
         FileWriteString(terminal_ini_handle,"Bottom="+IntegerToString(Bottom)+"\r\n",-1);
        }
     }
//--- close files
   FileClose(terminal_ext_ini__handle);
   FileClose(terminal_ini_handle);
   return(true);
  }

Do đó, các tệp “terminal.ini” được chỉnh sửa trong các terminal của Slave, cho phép khởi chạy chúng như trong Hình 9. Có thể quan sát các biểu đồ thử nghiệm để so sánh độ chính xác của thử nghiệm ở các chế độ khác nhau. 

5. Khởi chạy thử nghiệm trên các terminal Slave

Mọi thứ hiện đã sẵn sàng để khởi chạy các terminal Slave ở chế độ thử nghiệm EA:

  • các tệp cấu hình “myconfiguration.ini” đã được chuẩn bị cho tất cả các terminal Slave;
  • các tệp “terminal.ini” của tất cả các terminal Slave đã được chỉnh sửa;
  • tên của Cố vấn Chuyên gia sẽ được test.

Chỉ còn lại hai nhiệm vụ: sao chép EA đã chọn vào hộp cát của các terminal Slave và chạy các terminal đó.

5.1. Sao chép EA vào các thư mục của các terminal Slave

Việc sao chép chuyên gia đã chọn trước đó (tên của chuyên gia được lưu trữ trong biến “Expert_name”) xảy ra trong OnInit ():

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   ArrayFree(arr_path);
   if(!FindDataFolders(arr_path))
      return(INIT_SUCCEEDED);
//---
   if(MessageBox("Ready?",NULL,MB_YESNO)==IDYES)
     {
      expert_name=OpenFileName();
      if(expert_name==NULL)
         return(INIT_FAILED);
      //--- editing and copying of the ini-file in the folder of the terminals
      if(!CopyCommonIni())
         return(INIT_FAILED);
      if(!CopyTerminalIni())
         return(INIT_FAILED);
      //--- copying an expert in the terminal folders
      ResetLastError();
      if(!CopyFileW(expert_name,slaveTerminalDataPath1+"\\MQL5\\Experts\\test.ex5",false))
        {
         PrintFormat("Failed CopyFileW #1 with error: %x",kernel32::GetLastError());
         return(INIT_FAILED);
        }

      if(!CopyFileW(expert_name,slaveTerminalDataPath2+"\\MQL5\\Experts\\test.ex5",false))
        {
         PrintFormat("Failed CopyFileW #2 with error: %x",kernel32::GetLastError());
         return(INIT_FAILED);
        }

      if(!CopyFileW(expert_name,slaveTerminalDataPath3+"\\MQL5\\Experts\\test.ex5",false))
        {
         PrintFormat("Failed CopyFileW #3 with error: %x",kernel32::GetLastError());
         return(INIT_FAILED);
        }

      if(!CopyFileW(expert_name,slaveTerminalDataPath4+"\\MQL5\\Experts\\test.ex5",false))
        {
         PrintFormat("Failed CopyFileW #4 with error: %x",kernel32::GetLastError());
         return(INIT_FAILED);
        }
      //---
      Sleep(sleeping);

5.2. ShellExecuteW

ShellExecuteW – thực thi hoạt động trên tệp được chỉ định.

//--- x64
long ShellExecuteW(
   long hwnd,               //
   string lpOperation,      //
   string lpFile,           //
   string lpParameters,     //
   string lpDirectory,      //
   int nShowCmd             //
   );
//--- x32
int ShellExecuteW(
   int hwnd,                //
   string lpOperation,      //
   string lpFile,           //
   string lpParameters,     //
   string lpDirectory,      //
   int nShowCmd             //
   );

Thông số

hwnd

[in] Xử lý cửa sổ mẹ được sử dụng để hiển thị giao diện người dùng và thông báo lỗi. Giá trị này phải là NULL , có nghĩa là hoạt động không liên quan đến cửa sổ hay nói cách khác cửa sổ này chạy tự động.

lpOperation

[in] Chuỗi với tên lệnh xác định hành động sẽ được thực thi. Tập hợp các lệnh có sẵn phụ thuộc vào một tệp hoặc thư mục cụ thể. Theo quy tắc, đó là những hành động có sẵn từ menu ngữ cảnh của đối tượng. Các lệnh sau thường được sử dụng:

edit” Khởi động trình chỉnh sửa và mở tài liệu để chỉnh sửa. Nếu lpFile không phải là một tệp tài liệu, hàm sẽ không được thực thi.

explore” Mở tệp được chỉ định trong  lpFile .

find” Bắt đầu tìm kiếm bắt đầu trong thư mục được chỉ định trong  lpDirectory .

open” Mở phần tử được xác định bởi tham số lpFile . Phần tử này có thể là một tệp hoặc thư mục.

print” In tệp được chỉ định bởi  lpFile . Nếu lpFile không phải là tệp tài liệu, hàm kết thúc do lỗi.

NULL” Tên lệnh mặc định được sử dụng, nếu có. Nếu không, lệnh “open” sẽ được sử dụng. Nếu không có lệnh nào được sử dụng, hệ thống sẽ sử dụng lệnh đầu tiên được chỉ định trong sổ đăng ký.

lpFile 

[in] String đặt tệp hoặc đối tượng để thực thi lệnh. Tên này là tên đầy đủ (không chỉ bao gồm tên tệp mà còn cả đường dẫn đến nó). Lưu ý rằng đối tượng có thể không hỗ trợ tất cả các lệnh. Ví dụ: không phải tất cả các tài liệu đều hỗ trợ lệnh ” in “. Nếu một đường dẫn được sử dụng cho tham số lpDirectory , nó sẽ không được sử dụng cho lpFile .

lpParameters

[in] Nếu  lpFile trỏ đến một tệp thực thi, thì tham số này là một chuỗi xác định các tham số sẽ được truyền cho ứng dụng. Định dạng của chuỗi này được xác định bởi tên của lệnh sẽ được thực thi. Nếu lpFile trỏ đến một tệp tài liệu, thì lpParameters phải là NULL .

lpDirectory

[in] Chuỗi xác định thư mục làm việc. Nếu giá trị này là NULL , thư mục làm việc hiện tại được sử dụng. Nếu một đường dẫn đã được chỉ định trong lpFile , thì không được sử dụng cho lpDirectory .

nShowCmd

[in] Cờ xác định cách ứng dụng sẽ được hiển thị khi mở. Nếu lpFile chỉ định một tệp tài liệu, cờ chỉ đơn giản được chuyển đến ứng dụng tương ứng. Cờ đã sử dụng:

// + ----------------------------------------------- ------------------- + 
// | Lệnh liệt kê để khởi động ứng dụng | 
// + ----------------------------------------------- ------------------- + 
enum EnSWParam
  {
   // + ----------------------------------------------- ------------------- + 
   // | Hiển thị cửa sổ dưới dạng cửa sổ thu nhỏ. Giá trị này tương tự | 
   // | thành SW_SHOWMINIMIZED, ngoại trừ cửa sổ không được kích hoạt. | 
   // + ----------------------------------------------- ------------------- + 
   SW_SHOWMINNOACTIVE = 7 ,
    // + ----------------------- ------------------------------------------- + 
   // | Kích hoạt và hiển thị một cửa sổ. Nếu cửa sổ được thu nhỏ hoặc | 
   // | tối đa, hệ thống khôi phục kích thước ban đầu và | 
   // | Chức vụ. Một ứng dụng phải chỉ định cờ này khi | 
   // | hiển thị cửa sổ lần đầu tiên. |
   // + ----------------------------------------------- ------------------- + 
   SW_SHOWNORMAL = 1 ,
    // + ----------------------- ------------------------------------------- + 
   // | Kích hoạt cửa sổ và hiển thị dưới dạng cửa sổ thu nhỏ. | 
   // + ----------------------------------------------- ------------------- + 
   SW_SHOWMINIMIZED = 2 ,
    // + ----------------------- ------------------------------------------- + 
   // | Kích hoạt cửa sổ và hiển thị dưới dạng cửa sổ được phóng to. | 
   // + ----------------------------------------------- ------------------- + 
   SW_SHOWMAXIMIZED = 3 ,
    // + ----------------------- ------------------------------------------- +
   // | Ẩn cửa sổ và kích hoạt một cửa sổ khác. | 
   // + ----------------------------------------------- ------------------- + 
   SW_HIDE = 0 ,
    // + ----------------------- ------------------------------------------- + 
   // | Kích hoạt cửa sổ và hiển thị nó ở kích thước hiện tại | 
   // | và vị trí. | 
   // + ----------------------------------------------- ------------------- + 
   SW_SHOW = 5 ,
  }

Giá trị trả về : Nếu hàm thành công, nó trả về giá trị lớn hơn 32.

Ví dụ về cách gọi hàm ShellExecuteW của Win API:

#import  "shell32.dll"
int  GetLastError();
//+------------------------------------------------------------------+
//| ShellExecute function                                            |
//| https://msdn.microsoft.com/en-us/library/windows/desktop/bb762153(v=vs.85).aspx
//| Performs an operation on a specified file                        |
//+------------------------------------------------------------------+
//--- x64
long ShellExecuteW(long hwnd,string lpOperation,string lpFile,string lpParameters,string lpDirectory,int nShowCmd);
//--- x32
int ShellExecuteW(int hwnd,string lpOperation,string lpFile,string lpParameters,string lpDirectory,int nShowCmd);
#import
#import "kernel32.dll"

//+------------------------------------------------------------------+
//| Enumeration command to start the application                     |
//+------------------------------------------------------------------+
enum EnSWParam
  {
   //+------------------------------------------------------------------+
   //| Displays the window as a minimized window. This value is similar |
   //| to SW_SHOWMINIMIZED, except the window is not activated.         |
   //+------------------------------------------------------------------+
   SW_SHOWMINNOACTIVE=7,
   //+------------------------------------------------------------------+
   //| Activates and displays a window. If the window is minimized or   |
   //| maximized, the system restores it to its original size and       |
   //| position. An application should specify this flag when           |
   //| displaying the window for the first time.                        |
   //+------------------------------------------------------------------+
   SW_SHOWNORMAL=1,
   //+------------------------------------------------------------------+
   //| Activates the window and displays it as a minimized window.      |
   //+------------------------------------------------------------------+
   SW_SHOWMINIMIZED=2,
   //+------------------------------------------------------------------+
   //| Activates the window and displays it as a maximized window.      |
   //+------------------------------------------------------------------+
   SW_SHOWMAXIMIZED=3,
   //+------------------------------------------------------------------+
   //| Hides the window and activates another window.                   |
   //+------------------------------------------------------------------+
   SW_HIDE=0,
   //+------------------------------------------------------------------+
   //| Activates the window and displays it in its current size         |
   //| and position.                                                    |
   //+------------------------------------------------------------------+
   SW_SHOW=5,
  };

5.3. Khởi chạy các terminal

Các terminal Slave được khởi chạy từ OnInit ():

      //---
      Sleep(sleeping);
      LaunchSlaveTerminal(ExtInstallationPathTerminal_1,slaveTerminalDataPath1+"\\MQL5\\Files\\"+common_file_name);
      Sleep(sleeping);
      LaunchSlaveTerminal(ExtInstallationPathTerminal_2,slaveTerminalDataPath2+"\\MQL5\\Files\\"+common_file_name);
      Sleep(sleeping);
      LaunchSlaveTerminal(ExtInstallationPathTerminal_3,slaveTerminalDataPath3+"\\MQL5\\Files\\"+common_file_name);
      Sleep(sleeping);
      LaunchSlaveTerminal(ExtInstallationPathTerminal_4,slaveTerminalDataPath4+"\\MQL5\\Files\\"+common_file_name);
     }
//---
   return(INIT_SUCCEEDED);
  

Đồng thời, EA chờ đợi mili giây ” Sleep” giữa mỗi lần khởi chạy. Theo mặc định, thông số “-Sleep” bằng 9000 (tức là 9 giây). Nếu lỗi ủy quyền agent  xảy ra trong các terminal Slave, hãy tăng tham số này. 

6 Hướng dẫn chạy phần mềm

Bước 1 : cài đặt MT5 vào ổ C sau đó mở phần mềm lên đăng nhập vào một tài khoản demo bất kỳ. Sau khi cài đặt xong các bạn sẽ có được thư mục C:\Program Files\MetaTrader 5 (Mt5 Master)

Bước 2 : Copy thư mục này ra 4 thư mục giống nó với tên là #1 #2 #3 #4 (Mt5 Slave) ở bất kỳ đâu ví dụ ở đây mình copy ngay tại thư mục Program file của ổ C và được 4 thư mục như hình dưới. Bạn vào 4 thư mục mới này mở phần mềm Mt5 trong đó ra và đăng nhập cùng 1 tài khoản giống tài khoản của MT5 Master. 

Bước 3 : Download file thực thi ở cuối bài viết về máy và giải nén sau đó copy file vừa giải nén vào thư mục Expert của MT5 Master mà bạn cài ban đầu, không cần copy file này vào các thư mục Mt5 Slave. ( nếu bạn download file mã nguồn thì cần compile trước khi chạy). 

Bước 4 : mở chương trình MT5 master lên đăng nhập tài khoản nếu bạn chưa đăng nhập. Mở một biểu đồ bất kỳ và thêm EA Test4Model vào biểu đồ này. Cửa sổ bật lên bạn chọn ô Allow DLL imports như hình dưới và chuyển sang thẻ Input.

Bước 5 : tại thẻ input các bạn sửa cột value là được dẫn của 4 thư mục mt5 Slave vừa được tạo ở bước 2 và ấn OK .Phần mềm hiện ra cửa sổ Ready? như hình dưới là bạn đã làm đúng tất cả các bước ở trên. Ở đây bạn chọn Yes.

Bước 6 : Phần mềm sẽ tự động mở ra 1 của sổ để bạn chọn EA cần backtest. Vào thư mục MQL5\Experts như hình dưới và chọn EA của bạn. vidu ở đây mình chọn ea “Macd sample” ấn open 

Cuối cùng phần mềm sẽ tự động chạy ra 4 cửa sổ như hình dưới và bạn có thể thấy là Ea của bạn đã được Backtest ở 4 chế độ khác nhau trong cùng 1 cặp tiền và 1 khoảng thời gian, 

Bạn có thể mở tab Graph để xem sự thay đổi khác nhau của 4 chế độ test này.

 

7 Các lỗi có thể gặp

8 Download

File thực thi:

File mã nguồn: 2345  

 

 

 

 

 

 

 

 

 

 

Bài viết dịch từ bài LIFEHACK FOR TRADER: FOUR BACKTESTS ARE BETTER THAN ONE