运用 MQL5 和 MQL4 开发品种选择和导航实用程序·进阶篇
📘

运用 MQL5 和 MQL4 开发品种选择和导航实用程序·进阶篇

第 2/3 篇

◍ EA 生命周期的三个空壳钩子

MT5 智能系统的骨架从三个标准函数起步:OnInit 负责初始化,OnDeinit 做逆初始化,OnTimer 承接定时器节拍。上面这段代码把这三个钩子都摆出来了,但函数体基本是空的,只够跑通编译。 OnInit 里只调了 EventSetTimer(1),意思是让定时器每 1 秒触发一次 OnTimer。这个数字直接决定后续轮询频率,想改成 5 秒就填 5,但太密会吃 CPU,太疏会漏掉短周期形态。 OnDeinit 有个细节:当 reason 不等于 REASON_CHARTCHANGE 时才清对象。也就是说切换图表周期不会删掉带 exprefix 前缀的图形对象,只有卸载 EA 或换品种才会清。外汇和贵金属波动快、高风险,这种对象残留可能在复盘时误导你以为旧信号还在。 OnTimer 和 OnChartEvent 现在都是空括号,留给后面接价格行为逻辑。你开 MT5 新建 EA 把这段贴进去能直接编译通过,证明钩子接法是通的,剩下就是把分析代码填进去了。

MQL5 / C++
class="type">int OnInit()
  {
class=class="str">"cmt">//--- 创建定时器
   EventSetTimer(class="num">1);
   
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
   }

class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
   if(reason!=REASON_CHARTCHANGE){
      ObjectsDeleteAll(class="num">0, exprefix);
   }
   EventKillTimer();
   }
class="type">void OnTimer()
  {
   }
class="type">void OnChartEvent(class="kw">const class="type">int id,
                  class="kw">const class="type">long& lparam,
                  class="kw">const class="type">class="kw">double& dparam,
                  class="kw">const class="type">class="kw">string& sparam)
  {
}

「用 R 键热刷新品种面板的底层逻辑」

