自动选择有
📘

自动选择有 "钱途" 的信号·进阶篇

第 2/3 篇

抓取信号底座字段并折算杠杆评分

在 MT5 信号遍历循环里,每一路信号都要先把它底座属性读进数组。下面这段代码就是干这件事的:用 SignalBaseGet 系列函数把货币、杠杆、增长、ROI、最大回撤、ID、订阅数和存活周数分别落袋,存活周数由发布时间戳除以 3600*24*7 折算。 [CODE] m_curr[i]=SignalBaseGetString(SIGNAL_BASE_CURRENCY); // 信号的货币 m_leverage[i]=SignalBaseGetInteger(SIGNAL_BASE_LEVERAGE); // 杠杆 m_gain[i]=SignalBaseGetDouble(SIGNAL_BASE_GAIN); // 账户增长 % m_roi[i]=SignalBaseGetDouble(SIGNAL_BASE_ROI); // 投资回报 m_max_drawdown[i]=SignalBaseGetDouble(SIGNAL_BASE_MAX_DRAWDOWN); // 最大回撤 m_id[i]=SignalBaseGetInteger(SIGNAL_BASE_ID); // 信号 ID m_subscr[i]=SignalBaseGetInteger(SIGNAL_BASE_SUBSCRIBERS); // 订阅者数量 m_weeks[i]=int((TimeCurrent()-SignalBaseGetInteger(SIGNAL_BASE_DATE_PUBLISHED))/3600/24/7); // 信号生存时间 m_rating[i]=GetLeverageRating(m_leverage[i])+GetGainRating(m_gain[i],m_weeks[i])+GetDrawDownRating(m_max_drawdown[i])+GetROIRating(m_roi[i])+GetWeeksRating(m_weeks[i]); [/CODE] 杠杆评分函数给了一条硬折算线:lev_rating = int(-20.0/499.0 * leverage + 10000.0/499.0),上限钳在 20。代入可知杠杆 1 倍时评分约 20,杠杆 500 倍时掉到 0,也就是说高杠杆在总评里直接吃满扣分项。 外汇和贵金属信号自带高杠杆高风险,这套评分只是筛选参考,不预示任何收益;真要拿来跑,先把杠杆上限卡在你能扛的范围内再看总分。

MQL5 / C++
m_curr[i]=SignalBaseGetString(SIGNAL_BASE_CURRENCY);           class=class="str">"cmt">// 信号的货币
m_leverage[i]=SignalBaseGetInteger(SIGNAL_BASE_LEVERAGE);     class=class="str">"cmt">// 杠杆
m_gain[i]=SignalBaseGetDouble(SIGNAL_BASE_GAIN);              class=class="str">"cmt">// 账户增长 %
m_roi[i]=SignalBaseGetDouble(SIGNAL_BASE_ROI);                class=class="str">"cmt">// 投资回报
m_max_drawdown[i]=SignalBaseGetDouble(SIGNAL_BASE_MAX_DRAWDOWN); class=class="str">"cmt">// 最大回撤
m_id[i]=SignalBaseGetInteger(SIGNAL_BASE_ID);                 class=class="str">"cmt">// 信号 ID
m_subscr[i]=SignalBaseGetInteger(SIGNAL_BASE_SUBSCRIBERS);    class=class="str">"cmt">// 订阅者数量
m_weeks[i]=class="type">int((TimeCurrent()-SignalBaseGetInteger(SIGNAL_BASE_DATE_PUBLISHED))/class="num">3600/class="num">24/class="num">7); class=class="str">"cmt">// 信号生存时间
m_rating[i]=GetLeverageRating(m_leverage[i])+GetGainRating(m_gain[i],m_weeks[i])+GetDrawDownRating(m_max_drawdown[i])+GetROIRating(m_roi[i])+GetWeeksRating(m_weeks[i]);

class="type">int CProgram::GetLeverageRating(class="type">long leverage)
  {
   class="type">int lev_rating=class="type">int(-class="num">20.0/class="num">499.0*class="type">class="kw">double(leverage)+class="num">10000.0/class="num">499.0);
   lev_rating=(lev_rating>class="num">20)?class="num">20:lev_rating;
   class="kw">return lev_rating;
  }

◍ 信号评分的四套量化公式

