信号计算器·进阶篇
🧮

信号计算器·进阶篇

(2/3)· 搞懂信号兼容限制与复制比例算法,才能把订阅决策从盲猜变成可计算

进阶 第 2/3 篇
很多人以为信号页里看到的都能直接跟单,结果切换交易服务器后列表全变,复制比例算出来也对不上实际仓位。信号计算器不是点开就完事,它的过滤逻辑和账户绑定关系才是关键。

「信号计算器面板的控件骨架与画布挂载」

做信号计算器第一件事是把控件铺到对话框客户区。面板按五行排布:第一行放标签与下拉框组合,第二行单标签,第三行八个功能按钮,第四行是自研的表格控件,第五行用 CCanvas 画位图按钮。所有控件对象创建后都必须调用 CDialog::Add 塞进客户区,否则界面上根本看不到。 以 Label4 为例,先 new 出对象、设好属性,退出函数前一句 Add(label4) 决定它能不能渲染。这套顺序可概括为:Create 面板 → 逐个 Create 元件 → 改属性 → Add 到面板,顺序乱了 MT5 会报空指针或控件失踪。 画布走的是 CoSDialog::CreateBmpButton1。先 m_canvas1.Create 建独立资源,注意传入的 x2、y2 就是图标像素尺寸,必须一致;接着 FontSet、Erase 填面板色、Update 推显。第二步建 10×10 的 CBmpButton,锚点 x1、y1 才是关键。第三步把 ResourceName 喂给 BmpOnName,控件按画布尺寸拉伸;第四步照样 Add 进面板。 表格数据排序在 InitStructurs 里用 CArrayObj 线性表完成,方案来自 Vasiliy Sokolov。原始信号复制比例乱序如 Siksikawa 8770.0、Hedge 1.0 混排,插入时按 rate 字段实时重排——日志里 row #0 到 row #5 每次 Insert 后顺序都变,证明是插入即排序。想看完整过程,把该方法里打印块注释去掉重编译即可。 新表格 CTableListView 基于 CListView 改,分可见与不可见两层。可见部分由 x1,y1,x2,y2 矩形加列元件限定,内部用 m_arr_rows(CArrayObj)管 CEdit 指针,像两层俄套娃;不可见部分用 m_arr_rows_str(CArrayString)和 m_arr_rows_val(CArrayLong)存文字与数值。外汇贵金属信号订阅涉及跟单,杠杆与汇率波动属高风险,排序结果仅作参考而非收益保证。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 定义                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- 缩进和间隙
class="macro">#define INDENT_LEFT(class="num">11)      class=class="str">"cmt">// 左边的缩进 (包含边框宽度)

◍ 面板控件尺寸与标签创建的实现细节

在 MT5 自定义对话框里,先靠一组宏把控件尺寸钉死,能避免不同 DPI 下布局错位。顶端缩进 INDENT_TOP 取 11 像素(含边框),纵向间隙 CONTROLS_GAP_Y 为 5 像素;下拉框宽 60、高 20,列表高 102,按钮宽 72、高 20,编辑框宽 60、高 20。 CoSDialog::Create 先置 m_error=true,再调用基类 CAppDialog::Create 建壳,若失败直接返回 false,这是标准的资源申请失败短路写法。 CreateLabel4 里标签坐标用 x1=INDENT_LEFT、y1=INDENT_TOP+COMBOBOX_HEIGHT+CONTROLS_GAP_Y 算出来,也就是顶缩进 11 加下拉高 20 加间隙 5,y1 实际为 36(假设 INDENT_LEFT 为 0 时);标签宽固定 100、高复用下拉框 20。文本取自多语言对象的第 3 号索引,最后 Add 进容器,任一步失败都返回 false。 开 MT5 把这段宏和 CreateLabel4 抄进你的面板类,改 m_languages.GetText(3) 的索引就能换标签文案,验证布局是否如数落位。

