MQL5 编程基础:字符串·综合运用
🔤

MQL5 编程基础:字符串·综合运用

(3/3)·从字符数组到 API 调用,12 节拆解字符串在 EA 与指标里的最后一块拼图

含代码示例偏理论 第 3/3 篇
写 EA 时外部参数堆到几十个,用户在属性窗口里点得眼花;指标被 iCustom 调却卡在 64 参数上限。把多开关和多数值塞进一个字符串,往往比加变量更清爽,只是多数人没用过这套拆解法。

字符串在机器里的真实样子

人在看字符串时,脑子里直接映射成一段可读文字;计算机底层只认数字,所谓字符串不过是一串编号。早期扩展 ASCII 用 0–255 共 256 个代码覆盖一个字符集,比如俄语环境常用的 Windows-1251 就塞进了拉丁、西里尔字母和标点,而中欧用的 Windows-1250 从代码 192 起换成带变音符号的欧洲字母。 前 32 个 ASCII 代码是控制字符,图里通常不画出来,但像制表符(9)、换行符(10)会实实在在影响显示和解析。256 个字符应付单语种还行,碰到中俄英法阿多种文字混排,或者中文日文这种成千上万象形字,单字节编码直接不够用。 Unicode 把每个字符扩成两字节,容量拉到 65536,基本收齐了全球字母表和常见象形文字。MQL5 的字符串就是 Unicode 实现:字符可用 0–65535 表示,其中 0–127 与 ASCII 完全一致。这意味着你用 MT5 读外部文本文件时,ASCII 和 Unicode 的字节结构不同,StringToShortArray 或文件函数处理出来的长度和行为会有差异——开 MT5 随便写个 Print((ushort)'A', (ushort)'中'); 就能看到 65 和 20013 这两个底层码点。

◍ 字符串与数组互转的坑和用法

日常处理字符串用 StringLen、StringFind、StringReplace 基本够用,但碰到加密、压缩、校验值计算,或要把字符串丢给 Windows API 时,就得把字符串拆成数组来操作。MT5 里转 Unicode 用 StringToShortArray,转 ASCII 用 StringToCharArray;前者结果在任何环境都一致,后者会受系统区域设置影响。 以 "MetaTrader 5" 为例,因其字符码都不超过 127,两种函数得到的数组完全相同,弹窗会显示 Identical。但俄语字母 А(Unicode 码 1040)在俄语区域下 StringToCharArray 得到 192,捷克语区域下却得到 63(问号)——这种差异在跨语言环境跑 EA 时可能引出隐蔽 bug。 数组元素永远比字符串字符数多 1,多出来的那个是结尾的 0 码字符。它不显示,但标记着字符串终止;加密或压缩后若中间出现 0 码,反向转回字符串就会被截断,这类任务需要特殊技巧。 逆向转换用 ShortArrayToString 和 CharArrayToString 即可,short 数组 {85,110,105,99,111,100,101} 会还原成 "Unicode",uchar 数组 {65,83,67,73,73} 还原成 "ASCII"。CharToString 单独配合 OBJ_LABEL 加 Wingdings 字体,能在图表固定坐标画钟形等符号,做信息面板很实用。 StringGetCharacter 取指定位置字符码,如取 "L5" 的 0 位和 1 位分别得 76 和 53;StringSetCharacter 可改指定位或尾部追加,把 "MQ5" 先改成 "MQL" 再补成 "MQL5"。外汇贵金属 EA 开发涉及这类底层转换时,注意 MT5 环境的高风险,区域设置差异可能引发非预期行为。

MQL5 / C++
class="type">class="kw">string str="MetaTrader class="num">5";
class=class="str">"cmt">//--- converting the class="type">class="kw">string to a Unicode array
class="type">class="kw">short sha[];
StringToShortArray(str,sha);
class=class="str">"cmt">//--- converting the class="type">class="kw">string to a ASCII array
class="type">uchar cha[];
StringToCharArray(str,cha);
class=class="str">"cmt">//--- flag the difference
class="type">bool Dif=class="kw">false;
class=class="str">"cmt">//--- compare the arrays element by element
for(class="type">int i=class="num">0;i<StringLen(str);i++)
  {
   if(sha[i]!=cha[i])
     {
      Dif=true;
     }
  }
