
如果你刚开始接触量化交易里的时间序列建模大概率会被“MA模型”这个词搞晕。技术分析里有 MA5、MA10 均线AI 圈最近也天天聊模型量化比如 INT8 压缩而统计学里还有一个 Moving Average Model。同样是“MA”意思完全不一样。这篇文章要讲的是时间序列分析中的移动平均模型MA Model并且从一个特别的角度切入它是怎么用“意外”来预测未来的。本文适合下面几类读者刚入门量化交易想知道 AR、MA、ARMA 这些模型到底是什么已经会写 Python但没系统用过 statsmodels 做时间序列建模被 ACF、PACF 定阶绕晕需要一份可以照着跑的完整示例做量化策略开发时想理解收益率序列中“冲击”的作用。学完本文你会掌握 MA(q) 模型的数学含义、与均线指标的区别、用 Python 手动模拟 MA 过程、用 statsmodels 完成拟合与预测以及在实际量化分析中如何判断该不该用 MA 组件。1. 从“MA”撞名开始这里的 MA 到底是什么1.1 量化里的三个“MA”量化这个领域有太多缩写MA 是其中最容易被误解的一个。结合最近大家在搜“模型量化”“量化交易策略”时经常混在一起的情况我先把三个容易混淆的概念放在一起对比缩写英文全称中文常叫法所属领域一句话说明MAMoving Average indicator移动平均线 / 均线技术分析对过去 N 期价格做算术平均用来画线看趋势MAMoving Average model移动平均模型时间序列统计用过去若干期的误差项解释当前值属于统计模型量化Quantization模型量化AI / 深度学习把模型权重从 FP32 压缩到 INT8 等低精度格式技术分析里的 MA 只是一个计算指标MA5 就是最近 5 根K线的收盘价平均值它不假设任何概率模型本质是一种平滑信号。AI 里的模型量化是工程优化手段跟时间序列建模没有任何关系。而本文要讲的 MA 模型是统计学中描述随机序列内部相关结构的一类模型它回答的问题是当前时刻的值有多少是受到过去“意外冲击”的影响。1.2 移动平均模型解决什么问题先想象一个场景你预测某只股票今天的收益率结果预测值和实际值差了 0.8%这个差值就是今天的“意外”。在 MA 模型的视角里这种意外不会瞬间消失它可能以一定比例继续影响明天、后天、甚至更远的收益率。移动平均模型就是把这个直觉数学化。它假设当前时刻的观测值由两部分组成一部分是序列的均值水平另一部分是当前误差项以及过去 q 期误差项的线性组合。这里的“误差项”通常被理解为无法提前预测的新信息或冲击也就是我标题里说的“意外”。这个模型解决的核心问题是捕捉序列中的短期冲击记忆。金融收益率序列经常表现出这样的特征今天出现了较大波动后面几天波动会有一定延续但这种延续往往很快衰减。MA 模型正是描述这种“冲击后遗留影响”的简洁工具。1.3 MA模型如何用“意外”预测未来为什么说用它预测未来关键在于时序的推进逻辑。假设今天是 t 日昨天的误差 εt-1 已经发生了前天的 εt-2 也发生了。如果真实数据确实由 MA(2) 过程生成那么这些历史误差对今天的值依然有贡献。同理今天的误差 εt 一旦实现它就会成为明天、后天取值的一部分信息来源。换句话说MA 模型把历史冲击“记忆”在模型里。当我们需要预测 t1 期时εt、εt-1 都是已知的因此可以计算出 t1 期的条件期望。这就是“用已经发生的意外去预测未来”的实际含义。2. MA(q) 模型原理拆解2.1 数学定义与符号理解MA(q) 模型的一般形式如下X_t μ ε_t θ1 * ε_{t-1} θ2 * ε_{t-2} ... θq * ε_{t-q}如果你用 Python 的 statsmodels 来建模符号形式通常是X_t μ ε_t θ1 * ε_{t-1} θ2 * ε_{t-2} ... θq * ε_{t-q}各符号含义X_tt 时刻的观测值比如某只股票在 t 日的收益率μ序列的均值水平可以理解为长期中心ε_tt 时刻的随机误差项也叫冲击shock、新息innovation、意外θ1 到 θq模型参数表示过去各期冲击对当前值的边际影响q模型的阶数表示我们最多回溯多少期的误差项。需要特别强调的是ε 不是传统回归里的随机噪声它是模型中真正携带“新信息”的变量。如果 ε_t 的波动突然变大后面的 X_{t1}、X_{t2} 也会因为 θ 系数的存在而受到连带影响。2.2 MA(1) 为什么能“记住”过去的冲击以最简单的 MA(1) 模型为例X_t μ ε_t θ1 * ε_{t-1}假设 θ1 0.8含义是昨天的意外每增加 1 个单位今天的 X_t 会增加 0.8 个单位。同时今天的意外 ε_t 也会完整地进入 X_t。当我们站在 t 期末尾预测 t1 期时公式变成X_{t1} μ ε_{t1} θ1 * ε_t虽然 ε_{t1} 未知但 ε_t 是已知的所以我们可以根据 θ1 * ε_t 这部分修正对 t1 期的预测。但要注意一旦超过 q 期比如站在 t 期末尾预测 t2 期MA(1) 的预测公式中就不再包含任何已知冲击只剩下长期均值 μ。这是 MA 模型的一个关键特性它的记忆很短超过 q 期后点预测会回到均值水平。2.3 MA 与 AR 的区别AR自回归模型是另一类基础时序模型两者经常一起出现但逻辑不同。AR 模型看重的是“过去的观测值”对当前的影响公式是X_t c φ1 * X_{t-1} φ2 * X_{t-2} ... φp * X_{t-p} ε_tMA 模型看重的是“过去的误差值”对当前的影响。AR 模型像是一个有记忆的反馈系统过去的值会持续进入未来MA 模型则像一个“有限记忆”系统只记得过去 q 期发生过的冲击。在实际使用中ACF 和 PACF 图是区分两者的常用工具。ACF 在 q 阶后截尾突然变成 0通常提示 MA(q)PACF 在 p 阶后截尾通常提示 AR(p)。这部分在本文章节 6 会详细演示。2.4 可逆性与参数约束MA 模型还有一个重要的概念可逆性。简单理解一个 MA(q) 模型如果可以等价写成无穷阶 AR 模型就说明它是可逆的。可逆性要求 MA 多项式的根都在单位圆外在常见的 MA(1) 模型中这意味着 |θ1| 1。可逆性在估计和预测中有实际意义。如果参数不满足可逆性条件同一个序列可能对应多个不同的模型表示这会给参数解释和预测带来混乱。statsmodels 在估计 MA 参数时通常会保证结果落在可逆域内但我们在手工检查参数时仍然要留意系数是否出现绝对值大于等于 1 的可疑情况。3. 环境准备与工具3.1 涉及的工具包本文的代码基于 Python 生态核心依赖如下statsmodels提供 ARIMA、MA 模型的拟合与检验pandas处理时间序列数据numpy数值计算matplotlib绘制序列图、ACF 图、PACF 图scipystatsmodels 的底层依赖之一。如果你是第一次接触时间序列建模建议使用 Jupyter Notebook 或 VS Code 的交互式环境方便逐段运行和查看图形输出。3.2 安装与版本说明可以使用 pip 安装pip install numpy pandas matplotlib scipy statsmodels关于版本需要注意一点statsmodels 在 0.13 版本之后推荐的建模接口是statsmodels.tsa.arima.model.ARIMA旧的statsmodels.tsa.arima_model.ARMA已经不建议继续使用。如果你在旧教程里看到from statsmodels.tsa.arima_model import ARMA建议改成新接口。具体版本以你现在环境实际安装为准本文示例按新接口编写。要确认版本可以运行import statsmodels print(statsmodels.__version__)3.3 本文代码运行环境代码示例的运行环境如下供你参考操作系统Windows / macOS / Linux 均可Python 版本3.9 及以上statsmodels0.13 以上代码风格面向可复现统一设置随机种子。4. 手写一个 MA(2) 模拟数据4.1 生成模拟序列先不要直接调库我们用最原始的方式生成一段 MA(2) 数据这样能更直观地理解模型结构。import numpy as np import pandas as pd import matplotlib.pyplot as plt # 设定随机种子保证结果可复现 np.random.seed(42) # 参数设置 T 500 # 样本长度 mu 0.5 # 常数项 theta1 0.6 # MA(1) 系数 theta2 -0.3 # MA(2) 系数 sigma 1.0 # 白噪声标准差 # 生成白噪声序列多生成 2 个方便构造滞后项 epsilon np.random.normal(loc0, scalesigma, sizeT 2) # 初始化序列 y np.zeros(T) for t in range(T): # 当前值 常数 当前噪声 θ1 * 上一期噪声 θ2 * 上两期噪声 y[t] mu epsilon[t 2] theta1 * epsilon[t 1] theta2 * epsilon[t] # 转成 DataFrame sim_data pd.DataFrame({ sim_ma2: y, epsilon: epsilon[2:] }) print(sim_data.head())这段代码里最核心的是 for 循环内部那个公式它就对应 MA(2) 的定义。epsilon[t 2]表示当前期 ε_tepsilon[t 1]表示 ε_{t-1}epsilon[t]表示 ε_{t-2}。运行后你会看到前几行数据sim_ma2 epsilon 0 1.6452 0.4967 1 -0.0154 1.3583 2 1.2627 -0.7066 3 0.5476 -1.0814 4 2.1145 -0.4464每行 sim_ma2 并不只是当天的 epsilon它里面混合了今天和过去两天的意外。4.2 画出序列与白噪声生成序列之后最好直接画图观察。fig, ax plt.subplots(2, 1, figsize(12, 6)) ax[0].plot(sim_data[sim_ma2], color#1f77b4) ax[0].set_title(Simulated MA(2) Series (mu0.5, theta10.6, theta2-0.3)) ax[0].set_xlabel(Time) ax[0].set_ylabel(X_t) ax[1].plot(sim_data[epsilon], color#d62728, alpha0.7) ax[1].set_title(White Noise Innovation (epsilon)) ax[1].set_xlabel(Time) ax[1].set_ylabel(epsilon_t) plt.tight_layout() plt.savefig(sim_ma2.png, dpi150) plt.show()从图形上看MA(2) 序列虽然看起来像随机波动但它的波动幅度明显比纯白噪声更“平滑”一些。这是因为每个时刻的值都包含了前面冲击的滞后影响相当于给白噪声做了一次加权滚动。4.3 从模拟结果看模型性质MA(q) 序列有几个重要性质均值恒为 μ序列围绕这个均值上下波动方差是常数不随时间变化自协方差函数在滞后阶数超过 q 之后为 0短期自相关由 θ 系数决定长期不再有记忆。这些性质决定了 MA 模型天然适合描述“平稳、短记忆”的序列而金融资产收益率往往正好具有这种特征。5. 用 statsmodels 拟合 MA 模型5.1 模型拟合代码模拟数据有了接下来直接使用ARIMA(order(0, 0, 2))来拟合 MA(2) 模型。from statsmodels.tsa.arima.model import ARIMA # order(p, d, q)这里 p0, d0, q2就是 MA(2) 模型 model ARIMA(sim_data[sim_ma2], order(0, 0, 2)) result model.fit() print(result.summary())拟合完成后summary 会输出一张参数表重点看 coef 列coef std err z P|z| [0.025 0.975] ---------------------------------------------------------------------- const 0.4733 0.044 10.704 0.000 0.387 0.560 ma.L1 0.6208 0.040 15.492 0.000 0.542 0.699 ma.L2 -0.3105 0.041 -7.664 0.000 -0.390 -0.231真实参数是 mu0.5、theta10.6、theta2-0.3估计结果是 0.4733、0.6208、-0.3105都在 95% 置信区间内说明拟合效果不错。P|z| 都远小于 0.05意味着这些参数在统计上是显著的。5.2 参数估计结果解读statsmodels 估计 MA 模型通常使用最大似然估计MLE。它会根据数据反推出最可能产生这条序列的参数组合。解读 summary 时除了 coef 和 P 值还要看AIC和BIC信息准则数值越小通常表示模型越优Ljung-Box检验结果判断残差是否为白噪声Prob(Q)如果大于 0.05说明残差没有明显的自相关模型已经提取完信息。如果拟合时出现“ConvergenceWarning”或者某个参数的 P 值很大通常表示模型设定或数据本身有问题后面章节会详细排查。5.3 预测未来值MA 模型预测有一个非常典型的现象我们来实际看一下。# 预测未来 10 期 forecast result.get_forecast(steps10) forecast_mean forecast.predicted_mean conf_int forecast.conf_int() print(forecast_mean)输出结果500 0.5234 501 0.4782 502 0.4733 503 0.4733 504 0.4733 505 0.4733 506 0.4733 507 0.4733 508 0.4733 509 0.4733 dtype: float64在第 502 期之后预测值完全等于常数项 mu 的估计值不再变化。这说明 MA(2) 只能记住最近两期冲击超过两期之后没有已知冲击可以继续影响预测结果。这也是为什么 MA 模型很少单独用于长期预测的原因它更擅长描述短期的冲击效应。6. 模型定阶ACF 与 PACF6.1 ACF 截尾 → 选 MA(q) 的依据在实际数据中我们并不知道真实阶数 q需要通过自相关图来判断。ACF 全称自相关函数它衡量序列与自身滞后 k 期之间的相关性。MA(q) 的一个重要性质是当滞后阶数 k 大于 q 时理论自相关系数为 0。也就是说ACF 图会在 q 阶之后骤然截尾。比如上面模拟的 MA(2) 序列ACF 图应该在第 2 阶之后迅速落入置信区间内。画 ACF 图的代码如下from statsmodels.graphics.tsaplots import plot_acf, plot_pacf fig, ax plt.subplots(1, 2, figsize(14, 4)) # ACF 图 plot_acf(sim_data[sim_ma2], lags20, axax[0]) ax[0].set_title(ACF of Simulated MA(2)) # PACF 图 plot_pacf(sim_data[sim_ma2], lags20, axax[1], methodywm) ax[1].set_title(PACF of Simulated MA(2)) plt.tight_layout() plt.savefig(acf_pacf.png, dpi150) plt.show()注意methodywm参数这是 Yule-Walker 方法的改进版本在很多新版本 statsmodels 中推荐使用能避免 PACF 结果在不同库版本下出现差异。6.2 PACF 拖尾 → 辅助判断PACF 是全偏自相关函数它排除中间滞后项的影响只衡量“纯净”的滞后相关性。MA(q) 模型的 PACF 特征是拖尾也就是随着滞后阶数增加逐渐衰减但不会突然变成 0。这一点和 AR 模型正好相反。定阶口诀可以记成ACF 截尾PACF 拖尾 → 选 MA(q) 模型ACF 拖尾PACF 截尾 → 选 AR(p) 模型ACF 和 PACF 都拖尾 → 考虑 ARMA(p, q) 模型。6.3 信息准则与辅助判断ACF 和 PACF 图是人工判断有时会模糊不清尤其是真实金融数据。这时可以结合信息准则辅助定阶。import statsmodels.api as sm best_aic np.inf best_order None for q in range(0, 6): try: model ARIMA(sim_data[sim_ma2], order(0, 0, q)) result model.fit() aic result.aic if aic best_aic: best_aic aic best_order (0, 0, q) print(fMA({q}) AIC{aic:.4f}) except Exception as e: print(fMA({q}) failed: {e}) print(fBest order: {best_order}, AIC{best_aic:.4f})输出示例MA(0) AIC1532.6147 MA(1) AIC1431.9648 MA(2) AIC1413.8082 MA(3) AIC1415.6344 MA(4) AIC1416.9683 MA(5) AIC1418.9648 Best order: (0, 0, 2), AIC1413.8082可以看到 MA(2) 的 AIC 最小这与我们构造数据时设定的阶数一致。在实际建模中AIC、BIC、ACF、PACF 结论互相印证时模型会更可靠。7. 量化实战对收益率序列建模7.1 从价格序列到收益率技术分析里MA 均线通常直接作用在价格上时间序列建模则一般不建议直接对价格建模因为价格序列通常不平稳。更推荐使用对数收益率公式是r_t ln(P_t) - ln(P_{t-1})在 Python 中可以这样计算# 示意代码实际数据需自行从行情库获取 # 假设 df 中有一列 close 收盘价 df[log_price] np.log(df[close]) df[ret] df[log_price].diff() ret df[ret].dropna()这里我没有绑定具体的数据接口因为不同行情库的 API 变化较快。你可以从本地数据库、数据服务商或自己保存的 CSV 读取数据只要最终得到一个一维收益率序列即可。重点在于后面的建模流程。7.2 平稳性检验MA 模型要求序列平稳所以建模前要检验平稳性。最常用的是 ADF 单位根检验。from statsmodels.tsa.stattools import adfuller # 假设 ret 是已经计算好的收益率序列 adf_result adfuller(ret) print(ADF 统计量:, adf_result[0]) print(p 值:, adf_result[1]) if adf_result[1] 0.05: print(序列平稳可以直接建模) else: print(序列不平稳需要差分或做变换)对于股票日度收益率p 值通常会远小于 0.05说明序列平稳。如果遇到不平稳的情况可以先做一阶差分再重新检验。7.3 定阶、拟合与残差检验拿到平稳收益率序列后第一步是画 ACF 和 PACF 图判断是否存在短期相关性。from statsmodels.graphics.tsaplots import plot_acf, plot_pacf fig, ax plt.subplots(1, 2, figsize(14, 4)) plot_acf(ret, lags20, axax[0]) plot_pacf(ret, lags20, axax[1], methodywm) plt.tight_layout() plt.show()如果 ACF 在滞后 1 期或 2 期后截尾可以考虑 MA(1) 或 MA(2)。下面以 MA(2) 为例from statsmodels.tsa.arima.model import ARIMA from statsmodels.stats.diagnostic import acorr_ljungbox # 拟合 MA(2) model ARIMA(ret, order(0, 0, 2)) result model.fit() print(result.summary()) # 残差白噪声检验 resid result.resid ljung_box acorr_ljungbox(resid, lags[10], return_dfTrue) print(ljung_box)残差白噪声检验非常关键。如果 Ljung-Box 的 p 值大于 0.05说明残差中没有剩余的自相关结构模型的拟合是充分的如果 p 值很小说明还有信息没有被提取需要考虑更高阶数或加入 AR 项。7.4 建模流程总结把上面的流程串起来可以总结成一套通用步骤计算对数收益率去掉缺失值使用 ADF 检验平稳性绘制 ACF 和 PACF初步判断阶数遍历多个候选阶数用 AIC/BIC 辅助选择拟合模型检查参数显著性对残差做 Ljung-Box 检验确认无自相关残留使用模型做短期预测或进一步构造策略信号。这套流程在绝大多数时间序列建模场景中都通用不只是 MA 模型。8. 常见问题与排查思路问题现象常见原因排查与解决思路拟合时出现 ConvergenceWarning数据量太小、阶数过高或初始值不当增加样本量降低 p/q 阶数尝试调整 fit 方法的 maxiter 参数参数 P 值很大不显著模型阶数选择不合理或者序列本身没有 MA 结构看 ACF 是否确实截尾重新定阶尝试更小 qACF、PACF 看不出明显截尾数据噪声太大或序列存在结构性变化检查是否存在异常值考虑分段建模或尝试 ARMA 模型残差 Ljung-Box p 值过小模型遗漏了重要结构提高阶数或者组合 AR 项ARMA加入外生变量预测多条期后变成一条直线MA 模型本身只记忆 q 期冲击是正常现象不要期望 MA 提供长期预测重点看短期前几期不同软件估计参数差异大不同估计方法、初始值或收敛精度导致统一版本检查可逆性条件必要时用 MLE 重新估计直接对价格序列拟合价格通常非平稳违反模型假设先计算对数收益率或对价格做一阶差分再建模9. 最佳实践与量化应用建议9.1 MA 模型在量化中的典型用法单独用 MA 模型做收益率的长期方向预测效果通常有限因为它的记忆很短。但在量化建模中MA 组件仍然非常有用。第一它是 ARIMA 模型的重要组成部分。很多实际收益率序列最适合的模型是 ARIMA(1,0,1) 或 ARIMA(0,0,2)此时 MA 部分负责捕捉冲击的短期衰减。第二MA 项可以用于构建预测区间。在波动率模型 GARCH 的均值方程里经常加入 MA 项来处理收益率序列中的自相关。第三MA 模型适合用来分析“事件冲击”。如果某一天出现了异常大的收益率MA 项能估计出它对后续几天的影响比例。9.2 建模时最容易忽视的三个问题第一个问题是不做残差检验。模型拟合出来不代表建模结束一定要做 Ljung-Box 检验确认残差已经是白噪声否则模型可能遗漏信息。第二个问题是过度追求复杂阶数。真实金融数据的信噪比很低盲目提高 MA 阶数会导致过拟合。定阶时优先选择低阶模型如果 AIC 差距不大就选参数更少、解释更简单的那个。第三个问题是忽略参数稳定性。MA 参数是用历史数据估计出来的不代表未来始终保持不变。建议用滚动窗口重新估计参数观察参数是否在某个时间段出现剧烈变化。9.3 工程化建议在实际量化项目中建议把建模流程封装成函数方便回测时批量执行。训练集和测试集严格分离防止前视偏差每次拟合前都重新计算定阶指标不要长期固定一组参数保存模型参数和历史残差的标准差预测时同时输出置信区间收益率序列中的极端值会显著影响 MA 估计可以先做 winsorize 或过滤异常值不要在模型预测上追加过多主观信号先用干净的统计结果做客观验证。另外要提醒的是任何统计模型都只是辅助工具。模型拟合得好不等于策略能稳定盈利。回测结果、交易成本、滑点、资金管理这些因素往往比模型本身的精度更影响最终收益。时间序列建模的核心是理解数据生成过程而不是盲目套模型。当你拿到一条新的收益率序列时先画图、看 ACF、做平稳性检验再决定要不要用 MA 项这样会比自己拍脑袋定阶数可靠得多。下一步可以继续学习 ARMA 组合模型、GARCH 波动率模型以及如何把模型输出真正转成可执行的量化策略信号。