一维奇异谱分析(SSA)·综合运用
📊

一维奇异谱分析(SSA)·综合运用

(3/3)·从轨迹矩阵到信号重建,前两步铺垫完,这一篇把 SSA 全套流程跑通并接回盘面

新手友好 第 3/3 篇
很多人把 SSA 当成只输出一条平滑线的黑箱,其实它把序列拆成趋势、周期、噪声三件套后,每一件都能单独拿来看。跳过分组与重建直接信滤波结果,往往把假突破里的噪声当成了信号。

从奇异谱分解到线性递归外推

这段逻辑紧接重构序列之后,核心是把 SSA 的残差结构转成可递推的预测向量。它先截取前 r_ 个左奇异向量,按最后一行元素构造 LRR(线性递归关系)比例向量 a,分母用 1 减去末元素平方和做归一。 denom = 1 - denom 这一步很关键:若末元素平方和接近 1,说明序列能量几乎被前 r_ 个分量吃尽,外推会倾向走平;实盘里黄金 1H 在趋势中段常出现 denom 落在 0.2~0.4 区间,对应外推斜率更陡。 预测循环里 fi 始终保留最近 L-1 个值,每步算加权和后左移一位、把新预测塞进队尾。注意 forecast_steps 由 fs 传入,MT5 里若设 fs=15,图形会向后画 15 根虚拟 K 线,仅作概率参考。 最后用自写结果与系统函数 SingularSpectrumAnalysisForecast 叠图比对,参数 15、5 与 10、6 是两套 PlotGraphic 的展示编号。外汇与贵金属杠杆高,外推失效可能瞬间扫损,任何信号都只作辅助。

MQL5 / C++
  Vi.Row(V.Col(i),class="num">0);
  X_i = (Ui.MatMul(Vi))*singular_values[i];  class=class="str">"cmt">// rank one matrices
  diagonal_averaging(X_i,x_tilde);
  recon_series = recon_series + x_tilde;     class=class="str">"cmt">// reconstructed series
   }
 class="type">class="kw">double recon[];
 VectortoArray(recon_series,recon);
class=class="str">"cmt">//------------class="num">5. LRR ratio vector  --------------------
 matrix U_r = U;
 U_r.Resize(L,r_);              class=class="str">"cmt">// r left singular vectors
 vector a = vector::Zeros(L-class="num">1); class=class="str">"cmt">// vector a of LRR ratios
 class="type">class="kw">double denom =class="num">0;
 vector u_k;
 class="type">class="kw">double last;
 for(class="type">int k=class="num">0;k<r_;k++)
   {
    u_k = U_r.Col(k);           class=class="str">"cmt">//  k th singular vector
    last = u_k[L-class="num">1];
    u_k.Resize(L-class="num">1);
    a = a + last*u_k;
    denom = denom + MathPow(last,class="num">2);
   }
 denom = class="num">1 - denom;
 a = a/denom;                   class=class="str">"cmt">// vector a of LRR ratios
class=class="str">"cmt">//----------------- class="num">6. Forecast class="kw">using LRR ratios -----------
 class="type">int forecast_steps = fs;
 class="type">class="kw">double forecast[];
 ArrayResize(forecast,forecast_steps);
 class="type">class="kw">double fi[];
 ArrayCopy(fi,recon,class="num">0,N-L+class="num">1,L-class="num">1);
 for(class="type">int i=class="num">0;i<forecast_steps;i++)
   {
    class="type">class="kw">double sum = class="num">0.0;
    for(class="type">int j = class="num">0; j < L-class="num">1; j++)
      {
       sum += a[j] * fi[j];
      }
    forecast[i]= sum;  class=class="str">"cmt">// Forecast
    class=class="str">"cmt">// Update fi
    ArrayCopy(fi, fi, class="num">0, class="num">1, ArraySize(fi)-class="num">1); class=class="str">"cmt">// Shift to the left
    fi[L-class="num">2] = forecast[i];                    class=class="str">"cmt">// Add a new value
   }
 class="type">class="kw">double originalplusforecast[];
 ArrayResize(originalplusforecast,N+forecast_steps);
 ArrayCopy(originalplusforecast,original_series,class="num">0,class="num">0,WHOLE_ARRAY);
 ArrayCopy(originalplusforecast,forecast,N,class="num">0,WHOLE_ARRAY);
 class="type">class="kw">double reconstructedplusforecast[];
 ArrayResize(reconstructedplusforecast,N+forecast_steps);
 ArrayCopy(reconstructedplusforecast,recon,class="num">0,class="num">0,WHOLE_ARRAY);
 ArrayCopy(reconstructedplusforecast,forecast,N,class="num">0,WHOLE_ARRAY);
 PlotGraphic(originalplusforecast,reconstructedplusforecast,class="num">15,class="num">5);
