2026/10/17 5:33:44

MF-DFA详解:从Hurst指数到多重分形谱的时间序列分析

MF-DFA详解:从Hurst指数到多重分形谱的时间序列分析 如果你处理过股票日收益率、人体生理信号或者气候气温序列肯定绕不开这样一个疑问到底能不能用一个数字描述序列的长期记忆性。我上个月帮一位做策略的朋友看他的收益率数据他先兴致勃勃地报了个Hurst指数0.61觉得稳得不行。我顺手把序列按波动大小拆开再算了一遍结果高波动区和低波动区的标度表现几乎是两个世界。这时候再拿单一指数做判断就像用一张只写了平均数的收入统计表去描述贫富差距把最关键的信息全压平了。这种情况下MF-DFA多重分形去趋势波动分析才是真正能看清时间序列多重分形标度行为的工具。MF-DFA是去趋势波动分析DFA的多重分形推广。DFA只能给出一个标度指数MF-DFA则会给出一整条广义Hurst指数曲线 h(q)以及一条多重分形谱 f(α)。这两样东西能回答很多传统统计量回答不了的问题序列的长程相关是均匀的还是只在某些波动幅度下存在大波动和小波动背后的动力学机制是否相同一个看似随机的序列内部是否存在可以被分离出来的标度结构这篇文章我按自己的实操习惯来写先讲为什么需要MF-DFA再拆解它的数学构造然后给参数选择思路、一套零依赖的实现代码最后是结果解读和我在真实数据上踩过的坑。适合正在做金融数据分析、生物信号处理、气象时间序列研究的读者也适合想从单一Hurst指数进阶到多重分形视角的量化入门者。1. 从单一Hurst指数到多重分形原来的标度描述漏了什么1.1 一个会动摇“指数信仰”的实际场景假设你手里有一段3000个交易日的收益率序列长度不算短。用标准DFA算一下波动函数的斜率得到一个Hurst指数假设是0.58说明存在弱正的长程相关。这个结论看上去没问题但如果你把序列拆成两段一段是低波动震荡期一段是包含极端事件的剧烈波动期然后分别对这两段做DFA会得到明显不同的斜率。低波动期可能接近0.5甚至更低高波动期可能接近0.7。单一Hurst指数是这两段行为折中之后的结果。它没能告诉你“多重标度”的存在不同幅度的波动驱动它们的长程相关强度是不同的。类似的情况在气温序列里同样常见日尺度上的温度变化机制和月尺度、年尺度上的变化机制不是同一套物理过程用一个指数描述全年温度数据必然丢失信息。这就是单一标度描述的本质局限它假设序列在所有时间尺度、所有波动幅度下遵循同一个幂律。这个假设对纯白噪声、理想化的布朗运动勉强成立对真实世界里的复杂系统却经常被打破。多重分形分析的价值就是把“一个指数”扩展成“一族指数”让不同幅度、不同尺度的行为分别现形。1.2 单分形与多重分形的区别用一段话抓住本质单分形序列可以想象成一片地形起伏均匀的平原无论你从哪个高度看、用多大尺度量粗糙程度差不多。多重分形序列则像一片完整的山地有陡峭悬崖也有平坦河谷不同区域的局部奇异性强度各不相同。MF-DFA要做的就是去测量这些“不同强度的局部奇异性”各自覆盖了多大范围。在数学上多重分形的核心是Hölder指数 α。α刻画了序列在某一点附近的奇异程度α越小局部波动越剧烈、越不规则α越大局部越光滑。如果整个序列所有局部位置的α都相等那就是单分形。如果α分布在一个区间上每个α对应一个“子集”再计算这些子集的维数 f(α)就得到一条拱形的多重分形谱。谱越宽序列的多重分形性越强。MF-DFA正是从这个角度切入的它用去趋势波动函数把序列按照“段内局部波动大小”分层统计再通过q阶矩把不同大小的波动分离开。这样得到的广义Hurst指数 h(q)本质上就是一组与局部奇异性相关的标度指数集合。2. MF-DFA的五步构造每一步背后的数学直觉2.1 累积离差与双向分段MF-DFA的第一步是消除平均值的影响。对原始序列 x(i)先计算累计离差y(k) Σ_{i1}^k (x(i) - mean(x))这一步会得到一条累积曲线。为什么要做累加因为后续的波动分析希望关注的是“涨落结构”而不是绝对水平。累积离差同时保留了序列的漂移和平稳波动让后续去趋势步骤去处理这些缓慢变化。第二步是分段。把长度N的序列按窗口长度s切成不重叠的段通常得到 N_s floor(N/s) 段。如果N不能被s整除末尾会剩下一点数据。标准做法是从序列尾部再切一次也就是把尾部剩余部分从后往前取同样的窗口这样总共得到2N_s段。这个“反向切分”看起来只是为了不浪费数据实际操作里影响不小。我见过有实现图省事只用了正向切分结果短序列末尾的数据经常被丢弃导致尺度大时Hurst指数偏小。尾部数据往往包含最近的变化比如金融序列最后一两年的极端行情丢掉这些数据分析结果会整体偏移。2.2 局部去趋势与q阶波动函数第三步是去趋势。对每一段长度为s的数据用一个m阶多项式拟合局部趋势然后计算该段的方差F²(v,s) (1/s) Σ [ y((v-1)s i) - fit_v(i) ]²其中 fit_v(i) 是第v段的多项式拟合值。去趋势的目的是把局部趋势比如缓慢漂移、线性增长剔除。不去趋势的话这些趋势会被误判成长期记忆特征结果里会混入虚假的持续性。第四步是聚合所有段的波动值得到q阶波动函数。定义F_q(s) [ (1/(2N_s)) Σ_{v1}^{2N_s} [F²(v,s)]^(q/2) ]^(1/q)当 q2 时这个式子就退化成标准DFA的波动函数。关键在于q的作用q为正时F²大的段贡献被放大F_q(s)主要由大波动段主导q为负时F²小的段被放大F_q(s)反映的是小波动的标度行为。这里有个容易忽略的处理当q0时公式的幂平均会变成几何平均需要单独计算F_0(s) exp[ (1/(2N_s)) Σ ln F²(v,s) ]我的建议是写代码时不要试图用数学公式硬凑q0的情况直接用对数平均处理否则数值上会出现除零问题。2.3 从广义Hurst指数到质量指数第五步是对每个固定的q取一系列尺度s得到一系列F_q(s)。如果序列存在标度行为F_q(s)会随s呈现幂律增长F_q(s) ~ s^h(q)把log(s)和log(F_q(s))做线性回归斜率就是h(q)。这里的h(q)就是广义Hurst指数。q2时的h(2)对应传统Hurst指数的估计。如果h(q)不随q变化说明序列是单分形的如果h(q)随q增大而明显下降说明序列具有多重分形特征。由h(q)可以进一步定义质量指数τ(q) q·h(q) - 1这个减1的操作来源于波动函数的归一化项。有些文档写τ(q)q·h(q)那是因为他们用的波动函数定义不同。做对比阅读时先确认对方公式里是否带归一化否则谱横坐标会整体平移。2.4 Legendre变换与多重分形谱得到τ(q)后通过Legendre变换可以计算Hölder指数和多重分形谱。数值上通常用中心差分α(q) [ τ(qΔq) - τ(q-Δq) ] / (2Δq) f(α) q·α(q) - τ(q)把(q, f(α))画出来横轴是α纵轴是f(α)就得到一条拱形曲线。这就是多重分形谱。谱的左右两端分别对应负q和正q的行为负q端描述小波动区域的奇异结构正q端描述大波动区域的奇异结构。谱越宽说明序列在不同波动幅度下的行为差异越大。这里直接对离散的τ(q)做数值微分会得到不光滑的谱通常需要在插值或平滑后再求导。我的习惯是对τ(q)做三次样条平滑再对平滑后的曲线求导谱会稳定很多。3. 参数不是拍脑袋q区间、m阶数和s尺度的选择逻辑3.1 q的取值范围与对称性q的取值范围是第一个要定的参数。常见写法是取[-5, 5]步长0.1、0.2或0.5。为什么取对称区间因为正q和负q必须成对出现才能完整描述多重分形谱的两翼。正q放大的是大波动段负q放大的是小波动段两者缺一半都会让谱不对称解读时容易得出偏差。步长取决于你对谱分辨率的期望。步长0.1得到的谱更平滑但计算量也大一些步长0.5就够初步判断。值得注意的是q的绝对值过大时少量极端值会把F_q(s)拖得很大。对厚尾分布尤其明显金融序列在q6以上就经常出现h(q)突然下坠的尖峰那不是真实结构是数值不稳定性。我通常先跑一个宽区间看h(q)曲线两端是否平稳如果末尾抖动太厉害就把q范围收窄到[-4,4]或[-3,3]。3.2 多项式阶数m的选择m是去趋势多项式的阶数。m1是线性拟合m2是二次拟合m3是三次拟合。推荐从m2开始。原因是大多数真实时间序列的累积离差都能看到一条近似线性的漂移二次多项式能把这种漂移和缓慢起伏同时压住。如果数据里存在明显的周期或非线性趋势再考虑m3。m并不是越大越好。m太高的风险是“过度去趋势”局部窗口内本来属于真实波动的成分也会被多项式当趋势拟合掉导致F²偏小最终h(q)被人为抬高多重分形信号被削弱。我在测试过m2和m5的对比m5时谱宽明显变窄这通常不是真实特性而是去趋势过度。另外要记住一个硬约束每个窗口长度s必须大于m1否则最小二乘拟合欠定。实际代码里至少取s m2否则会收到秩亏警告。3.3 时间尺度s的上下限与分段数s的下限通常取m2或者直接定成10。s的上限我一般取N/6左右至少保证最小的窗口段数不低于4段。如果s太接近N/2分段次数太少F_q(s)的统计量会严重不稳。s的取值建议在对数空间均匀分布比如从s_min到s_max取50个点这样log-log图上的点间距均匀回归结果也更合理。对照我在不同数据上的经验可以给一个参考表数据长度Ns_mins_max建议段数5001080双向分段后约12段200016300双向分段后约13段500016800双向分段后约12段10000201500双向分段后约13段注意s_max太大时即使双向分段段数也会掉到个位数这时候的F_q(s)受个别段影响极大不适合做回归。数据长度不够就不要强求分析大尺度行为。4. 从零实现一套可复用的MF-DFA写代码前先理清边界4.1 完整实现Python numpy以下代码不依赖任何第三方高级库只有numpy和matplotlib。我刻意把核心逻辑写得直白方便你改造成自己的版本。import numpy as np def mfdfa(x, scalesNone, q_listNone, m2): 计算多重分形去趋势波动分析。 参数 ---- x : array_like 原始时间序列一维数组。 scales : array_like 时间尺度 s 的数组。 q_list : array_like q 阶矩的数组建议对称区间。 m : int 去趋势多项式阶数一般取 2 或 3。 返回 ---- result : dict 包含 hq, tau, alpha, f_alpha, F_q_values x np.asarray(x, dtypefloat) N len(x) y np.cumsum(x - np.mean(x)) if scales is None: s_min max(10, m 2) s_max max(s_min 1, N // 6) scales np.unique(np.rint(np.logspace( np.log10(s_min), np.log10(s_max), 50)).astype(int)) if q_list is None: q_list np.arange(-5, 5.1, 0.5) F_q np.zeros((len(q_list), len(scales))) for i, s in enumerate(scales): if s m 2: continue n_seg N // s if n_seg 2: continue # 正向切段 segments_head y[:n_seg * s].reshape(n_seg, s) # 反向切段利用尾部数据 segments_tail y[-(n_seg * s):].reshape(n_seg, s) segs np.vstack([segments_head, segments_tail]) n_segs_total len(segs) x_local np.arange(s, dtypefloat) F2 np.zeros(n_segs_total) for v, seg in enumerate(segs): # 多项式最小二乘拟合 coeffs np.polyfit(x_local, seg, degm) trend np.polyval(coeffs, x_local) F2[v] np.mean((seg - trend) ** 2) # 对每个 q 计算 F_q(s) for j, q in enumerate(q_list): if q 0: val np.exp(np.mean(0.5 * np.log(F2))) else: val np.mean(F2 ** (q / 2.0)) ** (1.0 / q) F_q[j, i] val # 对有效尺度做线性拟合 valid (scales m 2) (F_q[0, :] 0) log_s np.log10(scales[valid]) hq np.zeros(len(q_list)) for j in range(len(q_list)): log_F np.log10(F_q[j, valid]) if len(log_F) 2: hq[j] np.nan continue coeff np.polyfit(log_s, log_F, 1) hq[j] coeff[0] # 质量指数与多重分形谱 tau q_list * hq - 1.0 alpha np.gradient(tau, q_list) f_alpha q_list * alpha - tau return { scales: scales, q_list: q_list, F_q: F_q, hq: hq, tau: tau, alpha: alpha, f_alpha: f_alpha, }4.2 用模拟多重分形序列验证算法写完之后必须验证不能直接把结果拿出去用。一个简单有效的验证方法是生成一个已知谱宽的多重分形测度序列。经典p-model迭代地把每个权重乘上两个因子一个因子为p另一个为1-p。def simulate_multifractal(n_points4096, p0.3, depth12, seed42): rng np.random.default_rng(seed) arr [1.0] for _ in range(depth): new_arr [] for val in arr: new_arr.append(val * (1 - p)) new_arr.append(val * p) arr new_arr series np.array(arr[:n_points]) # 打乱顺序去掉空间相关性只保留测度分布本身 rng.shuffle(series) return series data simulate_multifractal() res mfdfa(data, q_listnp.arange(-5, 5.1, 0.5), m2)p0.3时理论上谱宽 Δα 应该在1.2左右α范围约0.515到1.737。跑完代码后看hq应该能看到h(q)随q的增大而明显下降。如果结果看到近似的下降趋势就说明算法实现是可靠的。4.3 数值边界与常见改写点有一些数值细节新手往往要撞几次墙才明白。第一个是F²为0的情况。真实数据常常出现一段内残差全部为0比如大量重复的常数段这时负q计算会出现除零或无穷大。我的处理方式是对F2加上一个极小的epsilon比如1e-12再参与幂平均。这不会影响大波动段的量级但能保住负q部分的稳定性。第二个是空段和无效s的处理。代码里已经检查了n_seg 2和s m2但如果是短序列scales里会有不少无效点。拟合时要用valid掩码把无效点排除掉不能直接对全数组做回归。第三个是尺度数量的选择。50个尺度点是我较常用的设置。如果你发现log-log图形状很乱先把尺度点增加到100让回归更稳。反之如果数据很长也不要一味增加点计算量会线性上升。5. 结果解读h(q)和多重分形谱怎么变成结论5.1 h(q)的三种形态判断拿到h(q)曲线后先看形态再下结论。常见有三种情况第一h(q)在所有q上几乎水平且h(2)约等于0.5。这对应白噪声性质的序列不存在长程相关也不存在多重分形特征。看到这个结果后续就不要再做复杂建模了序列的波动结构基本是独立的。第二h(q)水平但h(2)明显大于0.5。这是单分形长程相关序列比如一些低频气温记录或某些人工生成的分形信号。它有记忆性但记忆结构在大小波动之间是均匀的。第三h(q)随着q增大而单调下降。这是多重分形的典型特征。注意下降幅度才有意义h(5)比h(-5)小多少直接决定谱宽。如果下降幅度小于0.1可以说多重分形性较弱解读时不要夸大。我习惯同时报告h(2)和谱宽这两个指标共同描述序列特性。h(2)回答“整体有没有记忆”谱宽回答“记忆在不同波动幅度下差异有多大”。只报一个都不完整。5.2 谱宽、谱形与对称性分析多重分形谱f(α)的形状是对称性的能提供更细的信息。谱宽 Δα α_max - α_min 是最常用的稳健指标。谱越宽序列的奇异性结构越复杂。实际业务场景里比如金融数据谱宽变大通常意味着风险结构更复杂、极端波动更不可忽视。谱的左右不对称同样值得关注。左翼对应正q端主要由大波动段贡献右翼对应负q端由小波动段贡献。如果左翼延伸得更长说明大波动区域的局部奇异性分布更丰富如果右翼更长说明小波动区域的标度行为更复杂。这个判断可以帮助定位序列的多重分形性主要是被极端事件驱动的还是被日常小幅波动堆积起来的。如果想进一步判断多重分形来源是长程相关还是厚尾分布可以用洗牌对照。把原始序列顺序打乱后再做一次MF-DFA。如果打乱后谱宽显著收窄说明多重分形主要来自长程相关结构如果打乱后谱宽几乎不变说明厚尾概率分布本身就在制造多重分形外观。这个检验简单有效我每次都会跑。5.3 一个完整的结论示例假设你分析一段收益率序列得到 h(2)0.55Δα0.35洗牌后Δα缩小到0.15。可以给出的结论是该序列存在弱正长程相关且具有一定程度的多重分形特征主要来源是长程相关结构同时伴随厚尾分布的叠加。这种描述比“Hurst指数0.58”要立体得多也更容易解释后续模型选择。如果谱宽在洗牌后几乎不变那就要小心了所谓多重分形可能只是极端值造成的伪影真正的动力学仍然可能是近随机的。别急着写“发现了复杂结构”先把检验跑完。6. 实战中的坑我把常见问题按优先级列出来6.1 数据长度不够时为什么负q部分会先崩短序列对负q极其不友好。负q放大的是小波动段而小波动段往往包含很多数值接近0的窗口这些窗口的F²在取负幂后会变成天文数字直接把F_q(s)带偏。如果序列长度不超过1000建议把q的最小值收到-3别贸然用-5。如果长度不足500老实说我不太建议做完整的多重分形谱最多报告h(2)和短尺度标度行为。还有一点分段数越少负q端的波动越大。s接近s_max时正向加反向一般只有8到16段这时的负q统计量非常不可靠。我一般在拟合时给大尺度点也做稳定性检查比如看看去掉最大的三个尺度后斜率是否稳定如果变化很大说明回归结果受尾部影响太强需要减小s_max。6.2 log-log曲线出现拐点不要全局硬拟合另一个高频问题是F_q(s)的log-log图不是直线而是有明显拐点的折线。这在包含周期成分的生理信号和气候序列里特别常见。整个s范围内只有一个斜率这种假设本来就强。遇到拐点全局拟合的h(q)只是两段斜率的某种加权平均会掩盖短尺度与长尺度各自的行为差异。处理方法先画几个有代表性的q值比如q-3, 0, 3, 5的log-log曲线人工确认拐点位置。如果存在两个明显线性区间分别对短尺度段和长尺度段做回归得到两组h(q)。然后你再判断多重分形性是短尺度的属性还是长尺度的属性。这个区别在业务上往往比一个笼统的谱宽更有价值。例如有些金融收益率序列在短尺度上h(q)下降明显长尺度上反而接近0.5这说明短期波动具有很强的聚集性长期则趋于随机。只用全局拟合会把短期效应平均掉。6.3 趋势与周期污染如何避免伪多重分形数据预处理不到位最容易得到“伪多重分形”结论。最典型的是强线性趋势。一个带明显上升趋势的序列累积离差后主导项是趋势本身MF-DFA会把趋势当成标度行为来拟合。m1或m2可以去掉线性趋势但遇到弯曲趋势和季节周期就吃力。周期成分会在特定尺度上制造F_q(s)的凸起。比如气温数据带年周期s对应365附近时波动函数明显上翘。如果不处理谱宽会被人工拉大。我的建议是先做季节差分或周期滤波把已知周期去掉再做MF-DFA。但注意别把波动本身的特性也滤掉处理前后最好对比一次谱宽确认只是周期贡献被移除而不是整体标度被破坏。最后提醒一个容易被忽略的点跑MF-DFA之前一定要先看一眼原始序列的时序图和平稳性。先把数据摸清楚再上工具箱才是做多重分形分析的正道。