自动选择有 "钱途" 的信号(基础篇)
◍ 让模型替你筛掉没油水的信号
MT5 社区信号成千上万,肉眼挑性价比低的信号纯属浪费时间。2018 年 2 月一篇社区帖统计显示,当时信号库浏览量前十的条目累计访问超 1 万次,但其中半数以上回撤控制薄弱,跟单盈亏比并不乐观。 外汇与贵金属信号自带高杠杆高风险,评级不能只看收益率曲线,要把最大回撤、持仓周期、订阅人数衰减率一起算进去。 一个实用的思路是给每个信号打综合分:用 (净收益 / 最大回撤) 做核心因子,再乘上近期订阅留存率。分数过低的直接跳过,把精力留给高分且交易品种覆盖你熟悉盘面的那几个。 开 MT5 终端进「信号」标签页,按自定义指标排序,比盲翻榜单更容易撞上有油水的信号。
机器人离不开人:信号参数的适配逻辑
市场状态持续漂移,全自动 EA 很难覆盖所有行情情形,实盘里更稳的形态是「人管机」:由交易者把控自动化系统的开关与参数边界,在异常波动时手动介入。 MQL5 的「交易信号」服务就是这种共生关系的现成样本,里面按风险档位和交易节奏挂了大量信号源,用户能用筛选器粗略匹配风格。 但真正的难点不在找信号,而在判别某组参数是否契合自己的交易风格。一个可落地的做法是:先锚定「适度风险 + 保守风格」的数值区间,再反向比对信号方的参数组,偏离该区间的信号直接弃用。 打开 MT5 终端的「信号」标签页,把最大回撤、跟单比例两列按升序排,能快速剔除掉九成以上激进源,剩下少量进入人工复核。
「五维打分把信号拉到同一把尺子上」
给信号做横向比较,最怕各说各话。这里用五个维度,每个按 20 分制打分,总分 100:杠杆、周增长、最大回撤、ROI、生存期。分数高低只代表风格保守或激进,不预示未来盈亏,外汇和贵金属本身杠杆风险极高。 杠杆直接挂钩爆仓概率。1:1 给满 20 分,1:500 及以上给 0 分,中间用两点 (1,20) 和 (500,0) 连直线插值;超过 1:500 哪怕 1:1000 也一律 0 分,不向外延。 账户增长不看绝对值,先除以生存期折成周增长率。负增长直接 0 分;正增长里,周增 1% 视作保守给 20 分,周增 15% 视作风险门槛给 0 分,中间线性插值。 最大回撤是最直白的风险尺:≤20% 给 20 分,≥40% 给 0 分,20%–40% 按 (20,20) 与 (40,0) 连线算。ROI 则设 100% 以下 0 分、200% 以上 20 分,100–200 区间按 (100,0) 与 (200,20) 线性给分。 生存期以周计,不足 4 周给 0 分,满 25 周给 20 分,4–25 周之间线性插值。阈值纯属个人偏好,你可以把 25 周换成自己信得过的验证时长,但换完要同步改直线方程的两个锚点。
◍ 用图形库搭一个信号评分面板
做信号评估界面时,直接借用了 EasyandFastGUI 这套图形库,而不是从零画控件。整个程序分几块:左边列当前账户能订阅的信号清单,右边给选中信号按类别打明细分,底部状态栏显示可用信号总数,再用渐变条加指针把综合分可视化。 渐变条左红右绿,但红不等于亏钱、绿不等于有‘钱途’。它只是把信号背后的交易风格画出来——偏激进的策略亏损概率可能更高,但盈利空间也往往更大,这属于外汇和贵金属信号订阅里常见的高风险特征。 CreateGUI() 是总装函数,里面依次调 CreateWindow() 建窗体、CreateTable() 建 7 列信号表、CreateStatusBar() 写信号数、CreatePicture1/2() 画渐变和指针,再补几个文本标签和订阅按钮。GetTradingSignals() 把可用信号抓进 m_rating[] 数组,InitializingTable() 填表后默认按综合分降序排,且排序逻辑会翻转执行两次以避免缓存错序。 下面这段是界面初始化的核心代码,逐行看就能在 MT5 里照抄出骨架:CreateWindow 标题写死‘自动搜索信号’;CreateTable(7,100) 是 7 列宽 100;状态栏 1 行高 26;两张图分别 618x40 和 610x80;五个文本标签硬占位‘杠杆得分/增长得分/回撤得分/投资回报得分/生存期得分’;按钮放 440,40 写‘订阅’。OnEvent 里监听 CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM 来抓列表点击。
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| 创建程序的图形界面 |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">bool</span> CProgram::CreateGUI(<span class="keyword">class="type">void</span>) { <span class="comment">class=class="str">"cmt">//--- 创建面板</span> <span class="keyword">if</span>(!CreateWindow(<span class="class="type">class="kw">string">"自动搜索信号"</span>)) <span class="keyword">class="kw">return</span>(<span class="keyword">false</span>); <span class="comment">class=class="str">"cmt">//--- 创建表格</span> <span class="keyword">if</span>(!CreateTable(<span class="number">class="num">7</span>,<span class="number">class="num">100</span>)) <span class="keyword">class="kw">return</span>(<span class="keyword">false</span>); <span class="comment">class=class="str">"cmt">//--- 状态栏</span> <span class="keyword">if</span>(!CreateStatusBar(<span class="number">class="num">1</span>,<span class="number">class="num">26</span>)) <span class="keyword">class="kw">return</span>(<span class="keyword">false</span>); <span class="comment">class=class="str">"cmt">//--- 图像</span> <span class="keyword">if</span>(!CreatePicture1(<span class="number">class="num">618</span>,<span class="number">class="num">40</span>)) <span class="keyword">class="kw">return</span>(<span class="keyword">false</span>); <span class="keyword">if</span>(!CreatePicture2(<span class="number">class="num">610</span>,<span class="number">class="num">80</span>)) <span class="keyword">class="kw">return</span>(<span class="keyword">false</span>); <span class="comment">class=class="str">"cmt">//--- 文本标签</span> <span class="keyword">if</span>(!CreateTextLabel1(<span class="number">class="num">20</span>,<span class="number">class="num">20</span>,<span class="class="type">class="kw">string">"杠杆得分: -"</span>)) <span class="keyword">class="kw">return</span>(<span class="keyword">false</span>); <span class="keyword">if</span>(!CreateTextLabel2(<span class="number">class="num">20</span>,<span class="number">class="num">40</span>,<span class="class="type">class="kw">string">"增长得分: -"</span>)) <span class="keyword">class="kw">return</span>(<span class="keyword">false</span>); <span class="keyword">if</span>(!CreateTextLabel3(<span class="number">class="num">20</span>,<span class="number">class="num">60</span>,<span class="class="type">class="kw">string">"回撤得分: -"</span>)) <span class="keyword">class="kw">return</span>(<span class="keyword">false</span>); <span class="keyword">if</span>(!CreateTextLabel4(<span class="number">class="num">200</span>,<span class="number">class="num">20</span>,<span class="class="type">class="kw">string">"投资回报得分: -"</span>)) <span class="keyword">class="kw">return</span>(<span class="keyword">false</span>); <span class="keyword">if</span>(!CreateTextLabel5(<span class="number">class="num">200</span>,<span class="number">class="num">40</span>,<span class="class="type">class="kw">string">"生存期得分: -"</span>)) <span class="keyword">class="kw">return</span>(<span class="keyword">false</span>); <span class="comment">class=class="str">"cmt">//--- 图标按钮</span> <span class="keyword">if</span>(!CreateButton(class="num">440,<span class="number">class="num">40</span>,<span class="class="type">class="kw">string">"订阅"</span>)) <span class="keyword">class="kw">return</span>(<span class="keyword">false</span>); <span class="comment">class=class="str">"cmt">//--- 用户图形界面创建完成</span> CWndEvents::CompletedGUI(); <span class="keyword">class="kw">return</span>(<span class="keyword">true</span>); } <span class="comment">class=class="str">"cmt">//+-----------------------------------------------------------------</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| 事件处理器 |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">void</span> CProgram::OnEvent(<span class="keyword">const</span> <span class="keyword">class="type">int</span> id,<span class="keyword">const</span> <span class="keyword">class="type">long</span> &lparam,<span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span> &dparam,<span class="keyword">const</span> <span class="keyword">class="type">class="kw">string</span> &sparam) { <span class="comment">class=class="str">"cmt">//--- 列表或清单项的按击事件</span> <span class="keyword">if</span>(id==<span class="macro">CHARTEVENT_CUSTOM</span>+ON_CLICK_LIST_ITEM) {
信号面板与订阅的底层绑定
点击表格行后,面板会按选中项的坐标算出版面偏移:x = 610 + 3 * 行号,再把杠杆、增长、回撤、ROI、生存期五类得分分别贴到 (20,20)、(20,40)、(20,60)、(200,20)、(200,40) 五个文本标签上。这类硬编码坐标在 1920×1080 下看着整齐,换 4K 屏可能挤作一团,自己改 CreateTextLabel 的入参前先量一下像素。 自定义事件 CHARTEVENT_CUSTOM+ON_CLICK_BUTTON 里只做一件事:若 lparam 等于按钮 Id,就拿表格第 6 列的值去 SignalSubscribe 订阅该信号。也就是说,订阅动作完全依赖 m_table.SelectedItem() 的实时选中态,没选中时点按钮等于空转。 GetTradingSignals 先通过 SignalBaseTotal 拿到信号总数,再用 ArrayResize 把 12 个数组(名称、当前点差、杠杆、增益、ROI、最大回撤、点值、订阅数、周数、评级等)一次性扩到总信号量。遍历里用 SignalBaseSelect(i) 定位,SignalBaseGetString(SIGNAL_BASE_NAME) 取名称——外汇跟单信号的高风险在于历史评分不代表未来,订阅前建议把 m_max_drawdown 拉出来排个序。
class="type">int x=class="num">610+class="num">3*class="type">int(m_table.GetValue(class="num">9,m_table.SelectedItem())); class=class="str">"cmt">//--- CreateTextLabel1(class="num">20,class="num">20,"杠杆得分: "+IntegerToString(GetLeverageRating(class="type">int(m_table.GetValue(class="num">2,m_table.SelectedItem()))))); CreateTextLabel2(class="num">20,class="num">40,"增长得分: "+IntegerToString(GetGainRating(class="type">int(m_table.GetValue(class="num">3,m_table.SelectedItem())),class="type">int(m_table.GetValue(class="num">8,m_table.SelectedItem()))))); CreateTextLabel3(class="num">20,class="num">60,"回撤得分: "+IntegerToString(GetDrawDownRating(class="type">int(m_table.GetValue(class="num">5,m_table.SelectedItem()))))); CreateTextLabel4(class="num">200,class="num">20,"投资回报得分: "+IntegerToString(GetROIRating(class="type">int(m_table.GetValue(class="num">4,m_table.SelectedItem()))))); CreateTextLabel5(class="num">200,class="num">40,"生存期得分: "+IntegerToString(GetWeeksRating(class="type">int(m_table.GetValue(class="num">8,m_table.SelectedItem()))))); CreatePicture2(x,class="num">80); class=class="str">"cmt">//--- m_button.IsLocked(false); Update(true); } class=class="str">"cmt">//--- if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { class=class="str">"cmt">//--- 如果按钮被按下 if(lparam==m_button.Id()) { SignalSubscribe(class="type">long(m_table.GetValue(class="num">6,m_table.SelectedItem()))); } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 接收有关可用交易信号的信息 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::GetTradingSignals(class="type">void) { class=class="str">"cmt">//--- 请求信号数据库中的信号总数 m_signals_total=SignalBaseTotal(); class=class="str">"cmt">//--- ArrayResize(m_name,m_signals_total); ArrayResize(m_curr,m_signals_total); ArrayResize(m_leverage,m_signals_total); ArrayResize(m_gain,m_signals_total); ArrayResize(m_roi,m_signals_total); ArrayResize(m_max_drawdown,m_signals_total); ArrayResize(m_pips,m_signals_total); ArrayResize(m_subscr,m_signals_total); ArrayResize(m_weeks,m_signals_total); ArrayResize(m_rating,m_signals_total); class=class="str">"cmt">//--- 遍历所有信号 for(class="type">int i=class="num">0;i<m_signals_total;i++) { class=class="str">"cmt">//--- 选择一个信号进一步操作 if(SignalBaseSelect(i)) { class=class="str">"cmt">//--- 接收信号属性 m_name[i]=SignalBaseGetString(SIGNAL_BASE_NAME); class=class="str">"cmt">// 信号名称