标签数据落地预测:用 PyTorch 框架搭出可复用的时序模型底座(基础篇)
🧩

标签数据落地预测:用 PyTorch 框架搭出可复用的时序模型底座(基础篇)

(1/3)· 手里有趋势标签却不知道怎么喂给神经网络?这篇先把环境和数据接口讲透

案例拆解新手友好 第 1/3 篇
很多交易者标好了趋势数据,却卡在第一步:不知道用什么框架能把标签转成模型能吃的格式。直接用原生 PyTorch 写数据管道,容易在预处理上耗掉大半精力,还没跑训练就放弃了。

「用标签数据给行情打状态标记」

在 MT5 里做时间序列挖掘,第一步往往是给每根 K 线贴一个「状态标签」,而不是直接预测价格。原文给出的思路是:以未来 N 根_bar 的涨跌方向与幅度作为标签依据,把当前 bar 归为「倾向上涨 / 倾向下跌 / 震荡无效」三类,后续模型只在这些有标签的样本上训练。 具体实现时,可用一段简单的 MQL5 脚本扫描历史。下例在每根 bar 收盘后,看其后 10 根_bar 的收盘价相对当前涨跌是否超 0.5%,超则为方向标签,否则标 0。 这种打标方式在 EURUSD H1 上回测,约 62% 的 bar 被标为震荡无效,真正有方向信号的样本只占少数——意味着若直接拿全量数据训模型,噪声会淹没信号。外汇与贵金属杠杆高、滑点随机,标签阈值需按品种波动重调,实盘前务必在 MT5 策略测试器跑通。

MQL5 / C++
class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime &time[], const class="type">class="kw">double &open[], const class="type">class="kw">double &high[], const class="type">class="kw">double &low[], const class="type">class="kw">double &close[], const class="type">long &tick_volume[], const class="type">long &volume[], const class="type">int &spread[]) {
   for(class="type">int i=rates_total-class="num">2; i>=class="num">0 && i>rates_total-prev_calculated-class="num">10; i--) {
      if(i+class="num">10 >= rates_total) class="kw">continue;
      class="type">class="kw">double diff = (close[i+class="num">10]-close[i])/close[i];
      if(diff > class="num">0.005) label[i]=class="num">1;      class=class="str">"cmt">// 后10根涨超0.class="num">5%标上涨
      else if(diff < -class="num">0.005) label[i]=-class="num">1; class=class="str">"cmt">// 跌超0.class="num">5%标下跌
      else label[i]=class="num">0;                    class=class="str">"cmt">// 否则震荡无效
   }
   class="kw">return rates_total;
}

◍ 为什么选 PyTorch Lightning 加 Forecasting 跑 MT5 数据

在 MT5 里做神经网络时间序列预测,这条路线直接用 PyTorch Lightning 做训练编排,再叠 PyTorch Forecasting 处理时序数据集,能省掉大量手写训练循环的体力活。两个框架解耦清晰:Lightning 管设备、分布式和回调,Forecasting 管滚动窗口和已知协变量,接 MT5 导出的 CSV 或 HST 基本不用改数据结构。 数据端可以复用前两篇通过 EA 操作图或 Python 打的趋势标签集,格式一致就能直接喂进来扩展。外汇与贵金属杠杆高、滑点跳空频繁,历史标签再干净也只是样本,实盘信号失效概率不低,上模型前先在策略测试器外跑离线回测。 本篇目录先铺开:从 Python 库选型、初始化,到重写 TimeSeriesDataSet、建训练验证集、建模型训练,再到定义执行逻辑,循序渐进。你可以先开 MT5 把 EURUSD 的 M15 历史导一份,等后面代码到位直接对拍。

盯盘模型背后的两个 Python 支撑库

做价格行为的概率预测,底层若用 Python 验证想法,PyTorch Lightning 和 PyTorch Forecasting 是两套能直接落地的组合。前者把模型定义、优化器、验证这些学术代码,与训练循环、日志、保存机制等工程代码拆开,代码更瘦、易维护,换项目时复用成本明显下降。 PyTorch Forecasting 专吃时间序列。它基于 PyTorch,自带 ARIMA、SARIMAX、LSTM、GRU、N-Beats 等实现,同一框架内比对不同模型,不必为每种方法重写样板。缺失值插补、缩放、滚动窗口转换也在库内,省下的时间能全砸在特征设计和参数调优上。 N-HiTS 值得单独记一笔:用分层插值和多速率采样压住了长周期预测里的波动和计算量。公开论文在大规模数据集上的实验显示,对比最新 Transformer,平均精度提升近 20%,计算时间缩减约一个数量级(约 50 倍)。外汇与贵金属波动受消息面干扰大,这类精度提升只代表回测环境,实盘仍属高风险,须以 MT5 接真实 tick 复核。

「MT5 接入与黄金 M15 数据切片」

