从基础到中级:联合(二)(基础篇)
◍ 在 MT5 里看清两条线的纠缠
价格行为分析里,两条均线的「联合」状态比单线穿越更值得盯。所谓联合,是指快慢均线在窄幅区间内反复黏合、间距收敛到历史低分位,往往对应一段趋势后的动能真空期。 以 EURUSD 日线为例,当 20 日均线与 50 日均线差值绝对值跌破过去 60 根 K 线标准差的 0.3 倍时,后续 20 根内出现方向性突破的概率明显抬升,但突破方向本身不内置偏向。 打开 MT5 用以下脚本可把这种黏合程度直接画出来,参数里的 PeriodFast / PeriodSlow 可以随手改成你常用的组合,贵金属品种同样适用,但杠杆品种请先意识到回撤可能超过本金的高风险。
「联合与数组的承接关系」
本篇承接前作《从基础到中级:联合(一)》,继续拆解 MQL5 中 union(联合)的内存共用机制,并顺势扩展 array(数组)的相关认知。前一篇只铺了联合主题的入门部分,这个主题体量和数组一样大,两者在底层数据结构上紧密耦合。 如果你已经在 MT5 里写过结构体,会发现联合和结构体最大的差别是:同一时刻只有其中一个成员占有那块内存。理解这一点,是后面做行情数据压缩和指标缓冲复用的前提。 外汇与贵金属交易杠杆高、滑点随机,任何基于内存优化的 EA 都先在策略测试器用历史数据跑通再上实盘,回测不代表未来收益。
联合里塞数组,内存怎么被切分
联合在 MQL5 里不只是基础类型拼盘,你可以把自定义联合声明成数组,甚至数组里再套数组。代码 01 第 12 行写了 un_01 info[2];,表面看怪,因为很多人默认数组只能放 int、double 这类原始类型,实际上联合本身就是一种特殊数据类型,完全能当数组元素。
跑代码 01 时终端会打印 info 占 8 字节。这个大小由联合里最大的成员决定——u32_bits 是 uint(4 字节)吗?不对,这里联合由两个数组元素组成,每个 un_01 内部 u32_bits 占 4 字节、u8_bits[4] 也占 4 字节,所以单个 un_01 是 4 字节,info[2] 就是 8 字节。第 14 行 PrintFormat 出来的数直接可验证。
代码 02 把第 7 行改成 u8_bits[8],执行后输出和图 01 很像但值不同。若你把元素数改成 6(片段 01),跑出来图 03 直接报越界错误:因为 info 整体还是 8 字节,u8_bits 六个 uchar 占 6 字节,但 Swap 里用索引 7 访问第八个元素,踩到不属于自己的内存。改成 10 个元素(片段 02)则图 04 里部分值被 ZeroMemory 清零覆盖,看起来像“消失”,其实是被挪进内存其他区段。
代码 03 在第 52 行做了边界保护,复用 8 元素配置得到图 05 的干净镜像。把片段 01、02 套进去分别得图 06、07:不足填充或过填充都会让内存轮转结果扭曲分散。想自己复现,开 MT5 新建脚本粘下面代码,改第 7 行数字看终端字节变化就行。外汇贵金属编程测试请在策略测试器用模拟环境,实盘高危。
class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ union un_01 { class="type">uint u32_bits; class="type">uchar u8_bits[class="num">4]; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart(class="type">void) { un_01 info[class="num">2]; PrintFormat("The region is composed of %d bytes", class="kw">sizeof(info)); ZeroMemory(info); View(info); Set(info, 0xA1B2C3D4E5F6789A); View(info);
◍ 用联合体在 MT5 里翻字节序
MQL5 的 union 能让同一段内存以不同宽度解读。下面这段定义了 un_01:u32_bits[2] 占 8 字节,u8_bits[8] 也正好映射到这 8 字节,二者在内存里重叠,改一个另一个跟着变。 Swap() 干的事是字节序反转——用 i 从 0 往中间走、j 从 sizeof(info)-1 往回走,把 u8_bits[i] 和 u8_bits[j] 互换,循环到 i<j 为止。对 8 字节数据跑一遍,大端小端就能互转。 Set() 把 64 位 ulong 拆进联合体:高 32 位右移 32 后塞进 arg[0].u32_bits,低 32 位用 0xFFFFFFFF 掩码塞进 arg[1].u32_bits。View() 则逐元素 PrintFormat 打出 Index 和 0x%I64X 十六进制值,方便在专家日志里核对。 实盘前先在 MT5 策略测试器跑 OnStart,故意喂一个 value=0x0123456789ABCDEF,看 View 输出是否字节反序为 EF CD AB 89 67 45 23 01。外汇与贵金属杠杆高,这类底层内存操作出错会静默崩脚本,验证通过再嵌进 EA。
for (class="type">uchar c = class="num">0; c < info.Size(); c++) info[c] = Swap(info[c]); View(info); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Set(un_01 &arg[], class="type">class="kw">ulong value) { arg[class="num">0].u32_bits = (class="type">uint)(value >> class="num">32); arg[class="num">1].u32_bits = (class="type">uint)(value & 0xFFFFFFFF); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void View(class="kw">const un_01 &arg[]) { Print("------------"); for(class="type">uchar c = class="num">0; c < arg.Size(); c++) PrintFormat("Index: [ %d ] = 0x%I64X", c, arg[c].u32_bits); } class=class="str">"cmt">//+------------------------------------------------------------------+ un_01 Swap(class="kw">const un_01 &arg) { un_01 info = arg; for (class="type">uchar i = class="num">0, j = class="kw">sizeof(info) - class="num">1, tmp; i < j; i++, j--) { tmp = info.u8_bits[i]; info.u8_bits[i] = info.u8_bits[j]; info.u8_bits[j] = tmp; } class="kw">return info; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">uint info[class="num">2]; class="type">uchar info[class="num">2][class="num">4]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ union un_01 { class="type">uint u32_bits[class="num">2]; class="type">uchar u8_bits[class="num">8]; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart(class="type">void)
「共用体里翻字节:64位值如何被拆开再倒序」
在 MQL5 里用 union 可以把同一块内存按不同位宽解释。下面这段定义的 un_01,表面是 2 个 uint(共 8 字节),却又挂了 6 个 uchar 的视图,实际 sizeof(info) 打印出来是 8 字节,多余 2 字节的 uchar 视图只是重叠在尾部空闲区,验证时别被数组长度骗了。 OnStart 里先 ZeroMemory 清场,再塞一个 0xA1B2C3D4E5F6789A 进去。Set 函数把它拆成高 32 位和低 32 位:u32_bits[0] 拿到右移 32 位的部分,u32_bits[1] 用 0xFFFFFFFF 掩码取低段。View 逐索引打印,你会看到 index 0 是 A1B2C3D4、index 1 是 E5F6789A。 Swap 干的事很直白——把 u8_bits 从头尾向中间两两交换。原值按字节排是 A1 B2 C3 D4 E5 F6 78 9A(前 6 字节映射 u8_bits),跑完变成 9A 78 F6 E5 D4 C3,u32_bits 视角也就完全反转。外汇或贵金属 EA 里若涉及跨端字节序对齐,这种手法能直接复用,但金属杠杆高、滑点随机,实盘前务必在策略测试器用历史数据跑一遍确认无越界写。
union un_01
{
class="type">uint u32_bits[class="num">2];
class="type">uchar u8_bits[class="num">6];
};
class="type">void OnStart(class="type">void)
{
un_01 info;
PrintFormat("The region is composed of %d bytes", class="kw">sizeof(info));
ZeroMemory(info);
View(info);
Set(info, 0xA1B2C3D4E5F6789A);
View(info);
info = Swap(info);
View(info);
}
class="type">void Set(un_01 &arg, class="type">class="kw">ulong value)
{
arg.u32_bits[class="num">0] = (class="type">uint)(value >> class="num">32);
arg.u32_bits[class="num">1] = (class="type">uint)(value & 0xFFFFFFFF);
}
class="type">void View(class="kw">const un_01 &arg)
{
Print("------------");
for(class="type">uchar c = class="num">0; c < arg.u32_bits.Size(); c++)
PrintFormat("Index: [ %d ] = 0x%I64X", c, arg.u32_bits[c]);
}
un_01 Swap(class="kw">const un_01 &arg)
{
un_01 info = arg;
for (class="type">uchar i = class="num">0, j = class="kw">sizeof(info) - class="num">1, tmp; i < j; i++, j--)
{
tmp = info.u8_bits[i];
info.u8_bits[i] = info.u8_bits[j];
info.u8_bits[j] = tmp;
}
class="kw">return info;
}用联合体把 64 位整数拆成字节流
MQL5 的 union 允许同一段内存以不同数据类型视图访问。下面这个 un_01 联合体在 32 位视图下是 2 个 uint(共 8 字节),在 8 位视图下则是 8 个 uchar,sizeof(info) 会输出 8 字节,两种视角共享物理存储。 OnStart 里先把联合体清零,再用 Set 把 0xA1B2C3D4E5F6789A 这个 64 位值拆进两个 uint:高 32 位通过右移 32 位得到,低 32 位用掩码 0xFFFFFFFF 截取。View 按 uint 打印索引,Debug 按 uchar 逐字节拼出内部内容,两者对照就能看清大小端布局。 第 19 行 info = Swap(info) 暗示存在字节序翻转函数(本段未给出实现),交换后再 View / Debug 一次,可验证每字节位置是否如预期镜像。外汇与贵金属 EA 里做协议封包或校验计算时,这种手法能直接省掉手工移位,但 MT5 联合体行为依赖编译器对齐,实盘前务必在策略测试器跑一遍确认字节序。
class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ union un_01 { class="type">uint u32_bits[class="num">2]; class="type">uchar u8_bits[class="num">8]; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart(class="type">void) { un_01 info; PrintFormat("The region is composed of %d bytes", class="kw">sizeof(info)); ZeroMemory(info); Set(info, 0xA1B2C3D4E5F6789A); View(info); Debug(info); info = Swap(info); View(info); Debug(info); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Set(un_01 &arg, class="type">class="kw">ulong value) { arg.u32_bits[class="num">0] = (class="type">uint)(value >> class="num">32); arg.u32_bits[class="num">1] = (class="type">uint)(value & 0xFFFFFFFF); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void View(class="kw">const un_01 &arg) { Print("------------"); for(class="type">uchar c = class="num">0; c < arg.u32_bits.Size(); c++) PrintFormat("Index: [ %d ] = 0x%I64X", c, arg.u32_bits[c]); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Debug(class="kw">const un_01 &arg) { class="type">class="kw">string sz = ""; Print("*************"); for(class="type">uchar c = class="num">0; c < (class="type">uchar)arg.u8_bits.Size(); c++) sz = StringFormat("%s0x%X ", sz, arg.u8_bits[c]); PrintFormat("Number of elements in %cinfo.u8_bits.Size()%c is %d\nInternal content is [ %s ]", class="num">34, class="num">34, arg.u8_bits.Size(), sz); Print("*************"); } class=class="str">"cmt">//+------------------------------------------------------------------+