掌握 MQL5:从入门到精通(第四部分):关于数组、函数和全局终端变量·综合运用
◍ 给函数参数留个出厂设定
在 MQL5 里写工具函数,经常有那种「大多数时候用同一套、偶尔才改」的附属参数。比如做日志输出,你想把自家消息和终端其他记录区分开,最省事的办法是给字符串前后加固定包裹符,而这个包裹符没必要每次调用都手写。
把默认值直接写在形参声明里就能解决:必填的放前面,带默认值的放后面,而且带默认值的参数必须连续排在尾部,不能插在必填项中间。
下面这段函数给字符串前后各加了默认 "=== " 和 " ===" 的包裹。只传主串时输出 === My first string ===;改了前缀不改后缀则是 ~~ My first string ===;前后都改就变成 ~~ My first string ~~。开 MT5 新建脚本粘进去跑,终端会直接打印这三行,验证默认参数省略规则。
写 EA 里的告警封装、品种标签前缀时,用这招能少写大量重复实参,但记住外汇和贵金属杠杆高,脚本只管信息格式化,不涉及任何方向判断。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add a prefix and suffix to a sting | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string MakeMessage( class="type">class="kw">string mainString, class="type">class="kw">string prefix="=== ", class="type">class="kw">string suffix=" ===" ) { class="kw">return (prefix + mainString + suffix); } Print( MakeMessage("My first class="type">class="kw">string") ); class=class="str">"cmt">// Default prefix and suffix Print( MakeMessage("My second class="type">class="kw">string", "~~ ") ); class=class="str">"cmt">// Prefix changed, suffix remains unchanged Print( MakeMessage("My third class="type">class="kw">string", "~~ ", " ~~") ); class=class="str">"cmt">// Both actual parameter have been changed class=class="str">"cmt">// Script output: class=class="str">"cmt">// === My first class="type">class="kw">string === class=class="str">"cmt">// ~~ My first class="type">class="kw">string === class=class="str">"cmt">// ~~ My first class="type">class="kw">string ~~
一个函数要吐多个值怎么破
MQL5 的 return 只能回传单个结果,且函数签名不允许直接返回数组。实战里常卡在:既要算一根烛形的收盘时间,又要顺带把收盘价带出来,或者一次性捞回当前可交易品种清单。 第一种思路是定义结构体,把多个字段打包成一个复合类型,函数返回该结构变量。这样调用方拿到一个对象就能同时取用时间、价格等字段,比拆成多个函数更干净。 第二种是引用传参。在参数前加 &,函数体内写进去的值会在调用结束后保留,虽不能用 return 带回,但效果和多个出口差不多。 全局变量能绕开限制,但会污染命名空间、多线程下易踩坑,仅在上述两条路都走不通时才考虑,日常写指标或 EA 尽量压住不用。
「input 与 extern 的全局变量门道」
MQL5 里写在函数外的全局变量,碰到 input 和 extern 两个修饰符就会走出特殊路径。input 声明的变量只在程序属性框或代码里赋值,运行期内只读,EA 和指标能丢进策略测试器做参数优化。 想让某个 input 避开优化,就在前面加 s 写成 sinput,或加 static input;示例里 points 和 unoptimizedVariable 就是这么处理的。参数还能用 input group "组名" 做视觉分组,MetaEditor 对话框里组标题会被高亮,长代码也好找。 枚举配合 input 能生成下拉列表,变量同行写注释就会取代变量名显示,比如 DIRECTION_UP 后跟 // Up。但注释语言编译时就钉死,想换语种得另编一份文件,老手一般直接用英文。 两个硬限制得记牢:参数名不超过 63 字符,超了编译器直接截断;字符串参数值上限 254 字符。真要传长网址,别塞进 input,硬编码成普通全局变量或从文件拼更稳。 extern 是多文件工程里的桥。A 文件定义全局变量,B 文件用 extern 声明同名变量就能跨文件读写,不必 #include。编译时链接到真实地址,IDE 能自动替换,函数里改它没障碍。 下面这段把上述写法揉在一起,重点看 sinput、static input 怎么躲优化,以及 extern 收尾的声明方式。
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">string</span> smart = <span class="class="type">class="kw">string">"The smartest decision"</span>; <span class="comment">class=class="str">"cmt">// The window will contain <i>this</i> description</span> <span class="keyword">input</span> <span class="keyword">class="type">class="kw">double</span> price =<span class="number">class="num">1.0456</span>; <span class="comment"> class=class="str">"cmt">// optimize</span> <span class="keyword">sinput</span> <span class="keyword">class="type">int</span> points =<span class="number">class="num">15</span>; <span class="comment">class=class="str">"cmt">// NOT optimize</span> <span class="keyword">class="kw">static</span> <span class="keyword">input</span> <span class="keyword">class="type">int</span> unoptimizedVariable =<span class="number">class="num">100</span>; <span class="comment">class=class="str">"cmt">// NOT optimize</span> <span class="keyword">input</span> group <span class="class="type">class="kw">string">"Group Name"</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">script_show_inputs</span> <span class="comment">class=class="str">"cmt">// Enumeration. Allows you to create a list box.</span> <span class="keyword">enum</span> ENUM_DIRECTION { <span class="comment">class=class="str">"cmt">// All class="kw">inline comments next to lines describing parameters, class=class="str">"cmt">// will be displayed instead of the names in the parameters window</span> DIRECTION_UP = <span class="number">class="num">1</span>, <span class="comment">class=class="str">"cmt">// Up</span> DIRECTION_DN = -<span class="number">class="num">1</span>, <span class="comment">class=class="str">"cmt">// Down</span> DIRECTION_FL = <span class="number">class="num">0</span> <span class="comment"> class=class="str">"cmt">// Unknown</span> }; <span class="keyword">input</span> group <span class="class="type">class="kw">string">"Will be optimized"</span> <span class="keyword">input</span> <span class="keyword">class="type">int</span> onlyExampleName = <span class="number">class="num">10</span>; <span class="keyword">input</span> ENUM_DIRECTION direction = DIRECTION_FL; <span class="comment">class=class="str">"cmt">// Possible directions list</span> <span class="keyword">input</span> group <span class="class="type">class="kw">string">"Will not be optimized"</span> <span class="keyword">sinput</span> <span class="keyword">class="type">class="kw">string</span> something = <span class="class="type">class="kw">string">"Something good"</span>; <span class="keyword">class="kw">static input</span> <span class="keyword">class="type">class="kw">double</span> doNotOptimizedMagickVariable = <span class="number">class="num">1.618</span>; <span class="comment">class=class="str">"cmt">// Some magic</span> <span class="comment"> class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| Script program start function |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">void</span> <span class="functions">OnStart</span>() { <span class="comment">class=class="str">"cmt">//---</span> } <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="kw">extern</span> <span class="keyword">class="type">bool</span> testComplete;
◍ 指标够不到的交易数据,用终端全局变量中转
MT5 里指标在编译器层面被禁止调用交易函数,OrderCalcMargin 就在禁用之列。想在自定义指标上显示开 1 手所需的保证金,直接算会编译报错,只能绕路。 可行做法是让脚本或服务先算好金额,写进终端全局变量,指标再去读。脚本和服务不受交易函数限制,这套中转在 EURUSD 上实测能跑通,跑完按 F3 或「工具 -> 全局变量」就能看到写入的两条记录。 全局变量名长度不能超过 63 个字符,这是硬限制。GlobalVariableSet 负责写,GlobalVariableGet 负责读,后者有两种调用形式:拿返回值、或用引用参数接值。 指标端要把读取动作放在 OnInit 里,因为数据只需取一次;OnCalculate 留空也得留着,否则编译不过。指标必须在对应脚本跑完之后再加载到同一图表,否则 GlobalVariableGet 读不到变量,注释不会输出。 DoubleToString 的精度参数默认 8 位小数,顶部注释省略它、底部显式写 2 位,显示差异就是这么来的。外汇与贵金属杠杆高,保证金随价波动,读取到的数值仅反映脚本运行那一刻,盘中介入风险自担。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CalculateMargin.mq5 | class=class="str">"cmt">//| Oleg Fedorov(aka certain) | class=class="str">"cmt">//| mailto:coder.fedorov@gmail.com | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Oleg Fedorov(aka certain)" class="macro">#class="kw">property link "mailto:coder.fedorov@gmail.com" class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property script_show_inputs class=class="str">"cmt">//--- Script input parameters input class="type">class="kw">double requiredAmount = class="num">1; class=class="str">"cmt">// Number of lots class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- Description of local variables class="type">class="kw">string symbolName = Symbol(); class=class="str">"cmt">// 取当前图表品种名 class="type">class="kw">string terminalVariableName; class=class="str">"cmt">// 准备拼终端全局变量名 class="type">class="kw">double marginBuy, marginSell; class=class="str">"cmt">// 分别存买、卖保证金 class="type">class="kw">double currentPrice = iClose(symbolName,PERIOD_CURRENT,class="num">0); class=class="str">"cmt">// 取当前收盘价作为计算价 class="type">bool okCalcBuy, okCalcSell; class=class="str">"cmt">// 标记买卖保证金计算是否成功 class=class="str">"cmt">//--- Main operations class=class="str">"cmt">// Calculate Buy margin okCalcBuy = OrderCalcMargin( ORDER_TYPE_BUY, class=class="str">"cmt">// 订单类型:买 symbolName, class=class="str">"cmt">// 品种名 requiredAmount, class=class="str">"cmt">// 输入的手数 currentPrice, class=class="str">"cmt">// 开仓价
把多空保证金写进全局变量再读回来
开仓前分别算多空保证金,是风险预检的务实动作。MQL5 里 OrderCalcMargin 对 ORDER_TYPE_BUY 与 ORDER_TYPE_SELL 可能返回不同占用额,尤其交叉盘或贵金属点差异常时,上下方向保证金差异可能到个位数百分比,不能只算一边。 下面这段把算出的 marginBuy / marginSell 通过 GlobalVariableSet 落进终端全局变量,命名直接拼 symbolName + "BuyAmount" / "SellAmount"。这样 EA 和指标跨周期也能读到,不依赖本地文件。 读的时候有两种写法:GlobalVariableGet(name, var) 靠引用回传并用 bool 告诉你是否存在;另一种直接 double v = GlobalVariableGet(name) 拿返回值,变量不存在时返回 0。实盘里建议用第一种,顺便判断 okCalcBuy 再决定要不要下单。 外汇与贵金属杠杆高,保证金随价波动,全局变量里的数值只是调用那一刻的快照,隔根 K 线就可能失效,开仓前务必重算。
okCalcSell = OrderCalcMargin( ORDER_TYPE_SELL, class=class="str">"cmt">// Sometimes different amounts are needed for opening up and down symbolName, requiredAmount, currentPrice, marginSell ); class=class="str">"cmt">// Create a terminal variable name for Buy details terminalVariableName = symbolName + "BuyAmount"; GlobalVariableSet( terminalVariableName, class=class="str">"cmt">// Where to write marginBuy class=class="str">"cmt">// What to write ); terminalVariableName = symbolName + "SellAmount"; GlobalVariableSet(terminalVariableName,marginSell); class="type">int OnInit() { class="type">class="kw">string symbolName = Symbol(); class="type">class="kw">string terminalVariableName; class="type">class="kw">double buyMarginValue, sellMarginValue; class="type">bool okCalcBuy; terminalVariableName = symbolName + "BuyAmount"; okCalcBuy = GlobalVariableGet(terminalVariableName, buyMarginValue); terminalVariableName = symbolName + "SellAmount"; sellMarginValue = GlobalVariableGet(terminalVariableName); Comment(
「把买卖保证金打印到图表角落」
这段尾部代码接在指标初始化之后,负责把多空保证金直接写到 MT5 图表左上角,并在退出时清掉注释。OnDeinit 里只调了一句 Comment(""),作用是防止指标卸载后屏幕上还留着旧文字,属于写面板类指标的卫生习惯。 看字符串拼接那几行:买方向用了 DoubleToString(buyMarginValue),第二个参数省略,意味着按默认精度输出;卖方向显式写了 DoubleToString(sellMarginValue,2),强制保留 2 位小数。两者差异在黄金这种点位跳动大的品种上会很直观——卖值永远对齐到分,买值可能甩出一长串浮点尾数。 OnCalculate 在这里是空跑,直接 return(rates_total) 把已处理柱数原样丢回去,说明这个指标不画曲线、只做信息输出。你开 MT5 挂上这类脚本,切到 XAUUSD 的 M5,就能在左上角同时看到 Buy margin / Sell margin 两行,外汇与贵金属杠杆品种保证金随报价实时变,属于高风险暴露,数值仅供仓位测算参考。
"Buy margin is " + DoubleToString(buyMarginValue) class=class="str">"cmt">// Buy margin value, the second parameter class=class="str">"cmt">// of the DoubleToString function is omitted +"\n" class=class="str">"cmt">// Line break +"Sell margin is " + DoubleToString(sellMarginValue,class="num">2) class=class="str">"cmt">// Margin value for sale, indicated the number of class=class="str">"cmt">// decimal places ); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| The function will be called when the program terminates. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">// Очищаем комментарии Comment(""); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator iteration function(not used here) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime &time[], const class="type">class="kw">double &open[], const class="type">class="kw">double &high[], const class="type">class="kw">double &low[], const class="type">class="kw">double &close[], const class="type">long &tick_volume[], const class="type">long &volume[], const class="type">int &spread[]) { class=class="str">"cmt">//--- class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 画得少,看得清
回看这一路讲过的东西,核心其实就几块:数组分静态和动态,静态能用花括号字面量初始化,动态得靠 ArrayResize、ArraySize 这类标准函数撑起容量;变量按生命周期有局部(可加 static 拖长寿命)和全局(可挂 extern、input 接外部入参)之分;参数传递走引用还是走值,直接决定函数里改的是不是原物;再加上跨程序交换数据的全局终端变量——这套骨架若你闭眼能复述,新手帽基本可以摘了。 剩下没碰的只是运算符细节和 MQL5 写指标、EA 时相对其他语言的特性,那些属于上手就能补的皮肉。真要到「专业」档,还有十几个主题(含面向对象)等着,但它们全都会回头依赖你现在半成形的底子。 开 MT5 随便建个脚本,把静态数组初始化和动态数组 ArrayResize 各写一遍,跑通了,这一节就算没白过。