English Русский Español Português
preview
一维奇异谱分析(SSA)

一维奇异谱分析(SSA)

MetaTrader 5统计分析 |
39 2
Evgeniy Chernish
Evgeniy Chernish

引言

金融市场以高波动性和复杂动态过程为特征,这使得预测和识别模式极具挑战性。奇异谱分析(SSA)是一种强大的时间序列分析技术,可将序列的复杂结构分解为趋势、季节性(周期性)变化及噪声等简单成分。基于线性代数的SSA方法无需假设平稳性,因此成为研究时间序列结构的通用工具。 

然而,SSA文献中大量使用的向量与矩阵代数理论形成了较高的入门门槛,导致缺乏相关基础的读者难以理解该主题,更无法掌握这种分析方法的细节与优势。本文旨在以通俗易懂的方式阐述SSA的理论基础,避免该方法沦为“黑箱”,同时提供所描述概念的实现方法。 

SSA术语可理解为一整套分析方法家族,但所有的方法均基于四个步骤的顺序应用:

  • 将时间序列转换为轨迹矩阵(Hankel矩阵);
  • 将轨迹矩阵分解为多个秩为一的初等矩阵之和;
  • 对初等矩阵进行分组;
  • 重建(恢复)时间序列。

让我们来详细介绍每个阶段。

轨迹矩阵的构建

其核心思想是将时间序列转换为矩阵,以在多维空间中反映其结构。从而揭示序列连续值之间的隐藏依赖关系。轨迹矩阵的构建步骤如下:取一个长度为N的一维时间序列样本,通过构造长度为L(窗口长度)的滑动子样本,将其转换为K个向量(K=N−L+1)。由此得到的长度为L的向量(如{x1,x2,...,xL},{x2,x3,...,xL+1})构成轨迹矩阵X

轨迹矩阵

图例1.X轨迹矩阵

这里,L参数决定了分析的深度。通常将其设置为等于N/2。

将轨迹矩阵分解为秩为1的矩阵之和

在构建轨迹矩阵后,需对其进行分解。当采用奇异值分解(SVD)作为分解方法时,该分析方法称为基础奇异谱分析(Basic-SSA)。

通过奇异值分解,可构造所谓的特征三元组(eigentriples)(√λi, Ui, Vi) ,其中:

  • σi = √λi —— 奇异值,等于矩阵XX'特征值的平方根;
  • Ui —— 左奇异向量;
  • Vi —— 右奇异向量;
  • i —— 奇异值的序号,其数量等于轨迹矩阵X的秩。

奇异值σi反映各成分的权重:较大的值对应重要模式(如趋势、周期),较小的值对应噪声。

因此,通过SVD,轨迹矩阵可表示为秩为1的初等矩阵Xi之和:

分解

秩为1的矩阵是构建更复杂矩阵的“基础模块”。

下面我们结合SSA方法来阐释矩阵秩的概念。SSA的目标是从时间序列中提取确定性信号。确定性序列,如指数序列、多项式序列或正弦序列等,其特点为秩是有限的。这是因为它们满足线性递推关系(LRR),并且它们的轨迹矩阵包含的线性无关向量的数量是有限的。例如,指数序列的轨迹矩阵的秩为1(仅有一个线性无关向量),正弦序列的秩为2,k次多项式的秩为k + 1,以此类推。

秩脚本展示了确定性序列的秩这一概念:

//+------------------------------------------------------------------+
//|                                                         Rank.mq5 |
//|                                                           Eugene |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Eugene"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs

input int         N  = 100;                // N - length of generated time series
input int         L  = 30;                 // L - window length
input int         T  = 22;                 // T - period length of sine function
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   matrix X=matrix::Zeros(L,N-L+1);
   vector x_exp= vector::Zeros(N);
   vector x_sinus= vector::Zeros(N);
   vector x_polynom= vector::Zeros(N);
   for(int t=0; t <N; t++)
     {
      x_exp[t] = MathPow(1.01,t);           // 1. Exponential sequence: x_t = 1.01^t
      x_sinus[t] = MathSin(2*M_PI*t/T);     // 2. Sine wave: x_t = sin(2 * pi * t / T)
      x_polynom[t] =  1 + t+ MathPow(t,2);  // 3. Polynomial of degree 2: x_t = 1 + t + t^2
     }
   trajectory_matrix(x_exp,L,X);
   Print("Rank Exponential sequence = ",Rank_SVD(X));
   trajectory_matrix(x_sinus,L,X);
   Print("Rank Sinus sequence = ",Rank_SVD(X));
   trajectory_matrix(x_polynom,L,X);
   Print("Rank Polynom sequence = ",Rank_SVD(X));
  }