class=class="str">"cmt">//---- reconstructed data and forecast class="kw">using the SingularSpectrumAnalysisForecast function
 vector MQLreconforecast;
 x.SingularSpectrumAnalysisForecast(L,r_,forecast_steps,MQLreconforecast);
 class="type">class="kw">double MQL_RF[];
 VectortoArray(MQLreconforecast,MQL_RF);
 PlotGraphic(reconstructedplusforecast,MQL_RF,class="num">10,class="num">6);
}

「用 CGraphic 把序列与奇异值画出来」

在 MT5 里做价格行为或噪声序列的可视化,直接用 CGraphic 比手绘 ObjectCreate 省事得多。下面这段 PlotGraphic 重载负责把传入的 double 数组渲染成曲线,并在末尾用 Sleep(sec*1000) 控制每帧停留秒数,方便肉眼比对不同合成序列的形态差异。 第一个重载接收单数组 data[] 与图形类型 n_graph:n_graph==1 时画原始序列(红曲线),n_graph==2 时画奇异值分布(蓝曲线,横轴 Index、纵轴 Singular values)。函数开头先 ChartSetInteger(0,CHART_SHOW,false) 隐藏图表避免闪烁,结尾再显示并 Destroy 释放,这是终端绘图的标准防卡顿手法。 代码里 sd 枚举决定了标题文字:SinusPlusNoise 显示「Sinus + Noise」,Trend_Sinus_Noise 显示「Trend + Sinus + Noise」,RandomWalk 与 WhiteNoise 同理。如果你要验证自己的 EURUSD tick 序列,把 sd 对应分支改掉或外部传参即可,背景主标题会同步变化。 图形尺寸取的是 ChartGetInteger 的 WIDTH_IN_PIXELS / HEIGHT_IN_PIXELS,坐标名尺寸设 18、主背景标题尺寸设 24,黑色底。外汇与贵金属价格序列波动结构差异大,用这套脚本跑白噪声和随机游走对比,可能更直观看出真实行情偏离纯随机的概率。

MQL5 / C++
class="type">void PlotGraphic(class="type">class="kw">double &data[], class="type">int sec, class="type">int n_graph)
  {
   ChartSetInteger(class="num">0,CHART_SHOW,false);
   CGraphic graphic;
   class="type">ulong width = ChartGetInteger(class="num">0,CHART_WIDTH_IN_PIXELS);
   class="type">ulong height = ChartGetInteger(class="num">0,CHART_HEIGHT_IN_PIXELS);
   if(ObjectFind(class="num">0,"Graphic")<class="num">0)
      graphic.Create(class="num">0,"Graphic",class="num">0,class="num">0,class="num">0,class="type">int(width),class="type">int(height));
   else
      graphic.Attach(class="num">0,"Graphic");
   class="type">class="kw">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==class="num">1) class=class="str">"cmt">// data graph
     {
      CCurve *curve = graphic.CurveAdd(data,ColorToARGB(clrRed,class="num">255),CURVE_LINES,st);
      graphic.XAxis().Name("Series " + st);
      graphic.BackgroundMain(st);
     }
   if(n_graph==class="num">2) class=class="str">"cmt">// chart of singular values(relative_variance = sigma_i^class="num">2/Sum Sigma_j^class="num">2)
     {
      CCurve *curve = graphic.CurveAdd(data,ColorToARGB(clrBlue,class="num">255),CURVE_LINES,st);
      graphic.XAxis().Name("Index ");
      graphic.YAxis().Name("Singular values ");
      graphic.BackgroundMain("Singular values " + st);
     }
   graphic.XAxis().NameSize(class="num">18);
   graphic.YAxis().NameSize(class="num">18);
   graphic.BackgroundMainColor(ColorToARGB(clrBlack,class="num">255));
   graphic.BackgroundMainSize(class="num">24);
   graphic.CurvePlotAll();
   graphic.Update();
   Sleep(sec*class="num">1000);
   ChartSetInteger(class="num">0,CHART_SHOW,true);
   graphic.Destroy();
   ChartRedraw(class="num">0);
  }

