Matlab实现CAPM估计:从数据清洗到Newey-West标准误
2026/9/14 5:28:35 网站建设 项目流程

简介:这套基于Matlab的资本资产定价模型(CAPM)估计程序,面向金融计量与投资学方向的研究者、学生及量化分析入门者,可直接用于估计贝塔系数、检验资产收益与市场组合的关系,完成经典定价模型的实证分析。压缩包内仅包含1个m文件,文件体积仅1KB,属于轻量级源码,适合快速调用或嵌入自定义框架学习。目前已有35人学习/下载。通过该源码,读者可看到利用Matlab实现CAPM估计的核心计算流程,包括数据读取、回归估计与结果输出等关键步骤,适合作为课程作业、论文复现或自学金融编程的参考模板。代码结构简洁,便于逐行理解资本资产定价的实证逻辑,稍加修改即可适配不同市场数据。

1. 为什么用Matlab估计资本资产定价模型值得把程序写全

金融学教科书里,资本资产定价模型(CAPM)被压缩成一个公式和一个beta。真到自己拿数据跑的时候,Excel得到的斜率和论文里带标准误的估计结果之间,隔着数据处理、频率选择、无风险利率口径和残差异方差四道坎。用Matlab估计CAPM,表面上是写一段回归源码,实际上是把这些坎挨个拆开。下面这份程序骨架能直接落到你的“基于Matlab实现估计资本资产定价程序”项目里:读取Excel、计算超额收益、回归、Newey-West标准误,再到滚动beta。适合金融工程学生、量化研究实习,以及想搞清楚“为什么同样数据beta不一样”的从业者。

2. 资本资产定价模型里beta的统计含义与估计前提

2.1 从CAPM等式到样本回归:alpha为什么不能丢

CAPM说的是期望收益与市场风险溢价的关系:

E(R_i) - R_f = beta * ( E(R_m) - R_f )

这里的beta是资产对市场组合敏感性的度量。实证中拿不到期望值,通常把等式转成时间序列回归:

R_i,t - R_f,t = alpha + beta * (R_m,t - R_f,t) + epsilon_t

多出来的alpha是样本期内的定价偏差,也叫Jensen’s alpha。如果CAPM成立,alpha在统计上应接近0。代码里不能为了省事把截距项去掉,否则当资产实际有稳定超额收益时,这部分会被强行塞进beta里,估计值发生偏移。

2.2 OLS估计beta与协方差法等价,但前提条件更看得见

回归斜率beta的最小二乘解是:

beta = Cov(R_i - R_f, R_m - R_f) / Var(R_m - R_f)

所以直接用cov函数算也行。但回归程序的价值在读完整的结果:alpha、beta的t统计量、R方、残差。这些可以用于检验定价偏差、识别异常波动。OLS有三条基本前提:误差期望为0、同方差、无自相关。金融日收益率的残差往往在牛市和熊市波动大小不同,冲击也会连续几天出现,所以只靠普通标准误容易把beta的统计量看高。

2.3 频率选择影响标准误:日频、周频、月频对照表

数据频率影响的不只是样本量,还影响残差性质。常用的对照口径如下:

频率三年样本量用途主要问题
日频约750快速估计、风控自相关、非同步交易
周频约156学术论文常用舍弃日内信息
月频约36长期CAPM检验样本少,置信区间宽

我一般在项目里先用日频做初版,写报告时改成周频或月频复核。日频样本多,标准误修正能处理部分自相关和异方差;月频样本太少时,就算修正标准误,置信区间依然很宽,beta差异可能不显著。理解了这一点,再去改程序参数就不会盲目追求多数据。

3. 用Matlab准备数据:Excel表格到可回归的超额收益序列

3.1 数据表结构和日期处理

估计CAPM的输入不复杂,但字段放的位置有讲究。我常用的Excel表格是四列:第一列Date,格式是Excel序列日期;第二列是股票收盘价;第三列是市场指数收盘价,比如沪深300或标普500;第四列是无风险利率RF,单位是年化百分数,比如2.5表示2.5%。

用readtable读进来后,日期列通常会被读成double,需要手工转成datetime:

% prepare_data.m % Excel表头:Date, AssetPrice, MarketIndex, RF data = readtable('input_data.xlsx'); % 如果日期列是Excel序列号,转成真实日期 if isnumeric(data.Date) data.Date = datetime(data.Date, 'ConvertFrom', 'excel'); end data = sortrows(data, 'Date'); % 确保日期升序

ConvertFrom','excel'把Excel从1900年起算的序列号转成真实日期。sortrows很重要,因为有些数据源会把日期打乱,不排序直接diff就会得到错误的收益率。如果是csv文件,readtable可能会自动把字符串日期识别成datetime,这时跳过if块里的转换即可,但sortrows仍要做。

3.2 计算日收益率与无风险利率折算

价格序列转日收益率常见两种写法。简单收益率的实现:

