当前位置:首页>python>一文搞懂 DFA:用 Python 揭开金融时间序列的「记忆」之谜

一文搞懂 DFA:用 Python 揭开金融时间序列的「记忆」之谜

  • 2026-10-11 07:39:15
一文搞懂 DFA:用 Python 揭开金融时间序列的「记忆」之谜

用 Python 揭秘均值回归策略:你的收益从何而来?

2026年重磅升级已全面落地!欢迎加入专注财经数据与量化投研的【数据科学实战】知识星球!您将获取持续更新的《财经数据宝典》与《量化投研宝典》,双典协同提供系统化指引;星球内含 500 篇以上独有高质量文章,深度覆盖策略开发、因子分析、风险管理等核心领域,内容基本每日更新;同步推出的「量化因子专题教程」系列(含完整可运行代码与实战案例),系统详解因子构建、回测与优化全流程,并实现日更迭代。我们持续扩充独家内容资源,全方位赋能您的投研效率与专业成长。无论您是量化新手还是资深研究者,这里都是助您少走弯路、事半功倍的理想伙伴,携手共探数据驱动的投资未来!

引言

你有没有想过这样一个问题:今天比特币涨了,明天是更可能继续涨,还是更可能跌回去?

这背后其实藏着一个金融领域的核心命题——时间序列到底有没有「记忆」。传统的统计方法在面对带有趋势、非平稳的真实金融数据时常常力不从心,而去趋势波动分析(Detrended Fluctuation Analysis,简称 DFA)正是为解决这一难题而生的利器。

DFA 被广泛应用于金融、生物、物理等多个领域,它最大的优势在于:即使数据存在趋势和非平稳性,它依然能稳健地刻画出序列内部的长程相关性。本文将带你用 Python 一步步实现 DFA,并以真实的比特币数据为例,亲手算出它的「记忆指数」。


一、DFA 到底在做什么

DFA 的核心目标是识别非平稳时间序列的标度特性(scaling properties),也就是观察数据的波动如何随着时间尺度的变化而变化。

整个算法可以拆解为四个关键步骤:

  1. 1. 对数据做累积求和
  2. 2. 对数据进行去趋势处理
  3. 3. 计算波动函数
  4. 4. 估计 DFA 标度指数 

下面我们逐一拆解。


二、第一步:累积求和

第一步是计算时间序列的累积和,这一步会先把均值从数据中扣除。其数学表达式为:

y(t) = Σ (xᵢ − ⟨x⟩),其中 i 从 1 到 t

这里的  表示整个序列的均值。通过累积求和,原本围绕均值波动的序列被转换成了一条类似「随机游走」轨迹的曲线,便于后续分析。

import numpy as np

def
 cumulative_sum(x):
    # 先减去均值,再做累积求和,得到「轮廓曲线」

    return
 np.cumsum(x - np.mean(x))

三、第二步:分段去趋势并计算局部波动

接下来,我们把累积和序列切分成若干个长度为  的互不重叠的分段,并对每个分段单独做一次线性回归,用拟合出来的直线去除该段的趋势。

对于每一个分段:

  • • 数据点为 X = (x₁, x₂, …, xₙ)
  • • 拟合直线为 Y = slope · t + intercept

去趋势后的残差为:

x(t) = X(t) − Y(t)

然后计算该分段相对于局部趋势的均方根偏差,得到这一段的波动值 :

F(n, i) = sqrt( (1/n) · Σ (xₜ − Yₜ)² )

import numpy as np
from
 numpy.lib.stride_tricks import as_strided

