English Русский Español Deutsch 日本語 Português
preview
您应该知道的 MQL5 向导技术(第 04 部分):线性判别分析

您应该知道的 MQL5 向导技术(第 04 部分):线性判别分析

MetaTrader 5交易系统 | 28 二月 2023, 09:47
767 0
Stephen Njuki
Stephen Njuki

线性判别分析(LDA)分类问题中非常常见的降维技术就像上一篇文章中的 kohonen 映射一样如果您有高维数据(即具有大量属性或变量),您希望从中对观测值进行分类,LDA 将帮助您转换数据,从而令分类尽可能有所区别。 更严格地说,LDA 会将数据线性投影到低维子空间当中,从而优化某些类的分离度量。 此子空间的维度永远不会超过类的数量。在本文中,我们将研究如何把 LDA 当作信号,尾随指标和资金管理工具。 但首先,我们来看一个大胆的定义,然后按我们的方式应用它。 

LDA 非常像 PCA、QDA 和方差分析技术;而且事实上它们通常都是缩写,并不是很有帮助。 本文不会介绍或解释这些不同的技术,而是简单地明确它们的差异。

1) 主成分分析 (PCA):

LDA 与 PCA 非常相似:事实上,有些人问 PCA 之后再执行 LDA 正则化(避免曲线拟合)是否有意义。 这是一个冗长的话题,也许应该改天写一篇文章。

但对于本文,二维降维方法之间的关键区别在于 PCA 试图找到整个数据集具有最大方差的轴,假设数据越分散,可分离性越大,而 LDA 试图依据分类找到将数据实际分开的轴。

lda

所以从上图中不难看出,PCA 会给我们 LD2,而 LDA 会给我们 LD1。 这导致 PCA 和 LDA 之间的主要差异(因此 LDA 偏好)非常明显:仅仅因为特征具有高方差(离散),并不意味着它在预测类时就会更有用。

2) 二次判别分析 (QDA):

QDA 是 LDA 作为分类器的推广。 LDA 假设类条件分布是具有相同协方差矩阵的高斯分布,如果我们打算用它为我们做任何分类。

QDA 没有做出这种同方差假设,并试图估算所有类的协方差。 而这似乎是一种更健壮的算法(给定更少的假设),但它也意味着大量的参数需要估算。 众所周知,参数的数量随着类的数量呈二次增长! 故此,除非可以保证协方差估值可靠,否则您可能不希望使用 QDA。

在所有这些之后,LDA、QDA之间的关系可能会有一些混淆,例如哪个更适合降维,哪个更适合分类等。 这个交叉验证的帖子及其链接的所有内容,都可以提供帮助。

3) 方差分析 (ANOVA):

LDA方差分析(ANOVA)似乎有相似的意图:两者都尝试将观察到的变量分解为若干个自变量/因变量。 然而,根据维基百科,方差分析使用的手段是 LDA 所用的镜像版本:

“LDA 与方差分析(ANOVA)和回归分析密切相关,也试图将一个因变量表示为其它特征或测量值的线性组合。 然而,方差分析使用类别自变量连续因变量,而判别分析具有连续自变量和分类因变量(即类标签)。"

 

LDA 的典型定义如下。

设:

  • n  是类的数量
  • μ  是所有观测值的平均值
  • N i  是第 i 类中的观测值数量
  • μ i  是第 i 类的中值
  • Σ i  是第 i 类的散点矩阵


现在,定义  SW 类内散点矩阵,由下式给出

SW = ∑ i = 1 n Σ i


并定义  SB 类间散点矩阵,由下式给出

SB = ∑ i = 1 n N i ( μ i − μ ) ( μ i − μ ) T


对角化  SW − 1 SB 以获得其特征值和特征向量。

择选 k最大的特征值,及其关联的特征向量。 我们将把观察结果投射到这些向量跨越的子空间上。

具体来说,这意味着我们形成矩阵 A ,其列是上面选择k 特征向量。alglib 函数库中的 CLDA 类正是做这个的,并基于向量的特征值,按降序对向量进行排序,这意味着我们只需要选择最佳预测向量即可进行预测。

