交易中的神经网络:使用语言模型进行时间序列预测(基础篇)
📘

交易中的神经网络:使用语言模型进行时间序列预测(基础篇)

第 1/3 篇

用语言模型给行情序列做预测

把时间序列丢进语言模型做预测,本质是把 OHLC 数据当成一种特殊‘文本’序列来建模。MetaTrader 5 自 2024 年起在 Python 桥接和本地 DLL 调用上放宽了限制,让这类实验能在实盘环境跑通,而不只是论文里的玩具。 这套思路的关键点在于:传统 RNN 吃的是连续数值,而语言模型擅长捕捉长程依赖和上下文结构。把每根 K 线的归一化涨跌幅编码成 token,模型可能比 LSTM 更早嗅到波段拐点的概率变化。 外汇与贵金属杠杆高、滑点无常,任何模型输出都只是倾向性信号,不是确定性指令。建议先拿 EURUSD 的 M15 历史数据做离线回测,确认样本外误差稳定再谈接入。

「为什么通用大模型啃不动时间序列」

做价格行为分析的人迟早会碰到一个尴尬:NLP 和 CV 里那些预训练基础模型很猛,但直接搬来跑外汇或贵金属的分钟线,往往还不如浅层网络甚至线性模型在基准测试里稳。最近不少研究指出,深度学习架构没以前想的那么健壮,趋势和季节性这种最基础的时间序列区分特征,经常被模型一股脑吞掉而没真正利用。 核心难点在于,语言模型的结构和方法并不能完整捕捉时间形态的演变。论文 TEMPO 的作者做过正式分析,把时域和频域联系起来,证明注意力机制很难自动把原始序列拆成趋势、季节性和残差——而这步分解对后续建模是必须的。换句话说,你想让 GPT 类模型直接学 K 线,它先天就缺了这道工序。 TEMPO 的做法是把多模态时间序列先拆成三个分量,各自映射进潜在空间当作 GPT 的初始嵌入,再用提示(prompt)从内部属性里解码头寸层面的时态知识。对交易者而言,这套分解本身也给了可解释结构:你能分开看趋势、季节性和残差怎么交互,而不是盯着一个黑箱输出。外汇和贵金属波动受宏观节奏与流动性周期牵引,这类高风险的频域分解思路,值得在 MT5 里自己复现验证。

◍ TEMPO 怎么把统计分解塞进变换器

TEMPO 把时间序列先拆成趋势 X_T、季节性 X_S、残差 X_R 三类分量,再喂给基于解码器的 GPT 架构做表示学习。趋势抓长期形态,季节性在去趋势后评估短周期重复,残差就是前两者抠完剩下的噪声。作者实验显示,这种分解显著简化了后续预测过程,但注意力机制本身是正交变换,类似 PCA,理论上拆不开非正交的趋势和季节性——直接拿原生序列喂自注意力基本无效。 为了算得快,他们用固定大小窗口做局部分解,而不是全局从头拆到尾,同时引入可训练参数评估各分量并扩展到其他模型块。每个全局分量先过可逆归一化,再配一个基于 MSE 的重造损失,强迫局部拆出来的东西和全局分解对齐。 位置编码按段打进去,相邻时间步聚成令牌,历史横向范围拉大了,冗余也降了。令牌过嵌入层后,语言模型的迁移能力就能落到时间序列这个新模态上。 提示策略是半软性的:给趋势、季节性、残差各生成不同提示,和对应原生分量拼一起送进 GPT 模块。每个分量过完 GPT 接全连接层出预测,再合并归一化时提的统计参数投回原空间,各分量预测加总就重造出整条轨迹。另一种做法是不同分量用独立 GPT 模块,但总体预测都是分量组合派生。

用 OpenCL 和 MQL5 搭 TEMPO 分解流水线

TEMPO 方法在 MT5 里落地,核心是把多模态金融序列拆成趋势、季节性、残差三块。我们没有预训练语言模型可用,所以只能复现作者提出的架构框架,用真实历史数据验证它预测时间序列的有效性。趋势提取我弃用了原文的移动平均思路,改用分段线性表示(PLR):每个分段压成斜率、偏移量、分段尺寸三个值,信息密度更高,还能识别不同长度的趋势。 PLR 结果不能直接从原始序列减掉,得在 OpenCL 端写内核做反向补偿。我建了 CutTrendAndOther 内核,用 2 维任务空间(维度一=序列长度,维度二=变量数)跑线程,输入 inputs 和 plr 两个张量,输出 trend 和 other 两个缓冲区。other 就是原始减趋势后的差,后面交给频谱分析提季节性。反向传播的 CutTrendAndOtherGradient 内核不覆盖梯度,而是累加到 PLR 梯度缓冲——因为同一份 PLR 既要隔离趋势、又要当注意力模型提示,两个方向梯度都得收。 季节性走频域路线。DFT 能无失真重造序列,iDFT 也能还原,关键是怎么截频段把季节和噪声分开。我让自注意力在单一序列频谱里找一致频率,匹配上的就当作季节分量。主程序里 CNeuronTEMPOOCL 类继承 CNeuronBaseOCL,所有嵌套对象声明为静态,构造函数留空,Init 里收 sequence / variables / Forecast / heads / layers 几个参数:比如 heads 控制频率注意力头数,layers 是注意力模块层数。前向里先 PLR 提趋势、FFT 提季节、剩残差,三路拼张量后加定位编码,交叉注意力吃 PLR 当提示,最后两层卷积 MLP 出预测。反向 calcInputGradients 按数据流逆序派梯度,季节量的虚部梯度直接写相反符号强制回零。 外汇和贵金属这种高波动品种,用这套分解做预测时过拟合风险不低,建议先拿 EURUSD 的 H1 历史跑通再谈调参。附件里有完整类代码,重点看 feedForward 里指针替换省拷贝的那几行,能少写两次数据复制。

