概述
本文介绍了如何通过MetaTrader5交易平台使用PyTorch Lightning和PyTorch Forecasting框架来实现基于神经网络的金融时间序列预测。
在本文中,我们还将解释选择这两个框架的原因以及我们使用的数据格式。
初始化
首先,我们需要导入所需的库。这些库包括MetaTrader5(用于与MT5终端交互)、PyTorch Lightning(用于训练模型)以及其他一些用于数据处理和可视化的库。
import MetaTrader5 as mt5
import lightning.pytorch as pl
from lightning.pytorch.callbacks import EarlyStopping
import matplotlib.pyplot as plt
import pandas as pd
from pytorch_forecasting import Baseline, NHiTS, TimeSeriesDataSet
from pytorch_forecasting.data import NaNLabelEncoder
from pytorch_forecasting.metrics import MAE, SMAPE, MQF2DistributionLoss, QuantileLoss
from lightning.pytorch.tuner import Tuner
接下来,我们需要初始化MetaTrader5。这是通过调用mt.initialize()函数来完成的。如果你不能简单地使用它来初始化它,你需要将MT5终端的路径作为参数传递给这个函数(在示例中“D:\Project\mt\MT5\terminal64.exe”是我的个人路径位置,在实际应用中你需要将它配置到你自己的路径位置)。如果初始化成功,函数将返回True,否则返回False。
if not mt.initialize("D:\\Project\\mt\\MT5\\terminal64.exe"):
print('initialize() failed!')
else:
print(mt.version())
mt.symbols_total()函数用于获取MT5终端中可用的可交易品种的总数。我们可以用它来判断我们是否能够正确地获得数据。如果总数大于0,我们可以使用mt.copy_rates_from_pos()函数来获取指定交易品种的历史数据。在本例中,我们获得了“GOLD_micro”品种的“mt_data_len”M15(15分钟)周期数据的最新长度。
sb=mt.symbols_total()
rts=None
if sb > 0:
rts=mt.copy_rates_from_pos("GOLD_micro",mt.TIMEFRAME_M15,0,mt_data_len)
mt.shutdown()
最后,我们使用mt.shutdown()函数关闭与MT5终端的连接,并将获得的数据转换为Pandas DataFrame格式。
mt.shutdown()
rts_fm=pd.DataFrame(rts)
现在,让我们讨论如何对从MT5终端获得的数据进行预处理。
我们需要先将时间戳转换为日期:
rts_fm['time']=pd.to_datetime(rts_fm['time'], unit='s')
在这里,我们不再描述如何标记数据。你可以在我之前的两篇文章中找到方法(本文简介中有文章链接)。为了简明地演示如何使用预测模型,我们只需将每个“max_encoder_length+2max_prediction_length”数据片段划分为一组。每组有一个从0到“max_encoder_length+2max_prediction_length-1”的序列,并填充它们。通过这种方式,我们将所需的标签添加到原始数据中。首先,我们需要将原始时间索引(即DataFrame的索引)转换为时间索引。计算原始时间索引的余数除以(max_encoder_length+2max_prediction_length),并将结果用作新的时间索引。这将时间索引映射到从0到“max_encoder_length+2*max_prediction_length-1”的范围内:
rts_fm['time_idx']= rts_fm.index%(max_encoder_length+2*max_prediction_length)
我们还需要将原始时间索引转换为一个组。计算原始时间指数除以“max_encoder_length+2*max_prediction_length”,并将结果用作新组:
rts_fm['series']=rts_fm.index//(max_encoder_length+2*max_prediction_length)
我们将数据预处理部分封装到一个函数中。我们只需要将我们需要获得的数据长度传递给它,它就可以完成数据预处理工作:
def get_data(mt_data_len:int):
if not mt.initialize("D:\\Project\\mt\\MT5\\terminal64.exe"):
print('initialize() failed!')
else:
print(mt.version())
sb=mt.symbols_total()
rts=None
if sb > 0:
rts=mt.copy_rates_from_pos("GOLD_micro",mt.TIMEFRAME_M15,0,mt_data_len)
mt.shutdown()
# print(len(rts))
rts_fm=pd.DataFrame(rts)
rts_fm['time']=pd.to_datetime(rts_fm['time'], unit='s')
rts_fm['time_idx']= rts_fm.index%(max_encoder_length+2*max_prediction_length)
rts_fm['series']=rts_fm.index//(max_encoder_length+2*max_prediction_length)
return rts_fm
重写 pytorch_forecasting.TimeSeriesDataSet 类
重写pytorch_foredicting中的to_datalader()函数。这允许您控制是否对数据进行混洗以及是否丢弃最后一组批处理(主要是为了防止最后一组数据长度不足导致的不可预测错误)。以下是您的操作方法:
class New_TmSrDt(TimeSeriesDataSet):
def to_dataloader(self, train: bool = True,
batch_size: int = 64,
batch_sampler: Sampler | str = None,
shuffle:bool=False,
drop_last:bool=False,
**kwargs) -> DataLoader:
default_kwargs = dict(
shuffle=shuffle,
drop_last=drop_last, #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"]
return DataLoader(self,**kwargs)
此代码创建从TimeSeriesDataSet继承的新类new_TmSrDt。to_datalader()函数随后在这个新类中被重写,以包括shuffle和drop_last参数。这样,您就可以更好地控制数据加载过程。请记住在代码中使用New_TmSrDt替换TimeSeriesDataSet的实例。