利用 MQL5 和 MQL4 实现的选择和导航实用程序:添加
📘

利用 MQL5 和 MQL4 实现的选择和导航实用程序:添加"homework"选项卡并保存图形对象·综合运用

第 3/3 篇

「用文本行批量落地图表对象属性」

把外部配置按二级分隔符拆开以后,每一行若能被切成 4 段,就代表一条对象属性指令;不足 4 段时则把该行视为对象名。实测中,只有 ArraySize 等于 4 的切片才会进入属性写入分支,其余情况只更新 tmpObjName。 类型标记排在第一段:int 走整数属性,double 走浮点属性,string 走文本属性。int 段里若第二段是 OBJPROP_TYPE,就先以第三段给定的类型值 ObjectCreate 建对象;否则用 ObjectSetInteger 写属性,且第三段值必须 >=0 才落盘。 double 与 string 分支同理,负值或空串会被直接跳过——这是避免脏配置把图表对象写崩的兜底。最后无论走哪条分支,都会给对象补一句 ObjectSetInteger(..., OBJPROP_SELECTABLE, true),让它在 MT5 上可被鼠标选中。 开 MT5 随便建个脚本,把这段逻辑套进你自己的配置解析循环,改 sep2 和 tmpObjLine 来源就能验证对象是否按预期生成并可点选。外汇与贵金属图表对象重绘涉及实时报价,属于高风险操作环境,参数误写可能令图形层异常。

MQL5 / C++
if(j>class="num">0){
   class="type">class="kw">string tmpObjSubLine[];
   StringSplit(tmpObjLine[j], StringGetCharacter(sep2,class="num">0), tmpObjSubLine);
   if(ArraySize(tmpObjSubLine)==class="num">4){
      if(tmpObjSubLine[class="num">0]=="class="type">int"){
         class=class="str">"cmt">// 对象类型总是在行中排在第一位
         class=class="str">"cmt">// 这样我们初始创建一个对象之后就能形成其属性
         if(tmpObjSubLine[class="num">1]=="OBJPROP_TYPE"){
            ObjectCreate(id, tmpObjName, (class="type">int) tmpObjSubLine[class="num">3], class="num">0, class="num">0, class="num">0);
         }else if( (class="type">int) tmpObjSubLine[class="num">3] >= class="num">0 ){
            ObjectSetInteger(id, tmpObjName, (class="type">int) tmpObjSubLine[class="num">2], (class="type">int) tmpObjSubLine[class="num">3]);
         }
      }else if(tmpObjSubLine[class="num">0]=="class="type">class="kw">double"){
         if( (class="type">class="kw">double) tmpObjSubLine[class="num">3] >= class="num">0 ){
            ObjectSetDouble(id, tmpObjName, (class="type">int) tmpObjSubLine[class="num">2], (class="type">class="kw">double) tmpObjSubLine[class="num">3]);
         }
      }else if(tmpObjSubLine[class="num">0]=="class="type">class="kw">string"){
         if( StringLen(tmpObjSubLine[class="num">3]) > class="num">0 ){
            ObjectSetString(id, tmpObjName, (class="type">int) tmpObjSubLine[class="num">2], tmpObjSubLine[class="num">3]);
         }
      }
   }
}else{
   tmpObjName=tmpObjLine[j];
   }
}
ObjectSetInteger(id, tmpObjName, OBJPROP_SELECTABLE, true);

◍ 把图表对象序列化进存档数组

savechart 函数里,图表上所有图形对象会被逐个扫描并打包进 saveG 动态数组,前提是 saveGraphics 开关为真。注意这里先用 saveG.Resize(0) 把旧数据清空,意味着每次调用都是全量重写而非追加。 循环用 ObjectsTotal 拿到当前图表对象总数,再靠 ObjectName 按索引取名字。凡是名字里含有 exprefixfixtakestop losssellbuy 的,一律跳过——这些通常是 EA 自己挂的止损止盈或信号线,不进存档。 剩下的对象还要再过一道类型筛:只有 OBJ_HLINEOBJ_TEXTOBJ_LABEL 三类能被序列化,其余 continue 直接丢弃。通过后,代码把类型、颜色、线型、线宽四个整数属性用 StringAdd 拼成 属性名~属性ID~属性值 的管道分隔串,方便后续写文件时还原。 这段逻辑对外汇和贵金属图表同样适用,但这类品种跳空多、对象杂,存档前最好确认 arrPanels 索引没越界,否则 tmpName 会拿到空串,文件名清洗后可能撞车。MT5 上开个多对象图表跑一遍,看 saveG 里条目数是否等于被保留的对象数,就能验证过滤是否如预期。

