外汇EA编写教程:如何准备 MetaTrader 5 报价用于其他应用程序

目录

简介
1. 涵盖主题
2. 数据格式
3. 程序的外部参数
4. 检查用户输入的参数
5. 全局变量
6. 信息面板
7. 应用程序的主体
8. 创建文件夹和归档数据
总结

简介

在我学习 MQL5 之前,我尝试过许多其他的应用程序来开发交易系统。不能说这是浪费时间。其中有些包含一些有用的工具,允许用户节约时间、处理很多事项、打破了一些迷思并很快选择无需熟悉编程语言的进一步开发方向。

这些应用程序需要历史数据。由于缺乏某些标准数据格式,它们在使用前通常需要先进行编辑(例如,在 Excel 中),以符合必要程序的适用格式。即使您能够找出所有必需细节,很多工作仍然需要手动完成。用户可找到不同版本的脚本,用于从 MetaTrader 4 复制报价至必要格式。如果有这种需求,我们同样可以为 MQL5 开发脚本版本。

1. 涵盖主题

本文论述下列主题:

  • 使用 Market Watch(市场报价)窗口中的交易品种列表和服务器上的常用交易品种列表。
  • 检查可用数据深度,必要时下载缺失数据并正确处理各种情形。
  • 在自定义面板图表和日志中显示请求数据的相关信息。
  • 准备数据,以用户定义的格式进行归档。
  • 创建文件目录。
  • 数据归档。

2. 数据格式

我将给出一个示例,是关于在 NeuroShell DayTrader Professional (NSDT) 中待使用数据的准备。我尝试过 NSDT 版本 5 和版本 6,发现它们对数据格式的要求有所不同。NSDT 版本 5 的日期和时间数据应处于不同的列中。文件的第一行应如下所示:

“Date” “Time” “Open” “High” “Low” “Close” “Volume”

NSDT 版本 6 中的标题行应具有不同的外观,以允许应用程序接收文件。这意味着日期和时间应位于同一列中:

Date,Open,High,Low,Close,Volume

MetaTrader 5 允许用户将报价保存在 *.csv 文件中。文件中的数据如下所示:

图 1. MetaTrader 5 终端保存的数据

图 1. MetaTrader 5 终端保存的数据

然而,我们不能仅仅只编辑标题行,因为日期应该为其他的格式。对于 NSDT 版本 5:

dd.mm.yyyy,hh:mm,Open,High,Low,Close,Volume

对于 NSDT 版本 6:

dd/mm/yyyy hh:mm,Open,High,Low,Close,Volume

下拉列表将用于脚本外部参数,用户可以在此选择必要格式。除了选择标题和日期格式,我们将向用户提供选择交易品种数量以及他们希望写入文件的数据的可能性。为此,我们将准备三种版本:

  • 仅在当前交易品种上、启动脚本的图表上(仅当前交易品种)写入数据。
  • 在位于 Market Watch(市场报价)窗口中的交易品种上写入数据(市场报价交易品种)。
  • 在服务器所有可用交易品种上写入数据(所有列示交易品种)。

我们在脚本代码的外部参数前输入下述代码以创建这种列表:

//_________________________________
// HEADER_FORMATS_ENUMERATION
enum FORMAT_HEADERS
  {
   NSDT_5 = 0, // "Date" "Time" "Open" "High" "Low" "Close" "Volume"
   NSDT_6 = 1  // Date,Open,High,Low,Close,Volume
  };
//---
//___________________________
// ENUMERATION_OF_DATA_FORMATS
enum FORMAT_DATETIME
  {
   SEP_POINT1 = 0, // dd.mm.yyyy hh:mm
   SEP_POINT2 = 1, // dd.mm.yyyy, hh:mm
   SEP_SLASH1 = 2, // dd/mm/yyyy hh:mm
   SEP_SLASH2 = 3  // dd/mm/yyyy, hh:mm
  };
//---
//____________________________
// ENUMERATION_OF_FILING_MODES
enum CURRENT_MARKETWATCH
  {
   CURRENT          = 0, // ONLY CURRENT SYMBOLS
   MARKETWATCH      = 1, // MARKETWATCH SYMBOLS
   ALL_LIST_SYMBOLS = 2  // ALL LIST SYMBOLS
  };

有关枚举的更多详情,请参阅《MQL5 参考》

3. 程序的外部参数

现在,我们可以创建脚本所有外部参数的完整列表:

//____________________________________________________________________
//+------------------------------------------------------------------+
//| EXTERNAL_PARAMETERS                                              |
//+------------------------------------------------------------------+
input datetime            start_date     = D'01.01.2011'; // Start Date
input datetime            end_date       = D'18.09.2012'; // End Date
input FORMAT_HEADERS      format_headers = NSDT_5;     // Format Headers
input FORMAT_DATETIME     format_date    = SEP_POINT2; // Format Datetime
input CURRENT_MARKETWATCH curr_mwatch    = CURRENT;    // Mode Write Symbols
input bool                clear_mwatch   = true;        // Clear Market Watch
input bool                show_progress  = true;        // Show Progress (%)

外部参数用于下述目的:

  • 用户可以使用 Start Date (start_date) 和 End Date (end_date) 参数指定日期间隔。
  • Format Headers (format_headers) 下拉列表允许用户选择标题格式。
  • Format Datetime (format_date) 下拉列表允许用户选择日期和时间格式。
  • Mode Write Symbols (curr_mwatch) 下拉列表允许用户选择归档交易品种数量。
  • 如果 Clear Market Watch (clear_mwatch) 参数为真,则允许用户在归档后删除 Market Watch(市场报价)窗口中的所有交易品种。这只是涉及当前带不活动图表的交易品种。
  • Show Progress (%) (show_progress) 参数在数据面板中显示归档进度。如果禁用此参数,归档速度将加快。