与之前的文章一样,我们将利用 MQL 代码库为我们的智能系统实现 LDA。 具体来说,我们将依赖 “dataanalysis.mqh” 文件中的 “CLDA” 类。

我们将在 2022 年日线时间帧内探索外汇对 USDJPY 的 LDA。 我们的智能系统在输入数据选择方面,很大程度上取决于用户。 在我们的例子中,对于这个 LDA,输入数据有一个变量和类分量。 我们需要在运行测试之前为其准备这些数据。 鉴于我们应对的是收盘价,因此默认情况下它将“延续”(在其无修改状态下)。 我们将对数据的变量和类分量应用规范化和离散化。 规范化意味着所有数据都在设定的最小值和最大值之间,而离散化意味着数据被转换为布尔值(真或假)。以下是我们将为信号准备的 5 组数据:

  1. 跟踪收盘价变化的离散变量数据,以便匹配类的类别。
  2. 无修改收盘价的 归一化变量数据,变化范围处于 -1.0 到 +1.0 内。
  3. 无修改收盘价变化中的连续变量数据。 
  4. 无修改收盘价。

归一化将提供最后 2 根柱线收盘价变化范围的比例(从 -1.0 到 +1.0),而离散化将说明价格是上涨(指数为 2),还是保持在中性范围内(意味着指数为 1),或下跌(意味着指数为 0)。 我们将测试所有数据类型,来检查性能。 此准备工作通过如下所示的 “Data” 方法完成。 所有 4 种数据类型都依据 “m_signal_regulizer” 输入进行正则化,为我们的数据定义一个中性区域,从而减少白噪声。

//+------------------------------------------------------------------+
//| Data Set method                                                  |
//| INPUT PARAMETERS                                                 |
//|     Index   -   int, read index within price buffer.             |
//|                                                                  |
//|     Variables                                                    |
//|             -   whether data component is variables or .         |
//|                  classifier.                                     |
//| OUTPUT                                                           |
//|     double  -   Data depending on data set type                  |
//|                                                                  |
//| DATA SET TYPES                                                   |
//| 1. Discretized variables. - 0                                    |
//|                                                                  |
//| 2. Normalized variables. - 1                                     |
//|                                                                  |
//| 3. Continuized variables. - 2                                    |
//|                                                                  |
//| 4. Raw data variables. - 3                                       |
//|                                                                  |
//+------------------------------------------------------------------+
double CSignalDA::Data(int Index,bool Variables=true)
   {
      m_close.Refresh(-1);
         
      m_low.Refresh(-1);
      m_high.Refresh(-1);
            
      if(Variables)
      {
         if(m_signal_type==0)
         {
            return(fabs(Close(StartIndex()+Index)-Close(StartIndex()+Index+1))<m_signal_regulizer*Range(Index)?1.0:((Close(StartIndex()+Index)>Close(StartIndex()+Index+1))?2.0:0.0));
         }
         else if(m_signal_type==1)
         {
            if(fabs(Close(StartIndex()+Index)-Close(StartIndex()+Index+1))<m_signal_regulizer*Range(Index))
            {
               return(0.0);
            }
            return((Close(StartIndex()+Index)-Close(StartIndex()+Index+1))/fmax(m_symbol.Point(),fmax(High(StartIndex()+Index),High(StartIndex()+Index+1))-fmin(Low(StartIndex()+Index),Low(StartIndex()+Index+1))));
         }
         else if(m_signal_type==2)
         {
            if(fabs(Close(StartIndex()+Index)-Close(StartIndex()+Index+1))<m_signal_regulizer*Range(Index))
            {
               return(0.0);
            }
            return(Close(StartIndex()+Index)-Close(StartIndex()+Index+1));
         }
         else if(m_signal_type==3)
         {
            if(fabs(Close(StartIndex()+Index)-Close(StartIndex()+Index+1))<m_signal_regulizer*Range(Index))
            {
               return(Close(StartIndex()+Index+1));
            }
            return(Close(StartIndex()+Index));
         }
      }
      
      return(fabs(Close(StartIndex()+Index)-Close(StartIndex()+Index+1))<m_signal_regulizer*Range(Index)?1.0:((Close(StartIndex()+Index)>Close(StartIndex()+Index+1))?2.0:0.0));
   }