把信号放进排行榜之前,得先把它拆成几个可比较的维度。下面这套 CProgram 成员函数,把增长率、回撤、ROI、存活周数各自压成一个 0~20 的整数分,方便后面加权排序。 增长率评分先防住 weeks 为 0 的除零:weeks 取 1 兜底。核心算式是 -10*(gain/weeks/7.0)+150.0/7.0,相当于把「周化日收益」映射进区间,上限 20、下限 0;gain 本身为负直接判 0 分。回撤评分更简单:40 减 max_drawdown 百分比,回撤超 40% 就吃 0 分,这对外汇跟单信号算是一道硬门槛。 ROI 评分用 0.2*roi-20,意思是 roi 要到 100 才勉强满 20 分,小盈利信号在这里会被压得很低。存活周数评分按 20*weeks/21 - 80/21 走,大约活过 4 周才脱离 0 分区,21 周拉满——跟 MT5 信号市场「新信号观察期」的节奏能对上。 InitializingTable 里先铺好 10 列字段,从「信号名称」到「订阅者数量」,其中账户增长、投资回报、最大回撤三列就是上面三个评分函数的取数入口。开 MT5 导航到信号标签页,拿任意一个信号的实际回撤和周数代进公式,能验证你手算的分和榜单排序倾向是否一致;贵金属与外汇信号杠杆差异大,回撤阈值需结合自身风险承受再判。