MQL5 / C++
class="macro">#define INDENT_TOP(class="num">11)      class=class="str">"cmt">// 顶端的缩进 (包含边框宽度)
class="macro">#define CONTROLS_GAP_Y(class="num">5)       class=class="str">"cmt">// 纵坐标上的间隙
class=class="str">"cmt">//--- for combo boxes
class="macro">#define COMBOBOX_WIDTH(class="num">60)      class=class="str">"cmt">// 横坐标上的大小
class="macro">#define COMBOBOX_HEIGHT(class="num">20)      class=class="str">"cmt">// 纵坐标上的大小
class=class="str">"cmt">//--- for list view
class="macro">#define LIST_HEIGHT(class="num">102)     class=class="str">"cmt">// 纵坐标上的大小
class=class="str">"cmt">//--- for buttons
class="macro">#define BUTTON_WIDTH(class="num">72)      class=class="str">"cmt">// 横坐标上的大小
class="macro">#define BUTTON_HEIGHT(class="num">20)      class=class="str">"cmt">// 纵坐标上的大小
class=class="str">"cmt">//--- for the indication area
class="macro">#define EDIT_WIDTH(class="num">60)      class=class="str">"cmt">// 横坐标上的大小
class="macro">#define EDIT_HEIGHT(class="num">20)      class=class="str">"cmt">// 纵坐标上的大小
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CoSDialog::Create(const class="type">long chart,const class="type">class="kw">string name,const class="type">int subwin,const class="type">int x1,const class="type">int y1,const class="type">int x2,const class="type">int y2)
  {
class=class="str">"cmt">//---
   m_error=true;
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
      class="kw">return(false);
class=class="str">"cmt">//---
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建 "Signals" 标签                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CoSDialog::CreateLabel4(class="type">void)
  {
class=class="str">"cmt">//--- 坐标
   class="type">int x1=INDENT_LEFT;
   class="type">int y1=INDENT_TOP+COMBOBOX_HEIGHT+CONTROLS_GAP_Y;
   class="type">int x2=x1+class="num">100;
   class="type">int y2=y1+COMBOBOX_HEIGHT;
class=class="str">"cmt">//--- 创建
   if(!m_label4.Create(m_chart_id,m_name+"Label4",m_subwin,x1,y1,x2,y2))
      class="kw">return(false);
   if(!m_label4.Text(m_languages.GetText(class="num">3)))
      class="kw">return(false);
   if(!Add(m_label4))
      class="kw">return(false);
class=class="str">"cmt">//--- 成功
   class="kw">return(true);
  }

在面板里铺画布和位图按钮

往 MT5 自定义控件面板里塞元素时,先建 Label 再建 Canvas 是常见顺序。下面这段逻辑里,Label4 创建失败就直接 return(false),不往下走,避免空指针把整块面板拖崩。 画布用 COLOR_FORMAT_XRGB_NOALPHA 建,尺寸取 x2、y2 两点坐标。字体设成 Trebuchet MS、高度 -100、FW_THIN,背景擦成 C'0xF7,0xF7,0xF7' 的浅灰并立即 Update(true) 推到图表,肉眼能看到面板底色瞬间刷出来。 位图按钮 BmpButton1 占地只有 10×10 像素(x1+10, y1+10),它的图案直接绑刚建好的画布资源名 m_canvas1.ResourceName()。若 BmpOnName 失败同样 return(false),否则 Add 进容器。 插入排序那行值得盯:min_deposit = AccInfo.balance / rate * 100.0,用账户余额除以某汇率再乘 100 算出最小入金,塞进 m_table.InsertSort(new CLineTable(...))。外汇和贵金属杠杆品种汇率跳动大,这套算法算出的数值仅供参考,实际爆仓风险仍高。 几个容器数组也得一并声明:m_arr_rows 装 CEdit 行对象,m_arr_rows_str 装 CArrayString,m_arr_rows_val 装 CArrayLong,三者都靠 CArrayObj 管指针,少一个都可能让后续表格刷新漏行。

MQL5 / C++
  if(!m_label4.Create(m_chart_id,m_name+"Label4",m_subwin,x1,y1,x2,y2))
      class="kw">return(false);
  if(!Add(m_label4))
      class="kw">return(false);
class=class="str">"cmt">//--- 创建画布
  if(!m_canvas1.Create("Canvas1",x2,y2,COLOR_FORMAT_XRGB_NOALPHA))
    {
      Print("Error creating canvas: ",GetLastError());
      class="kw">return(false);
    }
  m_canvas1.FontSet("Trebuchet MS",-class="num">100,FW_THIN);
  m_canvas1.Erase(ColorToARGB(C&class="macro">#x27;0xF7,0xF7,0xF7&class="macro">#x27;,class="num">255));
  m_canvas1.Update(true);
class=class="str">"cmt">//--- 创建
  if(!m_bmp_button1.Create(m_chart_id,m_name+"BmpButton1",m_subwin,x1,y1,x1+class="num">10,y1+class="num">10))
      class="kw">return(false);
class=class="str">"cmt">//--- 设置 CBmpButton 控件位图文件的名称
  if(!m_bmp_button1.BmpOnName(m_canvas1.ResourceName()))
      class="kw">return(false);
  if(!Add(m_bmp_button1))
      class="kw">return(false);
        else
        {
        min_deposit=AccInfo.balance/rate*class="num">100.0;
        }
    class=class="str">"cmt">//Print("Insert : ",name,"; ",rate,"; ",min_deposit);
        m_table.InsertSort(new CLineTable(name,rate,min_deposit));
    class=class="str">"cmt">//for(class="type">int m=class="num">0;m<m_table.Total();m++)
    class=class="str">"cmt">//  {
    class=class="str">"cmt">//  CLineTable *line=m_table.At(m);
    class=class="str">"cmt">//  Print("row #",m,": ",line.Text(),"; ",line.Number(),"; ",line.Number1());
    class=class="str">"cmt">//  }
        }
    else PrintFormat("Error in call of SignalBaseSelect. Error code=%d",GetLastError());
    }
class=class="str">"cmt">//---
  class="kw">return(true);
  }
  class="kw">virtual class="type">bool      Create(const class="type">long chart,const class="type">class="kw">string name,const class="type">int subwin,const class="type">int x1,
                    const class="type">int y1,const class="type">int x2,const class="type">int y2,const class="type">uchar columns,const class="type">class="kw">ushort &columns_size[]);
  CArrayObj        m_arr_rows;          class=class="str">"cmt">// objects-rows(CEdit) 指针数组
  CArrayObj        m_arr_rows_str;      class=class="str">"cmt">// objects-rows(CArrayString) 的指针数组
  CArrayObj        m_arr_rows_val;      class=class="str">"cmt">// objects-rows(CArrayLong) 的指针数组