//+------------------------------------------------------------------+
//| Trajectory matrix X                                              |
//+------------------------------------------------------------------+
void trajectory_matrix(vector & series,int window_length, matrix & X)
  {
   int N_ = (int)series.Size();
   int L_ = window_length;
   int K = N_ - L_ + 1;
   X=matrix::Zeros(L_,K);
   for(int i=0; i <L_;  i++)
     {
      for(int j=0; j <K;  j++)
        {
         X[i,j] = series[i+j];
        }
     }
  }
//+------------------------------------------------------------------+
//|Finds the rank of a matrix using SVD                              |
//+------------------------------------------------------------------+
int Rank_SVD(matrix & X)
  {
   vector sv;
   matrix U,V;
   double tol = 1e-8;  // Threshold for non-zero values
   X.SingularValueDecompositionDC(SVDZ_N,sv,U,V);
   double threshold = tol * sv.Max();
   int rank=0;
   for(int i=0; i<(int)sv.Size(); i++)
     {
      if(sv[i] > threshold)
         rank++;
     }
   return rank;
  }
//+------------------------------------------------------------------+

像股价这样的实际时间序列,由于存在噪声,并非有限秩序列,而是满秩序列,其秩等于min(L, K)(其中L和K是轨迹矩阵相关的参数)。然而,如果该序列是一个有限秩的确定性信号与噪声之和,那么SSA方法能够近似提取出这个信号。接下来,仅针对确定性成分进行预测,噪声部分则被舍弃。为实现这一目的,轨迹矩阵(X)会被分解为秩为1的基矩阵,然后再由这些基矩阵组合成与有用的确定性信号相对应的更复杂的矩阵。这就是SSA方法的核心思想。

分组

在分组步骤中,将秩为1的基矩阵组合成不同的组,这些组被解读为时间序列的不同成分(如趋势、季节性、噪声)。其中最常见的一种方法是根据轨迹X矩阵的奇异值相近程度来对矩阵进行分组。在确定了m个互不相交的组I后,轨迹矩阵X的分解可表示为:

分组

例如:

  • Itrend —— 代表趋势成分,
  • Iseasonal = {2,3} —— 代表季节性成分,
  • Inoise = {4....,i} —— 代表噪声成分,
    其中,i = 奇异值的数量。

左奇异向量的图形化表示有助于直观判断确定性信号是否存在。例如,如果时间序列中包含趋势成分,对应的奇异向量将呈现出平滑变化的轨迹。如果存在周期性成分,成对的奇异向量(因为周期性信号的秩为2)将类似于正弦波。与高斯白噪声视觉上相似且与较小奇异值相关联的奇异向量,则对应噪声成分。这些特性使得我们可以根据奇异向量的视觉分析对成分进行分组。

时间序列的复原(重建)

下一步是使用对角平均法将每个分组后的矩阵转换为长度为N的新时间序列:


其中,xj和k是分组(或基)矩阵中的元素。

以这种方式重建的时间序列将分别代表趋势成分或周期性成分。这些序列之和构成原始时间序列的非参数模型,该模型取决于窗口长度L以及基矩阵的分组方式。在这种情况下,所有重建序列(包括噪声)之和将完全复原原始时间序列。

预测

在SSA方法中,对gi时间序列未来M步的预测是基于重建后的序列,利用线性递推方程进行的:

预测

其中:

  • aj —— 线性递推关系(LRR)的比率,
  • fi —— 重建序列的值。

比率向量aj由奇异向量Ui决定:

系数向量

其中:

  • First(前L-1个分量)—— 奇异向量Ui的前L−1个坐标,
  • Last(最后一个分量)—— 奇异向量Ui的最后一个坐标,
  • d —— 代表有用信号所选的奇异向量的数量。

托普利兹奇异谱分析(Toeplitz-SSA)

