自动选择有 "钱途" 的信号·进阶篇
抓取信号底座字段并折算杠杆评分
在 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,也就是说高杠杆在总评里直接吃满扣分项。 外汇和贵金属信号自带高杠杆高风险,这套评分只是筛选参考,不预示任何收益;真要拿来跑,先把杠杆上限卡在你能扛的范围内再看总分。
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 导航到信号标签页,拿任意一个信号的实际回撤和周数代进公式,能验证你手算的分和榜单排序倾向是否一致;贵金属与外汇信号杠杆差异大,回撤阈值需结合自身风险承受再判。
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 数组就能实时看排版松紧,不用重编译资源。
"生存时间, 周", "评分" }; 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 时别把指针图当背景图覆盖掉。
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 文件名就能验证容器是否真的持有了它。
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); }