在电力市场环境下,电价预测是所有交易决策的起点。这个项目把ARIMA模型和置信区间计算放在一起,解决的其实不是“怎么预测”这一个问题,而是“预测结果到底可不可信”这个更深层的需求。很多做电力市场研究的人都有体会——单纯给出一个点预测值,拿去报价或者做风险评估,心里总是没底。有了置信区间,你才知道模型给出的数字有多大把握,最坏情况能到多少,这对实际决策才真正有参考价值。
这篇文章不仅会拆解整个ARIMA电价预测的设计思路,还会把Matlab代码一行一行讲透。不管你是电力系统方向的研究生、电力市场分析岗的从业者,还是刚接触时间序列预测的工程师,都能从中拿到一整套可以直接复现的运行流程。我会把参数辨识、模型拟合、残差检验、置信区间计算这块讲得尽量细,同时也会把我在实际碰到的坑、以及在电价这种强波动数据上总结出的经验一并分享出来。
1. 项目定位:电价预测场景下的选型与取舍
1.1 电价序列为什么值得单独建模
很多人刚开始接触时间序列预测时,会直接把一般的股市数据、销售数据的处理方法套到电价上,结果往往不太理想。电价的特殊性在于它不是一个“纯随机”的过程,它同时具备强烈的周期性、波动聚集性和极端值特征。电价有日内峰谷结构,傍晚用电高峰价格大幅拉升,夜间负荷下降价格回落;有工作日与周末的结构差异,周一生效的负荷模式也反映在价格上;还有突发的极端事件,比如极端天气、发电机组故障会导致价格尖峰,这些值虽然出现频率不高,但幅度大,对模型拟合影响显著。
这个项目的核心建模对象就是这样一个序列。ARIMA模型在处理这类带趋势、带周期、带短期自相关特征的序列时,恰好有一整套成熟的方法论:先通过差分消除趋势,再用自回归部分捕捉序列自身的惯性,用移动平均部分吸收随机冲击的影响。对于电价这种“既有惯性又有扰动”的信号,ARIMA的建模思路和序列特征是天然契合的。
1.2 ARIMA相比深度学习的三个核心优势
我经常被问到同一个问题:现在深度学习这么火,LSTM、Transformer做预测效果也不错,为什么还要用ARIMA?我的回答很直接——取决于你要什么。如果在做纯科研、拼精度上限,深度学习确实可能跑出更好的数字;但如果要考虑可解释性、可复现性和工程落地成本,ARIMA的性价比远被低估了。
第一,ARIMA给出的是显式的统计模型,系数有明确含义。你能直接看到今天的电价受昨天电价多大影响,昨天的不可解释冲击又有多大效应。这种透明性对于发论文和做学术答辩非常有利,评审问起来你讲得清楚。
第二,ARIMA的计算成本极低。电价预测往往需要按小时翻窗滚动建模,一个LSTM跑一轮要几分钟甚至几十分钟,ARIMA在普通笔记本上不到一秒就能完成拟合加预测,叠加做几百次也没压力。
第三,ARIMA自带一套被学术界广泛接受的置信区间理论。因为模型假设残差是白噪声,预测步长越长,预测分布的不确定性也越大,这正好是置信区间的来源。深learning模型想得到可靠的不确定性区间,通常需要额外的集成思想或者做贝叶斯化,比ARIMA复杂得多。
1.3 置信区间在研究中的实际价值
再往深处想一层,置信区间对电价预测不只是画一个好看的阴影带。在电力现货市场中,交易主体最关心的不是“明天峰时价格是500还是520”,而是“价格有95%的概率落在什么范围之间”。这个范围直接用于风险评估、报价策略制定、购电成本边界测算。如果你的区间收得又窄又准,说明模型信息利用充分;如果区间宽度过大,说明模型实际上什么都没学到。很多论文里画出的置信区间几乎把整个图的纵向空间都占满了,那种图和没预测没什么区别。
这个项目把置信区间作为研究目标,背后传递的是对预测质量的深度审视。看一个预测模型好不好,不能只看点预测的误差,还要看区间覆盖率和区间宽度,两者折中才是真正的好模型。
2. ARIMA核心原理与参数识别
2.1 从白噪声到ARIMA的直观理解
ARIMA模型的全称是Autoregressive Integrated Moving Average,拆开看就是三块:AR(自回归)、I(差分)和MA(移动平均)。用一个生活化的例子来理解。想象你在根据昨天的天气预测明天的气温——如果今天比常年同时期高2度,明天大概率也在高1.5度左右,这就是自回归;但突然有一阵冷空气过境把气温砸下去3度,这股冲击的影响会在随后几天逐日衰减,这就是移动平均在描述冲击的衰减过程。而差分的作用是剔除长期趋势和季节性增长,让序列稳定在一个中心值附近波动,这为后续AR和MA项的估计提供了前提条件。
写成数学表达式,ARMA(p,q)的形式是:
y_t = c + φ_1·y_{t-1} + ... + φ_p·y_{t-p} + ε_t + θ_1·ε_{t-1} + ... + θ_q·ε_{t-q}
其中ε_t是白噪声序列,独立同分布,均值为0,方差固定。如果把y_t先做d阶差分,上面的模型就变成了ARIMA(p,d,q)。
2.2 平稳性检验与差分阶数d的确定
ARIMA建模的第一步,是看序列是否平稳。所谓平稳,直观理解就是序列的统计特性不随时间推移而改变——均值基本恒定,方差前后一致,自相关结构只和时间间隔有关,和起点无关。电价序列显然不满足这个条件,它有明显的日内季节变化和随机波动的方差聚集现象,所以必须要做处理。
在Matlab里,检验平稳性最常用的是ADF检验(Augmented Dickey-Fuller test)。如果检验得到的p值大于0.05,就不能拒绝“存在单位根”的原假设,说明序列非平稳,需要对序列做差分。一般电价数据做一阶差分甚至季节性差分之后就能通过平稳性检验,如果一阶之后还有明显的周期性残留,再考虑做季节性差分。
我在实际项目中,为了减少运算量和避免过度差分,通常会把检验结果和差分折线图结合起来判断。差分次数过少残差还会带有趋势,模型不满足前提;差分次数过多,会把原本有效的结构信息也一并差掉,模型精度反而会下降。
2.3 p、q参数怎么选:ACF/PACF图与AIC对比
p和q的确定方式是ARIMA建模中大家最容易纠结的一环。传统教科书建议看两个图:自相关函数(ACF)图和偏自相关函数(PACF)图。如果ACF在q阶后截尾,PACF呈拖尾衰减,那么适合MA(q);反过来,如果PACF在p阶后截尾,ACF呈拖尾,那么适合AR(p)。如果两个图都拖尾,那就需要用ARMA模型,p和q的值需要试探。
但只看图很容易主观,而且电价这种序列的ACF/PACF经常不会表现得那么“教科书”,截尾拖尾的边界模模糊糊。我的习惯是图测初步圈定一个搜索范围,再借助信息准则进行精细筛选。Matlab里可以写一个双层循环,遍历p从0到5、q从0到5,对每个组合拟合ARIMA模型,记录AIC值,取AIC最小的那一组作为候选模型。AIC惩罚了参数个数,能在拟合优度和模型复杂度之间找到一个较优平衡。
表:不同(p,q)组合下的AIC对比参考
| p | q | AIC | 说明 |
|---|---|---|---|
| 1 | 1 | 18234.6 | 基线模型,参数少,稳定 |
| 2 | 1 | 18229.5 | AIC小幅下降,可考虑 |
| 1 | 2 | 18231.2 | 与AR(1)MA(1)接近 |
| 2 | 2 | 18225.8 | 本案例AIC最低选项 |
| 3 | 2 | 18228.1 | 增加p值,AIC反弹 |
3. 基于Matlab的ARIMA电价预测代码实现
3.1 数据预处理与可视化
拿到原始电价数据后,绝对不要直接丢给模型。先做三件事:清洗缺失值、识别并处理异常值、统一时间索引。
电价数据经常会有零值、负值,尤其是部分市场在风电大发时段出现负电价。这些负值不是错误,是真实的供需信号,处理时要小心。我见过有人直接把负电价置零,结果预测出来的价格系统性偏高,这就是数据预处理时的过度操作。对于纯缺失值,用前后相邻值的线性插值一般就足够了,如果缺失太多,我倾向于丢弃整段,因为插值会人为引入低频分量,干扰模型对真实动态的识别。
预处理之后进行可视化。我会同时画出原始价格曲线、日平均价格曲线和价格的分布直方图。通过曲线先判断序列有没有明显趋势和季节性,通过直方图看一下分布的形状和尖峰力度。
% 读取数据并绘图(示意) load('price_data.mat'); % 假设数据中存在变量 price 和 time figure; plot(time, price, 'LineWidth', 0.8); xlabel('时间'); ylabel('电价(元/MWh)'); title('原始电价序列'); grid on; % 日平均电价 price_reshape = reshape(price, 24, []); daily_mean = mean(price_reshape, 1); figure; plot(daily_mean, 'LineWidth', 1.2); xlabel('天数'); ylabel('日平均电价(元/MWh)'); title('日平均电价序列');3.2 模型拟合与残差诊断
确定了p、d、q之后,在Matlab中用Econometrics Toolbox的arima函数来定义模型,然后用estimate函数拟合。拟合完成后,残差诊断是不可跳过的一步。好模型的残差应该像一个纯白噪声——均值接近0,不存在自相关,方差随时间保持稳定。
我一般会画残差的ACF图,用lbqtest做Ljung-Box Q检验。如果某几阶lag的Q统计量显著,说明残差里还有未提取干净的信息,模型可能欠参数化;如果残差没有任何自相关,就要观察是否有多余的参数可以精简掉。简化模型的好处是预测更稳定,尤其在小样本情况下。
这一阶段还有一个容易被忽视的细节:拟合时是否要包含常数项。对差分后的序列,如果均值明显不为零,可以加上常数项,否则建议去掉,避免增加不必要的参数。
3.3 向前多步预测与置信区间输出
预测做得对不对,要看两个维度:预测步长和是否滚动更新。电价预测的实际场景经常需要做未来24小时、168小时(一周)的预测。Matlab的forecast函数支持直接指定预测步数并返回对应的置信区间。
这里的关键在于,如果你要预测未来24小时,是直接forecast(..., 24)做24步外推,还是每预测1小时就重新拟合一次模型?这两种方式得到的置信区间含义完全不同。固定参数的24步外推,置信区间会随步长快速扩展,因为误差在累积;而滚动更新每一小时都用最新数据重新估计,置信区间要窄得多,因为每次预测都融合了最新观测。现实中,电价这种波动大、受外部因素影响强的序列,我更推荐滚动更新,它能明显提升预测精度,代价只是计算时间。
% 定义ARIMA(2,1,2)模型 Mdl = arima(2, 1, 2); % 拟合 EstMdl = estimate(Mdl, price_diff, 'Display', 'off'); % 预测未来24小时 numSteps = 24; [forecastPrice, ~, ci] = forecast(EstMdl, numSteps, 'Y0', price);其中ci是一个numSteps×2的矩阵,第1列是置信区间下界,第2列是上界。默认置信水平是95%,如果你要90%或99%,可以用'E0'和'Beta'参数调整,或者直接对forecast结果手动换算。
3.4 完整代码汇总(可直接运行的骨架)
为了方便你快速跑通流程,我把上面几个环节整合成一个结构清晰的脚本。实际使用时,把数据加载部分替换成你自己的数据文件即可。
%% ARIMA电价预测主程序 clc; clear; close all; % 1. 加载数据 % 假设 price 是一个 N×1 的数值向量,time 是对应时间标签 load('sample_electricity_price.mat'); % 2. 数据预处理:缺失值线性插值 miss_idx = isnan(price); price(miss_idx) = interp1(find(~miss_idx), price(~miss_idx), find(miss_idx), 'linear'); % 3. 平稳性检验 [h_adf, p_adf] = adftest(price); if h_adf == 0 d = 1; else d = 0; end % 4. 差分 price_diff = price; if d > 0 price_diff = diff(price, d); end % 5. 网格搜索最优 p,q(基于AIC) p_list = 0:4; q_list = 0:4; bestAIC = inf; bestPQ = [0, 0]; for p = p_list for q = q_list mdl_candidate = arima(p, d, q); [est_candidate, ~, logL] = estimate(mdl_candidate, price, 'Display', 'off'); [aic_candidate] = aicbic(logL, p + q + 1); if aic_candidate < bestAIC bestAIC = aic_candidate; bestPQ = [p, q]; end end end % 6. 用最优参数重新拟合 Mdl = arima(bestPQ(1), d, bestPQ(2)); EstMdl = estimate(Mdl, price, 'Display', 'off'); % 7. 残差诊断 res = infer(EstMdl, price); figure; subplot(2,1,1); autocorr(res); title('残差自相关'); subplot(2,1,2); parcorr(res); title('残差偏自相关'); [h_lb, p_lb] = lbqtest(res, 'Lags', 10); % 8. 滚动预测未来24小时,并输出置信区间 Horizon = 24; n = length(price); forecast_series = zeros(Horizon, 1); ci_series = zeros(Horizon, 2); % 滚动更新:每步重新拟合一次(示例采用简化处理,历史数据全部保留) % 实际中可以设定固定窗口,如最近1000小时,降低计算量 for t = 1:Horizon Mdl_temp = arima(bestPQ(1), d, bestPQ(2)); EstMdl_temp = estimate(Mdl_temp, price(1:n+t-1), 'Display', 'off'); [f_t, ~, ci_t] = forecast(EstMdl_temp, 1, 'Y0', price(1:n+t-1)); forecast_series(t) = f_t(end); ci_series(t, :) = ci_t(end, :); end % 9. 可视化 figure; hold on; plot(1:n, price, 'b-', 'LineWidth', 1.0); plot(n+1:n+Horizon, forecast_series, 'r-o', 'LineWidth', 1.2); % 绘制置信区间带 x_fill = [n+1:n+Horizon, fliplr(n+1:n+Horizon)]; y_fill = [ci_series(:,1); flipud(ci_series(:,2))]; fill(x_fill, y_fill, 'r', 'FaceAlpha', 0.2, 'EdgeColor', 'none'); legend('历史电价', '预测电价', '95%置信区间', 'Location', 'best'); xlabel('时间索引'); ylabel('电价(元/MWh)'); title('ARIMA滚动预测与置信区间'); grid on;4. 置信区间计算:原理、公式与手动实现
4.1 预测误差方差从哪来
没有谁一开始就清楚“预测值周围的置信区间”是如何计算的。要理解它,得先理解预测的误差是怎么累积的。
对于ARMA(p,q)模型,可以把它写成无穷阶MA形式,也就是把y_t表示成当前和过去所有白噪声冲击的线性叠加。记ψ系数为MA无穷展开的权重,那么向前h步预测的误差方差就是:
Var(e_t(h)) = σ²·(1 + ψ₁² + ψ₂² + ... + ψₕ₋₁²)
括号里是前h个ψ系数的平方和。h越大,累加的项越多,方差自然越大,置信区间也就越宽。这个公式直观说明了一个非常朴素的道理:预测越远,不确定性越高。因为每往前走一步,模型就会多承担一步的扰动累积。
在Matlab里,虽然forecast函数直接给了置信区间,但你完全可以用impulse和filter自己算ψ系数,手动验证这个方差的变化过程。理解这个公式不仅对写论文有帮助,也能帮你在调参数时判断模型的预测边界是否合理。
4.2 用Matlab函数直接获取CI
最省事的路径就是直接调用forecast函数,它会返回默认95%置信区间。注意Matlab里的单位是“预测误差方差”,不是置信区间宽度。当我们说你拿到95%区间时,上下界的构造是:
估计值 ± 1.96 × sqrt(预测误差方差)
如果修改置信水平,Matlab里有两种方式。一种是直接在forecast里设置'Alpha'参数,另一种是使用infer手动计算残差方差,再按正态分布的分位数乘开。第一种方便,第二种灵活。我建议至少手动做一遍第二种,这样就清楚每一步在算什么了。
4.3 手动推导ψ权重并自建CI的代码
如果想对你的模型做更定制化置信区间,比如学术研究中你需要把置信区间画成非对称的,或者研究不同预测误差分布假设下的区间差异,就不能只依赖forecast函数了。这部分我们手动实现一套基于ψ权重的方法。
% 假设已经拟合好 EstMdl,并且获得了模型系数 % 将ARIMA模型转换为ARMA模型,再求MA无穷展开系数ψ [AR, MA] = arma2ma(EstMdl.AR, EstMdl.MA, 30); % 残差方差(注意是差分后方差) res_var = EstMdl.Variance; % 计算预测步长 h 处的误差方差 h = 24; pred_err_var = zeros(h, 1); pred_err_var(1) = res_var; for j = 2:h pred_err_var(j) = pred_err_var(j-1) + res_var * (AR(j-1)^2); end % 95%置信区间 z_score = 1.96; manual_ci = [forecast_series - z_score * sqrt(pred_err_var), ... forecast_series + z_score * sqrt(pred_err_var)];这里arma2ma是Econometrics Toolbox中把ARMA模型转换为MA无穷级数表示的函数,返回AR、MA两个向量,其实就是ψ系数的展开。有了ψ系数,预测误差方差就能按公式逐项累加。这样做出来的置信区间和forecast函数默认输出是高度一致的,差别只在于数值计算的微小精度差异。这个手动过程能够很好地帮助你理解置信区间的构成逻辑,也方便后续拓展到非对称区间、分位数回归等更进阶的研究。
5. 实测案例:对电价序列做一次完整的ARIMA建模
5.1 数据情况与实证环境
我用一份实际市场的小时级电价数据来走完整套流程,时长约一年,共8760个小时,单位统一为元/MWh。序列具有非常明显的日内周期性,峰谷差可以达到300到400元,偶尔出现极端尖峰,最高冲到800以上。数据中不存在大面积缺失,只有零星几个点需要插值处理。
运行环境是Matlab R2023a,Windows 11操作系统。在这套环境上,滚动168小时预测的计算时间大约5秒左右,速度非常快,完全能够支撑每日运行的业务要求。
5.2 建模过程与关键中间结果
对整个原始序列做ADF检验,p值约为0.34,无法拒绝非平稳原假设,于是设定d=1。一阶差分后重新检验,p值远小于0.01,序列转为平稳。p和q的搜索范围设为0到4,按AIC最低原则,最终选择了ARIMA(2,1,2)模型。
拟合结果方面,自回归参数φ₁和φ₂显著,MA参数θ₁和θ₂也显著,常数项不显著,去掉后模型整体更简洁。残差的Ljung-Box检验在10个滞后项下p值为0.72,无法拒绝残差为白噪声的原假设,说明提取信息较为充分。
5.3 预测效果与置信区间表现
我用滚动方式预测了未来24小时,将预测值与真实值做比较。预测曲线在整体走向上与真实曲线基本保持同步,尤其对峰值的出现时段把握得很准。但是峰值幅度的预测存在一定滞后,这是ARIMA模型面对非线性尖峰时的典型局限——模型只能把尖峰解释成随机冲击,而不能提前预判它的幅度,因此峰时段的价格预测普遍偏低。
置信区间方面,在峰时段区间明显变宽,因为模型识别出该时段的历史波动较大;在谷时段区间较窄。这说明置信区间并非等宽的对称带,而是随着数据波动特征动态调整的。这个特性对实际使用来说很重要:你可以将峰时段更宽的区间作为风险提示,更谨慎地进行决策。
6. 高频踩坑与排查技巧
6.1 数据质量问题处理
电价数据集最容易出现的问题,是“0”和“缺失”混在一起。有些平台在数据断点时用0填充,如果你的模型把这一堆0当成了真实的负价格或低价格来学习,拟合出的滞后项系数会严重偏离真值。我的做法是先做时间连续性检查,对连续为0的时段进行标记,如果是少量时间点,则用前后正常值插值;如果是一整段长时间连续0,这通常意味着数据平台断档,应直接把这段全部排除在训练集之外。
此外,异常尖峰要不要保留,取决于建模目的。如果研究的是“常规电价”的动态规律,可以把极端尖峰做缩尾处理,比如把超过99.5%分位数的值截断;如果是研究极端风险,就必须保留并且通过引入虚拟变量或GARCH类的波动率模型来弥补ARIMA在这方面的不足。
6.2 模型自由度与过拟合
在网格搜索参数时,我曾遇到AIC一直下降、模型参数越来越多的现象。表面上拟合效果越来越好,但预测时几乎崩溃,这就是过拟合。
在Matlab的arima模型里,如果p和q设得过大,参数之间会出现近似共线性,估计结果变得极不稳定。一个明显信号是:拟合后的标准误越来越大,或者估计过程中出现警告信息。控制参数数量的一个有效办法是使用BIC准则,它对参数数量的惩罚比AIC更重,能帮你在小样本场景下自动选择更简洁的模型。
表:ARIMA模型参数过多时的典型诊断信号
| 信号 | 可能原因 | 对策 |
|---|---|---|
| 估计参数标准误差变大 | p+q过大,参数冗余 | 改用BIC准则或缩减p/q范围 |
| 预测置信区间异常宽 | 模型捕捉到噪声而非结构 | 检查残差是否还有相关性;降阶 |
| 拟合优度很高但预测误差很大 | 过拟合历史噪声 | 交叉验证;滚动预测评估真实精度 |
6.3 forecast结果异常的排查思路
如果你发现forecast输出的结果有明显异常,比如预测值掉到负值、置信区间变成NaN,先不要急着怀疑模型。按这个顺序排查:第一步,检查输入到forecast的Y0是否包含NaN,只要历史数据里有一个NaN,预测结果就可能会污染一串;第二步,确认模型定义中的差分阶数和实际输入的数据是否一致,如果你定义的是arima(p,1,q),但传入的数据已经是差分后的序列,相当于做了一次多余的差分操作;第三步,检查估计结果是否有收敛警告,如果estimate过程本身就提示收敛失败,forecast的结果自然不可靠。
我见过不少同学在Matlab里传参时弄混了“原始序列+模型内差分”和“先自己差分再传入模型”这两种方式。arima模型自带差分项,你直接传原始序列就行,它会自动在内部完成差分逻辑。如果你先手动差分,就应该把模型设为arima(p,0,q),否则会对差分后的序列再次差分。
6.4 区间覆盖率的自检方法
最后分享一个我特别推荐的自检方式:回测区间覆盖率。把数据集切成长度相同的多个窗口,用前面的部分训练模型,预测后面的部分,然后统计真实值落在95%置信区间内的比例。如果这个比例明显低于95%,说明模型对不确定性的估计偏乐观,区间太窄了;如果接近100%,说明区间太宽,模型几乎没有提供有效的信息量。
在电价预测的场景中,由于序列本身的尖峰特性和波动聚集性,95%的真实覆盖率往往只能做到85%到92%左右。这时候可以引入GARCH族模型或者直接对预测误差用经验分布来构建区间,效果会好很多。这也是从ARIMA起步后自然延伸的方向之一。
我个人在实际操作中的体会是,ARIMA模型的价值不只是在预测结果上,更在于它让你把整个时间序列的“性格”摸得很透。每做一次差分、每选一个参数,都是在对数据做一次深入的结构性理解。等这套流程跑通了,再去上LSTM、Transformer那些工具,你会发现自己的起点和直接从深度学习入手的同事完全不一样——你会带着统计直觉去看问题,而不仅仅是调包跑数。
后续如果想继续扩展这个项目,可以从三个方向入手:一是在ARIMA的残差上叠加GARCH模型,把电价的波动聚集性建模进去,这样置信区间就不再是常数方差的假设;二是把区间预测从参数化的正态分布扩展成分位数回归或者基于模拟的经验区间,应对电价的尖峰和非对称分布;三是引入外生变量,比如负荷、新能源出力、燃料价格等,升级成ARIMAX模型,让预测在电力市场场景下更具竞争力。这些方向都能在你现有的Matlab代码基础上直接演进,边际成本很低,但产出的深度和论文价值会提升一个台阶。