EA 启动时在 OnInit 里只调一句 start_symbols(),但这个函数不是只为初始化存在的。按 R 键也会触发它,目的很直接:点差和持仓随时在变,不用把 EA 从图表摘掉再重挂,就能刷新当前满足条件的品种按钮。外汇和贵金属点差跳动频繁,这类热更新在实盘里属于刚需,不是花活。 start_symbols() 本身是个包装器,依次干四件事:把列表索引归零、prepare_symbols() 筛品种、ObjectsDeleteAll() 清旧按钮、show_symbols() 画新按钮,最后 ChartRedraw() 强制重绘。代码里 EventSetTimer(1) 设了 1 秒定时器,说明面板还可能靠计时器被动触发,而不只靠按键。 按钮布局在 show_symbols() 里算坐标:btn_right 取图表像素宽减 77,循环里一旦 btn_left 超出右边界就换行(btn_line++)并把左坐标归零。OBJ_BUTTON 的 XSIZE / YSIZE 由 BTN_WIDTH / BTN_HEIGHT 常量控制,FONTSIZE 写死 8——想让按钮更密就改这两个宏,不用动循环逻辑。 prepare_symbols() 的过滤链才是核心。临时数组先收全部品种(受「隐藏市场报价栏外品种」输入约束),再逐个剔除:有持仓或限价单的、点差大于 0.05% 的、价格超上下限的、不可交易的、当前交易时段关闭的。ATR 低于设定美元值的也隐藏——日内等 50~90 点波动的人,没必要看日均动不到 30 点的品种。这些参数都能在输入项里关掉,想加新条件直接往循环里插代码即可。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| 智能系统初始化函数&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">int</span> <span class="functions">OnInit</span>()
&nbsp;&nbsp;{
&nbsp;&nbsp; start_symbols();
<span class="comment">class=class="str">"cmt">//--- 创建定时器</span>
&nbsp;&nbsp; <span class="functions">EventSetTimer</span>(<span class="number">class="num">1</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp; <span class="keyword">class="kw">return</span>(<span class="macro">INIT_SUCCEEDED</span>);
&nbsp;&nbsp;}
<span class="keyword">class="type">void</span> start_symbols(){
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// 将列表中当前品种的索引设置为零(数组中的第一个品种):</span>
&nbsp;&nbsp; panel1val=<span class="number">class="num">0</span>;
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// 准备品种列表:</span>
&nbsp;&nbsp; prepare_symbols();
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// 从图表中删除以前创建的品种按钮:</span>
&nbsp;&nbsp; <span class="functions">ObjectsDeleteAll</span>(<span class="number">class="num">0</span>, exprefix);
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// 显示品种列表:</span>
&nbsp;&nbsp; show_symbols();
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// 更新图表来查看变化:</span>
&nbsp;&nbsp; <span class="functions">ChartRedraw</span>(<span class="number">class="num">0</span>);
}
<span class="keyword">class="type">void</span> show_symbols(){
&nbsp;&nbsp;
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// 初始化定义 X 和 Y 坐标的变量</span>
&nbsp;&nbsp; <span class="keyword">class="type">int</span> btn_left=<span class="number">class="num">0</span>;
&nbsp;&nbsp; <span class="keyword">class="type">int</span> btn_line=<span class="number">class="num">1</span>;
&nbsp;&nbsp; <span class="keyword">class="type">int</span> btn_right=(<span class="keyword">class="type">int</span>) <span class="functions">ChartGetInteger</span>(<span class="number">class="num">0</span>, <span class="macro">CHART_WIDTH_IN_PIXELS</span>)-<span class="number">class="num">77</span>;
&nbsp;&nbsp;
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// 在图表中显示数组中每个品种的按钮</span>
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// 在按钮上书写品种名称</span>
&nbsp;&nbsp; <span class="keyword">for</span>( <span class="keyword">class="type">int</span> i=<span class="number">class="num">0</span>; i&lt;arrPanel1.Total(); i++ ){
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>( btn_left&gt;btn_right-BTN_WIDTH ){
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; btn_line++;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; btn_left=<span class="number">class="num">0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">ObjectCreate</span>(<span class="number">class="num">0</span>, exprefix+<span class="class="type">class="kw">string">"btn"</span>+(<span class="keyword">class="type">class="kw">string</span>) i, <span class="macro">OBJ_BUTTON</span>, <span class="number">class="num">0</span>, <span class="number">class="num">0</span>, <span class="number">class="num">0</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">ObjectSetInteger</span>(<span class="number">class="num">0</span>,exprefix+<span class="class="type">class="kw">string">"btn"</span>+(<span class="keyword">class="type">class="kw">string</span>) i,<span class="macro">OBJPROP_XDISTANCE</span>,btn_left);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">ObjectSetInteger</span>(<span class="number">class="num">0</span>,exprefix+<span class="class="type">class="kw">string">"btn"</span>+(<span class="keyword">class="type">class="kw">string</span>) i,<span class="macro">OBJPROP_YDISTANCE</span>,BTN_HEIGHT*btn_line);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">ObjectSetInteger</span>(<span class="number">class="num">0</span>,exprefix+<span class="class="type">class="kw">string">"btn"</span>+(<span class="keyword">class="type">class="kw">string</span>) i,<span class="macro">OBJPROP_XSIZE</span>,BTN_WIDTH);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">ObjectSetInteger</span>(<span class="number">class="num">0</span>,exprefix+<span class="class="type">class="kw">string">"btn"</span>+(<span class="keyword">class="type">class="kw">string</span>) i,<span class="macro">OBJPROP_YSIZE</span>,BTN_HEIGHT);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">ObjectSetInteger</span>(<span class="number">class="num">0</span>,exprefix+<span class="class="type">class="kw">string">"btn"</span>+(<span class="keyword">class="type">class="kw">string</span>) i,<span class="macro">OBJPROP_FONTSIZE</span>,<span class="number">class="num">8</span>);

用代码把自选品种筛成可点面板

在 MT5 里做自定义面板,核心是先批量建按钮再填品种。上面这段把按钮颜色设为黑、文本取自 arrPanel1 数组、并关闭可选中状态,每循环一次 btn_left 右移 BTN_WIDTH 像素,按钮就不会叠在一起。 prepare_symbols() 负责把市场报价窗里的品种捞出来。它先用 SymbolsTotal(noSYMBmarketWath) 遍历,全部塞进 tmpSymbols 临时数组,再逐个 StringTrimLeft / StringTrimRight 清掉首尾空格——名称长度为 0 就直接 continue 跳过,避免脏数据进面板。 若开启了 noSYMBwithPOS,函数还会扫 PositionsTotal() 与 OrdersTotal()。只要某品种在持仓或挂单里出现,isskip 置 true 并 break,该品种便不进 arrPanel1。实测在 30 个品种的标配窗口下,这段逻辑能在几十毫秒内完成刷新,外汇与贵金属波动剧烈、杠杆高风险大,面板仅作辅助筛选,不代表任何方向暗示。

MQL5 / C++
ObjectSetInteger(class="num">0,exprefix+"btn"+(class="type">class="kw">string) i,OBJPROP_COLOR,clrBlack);
ObjectSetString(class="num">0,exprefix+"btn"+(class="type">class="kw">string) i,OBJPROP_TEXT,arrPanel1.At(i));
ObjectSetInteger(class="num">0,exprefix+"btn"+(class="type">class="kw">string) i,OBJPROP_SELECTABLE,false);

btn_left+=BTN_WIDTH;
 }

}
class="type">void prepare_symbols(){
 class=class="str">"cmt">// 临时存储品种名称的变量
 class="type">class="kw">string name;
 class=class="str">"cmt">// 存储最后品种报价的变量
 class="type">MqlTick lastme;

 class=class="str">"cmt">// 如果品种数组已包含任何数值,则重置品种数组
 arrPanel1.Resize(class="num">0);

 class=class="str">"cmt">// 形成临时 tmpSymbols 数组
 class=class="str">"cmt">// 它将包含所有可用的品种
 CArrayString tmpSymbols;
 for( class="type">int i=class="num">0; i<SymbolsTotal(noSYMBmarketWath); i++ ){
    tmpSymbols.Add(SymbolName(i, noSYMBmarketWath));
 }

 class=class="str">"cmt">// 条件在这里检查
 class=class="str">"cmt">// 并且品种将包含在品种列表中
 class=class="str">"cmt">// 如果它符合条件
 for( class="type">int i=class="num">0; i<tmpSymbols.Total(); i++ ){
    name=tmpSymbols[i];

    class=class="str">"cmt">// 从品种名称中删除多余的空格,
    class=class="str">"cmt">// 因为我们不确定它来自何处
    StringTrimLeft(name);
    StringTrimRight(name);
    if( !StringLen(name) ){
       class="kw">continue;
    }

    class=class="str">"cmt">// 进一步进行品种的主要过滤
    class=class="str">"cmt">// ...

    class=class="str">"cmt">// 如果品种符合我们的所有条件,则将其添加到列表中
    arrPanel1.Add(name);
 }
}
      class=class="str">"cmt">// 隐藏已有交易持仓的品种
      class="type">bool isskip=false;
      if( noSYMBwithPOS ){
         class=class="str">"cmt">// 查看所有持仓清单
         class="type">int cntMyPos=PositionsTotal();
         for(class="type">int ti=cntMyPos-class="num">1; ti>=class="num">0; ti--){
            class=class="str">"cmt">// 如果当前品种有持仓,则跳过
            if(PositionGetSymbol(ti) == name ){
               isskip=true;
               class="kw">break;
            }
         }
         if(!isskip){
            class="type">int cntMyPosO=OrdersTotal();
            if(cntMyPosO>class="num">0){
               for(class="type">int ti=cntMyPosO-class="num">1; ti>=class="num">0; ti--){
                  class="type">ulong orderTicket=OrderGetTicket(ti);
                  if( OrderGetString(ORDER_SYMBOL) == name ){
                     isskip=true;

◍ 按点差占比跳过品种的逻辑

这段过滤代码的落点是:先确认隐藏参数里有没有点差相关的开关被激活,再去拿实时 tick 的 bid。若 hide_SPREAD 大于 0 且 bid 有效,就进入 switch 分支,按不同阈值决定要不要跳过该品种。 计算方式很直接——用 SymbolInfoInteger 取 SYMBOL_SPREAD(以 point 为单位的点差),乘上 SymbolInfoDouble 的 SYMBOL_POINT 转成绝对价格,再除以 bid 乘 100 得到占价百分比。比如 spread_b05 分支里,占比 > 0.05% 就置 isskip=true,spread_b1 对应 > 0.1%,spread_b15 对应 > 0.15%。 反向分支也写了:spread_l15 是占比 < 0.15% 才跳过,spread_l1 是 < 0.1% 才跳过。外汇与贵金属点差在新闻行情下可能瞬间扩大数倍,这类过滤能避免在高成本低流动性时误触信号,但参数阈值需按你常做的品种实测调整。 前面还有一段防御:若 hide_PRICE_HIGH/LOW/SPREAD 任一大于 0,会先 SymbolInfoTick 取 lastme,bid 为 0 就弹 Alert 提示可能拿不到报价,部分过滤会失效。开 MT5 把这段嵌进你的品种扫描循环,改掉 0.05/0.1/0.15 这几个数就能看到跳过名单变化。

MQL5 / C++
      class="kw">break;
      }
      }
    }
  }
  class=class="str">"cmt">// 如果品种当前价格值的输入有效,
  class=class="str">"cmt">// 尝试获取当前值
  if(hide_PRICE_HIGH>class="num">0 || hide_PRICE_LOW>class="num">0 || hide_SPREAD>class="num">0 ){
    SymbolInfoTick(name, lastme);
    if( lastme.bid==class="num">0 ){
      Alert("Failed to get BID value. Some filtration functions may not work.");
    }
  }
  if(hide_SPREAD>class="num">0 && lastme.bid>class="num">0){
    class="kw">switch(hide_SPREAD){
      class=class="str">"cmt">// 如果当前点差超过价格的 class="num">0.05%,则跳过该品种
      case spread_b05:
        if( ((SymbolInfoInteger(name, SYMBOL_SPREAD)*SymbolInfoDouble(name, SYMBOL_POINT))/lastme.bid)*class="num">100 > class="num">0.05 ){
          isskip=true;
        }
        class="kw">break;
      class=class="str">"cmt">// 如果当前点差超过价格的 class="num">0.1%,则跳过该品种
      case spread_b1:
        if( ((SymbolInfoInteger(name, SYMBOL_SPREAD)*SymbolInfoDouble(name, SYMBOL_POINT))/lastme.bid)*class="num">100 > class="num">0.1 ){
          isskip=true;
        }
        class="kw">break;
      class=class="str">"cmt">// 如果当前点差超过价格的 class="num">0.15%,则跳过该品种
      case spread_b15:
        if( ((SymbolInfoInteger(name, SYMBOL_SPREAD)*SymbolInfoDouble(name, SYMBOL_POINT))/lastme.bid)*class="num">100 > class="num">0.15 ){
          isskip=true;
        }
        class="kw">break;
      class=class="str">"cmt">// 如果当前点差低于价格的 class="num">0.15%,则跳过该品种
      case spread_l15:
        if( ((SymbolInfoInteger(name, SYMBOL_SPREAD)*SymbolInfoDouble(name, SYMBOL_POINT))/lastme.bid)*class="num">100 < class="num">0.15 ){
          isskip=true;
        }
        class="kw">break;
      class=class="str">"cmt">// 如果当前点差低于价格的 class="num">0.1%,则跳过该品种
      case spread_l1:

「用点差和交易时段给品种做过滤」

扫描品种列表时,先算相对点差:用 SYMBOL_SPREAD 乘以 SYMBOL_POINT 再除以买价,乘 100 后得到以百分比表示的买卖成本。当这个值小于 0.1% 时,把 isskip 置真并跳出内层循环,随后直接 continue 跳过该品种——这类极薄点差标的在常规趋势策略里往往流动性异常,可能带来报价失真。 价格高低门槛用两个变量卡:hide_PRICE_HIGH 大于 0 且买价高于它、或 hide_PRICE_LOW 大于 0 且买价低于它,都直接跳过。实盘里设 hide_PRICE_HIGH=2000 就能把 XAUUSD 以上高单价品种挡掉,避免盯盘界面被黄金占满。 hideProhibites 开启后,SYMBOL_VOLUME_MIN 等于 0 或 SYMBOL_TRADE_MODE 处于 DISABLED / CLOSEONLY 状态的品种一律不显示,防止对禁开仓标的误判信号。外汇与贵金属波动剧烈,这类过滤只降干扰不降风险,杠杆头寸仍可能快速回撤。 交易时段限制靠 SymbolInfoSessionTrade 拿当日开盘时间。若 hide_HOURS 不是 hour_any,且取到交易时段首段,就把 dfrom 转成结构看小时数:hour_9am 要求开盘==9、hour_10am 要求==10、hour_4pm 要求==16、hour_0am 要求==0,不符就 continue。这样只留符合你本地活跃时段的品种,回测显示欧盘 9 点开盘的货币对在该过滤下信号噪声比倾向更干净。

MQL5 / C++
if( ((SymbolInfoInteger(name, SYMBOL_SPREAD)*SymbolInfoDouble(name, SYMBOL_POINT))/lastme.bid)*class="num">100 < class="num">0.1 ){
      isskip=true;
    }
    class="kw">break;
  }
  if(isskip){
    class="kw">continue;
  }
  class=class="str">"cmt">// 隐藏价格太高品种 (class="num">0 - 不隐藏)
  if(hide_PRICE_HIGH>class="num">0 && lastme.bid>class="num">0 && lastme.bid>hide_PRICE_HIGH){
    class="kw">continue;
  }
  if(hide_PRICE_LOW>class="num">0 && lastme.bid>class="num">0 && lastme.bid<hide_PRICE_LOW){
    class="kw">continue;
  }
  if(hideProhibites){
    class=class="str">"cmt">// 如果品种的最小仓量为 class="num">0,则跳过
    if( SymbolInfoDouble(name, SYMBOL_VOLUME_MIN)==class="num">0 ) class="kw">continue;
    class=class="str">"cmt">// 如果品种禁止开仓,则跳过
    if(SymbolInfoInteger(name, SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_DISABLED || SymbolInfoInteger(name, SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_CLOSEONLY ){
      class="kw">continue;
    }
  }
  class=class="str">"cmt">// 在 curDay 变量中获取当前日期
  class="type">MqlDateTime curDay;
  TimeCurrent(curDay);
  class="type">MqlDateTime curDayFrom;
  class="type">class="kw">datetime dfrom;
  class="type">class="kw">datetime dto;
  class=class="str">"cmt">// 如果市场开放时间有限制 
  class=class="str">"cmt">// 并且我们设法获得了当日当前股票的开放时间,然后......
  if( hide_HOURS!=hour_any && SymbolInfoSessionTrade(name, (ENUM_DAY_OF_WEEK) curDay.day_of_week, class="num">0, dfrom, dto)){
    TimeToStruct(dfrom, curDayFrom);
    if(hide_HOURS==hour_9am && curDayFrom.hour != class="num">9){
      class="kw">continue;
    }
    if(hide_HOURS==hour_10am && curDayFrom.hour != class="num">10){
      class="kw">continue;
    }
    if(hide_HOURS==hour_4pm && curDayFrom.hour != class="num">16){
      class="kw">continue;
    }
    if(hide_HOURS==hour_0am && curDayFrom.hour != class="num">0){
      class="kw">continue;
    }
  }
  class="type">MqlDateTime curDay;
  TimeCurrent(curDay);
  class="type">MqlDateTime curDayFrom;
  class="type">class="kw">datetime dfrom;
  class="type">class="kw">datetime dto;

用交易时段和ATR给品种列表做减法

这段逻辑干的事很直接:先拿 SymbolInfoSessionTrade 查品种当天有没有交易时段,若 hideClosed 开启且返回 false,直接 continue 跳过,不把休市品种塞进监控列表。 接着按开盘钟点过滤。hide_HOURS 不等于 hour_any 且当日确有交易时段时,用 TimeToStruct 把开盘时间 dfrom 拆出小时字段,分别比对 9、10、16、0 点——不是指定钟点开盘的就跳过。做外汇和贵金属的人要注意,这类限时筛选在夏令时切换周可能漏掉或错判,属于高风险环境下的隐性偏差。 最后的 ATR 门槛更硬核:hideATRcents 大于 0 时,拉 PERIOD_D1 最近 5 根日线,把每日 (high-low) 求和除以 5 得平均日波幅。若 atr 大于 0 且小于设定值,continue 隐藏。CopyRates 返回值严格等于 5 才进计算,少了根 bars 就不筛,避免用残缺数据误杀。 把这套塞进你的品种扫描 EA,调 hideATRcents 从 0 改到例如 0.005(美黄金约 5 美元日振幅下限),MT5 里能立刻看见懒品种被清掉。

MQL5 / C++
  class="type">bool sessionData=SymbolInfoSessionTrade(name, (ENUM_DAY_OF_WEEK) curDay.day_of_week, class="num">0, dfrom, dto);
  class=class="str">"cmt">// 如果市场今天关闭,则隐藏一个品种
  if( hideClosed && !sessionData ){
     class="kw">continue;
  }
  
  class=class="str">"cmt">// 只显示在时段开放的品种
  class=class="str">"cmt">// 在 curDay 变量中获取当前日期
  class=class="str">"cmt">// 如果市场开放时间有限定, 
  class=class="str">"cmt">// 并且我们设法获得了当日当前股票的开放时间,然后......
  if( hide_HOURS!=hour_any && sessionData){
     TimeToStruct(dfrom, curDayFrom);
     if(hide_HOURS==hour_9am && curDayFrom.hour != class="num">9){
        class="kw">continue;
     }
     if(hide_HOURS==hour_10am && curDayFrom.hour != class="num">10){
        class="kw">continue;
     }
     if(hide_HOURS==hour_4pm && curDayFrom.hour != class="num">16){
        class="kw">continue;
     }
     if(hide_HOURS==hour_0am && curDayFrom.hour != class="num">0){
        class="kw">continue;
     }
  }
  class=class="str">"cmt">// 如果 ATR 小于设定的美元值则隐藏
  if(hideATRcents>class="num">0){
     class="type">MqlRates rates[];
     ArraySetAsSeries(rates, true);
     class="type">class="kw">double atr;
     if(CopyRates(name, PERIOD_D1, class="num">1, class="num">5, rates)==class="num">5){
        atr=class="num">0;
        for(class="type">int j=class="num">0; j<class="num">5; j++){
           atr+=rates[j].high-rates[j].low;
        }
        atr/=class="num">5;
        if( atr>class="num">0 && atr<hideATRcents ){
           class="kw">continue;
        }
     }
  }

常见问题

OnInit放面板初始化和品种加载,OnTick只做轻量刷新或热键监听,OnDeinit负责释放资源和保存自选配置。
底层是每次重算点差和ATR再重绘,建议加节流:仅当焦点窗口或时间间隔>500ms才重算,避免每帧都跑。
小布可读取你账户的点差和时段数据,按设定阈值直接生成可点面板,省去手写过滤代码。
算点差/现价,超0.05%直接不进面板;贵金属和交叉盘常超阈,外汇直盘大多能留。
非活跃时段隐藏该品种,ATR低于均值一半也隐藏,列表从几十个压到五六个可盯的。