MQL5 中的策略可视化:在标准图表中展示优化结果·进阶篇
📘

MQL5 中的策略可视化:在标准图表中展示优化结果·进阶篇

第 2/3 篇

◍ 表格单元类的取数与绘制方法

在 MT5 自定义表格控件里,先把单元格的坐标与尺寸暴露成只读接口,后续布局才不会乱。下面这组 getter 直接回传成员变量:X()/Y() 给左上角坐标,Width()/Height() 给宽高,Text() 回传单元格内字符串,全部带 const 修饰避免误写。 真正把字画到画布上的是 TextOut()。它接收 CCanvas 指针、偏移量 x_shift/y_shift,以及可选的背景色、字体标志与对齐方式;若 canvas 为空则直接 return,防止空指针崩 EA。 方法内部先记住原有 FontFlags,再把背景色转成 ARGB——clrNONE 时默认 0x00FFFFFF 全透,否则走 ColorToARGB()。随后用 FillRectangle 以 (m_x+1, m_y+1) 到 (m_x+m_w-1, m_y+m_h-1) 的四至擦掉旧标签,再设字体标志写新文本。 开 MT5 建个 CCanvas 派生面板,把这段贴进单元格结构体,就能验证:背景传 clrNONE 时单元透明,传 clrBlack 时整格被填黑再出字。外汇与贵金属图表叠加自绘控件波动剧烈,实盘前请在策略测试器跑够样本。

MQL5 / C++
  class="type">int               X(class="type">void)              const { class="kw">return this.m_x;    }
  class="type">int               Y(class="type">void)              const { class="kw">return this.m_y;    }
  class="type">int               Width(class="type">void)          const { class="kw">return this.m_w;    }
  class="type">int               Height(class="type">void)         const { class="kw">return this.m_h;    }
  class="type">class="kw">string            Text(class="type">void)           const { class="kw">return this.m_text;  }

class=class="str">"cmt">//--- Prints the text written in cell properties to the canvas, the pointer to which is passed to the method
  class="type">void              TextOut(CCanvas *canvas, const class="type">int x_shift, const class="type">int y_shift, const class="type">color bg_color=clrNONE, const class="type">uint flags=class="num">0, const class="type">uint alignment=class="num">0)
    {
                      if(canvas==NULL)
                         class="kw">return;
                      class=class="str">"cmt">//--- Remember current font flags
                      class="type">uint flags_prev=canvas.FontFlagsGet();
                      class=class="str">"cmt">//--- Set background class="type">color
                      class="type">uint clr=(bg_color==clrNONE ? 0x00FFFFFF : ::ColorToARGB(bg_color));
                      class=class="str">"cmt">//--- Fill in the cell with the set background class="type">color (erase the previous label)
                      canvas.FillRectangle(this.m_x+class="num">1, this.m_y+class="num">1, this.m_x+this.m_w-class="num">1, this.m_y+this.m_h-class="num">1, clr);
                      class=class="str">"cmt">//--- Set font flags
                      canvas.FontFlagsSet(flags);
                      class=class="str">"cmt">//--- Print text in the cell

表格控件的颜色与透明度拆解

在 MT5 自定义表格控件里,颜色不是直接写死的对象,而是靠位运算从 color 类型里抠出 R/G/B 分量。GetR 用 clr&0xff 取最低字节,GetG 右移 8 位再 &0xff,GetB 右移 16 位,三者都返回 0~255 的 double,方便后续做渐变或高亮计算。 CTableDataControl 类把前景色 m_fore_color 和透明度 m_alpha 分开存,SetAlpha 只改 uchar 成员不改颜色本体,渲染时再由 Canvas 层合成 ARGB。这样你可以在不重画单元格的前提下,单独调透明度做 hover 或选中态。 TextOut 绘制前用 ColorToARGB 把 m_fore_color 转成带通道的整数,画完立刻 FontFlagsSet 还原之前记忆的字体标志并 Update(false) 避免画面闪烁。外汇与贵金属图表叠加这类自绘控件时,注意高 CPU 占用风险,建议先在 EURUSD 1 分钟周期挂测试实例验证帧率。

MQL5 / C++
canvas.TextOut(this.m_x+x_shift, this.m_y+y_shift, this.m_text, ::ColorToARGB(this.m_fore_color), alignment);
class=class="str">"cmt">//--- Return previously memorized font flags and update canvas
canvas.FontFlagsSet(flags_prev);
canvas.Update(false);
}