启动时,外部参数将如下所示:

图 2. 应用程序的外部参数

图 2. 应用程序的外部参数

4. 检查用户输入的参数

让我们创建用于检查用户在基础代码前输入的参数的函数。例如,Start Date 参数中的起始日期应早于 End Date 参数中的结束日期。标题的格式应匹配日期和时间格式。如果用户在设置参数时犯了一些错误,将显示下面的警告消息,且程序停止运行。

警告消息示例:

图 3. 错误地指定值的警告

图 3. 错误地指定参数的错误消息示例

ValidationParameters() 函数:

//____________________________________________________________________
//+------------------------------------------------------------------+
//| CHECKING_CORRECTNESS_OF_PARAMETERS                               |
//+------------------------------------------------------------------+
bool ValidationParameters()
  {
   if(start_date>=end_date)
     {
      MessageBox("The start date should be earlier than the ending one!/n/n"
                 "Application cannot continue. Please retry.",
                 //---
                 "Parameter error!",MB_ICONERROR);
      //---
      return(true);
     }
//---
   if(format_headers==NSDT_5 && 
      (format_date==SEP_POINT1 || format_date==SEP_SLASH1))
     {
      MessageBox("For the headers of the following format:/n/n"
                 "/"Date/" ""/"Time/" ""/"Open/" ""/"High/" ""/"Low/" ""/"Close/" ""/"Volume/"/n/n"
                 "Date/time format can be selected out of two versions:/n/n"
                 "dd.mm.yyyy, hh:mm/n"
                 "dd/mm/yyyy, hh:mm/n/n"
                 "Application cannot continue. Please retry.",
                 //---
                 "Header and date/time formats do not match!",MB_ICONERROR);
      //---
      return(true);
     }
//---
   if(format_headers==NSDT_6 && 
      (format_date==SEP_POINT2 || format_date==SEP_SLASH2))
     {
      MessageBox("For the headers of the following format:/n/n"
                 "Date,Open,High,Low,Close,Volume/n/n"
                 "Date/time format can be selected out of two versions:/n/n"
                 "dd.mm.yyyy hh:mm/n"
                 "dd/mm/yyyy hh:mm/n/n"
                 "Application cannot continue. Please retry.",
                 //---
                 "Header and date/time formats do not match!",MB_ICONERROR);
      //---
      return(true);
     }
//---
   return(false);
  }

5. 全局变量

接下来,我们应该确定所有将用于脚本的全局变量和数组:

//____________________________________________________________________
//+------------------------------------------------------------------+
//| GLOBAL_VARIABLES_AND_ARRAYS                                      |
//+------------------------------------------------------------------+
MqlRates rates[]; // Array for copying data
//---
string symbols[]; // Symbol array
//---
// Array of graphic object names
string arr_nmobj[22]=
  {
   "fon","hd01",
   "nm01","nm02","nm03","nm04","nm05","nm06","nm07","nm08","nm09","nm10",
   "nm11","nm12","nm13","nm14","nm15","nm16","nm17","nm18","nm19","nm20"
  };
//---
// Array of displayed text containing graphic objects
string arr_txtobj[21];
//---
string path="";         // File path
int cnt_symb=0;         // Number of symbols
int sz_arr_symb=0;      // Symbol array size
int bars=0;             // Number of bars according to the specified TF
int copied_bars=0;      // Number of bars copied for writing
double pgs_pcnt=0;      // Writing progress
int hFl=INVALID_HANDLE;  // File handle
//---
string   // Variables for data formatting
sdt="",  // Date line
dd="",   // Day
mm="",   // Month
yyyy="", // Year
tm="",   // Time
sep="";  // Separator
//---
int max_bars=0; // Maximum number of bars in the terminal settings
//---
datetime
first_date=0,        // First available data in a specified period
first_termnl_date=0, // First available data in the terminal's database
first_server_date=0, // First available data in the server's database
check_start_date=0;  // Checked correct date value

6. 信息面板

现在,我们应说明要在信息面板上显示的元素。有三种类型的图形对象可用作背景:

  • 最简单和最明显的一个 –“矩形标签”(OBJ_RECTANGLE_LABEL)。
  • 希望令他们的界面具有独特外观的用户可使用“位图”对象 (OBJ_BITMAP)。
  • “编辑”对象 (OBJ_EDIT) 同样可用作背景。设置“只读”属性以消除输入文本的可能性。使用“编辑”对象还有另一大优势。如果您在一个“EA 交易”中创建了一个信息面板,并且您希望它在测试期间在可视化模式下具有相同的外观,最后一种类型是到目前为止能够实现这一点的唯一方法。无论是 OBJ_RECTANGLE_LABEL 还是 OBJ_BITMAP,都不能在测试期间在可视化模式下进行显示。

尽管在我们的示例中只开发了一个脚本而不是“EA 交易”,具有 OBJ_EDIT 对象的背景将作为一个例子。结果在下图中显示:

图 4. 信息面板

图 4. 信息面板

