做量化的人几乎都踩过同一个坑:特征用错了差分阶数。
- 直接用价格 喂给模型?价格是 (单位根),均值、方差都随时间漂移,模型会学到一堆「伪相关」——比如价格和自己的滞后项高度相关只是因为它俩都在往上爬。
- 那用收益率 (一阶差分,)?平稳了,可你把记忆也一起差分没了。收益率几乎不携带「价格现在处在历史什么位置」这种长周期信息,很多慢变量信号就这样被洗掉。
有没有中间地带?有——分数阶差分(fractional differentiation),记作 ,其中 不必是整数。Marcos López de Prado 在 Advances in Financial Machine Learning 里把它作为特征工程的核心工具:用 的阶数,把序列从 降到 ,在「足够平稳」和「保留记忆」之间找平衡点。
本文用一段合成的「类资产价格」随机游走(3000 点),亲手走完 fracdiff 的全部流程:权重公式 → ADF 平稳性扫描 → 记忆保留曲线 → 自相关对比,所有图表与数字由文末代码真实生成。
一、分数阶差分的权重公式#
一阶差分是 ,权重为 。分数阶差分把「减去一阶」推广成「减去一个分数阶的导数」,其无限阶 MA 权重递归定义为:
对序列做 就是 。注意两点:
- 时所有权重退化成 ,即原序列;
- 越大,权重衰减越快——也就是说,更大的 把越久远的历史「稀释」得越狠,记忆越短。

看图就很直观: 的权重长长地拖一条尾巴(长记忆), 几乎两步就归零(短记忆)。这尾巴的长度,就是 fracdiff 区别于「直接取收益率」的本质。
二、核心权衡:越平稳,越丢记忆#
我们拿一段随机游走(典型的价格形态:强记忆、非平稳)做实验,对网格 计算两个量:
- 平稳性:对差分后的序列跑 ADF 单位根检验,取 值。值越小越平稳(惯例以 0.05 为阈值)。
- 记忆保留:差分后序列与原序列的线性相关 。值越接近 1,说明越没丢掉原序列的信息。

曲线说得很清楚:
- (原价格):记忆满分(相关 1.000),但 ADF ,非平稳——直接进模型就是坑。
- (收益率):ADF ,彻底平稳,可记忆相关只剩 0.036——几乎把价格信息丢光。
- 中间地带:取 ,序列已经显著平稳(ADF ),记忆相关仍有 0.815。也就是说,你用 40% 的差分,既治好了非平稳,又保住了八成以上的「与原价格的同步性」。
真实数字(本文合成序列,3000 点):原序列 ADF ;收益率 ADF 、与原序列相关 ; 时 ADF 、记忆相关 ;记忆相关随 从 1.000 平滑跌到 0.036。
实践启示:别无脑用收益率。先扫一遍 ,挑一个「ADF 通过 + 记忆保留尽量高」的折中区间(本文大致是 ,记忆从 0.995 退到 0.647),你的特征既平稳又有信息量。
三、自相关对比:fracdiff 把「慢衰减压」成「快衰减」#
平稳性在自相关函数(ACF)上看得更明白。序列越「有记忆」,ACF 衰减越慢。

- 原随机游走:ACF 几乎不衰减(强记忆、非平稳的典型长尾巴)。
- 收益率():ACF 在滞后 1 就几乎归零——确实平稳,但太干净了,什么结构都没留。
- 的分数差分:ACF 比原序列衰减快得多(更接近平稳),却又明显比收益率「厚」——它保留了收益率丢掉的那部分中长周期结构。
这正是 fracdiff 的甜区:比原序列平稳,比收益率有记忆。
四、保记忆的直观证据:叠加图#
光看数字不够,把 的分数差分序列和原价格叠在一起(为同图可视做了缩放):