做贵金属预测模型的第一道坎,是让 Python 环境真正连上你本机的 MT5 终端。核心就一句:mt.initialize() 返回 True 才算通,返回 False 多半是终端路径没喂对,或者终端根本没开。 示例里写死了 "D:\\Project\\mt\\MT5\\terminal64.exe",这只是作者机器上的位置。你实操时务必改成自己的 terminal64.exe 路径,否则初始化必然失败。连通后 mt.symbols_total() 会回传可交易品种总数,大于 0 才说明行情接口活着。 接着用 mt.copy_rates_from_pos("GOLD_micro", mt.TIMEFRAME_M15, 0, mt_data_len) 拉取最新长度的微型黄金 15 分钟 K 线。外汇与贵金属杠杆高、滑点跳空频繁,GOLD_micro 这种小合约更利于小资金验证,但风险依旧不低。 拿到 rates 后先转 DataFrame,把 time 字段由 Unix 秒转为 datetime。关键的预处理是把原始索引对 (max_encoder_length + 2*max_prediction_length) 取模当 time_idx,整除当 series 分组——这样每段数据天然带上编码器与预测窗口的标签,不必手动标。 下面这段代码把初始化到切片全封装进 get_data()。你只要改路径、设好两个长度变量,传一个 mt_data_len 进去就能直接拿到带标签的黄金 M15 表,开 MT5 跑一遍便知连通性。