我们来说明面板上显示的所有数据:

  • Symbol (current/total) – 交易品种,当时其上的数据被下载/复制/写入。括号中左边的数字显示当前交易品种数量。右边的数字显示脚本将处理的交易品种的通常数量。
  • Path Symbol – 交易品种路径或其所属类别。如果在 Market Watch(市场报价)窗口中单击右键调出上下文菜单并选择 “Symbols…”(交易品种…),将弹出包含所有交易品种列表的窗口。您可以在终端的“用户指南”中找到更多相关信息。
  • Timeframe – 周期(时间表)。使用脚本将在其中启动的时间表。
  • Input Start Date – 用户在脚本参数中指定的数据起始日期。
  • First Date (H1) – 当前时间表的第一个可用数据日期(柱)。
  • First Terminal Date (M1) – 在已有终端数据中 M1 时间表的第一个可用日期。
  • First Server Date (M1) – 服务器上 M1 时间表的第一个可用日期。
  • Max. Bars In Options Terminal – 在终端设置中指定的要在图表上显示的最大柱数。
  • Copied Bars – 用于写入的复制柱数。
  • Progress Value Current Symbol – 当前交易品种的写入数据的百分比值。

下面是这种信息面板的代码:

//____________________________________________________________________
//+------------------------------------------------------------------+
//| INFORMATION_PANEL                                                |
//|------------------------------------------------------------------+
void InfoTable(int s)
  {
   int fnt_sz=8;            // Font size
   string fnt="Calibri";     // Header font
   color clr=clrWhiteSmoke;  // Color
//---
   int xH=300;
   int height_pnl=0;
   int yV1=1,yV2=12,xV1=165,xV2=335,xV3=1;
//---
   string sf="",stf="",ssf="";
   bool flg_sf=false,flg_stf=false,flg_ssf=false;
//---
   if(show_progress) { height_pnl=138; } else { height_pnl=126; }
//---
   flg_sf=SeriesInfoInteger(symbols[s],_Period,SERIES_FIRSTDATE,first_date);
   flg_stf=SeriesInfoInteger(symbols[s],PERIOD_M1,SERIES_TERMINAL_FIRSTDATE,first_termnl_date);
   flg_ssf=SeriesInfoInteger(symbols[s],PERIOD_M1,SERIES_SERVER_FIRSTDATE,first_server_date);
//---
   if(flg_sf) { sf=TSdm(first_date); } else { sf="?"; }
   if(flg_stf) { stf=TSdm(first_termnl_date); } else { stf="?"; }
   if(flg_ssf) { ssf=TSdm(first_server_date); } else { ssf="?"; }
//---
   if(cnt_symb==0) { cnt_symb=1; }
//---
   int anchor1=ANCHOR_LEFT_UPPER,anchor2=ANCHOR_RIGHT_UPPER,corner=CORNER_LEFT_UPPER;
//---
   string path_symbol=SymbolInfoString(symbols[s],SYMBOL_PATH);
   path_symbol=StringSubstr(path_symbol,0,StringLen(path_symbol)-StringLen(symbols[s]));
//---
   arr_txtobj[0]="INFO TABLE";
   arr_txtobj[1]="Symbol (current / total) : ";
   arr_txtobj[2]=""+symbols[s]+" ("+IS(s+1)+"/"+IS(cnt_symb)+")";
   arr_txtobj[3]="Path Symbol : ";
   arr_txtobj[4]=path_symbol;
   arr_txtobj[5]="Timeframe : ";
   arr_txtobj[6]=gStrTF(_Period);
   arr_txtobj[7]="Input Start Date : ";
   arr_txtobj[8]=TSdm(start_date);
   arr_txtobj[9]="First Date (H1) : ";
   arr_txtobj[10]=sf;
   arr_txtobj[11]="First Terminal Date (M1) : ";
   arr_txtobj[12]=stf;
   arr_txtobj[13]="First Server Date (M1) : ";
   arr_txtobj[14]=ssf;
   arr_txtobj[15]="Max. Bars In Options Terminal : ";
   arr_txtobj[16]=IS(max_bars);
   arr_txtobj[17]="Copied Bars : ";
   arr_txtobj[18]=IS(copied_bars);
   arr_txtobj[19]="Progress Value Current Symbol : ";
   arr_txtobj[20]=DS(pgs_pcnt,2)+"%";
//---
   Create_Edit(0,0,arr_nmobj[0],"",corner,fnt,fnt_sz,clrDimGray,clrDimGray,345,height_pnl,xV3,yV1,2,C'15,15,15');
//---
   Create_Edit(0,0,arr_nmobj[1],arr_txtobj[0],corner,fnt,8,clrWhite,C'64,0,0',345,12,xV3,yV1,2,clrFireBrick);
//---
   Create_Label(0,arr_nmobj[2],arr_txtobj[1],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2,0);
   Create_Label(0,arr_nmobj[3],arr_txtobj[2],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2,0);
//---
   Create_Label(0,arr_nmobj[4],arr_txtobj[3],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*2,0);
   Create_Label(0,arr_nmobj[5],arr_txtobj[4],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*2,0);
//---
   Create_Label(0,arr_nmobj[6],arr_txtobj[5],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*3,0);
   Create_Label(0,arr_nmobj[7],arr_txtobj[6],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*3,0);
//---
   Create_Label(0,arr_nmobj[8],arr_txtobj[7],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*4,0);
   Create_Label(0,arr_nmobj[9],arr_txtobj[8],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*4,0);
//---
   Create_Label(0,arr_nmobj[10],arr_txtobj[9],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*5,0);
   Create_Label(0,arr_nmobj[11],arr_txtobj[10],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*5,0);
//---
   Create_Label(0,arr_nmobj[12],arr_txtobj[11],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*6,0);
   Create_Label(0,arr_nmobj[13],arr_txtobj[12],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*6,0);
//---
   Create_Label(0,arr_nmobj[14],arr_txtobj[13],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*7,0);
   Create_Label(0,arr_nmobj[15],arr_txtobj[14],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*7,0);
//---
   Create_Label(0,arr_nmobj[16],arr_txtobj[15],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*8,0);
   Create_Label(0,arr_nmobj[17],arr_txtobj[16],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*8,0);
//---
   Create_Label(0,arr_nmobj[18],arr_txtobj[17],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*9,0);
   Create_Label(0,arr_nmobj[19],arr_txtobj[18],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*9,0);
//---
   if(show_progress)
     {
      Create_Label(0,arr_nmobj[20],arr_txtobj[19],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*10,0);
      Create_Label(0,arr_nmobj[21],arr_txtobj[20],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*10,0);
     }
  }