「表格视图的构造与行列内存分配」

CTableListView::Create 是列表控件初始化的核心入口,先把列数 m_columns 和每列宽度数组 columns_size 拷进成员,这里有个隐蔽点:首列和末列的宽度会各减 1,用来抵消边框占位,若你传入的数组长度和列数不一致函数直接返回 false,调用方拿不到任何报错提示只能自己查。 可见行数由 (y2-y1)/m_item_height 推算,如果 TotalView 失败就回退到单行高度加两倍边框的保底值;m_total_view 小于 1 同样直接退出,意味着控件区域高度不够一行就别想画出来。 真正吃内存的是双重循环:外层按可见行数 new 出 CArrayObj 行容器,内层按列数 new 出 CEdit 单元格塞进去。假设 10 行 5 列就是 50 个 CEdit 对象,实盘面板若开多图表要算好句柄占用。 下面这段是 Create 的真实代码,逐行拆给你看关键分支落在哪。

MQL5 / C++
class="type">bool CTableListView::Create(const class="type">long chart,const class="type">class="kw">string name,const class="type">int subwin,const class="type">int x1,
                      const class="type">int y1,const class="type">int x2,const class="type">int y2,const class="type">uchar columns,const class="type">class="kw">ushort &columns_size[])
  {
   m_columns=columns;
   ArrayResize(m_columns_size,m_columns);
   if(ArraySize(columns_size)!=m_columns)
      class="kw">return(false);
   ArrayCopy(m_columns_size,columns_size,class="num">0,class="num">0,WHOLE_ARRAY);
   m_columns_size[class="num">0]-=class="num">1;
   m_columns_size[m_columns-class="num">1]-=class="num">1;
   class="type">int y=y2;
class=class="str">"cmt">//--- 如果之前确定了可见的行数,调整垂直大小
   if(!TotalView((y2-y1)/m_item_height))
      y=m_item_height+y1+class="num">2*CONTROLS_BORDER_WIDTH;
class=class="str">"cmt">//--- 检查可见行数
   if(m_total_view<class="num">1)
      class="kw">return(false);
class=class="str">"cmt">//--- 调用父类方法
   if(!CWndClient::Create(chart,name,subwin,x1,y1,x2,y))
      class="kw">return(false);
class=class="str">"cmt">//--- 设置
   if(!m_background.ColorBackground(CONTROLS_LIST_COLOR_BG))
      class="kw">return(false);
   if(!m_background.ColorBorder(CONTROLS_LIST_COLOR_BORDER))
      class="kw">return(false);
class=class="str">"cmt">//--- 创建依赖的控件
   CArrayObj *m_arr_cells;
   for(class="type">int i=class="num">0;i<m_total_view;i++)
     {
      m_arr_cells=new CArrayObj;
      if(CheckPointer(m_arr_cells)==POINTER_INVALID)
         class="kw">return(false);
      for(class="type">int j=class="num">0;j<m_columns;j++)
        {
         CEdit *m_cell;
         m_cell=new CEdit;
         if(CheckPointer(m_cell)==POINTER_INVALID)
            class="kw">return(false);
         m_arr_cells.Add(m_cell);
         }
      m_arr_rows.Add(m_arr_cells);
     }
class=class="str">"cmt">//---
   for(class="type">int i=class="num">0;i<m_total_view;i++)
     {
      if(!CreateRow(i))
         class="kw">return(false);
      if(m_height_variable && i>class="num">0)
        {
         class=class="str">"cmt">// m_rows[i].Hide(); ///
         CArrayObj *m_arr_cells_i=m_arr_rows.At(i);
         if(CheckPointer(m_arr_cells_i)==POINTER_INVALID)
            class="kw">return(false);
         for(class="type">int j=class="num">0;j<m_arr_cells_i.Total();j++)
           {
            CEdit *m_cell=m_arr_cells_i.At(j);
            if(CheckPointer(m_cell)==POINTER_INVALID)

◍ 行与单元格的实例化收尾

表格可视区域在确认了行数之后,会按 (y2-y1)/m_item_height 的比值去申请行对象;若 TotalView 调用失败,则回退到 y=m_item_height+y1+2*CONTROLS_BORDER_WIDTH 的兜底坐标,避免控件叠在边框上。 紧接着用双层循环把每一行的单元格数组建出来:外层 i 跑 m_total_view 次,内层 j 跑 m_columns 次,每次 new 一个 CEdit 并做 CheckPointer 校验,指针无效立即 return(false)。这一步决定了你之后在 MT5 里看到的行×列网格规模。 CreateRow 内部则按索引取回该行单元格数组,再遍历 m_arr_cells.Total() 逐个创建 CEdit,名称拼成 name_index_i,并强制 Text("") 与 ReadOnly(true)。想改可编辑表格,把 ReadOnly 的参数翻成 false 即可,但外汇与贵金属行情波动剧烈、GUI 误触可能带来高风险,建议先用模拟账户验证。

MQL5 / C++
if(!TotalView((y2-y1)/m_item_height))
   y=m_item_height+y1+class="num">2*CONTROLS_BORDER_WIDTH;
CArrayObj *m_arr_cells;
for(class="type">int i=class="num">0;i<m_total_view;i++)
  {
   m_arr_cells=new CArrayObj;
   if(CheckPointer(m_arr_cells)==POINTER_INVALID)
      class="kw">return(false);
   for(class="type">int j=class="num">0;j<m_columns;j++)
     {
      CEdit *m_cell;
      m_cell=new CEdit;
      if(CheckPointer(m_cell)==POINTER_INVALID)
         class="kw">return(false);
      m_arr_cells.Add(m_cell);
     }
   m_arr_rows.Add(m_arr_cells);
  }
for(class="type">int i=class="num">0;i<m_total_view;i++)
  {
   if(!CreateRow(i))
      class="kw">return(false);
  }
class="kw">return(true);

class="type">bool CTableListView::CreateRow(const class="type">int index)
  {
   CArrayObj *m_arr_cells=m_arr_rows.At(index);
   if(CheckPointer(m_arr_cells)==POINTER_INVALID)
      class="kw">return(false);
   for(class="type">int i=class="num">0;i<m_arr_cells.Total();i++)
     {
      CEdit *m_cell=m_arr_cells.At(i);
      if(CheckPointer(m_cell)==POINTER_INVALID)
         class="kw">return(false);
      x1+=x2;
      x2=m_columns_size[i];
      if(!m_cell.Create(m_chart_id,m_name+"_"+IntegerToString(index)+"_"+IntegerToString(i),
         m_subwin,x1,y1,x1+x2,y2))
         class="kw">return(false);
      if(!m_cell.Text(""))
         class="kw">return(false);
      if(!m_cell.ReadOnly(true))
         class="kw">return(false);
      if(!Add(m_cell))
         class="kw">return(false);
     }
   class="kw">return(true);
  }
让小布替你跑这套兼容检查
这些信号兼容与复制比例的初筛,小布盯盘的 AIGC 已内置,打开对应品种页即可看到账户层面的跟单风险标注,你只需专注是否订阅的决策。

常见问题

终端按当前连接账户的兼容性过滤信号,不同服务器或账户权益结构不同,展示集合可能差异明显,属正常限制。
核心依赖交易账户余额、币别与信号自身手数映射规则,具体算法在开发章节的二维数组排序与表格控件中体现,倾向按资金规模线性缩放。
可以,小布在品种页内置了账户维度的跟单兼容性诊断,会基于你的余额和币别提示可能适用的复制区间,但不构成收益承诺。
通过在画布上绑定指标动态数组并触发重绘,配合排序逻辑刷新行列,案例在开发小节的操作实例中给出思路。