我们欲用四个维度,这意味着每个指标值将提供 4 个变量。 故此,出于简洁起见,我们将在训练中查看每个数据集的最后四个指标值。 我们的分类也只是基本的,只包含每个数据集的类分量中的两个类(最小值)。 我们还需要在训练样本中设置数据点的数量。 此值存储在输入参数 “m_signal_points” 当中。

LDA 的输出通常是系数矩阵。 这些系数在向量中排序,其中任何一个向量点积与当前指标数据点都应产生一个值,然后将该值与训练数据集的乘积产生的类似值进行比较,以便对这个新的/当前数据点进行分类。 如此,为简单起见,如果我们的训练集只有 2 个数据点,LDA 投影为 0 和 1,并且我们的新值产生的点积为 0.9,我们就可得出结论,它与 LDA 投影为 1 的数据点属于同一类别,因为它更接近它。 另一方面,如果它产生的值为 0.1,那么我们认为这个新数据点必须与 LDA 投影为 0 的数据点属于同一类别。

训练数据集很少只有两个数据点,因此,在实践中,我们取每个类的“质心”和新数据点的点积输出,与 LDA 的输出向量进行比较。 这个“质心”应是每个类的 LDA 投影中值。

为了将每个数据点归类为看涨或看跌,我们简单地查看指标值之后的收盘价变化。 如果为正值,则数据点看涨;如果数据点为负值,则看跌。 请注意,它也可能是横盘。 为简单起见,我们把横盘或无价格变化也当作看涨。

“ExpertSignal” 类通常依赖于规范化的整数值(0-100),来为做多和做空决策加权。 由于 LDA 投影必然是双精度数据类型,我们如下图所示将它们标准化,从而落在 -1.0 到 +1.0 的范围内(看跌为负,看涨为正)。 

         // best eigen vector is the first 
         for(int v=0;v<__S_VARS;v++){ _unknown_centroid+= (_w[0][v]*_z[0][v]); }
         
         //

         
         if(fabs(_centroids[__S_BULLISH]-_unknown_centroid)<fabs(_centroids[__S_BEARISH]-_unknown_centroid) && fabs(_centroids[__S_BULLISH]-_unknown_centroid)<fabs(_centroids[__S_WHIPSAW]-_unknown_centroid))
         {
            _da=(1.0-(fabs(_centroids[__S_BULLISH]-_unknown_centroid)/(fabs(_centroids[__S_BULLISH]-_unknown_centroid)+fabs(_centroids[__S_WHIPSAW]-_unknown_centroid)+fabs(_centroids[__S_BEARISH]-_unknown_centroid))));
         }
         else if(fabs(_centroids[__S_BEARISH]-_unknown_centroid)<fabs(_centroids[__S_BULLISH]-_unknown_centroid) && fabs(_centroids[__S_BEARISH]-_unknown_centroid)<fabs(_centroids[__S_WHIPSAW]-_unknown_centroid))
         {
            _da=-1.0*(1.0-(fabs(_centroids[__S_BEARISH]-_unknown_centroid)/(fabs(_centroids[__S_BULLISH]-_unknown_centroid)+fabs(_centroids[__S_WHIPSAW]-_unknown_centroid)+fabs(_centroids[__S_BEARISH]-_unknown_centroid))));
         }

然后,该值很容易规范化为信号类预期的典型整数(0-100)。

   if(_da>0.0)
     {
      result=int(round(100.0*_da));
     }

 对于检查做多函数,以及,

   if(_da<0.0)
     {
      result=int(round(-100.0*_da));
     }

对于检查做空函数,