def
 calc_rms(x, scale):
    # 将序列切成 (段数, scale) 的二维视图,每行就是一个分段

    shape = (x.shape[0] // scale, scale)
    X = as_strided(x, shape=shape)

    scale_ax = np.arange(scale)            # 分段内的横坐标 0,1,2,...
    rms = np.zeros(X.shape[0])             # 用于存放每段的均方根偏差

    for
 e, xcut in enumerate(X):
        coeff = np.polyfit(scale_ax, xcut, 1)   # 一阶线性回归,得到趋势
        xfit = np.polyval(coeff, scale_ax)      # 计算拟合直线上的值
        rms[e] = np.sqrt(np.mean((xcut - xfit) ** 2))  # 去趋势残差的 RMS

    return
 rms

小贴士:这里用 as_strided 是为了高效切分数据,但它直接操作内存视图,使用时务必保证 scale 能整除数据长度,否则可能读到越界数据。对新手而言,用普通切片循环更安全。


四、第三步:计算总体波动函数

把同一个尺度  下所有分段的波动值汇总,再取均方根,就得到了该尺度对应的总体波动函数 :

F(n) = sqrt( (1 / (N/n)) · Σ F(n, i)² )

def calculate_fluctuations(y, scales):
    fluct = np.zeros(len(scales))          # 存放每个尺度对应的总体波动
    for
 e, sc in enumerate(scales):
        # 对每个尺度,计算所有分段 RMS 的均方根,得到总体波动

        fluct[e] = np.sqrt(np.mean(calc_rms(y, sc) ** 2))
    return
 fluct

五、第四步:估计 DFA 标度指数

最后一步,我们在双对数坐标下,对波动函数与尺度做线性拟合,拟合直线的斜率就是我们梦寐以求的 DFA 标度指数 :

log F(n) = α · log n + β

def dfa(x, scale_lim=[5, 9], scale_dens=0.25, show=False):
    y = cumulative_sum(x)                  # 第一步:累积求和
    # 以 2 的幂次生成一系列时间尺度(窗口大小)

    scales = (2 ** np.arange(scale_lim[0], scale_lim[1], scale_dens)).astype(int)

    fluct = calculate_fluctuations(y, scales)   # 计算各尺度的波动函数

    # 在双对数坐标下做线性拟合,斜率 coeff[0] 即为标度指数 alpha

    coeff = np.polyfit(np.log2(scales), np.log2(fluct), 1)

    if
 show:
        plt.loglog(scales, fluct, 'bo')
        plt.loglog(scales, 2 ** np.polyval(coeff, np.log2(scales)),
                   'r'
, label=r'$\alpha$ = %0.2f' % coeff[0])
        plt.title('DFA')
        plt.xlabel('log10(time window)')
        plt.ylabel('log10 <F(t)>')
        plt.legend()
        plt.show()

    return
 scales, fluct, coeff[0]

易错点提醒:原始代码中使用了 np.int,但它在较新版本的 NumPy 中已被移除,直接运行会报错。上面的代码已替换为内置的 int,这是实际跑通代码时最容易踩的坑之一。


六、标度指数 α 怎么读

 可以看作是经典 Hurst 指数的推广,它的取值直接告诉我们序列的自相关结构:

  • • α < 0.5:反相关,涨了之后倾向于跌(均值回归)
  • • α = 0.5:无相关,即白噪声
  • • α > 0.5:正相关,趋势倾向于延续(持续性)
  • • α = 1:1/f 噪声,也叫粉红噪声
  • • α > 1:非平稳、无界
  • • α = 1.5:布朗噪声

直觉上理解:因为长度为  的无相关随机游走,其期望位移大致按  增长,所以  恰好对应白噪声;当  落在 0 到 1 之间时,结果对应于分数高斯噪声。


七、完整案例:分析比特币 5 年日线

下面把所有模块拼起来,应用到过去 5 年的比特币日线收益率上:

import numpy as np
import
 matplotlib.pyplot as plt
from
 numpy.lib.stride_tricks import as_strided

def
 cumulative_sum(x):
    return
 np.cumsum(x - np.mean(x))

def
 calc_rms(x, scale):
    shape = (x.shape[0] // scale, scale)
    X = as_strided(x, shape=shape)
    scale_ax = np.arange(scale)
    rms = np.zeros(X.shape[0])
    for
 e, xcut in enumerate(X):
        coeff = np.polyfit(scale_ax, xcut, 1)   # 线性回归求趋势
        xfit = np.polyval(coeff, scale_ax)
        rms[e] = np.sqrt(np.mean((xcut - xfit) ** 2))
    return
 rms

def
 calculate_fluctuations(y, scales):
    fluct = np.zeros(len(scales))
    for
 e, sc in enumerate(scales):
        fluct[e] = np.sqrt(np.mean(calc_rms(y, sc) ** 2))
    return
 fluct

def
 dfa(x, scale_lim=[5, 9], scale_dens=0.25, show=False):
    y = cumulative_sum(x)
    scales = (2 ** np.arange(scale_lim[0], scale_lim[1], scale_dens)).astype(int)
    fluct = calculate_fluctuations(y, scales)
    coeff = np.polyfit(np.log2(scales), np.log2(fluct), 1)
    if
 show:
        plt.loglog(scales, fluct, 'bo')
        plt.loglog(scales, 2 ** np.polyval(coeff, np.log2(scales)),
                   'r'
, label=r'$\alpha$ = %0.2f' % coeff[0])
        plt.title('DFA')
        plt.xlabel('log10(time window)')
        plt.ylabel('log10 <F(t)>')
        plt.legend()
        plt.show()
    return
 scales, fluct, coeff[0]

if
 __name__ == '__main__':
    import
 yfinance as yf

    # 下载比特币近 5 年的日线数据

    data = yf.download('BTC-USD', period='5y')

    # 计算每日收益率:(P_t - P_{t-1}) / P_{t-1}

    r = data['Close'].diff() / data['Close'].shift(1)
    r.dropna(inplace=True)

    # 运行 DFA 并绘图

    scales, fluct, alpha = dfa(r, show=True)
    print
("Scales:", scales)
    print
("Fluctuations:", fluct)
    print
("DFA Exponent: {}".format(alpha))

运行结果:我们得到 。

这个略大于 0.5 的数值意味着比特币的收益率序列表现出持续性(persistent behavior)——也就是说,价格的上涨倾向于被更多的上涨跟随,下跌也倾向于被更多的下跌跟随,趋势具有一定的延续惯性。

延伸思考:0.57 离 0.5 并不算远,说明这种持续性其实相当微弱。在实盘中,单凭一个略大于 0.5 的标度指数就重仓押注趋势是很危险的。DFA 更适合作为研究市场结构、辅助判断策略类型(趋势型 vs 均值回归型)的工具,而非直接的买卖信号。


总结

本文带你完整走过了 DFA 的四个核心步骤:累积求和 → 分段去趋势 → 计算波动函数 → 双对数拟合求标度指数。

核心要点回顾:

  • • DFA 的最大价值在于:即使面对带趋势、非平稳的真实金融数据,它依然能稳健地刻画长程相关性。
  • • 标度指数  是判断序列「记忆」的关键:小于 0.5 是均值回归,等于 0.5 是随机,大于 0.5 是趋势持续。
  • • 在比特币的实例中,,揭示出微弱但确实存在的趋势持续性。

掌握 DFA,你就拥有了一把分析时间序列内在结构的趁手工具。不妨把它套用到你关注的股票、外汇或其他资产上,看看它们各自的「记忆」如何。

参考文章

加入专注于财经数据与量化投研的知识星球【数据科学实战】,获取本文完整研究解析、代码实现细节。

财经数据与量化投研知识社区

2026年全面升级已落地!【数据科学实战】知识星球核心权益如下:

  1. 1. 双典系统赋能:获赠《财经数据宝典》与《量化投研宝典》完整文档,凝练多年实战经验,构建系统化知识框架;
  2. 2. 量化因子日更教程(2026重磅新增):每日更新「量化因子专题教程」,配套完整可运行代码与实战案例,深度拆解因子构建、回测与优化全流程;
  3. 3. 量化文章专题教程库:500+篇星球独有高质量教程式文章,系统覆盖策略开发、因子研究、风险管理等核心领域,内容基本每日更新,并配套精选学习资料与实战参考;
  4. 4. 量化投研实战课程:赠送《AKQuant-入门及实战》《PyBroker-入门及实战》视频课程,手把手教学,快速掌握量化策略开发技能;
  5. 5. 财经数据支持:定期更新国内外财经数据,为策略研发提供精准、可靠的数据基础;
  6. 6. 顶尖学者与行业专家分享:年度邀请学术界博士与业界资深专家开展前沿论文精讲与实战案例分享,不少于4场,直击研究前沿与产业实践;
    专家直连答疑:与核心开发者及领域专家实时互动,高效解决投研实战难题;
  7. 7. 专业社群与专属福利:加入高质量交流社群,获取课程折扣及更多独家资源。

星球已沉淀丰富内容生态——涵盖量化文章专题教程库、因子日更系列、高频数据集、PyBroker实战课程、专家深度分享与实时答疑服务。无论您是初探量化的学习者,还是深耕领域的从业者,这里都是助您少走弯路、高效成长的理想平台。诚邀加入,共探数据驱动的投资未来!

好文推荐

1. 用 Python 打造股票预测系统:Transformer 模型教程(一)

2. 用 Python 打造股票预测系统:Transformer 模型教程(二)

3. 用 Python 打造股票预测系统:Transformer 模型教程(三)

4. 用 Python 打造股票预测系统:Transformer 模型教程(完结)

5. 揭秘隐马尔可夫模型:因子投资的制胜武器

6. YOLO 也能预测股市涨跌?计算机视觉在股票市场预测中的应用

7. 金融 AI 助手:FinGPT 让你轻松掌握市场分析

8. 量化交易秘籍:为什么专业交易员都在用对数收益率?

9. Python 量化投资利器:Ridge、Lasso 和 Elastic Net 回归详解

10. 掌握金融波动率模型:完整 Python 实现指南

好书推荐

最新文章

随机文章