assetRet = [nan; diff(data.AssetPrice) ./ data.AssetPrice(1:end-1)]; mktRet = [nan; diff(data.MarketIndex) ./ data.MarketIndex(1:end-1)]; % 无风险利率从年化百分数折算到每日 rfDaily = data.RF / 100 / 252; % 超额收益:资产收益减去当日无风险收益 assetExRet = assetRet - rfDaily; mktExRet = mktRet - rfDaily;

diff是向后差分,第一个位置没有前一交易日,所以手动补一个NaN。分母用前一日价格,得到的是单期简单收益率。252是股票市场常用的年化交易日天数,也有用365的,但国债逆回购这类按实际天数计息的产品才更适合365,股票CAPM估计里252是更稳的选择。

如果数据源给的是对数价格的日频率,也可以换一行:

% assetRet = [nan; diff(log(data.AssetPrice))];

对数收益率的好处是可以直接按时间累加,但普通CAPM回归用简单收益率和用对数收益率得到的beta差别通常在0.01以内,不必纠结。

3.3 清理缺失值并防止日期错位

数据里可能有停牌NaN、除权跳空和交易所非共同交易日。先把缺失的行删掉,注意不能用按列各自删的写法:

tmp = table(data.Date, assetExRet, mktExRet, ... 'VariableNames', {'Date', 'AssetEx', 'MktEx'}); tmp = rmmissing(tmp);

rmmissing会整行删除,保证同一日期的资产收益和市场收益对上。真正要避免的是分别对assetExRet和mktExRet各自删除NaN,那样会改变序列长度,日期就错位了。再往下回归之前,我会加一个保护:

if height(tmp) < 30 error('有效数据不足,无法估计'); end

30是经验值,日频至少一个季度以上才有意义。到这里,数据已经变成两列干净的日频超额收益序列,可以直接进入CAPM估计程序。

4. 最小二乘与Newey-West标准误:一份可直接改写的估计源码

4.1 用fitlm快速拿到回归统计量

数据准备好后,第一版程序可以一口气算完:

% capm_estimate.m 最小可用版本 y = tmp.AssetEx; % 资产超额收益 x = tmp.MktEx; % 市场超额收益 mdl = fitlm(x, y); % 默认带截距 alpha = mdl.Coefficients.Estimate(1); beta = mdl.Coefficients.Estimate(2); alpha_t = mdl.Coefficients.tStat(1); beta_t = mdl.Coefficients.tStat(2); rsq = mdl.Rsquared.Ordinary;

fitlm的调用形式是fitlm(预测变量, 响应变量)。这里x和y都是列向量;alpha对应截距,beta对应市场因子斜率。mdl.Rsquared.Ordinary是普通R方,记录市场因子能解释资产收益波动的比例。这个版本适合先验证数据有没有问题,但标准误默认是普通OLS标准误,金融序列里常偏小。

4.2 矩阵解法看回归内部,beta和残差怎么出来的

为了后面能嵌入Newey-West修正,把fitlm内部展开:

n = length(y); X = [ones(n,1), x]; % 第一列截距,第二列市场超额 par = X \ y; resid = y - X * par; alpha = par(1); beta = par(2);

