将您自己的 LLM 集成到 EA 中(第 3 部分):使用 CPU 训练自己的 LLM(基础篇)
「在 MT5 里用 CPU 跑通自己的 LLM 训练」
把大语言模型接进 EA 并不一定要靠云端 GPU,MetaTrader 5 的本地环境就能用 CPU 完成轻量训练。2025 年 2 月 21 日发布的示例工程显示,一个参数量可控的 LLM 在普通多核处理器上也能完成前向与反向传播,适合把行情文本信号做成私有模型。 外汇与贵金属市场杠杆高、跳空频繁,本地训练出的模型只代表历史样本下的概率倾向,实盘前必须在 MT5 策略测试器里用 tick 级数据回测。 下面这段 MQL5 代码演示了如何在 EA 中初始化一个极简线性层并做 CPU 上的梯度更新,重点看权重数组与学习率的硬设定。
class="type">class="kw">double weights[class="num">10]; class="type">class="kw">double lr = class="num">0.01; for(class="type">int i=class="num">0;i<class="num">10;i++){ class="type">class="kw">double grad = ComputeGradient(i); weights[i] = weights[i] - lr * grad; }
在普通电脑上跑LLM训练的可行性
上一节我们把 WSL 加 llama.cpp 的环境跑通了,核心结论是:不依赖独显,纯 CPU 也能让一个轻量 LLM 实例跑起来。这一节先把系列的定位说清楚——后续示例都尽量压低硬件门槛,让读者在自家笔记本上就能复现。 系列会分两条硬件分支:纯 CPU 训练线,以及支持 AMD 显卡加速的计算线。模型训练章节里两者都会给到,避免出现「没好卡就别玩」的门槛。 有人会质疑,CPU 训出来的模型能干什么?客观说,让它学复杂金融推理不现实,但针对某类固定格式、样本量不大的任务(比如特定品种的价格注释归类),CPU 版完全够用。本文就聚焦用 CPU 造一个金融数据集并跑通训练。 外汇与贵金属数据带杠杆、波动剧烈,用模型辅助分析属高风险行为,任何输出仅作概率参考。文中涉及的前期环境配置不重复展开,需要的读者可查本系列前篇。
◍ Tokenizer 与预训练模型的分工
在 MT5 接 LLM 做行情点评前,先得弄清文本是怎么被切成 token 的。Tokenizer 把一段文字拆成独立标记再转成向量,这一步决定了模型能『看懂』什么。主流实现已封装在 Transformers 和 tiktoken 库里,不用自己写底层。 不同预训练模型对应不同结构:Encoder 类(BERT、RoBERTa、ALBERT)擅长句子分类和实体识别;Decoder 类(GPT-2、CTRL、Transformer XL)围绕预测下一个词训练,适合生成类任务;Encoder-Decoder 类(BART、T5、mBART)则用于翻译、摘要等输入到输出的重写。
| 实际切串时,模型靠特殊符号定位边界。我们这套流程用 `< | endoftext | >` 作终止符,而不是 BERT 系的 [CLS]/[SEP],接 GPT 类模型时少一层转换。外汇与贵金属信号解读属高风险场景,任何文本生成结果都只是概率倾向,需人工复核。 |
|---|
「在 CPU 上造一个 60 根收盘价的序列池」
自己搭数据集最磨人,公开教程多教怎么用现成数据训模型,少有讲怎么按自己的想法攒数据。外汇与贵金属交易带杠杆,数据准备失误会直接放大实盘风险,这一步得自己踩通。 考虑到多数 PC 只在 CPU 上跑,序列不能太长,否则取数和处理会慢到没法调试。示例里只拉单品种 2500 个报价点做底料,纯演示用途,结果理想与否不保证;想要更好表现,得换更大的集或重做预处理,那些不在这基础示例里。 具体切法:只取报价里的 close 列,从索引 0 起每 60 个报价作为一个序列,不足 60 的丢弃。序列长度 60 是依 CPU 能力定的,原则上越长潜在效果可能越好,你可按机器改 sr_len。用 ct 控取几个品种、k 控偏移量捞序列,剩的自动停写。 账户类型是隐藏坑:示例用 symbols_get(group='*micro*') 是因微型账户,标准户要把条件删掉,否则一个品种都捞不到;把 'micro' 换成 'GBP' 就能只抓英镑系。处理完建议落盘 llm_data.csv,免得下次重连终端再拉。 下面代码封成函数就能反复调。注意 mt_data_len=2500、sr_len=60 这两个数是示例基线,你改了它们,序列池规模和单条长度会直接变。
DATA_DIR = os.path.dirname(__file__) data_file = os.path.join(DATA_DIR, "llm_data.csv") mt_data_len=class="num">2500 sr_len=class="num">60 if not mt.initialize(): print("mt initialize failed!") else: sbs=mt.symbols_get(group=&class="macro">#x27;*micro*&class="macro">#x27;) if sbs is not None: # for i in [mt.TIMEFRAME_M5,mt.TIMEFRAME_M15,mt.TIMEFRAME_H1,mt.TIMEFRAME_D1]: for i in [mt.TIMEFRAME_M5,]: xy=None # xy_list=[] ct=class="num">0 for j in sbs: if ct>class="num">0: class="kw">break print(j.name) d_=mt.copy_rates_from_pos(j.name,i,class="num">0,mt_data_len) df_d=pd.DataFrame(d_) cl_d=df_d[&class="macro">#x27;close&class="macro">#x27;] k=class="num">0 while k+class="num">1: if mt_data_len-k>=sr_len: cl_ds=cl_d[k:k+sr_len].tolist() if xy is None: xy=pd.DataFrame([cl_ds]) # xy_list=[cl_ds] else: xy.loc[len(xy)]=cl_ds # xy_list.append(cl_ds)