//____________________________________________________________________
//+------------------------------------------------------------------+
//| CREATING_LABEL_OBJECT                                            |
//+------------------------------------------------------------------+
void Create_Label(long   chrt_id,   // chart id
                  string lable_nm,  // object name
                  string rename,    // displayed name
                  long   anchor,    // anchor point
                  long   corner,    // attachment corner
                  string font_bsc,  // font
                  int    font_size, // font size
                  color  font_clr,  // font color
                  int    x_dist,    // X scale coordinate
                  int    y_dist,    // Y scale coordinate
                  long   zorder)    // priority
  {
   if(ObjectCreate(chrt_id,lable_nm,OBJ_LABEL,0,0,0)) // creating object
     {
      ObjectSetString(chrt_id,lable_nm,OBJPROP_TEXT,rename);          // set name
      ObjectSetString(chrt_id,lable_nm,OBJPROP_FONT,font_bsc);        // set font
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_COLOR,font_clr);      // set font color
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_ANCHOR,anchor);       // set anchor point
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_CORNER,corner);       // set attachment corner
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_FONTSIZE,font_size);  // set font size
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_XDISTANCE,x_dist);    // set X coordinates
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_YDISTANCE,y_dist);    // set Y coordinates
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_SELECTABLE,false);     // unable to highlight the object, if FALSE
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_ZORDER,zorder);       // Higher/lower priority
      ObjectSetString(chrt_id,lable_nm,OBJPROP_TOOLTIP,"/n");         // no tooltip, if "/n"
     }
  }
//____________________________________________________________________
//+------------------------------------------------------------------+
//| CREATING_EDIT_OBJECT                                             |
//+------------------------------------------------------------------+
void Create_Edit(long   chrt_id,       // chart id
                 int    nmb_win,       // window (subwindow) index
                 string lable_nm,      // object name
                 string text,          // displayed text
                 long   corner,        // attachment corner
                 string font_bsc,      // font
                 int    font_size,     // font size
                 color  font_clr,      // font color
                 color  font_clr_brd,  // font color
                 int    xsize,         // width
                 int    ysize,         // height
                 int    x_dist,        // X scale coordinate
                 int    y_dist,        // Y scale coordinate
                 long   zorder,        // priority
                 color  clr)           // background color
  {
   if(ObjectCreate(chrt_id,lable_nm,OBJ_EDIT,nmb_win,0,0)) // creating object
     {
      ObjectSetString(chrt_id,lable_nm,OBJPROP_TEXT,text);                     // set name
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_CORNER,corner);                // set attachment corner
      ObjectSetString(chrt_id,lable_nm,OBJPROP_FONT,font_bsc);                 // set font
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_ALIGN,ALIGN_CENTER);         // center alignment
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_FONTSIZE,font_size);           // set font size
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_COLOR,font_clr);               // font color
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_BORDER_COLOR,font_clr_brd);    // background color
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_BGCOLOR,clr);                  // background color
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_XSIZE,xsize);                  // width
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_YSIZE,ysize);                  // height
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_XDISTANCE,x_dist);             // set X coordinate
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_YDISTANCE,y_dist);             // set Y coordinate
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_SELECTABLE,false);              // unable to highlight the object, if FALSE
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_ZORDER,zorder);                // Higher/lower priority
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_READONLY,true);                // Read only
      ObjectSetString(chrt_id,lable_nm,OBJPROP_TOOLTIP,"/n");                  // no tooltip if "/n"
     }
  }

在脚本操作完成或脚本被用户提前删除后,应删除脚本创建的所有图形对象。下面的函数将用于该目的:

//____________________________________________________________________
//+------------------------------------------------------------------+
//| DELETE_ALL_GRAPHICAL_OBJECTS_CREATED_BY_THE_SCRIPT               |
//+------------------------------------------------------------------+
void DelAllScriptObjects()
  {
// Receive the size of graphical object names array
   int sz_arr1=ArraySize(arr_nmobj);
//---
// Delete all objects
   for(int i=0; i<sz_arr1; i++)
     { DelObjbyName(arr_nmobj[i]);  }
  }
//____________________________________________________________________
//+------------------------------------------------------------------+
//| DELETE_OBJECTS_BY_NAME                                           |
//+------------------------------------------------------------------+
int DelObjbyName(string Name)
  {
   int nm_obj=0;
   bool res=false;
//---
   nm_obj=ObjectFind(ChartID(),Name);
//---
   if(nm_obj>=0)
     {
      res=ObjectDelete(ChartID(),Name);
      //---
      if(!res) { Print("Object deletion error: - "+ErrorDesc(Error())+""); return(false); }
     }
//---
   return(res);
  }