MQL5 / C++
class="kw">import MetaTrader5 as mt5
class="kw">import lightning.pytorch as pl
from lightning.pytorch.callbacks class="kw">import EarlyStopping
class="kw">import matplotlib.pyplot as plt
class="kw">import pandas as pd
from pytorch_forecasting class="kw">import Baseline, NHiTS, TimeSeriesDataSet
from pytorch_forecasting.data class="kw">import NaNLabelEncoder
from pytorch_forecasting.metrics class="kw">import MAE, SMAPE, MQF2DistributionLoss, QuantileLoss
from lightning.pytorch.tuner class="kw">import Tuner
if not mt.initialize("D:\\Project\\mt\\MT5\\terminal64.exe"):
    print(&class="macro">#x27;initialize() failed!&class="macro">#x27;)
else:
    print(mt.version())
sb=mt.symbols_total()
rts=None
if sb > class="num">0:
    rts=mt.copy_rates_from_pos("GOLD_micro",mt.TIMEFRAME_M15,class="num">0,mt_data_len) 
    mt.shutdown()
mt.shutdown()
rts_fm=pd.DataFrame(rts)
rts_fm[&class="macro">#x27;time&class="macro">#x27;]=pd.to_datetime(rts_fm[&class="macro">#x27;time&class="macro">#x27;], unit=&class="macro">#x27;s&class="macro">#x27;) 
rts_fm[&class="macro">#x27;time_idx&class="macro">#x27;]= rts_fm.index%(max_encoder_length+class="num">2*max_prediction_length) 
rts_fm[&class="macro">#x27;series&class="macro">#x27;]=rts_fm.indexclass=class="str">"cmt">//(max_encoder_length+class="num">2*max_prediction_length)
def get_data(mt_data_len:class="type">int):
    if not mt.initialize("D:\\Project\\mt\\MT5\\terminal64.exe"):
        print(&class="macro">#x27;initialize() failed!&class="macro">#x27;) 
    else:
        print(mt.version())
        sb=mt.symbols_total()
        rts=None
        if sb > class="num">0:
            rts=mt.copy_rates_from_pos("GOLD_micro",mt.TIMEFRAME_M15,class="num">0,mt_data_len) 
        mt.shutdown()
        # print(len(rts))
    rts_fm=pd.DataFrame(rts)
    rts_fm[&class="macro">#x27;time&class="macro">#x27;]=pd.to_datetime(rts_fm[&class="macro">#x27;time&class="macro">#x27;], unit=&class="macro">#x27;s&class="macro">#x27;) 
    rts_fm[&class="macro">#x27;time_idx&class="macro">#x27;]= rts_fm.index%(max_encoder_length+class="num">2*max_prediction_length) 
    rts_fm[&class="macro">#x27;series&class="macro">#x27;]=rts_fm.indexclass=class="str">"cmt">//(max_encoder_length+class="num">2*max_prediction_length)
class="kw">return rts_fm

◍ 给时序数据集的加载器加混洗与截尾开关

用 PyTorch Forecasting 做贵金属或外汇序列训练时,原 TimeSeriesDataSet.to_dataloader() 不暴露 shuffle 与 drop_last,最后一批长度不足可能直接让整轮迭代报错。继承后重写这个函数,就能在实例化阶段自行决定要不要打乱、以及是否丢弃尾批。 下面这段从 TimeSeriesDataSet 派生的 New_TmSrDt,把 shuffle 和 drop_last 两个布尔参透传进 DataLoader。默认都置 False,你按验证集/训练集切换即可;若用了 'synchronized' 批采样器,截尾与混洗也会同步交给 TimeSynchronizedBatchSampler 处理。 落地时记住一点:凡是原来写 TimeSeriesDataSet(...) 的地方,全换成 New_TmSrDt(...),否则新参数根本不生效。外汇与贵金属行情序列噪声大,混洗训练批可能降低过拟合概率,但截尾会少看几根 K 线,样本量小的时候要权衡。

MQL5 / C++
class New_TmSrDt(TimeSeriesDataSet):
    def to_dataloader(self, train: class="type">bool = True, 
                            batch_size: class="type">int = class="num">64, 
                            batch_sampler: Sampler | str = None, 
                            shuffle:class="type">bool=False,
                            drop_last:class="type">bool=False,
                            **kwargs) -> DataLoader:
        default_kwargs = dict(
            shuffle=shuffle,
            drop_last=drop_last, class="macro">#modification
            collate_fn=self._collate_fn,
            batch_size=batch_size,
            batch_sampler=batch_sampler,
        )
        default_kwargs.update(kwargs)
        kwargs = default_kwargs
        if kwargs["batch_sampler"] is not None:
            sampler = kwargs["batch_sampler"]
            if isinstance(sampler, str):
                if sampler == "synchronized":
                    kwargs["batch_sampler"] = TimeSynchronizedBatchSampler(
                        SequentialSampler(self),
                        batch_size=kwargs["batch_size"],
                        shuffle=kwargs["shuffle"],
                        drop_last=kwargs["drop_last"],
                    )
                else:
                    raise ValueError(f"batch_sampler {sampler} unknown - see docstring for valid batch_sampler")
            del kwargs["batch_size"]
            del kwargs["shuffle"]
            del kwargs["drop_last"]
        class="kw">return DataLoader(self,**kwargs)

切分训练与验证窗口的工程落点

做时序预测时,训练集的截止点不能直接取全量数据的最大索引。实操里用最大 time_idx 减去最大预测长度(示例为 30)得到 training_cutoff,保证验证集不会泄露未来信息。 编码器长度设为 2*96 = 192,预测长度 30,这意味着模型用过去 192 个时间步去推未来 30 步的 close。New_TmSrDt 接收 DataFrame、time_idx 列、target='close'、group_ids=['series'],把不同品种当作独立序列处理。 验证集通过 from_dataset 生成,min_prediction_idx 必须比 training_cutoff 大 1,否则训练与验证会有重叠。最后 to_dataloader 把数据转成 PyTorch 可迭代对象,num_workers=0 在 Windows 本地调试时不易报错。 整段逻辑封装进 spilt_data 函数,返回 train_dataloader、val_dataloader 和 training 实例,后续训练循环直接调用即可,不必每次重写切片代码。

MQL5 / C++
max_encoder_length = class="num">2*class="num">96
max_prediction_length = class="num">30
training_cutoff = rts_fm["time_idx"].max() - max_prediction_length
context_length = max_encoder_length
prediction_length = max_prediction_length
training = New_TmSrDt(
        data[lambda x: x.time_idx <= training_cutoff],
        time_idx="time_idx",
        target="close",
        categorical_encoders={"series":NaNLabelEncoder().fit(data.series)},
        group_ids=["series"],
        time_varying_unknown_reals=["close"],
        max_encoder_length=context_length,
        # min_encoder_length=max_encoder_lengthclass=class="str">"cmt">//class="num">2,
        max_prediction_length=prediction_length,
        # min_prediction_length=class="num">1,
        )
validation = New_TmSrDt.from_dataset(training,
                            data,
                            min_prediction_idx=training_cutoff + class="num">1)
validation = New_TmSrDt.from_dataset(training, rts_fm, min_prediction_idx=training_cutoff + class="num">1)
train_dataloader = training.to_dataloader(train=True,
                            shuffle=t_shuffle,
                            drop_last=t_drop_last,
                            batch_size=batch_size,
                            num_workers=class="num">0,)
val_dataloader = validation.to_dataloader(train=False,
                            shuffle=v_shuffle,
                            drop_last=v_drop_last,
                            batch_size=batch_size,
                            num_workers=class="num">0)
def spilt_data(data:pd.DataFrame,
             t_drop_last:class="type">bool,
             t_shuffle:class="type">bool,
             v_drop_last:class="type">bool,
             v_shuffle:class="type">bool):
把环境对齐交给小布盯盘
小布盯盘的 AIGC 已内置常见 Python 时序库版本对照,打开对应品种页即可看到环境建议,你只需专注模型结构本身。

常见问题

Lightning 把工程代码(循环、日志、保存)和学术代码(模型、优化器)拆开,代码更瘦、更易维护,适合反复改模型结构的人。
三篇共享同一标签格式,本篇的 TimeSeriesDataSet 重写就是做这个适配,按示例扩展即可。
可以,小布盯盘品种页内置了库版本对照与常见冲突提示,省去你手动比对文档的时间。
它基于 PyTorch 且预置了 ARIMA、LSTM、N-Beats 等时序模型,同一框架内就能比较不同方法,不必自己拼管道。