MQL5 编程基础:字符串·进阶篇
(2/3)· 用分号串起手数、用字母开关通知,外部参数爆炸时 EA 与指标怎么不掉链子
不少交易者写 EA 时习惯给每种通知开一个布尔变量,参数窗口很快堆到几十项,指标被 iCustom 的 64 参数上限卡死却还蒙在鼓里。把一堆值硬塞进数组而非字符串,回测时连自己都看不清文件里写的是什么。
字符串里的转义陷阱
在 MT5 里给字符串变量赋值时,内容必须包在双引号中,编译器才认它是文本而非代码。想让引号本身成为字符串的一部分,就得在前面加反斜杠 \ 转义,例如写 "Text in quotes" 才会输出带引号的文本。
反斜杠自己也是特殊字符,要在字符串里真打出一条反斜杠,必须写成 \\,否则编译器会把它当成转义起始符吃掉。上面代码里 string str="\\"; 执行后,变量里就只剩一个 \ 字符。
水平制表符用 \t 表示,换行用 \n。实测 "Column-1\tColumn-2\tColumn-3" 经 Alert() 弹出会是「Column-1 Column-2 Column-3」这样对齐的带间隔文本。
不同输出函数对这两个符的处理并不一致:Alert() 和 MessageBox() 都认 \t 与 \n;Comment() 只认 \n,\t 直接被忽略;Print() 写日志时 \n 分行、\t 被替换成空格。写面板注释和写日志前,先想清楚用哪个函数,别指望一种写法通吃。
class="type">class="kw">string str1="Simple text"; class="type">class="kw">string str2="\"Text in quotes\""; class=class="str">"cmt">//--- output the results Alert(str1); Alert(str2); class="type">class="kw">string str="\\"; Alert(str); class="type">class="kw">string str="Column-class="num">1\tColumn-class="num">2\tColumn-class="num">3"; Alert(str); class="type">class="kw">string str="Line-class="num">1\nLine-class="num">2\nLine-class="num">3"; Alert(str);
「用 StringFormat 把变量塞进模板里」
手动拼字符串做日志输出,代码会又长又难改:把 int 转成文本再一路加号连起来,后期想加个字段就得重读整行。MQL5 里用 StringFormat() 可以把模板和变量分开,第一个参数是带占位符的消息,后面按出现顺序塞变量即可。 占位符以 % 起头,整数用 i(对应 char/short/int/color),无符号用 u(uchar/bool/ushort/uint)。long、ulong、datetime 要在类型前加 I64,例如 %I64i 才能正确拿到 LONG_MIN 那种边界值。 实数默认用 %f,输出 6 位小数——Percents=5.5 会得到 "Percents = 5.500000"。想要 2 位就写 %.2f,结果是 "Percents = 5.50",行为和 DoubleToString() 一致。 总长度也能锁:%06.2f 表示总宽 6 含小数点,5.5 出来是 "Percents = 005.50"。注意百分号本身要写 %%,否则被当成占位符。整数同理,%05i 让 123 变成 "Variable = 00123";若指定宽度小于实际位数如 %02i,仍原样输出 "Variable = 123"。 科学计数法用 e 或 E,123.456 经 %e 得到 "1.234560e+002"。%g 只留 6 位有效数字,整数超 6 位才转科学计数;%o 转八进制(17 变 "21"),%x/%X 转十六进制(16711680 变 "ff0000"),%d 能把十六进制字符串转回十进制。 负数因负号会错位,在 % 后加空格可给正数留位实现对齐:"Variable1= 01" 对 "Variable2=-01"。PrintFormat() 和 printf() 与 StringFormat() 语法完全一致,只是直接写日志。 下面这段对比最直观:同样三个变量,拼接版要写满一行加号,模板版一行搞定。
class=class="str">"cmt">//--- initialize the variables class="type">int Variable1=class="num">1; class="type">int Variable2=class="num">2; class="type">int Variable3=class="num">3; class=class="str">"cmt">//--- class="type">long addition of strings class="type">class="kw">string str="Variable1 = "+IntegerToString(Variable1)+", Variable2 = "+IntegerToString(Variable2)+", Variable3 = "+IntegerToString(Variable2); class=class="str">"cmt">//--- output the results Alert(str); class=class="str">"cmt">//--- initialize the variables class="type">int Variable1=class="num">1; class="type">int Variable2=class="num">2; class="type">int Variable3=class="num">3; class=class="str">"cmt">//--- simpler addition of strings class="type">class="kw">string str=StringFormat("Variable1 = %i, Variable2 = %i, Variable3 = %i",Variable1,Variable2,Variable3); class=class="str">"cmt">//--- output the results Alert(str); class="type">class="kw">string LongMin=StringFormat("%I64i",LONG_MIN); class="type">class="kw">string LongMax=StringFormat("%I64i",LONG_MAX); class="type">class="kw">string ULongMax=StringFormat("%I64u",ULONG_MAX); class="type">class="kw">string DateTimeMax=StringFormat("%I64u",DateMax); class=class="str">"cmt">//--- output the results Alert("LongMin = "+LongMin); Alert("LongMax = "+LongMax); Alert("ULongMax = "+ULongMax); Alert("DateTimeMax = "+DateTimeMax); class="type">class="kw">double Percents=class="num">5.5; class=class="str">"cmt">//--- real number as a class="type">class="kw">string class="type">class="kw">string str=StringFormat("Percents = %f",Percents); class=class="str">"cmt">//--- output the results Alert(str); class="type">class="kw">string str=StringFormat("Percents = %.2f",Percents); class="type">class="kw">string str=StringFormat("Percents = %class="num">06.2f",Percents); class="type">class="kw">string str=StringFormat("Percents = %class="num">06.2f%%",Percents); class="type">int Variable=class="num">123; class=class="str">"cmt">//--- integer as a class="type">class="kw">string with a set output length class="type">class="kw">string str=StringFormat("Variable = %05i",Variable); class=class="str">"cmt">//--- output the results Alert(str); class="type">class="kw">string str=StringFormat("Variable = %02i",Variable); class="type">class="kw">double Variable=class="num">123.456; class=class="str">"cmt">//--- real number as a class="type">class="kw">string in scientific notation
◍ 用 StringFormat 把变量塞进提示框
在 MT5 脚本里想快速看某个变量当前值,最省事的办法不是开调试器,而是用 StringFormat 拼一条字符串再 Alert 弹出来。比如浮点变量用 %e 走科学计数法,12.3456789 配 %g 会压成 12.3457,1234567.89 配 %g 则变成 1.23457e+006,位数差异一眼能分辨。 整数还能换进制看:17 用 %o 出 octal 的 21,clrBlue 用 %x 出十六进制颜色值 ff,0x0000ff 用 %d 回退成十进制的 255。字符串直接 %s 包裹,'text' 就原样进框。 对齐也有讲究,% 03i 让 1 显示成 ' 01'、-1 显示成 '-01',前面留空格补三位。外汇和贵金属波动快,这类弹窗只适合临时核对,实盘挂太多 Alert 会拖慢终端,且杠杆品种高风险,信号仅供参考。 下面这段可直接复制到脚本 OnInit 里跑,开 MT5 新建脚本粘进去编译,就能看到六组不同格式的输出。
class="type">class="kw">string str=StringFormat("Variable = %e",Variable); class=class="str">"cmt">//--- output the results Alert(str); class="type">class="kw">double Variable1=class="num">12.3456789; class="type">class="kw">double Variable2=class="num">1234567.89; class=class="str">"cmt">//--- get real numbers as strings using "g" class="type">class="kw">string str1=StringFormat("Variable = %g",Variable1); class="type">class="kw">string str2=StringFormat("Variable = %g",Variable2); class=class="str">"cmt">//--- output the results Alert(str1+" "+str2); class="type">int Variable=class="num">17; class=class="str">"cmt">//--- real number as a class="type">class="kw">string in the octal system class="type">class="kw">string str=StringFormat("Variable = %o",Variable); class=class="str">"cmt">//--- output the results Alert(str); class="type">class="kw">color Variable=clrBlue; class=class="str">"cmt">//--- real number as a class="type">class="kw">string in the hexadecimal system class="type">class="kw">string str=StringFormat("Variable = %x",Variable); class=class="str">"cmt">//--- output the results Alert(str); class="type">int Variable=0x0000ff; class=class="str">"cmt">//--- real number as a class="type">class="kw">string in the decimal system class="type">class="kw">string str=StringFormat("Variable = %d",Variable); class=class="str">"cmt">//--- output the results Alert(str); class="type">class="kw">string Variable="text"; class=class="str">"cmt">//--- output the class="type">class="kw">string variable class="type">class="kw">string str=StringFormat("Variable = %s",Variable); class=class="str">"cmt">//--- output the results Alert(str); class="type">int Variable1=class="num">1; class="type">int Variable2=-class="num">1; class=class="str">"cmt">//--- representation of numbers as aligned strings class="type">class="kw">string str1=StringFormat("Variable1=% 03i",Variable1); class="type">class="kw">string str2=StringFormat("Variable2=% 03i",Variable2); class=class="str">"cmt">//--- output the results Alert(str1); Alert(str2);
按终端语言切换提示文案
MT5 的 TERMINAL_LANGUAGE 配合 TerminalInfoString() 能直接拿到当前客户端界面语言,用这个值在初始化阶段挑好一条格式串,后面所有输出都走本地化模板,不用运行时反复判断。 上面这段 EA 只在 OnInit 里调一次 GetFormatString(),把 FormatString 定下来;之后 OnTick 每次取两个 0–9 的随机整数,算和后用 StringFormat 填进模板,Alert 弹窗显示。俄语环境出「%i плюс %i равно %i」,西语出「%i más %i es igual a %i」,其余一律英文「%i plus %i equals %i」。 实盘里这套思路适合写多语种日志或面板提示——比如你做跨境信号分发,俄语客户和英文客户同跑一个 EA,报错文案各看各的。外汇与贵金属杠杆高、滑点随机,别因为提示本地化了就低估执行风险,语言只是壳,逻辑照旧要回测。 代码逐行拆一下:string FormatString 先声明格式串容器;OnInit 里 FormatString=GetFormatString() 完成语言匹配,并手动调一次 OnTick 保证周末也能跑起码一轮;OnTick 中 MathRand()%10 取 0–9 随机数,Variable3 存两者之和,Alert(StringFormat(...)) 把三个整数塞进模板弹窗;GetFormatString 用 TerminalInfoString(TERMINAL_LANGUAGE) 取语言,命中 Russian / Spanish 返相应串,漏了就回退英文。
class=class="str">"cmt">//--- variable for a format class="type">class="kw">string class="type">class="kw">string FormatString; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Handling the Init event | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- get the format class="type">class="kw">string FormatString=GetFormatString(); class=class="str">"cmt">//--- additional call in case you want to ensure that the Expert Advisor operates at least once at weekends OnTick(); class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Handling the Tick event | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class="type">int Variable1,Variable2,Variable3; Variable1=MathRand()%class="num">10; class=class="str">"cmt">// Random number from class="num">0 to class="num">10 Variable2=MathRand()%class="num">10; class=class="str">"cmt">// Another random number Variable3=Variable1+Variable2; class=class="str">"cmt">// Sum of numbers class=class="str">"cmt">//--- output the results Alert(StringFormat(FormatString,Variable1,Variable2,Variable3)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Determining the format class="type">class="kw">string | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string GetFormatString(class="type">void) { class="type">class="kw">string Language=TerminalInfoString(TERMINAL_LANGUAGE); class=class="str">"cmt">//--- language check if(Language=="Russian") class="kw">return("%i плюс %i равно %i"); class=class="str">"cmt">// Russian if(Language=="Spanish") class="kw">return("%i más %i es igual a %i"); class=class="str">"cmt">// Spanish class=class="str">"cmt">//--- English - in all other cases class="kw">return("%i plus %i equals %i"); }
「字符串清理与改写的高频坑」
从属性窗口或 CSV 读进来的字符串,两端常带着空格、制表符甚至换行。直接拿去比对手动输入的交易品种名,大概率对不上。MQL5 里 StringTrimLeft() 和 StringTrimRight() 能分别吃掉左右两端的这些字符,但多数场景你要两端一起清,所以自己包一层 Trim() 更顺手。 用户输入实数时爱用逗号当小数点,比如 "123,456"。不先把逗号换成点,StringToDouble() 转换后小数部分直接被截掉,数值从 123.456 变成 123。下面这段代码演示了替换和转换的全过程: 连续空格也常捣乱。思路是先制表符换空格,再用 while 循环把双空格压成单空格,直到 StringReplace() 返回 0(即不再有替换发生)。该函数返回已完成替换的次数,出错才返回 -1,循环体故意留空,只在条件判断里调函数。 StringFind() 找子串位置是从 0 计数的。在 "Programming in MQL5 for MetaTrader 5" 里找 "5",返回的是 23 不是 24;找不到则返回 -1。若要找最后一次出现的位置,标准库没给,得自己写 StringFindRev() 用 do-while 不断偏移起点去搜。 大小写是另一个隐形雷。SymbolInfoDouble() 传 "eurusd" 而非 "EURUSD" 会拿不到数据。StringToLower() / StringToUpper() 做转换,StringCompare() 的第三个参数填 true 可忽略大小写比对——返回 0 表示相同,非 0 表示按字母序有大小。EURUSD 这类外汇品种名大小写错了就是取不到报价,实盘前务必规范化。
class="type">class="kw">string Trim(class="type">class="kw">string Str) { StringTrimLeft(Str); StringTrimRight(Str); class="kw">return(Str); } class="type">class="kw">string str="class="num">123,class="num">456"; class=class="str">"cmt">//--- replace a comma with a dot StringReplace(str,",","."); class="type">class="kw">double Value=StringToDouble(str); class=class="str">"cmt">//--- output the results Alert(DoubleToString(Value)); class="type">class="kw">string str="Column-class="num">1 \t Column-class="num">2 \t Column-class="num">3"; class=class="str">"cmt">//--- replace the tab character with a space StringReplace(str,"\t"," "); class=class="str">"cmt">//--- get one space instead of the series of spaces class="kw">while(StringReplace(str," "," ")>class="num">0){} class=class="str">"cmt">//--- output the results Alert(str); class="type">class="kw">string str="Programming in MQL5!"; class=class="str">"cmt">//--- replace the substring, output the results StringReplace(str,"in MQL5","for MetaTrader class="num">5"); Alert(str); class=class="str">"cmt">//--- reverse replacement, output the results StringReplace(str,"for MetaTrader class="num">5","in MQL5"); Alert(str); class="type">class="kw">string str="Programming in MQL5 for MetaTrader class="num">5"; class=class="str">"cmt">//--- get the position of the character class="type">int Pos=StringFind(str,"class="num">5"); class=class="str">"cmt">//--- output the results Alert(IntegerToString(Pos)); class="type">int StringFindRev(class="type">class="kw">string Str,class="type">class="kw">string Find) { class=class="str">"cmt">//--- the pos variable for the returned value class="type">int pos; class=class="str">"cmt">//--- auxiliary variable initialized to -class="num">1, class=class="str">"cmt">//--- in case the substring is not found in the class="type">class="kw">string class="type">int tmp=-class="num">1; class=class="str">"cmt">//--- loop. It will be executed at least once do { class=class="str">"cmt">//--- assign the last known position of the substring pos=tmp; class=class="str">"cmt">//--- class="kw">continue searching(using the third parameter of the function) tmp=StringFind(Str,Find,tmp+class="num">1); } class="kw">while(tmp!=-class="num">1); class=class="str">"cmt">// If the substring is not found in the remaining part of the class="type">class="kw">string, the loop class=class="str">"cmt">// is terminated and the pos variable stores the last
◍ 字符串截取与大小写转换的落地写法
在 MT5 里处理品种名、日志前缀这类文本时,从后往前找字符和按位置截子串是两个高频动作。下面这段把反向查找、子串提取、左右去空白、大小写互转都揉在一起,直接能塞进 EA 的辅助模块。 反向定位字符用 StringFindRev,对 "Programming in MQL5 for MetaTrader 5" 找最后一个 '5',返回值是 23;再用 StringSubstr(str,23,1) 从索引 23 取 1 个字符,弹窗会得到 "5" 本身。索引从 0 计,这个 23 是手算也能核对的位置。 自己写的 TrimL / TrimR 靠循环扫首尾,默认剔除制表符、换行、空格和分号。TrimL 从头跑,碰到不在清单里的字符就 break 并返回剩余子串;TrimR 从 Len-1 往回跑,结束时返回 0 到 i+1 的片段。比系统函数更可控,尤其接外部信号文本时不容易留脏字符。 大小写转换不需要接收返回值,StringToUpper(str1) 后 "EuRuSd" 变成 "EURUSD",StringToLower(str2) 变成 "eurusd"。比较时 StringCompare("eurusd","EURUSD",false) 区分大小写会给出非零结果,第三个参数传 true 则不区分,"a" 对 "b" 无论大小写都返回 -1。外汇与贵金属杠杆高,这类字符串清洗用在自动下单前校验品种名,能少踩拼写误触的坑。
class="type">class="kw">string str="Programming in MQL5 for MetaTrader class="num">5"; class=class="str">"cmt">//--- call the function for searching for a position of the last occurrence of the character in the class="type">class="kw">string class="type">int pos=StringFindRev(str,"class="num">5"); class=class="str">"cmt">//--- output the results Alert(pos); class="type">class="kw">string str="Programming in MQL5 for MetaTrader class="num">5"; class=class="str">"cmt">//--- get the substring of the given length from the given position class="type">class="kw">string str2=StringSubstr(str,class="num">23,class="num">1); class=class="str">"cmt">//--- output the results Alert(str2); class="type">class="kw">string TrimL(class="type">class="kw">string Str,class="type">class="kw">string List="\t\n ;") { class=class="str">"cmt">//--- variable for one character of the Str class="type">class="kw">string class="type">class="kw">string ch; class="type">int Len=StringLen(Str); class="type">int i=class="num">0; class=class="str">"cmt">//--- loop iteration over all characters of the Str class="type">class="kw">string for(;i<Len;i++) { class=class="str">"cmt">//--- the next character of the Str class="type">class="kw">string ch=StringSubstr(Str,i,class="num">1); class=class="str">"cmt">//--- if this character is not on the List list, the class="type">class="kw">string should start from this position if(StringFind(List,ch,class="num">0)==-class="num">1) { class="kw">break; class=class="str">"cmt">// terminate the loop } } class=class="str">"cmt">//--- get the substring and class="kw">return it class="kw">return(StringSubstr(Str,i)); } class="type">class="kw">string TrimR(class="type">class="kw">string Str,class="type">class="kw">string List="\t\n ;") { class=class="str">"cmt">//--- variable for one character of the Str class="type">class="kw">string class="type">class="kw">string ch; class="type">int Len=StringLen(Str); class=class="str">"cmt">//--- characters in the class="type">class="kw">string are numbered from class="num">0, so the last character index is one less than the class="type">class="kw">string length class="type">int i=Len-class="num">1; class=class="str">"cmt">//--- loop iteration over all characters of the Str class="type">class="kw">string for(;i>=class="num">0;i--) { class=class="str">"cmt">//--- the next character of the Str class="type">class="kw">string ch=StringSubstr(Str,i,class="num">1); class=class="str">"cmt">//--- if this character is not on the List list, the class="type">class="kw">string should start from this position if(StringFind(List,ch,class="num">0)==-class="num">1) { class="kw">break; class=class="str">"cmt">// terminate the loop } } class=class="str">"cmt">//--- get the substring and class="kw">return it class="kw">return(StringSubstr(Str,class="num">0,i+class="num">1)); } class="type">class="kw">string str="EuRuSd"; class="type">class="kw">string str1=str; class="type">class="kw">string str2=str; class=class="str">"cmt">//--- change the case of strings StringToUpper(str1); StringToLower(str2); class=class="str">"cmt">//--- output the results Alert(str1," ",str2); class="type">int Result=StringCompare("eurusd","EURUSD",class="kw">false); Alert(Result); class="type">int Result=StringCompare("a","b",true); Alert(Result);