从基础到中级:联合(一)(基础篇)
📘

从基础到中级:联合(一)(基础篇)

第 1/2 篇

从零看 MT5 怎么把多周期信号叠一起

很多做价格行为的人卡在第一步:明明看 1 小时是多头,切到 15 分钟又被扫损。问题不在信号本身,而在你没把不同周期的状态做联合判断。MT5 的优势是原生支持多周期数据拉取,不用自己拼 CSV。 下面这段 MQL5 演示了如何在一个 EA 里同时取日线和 1 小时线的收盘价,用来做最朴素的联合过滤。外汇和贵金属杠杆高,多周期共振也只是提高概率,不等于方向锁死。 代码里 iClose 的第二个参数是周期枚举,换周期只改这一个参数就能复用。你可以直接开 MT5 新建 EA 把这段贴进去,把 Print 输出的两个值对照图表验证。

MQL5 / C++
class="type">int OnInit()
{
   class="type">class="kw">double d1 = iClose(_Symbol, PERIOD_D1, class="num">0);
   class="type">class="kw">double h1 = iClose(_Symbol, PERIOD_H1, class="num">0);
   Print("Daily close: ", d1, "  H1 close: ", h1);
   class="kw">return(INIT_SUCCEEDED);
}

「先搞定数组那篇再往下看」

这套材料只作学习用途,别拿去当实盘信号源或自动化下单工具直接跑。外汇和贵金属杠杆高、滑点诡,任何代码在没验证前都只是教学样本。 上一节讲到的数组用法,是后面所有内容的前置门槛。我之前那篇《从基础到中级:数组(四)》里提到的处理方式,如果没摸熟,这一篇开始的 UNION 概念你会觉得像在变魔术。 所谓 UNION,不是平时说的“联盟”,而是不少编程语言里那个能把同一段内存按不同类型解释的联合结构。它在 MQL5 里不常露面,但一旦用对,能省掉很多冗余拷贝,尤其在要压榨计算资源时。 先决条件很直白:至少对数组那篇有肤浅理解,否则跟解释会吃力。它是分水岭——左边是基础语法,右边是更硬核的内存与类型操作。

◍ 用共享内存块拼出 UNION 的底层逻辑

在 MT5 里跑一段普通结构代码,常会看到第 8、9、10 行定义的离散值被整体丢进第 60 行过程,而过程签名要的其实是个数组。CPU 不在乎变量名,它只认一连串字节;源码里值和命名变量没有直连映射,对机器反而最自然。图 02 标出的 10 个红色矩形即 10 个字节元素,6 个蓝色矩形若拆开要 6 个独立变量,但红色块因碎片考虑统一用最小单元,解码实际发生在第 70 行循环而非第 67 行。 顺着这个内存模型自然会问:能不能只读蓝色矩形就反推出红色元素的值?如果可以,第 28 到 48 行之间的编码和第 70 行的解码都能省掉拆块步骤。这个念头正是 UNION 诞生的起点——它把同一块共享内存按不同字节宽度切成可个性操控的单元,C/C++ 里早用烂了,MQL5 里规则稍有不同。 代码 02 先声明 union 并强制带标识符(MQL5 不允许匿名联合),它占 8 字节而非 16 字节,因为 u64_bits 与 u32_bits 数组共享区域。给 u64_bits 赋值后整块内存就写了进去,第 18–20 行借一个同尺寸临时变量互换数组两索引,第 22 行打印出镜像前和交换后的两行输出。 代码 03 把循环放进第 20 行只跑一半元素(偶数个时直接 len/2),就能把 ulong 联合整体镜像;想改镜 int 只需把第 8、9 行类型换成 int 并更新第 14 行值,联合内数组必须是静态的,动态数组编译器会直接报错。代码 04 把联合提为全局以供过程参数用 un_01 类型;代码 05 用函数返回同类型,第 23 行以 const 引用传入、第 25 行另建可改副本,因此第 12 行原变量没被改,返回的新联合在第 34 行交出结果。外汇与贵金属 EA 用这类手法做位级操纵时,杠杆与滑点风险仍偏高,回测前先在策略测试器单跑片段确认字节布局。

