FEATURED · 精选文章

电价预测全流程:MATLAB下ARIMA建模与置信区间计算指南

发布时间 / 2026/9/8 5:40:49
来源 / 创域科博编辑部
栏目 / 资讯中心
电价预测全流程:MATLAB下ARIMA建模与置信区间计算指南 做电价预测的人很多都卡在同一个问题上模型跑出来点预测看起来还行但一到真正做决策——报投标价格、估风险敞口、算备用容量——领导就问一句“你这个数准吗偏差多少”这时候你拿不出区间方案基本废一半。这也是我这篇想重点聊的东西。ARIMA在电价预测里不算新但“ARIMA 置信区间”这套组合在Matlab里落地时有不少细节教材不会写、官方文档也藏着掖着。这篇就当成一篇踩坑总结加完整实操记录从数据准备、模型识别、参数估计到置信区间计算全流程走一遍适合刚接触电价预测的学生也适合在电力交易、调度、售电侧做预测的人参考。1. ARIMA在电价预测里凭什么还能打很多人一看到电价预测第一反应是上LSTM、Transformer。我不能说深度学习没用但现实场景里尤其是样本量不大、数据质量一般、需要快速迭代模型的业务中ARIMA依然是性价比极高的起点。电价序列虽然复杂但拆开看它有很强的自相关结构今天的电价和昨天、前天同一时段的电价高度相关周期性和趋势性都明确这正好是ARIMA的用武之地。1.1 电价序列到底有什么特征电价序列和普通负荷序列不太一样。负荷是纯物理需求驱动相对平滑电价是市场出清结果叠加了报价策略、机组启停成本、网络阻塞、新能源出力波动所以它有几个典型特征强周期性日周期24小时峰谷、周周期工作日和周末差异明显、季节周期。均值回归价格偏离均值后会向长期均衡水平回归。这一点和金融资产价格“随机游走”完全不同。尖峰与厚尾极端天气、机组故障时价格可能瞬间飙到正常值的几倍甚至几十倍这种尖峰不是平稳序列里的普通噪声。波动聚集高价格时段往往伴随高波动低价格时段相对平稳类似金融里的GARCH效应。这些特征决定了ARIMA模型不能“裸奔”直接用必须先做差分和平稳化必要时要上SARIMA季节性ARIMA。实操中我的习惯是先用SARIMA结构的框架去套再通过信息准则判断季节项到底要不要留。1.2 为什么点预测不够置信区间才是核心输出电网里的绝大多数决策需要的不是一个确定值而是一个“可能的范围”。举个最简单的例子售电公司预测明天中午12点的现货价格如果点预测是500元/兆瓦时公司据此报了购电计划但实际价格可能落在400到600之间。如果你的预测区间能提前告诉决策者“有80%的置信水平落在420到580”那么决策者就能提前准备风险预案。反过来只有一个孤立点预测等于把所有的风险都藏在了你看不见的地方。这就是置信区间的价值。ARIMA模型天然能做区间预测因为它不仅能给出未来值的期望还能给出预测误差的方差。基于正态假设我们就可以构造预测区间。这在Matlab里实现并不复杂但有一点必须提前说清楚预测步长越长方差越大区间就越宽。这一点在后面代码部分会直观看到。2. 数据准备直接决定模型上限的环节很多文章一上来就调ARIMA的阶数仿佛阶数对了模型就准了。但以我的实测经验预测结果的好坏60%以上在数据准备阶段就定了。电价数据脏得很缺失、跳变、极端尖峰、时区错位哪个问题没处理好后面模型再精巧也白搭。2.1 数据清洗的三个常见坑先说缺失值。电价数据常见的缺失原因是采集终端离线、通讯中断、市场休市日处理不当。处理策略不能一概而论单个点缺失用前后时刻的线性插值即可不会引入太大偏差。连续多个点缺失如果缺失超过3个小时插值已经不可靠更推荐用“同类型日同时刻”的平均值填充。比如工作日晚上8点的缺失值用其他工作日晚上8点的历史均值补。休市日比如某些市场周末不交易如果研究的是连续预测需要把休市日剔除或者单独建模不要用插值硬填。异常值处理要小心。电价尖峰是真实波动不是错误数据。我见过有人一刀切把超过3倍标准差的数据全干掉结果把真实的价格尖峰也删了预测模型变得“钝感”尖峰事件完全预测不到。我的原则是先判断是真实市场价格还是明显的数据错误比如价格变成负值或者超过市场报价上限才处理正常范围内的尖峰保留。2.2 平稳性检验与差分阶数的确定ARIMA模型要求序列平稳所以第一步是检验原始序列。Matlab里有现成的ADF检验函数也可以自己写一个简单的平稳性判断% 读取电价序列假设 data 是列向量price 是电价 [h, pValue, ~, ~] adftest(price, Model, TS); disp([ADF检验p值: , num2str(pValue)]); % h 1 表示拒绝单位根原假设序列平稳h 0 表示不平稳需要差分如果原始序列不平稳通常做一阶差分。但电价序列往往有强周期性仅一阶差分还不够需要对季节周期再做一次差分。用Matlab的diff函数很容易实现% 一阶差分 d1 diff(price, 1); % 若存在24小时周期再做24步差分 d1_24 diff(d1, 24); % 再次ADF检验 [h2, pValue2] adftest(d1_24, Model, TS);这里有个容易踩的坑差分不是越多越好。差分次数过多会放大高频噪声导致模型过度拟合历史数据、丧失预测能力。我判断差分阶数的标准是每做一次差分后都做ADF检验一旦p值显著小于0.05就不再继续差分。同时观察差分后序列的ACF图如果ACF迅速衰减到置信带内说明差分阶数已经够了。2.3 训练集测试集划分的两个原则第一不要随机划分。时间序列必须按时间顺序切分否则会造成数据泄漏——训练集里包含了测试集的信息模型评估结果虚高。建议用“滚动预测”的思路比如用前80%的数据训练后20%逐点预测评估更严格的可以每预测一步就重新估计一次模型参数。第二如果要做归一化必须只用训练集计算均值和标准差。很多人直接对整个序列做z-score归一化再切训练测试集这就泄漏了测试集信息。虽然在ARIMA这类参数模型中归一化对预测影响不大但如果你后面要扩展到神经网络做对比这个习惯要从一开始就养成。3. 定阶与参数估计ACF/PACF之外必须看的信息准则ARIMA(p,d,q)里的p、d、q三个阶数d在上一节确定了剩下p和q。理论上讲看ACF和PACF就行AR项的p看PACF的截尾MA项的q看ACF的截尾。但实际电价序列的ACF/PACF往往拖尾明显尤其在存在周期性时图形判断非常主观。我的建议是图形辅助初判信息准则最终定阶。3.1 Matelab里的定阶思路Matlab没有像R里的auto.arima那样的一站式自动定阶函数但我们可以用estimate配合AIC/BIC写一个简单的网格搜索循环。基本思路是遍历p∈[0,5]、q∈[0,5]d已经确定对每个组合估计模型计算AIC或BIC取最小值对应的(p,q)。% 假设 diff_series 是已经平稳的序列 p_max 5; q_max 5; aic_matrix zeros(p_max1, q_max1); bic_matrix zeros(p_max1, q_max1); for p 0:p_max for q 0:q_max try Mdl arima(p, 0, q); % 这里d0因为序列已经差分平稳 [EstMdl, ~, logL] estimate(Mdl, diff_series, Display, off); [aic, bic] aicbic(logL, pq1, length(diff_series)); aic_matrix(p1, q1) aic; bic_matrix(p1, q1) bic; catch aic_matrix(p1, q1) inf; bic_matrix(p1, q1) inf; end end end [min_aic, idx_aic] min(aic_matrix(:)); [p_aic, q_aic] ind2sub(size(aic_matrix), idx_aic); disp([AIC最优: p, num2str(p_aic-1), , q, num2str(q_aic-1)]);需要提醒的是AIC和BIC的取舍BIC对参数数量惩罚更重选出来的模型更简洁AIC偏向预测精度。电价预测场景我更倾向AIC因为预测是核心目标稍微复杂一点的模型只要能提升预测精度就可以接受。相邻阶数的AIC差小于2时选择更简洁的模型不要追求极小值。这是经验也是防止过拟合的手段。估计失败的情况某些(p,q)组合会让似然函数无法收敛循环里用try-catch包住很关键否则一个异常就把整个循环中断了。3.2 参数估计与残差检验阶数定好之后用arima(p,d,q)直接估计Mdl arima(p_aic-1, d, q_aic-1); % 注意差分 EstMdl estimate(Mdl, price, Display, params);参数估计之后残差白噪声检验是必做题。如果残差还有自相关说明模型没提取干净信息预测区间会偏窄过度自信。Ljung-Box检验是标准做法[~, pValue_lb] lbqtest(EstMdl.Residuals, Lags, [10, 20, 30], Alpha, 0.05); disp(pValue_lb);如果p值小于0.05说明残差仍有显著自相关我的处理方案一般有三种增加AR或MA阶数把残留的相关性吸收掉。检查是否漏掉了周期项必要时换成SARIMA模型arima(ARLags,1:4,SARLags,24,D,1,Seasonality,24)。如果残差存在明显的波动聚集即残差平方相关可以考虑ARIMAGARCH组合但这就是另一篇文章的话题了。4. 置信区间是怎么来的从预测误差方差到区间上下界这部分是很多人最迷糊的地方。forecast函数在Matlab里直接输出点预测和均方根误差但区间怎么算、为什么算出来是这个宽度文档讲得含糊。我把原理拆开讲清楚。4.1 ARIMA预测误差方差的来源ARIMA模型可以写成MA(∞)形式[ y_{th} \mu \sum_{i0}^{\infty} \psi_i \varepsilon_{th-i} ]预测新息innovation是未来扰动项的组合模型的已知参数只能解释到当前时刻的历史扰动未来每一个时间步的扰动 (\varepsilon) 都是未知的。因此h步预测误差的方差是[ \text{Var}(e_{th}) \sigma^2 \sum_{i0}^{h-1} \psi_i^2 ]这里 (\sigma^2) 是白噪声方差(\psi_i) 是模型转换的脉冲响应系数。直观理解预测步长h越大累加的未知扰动越多方差线性通常递增增长置信区间也就越宽。4.2 区间计算公式与正态假设如果我们假设扰动项服从正态分布ARIMA估计本来就基于正态似然那么预测值的分布近似正态置信区间可以写成[ \text{CI} \hat{y}{th} \pm z{1-\alpha/2} \cdot \sqrt{\text{Var}(e_{th})} ](\hat{y}_{th})点预测值(z_{1-\alpha/2})标准正态分布的临界值95%置信区间对应1.9690%对应1.645(\sqrt{\text{Var}(e_{th})})h步预测标准差Matlab里forecast函数返回的第三输出量就是它。注意这里依赖正态假设。电价序列尖峰厚尾正态假设在极端分位数上不一定准。但如果你要的只是80%~90%左右的区间正态近似通常够用如果要更精确的尾部就得考虑用历史残差的经验分位数做预测区间或者用bootstrap方法。后面我会给一个简单的残差分位数版本作为对比。5. Matlab完整实操电价预测置信区间可视化到这一步前面所有理论都汇成一段可运行的代码。我用过一个典型的96点电价数据集每15分钟一个点一天96个点做过测试这里代码做了适当简化换成小时数据也一样。核心流程是读数据-差分定阶-模型估计-forecast-计算置信区间-画图。5.1 核心代码%% 1. 数据加载与预处理 % data.mat 中包含 price列向量和 timedatetime类型 load(electricity_price.mat); % 缺失值处理线性插值 price fillmissing(price, linear); % 划分训练集前85%和测试集后15% train_len floor(length(price) * 0.85); train price(1:train_len); test price(train_len1:end); %% 2. 平稳性检验与差分 [h, pValue] adftest(train, Model, TS); disp([原始序列ADF p值: , num2str(pValue)]); % 如果不平稳做一阶差分 if h 0 d 1; train_diff diff(train, 1); else d 0; train_diff train; end % 对差分后序列再做一次ADF必要时再差分一次 [h2, pValue2] adftest(train_diff, Model, TS); if h2 0 d d 1; train_diff diff(train_diff, 1); end disp([最终差分阶数 d , num2str(d)]); %% 3. 网格搜索定阶AIC最小 p_max 4; q_max 4; aic_matrix zeros(p_max1, q_max1); for p 0:p_max for q 0:q_max try Mdl_temp arima(p, 0, q); [~, ~, logL] estimate(Mdl_temp, train_diff, Display, off); [aic, ~] aicbic(logL, pq1, length(train_diff)); aic_matrix(p1, q1) aic; catch aic_matrix(p1, q1) inf; end end end [min_aic, idx] min(aic_matrix(:)); [p_best, q_best] ind2sub(size(aic_matrix), idx); p_best p_best - 1; q_best q_best - 1; disp([最优ARIMA(p,d,q) (, num2str(p_best), ,, num2str(d), ,, num2str(q_best), )]); %% 4. 模型估计 Mdl arima(p_best, d, q_best); EstMdl estimate(Mdl, train, Display, off); %% 5. 预测未来 h 步并计算置信区间 h 24; % 预测未来24个小时 [forecast_price, ~, forecast_sd] forecast(EstMdl, h, Y0, train); % 95%置信区间的正态临界值 z norminv(0.975); lower forecast_price - z * forecast_sd; upper forecast_price z * forecast_sd; %% 6. 可视化 figure(Position, [100, 100, 1200, 500]); hold on; % 绘制历史训练数据只画最近200个点 train_plot train(end-199:end); train_time time(end-199:end); plot(train_time, train_plot, b-, LineWidth, 0.8); % 测试集实际值 test_time time(train_len1:train_lenh); plot(test_time, test(1:h), k-, LineWidth, 1.0); % 预测值 forecast_time time(train_len1:train_lenh); plot(forecast_time, forecast_price, r-, LineWidth, 1.5); % 置信区间阴影 fill_x [forecast_time; flipud(forecast_time)]; fill_y [lower; flipud(upper)]; fill(fill_x, fill_y, r, FaceAlpha, 0.2, EdgeColor, none); legend(训练集, 实际值, 预测值, 95%置信区间, Location, best); xlabel(时间); ylabel(电价); title(ARIMA电价预测与置信区间); grid on; hold off;5.2 代码里容易忽略的细节第一forecast函数的第三个输出forecast_sd必须用[~, ~, forecast_sd] forecast(...)这种形式接住。如果你只写forecast(EstMdl, h, Y0, train)返回的是点预测标准差拿不到区间就无从谈起。第二Y0参数很关键。forecast默认用估计模型时的原始响应数据末端做起点但如果你对训练集做了差分、插值等操作建议显式传入Y0保证预测起点与你的数据窗口一致。特别提醒这里传入的是原始序列train而不是差分后的序列。因为EstMdl本身已经包含了差分信息forecast会自动处理。第三画置信区间的fill函数横坐标要正序和逆序拼起来才能形成闭合多边形否则阴影区域会乱掉。这是画图细节但很多人第一次都折在这里。5.3 基于残差分位数的替代方案如果担心正态假设撑不住电价序列的厚尾特征可以换用历史残差的经验分位数来构造区间。步骤很简单residuals EstMdl.Residuals; residuals residuals(~isnan(residuals)); % 去除估计前期的NaN q_low quantile(residuals, 0.025); q_high quantile(residuals, 0.975); lower_emp forecast_price q_low; upper_emp forecast_price q_high;这个方法本质上是把残差的分布直接投影到预测区间上不对分布做任何参数假设。但有个明显问题它假设未来残差分布和历史残差分布一致如果未来市场波动环境发生变化这个假设同样站不住。另外由于没有叠加上预测步长带来的方差递增这种区间宽度在长预测期会偏窄。混合用法是以理论区间为骨架用残差分位数对两端做校正比如用历史残差的偏度信息调整区间偏移让区间能捕捉到电价的正偏态。6. 跑通模型容易跑对模型很难实测踩坑记录这部分要是有人能早点告诉我我能少走很多弯路。以下每一条都在实际项目中真实遇到过。6.1 坑一过度差分让模型变成“噪声放大器”我第一次做电价预测时看到原始序列ADF检验不显著就一阶差分看到差分后仍不显著再做二阶差分再不行就24步季节差分。最后模型训练集拟合效果出奇地好测试集一预测结果全是震荡——预测值来回跳区间宽得没法用。后来才反应过来差分一次是为了消除趋势季节差分是为了消除周期但如果原始序列本来只有弱趋势你差分多了就把趋势信息全削掉了只剩高频噪声。ARIMA变成了在学习噪声而不是价格动态。现在的做法是先用adftest确认再用autocorr看差分后序列的ACF有没有出现“过度差分”的特征——过度差分的ACF会出现负值拖尾现象尤其是滞后1阶的ACF显著为负这就要退回上一阶差分。6.2 坑二预测步长增加置信区间却不变有次我把预测区间算好对比不同步长的区间宽度发现几乎一样宽。排了很久发现问题出在我用了infer函数获取残差方差然后手动按固定值构造区间完全忽略了预测步长带来的方差递增。后来改用forecast函数问题立即消失。forecast内部已经把ARIMA模型转换成了MA(∞)形式正确累计了每一步的预测误差方差。所以我的建议很简单不要手动去拼预测方差直接用Matlab的forecast第三输出量。除非你要实现自定义的预测区间方法否则没有必要重复造轮子。6.3 坑三数据泄漏让“验证结果”骗了你这是最隐蔽的坑。有一版代码里我为了加快处理速度先用整个序列做了缺失值插值然后用全序列均值做过归一化再切训练测试集。结果验证集的RMSE非常好看但到了下一周真实预测时误差突然飙升。原因在于用全序列做插值和归一化相当于训练时“偷看”了测试期的统计信息。比如用未来数据补了历史缺失值这个补进来的值已经包含了未来信息。短期来看模型拟合得好长期来看模型的泛化能力被高估了。修正办法也简单所有预处理操作都先fit在训练集上再transform到测试集上插值也只能用当前时间点之前的数据进行因果插值不能用未来的点回推。6.4 坑四只报告RMSE不看区间覆盖率很多人评估预测好不好只看RMSE或MAPE但如果你发布的是区间预测必须额外检查区间覆盖率。定义很简单inside (test(1:h) lower) (test(1:h) upper); coverage mean(inside) * 100;如果你宣称95%置信区间那么理论上测试集中大约95%的实际值应该落在区间内。实际中电价预测的95%区间覆盖率经常低于90%这是因为模型低估了尖峰时刻的波动。如果覆盖率严重偏离标称水平需要回溯模型假设是不是残差不是正态是不是有异方差没处理这才是区间预测评估的正确逻辑而不只是看点预测误差。7. 结果解读与扩展方向别把ARIMA当终点跑完上面流程你会得到一组点预测和区间。接下来怎么解读、怎么用才是区分新手和老手的分水岭。7.1 怎么看预测区间对电价这种序列预测区间比点预测信息量大得多。举个例子某天24小时的预测区间如果午夜谷段的区间很窄说明市场确定性高而傍晚峰段的区间非常宽说明市场不确定性高那么决策者就能合理分配精力峰段多关注、多准备应对方案谷段可以放心按常规操作。还要看区间是否覆盖了实际值。如果连续多天实际值总在区间下沿之外说明模型系统性地高估价格——这比随机误差更可怕因为它意味着模型结构有问题比如没有捕捉到某个新的市场机制或季节变化需要回头重新审视训练数据窗口和模型设定。7.2 可以从ARIMA扩展的方向ARIMA很难是终极方案但在实际项目中它是个非常好的baseline。扩展方向我列几个亲身验证有效的加入外生变量用arima配合回归项比如xreg参数可以直接把气温、新能源出力、负荷预测值作为外生变量引入退化成ARIMAX模型。这个在电价预测里提升通常非常明显因为电价不光受历史价格影响还受供需两侧的实时因素驱动。分时段建模不同的交易时段如峰、平、谷价格行为差异巨大强行用一个模型拟合所有时段会顾此失彼。更稳的做法是分多个时段分别建模比如上午峰、下午平、晚峰各建一个ARIMA预测时按要求拼起来。虽然代码繁琐一些但精度提升很明显。组合预测ARIMA擅长捕捉线性自相关树模型或神经网络擅长捕捉非线性关系。把ARIMA的预测值作为特征输入到XGBoost或LSTM里让第二层模型学习“残差”和“外生变量”的关系这个思路在很多竞赛里验证过效果好于任何单一模型。7.3 最后分享一点个人体会用ARIMA做电价预测技术上门槛不算高但做到能让人放心用来辅助决策差距就在细节里。数据有没有洗干净差分有没有做过头残差有没有残留自相关信息区间覆盖率到底多少这些才是真正决定模型能不能落地的东西。我做过的项目里能长期稳定投入使用的预测模型不是那些创新点花哨的神经网络而是预处理扎实、诊断到位、区间可信的简单模型。这也是我一直建议先把ARIMA吃透的原因——它能帮你把时间序列建模的基本功打得非常扎实后面学任何复杂模型都会轻松很多。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