MQL5 / C++
class=class="str">"cmt">//| 增长率评估                                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CProgram::GetGainRating(class="type">class="kw">double gain,class="type">int weeks)
  {
   weeks=(weeks==class="num">0)?class="num">1:weeks;
   class="type">int gain_rating=class="type">int(-class="num">10*(gain/class="type">class="kw">double(weeks)/class="num">7.0)+class="num">150.0/class="num">7.0);
   gain_rating=(gain_rating>class="num">20)?class="num">20:gain_rating;
   gain_rating=(gain_rating<class="num">0)?class="num">0:gain_rating;
   gain_rating=(gain<class="num">0)?class="num">0:gain_rating;
   class="kw">return gain_rating;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 最大回撤评估                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CProgram::GetDrawDownRating(class="type">class="kw">double max_drawdown)
  {
   class="type">int drawdn_rating=class="type">int(-max_drawdown+class="num">40);
   drawdn_rating=(drawdn_rating>class="num">20)?class="num">20:drawdn_rating;
   drawdn_rating=(drawdn_rating<class="num">0)?class="num">0:drawdn_rating;
   class="kw">return drawdn_rating;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 投资回报评估                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CProgram::GetROIRating(class="type">class="kw">double roi)
  {
   class="type">int roi_rating=class="type">int(class="num">0.2*roi-class="num">20);
   roi_rating=(roi_rating>class="num">20)?class="num">20:roi_rating;
   roi_rating=(roi_rating<class="num">0)?class="num">0:roi_rating;
   class="kw">return roi_rating;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 交易信号生存时间评估                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CProgram::GetWeeksRating(class="type">int weeks)
  {
   class="type">int age_rating=class="type">int(class="num">20.0*class="type">class="kw">double(weeks)/class="num">21.0-class="num">80.0/class="num">21.0);
   age_rating=(age_rating>class="num">20)?class="num">20:age_rating;
   age_rating=(age_rating<class="num">0)?class="num">0:age_rating;
   class="kw">return age_rating;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 初始化表格                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CProgram::InitializingTable(class="type">void)
  {
class=class="str">"cmt">//---
   class="type">class="kw">string columns[class="num">10]=
     {
      "信号名称",
      "账户货币",
      "账户杠杆",
      "账户增长, %",
      "投资回报",
      "最大回撤",
      "信号 ID",
      "订阅者数量",

「信号表格的字段填充与列宽设定」

这段逻辑干的事很直接:把 signals 数组里的 10 类指标逐项写进 GUI 表格。外层 for 循环跑 COLUMNS1_TOTAL(定义为 10)列,内层对 m_signals_total 个信号行做 SetValue,c==0 写名称、c==3 写增益(DoubleToString 保留两位小数)、c==5 写最大回撤——外汇与贵金属信号的高杠杆特性下,max_drawdown 这列值得优先盯。 列宽不是均分的。ArrayInitialize(width,110) 先把 10 列默认填 110 像素,随后手动覆盖:货币对列 width[1]=80、杠杆列 width[2]=100、ROI 列 width[4]=90、生存周数列 width[8]=85、评分列 width[9]=90。文本 X 偏移统一初始化为 7 像素,避免字贴边。 表头字符串数组末两项是「生存时间, 周」和「评分」,对应 c==8 / c==9 的 IntegerToString 转换。开 MT5 把这段塞进 EA 的 CreateTable 方法,改 width 数组就能实时看排版松紧,不用重编译资源。

MQL5 / C++
   "生存时间, 周",
   "评分"
   };
class=class="str">"cmt">//---
   for(class="type">int c=class="num">0;c<COLUMNS1_TOTAL; c++)
   {
   class=class="str">"cmt">//--- 设置表头标题
   m_table.SetHeaderText(c,columns[c]);
   class=class="str">"cmt">//---
   for(class="type">int r=class="num">0;r<m_signals_total; r++)
      {
       if(c==class="num">0)
          m_table.SetValue(c,r,m_name[r]);
       else if(c==class="num">1)
          m_table.SetValue(c,r,m_curr[r]);
       else if(c==class="num">2)
          m_table.SetValue(c,r,IntegerToString(m_leverage[r]));
       else if(c==class="num">3)
          m_table.SetValue(c,r,DoubleToString(m_gain[r],class="num">2));
       else if(c==class="num">4)
          m_table.SetValue(c,r,DoubleToString(m_roi[r],class="num">2));
       else if(c==class="num">5)
          m_table.SetValue(c,r,DoubleToString(m_max_drawdown[r],class="num">2));
       else if(c==class="num">6)
          m_table.SetValue(c,r,IntegerToString(m_id[r]));
       else if(c==class="num">7)
          m_table.SetValue(c,r,IntegerToString(m_subscr[r]));
       else if(c==class="num">8)
          m_table.SetValue(c,r,IntegerToString(m_weeks[r]));
       else if(c==class="num">9)
          m_table.SetValue(c,r,IntegerToString(m_rating[r]));
      }
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建渲染表格                                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\arrow_up.bmp"
class="macro">#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\arrow_down.bmp"
class="macro">#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\circle_gray.bmp"
class="macro">#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\calendar.bmp"
class=class="str">"cmt">//---
class="type">bool CProgram::CreateTable(const class="type">int x_gap,const class="type">int y_gap)
  {
class="macro">#define COLUMNS1_TOTAL class="num">10
class=class="str">"cmt">//--- 将指针存储到主控件
   m_table.MainPointer(m_window);
class=class="str">"cmt">//--- 列宽数组
   class="type">int width[COLUMNS1_TOTAL];
   ::ArrayInitialize(width,class="num">110);
   width[class="num">1]=class="num">80;
   width[class="num">2]=class="num">100;
   width[class="num">4]=class="num">90;
   width[class="num">8]=class="num">85;
   width[class="num">9]=class="num">90;
class=class="str">"cmt">//--- 在列中文本沿 X 轴偏移的数组
   class="type">int text_x_offset[COLUMNS1_TOTAL];
   ::ArrayInitialize(text_x_offset,class="num">7);
class=class="str">"cmt">//--- 列中的文本对齐数组

信号表格的像素级排版与排序陷阱

把交易信号塞进 GUI 表格,第一步不是画界面,而是把列对齐方式和单元格尺寸钉死。下面这段初始化先把所有列设为居中对齐,再给表格定下 1000×470 的画布、行高 20,表头开启、斑马纹用 clrWhiteSmoke、表头底色 clrSkyBlue——这些数值直接决定你在 1920 屏上能否一眼扫完多品种信号。 代码里有个容易被忽略的坑:DataType 连续声明第 2 列到 9 列的类型,其中第 2、6、7、8、9 列是 TYPE_INT,3/4/5 列是 TYPE_FLOAT。如果实际塞进去的浮点信号写进整型列,MT5 会静默截断,回测时你可能以为信号消失,其实是被强转丢了小数。 排序那段更诡异:m_table.SortData(9) 被连续调用了两次,指向同一列。单次调用就够,重复调用在 EasyAndFastGUI 框架下不会报错,但会多耗一次内部重排,品种数超过 50 时界面卡顿概率会抬头。 渐变比例图标走的是 #resource 预编译指令,把 000.bmp 和 ArrowUp_blue.bmp 编进 ex5。CreatePicture1 里图标区定成 300×40,CreatePicture2 的指针图标只有 16×16——这两个尺寸差说明比例条和拖动指针是分开画的,改 UI 时别把指针图当背景图覆盖掉。

MQL5 / C++
  ENUM_ALIGN_MODE align[COLUMNS1_TOTAL];
  ::ArrayInitialize(align,ALIGN_CENTER);
class=class="str">"cmt">//---
  GetTradingSignals();
class=class="str">"cmt">//--- 属性
  m_table.XSize(class="num">1000);
  m_table.YSize(class="num">470);
  m_table.CellYSize(class="num">20);
  m_table.TableSize(COLUMNS1_TOTAL,m_signals_total);
  m_table.TextAlign(align);
  m_table.ColumnsWidth(width);
  m_table.TextXOffset(text_x_offset);
  m_table.LabelXGap(class="num">5);
  m_table.LabelYGap(class="num">4);
  m_table.IconXGap(class="num">7);
  m_table.IconYGap(class="num">4);
  m_table.MinColumnWidth(class="num">0);
  m_table.ShowHeaders(true);
  m_table.IsSortMode(true);
  m_table.LightsHover(true);
  m_table.SelectableRow(true);
  m_table.IsWithoutDeselect(true);
  m_table.ColumnResizeMode(true);
  m_table.IsZebraFormatRows(clrWhiteSmoke);
  m_table.AutoXResizeMode(true);
  m_table.AutoXResizeRightOffset(class="num">7);
  m_table.AutoYResizeBottomOffset(class="num">28);
  m_table.HeadersColor(clrSkyBlue);
  m_table.DataType(class="num">2,TYPE_INT);
  m_table.DataType(class="num">3,TYPE_FLOAT);
  m_table.DataType(class="num">4,TYPE_FLOAT);
  m_table.DataType(class="num">5,TYPE_FLOAT);
  m_table.DataType(class="num">6,TYPE_INT);
  m_table.DataType(class="num">7,TYPE_INT);
  m_table.DataType(class="num">8,TYPE_INT);
  m_table.DataType(class="num">9,TYPE_INT);
class=class="str">"cmt">//--- 用数据填充表格
  InitializingTable();
class=class="str">"cmt">//--- 创建控件
  if(!m_table.CreateTable(x_gap,y_gap))
     class="kw">return(false);
class=class="str">"cmt">//--- 将该对象添加到对象组的公用数组中
  CWndContainer::AddToElementsArray(class="num">0,m_table);
  m_table.SortData(class="num">9);
  m_table.SortData(class="num">9);
  class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建一个渐变比例                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp64\\class="num">000.bmp"
class="macro">#resource "\\Images\\EasyAndFastGUI\\Controls\\ArrowUp_blue.bmp"
class=class="str">"cmt">//---
class="type">bool CProgram::CreatePicture1(const class="type">int x_gap,const class="type">int y_gap)
  {
class=class="str">"cmt">//--- 将指针存储到主控件
  m_picture1.MainPointer(m_window);
class=class="str">"cmt">//--- 属性
  m_picture1.XSize(class="num">300);
  m_picture1.YSize(class="num">40);
  m_picture1.IconFile("Images\\EasyAndFastGUI\\Icons\\bmp64\\class="num">000.bmp");
class=class="str">"cmt">//--- 创建按钮
  if(!m_picture1.CreatePicture(x_gap,y_gap))
     class="kw">return(false);
class=class="str">"cmt">//--- 在基类中添加一个指向元素的指针
  CWndContainer::AddToElementsArray(class="num">0,m_picture1);
  class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 为比例创建一个指针                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreatePicture2(const class="type">int x_gap,const class="type">int y_gap)
  {
class=class="str">"cmt">//--- 将指针存储到主控件
  m_picture2.MainPointer(m_window);
class=class="str">"cmt">//--- 属性
  m_picture2.XSize(class="num">16);
  m_picture2.YSize(class="num">16);

◍ 把箭头图标塞进容器才算完

上面这段是给 GUI 里第二个图片控件(m_picture2)收尾的实战写法。先指定蓝色向上箭头的位图路径,注意路径分隔必须用双反斜杠 Images\EasyAndFastGUI\Controls\ArrowUp_blue.bmp,少一个斜杠在 MT5 里会直接加载失败。 随后用 CreatePicture(x_gap,y_gap) 在指定坐标生成图片,返回 false 就中断初始化——这意味着若图标资源缺失,整个面板创建会就地报错而不是黑屏。 最后一行 CWndContainer::AddToElementsArray(0,m_picture2) 才是关键:必须把指针加进基类元素数组索引 0,否则后续循环遍历控件时这个箭头根本不会被绘制或响应。开 MT5 把这段接在你自己的 CAppDialog 派生类里,改个 bmp 文件名就能验证容器是否真的持有了它。

MQL5 / C++
  m_picture2.IconFile("Images\\EasyAndFastGUI\\Controls\\ArrowUp_blue.bmp");
class=class="str">"cmt">//--- 创建按钮
  if(!m_picture2.CreatePicture(x_gap,y_gap))
    class="kw">return(false);
class=class="str">"cmt">//--- 在基类中添加一个指向元素的指针
  CWndContainer::AddToElementsArray(class="num">0,m_picture2);
  class="kw">return(true);
}

常见问题

用接口拉取信号基础数据后,按净值、杠杆、回撤字段折算成统一评分,再写进本地表格做横向比较。
可叠加收益稳定性、最大回撤、持仓周期、资金曲线斜率四套公式,权重按自己风险偏好调,别只盯收益率。
小布可接入信号数据做多维度评分和每日更新提醒,你打开对应品种页就能直接看筛选结果,不用自己跑表。
多为像素级排版未锁列宽且排序键重复导致,设定固定列宽并指定唯一排序字段即可避坑。
箭头图标必须放进固定尺寸的容器控件里再绘制,否则不同分辨率下会被压扁或切掉一半。