概述
到目前为止,该系列已经展示了如何在 MQL5 代码中表示和使用范畴论中的一些基本概念,从而帮助交易者开发更强壮的交易系统。范畴论的主题有很多层面,但按理说两个关键方面可能是函子和性质变换。故此,通过再次讨论函子(就像我们之前的两篇文章一样),我们强调了该主题的顶端思路之一。
来自财经日历数据的范畴论函子
通过运用图论达成财经日历数据重新格式化,并作为一个范畴,适合得出日历数据复杂的相互关联性。从 MetaTrader 5 终端的日历选项卡中可以看出,有大量不同类型的财经数据。这个问题在之前的文章中已经强调过,需要将这些数据配对,鉴于货币对的特殊性,针对货币对制定交易决策会很困难。对于预期的证券,尽管在上一篇文章和本文中,没有必要将数据配对,但仍然应当考虑其中一些数据依赖于其它财经数据的概念,特别是考虑到我们的证券普适性,标普 500 指数。故此,为了定位这个问题,我们在上一篇文章中提出了一个简单的假设,即 CPI 数据依赖于 PMI 数据,PMI 又依赖于最新的 10 年期竞卖收益率,竞卖收益率又受到零售销售的影响。因此,时间序列并非只有其中一个经济数据点,而是针对标准普尔 500 指数波动,我们得到由多个点形成的系列基础。
不过,在这篇文章中,我们对标普 500 指数更感兴趣,不仅仅是因为它的波动性,就像上一篇文章的情况,还有它的趋势。我们希望对其短期(月度)趋势进行预测,并在我们的智能系统中使用这些预测开仓。这意味着我们与智能信号类打交道,而不是智能尾随类,就像该系列到目前为止的情况一样。那么,依据财经日历数据图形实现基于函子的变换将导致标普 500 指数的预测变化。这种实现将在多层感知器的帮助下达成。
标准普尔 500 指数值的范畴论函子
在信号文件内,将标普 500 指数值表述为图形相当于一个范畴,因为正如上一篇文章所分享的,每个图形顶点(数据点)等价于一个对象,因此顶点之间的箭头可以被视为态射。一个对象可以有一个元素,但在这种情况下,数据点不仅包含我们感兴趣的数值,还有我们的范畴未考虑的额外数据包括:财经数据发布日期、该数据发布前的预测共识、以及 MetaTrader 终端的日历选项卡中列出的其它数据。此链接指向一个包含日历事件类型的页面,列举的每个属性都适用于我们的对象。然后,所有这些数据将在财经日历范畴中形成一个对象,或我们所说的集合。
若要利用函子来分析和预处理历史财经日历数据,不幸的是,这在策略测试器中只能通过第三方完成,而不能直接从 MetaQuotes 的服务器完成。这当然是一个瓶颈问题,我们已经通过脚本将数据导出到 csv,然后像上一篇文章一样在策略测试器中读取该 csv。于此的区别在于,我们在执行此操作时,针对的是智能信号类实例,而非尾随类。由于我们正在与两个函子打交道,因此所用脚本在两个文件写入,其一前缀为 “true”,意即函子交叉对象,另一个前缀为 “false”,意即交叉态射。写入文件附在文章末尾。
上面的示意图中共享了转换后的标普 500 指数值的基于图形的表示。
基于函子的神经网络架构
在本文中,函子作为多层感知器(神经网络)比我们之前跨范畴、甚至范畴内对象(因为两个元素之间的态射关系可以用相同的方式定义)映射时所用的线性或二次关系更进一步。如前强调的,函子的使用不仅意味着两个范畴中的对象交叉映射,还意味着它们各自交叉态射。因此,彼此能够互查,即如果您知道协域范畴中的对象,则隐含态射,反之亦然。这意味着我们将与范畴之间的两个感知器打交道。
本文也不会提供关于感知器的入门知识,因为已有太多文章,不仅有发表在本网站上的文章,也有普遍的在线文章,故邀请好奇的读者自行去背景研究,这将有助于澄清这里讲述的内容。此处实现的网络架构很大程度上要归功于 Alglib,它可从 MetaTrader 的 IDE 中的 “Include\Math” 文件夹下访问。这是如何使用函数库执行感知器初始化:
//+------------------------------------------------------------------+
//| Function to train Perceptron. |
//+------------------------------------------------------------------+
bool CSignalCT::Train(CMultilayerPerceptron &MLP)
{
CMLPBase _base;
CMLPTrain _train;
if(!ReadPerceptron(m_training_profit))
{
_base.MLPCreate1(__INPUTS,m_hidden,__OUTPUTS,MLP);
m_training_profit=0.0;
}
else
{
printf(__FUNCSIG__+" read perceptron, with profit: "+DoubleToString(m_training_profit));
}
...
return(false);
}
该函数库中所用的感知器非常基本,它们由三层组成。输入层、隐藏层和输出层。我们的财经数据范畴一次有四个数据点(基于我们的假设),故隐藏层中的输入数量将为四个。隐藏层上的数据点数量是为数不多的可优化参数之一,但我们的默认值是 7。最后,输出层中有一个输出,即标普 500 指数的预测变化。权重、背离和激活函数的知识是理解感知器前馈工作的关键。再次邀请读者在必要时自行研究这些知识点。
训练基于函子的神经网络
历史财经日历数据的训练过程将采用 Levenberg-Marquardt 算法完成。与前馈和反向验算一样,该编码由 AlgLib 函数处理。我们将据函数库实现以下训练:
int _info=0;
CMatrixDouble _xy;
CMLPReport _report;
TrainingLoad(m_training_stop,_xy,m_training_points,m_testing_points);
//
if(m_training_points>0)
{
_train.MLPTrainLM(MLP,_xy,m_training_points,m_decay,m_restarts,_info,_report);
if(_info>0){ return(true); }
}
此处的关键部分是从共用目录中 csv 文件中读取输入数据,并填充 XY 矩阵。每当生成新柱线(或计时器)时,矩阵都会获取每个数据行上定义的四个数据点作为历史数据,并用它来训练网络,从而生成其权重和背离。由 “TrainingLoad” 函数处理 XY 输入矩阵的填充,如下所示:
//+------------------------------------------------------------------+
//| Function Get Training Points and Initialize Training Matrix. |
//+------------------------------------------------------------------+
void CSignalCT::TrainingLoad(datetime Date,CMatrixDouble &XY,int &TrainingPoints,int &TestingPoints)
{
TrainingPoints=0;
TestingPoints=0;
ResetLastError();
string _file="_s_"+m_currency+"_"+m_symbol.Name()+"_"+EnumToString(m_period)+"_"+string(m_objects)+".csv";
int _handle=FileOpen(_file,FILE_SHARE_READ|FILE_ANSI|FILE_COMMON,"\n",CP_ACP);
if(_handle!=INVALID_HANDLE)
{
string _line="";
int _line_length=0;
while(!FileIsLineEnding(_handle))
{
//--- find out how many characters are used for writing the line
_line_length=FileReadInteger(_handle,INT_VALUE);
//--- read the line
_line=FileReadString(_handle,_line_length);
string _values[];
ushort _separator=StringGetCharacter(",",0);
if(StringSplit(_line,_separator,_values)==6)
{
datetime _date=StringToTime(_values[0]);
_d_economic.Let(); _d_economic.Cardinality(4);
//printf(__FUNCSIG__+" initializing for: "+TimeToString(Date)+" at: "+TimeToString(_date));
if(_date<Date)
{
TrainingPoints++;
//
XY.Resize(TrainingPoints,__INPUTS+__OUTPUTS);
for(int i=0;i<__INPUTS;i++)
{
XY[TrainingPoints-1].Set(i,StringToDouble(_values[i+1]));
}
//
XY[TrainingPoints-1].Set(__INPUTS,StringToDouble(_values[__INPUTS+1]));
}
else
{
TestingPoints++;
}
}
}
FileClose(_handle);
}
else
{
printf(__FUNCSIG__+" failed to load file. Err: "+IntegerToString(GetLastError()));
}
}