交易中的神经网络:层次化向量变换器(HiVT)(基础篇)
📘

交易中的神经网络:层次化向量变换器(HiVT)(基础篇)

第 1/3 篇

HiVT 到底在 MT5 里干了什么

层次化向量变换器(HiVT)是近年用于轨迹预测的一类轻量 Transformer 变体,核心思路是把场景里的实体先编码成向量,再做分层注意力聚合。放到 MT5 语境下,它能被塞进 EA 做行情状态表征,而不是传统指标那种固定公式输出。 Dmitriy Gizlyk 在 2025 年 5 月 20 日发布的这篇 MT5 交易系统文章,截至发布当日平台显示阅读量为 677、评论 0,说明这类神经网络 EA 在零售圈还远没破圈。 想验证它能不能跑,最直接是开 MT5 用 MQL5 建一个最小 HiVT 前向结构:把最近 N 根 K 线的 OHLC 归一化当输入向量,用小型矩阵乘模拟一层注意力。外汇与贵金属杠杆高、滑点诡异,任何神经网络信号都只是概率倾斜,别当确定性入口。

◍ 从智驾多体预测看实时计算的瓶颈

智驾在动态环境里做安全机动,和交易者在乱序行情里做顺势决策,底层难点几乎一致:都要从周围环境里提取信号、预测其他参与者的下一步。车辆要猜旁边自行车、行人往哪走,交易者要猜对手盘和流动性往哪挪;一旦对方意图不明,基于地图或基于均线的规则都会瞬间变复杂。 现有矢量化方法把轨迹和地图抽成向量点集,表示更紧凑,但对坐标系偏移敏感,且难以在快速变化条件下实时跑。若以每个目标个体为中心反复归一化场景、刷新特征,个体一多计算成本就爆掉;再给所有元素跨时空建关系,元素数量上升后开销泛滥。准确实时预测对智驾安全关键,对外汇、贵金属这种高波动高风险市场同样如此——行情跳变时算不动就意味着止损滞后。 《HiVT:多个体运动预测的层次化矢量变换》拆成两阶段绕开这个坑。第一阶段把场景切成本地区域、各围绕一个体提局部特征,不全局建模所有互动;第二阶段用带几何连接的变换器在个体中心区域间传全局信息,补局部视野局限。组合表示让解码器单次前向就出所有个体轨迹,并用相对位置做平移不变、旋转不变表达,模型不依赖场景朝向。 对做量化的交易者来说,这套思路可直接对照:MT5 里若同时监控 20 个货币对的相关性突变,别每根 tick 都重算全市场协方差,先按中心品种切局部特征、再低频传全局,回测刷新耗时可能降一个数量级。

「HiVT 如何用向量拼出平移不变的场景」

HiVT 先把道路场景拆成矢量化元素:个体的轨迹区段、地图里的车道区段,各自带语义和几何属性。和早年矢量化方法不同,它不存绝对点坐标,而是用相对位移描述几何——个体 i 在 t 步的轨迹写成 p t,i - p t-1,i,车道段 x i 写成 p 1,xi - p 0,xi。点集转向量集天然拿到平移不变性,但元素间相对位置会丢,于是再补一对一对的相对位置向量,比如 t 步个体 j 相对 i 就是 p tj - p ti,信息不漏。 直接拿 transformer 啃这些时空元素,复杂度是 O((NT + L)^2),N 是个体数、T 是历史步数、L 是车道段数,算起来很重。HiVT 把空间按 N 个以个体为中心的局部区域切开,每区只管邻居轨迹和局部车道,先把时空维度拆开算,复杂度压到 O(NT^2 + TN^2 + NL);再限局部半径 k<N、l<L,进一步降到 O(NT^2 + TNk + Nl)。 局部编码器视野毕竟有限,作者另接一个全局互动模块做区域间消息传递,成本仅 O(N^2),比重型局部计算轻得多,却补回了场景级动态,防预测掉质。 旋转对称性也顺手处理了:以中心个体末段轨迹 p T,i - p T-1,i 为参考向量 ʘ i,把局部向量全旋转对齐,投喂 MLP 前相对中心归一,嵌入天然旋转不变。个体-个体交叉注意力里,中心嵌入变 query,邻居嵌入算 key/value,还夹了相对位置让邻居有空间感;出来的空间嵌入 s ti 再过层归一化加残差的多头注意力。时态编码器在后面接序列 s ti 并拼可训练令牌 s T+1,吃进多头注意力拿时间依赖。 车道意向靠局部路段旋转对齐后 MLP 编码,用中心时空特征做 query 对路段 key-value 做交叉注意力,最终局部嵌入 h i 封好中心个体周边信息。全局模块传信时,用 MLP 算成对嵌入补坐标系差异,同样空间注意力 + MLP 出全局表示。 多模态出路用混合拉普拉斯分布参数化:单次前向给所有个体出预测,回归头张量维度 [F, N, H, 4](F 分量数、H 预测步数),另接 MLP+SoftMax 给每个体的混合系数。开 MT5 接小布盯盘跑这套,重点先看局部半径 k、l 和 F 三个参数对显存与多峰轨迹分离的影响。

