经典回归理论
预计学习时间:3-4 小时
难度:⭐⭐⭐
核心问题:如何正确地用线性模型从数据中提取可信的关系?你的回归结果真的可信吗?
概述
经典回归理论是计量经济学的地基。量化中几乎所有的统计推断——因子收益估计、策略归因、风险模型——最终都建立在回归分析之上。
但大多数人只知道”跑回归”,不知道:
- 回归结果在什么条件下才是可信的
- 结果不可信时怎么诊断和修正
- 更糟糕的情况——因果方向搞反了——怎么处理
本文件覆盖回归分析从基础到进阶的完整链条。
一、OLS 最小二乘法
1.1 白话直觉
假设你有一组数据:每个股票的”账面市值比”和它”下个月的收益率”。你怀疑两者有线性关系:
OLS 要做的事,就是找一条最”贴合”所有数据点的线。
什么叫”最贴合”?直觉上,就是让所有数据点到这条线的距离总和最小。具体来说,是让残差(实际值减去预测值)的平方和最小。
为什么用平方?因为直接加距离会正负抵消(线上方为正、下方为负),而平方保证所有偏差都被”算上”。
1.2 数学推导
设我们有 个观测值, 个解释变量。模型可以写成矩阵形式:
其中:
- 是 的被解释变量(如收益率)
- 是 的解释变量矩阵(包含常数项列)
- 是 的系数向量(我们要估计的)
- 是 的误差项
目标函数——最小化残差平方和:
展开:
对 求导并令其为零:
得到正规方程(Normal Equation):
假设 可逆,解为:
这就是 OLS 估计量的闭式解。
1.3 Python 手写 OLS + 与 statsmodels 对比
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
np.random.seed(42)
# ============================================================
# 模拟数据:账面市值比 vs 收益率
# ============================================================
n = 200 # 200 只股票
# 账面市值比(BM)
bm = np.random.normal(1.0, 0.5, n)
# 真实关系:收益率 = 0.005 + 0.02 * BM + 噪声
alpha_true = 0.005
beta_true = 0.02
y = alpha_true + beta_true * bm + np.random.normal(0, 0.05, n)
# ============================================================
# 方法 1:手写 OLS(矩阵求解)
# ============================================================
# 构造设计矩阵 X = [1, BM](第一列是常数项)
X = np.column_stack([np.ones(n), bm])
# beta_hat = (X'X)^(-1) X'y
XtX = X.T @ X
Xty = X.T @ y
beta_hat = np.linalg.inv(XtX) @ Xty
# 计算残差和拟合值
y_hat = X @ beta_hat
residuals = y - y_hat
# 计算统计量
n_obs = n
k_params = 2
df = n_obs - k_params # 自由度
MSE = np.sum(residuals**2) / df # 均方误差
# 系数方差-协方差矩阵
var_beta = MSE * np.linalg.inv(XtX)
se_beta = np.sqrt(np.diag(var_beta)) # 标准误
t_stats = beta_hat / se_beta # t 统计量
# R² 和 调整 R²
SS_total = np.sum((y - np.mean(y))**2)
SS_residual = np.sum(residuals**2)
R2 = 1 - SS_residual / SS_total
R2_adj = 1 - (1 - R2) * (n_obs - 1) / df
print("=" * 50)
print("手写 OLS 结果")
print("=" * 50)
print(f"截距 alpha_hat = {beta_hat[0]:.6f} (真实值: {alpha_true})")
print(f"斜率 beta_hat = {beta_hat[1]:.6f} (真实值: {beta_true})")
print(f"标准误: {se_beta}")
print(f"t 统计量: {t_stats}")
print(f"R² = {R2:.4f}, 调整 R² = {R2_adj:.4f}")
# ============================================================
# 方法 2:使用 statsmodels(标准做法)
# ============================================================
import statsmodels.api as sm
X_sm = sm.add_constant(bm) # 自动添加常数项
model = sm.OLS(y, X_sm).fit()
print("\n" + "=" * 50)
print("statsmodels OLS 结果")
print("=" * 50)
print(model.summary())输出中,你会看到手写结果和 statsmodels 完全一致。理解手写过程后,实际工作中用 statsmodels 就够了,但它帮你做了很多隐含的事情(标准误、t 值、p 值等),你需要知道这些数字是怎么来的。
二、Gauss-Markov 定理:为什么 OLS 是”最好的”
2.1 白话解释
Gauss-Markov 定理说的是:在所有线性、无偏的估计量中,OLS 的方差最小。
“最小方差”意味着什么?意味着如果你重复抽样很多次,OLS 估计值波动最小、最稳定。
2.2 定理的前提假设(BLUE 条件)
要让 Gauss-Markov 定理成立,OLS 需要满足以下假设:
| 假设 | 数学表达 | 白话解释 | 违反后果 |
|---|---|---|---|
| 线性 | 因变量是参数的线性函数 | 模型设定错误 | |
| 严格外生性 | $E(\varepsilon | X) = 0$ | 误差项与解释变量不相关 |
| 无完全共线性 | 解释变量之间不能完全”重合” | 无法估计 | |
| 球形扰动 | $Var(\varepsilon | X) = \sigma^2 I$ | 误差方差恒定、无自相关 |
前三个假设比较直观,第四个”球形扰动”是实践中最容易违反的,也是后面诊断部分要解决的核心问题。
2.3 BLUE 的含义
Best(最优)——方差最小 Linear(线性)——估计量是 y 的线性函数 Unbiased(无偏)——多次估计的期望等于真实值 Estimator(估计量)
注意:BLUE 只在”线性无偏”这个类别里是最好的。如果你允许有偏估计,可能存在方差更小的估计量(如岭回归)。但在线性无偏的框架下,OLS 是王者。
三、假设检验:模型结果到底能不能信?
跑完回归后,你会得到一堆数字。怎么判断这些数字有意义还是巧合?
3.1 t 检验:单个系数是否显著不为零
问题:你估计出 ,但这个数值是真实的效应,还是数据噪音?
逻辑:
- 假设 (零假设 H0)
- 在零假设下, 的分布应该以 0 为中心
- 计算 t 统计量:
- 如果 足够大,就拒绝 H0
判定标准:
- :在 5% 显著性水平下显著
- :在 1% 显著性水平下显著
- p 值 < 0.05:拒绝零假设,认为系数显著不为零
3.2 F 检验:整个模型是否有解释力
问题:即使某个系数显著,整个模型是不是真的有用?
逻辑:比较”有模型”和”只有截距”的拟合差异。
如果 F 统计量远大于 1(通常 p 值 < 0.05),说明模型整体有解释力。
3.3 R²:模型解释了多少方差
- R² = 0.8:模型解释了 80% 的方差
- R² = 0.05:模型只解释了 5% 的方差
量化中的注意事项:
- R² 太高反而可疑——可能有过拟合
- 截面因子的 R² 通常很低(2%-5%),但只要有统计显著性,组合后也能获利
- 用调整 R² 比较不同变量数量的模型更公平
3.4 Python 代码:完整假设检验
import numpy as np
import statsmodels.api as sm
np.random.seed(42)
n = 500
# 模拟三因子模型
market = np.random.normal(0.001, 0.02, n)
size = np.random.normal(0.0, 0.01, n)
value = np.random.normal(0.0, 0.01, n)
# 真实关系:市场因子有解释力,size 没有
y = 0.001 + 1.2 * market + 0.3 * size + 0.5 * value + np.random.normal(0, 0.015, n)
X = np.column_stack([market, size, value])
X = sm.add_constant(X)
model = sm.OLS(y, X).fit()
print("=" * 60)
print("假设检验详解")
print("=" * 60)
# t 检验结果
print("\n--- t 检验(单个系数显著性)---")
for name, coef, tval, pval in zip(
['截距', '市场', '规模', '价值'],
model.params,
model.tvalues,
model.pvalues
):
sig = "***" if pval < 0.001 else "**" if pval < 0.01 else "*" if pval < 0.05 else ""
print(f" {name}: coef={coef:.4f}, t={tval:.2f}, p={pval:.4f} {sig}")
# F 检验结果
print(f"\n--- F 检验(模型整体显著性)---")
print(f" F 统计量 = {model.fvalue:.2f}")
print(f" F 检验 p 值 = {model.f_pvalue:.2e}")
print(f" 结论: {'模型整体显著' if model.f_pvalue < 0.05 else '模型整体不显著'}")
# R²
print(f"\n--- R² ---")
print(f" R² = {model.rsquared:.4f}")
print(f" 调整 R² = {model.rsquared_adj:.4f}")
print(f" 模型解释了 {model.rsquared*100:.1f}% 的收益方差")四、OLS 假设与诊断
Gauss-Markov 定理要求”球形扰动”假设。现实金融数据几乎一定会违反它。这里逐一讲解如何诊断和修正。
4.1 异方差:误差的大小不是均匀的
白话解释
正常情况下,OLS 假设误差的方差对所有观测值都一样。但金融数据中:
- 大盘股的收益波动小,小盘股的收益波动大
- 市场平静时误差小,危机时误差大
- 这就是异方差
为什么是问题?
异方差不改变系数估计的一致性(估计值方向没问题),但会让标准误有偏。标准误有偏 → t 检验不可靠 → 你可能把不显著的系数当显著的。
White 检验
检验是否存在异方差的标准方法。
import numpy as np
import statsmodels.api as sm
from statsmodels.stats.diagnostic import het_white
np.random.seed(42)
n = 300
x = np.random.normal(0, 1, n)
# 误差方差随 x 增大而增大 → 异方差
heteroskedastic_error = np.random.normal(0, 1, n) * (1 + 0.5 * np.abs(x))
y = 1 + 2 * x + heteroskedastic_error
X = sm.add_constant(x)
model = sm.OLS(y, X).fit()
# White 检验
white_test = het_white(model.resid, model.model.exog)
labels = ['LM 统计量', 'LM p 值', 'F 统计量', 'F p 值']
print("White 异方差检验:")
for name, val in zip(labels, white_test):
print(f" {name}: {val:.4f}")
is_heteroskedastic = white_test[1] < 0.05
print(f"\n结论: {'存在异方差' if is_heteroskedastic else '不存在异方差'}")WLS 加权最小二乘修正
给误差大的观测值较小的权重,误差小的观测值较大的权重。
# WLS:用 1/|x| 作为权重
weights = 1.0 / (1 + 0.5 * np.abs(x))
model_wls = sm.WLS(y, X, weights=weights).fit()
print("\nOLS vs WLS 对比:")
print(f" OLS 标准误: {model.bse}")
print(f" WLS 标准误: {model_wls.bse}")4.2 自相关:误差项之间存在相关性
白话解释
在时间序列数据中,如果今天的误差和昨天的误差有关系,就是自相关。
比如:市场情绪的持续性会导致正自相关——好消息之后往往还有好消息。
为什么是问题?
和异方差一样,自相关不会影响系数方向,但会让标准误被低估。标准误被低估 → t 值虚高 → 你会过度相信结果。
Durbin-Watson 检验
from statsmodels.stats.stattools import durbin_watson
np.random.seed(42)
n = 200
x = np.random.normal(0, 1, n)
# 构造自相关误差:误差 = 0.6 * 上期误差 + 新噪音
errors = np.zeros(n)
for t in range(1, n):
errors[t] = 0.6 * errors[t-1] + np.random.normal(0, 1)
y = 1 + 2 * x + errors
X = sm.add_constant(x)
model = sm.OLS(y, X).fit()
# Durbin-Watson 检验
dw = durbin_watson(model.resid)
print(f"Durbin-Watson 统计量: {dw:.4f}")
print(f" DW ≈ 2: 无自相关")
print(f" DW < 2: 正自相关(常见于金融时间序列)")
print(f" DW > 2: 负自相关")
print(f" 当前结果: {'正自相关' if dw < 2 else '负自相关' if dw > 2 else '无自相关'}")DW 值的判断规则:取 和 作为临界值(查 DW 表):
- :正自相关
- :无自相关
- :负自相关
4.3 多重共线性:两个变量太像了
白话解释
当解释变量之间高度相关时,模型无法区分哪个变量在起作用。
比如你同时放入”市值”和”流通市值”,这两个变量高度相关,回归结果会很不稳定——加一个数据点,系数可能完全翻转。
VIF 诊断
VIF(Variance Inflation Factor)衡量每个变量被其他变量”解释”的程度。
其中 是用其他解释变量回归第 个变量的 R²。
from statsmodels.stats.outliers_influence import variance_inflation_factor
np.random.seed(42)
n = 200
# 制造共线性:x1 和 x2 高度相关
x1 = np.random.normal(0, 1, n)
x2 = x1 + np.random.normal(0, 0.1, n) # 和 x1 几乎一样
x3 = np.random.normal(0, 1, n)
y = 1 + 2*x1 + 0.5*x3 + np.random.normal(0, 1, n)
X = np.column_stack([x1, x2, x3])
X_with_const = sm.add_constant(X)
# 计算 VIF
print("VIF 诊断(VIF > 10 表示严重共线性):")
for i, name in enumerate(['x1', 'x2', 'x3']):
vif = variance_inflation_factor(X_with_const, i+1) # +1 跳过常数项
warning = " ⚠️ 严重共线性!" if vif > 10 else ""
print(f" {name}: VIF = {vif:.1f}{warning}")处理方法:
- 删除共线性变量中不太重要的那个
- 使用 PCA 降维
- 使用岭回归(有偏但稳定)
4.4 HAC 稳健标准误(Newey-West)
为什么重要?
前面说的异方差和自相关,分别有各自的检验和修正方法。但在实际研究中,我们常常两种问题同时存在,逐个修正很麻烦。
Newey-West HAC 标准误是一次性解决异方差和自相关的方法。它是金融计量中最常用的标准误修正方法。
HAC = Heteroskedasticity-Autocorrelation Consistent(异方差-自相关一致)
import numpy as np
import statsmodels.api as sm
np.random.seed(42)
n = 300
x = np.random.normal(0, 1, n)
# 同时有异方差和自相关的误差
errors = np.zeros(n)
for t in range(1, n):
errors[t] = 0.5 * errors[t-1] + np.random.normal(0, 1 + 0.3 * abs(x[t]))
y = 1 + 2 * x + errors
X = sm.add_constant(x)
model_ols = sm.OLS(y, X).fit()
# 使用 Newey-West HAC 标准误重新拟合
model_hac = sm.OLS(y, X).fit(cov_type='HAC', cov_kwds={'maxlags': int(n**(1/4))})
print("OLS 标准误 vs HAC 稳健标准误:")
print(f"{'系数':>8} {'OLS SE':>10} {'HAC SE':>10} {'OLS t':>8} {'HAC t':>8}")
for i, name in enumerate(['截距', '斜率']):
print(f"{name:>8} {model_ols.bse[i]:10.4f} {model_hac.bse[i]:10.4f} "
f"{model_ols.tvalues[i]:8.2f} {model_hac.tvalues[i]:8.2f}")
print(f"\n对比结论:")
print(f" HAC 标准误通常 >= OLS 标准误")
print(f" 如果差距很大,说明 OLS 的 t 值虚高了")量化实战建议:
- 分析时间序列数据时,默认使用 HAC 标准误,而不是普通 OLS 标准误
- 这是最”安全”的做法,即使数据满足 OLS 假设,HAC 也仍然有效
五、工具变量(IV):因果方向搞反了怎么办
5.1 内生性问题
OLS 的核心假设之一是”严格外生性”:。如果违反了,就是内生性。
白话解释:你用 OLS 估计”广告投入 → 销售额”的关系,但可能是”销售额好 → 公司有钱投广告”。因果关系搞反了,OLS 估计就有偏。
量化中的内生性例子:
- 用公司市值预测收益:市值大的公司可能有其他系统性差异
- 用利率预测股市:利率和股市可能同时被宏观因素驱动
5.2 工具变量是什么
白话类比:你想知道”上补习班 → 成绩”的因果效果,但上补习班的学生本身可能更努力(内生性)。你需要找一个”只影响是否上补习班,但不直接影响成绩”的变量——比如”家附近有没有补习班的分校”。这个变量就是工具变量。
数学定义:工具变量 需要满足:
- 相关性: —— 工具变量和内生变量相关
- 外生性: —— 工具变量和误差项不相关
5.3 两阶段最小二乘(2SLS)
第一阶段:用工具变量 解释内生变量
得到 的拟合值 。
第二阶段:用 替代原来的 做 OLS
import numpy as np
import statsmodels.api as sm
from linearmodels.iv import IV2SLS
np.random.seed(42)
n = 500
# 工具变量 Z(外生)
Z = np.random.normal(0, 1, n)
# 误差项 v(影响 X 但不影响 y 的部分)
v = np.random.normal(0, 1, n)
# 内生变量 X(被 Z 和 v 共同决定)
X = 2 * Z + 1.5 * v + np.random.normal(0, 0.5, n)
# 误差项 u(和 v 相关 → 内生性!)
u = 0.8 * v + np.random.normal(0, 1, n)
# 被解释变量 y
beta_true = 1.0
y = 1 + beta_true * X + u
# ============================================================
# 方法 1:直接 OLS(有偏)
# ============================================================
X_ols = sm.add_constant(X)
ols_result = sm.OLS(y, X_ols).fit()
# ============================================================
# 方法 2:手写 2SLS
# ============================================================
# 第一阶段:X = gamma0 + gamma1 * Z
Z_stage1 = sm.add_constant(Z)
stage1 = sm.OLS(X, Z_stage1).fit()
X_hat = stage1.fittedvalues # X 的拟合值
# 第二阶段:y = alpha + beta * X_hat
X_hat_stage2 = sm.add_constant(X_hat)
stage2 = sm.OLS(y, X_hat_stage2).fit()
# ============================================================
# 方法 3:使用 linearmodels 的 IV2SLS
# ============================================================
# pip install linearmodels
import pandas as pd
df = pd.DataFrame({'y': y, 'X': X, 'Z': Z})
iv_result = IV2SLS(df['y'], sm.add_constant(df[['X']]), df[['Z']]).fit(cov_type='robust')
print("=" * 60)
print(f"真实 beta = {beta_true}")
print("=" * 60)
print(f" OLS 估计: beta = {ols_result.params[1]:.4f} (有偏!)")
print(f" 手写 2SLS: beta = {stage2.params[1]:.4f}")
print(f" IV2SLS 估计: beta = {iv_result.params['X']:.4f}")
print(f"\n结论: OLS 严重有偏,IV/2SLS 接近真实值")5.4 弱工具变量问题
如果工具变量 和内生变量 的相关性太弱,2SLS 的表现可能比 OLS 还差。
判断方法:第一阶段的 F 统计量。
- F > 10:工具变量足够强(经验法则)
- F < 10:弱工具变量,2SLS 可能有严重偏误
# 第一阶段 F 统计量
print(f"\n第一阶段诊断:")
print(f" 第一阶段 F = {stage1.fvalue:.2f}")
print(f" {'工具变量足够强' if stage1.fvalue > 10 else '⚠️ 弱工具变量!'}")六、虚假回归:两个独立随机游走也可能”相关”
6.1 什么是虚假回归
这是量化中最容易犯的错误之一。
假设你取两个完全独立的随机游走序列(比如抛硬币生成的两个序列),对它们做回归,你很可能得到”显著”的 R² 和 t 值。
但这两个序列根本没有任何关系——你的回归结果完全是假的。
为什么会这样? 因为随机游走是非平稳的。在非平稳序列之间做回归,即使它们完全独立,也会因为”共同趋势”而表现出虚假的相关性。
6.2 模拟演示
import numpy as np
import statsmodels.api as sm
import matplotlib.pyplot as plt
np.random.seed(42)
n = 500
# 生成两个完全独立的随机游走
rw1 = np.cumsum(np.random.normal(0, 1, n))
rw2 = np.cumsum(np.random.normal(0, 1, n))
# 对它们做回归
X = sm.add_constant(rw1)
model = sm.OLS(rw2, X).fit()
print("虚假回归演示(两个完全独立的随机游走):")
print(f" R² = {model.rsquared:.4f}")
print(f" 斜率 t 值 = {model.tvalues[1]:.2f}")
print(f" 斜率 p 值 = {model.pvalues[1]:.4f}")
print(f"\n 即使两个序列完全独立,")
print(f" 你很可能看到 '显著' 的回归结果!")
print(f" 这就是虚假回归(Spurious Regression)")
# 对比:对差分后的平稳序列做回归
drw1 = np.diff(rw1)
drw2 = np.diff(rw2)
X_diff = sm.add_constant(drw1)
model_diff = sm.OLS(drw2, X_diff).fit()
print(f"\n差分后(平稳序列)的回归:")
print(f" R² = {model_diff.rsquared:.4f}")
print(f" 斜率 t 值 = {model_diff.tvalues[1]:.2f}")
print(f" 斜率 p 值 = {model_diff.pvalues[1]:.4f}")
print(f" 差分后结果变得不显著了——这才是真实的")6.3 如何检测和避免
- 先做单位根检验(详见时间序列计量文件)——确认序列是平稳的
- 对非平稳序列先做差分,使其平稳后再回归
- 如果序列不平稳但有协整关系——可以用误差修正模型(ECM)
- 永远不要对两个非平稳序列直接做回归——除非你已经验证了它们有协整关系
6.4 量化中的教训
在量化研究中,你经常会对两个资产的收益率、两个因子、或者价格序列做回归。如果回归结果”太好”(R² 很高、系数非常显著),你反而应该警惕:
- 是不是虚假回归?
- 序列是不是非平稳的?
- 我是否验证了平稳性或协整性?
“太好的结果往往需要更多的怀疑。“
七、完整实战案例:因子收益估计与诊断
把前面的所有知识串联起来,做一个完整的因子收益估计流程。
import numpy as np
import pandas as pd
import statsmodels.api as sm
from statsmodels.stats.diagnostic import het_white
from statsmodels.stats.stattools import durbin_watson
from statsmodels.stats.outliers_influence import variance_inflation_factor
np.random.seed(42)
# ============================================================
# 模拟截面数据:500 只股票,5 个月
# ============================================================
n_stocks = 500
n_months = 60
results_list = []
beta_monthly = []
for m in range(n_months):
# 每月随机生成因子暴露
size = np.random.normal(0, 1, n_stocks)
value = np.random.normal(0, 1, n_stocks)
momentum = np.random.normal(0, 1, n_stocks)
quality = np.random.normal(0, 1, n_stocks)
# 因子收益(每月变化,但有持续性)
if m == 0:
factor_returns = np.array([0.02, 0.015, 0.03, 0.01])
else:
factor_returns = 0.7 * prev_factor_returns + np.array([
np.random.normal(0.02, 0.05),
np.random.normal(0.015, 0.04),
np.random.normal(0.03, 0.06),
np.random.normal(0.01, 0.03)
])
prev_factor_returns = factor_returns.copy()
# 股票收益 = 因子收益 × 因子暴露 + 特质收益
expected_return = (factor_returns[0] * size +
factor_returns[1] * value +
factor_returns[2] * momentum +
factor_returns[3] * quality)
stock_returns = expected_return + np.random.normal(0, 0.1, n_stocks)
# 截面回归
X = np.column_stack([size, value, momentum, quality])
X = sm.add_constant(X)
model = sm.OLS(stock_returns, X).fit()
beta_monthly.append(model.params[1:])
results_list.append({
'month': m,
'R2': model.rsquared,
'size_t': model.tvalues[1],
'value_t': model.tvalues[2],
'mom_t': model.tvalues[3],
'qual_t': model.tvalues[4],
'size_p': model.pvalues[1],
'value_p': model.pvalues[2],
'mom_p': model.pvalues[3],
'qual_p': model.pvalues[4],
})
results_df = pd.DataFrame(results_list)
betas = np.array(beta_monthly)
# ============================================================
# Fama-MacBeth 第二步:对因子收益做时间序列平均
# ============================================================
print("=" * 60)
print("Fama-MacBeth 因子收益估计")
print("=" * 60)
factor_names = ['size', 'value', 'momentum', 'quality']
for i, name in enumerate(factor_names):
monthly_betas = betas[:, i]
mean_beta = np.mean(monthly_betas)
std_beta = np.std(monthly_betas, ddof=1)
t_stat = mean_beta / (std_beta / np.sqrt(n_months))
sig_months = np.sum(np.abs(monthly_betas / std_beta) > 1.96)
print(f" {name:>10}: 平均收益={mean_beta:.4f}, t={t_stat:.2f}, "
f"显著月份={sig_months}/{n_months}")
# ============================================================
# 诊断汇总
# ============================================================
print(f"\n{'='*60}")
print("回归诊断汇总(以最后一个月为例)")
print(f"{'='*60}")
# 使用最后一个月的数据做完整诊断
X_diag = np.column_stack([size, value, momentum, quality])
X_diag = sm.add_constant(X_diag)
model_diag = sm.OLS(stock_returns, X_diag).fit()
# 1. 异方差检验(White)
white = het_white(model_diag.resid, model_diag.model.exog)
print(f"\n1. White 异方差检验: p = {white[1]:.4f} "
f"({'存在异方差' if white[1] < 0.05 else '无异方差'})")
# 2. 自相关检验(DW)
dw = durbin_watson(model_diag.resid)
print(f"2. Durbin-Watson: {dw:.4f} "
f"({'正自相关' if dw < 1.5 else '无自相关' if dw > 1.5 and dw < 2.5 else '负自相关'})")
# 3. VIF
print(f"3. VIF 多重共线性诊断:")
for i, name in enumerate(factor_names):
vif = variance_inflation_factor(X_diag, i+1)
warning = " ⚠️" if vif > 10 else ""
print(f" {name}: VIF = {vif:.1f}{warning}")
# 4. HAC 标准误 vs OLS 标准误
model_hac = sm.OLS(stock_returns, X_diag).fit(
cov_type='HAC', cov_kwds={'maxlags': int(n_stocks**0.25)}
)
print(f"\n4. OLS vs HAC 标准误:")
for i, name in enumerate(['截距'] + factor_names):
diff_pct = (model_hac.bse[i] - model_diag.bse[i]) / model_diag.bse[i] * 100
print(f" {name}: OLS SE={model_diag.bse[i]:.4f}, "
f"HAC SE={model_hac.bse[i]:.4f} (差异 {diff_pct:+.1f}%)")本文件要点回顾
| 概念 | 一句话总结 |
|---|---|
| OLS | 最小化残差平方和,找”最贴合”的线 |
| Gauss-Markov | 在线性无偏估计量中,OLS 方差最小 |
| t 检验 | 单个系数是否显著不为零 |
| F 检验 | 整个模型是否有解释力 |
| 异方差 | 误差方差不均匀 → 标准误有偏 → t 值不可信 |
| 自相关 | 误差项之间相关 → 标准误被低估 |
| 多重共线性 | 解释变量太像 → 系数不稳定 |
| HAC 稳健标准误 | 一次性解决异方差 + 自相关 |
| 工具变量 | 解决内生性(因果方向搞反了) |
| 虚假回归 | 两个独立非平稳序列也可能”显著相关” |
下一站
理解了经典回归后,我们进入时间序列计量——金融数据天然是时间序列,普通回归用在时间序列上会出大问题。