好了,每种输入数据类型的测试运行都会给出以下策略测试器报告。

数据集 1 报告

sr1

cs1

 

数据集 2 报告

sr2

cs2

 

数据集 3 报告

sr3

cs3

 

数据集 4 报告

sr4

cs4

  

这些报告展示了 LDA 作为交易者工具的潜力。

“ExpertTrailing” 类为持仓调整或设置止损。 这里的关键输出是新止损的双倍。 因此,取决于持仓,我们将考虑最高价和最低价作为我们的主要数据集。 根据持仓的类型进行选择,针对最高价和最低价准备如下: 

  1. 离散变量数据跟踪(最高价或最低价)变化,来匹配类的类别。
  2. 无修改(最高或最低)价格的 归一化变量数据变化范围 -1.0 到 +1.0。 
  3. 无修改(最高或最低)价格变化中的连续变量数据。 
  4. 无修改(最高或最低)价格。

LDA 的输出将与信号类一样归一化为双精度。 由于这对定义止损没有帮助,因此将根据持仓类型进行调整,如下所示,从而得出止损价位。

      int _index   =StartIndex();
      double _min_l=Low(_index),_max_l=Low(_index),_min_h=High(_index),_max_h=High(_index);
      
      for(int d=_index;d<m_trailing_points+_index;d++)
      {
         _min_l=fmin(_min_l,Low(d));
         _max_l=fmax(_max_l,Low(d));
         _min_h=fmin(_min_h,High(d));
         _max_h=fmax(_max_h,High(d));
      }
      
      if(Type==POSITION_TYPE_BUY)
      {
         _da*=(_max_l-_min_l);
         _da+=_min_l;
      }
      else if(Type==POSITION_TYPE_SELL)
      {
         _da*=(_max_h-_min_h);
         _da+=_max_h;
      }

此处还有我们如何调整和设置新的止损价位。 对于多头持仓:

   m_long_sl=ProcessDA(StartIndex(),POSITION_TYPE_BUY);

   double level =NormalizeDouble(m_symbol.Bid()-m_symbol.StopsLevel()*m_symbol.Point(),m_symbol.Digits());
   double new_sl=NormalizeDouble(m_long_sl,m_symbol.Digits());
   double pos_sl=position.StopLoss();
   double base  =(pos_sl==0.0) ? position.PriceOpen() : pos_sl;
//---
   sl=EMPTY_VALUE;
   tp=EMPTY_VALUE;
   if(new_sl>base && new_sl<level)
      sl=new_sl;

我们在这里所做的就是判定多头持仓('m_long_sl')的可能最低价点位,直到下一根柱线,然后如果它超过持仓的开立价或其当前止损位,同时低于出价减去停止级别,则将其设置为我们的新止损价位。 计算此值所用的数据类型是最低价。

设置空头持仓的止损价位是其镜像版本。

因此,针对每种输入数据类型运行的测试,若信号依据 ...数据类型,策略测试器给出如下报告。

数据集 1 报告

tr1

ct1

 

数据集 2 报告

tr2



ct2

 

数据集 3 报告

tr3

ct3

 

数据集 4 报告。

tr4

ct4

 

这些报告大概指向,给出 6.82 恢复因子的持续无修改 .data 集最适合。

‘ExpertMoney’ 类设置我们的持仓手数。 这可能是以往性能的函数,这就是我们构建 “OptimizedVolume” 类的原因。 然而,如果我们考虑波动性、或最高价和最低价之间的范围,LDA 配合初始规模能有所帮助。 因此,我们的主要数据集将是价格柱线范围。 我们将看看价格柱线范围是增加还是减少。 配合这个,我们进行以下数据准备: - 

  1. 离散化变量数据跟踪范围值更改,来匹配类的类别。
  2. 无修改范围的规范化变量数据更改-1.0 到 +1.0 的范围。
  3. 无修改范围变化中的连续变量数据。 
  4. 无修改范围值。