MQL5 / C++
__kernel class="type">void CutTrendAndOther(__global const class="type">float *inputs, __global const class="type">float *plr, __global class="type">float *trend, __global class="type">float *other)

「GPU 内核里的趋势剥离循环」

这段 OpenCL 内核把分段线性回归的结果直接拆成趋势项与残差项,核心在 do-while 里按每段长度定位当前 bar 落在哪一段。dist 用 fmax(plr[...]*lenth, 1) 计算,保证最小跨度为 1 根 bar,避免零步长死循环。 定位到段后,用该段斜率 sloat 与截距 intercept 做 trend_i = sloat * pos + intercept,再用原输入减趋势得到 other_i。inputs 与 trend、other 三个缓冲在同一 shift_in 下写回,方便后续在 MT5 里直接拿残差做均值回归类信号。 第二个内核 CutTrendAndOtherGradient 头部常量计算与第一个完全一致:step_plr = 3 * step_in 是因为每段存了斜率、截距、长度三个浮点。跑之前在 MT5 的 OpenCL 面板确认 GPU 可用,否则 get_global_size 返回 1 会退化成串行。外汇与贵金属杠杆高,这类分解仅用于辅助判断波动结构,实盘须自担风险。

MQL5 / C++
 __global const class="type">float *plr,
 __global class="type">float *trend,
 __global class="type">float *other
 )
 {
 const class="type">size_t i = get_global_id(class="num">0);
 const class="type">size_t lenth = get_global_size(class="num">0);
 const class="type">size_t v = get_global_id(class="num">1);
 const class="type">size_t variables = get_global_size(class="num">1);
class=class="str">"cmt">//--- constants
 const class="type">int shift_in = i * variables + v;
 const class="type">int step_in = variables;
 const class="type">int shift_plr = v;
 const class="type">int step_plr = class="num">3 * step_in;
class=class="str">"cmt">//--- calc position
 class="type">int pos = -class="num">1;
 class="type">int prev_in = class="num">0;
 class="type">int dist = class="num">0;
 do
 {
 pos++;
 prev_in += dist;
 dist = (class="type">int)fmax(plr[shift_plr + pos * step_plr + class="num">2 * step_in] * lenth, class="num">1);
 }
 while(!(prev_in <= i && (prev_in + dist) > i));
class=class="str">"cmt">//--- calc trend
 class="type">float sloat = plr[shift_plr + pos * step_plr];
 class="type">float intercept = plr[shift_plr + pos * step_plr + step_in];
 pos = i - prev_in;
 class="type">float trend_i = sloat * pos + intercept;
 class="type">float other_i = inputs[shift_in] - trend_i;
class=class="str">"cmt">//--- save result
 trend[shift_in] = trend_i;
 other[shift_in] = other_i;
 }
__kernel class="type">void CutTrendAndOtherGradient(__global class="type">float *inputs_gr,
 __global const class="type">float *plr,
 __global class="type">float *plr_gr,
 __global const class="type">float *trend_gr,
 __global const class="type">float *other_gr
 )
 {
 const class="type">size_t i = get_global_id(class="num">0);
 const class="type">size_t lenth = get_global_size(class="num">0);
 const class="type">size_t v = get_global_id(class="num">1);
 const class="type">size_t variables = get_global_size(class="num">1);
class=class="str">"cmt">//--- constants
 const class="type">int shift_in = i * variables + v;
 const class="type">int step_in = variables;
 const class="type">int shift_plr = v;
 const class="type">int step_plr = class="num">3 * step_in;
class=class="str">"cmt">//--- calc position
 class="type">int pos = -class="num">1;
 class="type">int prev_in = class="num">0;

常见问题

通用大模型没见过规整的数值时间序列,容易编造趋势;应先做统计分解再喂给模型,或改用专门处理序列的架构。
先对价格序列做季节趋势分解,把各分量分别编码进变换器输入;可用 OpenCL 在显卡上跑分解循环,降低 CPU 占用。
小布可调用内置的序列分解与轻量模型,直接对打开品种做趋势剥离和概率预测,省去自己搭流水线。
显存带宽和浮点精度是瓶颈,建议用半精度并分批处理;先在少量 K 线验证再全量跑。
是,模型误差可能在杠杆下放大亏损;任何预测只作概率参考,必须配合止损和仓位控制。