交易中的神经网络:TEMPO 方法的实施结果(基础篇)
用 TEMPO 把神经网络搬进 MT5
- 年 4 月 15 日发布的 TEMPO 方法,把时间序列的时序模式编码后直接喂给神经网络,在 MetaTrader 5 里做信号生成。作者 Dmitriy Gizlyk 的公开帖获得 730 次浏览、11 条讨论,说明这类做法在实盘圈已有一定关注度。
这套思路的核心不是堆层数,而是先把价格运动转成带时间偏移的特征向量,再让网络学「当前形态之后倾向于怎么走」。外汇与贵金属杠杆高、滑点跳空频繁,任何历史拟合出的倾向都只是概率,不是保证。 想验证的人可以直接开 MT5 新建一个 EA 骨架,把 TEMPO 的特征构造逻辑写进 OnTick,用策略测试器跑 EURUSD 的 M15 看看信号分布,再决定是否深挖。
◍ 用冻结的GPT-2给行情序列做嵌入
TEMPO 的思路是把已经预训练好的语言模型(作者实验里用的是 GPT-2)直接拿来跑时间序列,而不是从零训一个模型。GPT-2 在多种语言的大规模语料上见过词与词之间的长期依赖,这种能力正好能借来预测价格序列里的先后关系。 但语言模型和裸行情数据之间隔着一层:模型不吃纯数字,它要先经过嵌入把文本变成隐藏状态,再算概率、出下一个符号。TEMPO 的做法是在训练时把 GPT-2 的参数冻结,只去优化「把行情转成它能懂的嵌入」这一截。 具体落地时,先把时间序列拆成趋势、季节性和其余分量,各分段后映射为模型可用的嵌入;作者还加了软性提示来引导模型偏向趋势或季节性分析。这样模型可解释性比黑箱 LSTM 强,你能看清哪个分量在拽预测值。外汇和贵金属波动受消息面干扰大,这套方法也只是概率层面辅助,实盘前务必在 MT5 用历史数据回测验证。
「把 TEMPO 封装进单层模块的真实代价」
这套预测架构表面只有 3 个神经层,但 CNeuronTEMPOOCL 内部藏了 24 个嵌套层,其中 12 层带可训练参数,还塞了 2 个注意力单元——每个都是四层自注意力、4 个头。作者把它包成单层模块,用户只填 Init 参数就能搭出复杂模型,代价是调结构自由度被类里参数卡死,想换复杂性得改代码。 原生数据进来后故意跳过批量归一化。TEMPO 把序列拆成趋势、季节、残差三条线:趋势用分段线性提,季节用离散傅里叶按振幅抓周期,残差就是前两者减完的剩量。单变量序列数值本来就可比,提前归一化反而白吃算力。 但分解后必须在分量级做归一化。趋势装了信息主体,季节振幅明显低,残差多是噪声(新闻冲击之类)。若只在分解前归一化原生数据,分量之间仍不可比,模型稳定性会掉。实验里频域和交叉注意力模块都设了 4 头、4 嵌套层。 输出处没用简单相加,而是无激活卷积做加权总和,参数上限锁在小于 1,避免爆值。可这和环境里 RSI(0–100)这类指标尺度对不上,MSE 一跑预测可能顶满。补的 CNeuronFreDFOCL 频率对齐层对缩放不敏感,学的是走势结构而非绝对值。外汇/贵金属杠杆高,这套结构若直接照搬实盘,训练成本与过拟合风险都偏大,建议先 MT5 用附件架构跑历史回测看误差曲线。
class="type">bool CreateEncoderDescriptions(CArrayObj *&encoder) { class=class="str">"cmt">//--- CLayerDescription *descr; class=class="str">"cmt">//--- if(!encoder) { encoder = new CArrayObj(); if(!encoder) class="kw">return false; } class=class="str">"cmt">//--- Encoder encoder.Clear(); class=class="str">"cmt">//--- Input layer if(!(descr = new CLayerDescription())) class="kw">return false; descr.type = defNeuronBaseOCL; class=class="str">"cmt">// 基础全连接输入层类型 class="type">int prev_count = descr.count = (HistoryBars * BarDescr); class=class="str">"cmt">// 层节点数=历史柱数*单柱描述维度,须与输入张量同尺寸 descr.activation = None; class=class="str">"cmt">// 输入层不激活 descr.optimization = ADAM; class=class="str">"cmt">// 用 ADAM 优化 if(!encoder.Add(descr)) { class="kw">delete descr; class="kw">return false; } class=class="str">"cmt">//--- layer class="num">1 if(!(descr = new CLayerDescription())) class="kw">return false; descr.type = defNeuronTEMPOOCL; class=class="str">"cmt">// TEMPO 封装模块 descr.count = HistoryBars; class=class="str">"cmt">// 输出预测步数=历史柱数 descr.window = BarDescr; class=class="str">"cmt">// 单时间序列特征宽 descr.step = NForecast; class=class="str">"cmt">// 预测前向步长 descr.window_out = class="num">4; class=class="str">"cmt">// 输出通道4(趋势/季节/残差+聚合) descr.layers = class="num">4; class=class="str">"cmt">// 注意力嵌套4层 descr.batch = class="num">1e4; class=class="str">"cmt">// 批大小1万 descr.activation = None; descr.optimization = ADAM; if(!encoder.Add(descr)) { class="kw">delete descr;
编码器与 actor 的网络层描述构造
下面这段是构建预测编码器第二层与强化学习 actor 网络的核心代码,直接决定 MT5 里 OpenCL 神经层的结构与内存分配。 编码器第二层把 descr.type 设为 defNeuronFreDFOCL,window 绑定 BarDescr,count 取 NForecast,step 写成 int(true) 即 1,probability 给到 0.7f,优化器用 ADAM,激活函数为 None。若 encoder.Add(descr) 失败则 delete 并回 false,成功才继续。 actor 网络先清表,输入层用 defNeuronBaseOCL,节点数 = AccountDescr,无激活;第一隐藏层同样类型,count = EmbeddingSize,激活改 SIGMOID。第二层换成 defNeuronMLCrossAttentionMLKV,通过 ArrayCopy 把 units 设成 {1, NForecast}、windows 设成 {EmbeddingSize, BarDescr}、heads 设成 {4, 2},layers=4、step=1、window_out=32。 最后 cSum 以 Init(0,24,OpenCL,3,3,1,iVariables,iForecast,optimization,iBatch) 起手,激活置 None,并宏定义 MAX_WEIGHT 为 1.0e-3f 限制权重上限。外汇与贵金属行情下用这套结构做推理属高风险,层参数若与显存不匹配会直接初始化失败,建议先在策略测试器开 OpenCL 日志核验。
class="kw">return false; } class=class="str">"cmt">//--- layer class="num">2 if(!(descr = new CLayerDescription())) class="kw">return false; descr.type = defNeuronFreDFOCL; descr.window = BarDescr; descr.count = NForecast; descr.step = class="type">int(true); descr.probability = class="num">0.7f; descr.activation = None; descr.optimization = ADAM; if(!encoder.Add(descr)) { class="kw">delete descr; class="kw">return false; } class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CreateDescriptions(CArrayObj *&actor, CArrayObj *&critic) { class=class="str">"cmt">//--- CLayerDescription *descr; class=class="str">"cmt">//--- if(!actor) { actor = new CArrayObj(); if(!actor) class="kw">return false; } if(!critic) { critic = new CArrayObj(); if(!critic) class="kw">return false; } class=class="str">"cmt">//--- Actor actor.Clear(); class=class="str">"cmt">//--- Input layer if(!(descr = new CLayerDescription())) class="kw">return false; descr.type = defNeuronBaseOCL; class="type">int prev_count = descr.count = AccountDescr; descr.activation = None; descr.optimization = ADAM; if(!actor.Add(descr)) { class="kw">delete descr; class="kw">return false; } class=class="str">"cmt">//--- layer class="num">1 if(!(descr = new CLayerDescription())) class="kw">return false; descr.type = defNeuronBaseOCL; prev_count = descr.count = EmbeddingSize; descr.activation = SIGMOID; descr.optimization = ADAM; if(!actor.Add(descr)) { class="kw">delete descr; class="kw">return false; } class=class="str">"cmt">//--- layer class="num">2 if(!(descr = new CLayerDescription())) class="kw">return false; descr.type = defNeuronMLCrossAttentionMLKV; { class="type">int temp[] = {class="num">1, NForecast}; ArrayCopy(descr.units, temp); } { class="type">int temp[] = {EmbeddingSize, BarDescr}; ArrayCopy(descr.windows, temp); } { class="type">int temp[] = {class="num">4, class="num">2}; ArrayCopy(descr.heads, temp); } descr.layers = class="num">4; descr.step = class="num">1; descr.window_out = class="num">32; descr.activation = None; descr.optimization = ADAM; if(!actor.Add(descr)) { class="kw">delete descr; class="kw">return false; } if(!cSum.Init(class="num">0, class="num">24, OpenCL, class="num">3, class="num">3, class="num">1, iVariables, iForecast, optimization, iBatch)) class="kw">return false; cSum.SetActivationFunction(None); class="macro">#define MAX_WEIGHT class="num">1.0e-3f class=class="str">"cmt">//--- layer class="num">3
◍ Actor-Critic 网络的末端层装配
上面这段把智能体的 Actor 与 Critic 两条网络在 Latent 层之后的结构一次性铺开。Actor 从第 4 层起连续两个 LatentCount 规模的 sigmoid 隐层,第 5 层直接拉到 2*NActions 且不用激活函数,第 6 层用 VAE 层压缩到 NActions,第 7 层是概率 0.7 的自由度滤波层(defNeuronFreDFOCL),输出维度锁死为 1。 Critic 这边先 Clear 清空旧结构,输入层节点数等于 NActions、无激活;第 6 层接 NRewards 个线性节点;第 7 层同样挂一个 window=NRewards、probability=0.7f 的 FreDF 层。两层 FreDF 的 step 都写成 int(false) 即 0,意味着不做跨步滑动。 逐行拆一下 Actor 第 7 层那段:new CLayerDescription() 失败就直接 return false;descr.window 设成 NActions 控制滤波窗口;descr.probability=0.7f 表示约七成概率保留该自由度分量;若 actor.Add(descr) 返回否,先 delete descr 再退出,避免内存泄漏。 在 MT5 里把 LatentCount、NActions、NRewards 三个宏改成你品种的实际维度(比如 XAUUSD 用 4 动作 3 奖励),编译后观察专家函数是否因 Add 失败而中断——外汇与贵金属波动剧烈,这类网络结构只是概率建模,实盘仍属高风险。
if(!(descr = new CLayerDescription())) class="kw">return false; descr.type = defNeuronBaseOCL; descr.count = LatentCount; descr.activation = SIGMOID; descr.optimization = ADAM; if(!actor.Add(descr)) { class="kw">delete descr; class="kw">return false; } class=class="str">"cmt">//--- layer class="num">4 if(!(descr = new CLayerDescription())) class="kw">return false; descr.type = defNeuronBaseOCL; descr.count = LatentCount; descr.activation = SIGMOID; descr.optimization = ADAM; if(!actor.Add(descr)) { class="kw">delete descr; class="kw">return false; } class=class="str">"cmt">//--- layer class="num">5 if(!(descr = new CLayerDescription())) class="kw">return false; descr.type = defNeuronBaseOCL; descr.count = class="num">2 * NActions; descr.activation = None; descr.optimization = ADAM; if(!actor.Add(descr)) { class="kw">delete descr; class="kw">return false; } class=class="str">"cmt">//--- layer class="num">6 if(!(descr = new CLayerDescription())) class="kw">return false; descr.type = defNeuronVAEOCL; descr.count = NActions; descr.optimization = ADAM; if(!actor.Add(descr)) { class="kw">delete descr; class="kw">return false; } class=class="str">"cmt">//--- layer class="num">7 if(!(descr = new CLayerDescription())) class="kw">return false; descr.type = defNeuronFreDFOCL; descr.window = NActions; descr.count = class="num">1; descr.step = class="type">int(false); descr.probability = class="num">0.7f; descr.activation = None; descr.optimization = ADAM; if(!actor.Add(descr)) { class="kw">delete descr; class="kw">return false; } class=class="str">"cmt">//--- Critic critic.Clear(); class=class="str">"cmt">//--- Input layer if(!(descr = new CLayerDescription())) class="kw">return false; descr.type = defNeuronBaseOCL; prev_count = descr.count = NActions; descr.activation = None; descr.optimization = ADAM; if(!critic.Add(descr)) { class="kw">delete descr; class="kw">return false; } class=class="str">"cmt">//--- layer class="num">6 if(!(descr = new CLayerDescription())) class="kw">return false; descr.type = defNeuronBaseOCL; descr.count = NRewards; descr.activation = None; descr.optimization = ADAM; if(!critic.Add(descr)) { class="kw">delete descr; class="kw">return false; } class=class="str">"cmt">//--- layer class="num">7 if(!(descr = new CLayerDescription())) class="kw">return false; descr.type = defNeuronFreDFOCL; descr.window = NRewards; descr.count = class="num">1; descr.step = class="type">int(false); descr.probability = class="num">0.7f; descr.activation = None; descr.optimization = ADAM; if(!critic.Add(descr)) {