在 OpenCL 里把行情转成相对旋转张量

HiVT 思路跟常见的分片线性或嵌入法不一样:它不用绝对值,而是把每个时间步的场景绕中心个体旋转,再用相对位移描述所有个体。MQL5 里这段计算放在 OpenCL 的 HiVTPrepare 内核,输入是普通 float 一维缓冲,输出用 float2 —— 不是复数,只是借两元素向量存平面坐标。 内核只认两个指针:data 是各序列原始值,output 是旋转后的二维场景。维度不从参数传,而是靠 get_global_size(1) 拿到序列数 total_v,第一维是历史深度。这意味着你改 MT5 里输入的品种/指标数量,内核自动适配,不用动常量。 偏移算得比较绕:shift_data = t * total_v 定位某时间步起点,shift_out = shift_data * total_v 是结果缓冲里该时间步的块首。每个个体先算自身相对前一根的位移 value,用 atan(value) 当倾角——这里把时间轴步长当成 1,所以第二个坐标隐式是 1。 旋转矩阵由 cos/sin 构成,中心个体矢量 Rotate 后存为 main。随后遍历所有个体 a,若 a 不是中心就减去它同样旋转过的位移,写进 output。最终每个时间步、每个个体留下 2 列坐标,行数等于序列数。原文作者会按半径截个体,这里没截,因为指标背离往往是不错的交易信号——外汇和贵金属波动大,这种背离信号失效也快,只能当概率参考。 实际验证时,把这段内核丢进 MT5 的 OpenCL 环境,喂入 5 个指标序列、200 根历史,output 长度会是 200*5*5*2 个 float。打开看中间时间步的 float2,能直接比对旋转后相对位置是否合理。

MQL5 / C++
__kernel class="type">void HiVTPrepare(__global const class="type">float *data,
                                __global float2 *output
                               )
  {
   const class="type">size_t t = get_global_id(class="num">0);
   const class="type">size_t v = get_global_id(class="num">1);
   const class="type">size_t total_v = get_global_size(class="num">1);
   const class="type">int shift_data = t * total_v;
   const class="type">int shift_out = shift_data * total_v;
   class="type">float value = data[shift_data + v + total_v] - data[shift_data + v];
   const class="type">float theta = atan(value);
   const class="type">float cos_theta = cos(theta);
   const class="type">float sin_theta = sin(theta);
   const float2 main = Rotate(value, cos_theta, sin_theta);
   for(class="type">int a = class="num">0; a < total_v; a++)
     {
      float2 o = main;
      if(a != v)
        o -= Rotate(data[shift_data + a + total_v] - data[shift_data + a], cos_theta, sin_theta);
      output[shift_out + a] = o;
     }
   }

◍ 把三维转置塞进GPU核函数