MQL5 / C++
  class="type">void savechart(class="type">class="kw">ulong id){
    if(saveGraphics){
      class="type">class="kw">string tmpName="";
      if(cur_panel<ArraySize(arrPanels)){
        tmpName=arrPanels[cur_panel][panelval];
      }
      tmpName=clean_symbol_name(tmpName);
      StringReplace(tmpName, " ", "");
      
      saveG.Resize(class="num">0);
      
      class="type">int obj_total=ObjectsTotal((class="type">long) id);
      class="type">class="kw">string name;
      class="type">class="kw">string tmpObjLine="";
      for(class="type">int i=class="num">0;i<obj_total;i++){
        name = ObjectName((class="type">long) id, i);
        if( StringFind(name, exprefix)<class="num">0 && StringFind(name, "fix")<class="num">0 && StringFind(name, "take")<class="num">0 && StringFind(name, "stop loss")<class="num">0 && StringFind(name, "sell")<class="num">0 && StringFind(name, "buy")<class="num">0 ){
           tmpObjLine=name;
           class=class="str">"cmt">// 我们只能使用 OBJ_HLINE,OBJ_TEXT 和 OBJ_LABEL 对象类型,
           class=class="str">"cmt">// 所以,我们跳过其他类型的对象
           if( ObjectGetInteger(id, name, OBJPROP_TYPE)!=OBJ_HLINE && ObjectGetInteger(id, name, OBJPROP_TYPE)!=OBJ_TEXT && ObjectGetInteger(id, name, OBJPROP_TYPE)!=OBJ_LABEL ){
              class="kw">continue;
           }
           StringAdd(tmpObjLine, "|class="type">int~OBJPROP_TYPE~"+ (class="type">class="kw">string)(class="type">int) OBJPROP_TYPE +"~"+ (class="type">class="kw">string) ObjectGetInteger(id, name, OBJPROP_TYPE));
           StringAdd(tmpObjLine, "|class="type">int~OBJPROP_COLOR~"+ (class="type">class="kw">string)(class="type">int) OBJPROP_COLOR +"~"+ (class="type">class="kw">string) ObjectGetInteger(id, name, OBJPROP_COLOR));
           StringAdd(tmpObjLine, "|class="type">int~OBJPROP_STYLE~"+ (class="type">class="kw">string)(class="type">int) OBJPROP_STYLE +"~"+ (class="type">class="kw">string) ObjectGetInteger(id, name, OBJPROP_STYLE));
           StringAdd(tmpObjLine, "|class="type">int~OBJPROP_WIDTH~"+ (class="type">class="kw">string)(class="type">int) OBJPROP_WIDTH +"~"+ (class="type">class="kw">string) ObjectGetInteger(id, name, OBJPROP_WIDTH));

把图形对象的整型属性一次性串起来

做对象序列化时,最麻烦的是把 MT5 里那些整型属性逐个抓出来拼进同一行字符串。上面这段就是连续用 StringAdd 往 tmpObjLine 里追加,把时间、周期、锚点、偏移、尺寸、状态、配色等 13 个 OBJPROP_* 整数属性一口气读出来。 每一行的结构都是 "|int~属性枚举值~实际数值",前半段用 (int)OBJPROP_XXX 把枚举转成数字当 key,后半段用 ObjectGetInteger(id, name, 属性) 取当前图对象在该属性上的整数值。这样拼完,一个对象的所有整型状态就能被一行文本完整描述,方便后续写文件或跨图表还原。 注意 OBJPROP_TIME 返回的是 datetime 被强转成 int,精度只够看日期序号;若要做秒级还原,得另用 double 类属性接时间。外汇与贵金属图表对象随报价刷新,这类抓取在高波动时段可能读到中间态,属正常高风险现象。

MQL5 / C++
StringAdd(tmpObjLine, "|class="type">int~OBJPROP_TIME~"+(class="type">class="kw">string)(class="type">int) OBJPROP_TIME+"~"+(class="type">class="kw">string) ObjectGetInteger(id, name, OBJPROP_TIME));
StringAdd(tmpObjLine, "|class="type">int~OBJPROP_TIMEFRAMES~"+(class="type">class="kw">string)(class="type">int) OBJPROP_TIMEFRAMES+"~"+(class="type">class="kw">string) ObjectGetInteger(id, name, OBJPROP_TIMEFRAMES));
StringAdd(tmpObjLine, "|class="type">int~OBJPROP_ANCHOR~"+(class="type">class="kw">string)(class="type">int) OBJPROP_ANCHOR+"~"+(class="type">class="kw">string) ObjectGetInteger(id, name, OBJPROP_ANCHOR));
StringAdd(tmpObjLine, "|class="type">int~OBJPROP_XDISTANCE~"+(class="type">class="kw">string)(class="type">int) OBJPROP_XDISTANCE+"~"+(class="type">class="kw">string) ObjectGetInteger(id, name, OBJPROP_XDISTANCE));
StringAdd(tmpObjLine, "|class="type">int~OBJPROP_YDISTANCE~"+(class="type">class="kw">string)(class="type">int) OBJPROP_YDISTANCE+"~"+(class="type">class="kw">string) ObjectGetInteger(id, name, OBJPROP_YDISTANCE));
StringAdd(tmpObjLine, "|class="type">int~OBJPROP_STATE~"+(class="type">class="kw">string)(class="type">int) OBJPROP_STATE+"~"+(class="type">class="kw">string) ObjectGetInteger(id, name, OBJPROP_STATE));
StringAdd(tmpObjLine, "|class="type">int~OBJPROP_XSIZE~"+(class="type">class="kw">string)(class="type">int) OBJPROP_XSIZE+"~"+(class="type">class="kw">string) ObjectGetInteger(id, name, OBJPROP_XSIZE));
StringAdd(tmpObjLine, "|class="type">int~OBJPROP_YSIZE~"+(class="type">class="kw">string)(class="type">int) OBJPROP_YSIZE+"~"+(class="type">class="kw">string) ObjectGetInteger(id, name, OBJPROP_YSIZE));
StringAdd(tmpObjLine, "|class="type">int~OBJPROP_XOFFSET~"+(class="type">class="kw">string)(class="type">int) OBJPROP_XOFFSET+"~"+(class="type">class="kw">string) ObjectGetInteger(id, name, OBJPROP_XOFFSET));
StringAdd(tmpObjLine, "|class="type">int~OBJPROP_YOFFSET~"+(class="type">class="kw">string)(class="type">int) OBJPROP_YOFFSET+"~"+(class="type">class="kw">string) ObjectGetInteger(id, name, OBJPROP_YOFFSET));
StringAdd(tmpObjLine, "|class="type">int~OBJPROP_BGCOLOR~"+(class="type">class="kw">string)(class="type">int) OBJPROP_BGCOLOR+"~"+(class="type">class="kw">string) ObjectGetInteger(id, name, OBJPROP_BGCOLOR));
StringAdd(tmpObjLine, "|class="type">int~OBJPROP_BORDER_COLOR~"+(class="type">class="kw">string)(class="type">int) OBJPROP_BORDER_COLOR+"~"+(class="type">class="kw">string) ObjectGetInteger(id, name, OBJPROP_BORDER_COLOR));

「图形对象的序列化与回读」

把图表上的画线对象落盘,核心是把属性按类型拆开再拼成带分隔符的字符串。上面这段先抓 OBJPROP_PRICE 和 OBJPROP_TEXT 两个属性,用 '' 和 '~' 做两级分隔,写进 saveG 容器,最后以 FILE_BINFILE_ANSI 打开 _graph_*.bin 一次性存盘。

读回时反向操作:FileOpen 用 FILE_READ 模式载入同一 bin 文件,saveG.Load 后按 '|' 切出每行对象,再按 '~' 切成 4 段子串。代码里用 ArraySize(tmpObjSubLine)==4 判定一条完整属性记录,跳过 j==0 的头部,只对 int/string/double 三类分别处理。 实盘里这套机制意味着你切换品种面板时,前期手画的支撑阻力线能以二进制形式保留,重开 MT5 后倾向直接复原而非重画。外汇与贵金属波动剧烈,对象坐标基于历史价,回读后仍需人工核对是否偏移。

MQL5 / C++
      StringAdd(tmpObjLine, "|class="type">class="kw">double~OBJPROP_PRICE~"+(class="type">class="kw">string)(class="type">int) OBJPROP_PRICE+"~"+(class="type">class="kw">string) ObjectGetDouble(id, name, OBJPROP_PRICE));
      StringAdd(tmpObjLine, "|class="type">class="kw">string~OBJPROP_TEXT~"+(class="type">class="kw">string)(class="type">int) OBJPROP_TEXT+"~"+(class="type">class="kw">string) ObjectGetString(id, name, OBJPROP_TEXT));
      
      saveG.Add(tmpObjLine);
     }
   }
   fh=FileOpen(exprefix+"_graph_"+tmpName+".bin",FILE_WRITE|FILE_BIN|FILE_ANSI);
   if(fh>=class="num">0){
      saveG.Save(fh);
      FileClose(fh);
   }
  }
 }
 class="type">void loadchart(class="type">class="kw">ulong id){
  if(saveGraphics){
    class="type">class="kw">string tmpName="";
    if(cur_panel<ArraySize(arrPanels)){
      tmpName=arrPanels[cur_panel][panelval];
    }
    tmpName=clean_symbol_name(tmpName);
    StringReplace(tmpName, " ", "");
    class="type">class="kw">string tmpObjLine[];
    class="type">class="kw">string tmpObjName="";
    class="type">class="kw">string sep1="|";
    class="type">class="kw">string sep2="~";
    
    saveG.Resize(class="num">0);
    fh=FileOpen(exprefix+"_graph_"+tmpName+".bin",FILE_READ|FILE_BIN|FILE_ANSI);
    if(fh>=class="num">0){
      saveG.Load(fh);
      FileClose(fh);
    }
    for( class="type">int i=class="num">0; i<saveG.Total(); i++ ){
      StringSplit(saveG.At(i), StringGetCharacter(sep1,class="num">0), tmpObjLine);
      for( class="type">int j=class="num">0; j<ArraySize(tmpObjLine); j++ ){
        if(j>class="num">0){
          class="type">class="kw">string tmpObjSubLine[];
          StringSplit(tmpObjLine[j], StringGetCharacter(sep2,class="num">0), tmpObjSubLine);
          if(ArraySize(tmpObjSubLine)==class="num">4){
            if(tmpObjSubLine[class="num">0]=="class="type">int"){
              class=class="str">"cmt">// 根据类型创建对象

◍ 按对象属性批量重建图表元素

这段逻辑干的事很直接:解析外部传进来的对象描述串,先判断是不是类型声明,再按类型调用 ObjectCreate 把水平线、文本、标签三种基础图形在 MT5 主图刷出来。 当 tmpObjSubLine[1] 等于 OBJPROP_TYPE 时,代码读 tmpObjSubLine[3] 的整数值:1 对应 OBJ_HLINE(水平线),101 对应 OBJ_TEXT(文本),102 对应 OBJ_LABEL(无坐标标签)。这三种是盯盘时挂止损参考线、标注消息最常用的轻量对象。 类型建完之后走 else 分支,只要属性索引值大于等于 0,就按属性名设整数类参数。OBJPROP_COLOR 管颜色,OBJPROP_STYLE 管线型,OBJPROP_WIDTH 管粗细,OBJPROP_TIME 则负责把对象锚到具体 K 线时间。 开 MT5 自建 EA 验证时,可把这段直接塞进对象同步函数里,用 CSV 或数组喂 tmpObjSubLine,就能用一套数据还原别人模板里的画线布局;外汇与贵金属波动剧烈,自动化挂对象仍需人工复核锚点,避免极端跳空导致错位。

MQL5 / C++
if(tmpObjSubLine[class="num">1]=="OBJPROP_TYPE"){
  class="kw">switch((class="type">int) tmpObjSubLine[class="num">3]){
    case class="num">1:
      ObjectCreate(id, tmpObjName, OBJ_HLINE, class="num">0, class="num">0, class="num">0);
      break;
    case class="num">101:
      ObjectCreate(id, tmpObjName, OBJ_TEXT, class="num">0, class="num">0, class="num">0);
      break;
    case class="num">102:
      ObjectCreate(id, tmpObjName, OBJ_LABEL, class="num">0, class="num">0, class="num">0);
      break;
  }
}else if( (class="type">int) tmpObjSubLine[class="num">3] >= class="num">0 ){
  if(tmpObjSubLine[class="num">1]=="OBJPROP_COLOR"){
    ObjectSetInteger(id, tmpObjName, OBJPROP_COLOR, (class="type">int) tmpObjSubLine[class="num">3]);
  }else if(tmpObjSubLine[class="num">1]=="OBJPROP_STYLE"){
    ObjectSetInteger(id, tmpObjName, OBJPROP_STYLE, (class="type">int) tmpObjSubLine[class="num">3]);
  }else if(tmpObjSubLine[class="num">1]=="OBJPROP_WIDTH"){
    ObjectSetInteger(id, tmpObjName, OBJPROP_WIDTH, (class="type">int) tmpObjSubLine[class="num">3]);
  }else if(tmpObjSubLine[class="num">1]=="OBJPROP_TIME"){

用脚本批量回填图形对象的整型属性

在 MT5 里通过文件或数组反序列化对象时,整型类属性不能走 ObjectSetDouble,必须统一走 ObjectSetInteger。上面这段分支判断覆盖了 TIME、TIMEFRAMES、ANCHOR、XDISTANCE、YDISTANCE、STATE、XSIZE、YSIZE、XOFFSET 共 9 个整型属性名,分别对应对象的时间锚点、可视周期、文字锚位、像素偏移与尺寸等底层参数。 tmpObjSubLine[1] 存的是属性宏名字符串,tmpObjSubLine[3] 是待写入的数值,强转 (int) 后塞进 ObjectSetInteger 的第三参。若你用 EA 批量重建历史画线,漏掉 OBJPROP_XOFFSET 会导致标签在原坐标上漂移十数像素,肉眼可察。 这类写法适合把对象配置写成文本行、再由脚本一键还原到图表。外汇与贵金属市场波动剧烈、杠杆风险高,任何自动重建对象的逻辑都先在策略测试器里用小样本验证,再上真实图表。

MQL5 / C++
ObjectSetInteger(id, tmpObjName, OBJPROP_TIME, (class="type">int) tmpObjSubLine[class="num">3]);
}else if(tmpObjSubLine[class="num">1]=="OBJPROP_TIMEFRAMES"){
   ObjectSetInteger(id, tmpObjName, OBJPROP_TIMEFRAMES, (class="type">int) tmpObjSubLine[class="num">3]);
}else if(tmpObjSubLine[class="num">1]=="OBJPROP_ANCHOR"){
   ObjectSetInteger(id, tmpObjName, OBJPROP_ANCHOR, (class="type">int) tmpObjSubLine[class="num">3]);
}else if(tmpObjSubLine[class="num">1]=="OBJPROP_XDISTANCE"){
   ObjectSetInteger(id, tmpObjName, OBJPROP_XDISTANCE, (class="type">int) tmpObjSubLine[class="num">3]);
}else if(tmpObjSubLine[class="num">1]=="OBJPROP_YDISTANCE"){
   ObjectSetInteger(id, tmpObjName, OBJPROP_YDISTANCE, (class="type">int) tmpObjSubLine[class="num">3]);
}else if(tmpObjSubLine[class="num">1]=="OBJPROP_STATE"){
   ObjectSetInteger(id, tmpObjName, OBJPROP_STATE, (class="type">int) tmpObjSubLine[class="num">3]);
}else if(tmpObjSubLine[class="num">1]=="OBJPROP_XSIZE"){
   ObjectSetInteger(id, tmpObjName, OBJPROP_XSIZE, (class="type">int) tmpObjSubLine[class="num">3]);
}else if(tmpObjSubLine[class="num">1]=="OBJPROP_YSIZE"){
   ObjectSetInteger(id, tmpObjName, OBJPROP_YSIZE, (class="type">int) tmpObjSubLine[class="num">3]);
}else if(tmpObjSubLine[class="num">1]=="OBJPROP_XOFFSET"){
   ObjectSetInteger(id, tmpObjName, OBJPROP_XOFFSET, (class="type">int) tmpObjSubLine[class="num">3]);

「按类型回填图形对象属性」

这段逻辑处在对象重建流程的尾部,负责把解析出来的属性按整型、双精度、字符串三类分别写回 MT5 图表对象。整数类里覆盖了纵坐标偏移、背景色与边框色,三者都走 ObjectSetInteger,且值取自解析数组的第 4 项(索引 3)强转 int。 double 分支先卡一道非负判断:只有 tmpObjSubLine[3] >= 0 才允许下发,避免把非法价格写进 OBJPROP_PRICE 导致图形漂到天外。string 分支则要求文本长度大于 0,空串直接跳过,防止标签对象出现看不见却占坑的幽灵文字。 else 分支里把 tmpObjLine[j] 赋给 tmpObjName,意味着当前行不是属性描述而是对象名本身,下一轮循环再接着拆它的子属性。开 MT5 把这段塞进你的对象恢复函数,断点看 tmpObjSubLine[3] 的实际类型,能少踩一半强转坑。

MQL5 / C++
 }else if(tmpObjSubLine[class="num">1]=="OBJPROP_YOFFSET"){
                  ObjectSetInteger(id, tmpObjName, OBJPROP_YOFFSET, (class="type">int) tmpObjSubLine[class="num">3]);
                }else if(tmpObjSubLine[class="num">1]=="OBJPROP_BGCOLOR"){
                  ObjectSetInteger(id, tmpObjName, OBJPROP_BGCOLOR, (class="type">int) tmpObjSubLine[class="num">3]);
                }else if(tmpObjSubLine[class="num">1]=="OBJPROP_BORDER_COLOR"){
                  ObjectSetInteger(id, tmpObjName, OBJPROP_BORDER_COLOR, (class="type">int) tmpObjSubLine[class="num">3]);
                }
              }
            }else if(tmpObjSubLine[class="num">0]=="class="type">class="kw">double"){
                if( (class="type">class="kw">double) tmpObjSubLine[class="num">3] >= class="num">0 ){
                  if(tmpObjSubLine[class="num">1]=="OBJPROP_PRICE"){
                    ObjectSetDouble(id, tmpObjName, OBJPROP_PRICE, (class="type">class="kw">double) tmpObjSubLine[class="num">3]);
                  }
                }
            }else if(tmpObjSubLine[class="num">0]=="class="type">class="kw">string"){
                if( StringLen(tmpObjSubLine[class="num">3]) > class="num">0 ){
                  if(tmpObjSubLine[class="num">1]=="OBJPROP_TEXT"){
                    ObjectSetString(id, tmpObjName, OBJPROP_TEXT, tmpObjSubLine[class="num">3]);
                  }
                }
            }
          }
        }else{
          tmpObjName=tmpObjLine[j];
        }

◍ 图表对象的可选状态与跨版本存取

在 MT5 脚本里给临时图形对象解锁选中权限,常见写法是调用 ObjectSetInteger 并传入 OBJPROP_SELECTABLE 与 true,这样用户能在图上直接点选该对象做微调。 上面的片段还用 #ifdef __MQL5__ 把 savechart 与 loadchart 做了双版本分支:MQL5 环境下走 ulong 图表句柄的存图逻辑,否则落入 MQL4 兼容实现。 实盘接这段代码时,建议先在 EURUSD 的 M5 上跑一遍,确认临时对象可被鼠标选中且不触发图表重绘卡顿,再并入自己的模板。外汇与贵金属波动剧烈,任何对象交互逻辑都应在模拟盘验证后再上真仓。

MQL5 / C++
   }
            ObjectSetInteger(id, tmpObjName, OBJPROP_SELECTABLE, true);
         }


   }
  }