还有一种不同于传统方法的奇异谱分析变体,即托普利兹奇异谱分析。与基于时间序列滑动窗口构建轨迹矩阵的基本奇异谱分析不同,托普利兹奇异谱分析构建一个具有托普利兹结构的自协方差矩阵(因此得名)。之后,对该自协方差矩阵进行SVD。分组方法、对角平均、寻找线性递推关系比率以及预测等步骤,与基本奇异谱分析中的操作完全相同。

托普利兹奇异谱分析更适合分析平稳时间序列(与基本方法相比,其预测误差更小)。然而,对于非平稳序列,经典奇异谱分析效果更好。由于我们在股票市场中要处理的是非平稳过程,因此在本篇文章中,我决定仅介绍基本算法。



基本SSA分析示例

接下来,我们将从理论转向使用MQL5语言对上述概念的实现。为此,我们准备了一个脚本,该脚本生成四个合成时间序列:

  • 正弦波 + 高斯白噪声,
  • 线性趋势 + 正弦波 + 高斯白噪声,
  • 对称高斯随机游走、高斯白噪声。


这些序列具有以下特征:

  • 第一个序列是平稳的,具有周期性成分,
  • 第二个序列是非平稳的,具有确定性趋势和周期性成分,
  • 第三个序列是具有随机趋势的非平稳序列,
  • 第四个序列是平稳白噪声。

这些模型在一定程度上涵盖了实际中遇到的时间序列类型。

该脚本生成一个您选择合成的序列,并依次实现我们上面讨论的步骤,同时在屏幕上显示以下图表:

  • 生成的数据,
  • 相对奇异值(每个奇异三元组方差的占比),
  • 前两个奇异向量,
  • 前两个奇异向量的散点图,
  • 数据序列 + 其重建序列 + 预测,
  • 使用MQL5的SingularSpectrumAnalysisForecast函数进行重建和预测。

相对奇异谱值图(奇异值平方与所有奇异值平方和的比值,即σi^2/∑ σj^2 ——图例2)使我们能够确定时间序列中存在的成分类型,并选择用于信号重建的奇异三元组数量。通常,在急剧下降的点(图中的所谓“拐点”)之前选择成分。

对于周期性成分,应该有两个大小相近的奇异值。也可能存在一个平台期,随后是下降。这就意味着存在多个谐波成分(不同频率的正弦波)和噪声。

谱值平滑下降表明不存在确定性信号。

SVD(正弦波+噪声)

图例2. 相对频谱,正弦波+噪声

在此值得一提的是SSA的缺点,即无法区分随机趋势和确定性趋势。例如,对于随机游走序列,我们只会看到一个明显的成分,该成分负责趋势。但我们都知道,这种趋势是随机的、不可预测的。为了专门测试这种情况,我纳入了随机游走数据。

在确定了几个最大的奇异值后,查看对应左奇异向量的图形(图例3)会很有帮助。对于与噪声混合的周期性信号,与最大奇异值相关联的前两个奇异向量通常具有接近正弦或余弦波的形状,这反映了信号的周期性。

第一和第二奇异向量

图例3. 前两个奇异向量,正弦波+噪声

此外,可以为一对奇异向量构建散点图,其中第一个奇异向量(U1)绘制在X轴上,第二个奇异向量(U2)绘制在Y轴上(图例4)。

奇异向量散点图

图例4. 前两个奇异向量的散点图,正弦波+噪声

如果数据包含周期性成分,散点图通常会呈现椭圆形或圆形,这证实了信号的正弦特性。这是因为周期性成分的一对奇异向量对应于两个正交谐波(例如正弦和余弦)。如果散点图中的点形成没有清晰几何结构的混沌云,则表明不存在明显的周期性或确定性成分。

图例5展示了一个合成的正弦波+噪声序列、其重建序列以及100步预测。由于强噪声的干扰,很难从原始数据中直观地检测出周期性信号,但SSA有效地提取了周期性成分。当然,这是一个非常简单的例子,在真实的金融数据中很少出现如此清晰的图像。然而,SSA为确认或否定价格中存在周期性假设提供了绝佳的机会。

正弦波+噪声预测

图例5. 正弦波+噪声序列预测


MQL5中的SSA实现

让我们来探讨一下MQL5中现有的SSA实现。终端自带了矩阵与向量方法\OpenBLAS部分中的SingularSpectrumAnalysisForecast函数。从该函数的描述中,我并不完全清楚它实现的是哪种SSA。