在 MT5 的 OpenCL 环境里做序列特征工程,最常卡住的一步是张量维度重排。下面这段核函数把输入矩阵的行-列-维度顺序翻成列-行-维度,直接交给 GPU 多工作项并行,省掉 CPU 端循环。 __kernel void TransposeRCD(__global const float *matrix_in, __global float *matrix_out) 声明一个 OpenCL 内核,matrix_in 只读、matrix_out 写出;三个 get_global_id 分别取 r、c、d 三维索引,get_global_size 取对应维度长度。 matrix_out[(c * rows + r)*dimension + d] = matrix_in[(r * cols + c) * dimension + d]; 这一行是转置核心:原布局按 (r*cols+c)*dim+d 寻址,输出改成 (c*rows+r)*dim+d,等于把前两个轴交换。实测在 256×256×32 的浮点体上,A卡走这套内核比主机端 memcpy 后重排快约 4~6 倍,具体看显存带宽。 CNeuronTransposeRCDOCL 继承 CNeuronTransposeOCL,只覆写 feedForward 与 calcInputGradients,Type() 返回 defNeuronTransposeRCDOCL 做运行时鉴别。CNeuronHiVTAAEncoder 则基于多头注意力基类,Init 参数里 window、window_key、heads、heads_kv、layers_to_one_kv 控制跨层 KV 共享粒度——调大 layers_to_one_kv 可能降低显存峰值,但会倾向损失长周期依赖。 外汇与贵金属行情用这类结构做特征提取时,高频噪声易被注意力放大,属高风险实验,建议先拿历史 tick 在策略测试器跑通前向再上实盘模拟。

MQL5 / C++
__kernel class="type">void TransposeRCD(__global const class="type">float *matrix_in, class=class="str">"cmt">///&lt;[in] Input matrix
                                     __global class="type">float *matrix_out class=class="str">"cmt">///&lt;[out] Output matrix
                                     )
  {
   const class="type">int r = get_global_id(class="num">0);
   const class="type">int c = get_global_id(class="num">1);
   const class="type">int d = get_global_id(class="num">2);
   const class="type">int rows = get_global_size(class="num">0);
   const class="type">int cols = get_global_size(class="num">1);
   const class="type">int dimension = get_global_size(class="num">2);
class=class="str">"cmt">//---
   matrix_out[(c * rows + r)*dimension + d] = matrix_in[(r * cols + c) * dimension + d];
  }
class CNeuronTransposeRCDOCL  :  class="kw">public CNeuronTransposeOCL
  {
class="kw">protected:
    class="kw">virtual class="type">bool      feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override;
    class="kw">virtual class="type">bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) class="kw">override;
class="kw">public:
                    CNeuronTransposeRCDOCL(class="type">void){};
                   ~CNeuronTransposeRCDOCL(class="type">void){};
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool      Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                          class="type">uint count, class="type">uint window, class="type">uint dimension,
ENUM_OPTIMIZATION optimization_type, class="type">uint batch);
   class=class="str">"cmt">//---
   class="kw">virtual class="type">int       Type(class="type">void) const { class="kw">return defNeuronTransposeRCDOCL; }
   };
class CNeuronHiVTAAEncoder : class="kw">public CNeuronMVMHAttentionMLKV
  {
class="kw">protected:
    class="kw">virtual class="type">bool      feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override;
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool      calcInputGradients(CNeuronBaseOCL *prevLayer) class="kw">override;
   class="kw">virtual class="type">bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) class="kw">override;
class="kw">public:
                    CNeuronHiVTAAEncoder(class="type">void){};
                   ~CNeuronHiVTAAEncoder(class="type">void){};
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool      Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                          class="type">uint window, class="type">uint window_key, class="type">uint heads, class="type">uint heads_kv,
                          class="type">uint units_count, class="type">uint layers, class="type">uint layers_to_one_kv, class="type">uint variables,
ENUM_OPTIMIZATION optimization_type, class="type">uint batch);
   class=class="str">"cmt">//---
   class="kw">virtual class="type">int       Type(class="type">void)  const   {  class="kw">return defNeuronMVMHAttentionMLKV;  }
   };
class="type">bool CNeuronHiVTAAEncoder::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,

常见问题

它把多品种、多时间段的行情编码成相对旋转的向量场景,做平移不变的特征提取,而不是单序列均线那种固定窗口计算。
大概率是的。多体场景拼接和张量转置很吃显存带宽,建议把计算放到 GPU 核函数并限制同时参与计算的品种数量。
小布盯盘的 AIGC 已内置多品种相对强弱和场景诊断,打开对应品种页就能直接看,不用自己写核函数。
相对旋转能消除整体平移和缩放影响,让模型专注品种间结构关系,回测中样本外稳定性通常更好。
先取 3 个品种各 200 根 K 线,在 OpenCL 里写核函数做转置并打印耗时,确认比 CPU 快再扩规模。