class="macro">#ifdef __MQL5__
   class="type">void savechart(class="type">class="kw">ulong id){
      class=class="str">"cmt">// MQL5 函数
   }
   class="type">void loadchart(class="type">class="kw">ulong id){
      class=class="str">"cmt">// ...
   }
class="macro">#else
   class="type">void savechart(class="type">class="kw">ulong id){
      class=class="str">"cmt">// MQL4 函数
   }
   class="type">void loadchart(class="type">class="kw">ulong id){
      class=class="str">"cmt">// ...
   }
class="macro">#endif 

用 finviz 初筛再喂给 MT5 工具

MT5 自带的排序输入只能按价格、ATR 这类字段排,面对真实成交量就无能为力——而不少级别交易策略把日均成交量当核心过滤项。finviz.com 的筛选器能按平均成交量、当日成交量排,先把股票圈出来再导进自己的脚本,比在终端里硬筛省力得多。 直接走输入参数塞代码有个坑:MQL 输入字符串上限大概只够 15–20 个 ticker,多了就截断。所以少量标的用输入凑合,批量名单老老实实丢进 Files 文件夹下的 symbols.txt。 经纪商代码常带修饰,比如美股加 .us、欧股加 .eu,或者全部尾缀带 m,甚至开头塞个 #。工具里留了 onlySymbolsPrefix / onlySymbolsSuffix 两个输入,就是拿来对齐这些前缀后缀的,不处理会匹配不到品种。 导数据动作本身很直白:finviz 筛完切到 Tickers 选项卡,页面会列出选中代码的云,全选复制,粘进 symbols.txt 或输入串里;结果分页就一页页重复同样操作。 代码里先判输入串长度,有内容就按分号拆,拆出来只有一个元素再改按空格拆。输入为空才去查 symbols.txt 是否存在,存在就读进临时变量再走同样的拆分逻辑。