class=class="str">"cmt">//--- A class="kw">virtual method for comparing two objects
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Table control class                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CTableDataControl : class="kw">public CTableData
  {
class="kw">protected:
   class="type">uchar            m_alpha;
   class="type">color            m_fore_color;

class=class="str">"cmt">//--- Converts RGB to class="type">color
   class="type">color            RGBToColor(const class="type">class="kw">double r,const class="type">class="kw">double g,const class="type">class="kw">double b) const;
class=class="str">"cmt">//--- Writes RGB component values to variables
   class="type">void             ColorToRGB(const class="type">color clr,class="type">class="kw">double &r,class="type">class="kw">double &g,class="type">class="kw">double &b);
class=class="str">"cmt">//--- Returns class="type">color component(class="num">1) Red, (class="num">2) Green, (class="num">3) Blue
   class="type">class="kw">double           GetR(const class="type">color clr)      { class="kw">return clr&0xff;                }
   class="type">class="kw">double           GetG(const class="type">color clr)      { class="kw">return(clr>>class="num">8)&0xff;            }
   class="type">class="kw">double           GetB(const class="type">color clr)      { class="kw">return(clr>>class="num">16)&0xff;           }
class=class="str">"cmt">//--- Returns a new class="type">color
   class="type">color            NewColor(class="type">color base_color, class="type">int shift_red, class="type">int shift_green, class="type">int shift_blue);

class="kw">public:
class=class="str">"cmt">//--- Returns a pointer to itself
   CTableDataControl*Get(class="type">void)                 { class="kw">return &this;                   }

class=class="str">"cmt">//--- (class="num">1) Sets, (class="num">2) returns transparency
   class="type">void             SetAlpha(const class="type">uchar alpha){ this.m_alpha=alpha;             }

「表格控件的网格与绘制接口」

在 MT5 自定义指标里做可视化表格,CTableDataControl 这个类把底层绘制逻辑封装得很干净。它先暴露了 Alpha() 这个只读方法,直接返回成员 m_alpha,默认构造时 m_alpha 被置为 255,也就是完全不透明。 网格部分给了两个入口:DrawGrid 让你手动指定起始坐标 x、y 以及 header_h、rows、columns、row_size、col_size,适合嵌在已有布局里;DrawGridAutoFill 只吃 border、header_h、rows、columns 四个参数,按画布边界自动算单元格尺寸,快速铺满更省事。两个方法都支持 line_color 默认 clrNONE 和 alternating_color 默认 true,也就是隔行换色。 文字与色块分别由 DrawText 和 DrawRectangleFill 负责。DrawText 的 align 默认 0、width/height 默认 WRONG_VALUE,意味着不强制限定文本框;DrawRectangleFill 则必须传 alpha 通道,和前面 m_alpha 形成呼应——你可以让背景块半透明而不影响文字清晰度。 构造函数有两种:带 id 的版本会顺带把 m_fore_color 初始化成 clrDimGray,无参版本只设 m_alpha=255。析构为空,说明资源回收交给 CCanvas 外层管理。开 MT5 把这段声明贴进 EA 头文件,改一行 alternating_color=false 就能立刻看出隔行着色的差异。

MQL5 / C++
class="type">uchar Alpha(class="type">void) const { class="kw">return this.m_alpha; }

class=class="str">"cmt">//--- Draws(class="num">1) a background grid, (class="num">2) with automatic cell size
class="type">void DrawGrid(CCanvas *canvas,const class="type">int x,const class="type">int y,const class="type">uint header_h,const class="type">uint rows,const class="type">uint columns,const class="type">uint row_size,const class="type">uint col_size,
              const class="type">color line_color=clrNONE,class="type">bool alternating_color=true);
class="type">void DrawGridAutoFill(CCanvas *canvas,const class="type">uint border,const class="type">uint header_h,const class="type">uint rows,const class="type">uint columns,const class="type">color line_color=clrNONE,class="type">bool alternating_color=true);

class=class="str">"cmt">//--- Prints(class="num">1) a text message, (class="num">2) a filled rectangle at the specified coordinates
class="type">void DrawText(CCanvas *canvas,const class="type">class="kw">string text,const class="type">int x,const class="type">int y,const class="type">color clr=clrNONE,const class="type">uint align=class="num">0,const class="type">int width=WRONG_VALUE,const class="type">int height=WRONG_VALUE);
class="type">void DrawRectangleFill(CCanvas *canvas,const class="type">int x,const class="type">int y,const class="type">int width,const class="type">int height,const class="type">color clr,const class="type">uchar alpha);

class=class="str">"cmt">//--- Constructors/Destructor
CTableDataControl(const class="type">uint id) : CTableData(id), m_fore_color(clrDimGray), m_alpha(class="num">255) {}
CTableDataControl(class="type">void) : m_alpha(class="num">255) {}
~CTableDataControl(class="type">void) {}
};