LDA 的输出将是与信号和尾随类一样的归一化双精度。 由于再次没有立即帮助,我们将进行如下所示的调整,以更好地投影新的柱线范围。

      int _index   =StartIndex();
      double _min_l=Low(_index),_max_h=High(_index);
      
      for(int d=_index;d<m_money_points+_index;d++)
      {
         _min_l=fmin(_min_l,Low(d));
         _max_h=fmax(_max_h,High(d));
      }
   
      _da*=(_max_h-_min_l);
      _da+=(_max_h-_min_l);

开仓交易量的设置由 2 个镜像函数处理,具体取决于智能系统是开多头还是空头持仓。 以下是多头持仓。

   double _da=ProcessDA(StartIndex());
   
   if(m_symbol==NULL)
      return(0.0);
   
   sl=m_symbol.Bid()-_da;
   
//--- select lot size
   double _da_1_lot_loss=(_da/m_symbol.TickSize())*m_symbol.TickValue();
   double lot=((m_percent/100.0)*m_account.FreeMargin())/_da_1_lot_loss;
   
//--- calculate margin requirements for 1 lot
   if(m_account.FreeMarginCheck(m_symbol.Name(),ORDER_TYPE_BUY,lot,m_symbol.Ask())<0.0)
     {
      printf(__FUNCSIG__" insufficient margin for sl lot! ");
      lot=m_account.MaxLotCheck(m_symbol.Name(),ORDER_TYPE_BUY,m_symbol.Ask(),m_percent);
     }
   
//--- return trading volume
   return(Optimize(lot));

此处值得注意的是,我们判定范围价格的投影变化,并从我们的出价中减去此投影(也应该减去止损级别)。 这将为我们提供一个“风险调整”止损,如果我们用百分比输入参数作为最大风险损失参数,我们可以依据百分比输入参数值作为回撤百分比,计算出一个手数,若出价低于预期,我们限定承受的回撤。

因此,对每种输入数据类型运行测试,同时使用无修改收盘价数据类型作为信号和 …以及尾随,下面给出策略测试器报告。

数据集 1 报告

mr1

cm1

 

数据集 2 报告

mr2

cm2


数据集 3 报告

mr3

cm3


数据集 4 报告

mr4


cm4


它表现出离散数据集范围值变化的对资金管理最有希望。同样值得注意的是,考虑到资金管理数据集都使用相同的信号和尾随设置,它们的结果差异巨大。

本文重点介绍了判别分析智能系统作为交易工具的潜力。 它并非穷尽无遗。 进一步分析可以采取跨越较长周期的更趋多样化的数据集进行。


本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/11687

附加的文件 |
TrailingDA.mqh (16.66 KB)
SignalDA.mqh (13.48 KB)
MoneyDA.mqh (15.19 KB)
神经网络变得轻松(第三十二部分):分布式 Q-学习 神经网络变得轻松(第三十二部分):分布式 Q-学习
我们在本系列的早期文章中领略了 Q-学习方法。 此方法均化每次操作的奖励。 2017 年出现了两篇论文,在研究奖励分配函数时展现出了极大的成功。 我们来研究运用这种技术解决我们问题的可能性。
数据科学与机器学习(第 09 部分):K-最近邻算法(KNN) 数据科学与机器学习(第 09 部分):K-最近邻算法(KNN)
这是一种惰性算法,它不是基于训练数据集学习,而是以存储数据集替代,并在给定新样本时立即采取行动。 尽管它很简单,但它能用于各种实际应用。
如何利用 MQL5 处理指示线 如何利用 MQL5 处理指示线
在本文中,您将发现利用 MQL5 处理最重要的指示线(如趋势线、支撑线和阻力线)的方法。
DoEasy. 控件 (第 25 部分): Tooltip WinForms 对象 DoEasy. 控件 (第 25 部分): Tooltip WinForms 对象
在本文中,我将开始开发 Tooltip(工具提示)控件,以及函数库的新图形基元。 自然而然地,并非每个元素都有工具提示,但每个图形对象都有设置它的能力。