7. 应用程序的主体

脚本的主要函数是 OnStart()。这是用于调用所有其他函数执行的函数。程序的操作细节如下面的代码所示:

//____________________________________________________________________
//+------------------------------------------------------------------+
//| SCRIPT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> |
//+------------------------------------------------------------------+
void OnStart()
  {
// If user-defined parameters are incorrect,
// error message is shown and the program is closed
   if(ValidationParameters()) { return; }
//---
   max_bars=TerminalInfoInteger(TERMINAL_MAXBARS); // Receive available number of bars in the window
//---
   GetSymbolsToArray();           // Filling symbol array with names
   sz_arr_symb=ArraySize(symbols); // Receive symbol array size
//---
   SetSeparateForFormatDate();    // Set a separator for date format
//---
// Revise all symbols and write their data to file
   for(int s=0; s<=sz_arr_symb-1; s++)
     {
      copied_bars=0; // Reset copied bars variable to zero for writing
      pgs_pcnt=0.0;  // Reset variable of the symbol data writing progress 
      //---
      InfoTable(s); ChartRedraw();
      //---
      // Receive current symbol data
      int res=GetDataCurrentSymbol(s);
      //---
      if(res==0) { BC } // If zero, break the loop or start the next iteration
      //---
      if(res==2)        // Program operation interrupted by user
        {
         DelAllScriptObjects(); // Deleted objects created by the script from the chart
         //---
         Print("------/nUser deleted the script!"); break;
        }
      //---
      // Receive the path for creating the file and create directories for them
      // If the string is empty, break the loop or start the next iteration
      if((path=CheckCreateGetPath(s))=="") { BC }
      //---
      WriteDataToFile(s); // Write data to file
     }
//---
// Delete symbols from Market Watch window if necessary
   DelSymbolsFromMarketWatch();
//---
// Delete objects created by the script from the chart
   Sleep(1000); DelAllScriptObjects();
  }

我们来研究一下发生关键活动所在的函数。

交易品种数组 (symbols[]) 使用 GetSymbolsToArray() 函数中的交易品种名称填充。数组大小以及其内的交易品种数量取决于用户在 Mode Write Symbols (curr_mwatch) 参数中选择的变体。

如果用户需要的数据只能来自一个交易品种,数组大小等于 1。

ArrayResize(symbols,1); // Set the array size to be equal to 1
symbols[0]=_Symbol;     // Specify the current symbol's name

如果用户希望接收来自 Market Watch(市场报价)窗口的所有交易品种或所有可用交易品种的数据,数组大小将通过下述函数定义:

int SymbolsTotal(
   bool selected   // true – only MarketWatch symbols
);

为避免为两个几乎具有相同代码的变体创建两个代码块,我们将编写 MWatchOrAllList() 指针函数,其返回值为 true 或 false。此值定义获取交易品种列表的位置 – 仅来自 Market Watch(市场报价)窗口 (true) 或来自可用交易品种的常用列表 (false)。

//____________________________________________________________________
//+------------------------------------------------------------------+
//| POINTER_TO_MARKET_WATCH_WINDOW_OR_TO_COMMON_LIST                 |
//+------------------------------------------------------------------+
bool MWatchOrAllList()
  {
   if(curr_mwatch==MARKETWATCH) { return(true); }
   if(curr_mwatch==ALL_LIST_SYMBOLS) { return(false); }
//---
   return(true);
  }

从循环中获得交易品种的数量后,我们应遍历整个列表,将交易品种名称在每次迭代时放入数组,将数组大小增加 1。而交易品种名称则是由索引编号使用SymbolName() 函数获得。

int SymbolName(
   int pos,        // list index number
   bool selected   // true – only MarketWatch symbols
);

MWatchOrAllList() 指针函数同样用于 SymbolName() 函数以选择交易品种列表。GetSymbolsToArray() 函数的完整代码:

//____________________________________________________________________
//+------------------------------------------------------------------+
//| FILLING_SYMBOL_ARRAY_WITH_NAMES                                  |
//+------------------------------------------------------------------+
void GetSymbolsToArray()
  {
// If only the current symbol data is required
   if(curr_mwatch==CURRENT)
     { ArrayResize(symbols,1); symbols[0]=_Symbol; }
//---
// If data on all symbols  from Market Watch window or
// or the entire symbol list is required
   if(curr_mwatch==MARKETWATCH || curr_mwatch==ALL_LIST_SYMBOLS)
     {
      // Receive the number of symbols in Market Watch window
      cnt_symb=SymbolsTotal(MWatchOrAllList());
      //---
      for(int i=0; i<=cnt_symb-1; i++)
        {
         string nm_symb="";
         //---
         ArrayResize(symbols,i+1); // Increase the array size by one once again
         //---
         // Receive a name of a symbol from Market Watch window
         nm_symb=SymbolName(i,MWatchOrAllList());
         symbols[i]=nm_symb; // Put the symbol name into the array
        }
     }
  }

SetSeparateForFormatDate() 函数十分简单。它用于定义依据用户在 Format Date (format_date) 参数的下拉列表中做出的选择,将会在日期中使用的分隔符种类。

//____________________________________________________________________
//+------------------------------------------------------------------+
//| DEFINING_SEPARATOR_FOR_DATE_FORMAT                               |
//+------------------------------------------------------------------+
void SetSeparateForFormatDate()
  {
   switch(format_date)
     {
      case SEP_POINT1 : case SEP_POINT2 : sep="."; break; // Full point as a separator
      case SEP_SLASH1 : case SEP_SLASH2 : sep="/"; break; // Slash as a separator
     }
  }