class=class="str">"cmt">//--- output the results
if(Dif) Alert("Different");
else    Alert("Identical");
class="type">int StrLen=StringLen(str);
class="type">int shaLen=ArraySize(sha);
class="type">int chaLen=ArraySize(cha);
class=class="str">"cmt">//--- output the lengths
Alert(StringFormat("%i, %i, %i",StrLen,shaLen,chaLen));
class=class="str">"cmt">//--- convert an array of Unicode codes to a class="type">class="kw">string
class="type">class="kw">short sha[]={class="num">85,class="num">110,class="num">105,class="num">99,class="num">111,class="num">100,class="num">101};
class="type">class="kw">string str1=ShortArrayToString(sha);
class=class="str">"cmt">//--- convert an array of ASCII codes to a class="type">class="kw">string
class="type">uchar cha[]={class="num">65,class="num">83,class="num">67,class="num">73,class="num">73};
class="type">class="kw">string str2=CharArrayToString(cha);
class=class="str">"cmt">//--- output the results
Alert(str1+" "+str2);
   ObjectCreate(class="num">0,"lbl",OBJ_LABEL,class="num">0,class="num">0,class="num">0);            class=class="str">"cmt">// create the LABEL graphical object
   ObjectSetInteger(class="num">0,"lbl",OBJPROP_XDISTANCE,class="num">100);  class=class="str">"cmt">// set the X-coordinate
   ObjectSetInteger(class="num">0,"lbl",OBJPROP_YDISTANCE,class="num">100);  class=class="str">"cmt">// set the Y-coordinate
   ObjectSetInteger(class="num">0,"lbl",OBJPROP_FONTSIZE,class="num">20);    class=class="str">"cmt">// set the size

「用 Wingdings 与 Unicode 码点摆弄图表标签」

想在 MT5 图表上画一个不依赖外部图片的提示符号,可以直接把对象字体设成 Wingdings,再喂一个字符码进去。下面这段把名为 "lbl" 的文本对象字体改为 Wingdings,并用 CharToString(37) 取出码点 37 对应的“铃铛”字形,挂到对象文本上,图表里就能看到一个系统字体自带的图标,不占资源也不怕路径丢失。 [CODE] ObjectSetString(0,"lbl",OBJPROP_FONT,"Wingdings"); // set the Wingdings font string Icon=CharToString(37); // 37 - the bell ObjectSetString(0,"lbl",OBJPROP_TEXT,Icon); // set the displayed text string str="L5"; //--- get the Unicode code of the character at the given position in the string ushort uch1=StringGetCharacter(str,0); ushort uch2=StringGetCharacter(str,1); //--- output the results Alert(StringFormat("%i, %i",uch1,uch2)); string str="MQ5"; //--- replace the character at the given position in the string with the Unicode character corresponding to the passed code StringSetCharacter(str,2,76); Alert(str); //--- add the Unicode character corresponding to the passed code to the end of the string StringSetCharacter(str,3,53); Alert(str); [/CODE] 字符串层的 Unicode 操作也值得单独看。对 "L5" 调 StringGetCharacter 取位置 0 和 1 的码点,Alert 会打出 76, 53——也就是 L 和 5 的 ASCII/Unicode 十进制值,验证字符在内存里就是码点。 再把 "MQ5" 拿来改:StringSetCharacter(str,2,76) 把第 3 个字符(索引 2)换成码点 76 即 'L',弹窗变 "MQL";随后 StringSetCharacter(str,3,53) 在索引 3 追加码点 53 即 '5',最终得到 "MQL5"。外汇与贵金属图表上做动态标签时,这种码点拼装比硬编码字符串更不容易在跨语言环境下乱码,但这类 GUI 改动不影响报价,交易高风险仍在,信号误读可能导致亏损。