X反斜杠是Matlab里解最小二乘的标准写法,数值上比inv(X'*X)*X'*y稳定。残差里包含了样本期内的个股异质收益,也是后面异方差和自相关检验的对象。这一步得到的结果与fitlm完全一致,只是结构更开放,方便继续接自定义标准误。

4.3 Newey-West标准误手动实现,不依赖黑盒

日频收益率的残差通常同时存在条件异方差和序列相关。Newey-West用加权重叠的自协方差项来修正标准误。手动实现时,把中间量看清楚更重要:

S = (X'*X) / n; % 解释变量平均叉积 u = X .* resid; % 每个观测对得分矩阵的贡献 Gamma0 = (u' * u) / n; % 零阶自协方差 lag = floor(4 * (n/100)^(2/9)); % Newey-West (1994) 经验滞后 Omega = Gamma0; for j = 1:lag w = 1 - j / (lag + 1); % Bartlett权重 Gj = (u(1:end-j,:)' * u(1+j:end,:)) / n; Omega = Omega + w * (Gj + Gj'); end VCV = inv(S) * Omega * inv(S) / n; se = sqrt(diag(VCV)); beta_se = se(2); alpha_se = se(1);

lag是自相关能影响标准误的最大阶数,floor(4*(n/100)^(2/9))是Newey和West在1994年给出的经验公式。n=250时大约给到4阶,n=750时给到5到6阶,已覆盖常见日频数据。Bartlett权重让离当前观测越远的协方差贡献越小。最终VCV是HAC方差协方差矩阵,开方后就是修正标准误。若装了Econometrics Toolbox,也可以用hac函数做交叉验证,但这段手动代码足够日常使用。

需要说明,Matlab R2016b及以上支持X .* resid的隐式扩展;低版本请改成bsxfun(@times, X, resid)。结果表可以按下面格式整理:

估计值 普通标准误 NW标准误 alpha -0.0002 0.0003 0.0006 beta 1.0520 0.0180 0.0342

具体数字以你的样本为准,但报告里同时列出普通标准误和NW标准误是稳妥做法。

5. 无风险利率口径、月度重采样与alpha怎么读才对

5.1 无风险利率选择:隔夜、1年期国债还是3个月国库券

CAPM里的Rf是“无风险资产收益率”,实际操作按研究目的选。做A股日频,常用1年期国债到期收益率或银行间质押回购利率;做美股,常用3个月国库券收益率。程序里别只看数字,要确认它到底是年化还是单期。最常见错误是把年化2.5直接当成日收益率减掉,那会让超额收益整体偏小一个数量级。

我一般会在脚本开头顶部定义参数组:

% config: 无风险利率口径 rfAnnualPct = 2.5; % 年化百分数 rfDaily = rfAnnualPct / 100 / 252; rfMonthly = rfAnnualPct / 100 / 12;

如果数据表里RF本身就是日频小数,就不要除252。加一个注释在表头比事后猜更安全。

5.2 用timetable把日频数据重采样到月频再估beta

改成月频时,不能直接对日频超额收益做last,那会变成“取最后一天超额收益”,完全错误。正确的路径是先把价格重采样到月末,再一次差分:

priceTT = timetable(data.Date, data.AssetPrice, data.MarketIndex, data.RF); priceTT = sortrows(priceTT); monthlyTT = retime(priceTT, 'monthly', 'last'); % 每月最后一个交易日 monthlyPrices = monthlyTT.Variables; assetM = diff(monthlyPrices(:,1)) ./ monthlyPrices(1:end-1,1); mktM = diff(monthlyPrices(:,2)) ./ monthlyPrices(1:end-1,2); rfM = monthlyTT.RF / 100 / 12; % 月末无风险利率做近似

删去首行NaN后,用assetM - rfM就能得到月频超额收益。retime的'monthly'边界取自然月的最后日历日,如果那天不是交易日,Matlab会取该月最后一个可用观测,这正好是金融数据的常规处理方式。RF这里用的是月末值近似,更严谨的做法是对月内日度RF取平均,但多数情况下对beta影响很小。

5.3 alpha显著性:CAPM有效性的一条检验线

回归结果的alpha要看t统计量,不是只看正负。判断alpha显著不为0,一般用 |t| > 2 的粗略标准。比如上节示例中alpha的NW修正t值约-0.36,说明该股样本期内的超额收益没有显著偏离0,这不等于alpha就是0,只是证据不足。beta的t值通常很大,但如果用了月频数据、样本量只有36,beta置信区间会很宽。真正写报告时,建议将普通标准误和NW标准误一起列出,让读者清楚是否依赖修正。

配合一个参数速查表:

参数文件内位置建议值
无风险利率年化值rfAnnualPct按数据源年度均值
交易日天数252股票用252,债券回购用实际天数
重采样频率retime的'monthly'论文口径尽量用月频
NW滞后阶数lag变量不手工改,按公式

6. 滚动窗口验证beta稳定性,以及四个容易翻车的坑

6.1 滚动窗口源码

beta的稳定性比单点beta更有价值。用月频数据做36个月窗口,步长一个月,先构造月频超额收益,再做循环:

% rolling_beta.m assetExM = assetM - rfM; mktExM = mktM - rfM; assetExM(1) = []; mktExM(1) = []; window = 36; nobs = length(assetExM); betaRoll = nan(nobs-window+1, 1); for t = 1:(nobs-window+1) segY = assetExM(t:t+window-1); segX = [ones(window,1), mktExM(t:t+window-1)]; b = segX \ segY; betaRoll(t) = b(2); end plot(1:length(betaRoll), betaRoll);

循环里每次都重新构造设计矩阵,窗口移动一个观测。生成的曲线能直接看出beta是稳定在1附近,还是跟着市场周期大起大落。如果要画置信带,把第4章的NW标准误按同样窗口再算一遍,存成上下界即可。

6.2 程序能跑之外的四个坑

第一,日期错位。用Excel合并两个价格序列时,如果一个表多了几天,直接用矩阵减法会把不同日期的数据相减。解决方法是只保留两个序列共同日期,用rmmissing或innerjoin整行去除。

第二,用价格水平做回归。价格是非平稳序列,价格对指数回归经常得到非常高的R方,但那是伪回归,得到的beta没有经济意义。CAPM回归的响应变量必须是收益率或超额收益率。

第三,忽略无风险利率的频率。无风险利率按年化给,日频要除252,月频要除12。利率高企或波动剧烈时,错误口径对alpha的影响会被放大;对beta的影响相对小,但仍会污染标准误。

第四,把普通标准误当成修正标准误上报。日频数据的残差几乎都有自相关,建议在结果表里同时列出两者。如果差异超过30%,说明数据里存在明显依赖,需要检查重采样或异常值。

6.3 兜底的验证技巧

我建议每次跑完程序后只做三个检查:打印前5行tmp,确认日期连续;看残差的自相关函数前10阶绝对值是否快速衰减;把预测收益率和实际收益率画在一张图上,直观判断是否存在系统性偏移。这三项不用任何额外工具箱,加起来不到十行代码,能拦住绝大多数“算出来但不敢用”的结果。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询