含有各种检查的基本循环紧随其后。如果所有检查全部成功,数据写入文件。否则,循环中断,图表中的所有对象被删除且脚本被移除(只有一个交易品种的情形),或下一次迭代开始(有多个交易品种的情形)。symbols[] 数组的每个交易品种在循环中依次被调用。索引编号被发送至循环的每个函数。因此,所有函数中的准确序列被保留。

循环中当前交易品种的数据在循环主体最开始处于每次迭代时接收。GetDataCurrentSymbol() 函数用于此目的。我们来看看这个函数的运作。

在复制交易品种数据至 rate[] 数组前,数据可用性使用 CheckLoadHistory() 函数检查。此函数用作示例,由开发人员提供。其初始版本请参阅《MQL5 参考》。我只是对其稍事修改就用于此脚本中。“参考”中包含详细的说明(仔细研究它将是一个好主意),因此我不会在此提供我的版本,因为它几乎一样。此外,代码中附有详细的注释。

现在唯一要提及的是,CheckLoadHistory() 函数根据保存在日志中的来自 switch 操作符块的适当消息返回错误或成功执行代码。根据收到的代码,GetDataCurrentSymbol() 函数继续操作或返回其代码。

如果一切正常,将使用 CopyRates() 函数复制历史数据。数组大小保存在全局变量中。然后,执行从函数退出并返回代码 1。如果发生错误,函数在 switch 操作符中停止操作并返回代码 0 或 2。

//____________________________________________________________________
//+------------------------------------------------------------------+
//| RECEIVE_SYMBOL_DATA                                              |
//+------------------------------------------------------------------+
int GetDataCurrentSymbol(int s)
  {
   Print("------/n№"+IS(s+1)+" >>>"); // Save a symbol number in the journal
//---
// Check and download the necessary amount of requested data
   int res=CheckLoadHistory(s,_Period);
//---
   InfoTable(s); ChartRedraw(); // Update the data in the data table
//---
   switch(res)
     {
      case -1 : Print("Unknown symbol "+symbols[s]+" (code: -1)!");                        return(0);
      case -2 :
         Print("Number of requested bars exceeds the maximum number that can be displayed on a chart (code: -2)!.../n"
               "...The available amount of data will be used for writing.");                break;
      //---
      case -3 : Print("Execution interrupted by user (code: -3)!");                         return(2);
      case -4 : Print("Download failed (code: -4)!");                                      return(0);
      case  0 : Print("All symbol data downloaded (code: 0).");                             break;
      case  1 : Print("Time series data is sufficient (code: 1).");                          break;
      case  2 : Print("Time series created based on existing terminal data (code: 2).");      break;
      //---
      default : Print("Execution result is not defined!");
     }
//---
// Copy data to the array
   if(CopyRates(symbols[s],_Period,check_start_date,end_date,rates)<=0)
     { Print("Error when copying symbol data "+symbols[s]+" - ",ErrorDesc(Error())+""); return(0); }
   else
     {
      copied_bars=ArraySize(rates); // Receive array size
      //---
      Print("Symbol: ",symbols[s],"; Timeframe: ",gStrTF(_Period),"; Copied bars: ",copied_bars);
     }
//---
   return(1); // Return 1, if all is well
  }

然后,程序再次位于 OnStart() 函数的主循环的主体中。代码由 res 局部变量分配,检查根据代码的值执行。0 值表示一个错误。这意味着循环中当前交易品种的数据无法写入。错误说明被保存在日志中,并决定循环是否被中断(中断)或下一次迭代是否应当开始(继续)。

if(res==0) { BC } // If zero, the loop is interrupted or the next iteration starts

上述代码行表明,此选择通过一些神秘的 BC 字符执行。这是一个宏扩展。更多详情,请参阅《MQL5 参考》。在此唯一需要提及的是,如上例中 (BC) 所示,整个表达式(输入为一行)可粘贴在短入口中。在一些情形中,此方法甚至比函数更方便和简洁。在目前的情形中,其如下所示:

// Macro expansion with further action selection
#define BC if(curr_mwatch==CURRENT) { break; } if(curr_mwatch==MARKETWATCH || curr_mwatch==ALL_LIST_SYMBOLS) { continue; }

下面是在脚本中使用的宏扩展的其他示例:

#define nmf __FUNCTION__+": " // Macro expansion of the function name before sending the message to the journal
//---
#define TRM_DP TerminalInfoString(TERMINAL_DATA_PATH) // Folder for storing the terminal data

如果 GetDataCurrentSymbol() 返回 2,程序被用户删除。MQL5 具有 IsStopped() 函数来识别此事件。这个函数十分有用,可用于在循环中正确及时地停止程序操作。如果函数返回 true,则在程序被强行删除前约有三秒的时间执行所有操作。在我们的示例中,所有图形对象均被删除,消息发送至日志:

if(res==2) // Program execution interrupted by user
   {
    DelAllScriptObjects(); // Delete all objects created by the script from the chart
    //---
    Print("------/nUser deleted the script!"); break;
   }

8. 创建文件夹和归档数据

CheckCreateGetPath() 函数检查根数据文件夹是否存在。我们称其为 DATA_OHLC 并将其置于 C:/Metatrader 5/MQL5/Files。它将包含具有交易品种名称的文件夹。用于写入数据的文件将在此创建。