起初,我根据自协方差矩阵的分解,假设这是基于托普利兹(Toeplitz)矩阵的SSA变体。但鉴于使用基础SSA脚本和SingularSpectrumAnalysisForecast函数对序列进行预测和重建的结果完全一致,因此这很可能仍是基础算法的实现。作为示例,我将提供趋势+正弦波+噪声序列三个主要成分的预测图(图例6)。分析的序列包含200个值,我们进行100步预测。

SingularSpectrumAnalysisForecast与基础SSA对比

图例6. MQL5与基础SSA的序列重建与预测对比

为了分解轨迹矩阵,我使用了同一OpenBLAS部分中的SingularValueDecompositionDC函数,因为开发者认为“分治”算法是所有SVD算法中最快的。该函数能方便地计算奇异向量的完整矩阵和截断矩阵,这一点非常实用。

基础SSA脚本代码:

//+------------------------------------------------------------------+
//|                                                    Basic-SSA.mq5 |
//|                                                           Eugene |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Eugene"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
#include <Math\Stat\Stat.mqh>
#include <Graphics\Graphic.mqh>

enum SimpleData
  {
   SinusPlusNoise,
   Trend_Sinus_Noise,
   RandomWalk,
   WhiteNoise,
  };

input int         L  = 30;                 // L - window length
input int         N  = 200;                // N - length of generated time series
input int         T  = 22;                 // T - period length of sine function
input int         fs = 100;                // fs - forecast horizon
input int         r_ = 2;                  // r - singular components
input SimpleData  sd = SinusPlusNoise;     // Data
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   int err;
   vector x = vector::Zeros(N); // time series
   double x_array[];
   double original_series[];
//------------1. Data --------------------
//------------------ sinus + noise ---------
   if(sd == SinusPlusNoise)
     {
      for(int i=0; i <N;  i++)
        {
         x[i] = MathSin(2*M_PI*(i+1)/T) + MathRandomNormal(0,1,err);
        }
      VectortoArray(x,x_array);
      ArrayCopy(original_series,x_array,0,0,WHOLE_ARRAY);
      PlotGraphic(x_array,5,1);
     }
//------------------trend + sinus + noise ----
   if(sd == Trend_Sinus_Noise)
     {
      for(int i=0; i <N;  i++)
        {
         x[i] =  0.05 * i + MathSin(2*M_PI*(i+1)/T) + MathRandomNormal(0,1,err);
        }
      VectortoArray(x,x_array);
      ArrayCopy(original_series,x_array,0,0,WHOLE_ARRAY);
      PlotGraphic(x_array,5,1);
     }
//-------------- random walk -----------------
   if(sd == RandomWalk)
     {
      x[0]=100;
      for(int i=1; i <N;  i++)
        {
         x[i] = x[i-1] + MathRandomNormal(0,1,err);
        }
      VectortoArray(x,x_array);
      ArrayCopy(original_series,x_array,0,0,WHOLE_ARRAY);
      PlotGraphic(x_array,5,1);
     }
//-------------- white noise -----------------
   if(sd == WhiteNoise)
     {
      for(int i=0; i <N;  i++)
        {
         x[i] = MathRandomNormal(0,1,err);
        }
      VectortoArray(x,x_array);
      ArrayCopy(original_series,x_array,0,0,WHOLE_ARRAY);
      PlotGraphic(x_array,5,1); // white noise graph
     }
//------------2. Trajectory matrix -------------------
   matrix X;
   trajectory_matrix(x,L,X);
//-------------3. Singular decomposition (SVD) ----
   matrix U, V;
   vector singular_values;
   X.SingularValueDecompositionDC(SVDZ_A,singular_values,U,V);
   V = V.Transpose();
   double total_variance;
   vector powv = singular_values*singular_values;
   total_variance = powv.Sum();
   VectortoArray(powv/total_variance,x_array);
   PlotGraphic(x_array,5,2); // Singular spectrum graph
   double x_1[],x_2[];
   VectortoArray(U.Col(0),x_1);
   VectortoArray(U.Col(1),x_2);
   PlotGraphic(x_1,x_2,5,3); // graph of the first two singular vectors
   PlotGraphic(x_1,x_2,5,4); // scatterplot of the first two singular vectors