◍ 表格边框与交替行底色的绘制逻辑

在 MT5 自定义面板里画一张表,第一步不是填数据,而是把坐标和网格线定下来。这段构造函数把行高和列宽做了硬性下限保护:小于 2 像素一律拉回 2,避免后续 FillRectangle 因区域过窄直接不渲染。 表体左上角 X1 直接取传入的 x,X2 则按「列数 × 列宽」向右推;Y1 从表头高度 header_h 下方起算,Y2 再按「行数 × 行高」向下推。注意 SetCoords 调用时统一减了 header_h,说明内部坐标系统把表头区域单独剥离。 网格线颜色默认是 C'200,200,200'(浅灰),也可由 line_color 参数覆盖。若传 clrNONE 就用这个灰。画线前用 ColorToARGB 把颜色和透明度 m_alpha 打包,这是 Canvas 类绘图的标准做法。 alternating_color 开启后,偶数行(i%2==0)会先调 NewColor(clr,45,45,45) 把底色提亮,再用 FillRectangle 填一个比单元格内缩 1 像素的矩形。实测这个 +45 的 RGB 偏移在深色背景上能拉开约 18% 的亮度差,肉眼可辨但不刺眼。 每行创建 CTableRow 对象并 AddRow 挂到链表;失败就 delete 防内存泄漏,同时 SetY(row_y-header_h) 让行对象记录自己在表体内的相对偏移,后续命中测试或重绘都靠它。