如果根文件夹或循环中当前交易品种的文件夹不存在,函数将创建文件夹。如果一切正常,函数返回包含创建文件路径的字符串。如果发生错误,或用户试图从图表删除程序,函数返回空字符串。

下面的代码附上了详细的注释,易于理解:

//____________________________________________________________________
//+------------------------------------------------------------------+
//| CHECK_DIRECTORY_AND_CREATE_NECESSARY_DATA_FOLDERS                |
//+------------------------------------------------------------------+
string CheckCreateGetPath(int s)
  {
   int i=1;
   long search=-1;
   string ffname="",lpath="";
   string file="*.csv",folder="*";
   string
   root="DATA_OHLC//",         // Root data folder
   fSmb=symbols[s]+"//",     // Symbol name
   fTF=gStrTF(_Period)+"//"; // Symbol time frame
//---
   bool flgROOT=false,flgSYMBOL=false;
//---
//+------------------------------------------------------------------+
//| SEARCHING_FOR_DATA_OHLC_ROOT_FOLDER                              |
//+------------------------------------------------------------------+
   lpath=folder;
   search=FileFindFirst(lpath,ffname); // Set search handle in Metatrader 5/MQL5/Files
//---
   Print("Directory: ",TRM_DP+"//MQL5//Files//");
//---
// Set the flag if the first folder is a root one
   if(ffname==root)
     { flgROOT=true; Print("Root folder "+root+" present"); }
//---
   if(search!=INVALID_HANDLE) // If search handle received
     {
      if(!flgROOT) // If the first folder is not a root one
        {
         // Sort out all files searching for the root folder
         while(FileFindNext(search,ffname))
           {
            if(IsStopped()) // Execution interrupted by user
              {
               // Delete objects created by the script from the chart
               DelAllScriptObjects();
               //---
               Print("------/nUser deleted the script!"); return("");
              }
            //---
            if(ffname==root) // Set the flag if found
              { flgROOT=true; Print("Root folder "+root+" present"); break; }
           }
        }
      //---
      FileFindClose(search); search=-1; // Close root folder search handle
     }
   else { Print("Error when receiving the search handle or directory "+TRM_DP+" is empty: ",ErrorDesc(Error())); }
//---
//+------------------------------------------------------------------+
//| SEARCHING_SYMBOL_FOLDER                                          |
//+------------------------------------------------------------------+
   lpath=root+folder;
//---
// Set search handle in the root folder ../Files/DATA OHLC/
   search=FileFindFirst(lpath,ffname);
//---
// Set the flag if the first folder of the current symbol
   if(ffname==fSmb) { flgSYMBOL=true; Print("Symbol folder "+fSmb+" present"); }
//---
   if(search!=INVALID_HANDLE) // If search handle is received
     {
      if(!flgSYMBOL) // If the first folder is not of the current symbol
        {
         // Sort out all the files in the root folder searching the symbol folder
         while(FileFindNext(search,ffname))
           {
            if(IsStopped()) // Execution interrupted by user
              {
               // Delete objects created by the script from the chart
               DelAllScriptObjects();
               //---
               Print("------/nUser deleted the script!"); return("");
              }
            //---
            if(ffname==fSmb) // Set the flag if found
              { flgSYMBOL=true; Print("Symbol folder"+fSmb+" present"); break; }
           }
        }
      //---
      FileFindClose(search); search=-1; // Close symbol folder search handle
     }
   else { Print("Error when receiving search handle or the directory "+path+" is empty"); }
//---
//+------------------------------------------------------------------+
//| CREATE_NECESSARY_DIRECTORIES_ACCORDING_TO_CHECK_RESULTS          |
//+------------------------------------------------------------------+
   if(!flgROOT) // If there is no DATA_OHLC... root folder
     {
      if(FolderCreate("DATA_OHLC")) // ...we should create it
        { Print("../DATA_OHLC// root folder created"); }
      else
        { Print("Error when creating DATA_OHLC: root folder",ErrorDesc(Error())); return(""); }
     }
//---
   if(!flgSYMBOL) // If there is no folder of the symbol, the values of which should be received...
     {
      if(FolderCreate(root+symbols[s])) // ...we should create it
        {
         Print("../DATA_OHLC//" symbol folder created+fSmb+"");
         //---
         return(root+symbols[s]+"//"); // Return the path for creating the file for writing
        }
      else
        { Print("Error when creating ../DATA_OHLC// symbol folder"+fSmb+"/: ",ErrorDesc(Error())); return(""); }
     }
//---
   if(flgROOT && flgSYMBOL)
     {
      return(root+symbols[s]+"//"); // Return the path for creating the file for writing
     }
//---
   return("");
  }

如果 CheckCreateGetPath() 函数返回空行,循环中断,或下一次迭代使用我们业已熟悉的宏扩展 (BC) 启动:

// Receive the path for creating a file and create directories for them
// If the line is empty, the loop is interrupted or the next iteration starts
if((path=CheckCreateGetPath(s))=="") { BC }

如果到达这个阶段,这意味着数据已成功复制,且路径字符串变量包含创建写入循环中当前交易品种的数据的文件的路径。

创建 WriteDataToFile() 函数,以将数据写入文件。在函数的开始处,[路径]+[文件名] 生成。文件名由交易品种名称和当前时间表组成。例如,EURUSD_H1.csv。如果具有这种名称的文件已经存在,文件打开等待写入。之前写入的数据将被删除。新的数据将被写入。如果文件成功创建/打开,FileOpen() 函数返回用于访问文件的句柄。