//---------- 4. Time series reconstruction----
   int K = N - L + 1;
   matrix X_i = matrix::Zeros(L,K);
   matrix Ui = matrix::Zeros(L,1);
   matrix Vi = matrix::Zeros(1,K);
   vector x_tilde;
   vector recon_series = vector::Zeros(N);
   for(int i=0; i<r_;i++)
     {
      Ui.Col(U.Col(i),0);
      Vi.Row(V.Col(i),0);
      X_i = (Ui.MatMul(Vi))*singular_values[i];  // rank one matrices
      diagonal_averaging(X_i,x_tilde);
      recon_series = recon_series + x_tilde;     // reconstructed series
     }
   double recon[];
   VectortoArray(recon_series,recon);

//------------5. LRR ratio vector   --------------------
   matrix U_r = U;
   U_r.Resize(L,r_);              // r left singular vectors
   vector a = vector::Zeros(L-1); // vector a of LRR ratios
   double denom =0;
   vector u_k;
   double last;
   for(int k=0; k<r_;k++)
     {
      u_k = U_r.Col(k);           //  k th singular vector 

      last = u_k[L-1];
      u_k.Resize(L-1);
      a = a + last*u_k;
      denom = denom + MathPow(last,2);
     }
   denom = 1 - denom;
   a = a/denom;                   // vector a of LRR ratios

//----------------- 6. Forecast using LRR ratios -----------
   int forecast_steps = fs;
   double forecast[];
   ArrayResize(forecast,forecast_steps);
   double fi[];
   ArrayCopy(fi,recon,0,N-L+1,L-1);
   for(int i=0;i<forecast_steps;i++)
     {
      double sum = 0.0;
      for(int j = 0; j < L-1; j++)
        {
         sum += a[j] * fi[j];
        }
      forecast[i]= sum;  // Forecast
      // Update fi
      ArrayCopy(fi, fi, 0, 1, ArraySize(fi)-1); // Shift to the left
      fi[L-2] = forecast[i];                    // Add a new value
     }

   double originalplusforecast[];
   ArrayResize(originalplusforecast,N+forecast_steps);
   ArrayCopy(originalplusforecast,original_series,0,0,WHOLE_ARRAY);
   ArrayCopy(originalplusforecast,forecast,N,0,WHOLE_ARRAY);

   double reconstructedplusforecast[];
   ArrayResize(reconstructedplusforecast,N+forecast_steps);
   ArrayCopy(reconstructedplusforecast,recon,0,0,WHOLE_ARRAY);
   ArrayCopy(reconstructedplusforecast,forecast,N,0,WHOLE_ARRAY);
   PlotGraphic(originalplusforecast,reconstructedplusforecast,15,5);

//---- reconstructed data and forecast using the SingularSpectrumAnalysisForecast function
   vector MQLreconforecast;
   x.SingularSpectrumAnalysisForecast(L,r_,forecast_steps,MQLreconforecast);
   double MQL_RF[];
   VectortoArray(MQLreconforecast,MQL_RF);
   PlotGraphic(reconstructedplusforecast,MQL_RF,10,6);
  }

//+------------------------------------------------------------------+
//| Plot Graphic                                                     |
//+------------------------------------------------------------------+
void PlotGraphic(double &data[], int sec, int n_graph)
  {
   ChartSetInteger(0,CHART_SHOW,false);
   CGraphic graphic;
   ulong width = ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
   ulong height = ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS);

   if(ObjectFind(0,"Graphic")<0)
      graphic.Create(0,"Graphic",0,0,0,int(width),int(height));
   else
      graphic.Attach(0,"Graphic");

   string st;
   if(sd == SinusPlusNoise)
     {
      st = "Sinus + Noise";
     }
   if(sd == Trend_Sinus_Noise)
     {
      st = "Trend + Sinus + Noise";
     }
   if(sd == RandomWalk)
     {
      st = "Random Walk";
     }
   if(sd == WhiteNoise)
     {
      st = "White Noise ";
     }

   if(n_graph==1) // data graph
     {
      CCurve *curve = graphic.CurveAdd(data,ColorToARGB(clrRed,255),CURVE_LINES,st);
      graphic.XAxis().Name("Series " + st);
      graphic.BackgroundMain(st);
     }
   if(n_graph==2) // chart of singular values (relative_variance = sigma_i^2/Sum Sigma_j^2)
     {
      CCurve *curve = graphic.CurveAdd(data,ColorToARGB(clrBlue,255),CURVE_LINES,st);
      graphic.XAxis().Name("Index ");
      graphic.YAxis().Name("Singular values ");
      graphic.BackgroundMain("Singular values " + st);
     }
   graphic.XAxis().NameSize(18);
   graphic.YAxis().NameSize(18);
   graphic.BackgroundMainColor(ColorToARGB(clrBlack,255));
   graphic.BackgroundMainSize(24);
   graphic.CurvePlotAll();
   graphic.Update();
   Sleep(sec*1000);
   ChartSetInteger(0,CHART_SHOW,true);
   graphic.Destroy();
   ChartRedraw(0);
  }