MQL5 / C++
const class="type">color line_color=clrNONE,class="type">bool alternating_color=true)
  {
class=class="str">"cmt">//--- Clear all lists of the tabular data object(class="kw">delete cells from rows and all rows)
   this.Clear();
class=class="str">"cmt">//--- Row height cannot be less than class="num">2
   class="type">int row_h=class="type">int(row_size<class="num">2 ? class="num">2 : row_size);
class=class="str">"cmt">//--- Row width cannot be less than class="num">2
   class="type">int col_w=class="type">int(col_size<class="num">2 ? class="num">2 : col_size);

class=class="str">"cmt">//--- Left coordinate(X1) of the table
   class="type">int x1=x;
class=class="str">"cmt">//--- Calculate X2 coordinate(on the right) depending on the number of columns and their width
   class="type">int x2=x1+col_w*class="type">int(columns>class="num">0 ? columns : class="num">1);
class=class="str">"cmt">//--- Y1 coordinate is located under the panel header area
   class="type">int y1=(class="type">int)header_h+y;

class=class="str">"cmt">//--- Calculate Y2 coordinate(from below) depending on the number of rows and their height
   class="type">int y2=y1+row_h*class="type">int(rows>class="num">0 ? rows : class="num">1);
class=class="str">"cmt">//--- Set coordinates of the table
   this.SetCoords(x1,y1-header_h,x2,y2-header_h);

class=class="str">"cmt">//--- Get class="type">color of grid lines of table, either by class="kw">default or passed to method
   class="type">color clr=(line_color==clrNONE ? C&class="macro">#x27;class="num">200,class="num">200,class="num">200&class="macro">#x27; : line_color);
class=class="str">"cmt">//--- Draw table border
   canvas.Rectangle(x1,y1,x2,y2,::ColorToARGB(clr,this.m_alpha));
class=class="str">"cmt">//--- In a loop by table rows
   for(class="type">int i=class="num">0;i<(class="type">int)rows;i++)
     {
      class=class="str">"cmt">//--- calculate Y coordinate of next horizontal grid line(Y coordinate of next row of table)
      class="type">int row_y=y1+row_h*i;
      class=class="str">"cmt">//--- if the flag of "alternating" row colors is passed and the row is even
      if(alternating_color && i%class="num">2==class="num">0)
        {
         class=class="str">"cmt">//--- lighten background class="type">color of table and draw a background rectangle
         class="type">color new_color=this.NewColor(clr,class="num">45,class="num">45,class="num">45);
         canvas.FillRectangle(x1+class="num">1,row_y+class="num">1,x2-class="num">1,row_y+row_h-class="num">1,::ColorToARGB(new_color,this.m_alpha));
        }
      class=class="str">"cmt">//--- Draw horizontal grid line of table
      canvas.Line(x1,row_y,x2,row_y,::ColorToARGB(clr,this.m_alpha));

      class=class="str">"cmt">//--- Create new table row object
      CTableRow *row_obj=new CTableRow(i);
      if(row_obj==NULL)
        {
         ::PrintFormat("%s: Failed to create table row object at index %lu",(class="type">class="kw">string)__FUNCTION__,i);
         class="kw">continue;
        }
      class=class="str">"cmt">//--- Add it to list of rows of tabular data object
      class=class="str">"cmt">//--- (if failed to add object, class="kw">delete created object)
      if(!this.AddRow(row_obj))
       class="kw">delete row_obj;
      class=class="str">"cmt">//--- Set Y coordinate in created row object, given offset from panel header
      row_obj.SetY(row_y-header_h);
     }

class=class="str">"cmt">//--- In loop by table columns

用循环给表格铺网格并挂单元格

在 MT5 的 Canvas 面板里画表格,核心是用两层 for 循环:外层按列数推进 X 坐标,内层按行数把单元格对象挂到每一行。下面这段逻辑里,列宽 col_w 乘以列序号 i 得到 col_x,一旦 col_x 超过画布右边界减 2 像素就 break,避免越界绘制。

MQL5 / C++
for(class="type">int i=class="num">0;i<(class="type">int)columns;i++)
  {
  class=class="str">"cmt">//--- calculate X coordinate of next vertical grid line(X coordinate of next table column)
  class="type">int col_x=x1+col_w*i;
  class=class="str">"cmt">//--- If grid line has gone beyond panel, break cycle
  if(x1==class="num">1 && col_x>=x1+canvas.Width()-class="num">2)
    break;
  class=class="str">"cmt">//--- Draw vertical grid line of table
  canvas.Line(col_x,y1,col_x,y2,::ColorToARGB(clr,this.m_alpha));
  class=class="str">"cmt">//--- Get the number of created rows from tabular data object
  class="type">int total=this.RowsTotal();
  class=class="str">"cmt">//--- In loop through table rows
  for(class="type">int j=class="num">0;j<total;j++)
    {
    class=class="str">"cmt">//--- get next row
    CTableRow *row=this.GetRow(j);
    if(row==NULL)
      class="kw">continue;
    class=class="str">"cmt">//--- Create new table cell
    CTableCell *cell=new CTableCell(row.Row(),i);
    if(cell==NULL)
      {
      ::PrintFormat("%s: Failed to create table cell object at index %lu",(class="type">class="kw">string)__FUNCTION__,i);
      class="kw">continue;
      }
    class=class="str">"cmt">//--- Add created cell to row
    class=class="str">"cmt">//--- (if failed to add object, class="kw">delete created object)
    if(!row.AddCell(cell))
      {
      class="kw">delete cell;
      class="kw">continue;
      }
    class=class="str">"cmt">//--- In created cell object, set its X coordinate and Y coordinate from row object.
    cell.SetXY(col_x,row.Y());
    cell.SetSize(col_w, row_h);
    }
  }
class=class="str">"cmt">//--- Update canvas without redrawing chart
  canvas.Update(false);
}
逐行看:i 从 0 跑到 columns-1,col_x 是每列竖线的 X 位置;x1==1 且 col_x 触到右边界时直接退出循环。canvas.Line 用 ColorToARGB 把颜色和透明度 m_alpha 合成为 32 位色值画竖线。内层先取总行数 total,j 遍历每一行,GetRow 拿不到就 continue;new CTableCell 建单元格,失败打印函数名和索引后跳过;AddCell 挂不上就 delete 防内存泄漏;最后 SetXY 把单元格对齐到列 X 和行 Y,SetSize 用列宽和行高定型。 收尾的 canvas.Update(false) 只刷新画布缓冲、不重绘整个图表,在高频刷新面板时能少掉帧。DrawGridAutoFill 函数入口先算 x1=x2=y1=y2 四个边界:x1=border,x2=画布宽-border-1,y1=header_h+border-1,y2=画布高-border-1,再 SetCoords 把表格矩形钉死。外汇和贵金属图表叠加这类自定义面板属高风险操作,参数算错可能让 EA 在真实账户上绘制异常。

MQL5 / C++
for(class="type">int i=class="num">0;i<(class="type">int)columns;i++)
  {
  class=class="str">"cmt">//--- calculate X coordinate of next vertical grid line(X coordinate of next table column)
  class="type">int col_x=x1+col_w*i;
  class=class="str">"cmt">//--- If grid line has gone beyond panel, break cycle
  if(x1==class="num">1 && col_x>=x1+canvas.Width()-class="num">2)
    break;
  class=class="str">"cmt">//--- Draw vertical grid line of table
  canvas.Line(col_x,y1,col_x,y2,::ColorToARGB(clr,this.m_alpha));
  class=class="str">"cmt">//--- Get the number of created rows from tabular data object
  class="type">int total=this.RowsTotal();
  class=class="str">"cmt">//--- In loop through table rows
  for(class="type">int j=class="num">0;j<total;j++)
    {
    class=class="str">"cmt">//--- get next row
    CTableRow *row=this.GetRow(j);
    if(row==NULL)
      class="kw">continue;
    class=class="str">"cmt">//--- Create new table cell
    CTableCell *cell=new CTableCell(row.Row(),i);
    if(cell==NULL)
      {
      ::PrintFormat("%s: Failed to create table cell object at index %lu",(class="type">class="kw">string)__FUNCTION__,i);
      class="kw">continue;
      }
    class=class="str">"cmt">//--- Add created cell to row
    class=class="str">"cmt">//--- (if failed to add object, class="kw">delete created object)
    if(!row.AddCell(cell))
      {
      class="kw">delete cell;
      class="kw">continue;
      }
    class=class="str">"cmt">//--- In created cell object, set its X coordinate and Y coordinate from row object.
    cell.SetXY(col_x,row.Y());
    cell.SetSize(col_w, row_h);
    }
  }
class=class="str">"cmt">//--- Update canvas without redrawing chart
  canvas.Update(false);

「网格坐标与交替底色怎么落进画布」

这段逻辑干的事很直白:把表格的行列几何关系算清楚,再一笔笔画到 canvas 上。先定网格线颜色,没传 line_color 就用灰阶 C'200,200,200',传了就用传进来的;border 大于 0 才单独画表格外框,否则直接拿面板边框当表框用。 行高按总高 greed_h 除以 rows 四舍五入,列宽同理用 greed_w 除以 columns。假设面板内表格区高 240 像素、画 6 行,round(240/6)=40,每行占用 40 像素——调 rows 参数会直接改变这个整除结果,行密了字就容易挤。 交替底色只在 alternating_color 开且行号为偶数时触发:用 NewColor(clr,45,45,45) 把基线提亮,再 FillRectangle 填进单元格内部(四边各缩 1 像素避开网格线)。偶数行提亮、奇数行留底,肉眼扫数据行时不容易看错排。 每行 new 一个 CTableRow 挂进列表,失败就 delete 并 PrintFormat 报错继续循环;row_obj.SetY(row_y-header_h) 把行对象 Y 坐标扣掉表头高,保证后面填文本时不会和标题区重叠。列循环里 i>0 才画竖线,第一根竖线复用边框,省一次绘制调用。

MQL5 / C++
class=class="str">"cmt">//--- Get class="type">color of grid lines of table, either by class="kw">default or passed to method
  class="type">color clr=(line_color==clrNONE ? C&class="macro">#x27;class="num">200,class="num">200,class="num">200&class="macro">#x27; : line_color);
class=class="str">"cmt">//--- If indentation from edge of panel is greater than zero, draw border of table
class=class="str">"cmt">//--- otherwise, panel border acts as table border
  if(border>class="num">0)
    canvas.Rectangle(x1,y1,x2,y2,::ColorToARGB(clr,this.m_alpha));
class=class="str">"cmt">//--- Height of entire table grid
  class="type">int greed_h=y2-y1;
class=class="str">"cmt">//--- Calculate row height depending on table height and number of rows
  class="type">int row_h=(class="type">int)::round((class="type">class="kw">double)greed_h/(class="type">class="kw">double)rows);
class=class="str">"cmt">//--- In loop through number of rows
  for(class="type">int i=class="num">0;i<(class="type">int)rows;i++)
    {
    class=class="str">"cmt">//--- calculate Y coordinate of next horizontal grid line(Y coordinate of next row of table)
    class="type">int row_y=y1+row_h*i;
    class=class="str">"cmt">//--- if the flag of "alternating" row colors is passed and the row is even
    if(alternating_color && i%class="num">2==class="num">0)
      {
      class=class="str">"cmt">//--- lighten background class="type">color of table and draw a background rectangle
      class="type">color new_color=this.NewColor(clr,class="num">45,class="num">45,class="num">45);
      canvas.FillRectangle(x1+class="num">1,row_y+class="num">1,x2-class="num">1,row_y+row_h-class="num">1,::ColorToARGB(new_color,this.m_alpha));
      }
    class=class="str">"cmt">//--- Draw horizontal grid line of table
    canvas.Line(x1,row_y,x2,row_y,::ColorToARGB(clr,this.m_alpha));
    
    class=class="str">"cmt">//--- Create new table row object
    CTableRow *row_obj=new CTableRow(i);
    if(row_obj==NULL)
      {
      ::PrintFormat("%s: Failed to create table row object at index %lu",(class="type">class="kw">string)__FUNCTION__,i);
      class="kw">continue;
      }
    class=class="str">"cmt">//--- Add it to list of rows of tabular data object
    class=class="str">"cmt">//--- (if failed to add object, class="kw">delete created object)
    if(!this.AddRow(row_obj))
      class="kw">delete row_obj;
    class=class="str">"cmt">//--- Set Y coordinate in created row object, given offset from panel header
    row_obj.SetY(row_y-header_h);
    }
    
class=class="str">"cmt">//--- Width of table grid
  class="type">int greed_w=x2-x1;
class=class="str">"cmt">//--- Calculate column width depending on table width and number of columns
  class="type">int col_w=(class="type">int)::round((class="type">class="kw">double)greed_w/(class="type">class="kw">double)columns);
class=class="str">"cmt">//--- In loop by table columns
  for(class="type">int i=class="num">0;i<(class="type">int)columns;i++)
    {
    class=class="str">"cmt">//--- calculate X coordinate of next vertical grid line(X coordinate of next table column)
    class="type">int col_x=x1+col_w*i;
    class=class="str">"cmt">//--- If this is not the very first vertical line, draw it
    class=class="str">"cmt">//--- (the first vertical line is either table border or panel border)
    if(i>class="num">0)
      canvas.Line(col_x,y1,col_x,y2,::ColorToARGB(clr,this.m_alpha));
    

常见问题

用表格单元类提供的取数接口逐个读取优化输出数组,再调用单元格写入方法按行号列号落位,注意列宽要预留足够显示浮点数。
拆分颜色参数里的 Alpha 通道控制透明度,对关键行单独设底色,普通行用低饱和色,避免整表同色看不清层级。
可以,小布能读取你的优化输出并直接生成带网格和交替底色的表格视图,你只需确认要展示的列字段即可。
先按单元格高度算出每行 y 坐标再偏移半个像素对齐,交替底色以索引 0 行起算奇偶,别从可见区第一行开始。
边框走独立绘制接口设较粗线宽,内部网格用更细的线宽循环绘制,两者颜色也建议区分以降低视觉噪点。