利用 MQL5 和 MQL4 实现的选择和导航实用程序:添加"homework"选项卡并保存图形对象·综合运用
「用文本行批量落地图表对象属性」
把外部配置按二级分隔符拆开以后,每一行若能被切成 4 段,就代表一条对象属性指令;不足 4 段时则把该行视为对象名。实测中,只有 ArraySize 等于 4 的切片才会进入属性写入分支,其余情况只更新 tmpObjName。 类型标记排在第一段:int 走整数属性,double 走浮点属性,string 走文本属性。int 段里若第二段是 OBJPROP_TYPE,就先以第三段给定的类型值 ObjectCreate 建对象;否则用 ObjectSetInteger 写属性,且第三段值必须 >=0 才落盘。 double 与 string 分支同理,负值或空串会被直接跳过——这是避免脏配置把图表对象写崩的兜底。最后无论走哪条分支,都会给对象补一句 ObjectSetInteger(..., OBJPROP_SELECTABLE, true),让它在 MT5 上可被鼠标选中。 开 MT5 随便建个脚本,把这段逻辑套进你自己的配置解析循环,改 sep2 和 tmpObjLine 来源就能验证对象是否按预期生成并可点选。外汇与贵金属图表对象重绘涉及实时报价,属于高风险操作环境,参数误写可能令图形层异常。
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 按索引取名字。凡是名字里含有 exprefix、fix、take、stop loss、sell、buy 的,一律跳过——这些通常是 EA 自己挂的止损止盈或信号线,不进存档。
剩下的对象还要再过一道类型筛:只有 OBJ_HLINE、OBJ_TEXT、OBJ_LABEL 三类能被序列化,其余 continue 直接丢弃。通过后,代码把类型、颜色、线型、线宽四个整数属性用 StringAdd 拼成 属性名~属性ID~属性值 的管道分隔串,方便后续写文件时还原。
这段逻辑对外汇和贵金属图表同样适用,但这类品种跳空多、对象杂,存档前最好确认 arrPanels 索引没越界,否则 tmpName 会拿到空串,文件名清洗后可能撞车。MT5 上开个多对象图表跑一遍,看 saveG 里条目数是否等于被保留的对象数,就能验证过滤是否如预期。
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 类属性接时间。外汇与贵金属图表对象随报价刷新,这类抓取在高波动时段可能读到中间态,属正常高风险现象。
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_BIN | FILE_ANSI 打开 _graph_*.bin 一次性存盘。 |
|---|
读回时反向操作:FileOpen 用 FILE_READ 模式载入同一 bin 文件,saveG.Load 后按 '|' 切出每行对象,再按 '~' 切成 4 段子串。代码里用 ArraySize(tmpObjSubLine)==4 判定一条完整属性记录,跳过 j==0 的头部,只对 int/string/double 三类分别处理。 实盘里这套机制意味着你切换品种面板时,前期手画的支撑阻力线能以二进制形式保留,重开 MT5 后倾向直接复原而非重画。外汇与贵金属波动剧烈,对象坐标基于历史价,回读后仍需人工核对是否偏移。
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,就能用一套数据还原别人模板里的画线布局;外汇与贵金属波动剧烈,自动化挂对象仍需人工复核锚点,避免极端跳空导致错位。
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 会导致标签在原坐标上漂移十数像素,肉眼可察。 这类写法适合把对象配置写成文本行、再由脚本一键还原到图表。外汇与贵金属市场波动剧烈、杠杆风险高,任何自动重建对象的逻辑都先在策略测试器里用小样本验证,再上真实图表。
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] 的实际类型,能少踩一半强转坑。
}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 上跑一遍,确认临时对象可被鼠标选中且不触发图表重绘卡顿,再并入自己的模板。外汇与贵金属波动剧烈,任何对象交互逻辑都应在模拟盘验证后再上真仓。
}
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 是否存在,存在就读进临时变量再走同样的拆分逻辑。
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 即走文件分支,否则走全量分支。
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 跑一周盘后复盘最实在。