class="type">void PlotGraphic(class="type">class="kw">double &data1[],class="type">class="kw">double &data2[], class="type">int sec,class="type">int n_graph)
  {
   ChartSetInteger(class="num">0,CHART_SHOW,false);
   CGraphic graphic;
   class="type">ulong width = ChartGetInteger(class="num">0,CHART_WIDTH_IN_PIXELS);
   class="type">ulong height = ChartGetInteger(class="num">0,CHART_HEIGHT_IN_PIXELS);

◍ 用 CGraphics 把 SSA 结果画出来

在 SSA 分解完成后,真正能帮你做判断的是把奇异向量、重构序列和预测值直接绘到独立图形窗口。下面这段逻辑按 n_graph 编号切换六种图:3 号画前两个奇异向量曲线,4 号画 U1 对 U2 的散点,5 号叠原始与重构+预测,6 号对比 Basic-SSA 与 MQL5 自带函数的预测。 先查对象名 "Graphic" 是否存在,没有就 Create 按 width、height 建画布,有则 Attach 接管。注意 Create 的宽高用了 int() 强转,传入浮点尺寸时别漏这步,否则在部分版本会静默失败。 绘图细节上,XAxis().NameSize(18) 与 YAxis().NameSize(18) 把轴名放到 18 像素,BackgroundMainSize(24) 让标题更醒目,BackgroundMainColor 设成纯黑底。曲线点大小用 PointsSize(3) 调,预测点会比线粗一圈,肉眼好认。 画完必须 CurvePlotAll() 再 Update(),不然窗口不刷新;Sleep(sec*1000) 控制每帧停留秒数,循环演示时很有用。结束时 graphic.Destroy() 释放对象,ChartRedraw(0) 强制重绘主图,避免残留。外汇与贵金属价格序列做 SSA 可视化时波动剧烈,图形仅辅助判别周期结构,实际信号在高杠杆下风险极高,参数需自行回测。

MQL5 / C++
if(ObjectFind(class="num">0,"Graphic")<class="num">0)
     graphic.Create(class="num">0,"Graphic",class="num">0,class="num">0,class="num">0,class="type">int(width),class="type">int(height));
   else
     graphic.Attach(class="num">0,"Graphic");
   if(n_graph==class="num">3)
    {
      CCurve *curve =  graphic.CurveAdd(data1,ColorToARGB(clrRed,class="num">255),CURVE_LINES,"first");
      CCurve *curve1 = graphic.CurveAdd(data2,ColorToARGB(clrBlue,class="num">255),CURVE_LINES,"second");
      graphic.XAxis().Name(" ");
      graphic.BackgroundMain("first and second singular vectors");
    }
   if(n_graph==class="num">4) class=class="str">"cmt">// scatter plot of singular vectors
    {
      CCurve *curve =  graphic.CurveAdd(data1,data2,ColorToARGB(clrRed,class="num">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==class="num">5) class=class="str">"cmt">// data chart plus forecast
    {
      CCurve *curve = graphic.CurveAdd(data1,ColorToARGB(clrBlue,class="num">255),CURVE_LINES,"original");
      CCurve *curve1 = graphic.CurveAdd(data2,ColorToARGB(clrRed,class="num">255),CURVE_POINTS_AND_LINES,"reconstructed");
      graphic.XAxis().Name("Time ");
      graphic.YAxis().Name("Value ");
      graphic.BackgroundMain("Original(Blue) + reconstructed(Red) + forecast(Red) ");
      curve1.PointsSize(class="num">3);
    }
class=class="str">"cmt">// graph comparing the forecast of the MQL5 SingularSpectrumAnalysisForecast function with the Basic-SSA forecast
   if(n_graph==class="num">6)
    {
      CCurve *curve = graphic.CurveAdd(data1,ColorToARGB(clrBlue,class="num">255),CURVE_LINES,"BasicSSA");
      CCurve *curve1 = graphic.CurveAdd(data2,ColorToARGB(clrRed,class="num">255),CURVE_LINES,"MQL5");
      graphic.XAxis().Name("reconstructed + forecast ");
      graphic.BackgroundMain(" MQL5 SingularSpectrumAnalysisForecast vs script Basic-SSA ");
      curve1.PointsSize(class="num">3);
    }
   graphic.XAxis().NameSize(class="num">18);
   graphic.YAxis().NameSize(class="num">18);
   graphic.BackgroundMainColor(ColorToARGB(clrBlack,class="num">255));
   graphic.BackgroundMainSize(class="num">24);
   graphic.CurvePlotAll();
   graphic.Update();
   Sleep(sec*class="num">1000);
   ChartSetInteger(class="num">0,CHART_SHOW,true);
   graphic.Destroy();
   ChartRedraw(class="num">0);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Copy the vector into an array                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void VectortoArray(vector &v, class="type">class="kw">double &array[])
  {

SSA 里的轨迹矩阵与对角平均实现

奇异谱分析(SSA)在 MT5 里落地,核心两步是构造轨迹矩阵和对结果做对角平均。下面这段 MQL5 代码把这两步拆成了两个独立函数,直接能拷进 EA 或脚本里跑。 轨迹矩阵函数 trajectory_matrix 接收价格序列 series 和窗口长度 window_length,输出一个 L×K 的矩阵 X,其中 K = N - L + 1。比如一段 100 根 K 线的收盘价、窗口取 20,那 K 就是 81,矩阵维度为 20×81,内存占用极小。 对角平均 diagonal_averaging 负责把分解后的初等矩阵 Xi 还原成一维序列。它沿反对角线求和再除以元素个数,反推原始长度 N = L + K - 1。这一步是 SSA 重构信号的关键,外汇和贵金属 1H 以上周期做去噪时,窗口长度选 10~30 可能更顺手,但高频品种过短易留噪点。 开 MT5 用 matrix::Zerosvector::Zeros 初始化后,把历史收盘价塞进 series,调一下这两个函数,就能看到原序列被拆开再拼回的效果。贵金属受消息面跳空影响大,这类线性重构在高波动夜盘可能偏离明显,验证时务必用真实 Tick 或至少 1 分钟棒回测。

MQL5 / C++
  class="type">int v_size = (class="type">int)v.Size();
  ArrayResize(array,v_size);
  for(class="type">int i=class="num">0; i<v_size; i++)
    {
      array[i]  =  v[i];
    }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Trajectory matrix X                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void trajectory_matrix(vector & series,class="type">int window_length, matrix & X)
  {
   class="type">int N_ = (class="type">int)series.Size();
   class="type">int L_ = window_length;
   class="type">int K = N_ - L_ + class="num">1;
   X=matrix::Zeros(L_,K);
   for(class="type">int i=class="num">0; i <L_;  i++)
     {
       for(class="type">int j=class="num">0; j <K;  j++)
         {
          X[i,j] = series[i+j];
         }
     }
  }
class=class="str">"cmt">//+-------------------------------------------------------------------+
class=class="str">"cmt">//| Diagonal averaging of a matrix                                   |
class=class="str">"cmt">//| Input: Xi - matrix L x K(elementary matrix of the i th component)|
class=class="str">"cmt">//| Output: x_tilde - reconstructed time series                      |
class=class="str">"cmt">//+-------------------------------------------------------------------+
class="type">void diagonal_averaging(matrix &Xi,vector &x_tilde)
  {
   class="type">int L_ = (class="type">int)Xi.Rows();
   class="type">int K  = (class="type">int)Xi.Cols();
   class="type">int N_ = L_ + K - class="num">1; class=class="str">"cmt">// Length of the original time series
   x_tilde = vector::Zeros(N_);
   class="type">class="kw">double total; class=class="str">"cmt">// Sum of elements on the anti diagonal
   class="type">int w_n;      class=class="str">"cmt">// Number of elements on the anti diagonal
   class="type">int k;
   for(class="type">int n=class="num">0; n < N_; n++)
     {
      total = class="num">0;
      w_n = class="num">0;
      for(class="type">int j=class="num">0; j <L_; j++)
        {
         k = n - j ; class=class="str">"cmt">// Column index: n = j + k ---> k = n - j
         if(k >= class="num">0 && k < K) class=class="str">"cmt">// Check that the index is within the matrix
           {
            total = total + Xi[j, k];
            w_n = w_n + class="num">1;
           }
        }
      x_tilde[n] = total / w_n; class=class="str">"cmt">// Averaging
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

「多变量与变点检测还能往下挖」

SSA 这套分解逻辑并不只服务于单条价格曲线。轨迹矩阵本身可以横向拼入多个品种或指标序列,变成多维输入,用来抓跨资产结构的同步断裂。 一个能直接搬上 MT5 的延伸方向是变点检测指标:用重构后的残差能量突变,标记贵金属或外汇品种行为模式的突然切换,这类跳变往往早于肉眼可辨的波段反转,概率上更值得盯。 不过得泼点冷水,外汇和贵金属自带高杠杆与跳空风险,任何分解工具都只是把隐藏结构摊开给你看,不等于能替你过滤随机游走的骗线。 附带的 Basic-SSA.mq5(24.76 KB)和 Rank.mq5(5.61 KB)已经把 SVD 与成分排序跑通,打开改个窗口长度,就能验证你关心那段行情里趋势和噪声谁占大头。

把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到分解后的趋势与周期分量,你只管判断当前价格偏离哪一层。

常见问题

L 决定分析深度,偏小会漏掉长周期,偏大会让噪声分量膨胀;N/2 是经验折中,实盘可按品种周期微调。
可以,小布盯盘对应品种页已内置 AIGC 诊断,直接给出趋势、周期与噪声分层,省去自己写 SVD 代码。
通常是分组时把本该合并的相近成分拆开了,或噪声分量没剔干净;检查特征三元组权重再重分即可。
贵金属与外汇受事件驱动跳空多,SSA 基于历史窗口,突变后可能数根 K 线才跟得上,仅作概率参考。