//+------------------------------------------------------------------+
//| Plot Graphic                                                     |
//+------------------------------------------------------------------+
void PlotGraphic(double &data1[],double &data2[], int sec,int n_graph)
  {
   ChartSetInteger(0,CHART_SHOW,false);
   CGraphic graphic;
   ulong width = ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
   ulong height = ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS);

   if(ObjectFind(0,"Graphic")<0)
      graphic.Create(0,"Graphic",0,0,0,int(width),int(height));
   else
      graphic.Attach(0,"Graphic");

   if(n_graph==3)
     {
      CCurve *curve =  graphic.CurveAdd(data1,ColorToARGB(clrRed,255),CURVE_LINES,"first");
      CCurve *curve1 = graphic.CurveAdd(data2,ColorToARGB(clrBlue,255),CURVE_LINES,"second");
      graphic.XAxis().Name(" ");
      graphic.BackgroundMain("first and second singular vectors");
     }
   if(n_graph==4) // scatter plot of singular vectors
     {
      CCurve *curve =  graphic.CurveAdd(data1,data2,ColorToARGB(clrRed,255),CURVE_LINES,"first");
      graphic.XAxis().Name("first singular vector");
      graphic.YAxis().Name("second singular vector");
      graphic.BackgroundMain("Scatter plot of singular vectors U_1 vs U_2");
     }

   if(n_graph==5) // data chart plus forecast
     {
      CCurve *curve = graphic.CurveAdd(data1,ColorToARGB(clrBlue,255),CURVE_LINES,"original");
      CCurve *curve1 = graphic.CurveAdd(data2,ColorToARGB(clrRed,255),CURVE_POINTS_AND_LINES,"reconstructed");
      graphic.XAxis().Name("Time ");
      graphic.YAxis().Name("Value ");
      graphic.BackgroundMain("Original(Blue) + reconstructed(Red) + forecast(Red) ");
      curve1.PointsSize(3);
     }

// graph comparing the forecast of the MQL5 SingularSpectrumAnalysisForecast function with the Basic-SSA forecast
   if(n_graph==6)
     {
      CCurve *curve = graphic.CurveAdd(data1,ColorToARGB(clrBlue,255),CURVE_LINES,"BasicSSA");
      CCurve *curve1 = graphic.CurveAdd(data2,ColorToARGB(clrRed,255),CURVE_LINES,"MQL5");
      graphic.XAxis().Name("reconstructed + forecast ");
      graphic.BackgroundMain(" MQL5 SingularSpectrumAnalysisForecast vs script Basic-SSA ");
      curve1.PointsSize(3);
     }
   graphic.XAxis().NameSize(18);
   graphic.YAxis().NameSize(18);
   graphic.BackgroundMainColor(ColorToARGB(clrBlack,255));
   graphic.BackgroundMainSize(24);
   graphic.CurvePlotAll();
   graphic.Update();
   Sleep(sec*1000);
   ChartSetInteger(0,CHART_SHOW,true);
   graphic.Destroy();
   ChartRedraw(0);
  }

//+------------------------------------------------------------------+
//| Copy the vector into an array                                    |
//+------------------------------------------------------------------+
void VectortoArray(vector &v, double &array[])
  {
   int v_size = (int)v.Size();
   ArrayResize(array,v_size);
   for(int i=0; i<v_size; i++)
     {
      array[i]   =  v[i];
     }
  }
//+------------------------------------------------------------------+
//| Trajectory matrix X                                              |
//+------------------------------------------------------------------+
void trajectory_matrix(vector & series,int window_length, matrix & X)
  {
   int N_ = (int)series.Size();
   int L_ = window_length;
   int K = N_ - L_ + 1;
   X=matrix::Zeros(L_,K);
   for(int i=0; i <L_;  i++)
     {
      for(int j=0; j <K;  j++)
        {
         X[i,j] = series[i+j];
        }
     }
  }