把多类型变量压进一个字节流

在 MT5 脚本里做私有协议封装时,常要把 uint、ushort、uchar 等不同宽度的值塞进同一段 uchar 数组。下面这段从主程序入口 OnStart 拆出来,演示了如何用位移和掩码把 0xCADA5169、0x43BC、B'01011101' 三个量顺序写入 Infos 缓冲区。 Infos 最终长度会是 11 字节:头 5 字节装 ui(1 字节长度标记 + 4 字节大端数据),接着 3 字节装 us(1+2),最后 3 字节装 uc(1+1)。ArrayPrint 打出来能看到十进制形式的字节序列,方便对照 WireShark 或文件落盘校验。 位移写法里 (ui >> 24) 取最高位字节,(ui & 0xFF) 取最低位,这种大端排布在跨平台通信时更不容易踩坑。外汇与贵金属 EA 做信号外传时,这类手工打包比直接用结构体序列化更可控,但也要注意字节序,否则远端解出来可能是乱码。

MQL5 / C++
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "Tutorial\File class="num">01.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart(class="type">void)
{
    const class="type">uint   ui = 0xCADA5169;
    class="type">class="kw">ushort       us = 0x43BC;
    class="type">uchar        uc = B&class="macro">#x27;class="num">01011101&class="macro">#x27;;

    class="type">uchar        Infos[],
                 counter = class="num">0;
    class="type">uint         start,
                 number;

    PrintFormat("Translation personal.\n" +
                "FUNCTION: [%s]\n" +
                "ui => 0x%s\n" +
                "us => 0x%s\n" +
                "uc => B&class="macro">#x27;%s&class="macro">#x27;\n",
                __FUNCTION__,
                ValueToString(ui, FORMAT_HEX),
                ValueToString(us, FORMAT_HEX),
                ValueToString(uc, FORMAT_BINARY)
                );
    
    number = class="kw">sizeof(ui) + class="num">1;
    start = Infos.Size();
    ArrayResize(Infos, start + number);
    Infos[counter++] = class="kw">sizeof(ui);
    Infos[counter++] = (class="type">uchar)(ui >> class="num">24);
    Infos[counter++] = (class="type">uchar)(ui >> class="num">16);
    Infos[counter++] = (class="type">uchar)(ui >> class="num">8);
    Infos[counter++] = (class="type">uchar)(ui & 0xFF);

    number = class="kw">sizeof(us) + class="num">1;
    start = Infos.Size();
    ArrayResize(Infos, start + number);
    Infos[counter++] = class="kw">sizeof(us);
    Infos[counter++] = (class="type">uchar)(us >> class="num">8);
    Infos[counter++] = (class="type">uchar)(us & 0xFF);

    number = class="kw">sizeof(uc) + class="num">1;
    start = Infos.Size();
    ArrayResize(Infos, start + number);
    Infos[counter++] = class="kw">sizeof(uc);
    Infos[counter++] = (uc);

    Print("******************");
    PrintFormat("The Infos block contains %d bytes.", Infos.Size());
    ArrayPrint(Infos);
    Print("******************");
}

常见问题

建议先弄懂数组索引和内存布局,否则联合里字段错位会导致信号读错,顺序应是数组基础→共享内存→联合。
可以但占内存翻倍且无法同一地址复用,联合靠共享内存块能把多类型压进一个字节流,更省资源。
小布可读取你的品种页配置,自动比对联合内存布局与周期映射,标出可能错位的字段让你改。
按成员最大对齐字节算起始地址,依次排布,用sizeof看各类型长度,避免跨边界覆盖。
因为联合本质就是同一内存多解释,不懂块分配就会越界或脏读,实盘信号可能完全反掉。