掌握 MQL5:从入门到精通(第四部分):关于数组、函数和全局终端变量·综合运用
📘

掌握 MQL5:从入门到精通(第四部分):关于数组、函数和全局终端变量·综合运用

第 3/3 篇

◍ 给函数参数留个出厂设定

在 MQL5 里写工具函数,经常有那种「大多数时候用同一套、偶尔才改」的附属参数。比如做日志输出,你想把自家消息和终端其他记录区分开,最省事的办法是给字符串前后加固定包裹符,而这个包裹符没必要每次调用都手写。 把默认值直接写在形参声明里就能解决:必填的放前面,带默认值的放后面,而且带默认值的参数必须连续排在尾部,不能插在必填项中间。 下面这段函数给字符串前后各加了默认 "=== "" ===" 的包裹。只传主串时输出 === My first string ===;改了前缀不改后缀则是 ~~ My first string ===;前后都改就变成 ~~ My first string ~~。开 MT5 新建脚本粘进去跑,终端会直接打印这三行,验证默认参数省略规则。 写 EA 里的告警封装、品种标签前缀时,用这招能少写大量重复实参,但记住外汇和贵金属杠杆高,脚本只管信息格式化,不涉及任何方向判断。

MQL5 / C++
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 收尾的声明方式。

MQL5 / C++
<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
&nbsp;&nbsp;{
<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>
&nbsp;&nbsp; DIRECTION_UP =&nbsp;&nbsp;<span class="number">class="num">1</span>, <span class="comment">class=class="str">"cmt">// Up</span>
&nbsp;&nbsp; DIRECTION_DN = -<span class="number">class="num">1</span>, <span class="comment">class=class="str">"cmt">// Down</span>
&nbsp;&nbsp; DIRECTION_FL =&nbsp;&nbsp;<span class="number">class="num">0</span> <span class="comment"> class=class="str">"cmt">// Unknown</span>
&nbsp;&nbsp;};
<span class="keyword">input</span> group <span class="class="type">class="kw">string">"Will be optimized"</span>
<span class="keyword">input</span>&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;onlyExampleName&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= <span class="number">class="num">10</span>;
<span class="keyword">input</span>&nbsp;&nbsp;ENUM_DIRECTION direction&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= DIRECTION_FL;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; something&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= <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>&nbsp;&nbsp; doNotOptimizedMagickVariable = <span class="number">class="num">1.618</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <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&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">void</span> <span class="functions">OnStart</span>()
&nbsp;&nbsp;{
<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp;}
<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 位,显示差异就是这么来的。外汇与贵金属杠杆高,保证金随价波动,读取到的数值仅反映脚本运行那一刻,盘中介入风险自担。

MQL5 / C++
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 线就可能失效,开仓前务必重算。

MQL5 / C++
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 两行,外汇与贵金属杠杆品种保证金随报价实时变,属于高风险暴露,数值仅供仓位测算参考。

MQL5 / C++
   "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 各写一遍,跑通了,这一节就算没白过。

常见问题

在函数声明时直接给形参赋值即可,例如 void Calc(int lot=1),调用 Calc() 会用 1,Calc(2) 用 2。这样留出厂设定,少传参也能跑。
用引用参数把结果带出来:void GetData(double &a, double &b),函数内改 a、b,调用后原变量就被写了。比 return 单值灵活。
可以。小布盯盘的 AIGC 已内置保证金诊断,打开对应品种页就能看到多空保证金读数,不用自己写全局变量中转代码。
用终端全局变量做中转:EA 里把数据写进 GlobalVariableSet,指标里用 GlobalVariableGet 读回。两边不直连也能互通。
input 只能在属性框改、代码里只读;extern 旧版兼容写法,本质类似。高风险品种调参前确认是 input,避免运行时误写。