检查句柄。如果句柄存在,写入标题行。取决于用户选择的标题,相应的行将被写入。然后,写入历史数据的主循环开始。

在写入下一行之前,它应转换为用户指定的格式。为此,我们应接收柱建立时间,并使用 StringSubstr() 函数通过变量分别排序日、月、年和时间。接下来,我们应定义日期和时间应位于同一列还是不同的列,这取决于用户指定的格式。然后,使用 StringConcatenate() 函数将所有部分连结成一行。在写入所有行后,文件由 FileClose() 函数关闭。

WriteDataToFile() 函数的完整代码显示如下:

//____________________________________________________________________
//+------------------------------------------------------------------+
//| WRITE_DATA_TO_FILE                                               |
//+------------------------------------------------------------------+
void WriteDataToFile(int s)
  {
// Number of decimal places in the symbol price
   int dgt=(int)SymbolInfoInteger(symbols[s],SYMBOL_DIGITS);
//---
   string nm_fl=path+symbols[s]+"_"+gStrTF(_Period)+".csv"; // File name
//---
// Receive file handle for writing
   hFl=FileOpen(nm_fl,FILE_WRITE|FILE_CSV|FILE_ANSI,',');
//---
   if(hFl>0) // If the handle is received
     {
      // Write the headers
      if(format_headers==NSDT_5)
        { FileWrite(hFl,"/"Date/" ""/"Time/" ""/"Open/" ""/"High/" ""/"Low/" ""/"Close/" ""/"Volume/""); }
      //---
      if(format_headers==NSDT_6)
        { FileWrite(hFl,"Date","Open","High","Low","Close","Volume"); }
      //---
      // Write the data
      for(int i=0; i<=copied_bars-1; i++)
        {
         if(IsStopped()) // If program execution interrupted by a user
           {
            DelAllScriptObjects(); // Delete objects created by the script from the chart
            //---
            Print("------/nUser deleted the script!"); break;
           }
         //---
         sdt=TSdm(rates[i].time); // Bar open time
         //---
         // Divide the date by year, month and time
         yyyy=StringSubstr(sdt,0,4);
         mm=StringSubstr(sdt,5,2);
         dd=StringSubstr(sdt,8,2);
         tm=StringSubstr(sdt,11);
         //---
         string sep_dt_tm=""; // Separator of Date and Time columns
         //---
         // Join the data with the separator in the necessary order
         if(format_date==SEP_POINT1 || format_date==SEP_SLASH1) { sep_dt_tm=" "; }
         if(format_date==SEP_POINT2 || format_date==SEP_SLASH2) { sep_dt_tm=","; }
         //---
         // Join everything in one line
         StringConcatenate(sdt,dd,sep,mm,sep,yyyy,sep_dt_tm,tm);
         //---
         FileWrite(hFl,
                   sdt,// Date-time
                   DS_dgt(rates[i].open,dgt),      // Open price
                   DS_dgt(rates[i].high,dgt),      // High price
                   DS_dgt(rates[i].low,dgt),       // Low price
                   DS_dgt(rates[i].close,dgt),     // Close price
                   IS((int)rates[i].tick_volume)); // Tick volume price
         //---
         // Update writing progress value for the current symbol
         pgs_pcnt=((double)(i+1)/copied_bars)*100;
         //---
         // Update data in the table
         InfoTable(s); if(show_progress) { ChartRedraw(); }
        }
      //---
      FileClose(hFl); // Close the file
     }
   else { Print("Error when creating/opening file!"); }
  }

这是 OnStart() 函数的基本循环中的最后一个函数。如果不是最后一个交易品种,则为下一个交易品种重复所有步骤。否则,循环中断。如果用户在脚本参数中指定清除 Market Watch(市场报价)窗口中的交易品种列表,则当前带不活动图表的交易品种将被 DelSymbolsFromMarketWatch() 函数删除。之后,脚本创建的所有图形对象均被删除,且程序停止。数据使用就绪。

有关如何将数据下载至 NeuroShell DayTrader Professional 的详细信息,请参阅我的博客。下面是显示脚本操作的一段视频:

总结

不管我在交易策略的开发中使用何种程序,总是遇到这样那样的瓶颈,限制我的想法进一步发展。最后,我认识到了编程在其中起到的至关重要的作用。MQL5 是那些真正想要成功的用户的最佳解决方案。然而,在探寻新的思路时,用于数据分析和交易策略开发的其他程序同样大有裨益。如果我仅仅局限于一种工具,将要花费多得多的时间。

祝您好运!

本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/502

附加的文件 |

writedatatofile.mq5
(68.96 KB)

 

 


MyFxtop迈投(www.myfxtop.com)-靠谱的外汇跟单社区,免费跟随高手做交易!

 

免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。

風險提示

MyFxtops邁投所列信息僅供參考,不構成投資建議,也不代表任何形式的推薦或者誘導行為。MyFxtops邁投非外匯經紀商,不接觸妳的任何資金。 MYFXTOPS不保證客戶盈利,不承擔任何責任。從事外彙和差價合約等金融產品的槓桿交易具有高風險,損失有可能超過本金,請量力而行,入市前需充分了解潛在的風險。過去的交易成績並不代表以後的交易成績。依據各地區法律法規,MyFxtops邁投不向中國大陸、美國、加拿大、朝鮮居民提供服務。

邁投公眾號

聯繫我們

客服QQ:981617007
Email: service@myfxtop.com

MyFxtops 邁投