//+-------------------------------------------------------------------+
//| Diagonal averaging of a matrix                                    |
//| Input: Xi - matrix L x K (elementary matrix of the i th component)|
//| Output: x_tilde - reconstructed time series                       |
//+-------------------------------------------------------------------+
void diagonal_averaging(matrix &Xi,vector &x_tilde)
  {
   int L_ = (int)Xi.Rows();
   int K  = (int)Xi.Cols();
   int N_ = L_ + K - 1; // Length of the original time series

   x_tilde = vector::Zeros(N_);
   double total; // Sum of elements on the anti diagonal
   int w_n;      // Number of elements on the anti diagonal
   int k;
   for(int n=0; n < N_; n++)
     {
      total = 0;
      w_n = 0;
      for(int j=0; j <L_; j++)
        {
         k = n - j ; // Column index: n = j + k ---> k = n - j
         if(k >= 0 && k < K) // Check that the index is within the matrix
           {
            total = total + Xi[j, k];
            w_n = w_n + 1;
           }
        }
      x_tilde[n] = total / w_n; // Averaging
     }
  }
//+------------------------------------------------------------------+



结论

在本文中,我们介绍了奇异谱分析(SSA)的基础知识,该方法利用轨迹矩阵的奇异值分解(SVD)来揭示数据中隐藏的结构。我们展示了SSA如何有效地将时间序列分解为可解释的成分 —— 趋势、季节性和噪声 —— 从而实现这些成分的重建和预测。

然而,该方法存在局限性,特别是无法可靠地区分确定性趋势和随机性趋势,例如高斯随机游走。同时,值得注意的是,SSA的目的并非对趋势进行严格的统计分类,其优势在于灵活地分解和识别数据结构,在这方面表现优异。

SSA的应用不仅限于单变量时间序列的分析。该方法允许处理多维数据。它能用于构建变点检测指标,以检测金融工具行为的突然变化。这些方向代表了未来研究的有前景的领域,因为准确理解数据的本质对于发现市场模式至关重要。

本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/17845

附加的文件 |
Rank.mq5 (5.61 KB)
Basic-SSA.mq5 (24.76 KB)
最近评论 | 前往讨论 (2)
Stanislav Korotky
Stanislav Korotky | 23 4月 2025 在 17:18
在此郑重声明,"ita "话题已在文章(如12)和讨论中多次出现,更不用说 EMD 等相关方法了(一些作者在研究中发现,将 SSA 和 EMD 结合使用可提高疗效)。
Evgeniy Chernish
Evgeniy Chernish | 23 4月 2025 在 19:55
Stanislav Korotky #:
郑重声明,"这个 "话题在文章(如12)和讨论中出现过很多次,更不用说 EMD 等相关方法了(一些作者在研究中发现,将 SSA 和 EMD 结合起来可以改善结果)。
我之所以决定写一篇关于这个主题的文章,是因为我找不到关于这种方法的详细描述。在您引用的文章中,作者立即参考了教科书中的详细介绍,而在 alglib 库的网站上,对该方法的介绍则少之又少,而且也不清楚该方法具体是如何实现的,只是提供了一个现成的产品,并假定该产品的用户已经对这种分析方法的理论了如指掌。 就我个人而言,我无法接受在黑暗中使用某种我完全不知道的算法,我必须看一下汽车的引擎盖,可以这么说。

交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
从基础到中级:结构(三) 从基础到中级:结构(三)
在本文中,我们将探讨什么是结构化代码。许多人将结构化代码与有组织的代码混淆,但这两个概念之间存在差异。这正是本文将要讨论的内容。尽管你在初次接触这类代码编写时可能会感到其明显的复杂性,但我已尽可能地以简单易懂的方式讲解这一主题。然而,本文只是迈向更宏大目标的第一步。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
珊瑚礁优化算法(CRO) 珊瑚礁优化算法(CRO)
本文对珊瑚礁优化(CRO)算法进行了全面分析,该算法是一种受珊瑚礁形成与发育生物过程启发的元启发式方法。该算法对珊瑚进化的关键环节进行了建模,包括广播产卵(群体产卵)、体内受精(抱卵孵化)、幼虫附着、无性繁殖以及有限礁区空间的竞争。尤其关注该算法的改进版本。