一维奇异谱分析(SSA)·进阶篇
(2/3)· 跳过繁琐代数推导,直接掌握SSA复原、预测与Toeplitz变体的实战拆解路径
对角平均把矩阵变回序列
SSA 分解完之后,要把每个分组矩阵重新拼成长度为 N 的时间序列,这一步靠的是对角平均法。原理不复杂:对分组(或基)矩阵里的元素 xj 和 k 沿反对角线取平均,逐条推出一条新序列。 这样重建出来的每条序列,对应趋势成分或者周期性成分中的一种。把所有重建序列相加,就得到原始序列的非参数模型,模型形态由窗口长度 L 和基矩阵的分组方式共同决定。 一个可验证的事实是:若分组时把噪声组也一并纳入,所有重建序列(含噪声)之和会完全复原原始时间序列。在 MT5 里用 SSA 做行情分解时,你可以故意保留全部分组做求和,对照原收盘价数组,两者差值应落在浮点误差量级(约 1e-10 以内)。外汇与贵金属波动受宏观事件驱动,此类分解仅作结构观察,实盘仍属高风险。
「SSA 怎么把序列往前推 M 步」
SSA 对 gi 时间序列做未来 M 步预测,不是直接外推原始数据,而是基于重建后的序列跑线性递推。核心就是一组线性递推关系(LRR):用重建序列的近期值乘上比率向量 a_j,叠出下一步。 比率向量 a_j 由奇异向量 Ui 直接决定,算法只取 Ui 的前 L−1 个坐标(First)除以它的最后一个坐标(Last)。这里 L 是窗口长度,d 是挑出来代表有用信号的奇异向量数量。 实操上,d 选小了噪声没滤干净,选大了容易把趋势也当噪声切掉;L 一般取序列长度的 1/3 到 1/2 区间去 MT5 里跑一下才能看残差稳不稳。外汇与贵金属波动聚集明显,SSA 预测只是概率性参考,杠杆品种高风险,别拿递推值当进场价。
◍ 托普利兹结构在协方差矩阵上的差异
托普利兹奇异谱分析(Toeplitz-SSA)和基本 SSA 的根本分叉点,在于轨迹矩阵的构造方式。基本 SSA 用时间序列滑动窗口拼轨迹矩阵,而托普利兹版直接构建一个具托普利兹结构的自协方差矩阵,再做 SVD 分解。后续的重构、分组、对角平均、线性递推比与预测流程,两者完全一致。 实测上,托普利兹变体对平稳序列更友好,预测误差比基本 SSA 更小;但一旦序列非平稳,经典 SSA 的拟合与外推表现反超。外汇与贵金属分钟级报价属于典型非平稳过程,杠杆品种高风险,误用平稳假设会放大过拟合概率。 因此做 MT5 自定义指标时,若输入是去趋势后的残差(近似平稳),可试托普利兹;若直接喂原生收盘价,基本 SSA 更稳。本文系列只落地基本算法,变体留作读者自行验证。
用脚本跑通四类合成序列的SSA拆解
在 MT5 里验证 SSA 概念,最省事的办法是直接用 MQL5 脚本生成可控的合成序列。我们准备的四类序列覆盖了实战里常见的时间结构:正弦波叠高斯白噪声(平稳、有周期)、线性趋势加正弦波再加噪声(非平稳、含确定趋势与周期)、对称高斯随机游走(非平稳、随机趋势)、纯高斯白噪声(平稳)。 脚本会在屏幕上依次吐出六张图:原始数据、相对奇异值占比、前两个左奇异向量、这两个向量的散点图、原序列+重建序列+预测、以及调用 SingularSpectrumAnalysisForecast 做的重建与预测。相对奇异谱按 σi^2/∑σj^2 计算,陡降前的“拐点”之前通常就是该留的成分数。 看谱形能反推成分类型:周期信号往往有两个大小接近的奇异值,若前面还带一段平台再跌,说明混了多个频率谐波加噪声;谱值平滑下滑则基本没有确定性信号。但 SSA 有个硬伤——分不清随机趋势和确定趋势,随机游走只会暴露出一个管趋势的大成分,而那趋势不可预测,所以特意塞了随机游走数据做对照。 锁定几个大奇异值后,翻前两个左奇异向量图:噪声里的周期信号,头两个向量常长成近似正弦或余弦。再把 U1 放 X、U2 放 Y 画散点,周期成分会落出椭圆或圆(一对正交谐波),混沌云就说明没清晰周期。图例5 里强噪声下肉眼难辨周期,SSA 却抽出了基波并给出 100 步预测;外汇与贵金属属高风险品种,实盘价格远没这么干净,但拿它证伪“价格有周期”的假设很划算。
「MT5里SSA到底用的哪套算法」
MT5终端自带的矩阵与向量库(OpenBLAS部分)里有个SingularSpectrumAnalysisForecast函数,文档没写清它具体是哪种SSA变体。我一开始按自协方差矩阵分解的逻辑,猜它是基于托普利兹矩阵的改版,但拿基础SSA脚本和这个函数对同一序列做预测、重建,结果完全一致——那它大概率还是基础算法的封装。 验证用的序列是趋势+正弦波+噪声的合成数据,总长200点,向外预测100步。图例里两者重叠的曲线说明,开发者没在 forecast 函数里偷偷加 Toeplitz 约束。对外汇、贵金属这类高噪声行情做去噪重建时,这个一致性意味着你可以直接调系统函数,不必自己造轮子,但高波动时段伪分量会显著增多,需手动控阶。 轨迹矩阵分解我用了同库的SingularValueDecompositionDC,也就是分治SVD。开发者说它是所有SVD里最快的,实测对200×30的嵌入矩阵秒出结果,而且能一次性给完整奇异向量矩阵和截断矩阵,后面做分组重建很顺手。 别把正态当圣经 上面合成数据用的噪声是MathRandomNormal标准正态分布,实盘tick残差往往厚尾,直接套同样参数r_=2可能欠分解,建议先跑一段历史tick看残差分布再定阶。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Basic-SSA.mq5 | class=class="str">"cmt">//| Eugene | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Eugene" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property script_show_inputs class="macro">#include <Math\Stat\Stat.mqh> class="macro">#include <Graphics\Graphic.mqh> enum SimpleData { SinusPlusNoise, Trend_Sinus_Noise, RandomWalk, WhiteNoise, }; input class="type">int L = class="num">30; class=class="str">"cmt">// L - window length input class="type">int N = class="num">200; class=class="str">"cmt">// N - length of generated time series input class="type">int T = class="num">22; class=class="str">"cmt">// T - period length of sine function input class="type">int fs = class="num">100; class=class="str">"cmt">// fs - forecast horizon input class="type">int r_ = class="num">2; class=class="str">"cmt">// r - singular components input SimpleData sd = SinusPlusNoise; class=class="str">"cmt">// Data class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class="type">int err; vector x = vector::Zeros(N); class=class="str">"cmt">// time series class="type">class="kw">double x_array[]; class="type">class="kw">double original_series[]; class=class="str">"cmt">//------------class="num">1. Data -------------------- class=class="str">"cmt">//------------------ sinus + noise --------- if(sd == SinusPlusNoise) { for(class="type">int i=class="num">0; i <N; i++) { x[i] = MathSin(class="num">2*M_PI*(i+class="num">1)/T) + MathRandomNormal(class="num">0,class="num">1,err); } VectortoArray(x,x_array);
◍ 从轨迹矩阵到奇异谱的 SSA 落地
上面几段把四种合成序列(趋势+正弦+噪声、随机游走、白噪声等)塞进 original_series 并画图后,真正的 SSA 分解才开始。先调 trajectory_matrix(x,L,X) 把一维序列卷成 L×K 的轨迹矩阵,窗口 L 直接决定后能捕捉的周期尺度。 接着用 X.SingularValueDecompositionDC 做奇异值分解,拿到 singular_values、U、V。把奇异值平方求和得到 total_variance,再画 powv/total_variance 就是奇异谱——白噪声的谱通常平坦,随机游走则前几个分量占比极高,MT5 里跑一遍图 2 就能肉眼分辨。 前两个左奇异向量 U.Col(0)、U.Col(1) 分别绘成曲线和散点(图 3、图 4)。若散点呈环状或螺旋,说明序列里嵌了周期;若是一团云,基本是噪声主导。外汇与贵金属行情属高风险品种,这类合成实验仅用于方法验证,实盘信号需叠加风控。 重建阶段按 r_ 个分量累加:每次取 Ui、Vi 重构 X_i,再对角平均回卷成 recon_series。r_ 取小了剩趋势、取大了把噪声也救回来,参数调试空间就在这一行循环里。
matrix X; trajectory_matrix(x,L,X); matrix U, V; vector singular_values; X.SingularValueDecompositionDC(SVDZ_A,singular_values,U,V); V = V.Transpose(); class="type">class="kw">double total_variance; vector powv = singular_values*singular_values; total_variance = powv.Sum(); VectortoArray(powv/total_variance,x_array); PlotGraphic(x_array,class="num">5,class="num">2); class="type">class="kw">double x_1[],x_2[]; VectortoArray(U.Col(class="num">0),x_1); VectortoArray(U.Col(class="num">1),x_2); PlotGraphic(x_1,x_2,class="num">5,class="num">3); PlotGraphic(x_1,x_2,class="num">5,class="num">4); class="type">int K = N - L + class="num">1; matrix X_i = matrix::Zeros(L,K); matrix Ui = matrix::Zeros(L,class="num">1); matrix Vi = matrix::Zeros(class="num">1,K); vector x_tilde; vector recon_series = vector::Zeros(N); for(class="type">int i=class="num">0;i<r_;i++) { Ui.Col(U.Col(i),class="num">0);