两条线高度同步——分数差分序列忠实地「追踪」着原价格的形态,却已经脱去了单位根的非平稳外壳。把它作为特征喂给模型,模型拿到的是既平稳、又携带价格位置信息的输入。
五、完整 Python 实现#
下面这段代码自包含(不依赖 statsmodels,ADF 自己实现),可直接复现本文全部图表与数字。
import numpy as np
from scipy import stats
# ---------- 1) 分数阶差分权重 ----------
def fracdiff_weights(d, L=100):
w = np.zeros(L + 1)
w[0] = 1.0
for k in range(1, L + 1):
w[k] = w[k - 1] * (k - 1 - d) / k
return w
def fracdiff(x, d, L=100):
"""对 x 施加 (1-L)^d, 返回与 x 等长序列(前 L 个点为 NaN 预热)"""
w = fracdiff_weights(d, L)
n = len(x)
y = np.full(n, np.nan)
conv = np.convolve(x, w, mode="full") # conv[t] = Σ_k w[k] x[t-k]
for t in range(L, n):
y[t] = conv[t]
return y
# ---------- 2) 自包含 ADF 检验(仅常数项) ----------
def adf_test(y, max_lag=5):
y = np.asarray(y, float)[np.isfinite(y)]
n = len(y)
if n < 40:
return np.nan, np.nan
dy = np.diff(y)
p = max_lag
Js = np.arange(p + 1, len(dy))
T = len(Js)
X = np.zeros((T, 2 + p))
dep = np.zeros(T)
for r, j in enumerate(Js):
dep[r] = dy[j]
X[r, 0] = 1.0
X[r, 1] = y[j] # y_{t-1} (变化前的水平)
for k in range(1, p + 1):
X[r, 1 + k] = dy[j - k] # Δy 滞后项
beta, *_ = np.linalg.lstsq(X, dep, rcond=None)
resid = dep - X @ beta
dof = T - X.shape[1]
sigma2 = resid @ resid / dof
XtX_inv = np.linalg.inv(X.T @ X)
se = np.sqrt(max(sigma2 * XtX_inv[1, 1], 1e-18))
tstat = beta[1] / se
pval = 2.0 * (1.0 - stats.norm.cdf(abs(tstat)))
return tstat, pval
# ---------- 3) 合成类资产价格(随机游走 I(1)) ----------
rng = np.random.default_rng(20240712)
x = np.cumsum(rng.standard_normal(3000)) # 非平稳、强记忆
x = x - x.mean()
# ---------- 4) 扫描 d: 平稳性 vs 记忆 ----------
ds = np.round(np.arange(0.0, 1.01, 0.05), 2)
adf_p, mem = [], []
for d in ds:
y = fracdiff(x, d, L=100)
yv = y[~np.isnan(y)]; xv = x[~np.isnan(y)]
_, pv = adf_test(yv)
mem.append(np.corrcoef(yv, xv)[0, 1])
adf_p.append(pv)
print("d\tADF_p\t记忆相关")
for d, pv, m in zip(ds, adf_p, mem):
print(f"{d:.2f}\t{pv:.4f}\t{m:.3f}")python跑出来的关键读数(与正文一致): 记忆 1.000、ADF ; 记忆 0.815、ADF ; 记忆 0.036。
六、五个必须知道的坑#
- ADF 对分数根功率很低。当真实积分阶数接近 0.5 时,ADF 常常「分不清」 和 。所以本文用 ADF 作为指示性信号,而非金标准——真正该问的是「我的下游模型/检验的平稳性假设是否被满足」,fracdiff 给你的是一根连续可调的旋钮,而不是一个魔法数字。
- 权重要截断。理论上 无限长,实践里取 足够,前 个点会因边界效应设为
NaN(预热),别拿去训练。 - 别迷信 d* 唯一解。本文「最小平稳 」约 0.10,但真正好用的是 这段安全带——在这个区间里你既通过平稳性检验,又保留了六成以上的记忆。
- 真实价格不是纯随机游走。真实资产价格常带趋势、均值回复、结构断点,fracdiff 找到的 可能比本文的合成值更靠近 0.4–0.6,务必对你的数据重新扫描。
- fracdiff 不改尺度量纲的解释。差分后序列单位是「原单位的 阶差分」,做特征时通常还要标准化;它解决的是平稳性与记忆,不解决异常值、幸存者偏差等其它问题。
七、小结#
分数阶差分的精髓就一句话:差分不是只有 0 和 1 两个档位。 从 0 到 1 是一条连续光谱——0 端是「有记忆但不平稳」,1 端是「平稳但失忆」,中间 附近才同时拿到两者的好处。把它当成特征工程里的标准动作:先扫 、看 ADF 与记忆权衡、选安全带里的折中值,再进模型。这比直接丢收益率进去,往往多留住一大截有用的长周期信息。
所有图表均由本文代码在合成数据上真实生成,数字可复现;换成你的真实价量数据,只需替换「第三步」的数据源即可。