MQL5 / C++
  ObjectSetString(class="num">0,"lbl",OBJPROP_FONT,"Wingdings"); class=class="str">"cmt">// set the Wingdings font
  class="type">class="kw">string Icon=CharToString(class="num">37);                      class=class="str">"cmt">// class="num">37 - the bell
  ObjectSetString(class="num">0,"lbl",OBJPROP_TEXT,Icon);        class=class="str">"cmt">// set the displayed text
class="type">class="kw">string str="L5";
class=class="str">"cmt">//--- get the Unicode code of the character at the given position in the class="type">class="kw">string
class="type">class="kw">ushort uch1=StringGetCharacter(str,class="num">0);
class="type">class="kw">ushort uch2=StringGetCharacter(str,class="num">1);
class=class="str">"cmt">//--- output the results
Alert(StringFormat("%i, %i",uch1,uch2));
class="type">class="kw">string str="MQ5";
class=class="str">"cmt">//--- replace the character at the given position in the class="type">class="kw">string with the Unicode character corresponding to the passed code
StringSetCharacter(str,class="num">2,class="num">76);
Alert(str);
class=class="str">"cmt">//--- add the Unicode character corresponding to the passed code to the end of the class="type">class="kw">string
StringSetCharacter(str,class="num">3,class="num">53);
Alert(str);

WinAPI 字符串参数的编码坑

在 MT5 里直接调 Windows API,字符串参数怎么传决定了弹窗显示是否正常。WinExec 第一个参数要吃 uchar 数组,把 'C:\\WINDOWS\\notepad.exe' 转成 uchar 数组后传进去,记事本就会被拉起。 MessageBoxW 处理 Unicode,按文档应传 ushort 数组,但实测直接传 string 也能出 'Programming in MQL5 for MetaTrader 5' 这条消息。反过来若给 MessageBoxW 塞 uchar 数组,代码不报错、弹窗也出,文字却会乱码。 ASCII 场景对应 MessageBoxA,只认 uchar 数组。手里有 uchar 就走 A 版,有 string 或 ushort 就走 W 版,这是 WinAPI 双编码体系的常态。 调用前必须放开 DLL:终端菜单 Tools - Options - Expert Advisors 勾 Allow DLL imports,或在脚本属性 Dependencies 里勾选。不勾的话 #import 写了也跑不起来。外汇与贵金属交易使用 DLL 调用存在安全与平台稳定性高风险,仅在可信环境开启。

MQL5 / C++
class="macro">#class="kw">import "kernel32.dll"
class="type">int WinExec(class="type">uchar &Path[],class="type">int Flag);
class="macro">#class="kw">import 
class="type">class="kw">string PathName="C:\\WINDOWS\\notepad.exe";
class="type">uchar ucha[];
StringToCharArray(PathName,ucha);
class="type">int x=WinExec(ucha,class="num">1);
class="macro">#class="kw">import "user32.dll"
class="type">int MessageBoxW(class="type">int hWnd,class="type">class="kw">ushort &szText[],class="type">class="kw">ushort &szCaption[],class="type">int nType);
class="macro">#class="kw">import
class="type">class="kw">ushort arr[];
class="type">class="kw">ushort capt[];
class=class="str">"cmt">//--- convert
StringToShortArray("Programming in MQL5 for MetaTrader class="num">5.",arr);
StringToShortArray("Message",capt);
class=class="str">"cmt">//--- print the message
MessageBoxW(class="num">0,arr,capt,class="num">0);
class="macro">#class="kw">import "user32.dll"
class="type">int MessageBoxW(class="type">int hWnd,class="type">class="kw">string szText,class="type">class="kw">string szCaption,class="type">int nType);
class="macro">#class="kw">import
class="type">void OnStart()
  {
   MessageBoxW(class="num">0,"Programming in MQL5 for MetaTrader class="num">5","Message",class="num">0);
  }
class="macro">#class="kw">import "user32.dll"
class="type">int MessageBoxW(class="type">int hWnd,class="type">uchar &szText[],class="type">uchar &szCaption[],class="type">int nType);
class="macro">#class="kw">import
class="type">void OnStart()
  {
   class="type">uchar arr[];
   class="type">uchar capt[];
   StringToCharArray("Programming in MQL5 for MetaTrader class="num">5.",arr);
   StringToCharArray("Message",capt);
   MessageBoxW(class="num">0,arr,capt,class="num">0);
  }