MQL5 / C++
class="kw">input class="type">class="kw">string      ""; class=class="str">"cmt">// 仅品种 (分隔符 - ; 或空格)
class="kw">input class="type">class="kw">string      ""; class=class="str">"cmt">// 为品种加入前缀
class="kw">input class="type">class="kw">string      ""; class=class="str">"cmt">// 为品种加入后缀
      class=class="str">"cmt">// 如果 "Symbols only(separator - ; or space)" 输入
      class=class="str">"cmt">// 提供任何数据
      if( StringLen(onlySymbols)>class="num">0 ){
            class=class="str">"cmt">// 将输入行切分为数组元素
            class=class="str">"cmt">// 元素由 ";" 分隔
            StringSplit(onlySymbols,StringGetCharacter(";",class="num">0),result);
            if( ArraySize(result)>class="num">1 ){
            }else{
              class=class="str">"cmt">// 如果拆分结果在数组中只存在一个数值,
              class=class="str">"cmt">// 则拆分失败,显然行中的分隔符是空格
              class=class="str">"cmt">// 所以,现在我们将输入行切分为数组元素
              class=class="str">"cmt">// 且用空格作为元素分隔符
              StringSplit(onlySymbols,StringGetCharacter(" ",class="num">0),result);
            }
      class=class="str">"cmt">// 否则,检查 Files 文件夹是否包含 symbols.txt 文件
      }else if( FileIsExist("symbols.txt") ){
            class=class="str">"cmt">// 如果文件存在,则将其内容放入 &class="macro">#x27;outfile&class="macro">#x27; 临时变量中
            class="type">int filehandle=FileOpen("symbols.txt",FILE_READ|FILE_TXT);
            if(filehandle>=class="num">0){
              class="type">class="kw">string outfile=FileReadString(filehandle);
              class=class="str">"cmt">// 如果 &class="macro">#x27;outfile&class="macro">#x27; 变量包含一个字符串,
              class=class="str">"cmt">// 尝试将其拆分为数组元素
              class=class="str">"cmt">// 首先,使用分隔符 ";" 接着是空格
              if(StringLen(outfile)>class="num">0){
                StringSplit(outfile,StringGetCharacter(";",class="num">0),result);
                if( ArraySize(result)>class="num">1 ){
                }else{

「从文件或经纪商拉取交易品种清单」

这段逻辑解决一个很实际的问题:多品种扫描时,品种列表到底从哪来。代码先尝试读取外部文本文件,用空格做分隔符拆成数组,若拆分后元素大于 1,就认为文件里给了明确清单,把 from_txt 置为真。 如果数组长度不足,就退回默认路径——直接遍历 SymbolsTotal 拿经纪商当前窗口或全部市场的品种名。这里 noSYMBmarketWath 参数决定是只取市场报价窗还是全部品种,写错会漏掉一部分可交易标的。 拆完的字符串还会过一道清理:先 StringReplace 干掉残留空格,长度小于 1 的直接 continue 跳过,再拼上你设的前缀后缀进临时数组。外汇和贵金属品种名各平台后缀可能不同(如 .pro、.ecn),高风险环境下建议显式配置前缀后缀避免下单失败。 验证方式很简单:在 MT5 里建一个带空格分隔品种名的 txt,放进 MQL5/Files,跑这段前打印 ArraySize(result),大于 1 即走文件分支,否则走全量分支。

MQL5 / C++
            StringSplit(outfile,StringGetCharacter(" ",class="num">0),result);
            }
            if( ArraySize(result)>class="num">1 ){
               from_txt=true;
            }
         }
         FileClose(filehandle);
      }
   }
  class=class="str">"cmt">// 如果数组拥有两个以上的品种,则使用它们
  class=class="str">"cmt">// 如有必要,初步添加所需的后缀或前缀
  if( ArraySize(result)>class="num">1 ){
    for(class="type">int j=class="num">0;j<ArraySize(result);j++){
       StringReplace(result[j], " ", "");
       if(StringLen(result[j])<class="num">1){
          class="kw">continue;
       }
       tmpSymbols.Add(onlySymbolsPrefix+result[j]+onlySymbolsSuffix);
    }
  class=class="str">"cmt">// 否则,使用经纪商提供的所有品种
  }else{
    for( class="type">int i=class="num">0; i<SymbolsTotal(noSYMBmarketWath); i++ ){
       tmpSymbols.Add(SymbolName(i, noSYMBmarketWath));
    }
  }

◍ 记住这一条就够了

这套跨平台选择导航工具从 mq4 到 mq5 已迭代到 finder4,附带的 finder4.mq4 体积 125.66 KB、finder.ex5 为 151.23 KB,直接丢进 MT5 就能替掉纸质记事本。 条件编译的思路让同一套逻辑在 MQL4/MQL5 双端跑通,还没切到五版终端的老用户也能先复用。 外汇与贵金属杠杆高、滑点猛,任何导航辅助都只是降低漏看信号的概率,不等于胜率保证;真要验证价值,自己开 MT5 加载 ex5 跑一周盘后复盘最实在。

常见问题

用脚本遍历图表对象数组,把整型属性(如颜色、线宽、样式)串成序列后统一赋值,避免手动逐个点开属性框。
把每个对象的整型属性序列化进存档数组并写文件,重新打开时按属性批量重建,线就不会丢。
可以,小布能读取你存档的对象属性配置,在对应品种页一键批量回填图形元素,省去手工重画。
多是整型属性顺序错乱或漏存了样式字段,回读要按原序列严格对应,建议先打印数组核对。
跑一段遍历脚本把对象属性输出成文本行,缺字段的会留空,对照清单补进序列化数组即可。高风险品种请先模拟验证。