交易中的神经网络:针对金融市场的多模态、扩增工具型智代(终篇)(基础篇)
把神经网络当交易扩增器而非黑箱
在 MT5 里接神经网络,本质不是让模型替你拍板,而是把多模态信号(价量、时序、文本情绪)做成人工判断的扩增层。很多新手把它当全自动印钞机,实盘外汇与贵金属的高杠杆下,这种错觉会迅速放大回撤。 原文作者 Dmitriy Gizlyk 在 2026 年 1 月 4 日发布于 MT5 社区的文章,截至统计时获得 667 次浏览、0 条评论,说明这类硬核多模态思路在散户圈仍属冷门。 落地动作很直接:开 MT5 → 打开 MetaEditor → 新建 EA 骨架,先不接模型,只把多品种收盘价序列和成交量数组打印到日志,确认数据通道通顺,再谈神经网络接入。
「FinAgent 的模块拆解与我们的 MQL5 落地偏差」
FinAgent 原框架由五个互联模块构成:市场分析、低层/高层反射、记忆、决策制定。市场分析模块从价格图表、新闻、报告中抽取稳定形态;反射模块分两层,低层抓短期信号依赖,高层用历史交易结果调长期策略;记忆模块靠向量相似性压噪并长期存数据;决策模块汇总各路结果出交易建议。 我们上一篇文章已在 MQL5 里用 CNeuronLowLevelReflection 和 CNeuronHighLevelReflection 两个类落地了反射层,并能把记忆直接嵌进反射对象——这跟原版把记忆做成独立模块不同,我们这么干简化了数据流交互。 原文作者用 LLM 做维度压缩,我们没用,改投专门的时间序列模型。本文选了带分段注意力的变换器,在 CNeuronPSformer 里实现,精度和性能更稳。外汇/贵金属市场高波动,这类模型也只是降低误判概率,不保证胜率。 辅助工具模块用 MA、振荡器、量能指标打底,按明确规则出信号,提升决策可解释性。它不限于标准工具,还能塞进先验知识和专家评估,对风控和策略规划算是一层托底。
◍ 归一化后怎么把指标变成买卖信号
把经典指标直接喂给神经模型有个坑:不同指标的数值分布完全不相关也不可比,模型训练效率会掉、预测精度也受影响。之前我们只在模型里用归一化数据,把所有特征拉到零均值、单位方差的共同区间,失真风险最小,但代价是经典策略里靠固定阈值、双线交叉判信号的方式失效了——归一化后阈值会漂移,两条线未必同步偏,信号可能失真甚至不出现。 解法思路其实很朴素:既然归一化后每个变量都变成零均值单位方差,那它本质就是个振荡器。大于 0 视作买入倾向,小于 0 视作卖出倾向;再引入阈值走廊过滤微弱噪声,误报就能压下来。某些特征若历史上有逆转习惯,用可训练参数去适配即可。 落地时在 OpenCL 端写了 MoreLessEqual 内核,一维任务空间、按线程号取数。输入无效值直接替成 0;绝对值超过 1.2e-7 才判信号,正得 1(买)、负得 -1(卖)、否则 0(无信号)。这套是确定性前向计算,不带可训练参数也不传播误差梯度,只为高速出稳定信号。 主程序侧建了 CNeuronMoreLessEqual 类,只覆写前馈、梯度分配,把更新权重写成直接返回 true 的存根。梯度分配里把源缓冲区清零,等价于不传梯度,避免父类误调用。外汇与贵金属波动剧烈、杠杆高风险,这类信号仅作概率参考,实盘前请在 MT5 用历史数据验证阈值 sensitivity。
__kernel class="type">void MoreLessEqual(__global const class="type">class="kw">float * input, __global class="type">class="kw">float * output) { const class="type">size_t i = get_global_id(class="num">0); const class="type">class="kw">float value = IsNaNOrInf(input[i], class="num">0); class="type">class="kw">float result = class="num">0; if(fabs(value) > class="num">1.2e-7) { if(value > class="num">0) result = class="num">1; else result = -class="num">1; } output[i] = result; } class CNeuronMoreLessEqual : class="kw">public CNeuronBaseOCL { 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">virtual class="type">bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) class="kw">override {class="kw">return true; } class="kw">public: CNeuronMoreLessEqual(class="type">void) {}; ~CNeuronMoreLessEqual(class="type">void) {}; }; class="type">bool CNeuronMoreLessEqual::feedForward(CNeuronBaseOCL *NeuronOCL) { if(!OpenCL || !NeuronOCL) class="kw">return class="kw">false; class="type">uint global_work_offset[class="num">1] = { class="num">0 }; class="type">uint global_work_size[class="num">1] = { Neurons() }; ResetLastError(); const class="type">int kernel = def_k_MoreLessEqual; if(!OpenCL.SetArgumentBuffer(kernel, def_k_mle_inputs, NeuronOCL.getOutputIndex())) { printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__); class="kw">return class="kw">false; } if(!OpenCL.SetArgumentBuffer(kernel, def_k_mle_outputs, getOutputIndex())) {
核执行失败时的报错与梯度清零
在 MT5 的 OpenCL 神经网络封装里,set 参数和 execute 内核两步都做了硬失败拦截:任一步返回 false 就立刻打印函数名、错误码与行号并退出,避免脏数据进显存。 报错信息用 __FUNCTION__ 和 __LINE__ 自动定位,比手填字符串更不容易在重构时失真;GetLastError() 返回的是当前线程最后一次错误码,调试时建议配合终端的 Experts 日志对照查。 calcInputGradients 里对上游神经元梯度做了 Fill(0) 处理——当 NeuronOCL 或梯度缓冲为空直接返 false,否则把梯度张量整体置零。这一步意味着该层反向传播默认不向上游累积梯度,验证模型时若发现前层权重不更新,先来这里确认是否被悄悄清零。 外汇与贵金属行情受杠杆与跳空影响,这类 GPU 推理模块仅用于离线特征实验,实盘接入须自担高风险。
printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__); class="kw">return class="kw">false; } class=class="str">"cmt">//--- if(!OpenCL.Execute(kernel, class="num">1, global_work_offset, global_work_size)) { printf("Error of execution kernel %s: %d; line %d", OpenCL.GetKernelName(kernel), GetLastError(), __LINE__); class="kw">return class="kw">false; } class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CNeuronMoreLessEqual::calcInputGradients(CNeuronBaseOCL *NeuronOCL) { if(!NeuronOCL || !NeuronOCL.getGradient()) class="kw">return class="kw">false; class="kw">return NeuronOCL.getGradient().Fill(class="num">0); }