class="macro">#class="kw">import "user32.dll"
class="type">int MessageBoxA(class="type">int hWnd,class="type">uchar &szText[],class="type">uchar &szCaption[],class="type">int nType);
class="macro">#class="kw">import
class="type">void OnStart()
  {
   class="type">uchar arr[];
   class="type">uchar capt[];
   StringToCharArray("Programming in MQL5 for MetaTrader class="num">5",arr);

◍ 用 WinAPI 弹窗绕过脚本输入限制

在 MQL5 脚本里直接调用 MessageBoxA 可以绕开 #property script_show_inputs 强制弹出的输入参数框,适合做一键提示或调试输出。 上面这段把字符串转成字符数组后传给 MessageBoxA,第一个和第四个参数填 0 表示使用系统桌面与默认样式,capt 是窗口标题。 实测在 MT5 build 3440+ 的 64 位终端中,该调用会阻塞脚本线程直到用户点掉弹窗,若循环里调用会卡住 EA 的 OnTick,外汇与贵金属品种波动快,生产环境慎用。

MQL5 / C++
class="macro">#class="kw">property script_show_inputs
StringToCharArray("Message",capt);
class=class="str">"cmt">//--- print the message
MessageBoxA(class="num">0,arr,capt,class="num">0);

「把分号分隔的手工输入变成双精度数组」

MT5 的 EA 属性框里,交易者常想一次性塞进多组手数或参数,比如输入 0.1; 0.2; 0.3; 0.5 再用分号隔开。要能在程序里逐个调用,就得把这个字符串拆成 double 型数组,而不是写死几个 input 变量。 核心靠 StringSplit():第一个参数是源字符串,第二个是分隔符的 ASCII 或字符常量,第三个是用引用方式接收结果的字符串数组。注意单引号包一个字符就能直接拿 ASCII 码,例如 int Code='A'; 跑完 Alert 出来是 65,拉丁 A 的码值,这套写法比查表省事。 下面这个函数把脏数据也顺手清了:先 Trim 掉首尾空格,空串直接释放数组返回 0;按 ; 切完再给每个元素 Trim;倒序扫一遍把连续分隔符或结尾多打的 ; 产生的空串用 ArrayCopy 往前挪并缩减 size;最后把逗号替换成点号(欧版小数逗号兼容),转成 double 填回 Params,返回实际个数。 外汇和贵金属杠杆高,参数数组若含异常值(如手数 0 或负)可能触发下单拒绝,建议接完数组后自行加一道范围校验再喂给 OrderSend。

MQL5 / C++
input class="type">class="kw">string Lots="class="num">0.1; class="num">0.2; class="num">0.3; class="num">0.5";
class="type">int Code=&class="macro">#x27;A&class="macro">#x27;;
Alert(IntegerToString(Code)); 
class="type">int ParamsToArray(class="type">class="kw">string Str,class="type">class="kw">double &Params[])
  {
class=class="str">"cmt">//--- class="kw">delete spaces at the ends
   StringTrimLeft(Str);
   StringTrimRight(Str);
class=class="str">"cmt">//--- if the class="type">class="kw">string is empty
   if(StringLen(Str)==class="num">0)
     {
       ArrayFree(Params); class=class="str">"cmt">// free the array
       class="kw">return(class="num">0);          class=class="str">"cmt">// function operation complete
     }
class=class="str">"cmt">//--- auxiliary array
   class="type">class="kw">string tmp[];
class=class="str">"cmt">//--- split the class="type">class="kw">string
   class="type">int size=StringSplit(Str,&class="macro">#x27;;&class="macro">#x27;,tmp);
class=class="str">"cmt">//--- class="kw">delete spaces at the ends for each element of the array
   for(class="type">int i=class="num">0;i<size;i++)
     {
       StringTrimLeft(tmp[i]);
       StringTrimRight(tmp[i]);
     }
class=class="str">"cmt">//--- class="kw">delete empty elements from the array(user could accidentally 
class=class="str">"cmt">//--- put the separator two times in a row or at the end of the class="type">class="kw">string)
   for(class="type">int i=size-class="num">1;i>=class="num">0;i--)
     {
       if(StringLen(tmp[i])==class="num">0)
         {
          ArrayCopy(tmp,tmp,i,i+class="num">1);
          size--; class=class="str">"cmt">// array size reduced
         }
     }
class=class="str">"cmt">//--- scale the array according to the new size
   ArrayResize(tmp,size);
class=class="str">"cmt">//--- replace commas with dots
   for(class="type">int i=class="num">0;i<size;i++)
     {
      StringReplace(tmp[i],",",".");
     }
class=class="str">"cmt">//--- prepare the array to be returned
   ArrayResize(Params,size);
class=class="str">"cmt">//--- convert all elements to the class="type">class="kw">double type and fill the array to be returned 
   for(class="type">int i=class="num">0;i<size;i++)
     {
      Params[i]=StringToDouble(tmp[i]);
     }
class=class="str">"cmt">//--- the function returns the number of parameters
   class="kw">return(size);
  }

字符串到变量的类型转换坑点

在 MT5 的脚本里,把外部读入或界面输入的字符串落进具体变量类型,靠的是一组标准转换函数,而不是强制类型转换。最常用的是 StringToDouble(),它同时覆盖双精度和浮点,所以很多参数解析逻辑直接复用它。 整数转换要用 StringToInteger()。比如字符串 "12345.678" 经它处理后,变量只保留 12345,小数部分直接被截断,不是四舍五入。做仓位手数或魔术码解析时,这个截断行为容易埋 bug。 时间字符串用 StringToTime() 转 datetime。若只给日期如 "2012.01.01",小时分钟默认补 "00:00";写全 "2012.11.02 22:00" 则精确到位。颜色可用 StringToColor() 吃标准 Web 色名(如 clrYellow)或 RGB 串(如 255,255,0)。 还有一种更短的写法:赋值时用 D'2012.11.02 22:00' 直接给 datetime,C'255,255,0' 直接给 color。前者日期前缀是 D 加单引号,后者颜色前缀是 C 加逗号分隔的 RGB 单引号。开 MT5 新建脚本把下面代码贴进去跑一遍,就能看到截断和转换结果。

MQL5 / C++
class="type">class="kw">string Str="class="num">12345.678";
class=class="str">"cmt">//--- convert the class="type">class="kw">string to an integer
class="type">long Val=StringToInteger(Str);
class=class="str">"cmt">//--- inverse convert and output the results
Alert(IntegerToString(Val));
class="type">class="kw">string Str1="class="num">2012.11.class="num">02 class="num">22:class="num">00";
class="type">class="kw">string Str2="class="num">2012.01.class="num">01";
class=class="str">"cmt">//--- convert the class="type">class="kw">string expression of time to the class="type">class="kw">datetime type
class="type">class="kw">datetime DateTime1=StringToTime(Str1);
class="type">class="kw">datetime DateTime2=StringToTime(Str2);
class="type">class="kw">string Str1="clrYellow";
class="type">class="kw">string Str2="class="num">255,class="num">255,class="num">0";
class="type">class="kw">color Color1=StringToColor(Str1);
class="type">class="kw">color Color2=StringToColor(Str2); 
class="type">class="kw">datetime DateTime=D&class="macro">#x27;class="num">2012.11.class="num">02 class="num">22:class="num">00&class="macro">#x27;;
class="type">class="kw">color Color=C&class="macro">#x27;class="num">255,class="num">255,class="num">0&class="macro">#x27;;

◍ 用单字符串切换通知与柱偏移

在 MT5 指标或 EA 里堆五个输入变量去管通知,既占属性面板又难组合。改成让用户输入一串字符就能同时开启警示、声音、邮件、推送,是更干净的做法。字符约定很直接:a=警示,s=声音,e=邮件,p=推送;再追加 0 或 1 表示信号校验所在柱,不写则默认看前一根(Shift=1)。 下面这个函数把解析逻辑收口到一处。它接收字符串 Str,并通过引用回写 Shift 与四个布尔开关。注意 StringToLower 先把串转小写,所以用户大小写混输也不会漏判。 调用后属性窗口只剩 1 个字符串变量,而不是原先的 5 个。实盘里你可以输 "as0" 表示开警示+声音且看当前柱,或 "ep1" 表示邮件+推送看前一根——外汇与贵金属波动快,这种组合在切换品种复盘时挺省事,但杠杆品类高风险,通知开了也不代表信号必胜,仅作辅助。

MQL5 / C++
class="type">void NotifyOnOff(class="type">class="kw">string Str,class="type">int &Shift,class="type">bool &Alerts,class="type">bool &Sounds,class="type">bool &EMail,class="type">bool &Push)
  {
class=class="str">"cmt">//--- Convert the class="type">class="kw">string to lower case to allow the user
class=class="str">"cmt">//--- to use both lowercase and uppercase characters.
   StringToLower(Str);
class=class="str">"cmt">//--- search for characters in the class="type">class="kw">string
   Alerts=(StringFind(Str,"a")!=-class="num">1);      class=class="str">"cmt">// "a" found
   Sounds=(StringFind(Str,"s")!=-class="num">1);      class=class="str">"cmt">// "s" found
   EMail=(StringFind(Str,"e")!=-class="num">1);       class=class="str">"cmt">// "e" found
   Push=(StringFind(Str,"p")!=-class="num">1);        class=class="str">"cmt">// "p" found
class=class="str">"cmt">//--- search for zero
   if(StringFind(Str,"class="num">0")!=-class="num">1) Shift=class="num">0;   class=class="str">"cmt">// "class="num">0" found in the class="type">class="kw">string
   else                        Shift=class="num">1;  class=class="str">"cmt">// by class="kw">default
  }

「用缓冲区预分配躲开字符串重分配」

在 MT5 的 MQL5 里,字符串不是随便改改都零成本的。StringInit() 按指定字符数和 ASCII 码预填内容,比如填 10 个 '',变量直接拿到 "",这一步已经把内存占好了。

StringFill() 只替换字符、不动长度,前面例子里把 '|' 全换成 '/' 得到 "//////////";若填代码 0 的字符,字符串逻辑长度变 0,但底层缓冲区没缩水。 用 StringBufferLen() 量一下:上面那串填 0 后 StringLen 返回 0,而 StringBufferLen 返回 10(超过初始 10 个分配时实际给的缓冲往往更大),说明内存还留着。后续用 str+= 追加而不是 str= 重赋,就能在旧缓冲里写,避免重新申请内存,赋值更快。 在开头插代码 0 的字符等于清空逻辑内容却保住缓冲:str 长度回到 1 时缓冲依旧是原数。外汇和贵金属 EA 里高频拼字符串(如拼日志、拼请求)用这招能减卡顿,但杠杆品种波动剧烈,任何性能优化都不改交易高风险本质,先在策略测试器里跑一遍再上实盘。

MQL5 / C++
class="type">class="kw">string str;
StringInit(str,class="num">10,&class="macro">#x27;|&class="macro">#x27;);
Alert(str); 
StringFill(str,&class="macro">#x27;/&class="macro">#x27;);
Alert(str); 
StringFill(str,class="num">0); 
class="type">int Length=StringLen(str);
Alert(IntegerToString(Length)); 
class="type">int BLength=StringBufferLen(str);
Alert(IntegerToString(BLength)); 
str+="a"; 
StringSetCharacter(str,class="num">0,class="num">0);

字符串函数按使用频率该怎么分层

MQL5 里字符串处理常被误认为和 EA、指标开发关系不大,但真到要解析日志、拼 API 参数或处理 Wingdings 图形符号时,现学就太慢了。把这套函数按出现频率和必要性拆开,能让你在 MT5 里少走弯路。 第一层是绕不开的:StringLen()、StringFind()、StringSubstr()、StringReplace()、StringSplit()。这五个管长度、搜子串、截子串、换内容、切分,几乎任何字符串逻辑都从它们起手。 第二层是顺手就用得上的辅助:StringTrimLeft()、StringTrimRight()、StringToLower()、StringToUpper(),清空格和改大小写,写界面文本或比对时很省事。 数值和字符串互转各有一组。DoubleToString()、IntegerToString()、TimeToString()、EnumToString()、ColorToString()、StringFormat() 是把变量变成文本;反向的 StringToDouble()、StringToInteger()、StringToTime()、StringToColor()、StringCompare() 把文本读回数值。 StringAdd() 和 StringConcatenate() 适合在紧凑逻辑里拼串。更底层的是把字符串当数组看的 ShortToString()、CharArrayToString()、StringToCharArray() 等,其中 CharToString() 配合 Wingdings 字体做图形对象,CharArrayToString() 给 API 调用备参,这两个在非常规任务里是关键。 剩下 StringSetCharacter()、StringGetCharacter()、StringInit()、StringFill()、StringBufferLen() 属次要,平时用不到也别硬记。外汇与贵金属自动化高风险,字符串逻辑写错不会爆仓但会静默失效,上线前务必在策略测试器里跑一遍边界样例。

◍ 随包里的字符串工具与多语消息样例

随文附带的 IncStrFunctions.mqh 头文件封装了 Trim()、StringFindRev()、TrimL()、TrimR()、ParamsToArray() 和 NotifyOnOff() 六个函数,专门处理 EA 里的字符串裁剪与参数解析。另一份 eMultiLanguageMessage.mq5 则演示了同一套逻辑如何切换英、俄等不同语言的下单提示,直接丢进 MT5 就能编译运行。 下面这段数字千分位格式化的代码来自头文件思路的延伸实现,把 1234567.89 按空格分组输出成「1 234 567.89」概率很高。注意 ModToString 里用 Num/Mod 判断高位是否归零,避免无意义的前导零。 [CODE] 中 OnStart 的基准测试值得一抄:循环 1000 万次格式化,DelimiterGroupsDigits 与 NumToString 的耗时差可能达到数倍,fxsaber 在讨论里提到类似优化后快了四倍。外汇与贵金属自动化高风险,任何字符串处理函数上线前都应在策略测试器跑一遍边界值。

MQL5 / C++
<span class="keyword">class="type">class="kw">string</span> ModToString( <span class="keyword">class="type">ulong</span> &amp;Num, <span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span> Mod = <span class="number">class="num">1000</span>, <span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span> Len = <span class="number">class="num">3</span> )
{
&nbsp;&nbsp;<span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">string</span> Res = ((<span class="keyword">class="type">bool</span>)(Num / Mod) ? <span class="functions">IntegerToString</span>(Num % Mod, Len, <span class="class="type">class="kw">string">&class="macro">#x27;class="num">0&class="macro">#x27;</span>) : (<span class="keyword">class="type">class="kw">string</span>)(Num % Mod));
&nbsp;&nbsp;
&nbsp;&nbsp;Num /= Mod;
&nbsp;&nbsp;
&nbsp;&nbsp;<span class="keyword">class="kw">return</span>(Res);
}
<span class="keyword">class="type">class="kw">string</span> NumToString( <span class="keyword">class="type">ulong</span> Num, <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">string</span> Delimeter = <span class="class="type">class="kw">string">" "</span> )
{
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">string</span> Res = ModToString(Num);
&nbsp;&nbsp;<span class="keyword">class="kw">while</span> (Num)
&nbsp;&nbsp;&nbsp;&nbsp;Res = ModToString(Num) + Delimeter + Res;
&nbsp;&nbsp;<span class="keyword">class="kw">return</span>(Res);
}
<span class="keyword">class="type">class="kw">string</span> NumToString( <span class="keyword">class="type">class="kw">double</span> Num, <span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span> digits = <span class="number">class="num">8</span>, <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">string</span> Delimeter = <span class="macro">NULL</span> )
{
&nbsp;&nbsp;<span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">string</span> PostFix = (Num &lt; <span class="number">class="num">0</span>) ? <span class="class="type">class="kw">string">"-"</span> : <span class="macro">NULL</span>;
&nbsp;&nbsp;
&nbsp;&nbsp;Num = <span class="functions">MathAbs</span>(Num);
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;<span class="keyword">class="kw">return</span>(PostFix + NumToString((<span class="keyword">class="type">ulong</span>)Num, Delimeter) + <span class="functions">StringSubstr</span>(<span class="functions">DoubleToString</span>(Num - (<span class="keyword">class="type">long</span>)Num, digits), <span class="number">class="num">1</span>));
}
<span class="keyword">class="type">void</span> <span class="functions">OnStart</span>()
{
&nbsp;&nbsp;<span class="functions">Print</span>(NumToString(<span class="number">class="num">1234567.89</span>, <span class="number">class="num">2</span>, <span class="class="type">class="kw">string">" "</span>));
}
<span class="keyword">class="type">void</span> <span class="functions">OnStart</span>(<span class="keyword">class="type">void</span>)
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="keyword">class="type">long</span> count=<span class="number">class="num">10000000</span>;
&nbsp;&nbsp; <span class="keyword">class="type">uint</span> start=<span class="functions">GetTickCount</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;count &amp;&amp; !<span class="functions">IsStopped</span>(); i++)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DelimiterGroupsDigits(<span class="keyword">class="type">class="kw">string</span>(Value),Delimiter);
&nbsp;&nbsp; <span class="keyword">class="type">uint</span> end=<span class="functions">GetTickCount</span>()-start;
&nbsp;&nbsp; <span class="functions">Print</span>(<span class="class="type">class="kw">string">"class="num">01 &gt; ms: "</span>,end,<span class="class="type">class="kw">string">"; res: "</span>,DelimiterGroupsDigits(<span class="keyword">class="type">class="kw">string</span>(Value),Delimiter));
<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp; start=<span class="functions">GetTickCount</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;count &amp;&amp; !<span class="functions">IsStopped</span>(); i++)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;NumToString(Value,<span class="number">class="num">2</span>,Delimiter);
&nbsp;&nbsp; end=<span class="functions">GetTickCount</span>()-start;
&nbsp;&nbsp; <span class="functions">Print</span>(<span class="class="type">class="kw">string">"class="num">02 &gt; ms: "</span>,end,<span class="class="type">class="kw">string">"; res: "</span>,NumToString(Value,<span class="number">class="num">2</span>,Delimiter));
&nbsp;&nbsp;}

「记住这一条就够了」

把两个线程的耗时摆在一起看:01 号线程跑完一百万次浮点运算用了 19047 毫秒,02 号线程只用了 4688 毫秒,差距约四倍。 这种差异在 MT5 策略测试器的多线程回测里很常见,线程调度、缓存命中、后台任务抢占都会让同规模计算的实际耗时漂移。 做高频因子或批量样本外校验时,别只信单次测速,多跑几遍取中位数才接近真实算力开销。外汇与贵金属杠杆品种波动剧烈,回测快不等于实盘稳,参数上线前务必在模拟盘验证。

MQL5 / C++
class="num">2018.01.class="num">30 class="num">21:class="num">02:class="num">15.996 class="num">01 > ms: class="num">19047; res: class="num">1 class="num">000 class="num">000.0
class="num">2018.01.class="num">30 class="num">21:class="num">02:class="num">20.683 class="num">02 > ms: class="num">4688; res: class="num">1 class="num">000 class="num">000.00
让小布替你跑这套拆串逻辑
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到参数解析与通知组合的校验提示,把重复劳动交给小布,你专注决策。

常见问题

逗号、竖线、空格均可,只要在分割与拼接时保持一致,并在外部参数说明里写清,避免用户误填。
iCustom 仅支持 64 个参数、IndicatorCreate 数组上限 60,字符串能把多开关合一,绕开数量天花板,代价是初始化时要做解析。
可以,小布盯盘的品种页内置了参数校验,能识别 s/e/p 这类组合字符并提示遗漏或冲突,省去手动核对。
缓冲区预分配内存,适合循环内高频拼接;普通变量频繁重载可能触发重复分配,数据量小时差别不大,量大时缓冲区更省。
优化只认数值型外部变量的起止与步长,字符串整体作为一个常量传入,无法拆出内部值做逐步扫描。