news 2026/10/15 5:37:07

基于ARIMA的电价预测:置信区间计算与Matlab实现详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于ARIMA的电价预测:置信区间计算与Matlab实现详解

去年帮一个课题做短期电价预测,拿到数据第一眼我就觉得这玩意儿跟心电图似的,忽高忽低毫无规律。但真正把序列拆开看,你会发现每天同一时段的电价之间有很强的“记忆”,昨天的低谷和昨天的尖峰,都会以某种形态在今天重现。这正是ARIMA这类时间序列模型发挥价值的地方——它不关心电价为什么涨,只关心涨跌之间的统计规律能不能延续下去。本文围绕基于ARIMA的电价预测展开,重点讲清楚置信区间的含义和Matlab实现方法,适合正在做电价预测课题、想用ARIMA做基准模型、或者刚接触时间序列建模的研究生和从业者参考。

我会把整个建模过程按照实际踩坑顺序展开:先看数据特征,再做平稳化处理,然后是定阶、估计、预测、区间计算,最后是一套可以直接改的Matlab主流程,以及几个让我印象深刻的翻车场景。

1. 电价数据有哪些“脾气”,ARIMA为什么能接住

1.1 电价序列的三个典型特征

第一条特征是非平稳性。电价在一天24小时内会形成明显的“峰—平—谷”结构,早高峰和晚高峰价格高,深夜价格低,周末整体低于工作日。均值在漂移,方差也在变,传统的线性回归拿这种序列没办法,但ARIMA天生带差分环节,可以先把这种结构性变化去掉再建模。

第二条特征是周期性嵌套。电价不仅有24小时的小周期,还有周内的工作日/休息日大周期。夏季和冬季的负荷特性也完全不同。严格的讲,纯ARIMA模型对周期性的处理能力有限,它主要依靠差分和高阶自回归项去“追”这些周期。实际操作时,要么把d加高,要么把数据按小时拆开建模,我后面会详细说。

第三条特征是尖峰毛刺。某个时段突然的负荷激增、极端天气、机组检修,都可能让电价瞬间冲到正常价的数倍。这类点对统计模型非常不友好,它会拉高残差方差,让置信区间整体变宽,同时也容易让模型估计偏掉。如何处理尖峰,是电价预测里必须做的取舍,不是简单删掉就能解决。

1.2 ARIMA模型的定位:不是万能,但够用

很多人在做电价预测时一上来就考虑深度学习、集成学习,仿佛不用神经网络就不算“研究”。但实际上,当你需要一个可解释、可复现、能算置信区间的基准模型时,ARIMA依然是性价比最高的选择。

对比来看:

  • 线性回归假设误差独立同分布,电价残差明显自相关,不满足。
  • 支持向量回归调参复杂,预测区间需要用分位数回归或bootstrap额外构建。
  • 神经网络效果好,但样本量不够时容易过拟合,而且预测区间要费劲地做多次蒙特卡洛。

ARIMA的逻辑是:把“今天的值”理解为“过去若干天的值”加上一组历史扰动加权组合的结果。它结构紧凑,参数数量少,样本量几千条以内完全能跑,而且Matlab的工具箱给得非常完整,建模、估计、预测、区间一条龙。

1.3 在课题中ARIMA通常扮演什么角色

我在实际项目里,ARIMA一般不是最终答案,但它是所有模型的“起跑线”。拿到一段历史电价,先用ARIMA垫底,出一个点预测结果和区间,再对比其他模型的精度和区间覆盖率,就能快速判断更复杂的模型到底值不值得上。

另外一个很重要的用途是给交易策略提供风险边界。电力市场化交易里,单纯知道“明天均价大概是多少”是不够的,更关键的是知道“价格落在什么范围内”。ARIMA给出的置信区间,尽管在很多情况下偏窄,但它至少给出了一个统计意义上的参考边界,配合场景分析可以用得很实在。

2. 数据准备与平稳化:先让序列“老实”下来

2.1 数据粒度选择与缺失值处理

电价预测的第一步是确定粒度。我建议先从小时粒度入手,因为绝大多数市场对外公布的结算电价都是小时粒度,数据获取容易,后续换15分钟粒度只需把周期长度改掉即可。

数据清洗要注意几点:

  • 缺失值不要直接删。用前几个同时间点的中位数补齐,或者用线性插值。删掉会造成周周期断点。
  • 重复时间戳要合并,保留最后一次有效值。
  • 检查单个时段连续多天完全相同的值,这通常意味着数据源有问题,需要回溯。

数据量上,如果做单条序列的宏观建模,至少要有600个以上观测点,也就是25天的数据。如果想按小时拆成24条序列分别建模,建议每条序列至少200个点,也就是至少200天历史。数据太少时,ARIMA的参数估计方差会很大,预测区间看起来很美,实际上毫无意义。

2.2 用ADF检验判断差分次数

ARIMA的“I”部分,也就是差分,是用来把非平稳序列变平稳的。差分的次数d不能靠肉眼拍脑袋,要用单位根检验来定。

Matlab里直接用Econometrics Toolbox的adftest:

[h, pValue] = adftest(prices);

h = 1表示拒绝单位根假设,序列已经平稳,d可以取0;h = 0表示存在单位根,需要差分。对原始电价序列而言,大概率h = 0,所以先做一阶差分:

diffPrices = diff(prices); [h1, pValue1] = adftest(diffPrices);

如果一阶差分后p值远小于0.05,那d = 1就够了。电价序列一般一阶差分后就能平稳,少数情况需要进行季节性差分,比如24步差分,才能彻底打掉日周期。

注意:adftest默认含常数项,实际做的时候可以对比有趋势项和没有趋势项两种情况。电价序列没有明显的确定性线性趋势,所以默认配置通常够用。

2.3 要不要按小时分拆成24条序列

这是做电价预测最容易纠结的问题。

方案A是把所有小时的数据拼成一条长序列,用ARIMA直接建模。优势是数据量大,能捕捉小时之间的连续性,代码简单;劣势是周期项会让模型阶数变得非常高,常常出现p和q都要到3以上才够,参数多,过拟合风险大。

方案B是把历史数据按小时拆开,例如所有00:00的数据组成一条序列、所有01:00的数据组成另一条,一共24条,各自独立建模预测。优势是避开了日周期问题,每条序列的规律更纯粹;劣势是数据量变成原来的二十四分之一,对数据历史长度要求高,而且忽略了相邻时段之间的关联。

我个人的习惯是:如果历史数据超过一年,优先按小时拆开,分别建模,精度通常更好。如果只有几个月的数据,就用单条序列加高阶差分,不要硬拆。

3. 定阶的逻辑:ACF/PACF肉眼识别加信息准则兜底

3.1 自相关图与偏自相关图的判读

序列平稳之后,就要确定AR项阶数p和MA项阶数q。工具是自相关图(ACF)和偏自相关图(PACF)。

Matlab两个函数很直接:

autocorr(diffPrices, 'NumLags', 48); parcorr(diffPrices, 'NumLags', 48);

判读规则很简单,但实际应用时容易被带偏:

  • PACF在滞后k阶处突然截尾,而ACF拖尾,对应AR(p),p就是截尾的阶数。
  • ACF在滞后k阶处截尾,PACF拖尾,对应MA(q),q就是截尾的阶数。
  • 两个都拖尾,说明模型需要AR和MA混合,这时候眼睛就不好使了,需要信息准则兜底。

电价差分序列的ACF和PACF经常出现“双拖尾”,还有一个非常常见的情况:ACF在滞后24阶处又出现明显峰值,这是日周期的残留信号。如果d=1后还有这种情况,你可能需要把d提到24,或者使用SARIMA结构。不过既然标题里强调的是ARIMA,我们优先用信息准则扫参数。

3.2 自动扫参定阶的Matlab脚本

这部分是很多人卡住的地方,我给出一个固定套路:遍历p和q,采用AIC或BIC做比较。AIC更偏重预测精度,BIC更偏重模型简洁,电价预测一般两个都算,优先选两者都认可的候选。

maxP = 4; maxQ = 4; d = 1; AIC = zeros(maxP+1, maxQ+1); BIC = zeros(maxP+1, maxQ+1); for p = 0:maxP for q = 0:maxQ Mdl = arima(p, d, q); try [EstMdl, ~, logL] = estimate(Mdl, prices, 'Display', 'off'); numParams = EstMdl.NumEstimatedParameters; [aic, bic] = aicbic(logL, numParams, length(prices)); AIC(p+1, q+1) = aic; BIC(p+1, q+1) = bic; catch AIC(p+1, q+1) = inf; BIC(p+1, q+1) = inf; end end end [minAIC, idxA] = min(AIC(:)); [minBIC, idxB] = min(BIC(:)); [pA, qA] = ind2sub(size(AIC), idxA); [pB, qB] = ind2sub(size(BIC), idxB);

两个注意点:

第一,参数范围不用设太大。电价数据即使有周期,纯ARIMA的p和q通常不会超过5。设到4已经足够,再大会出现数值不稳定,estimate经常报错。

第二,aicbic里传入的logL值必须是极大似然估计得到的对数似然值,estimate的第三个输出就是。NumEstimatedParameters包括常数项和方差,这样比手动数参数个数更准确。

3.3 残差诊断:定阶后不能省的一步

定阶之后必须做残差检验。很多人跑完estimate就直接forecast,后面区间不对才发现问题出在模型本身。

使用infer提取残差:

[residuals] = infer(EstMdl, prices);

然后做两件事:画残差的ACF和PACF,跑Ljung-Box白噪声检验:

figure; autocorr(residuals, 'NumLags', 48); parcorr(residuals, 'NumLags', 48); [hLB, pLB] = lbqtest(residuals, 'Lags', [5 10 20 30]);

如果残差的ACF里还有显著超出置信带的点,或者Ljung-Box检验返回h = 1,说明残差中还有结构,当前阶数不够,要继续增加p或q。如果残差近似白噪声,说明模型已经把历史信息提取得差不多了,可以进行预测。

这一步就是我说的“返工点”,宁可在定阶时多花几个小时,也不要带着病态模型跑到预测阶段。

4. 置信区间是怎么来的,Matlab里如何拿到它

4.1 预测区间背后的数学逻辑

ARIMA模型经过估计后,会得到一个残差方差σ²,以及一组描述序列动态结构的AR和MA系数。基于这些系数,模型可以把未来时刻的值写成历史扰动项的线性组合。预测第h步的值时,预测误差的来源是:未来尚未发生的随机扰动,以及累计误差。

误差的方差记作预测均方误差,Matlab里叫预测MSE,也就是forecast的第二个输出YMSE。在误差近似正态分布的假设下,95%置信区间的上下界就是:

点预测 ± 1.96 × sqrt(YMSE)

置信区间的含义是:如果同样的数据生成机制重复很多次,未来真实值有95%的概率落在这个区间内。注意是“重复数据生成机制”意义上的概率,不是“真实电价一定落在区间里”,这两个说法有本质区别。

4.2 forecast函数的输出与区间计算

Matlab的forecast函数用法非常简单:

numSteps = 24; [forecastY, ymse] = forecast(EstMdl, numSteps, 'Y0', prices);

forecastY是接下来24个时段的点预测值,ymse是与预测步长对应的均方误差序列。计算区间:

alpha = 0.05; zScore = norminv(1 - alpha/2); lower = forecastY - zScore * sqrt(ymse); upper = forecastY + zScore * sqrt(ymse);

默认情况下forecast的置信水平是95%,如果想算90%或者99%区间,把alpha换掉,重新查正态分位数就行。

有一点必须提醒:forecast返回的ymse是累积误差方差,它会随着预测步数的增加而变化。对平稳的ARMA部分,这个值会逐步收敛到序列的无条件方差,而不是无限膨胀。这一点很多人搞错,以为预测越远区间越宽,其实对平稳序列并不完全是这样。

4.3 为什么区间宽度不会一路膨胀

举个直观例子。对AR(1)模型,如果自回归系数是0.8,残差方差是σ²,那么一步预测误差方差是σ²,两步预测误差方差是σ²乘以(1 + 0.64),三步是σ²乘以(1 + 0.64 + 0.41),逐步增大。但这些项是收敛的,最终极限是σ²除以(1 - 0.64),也就是该序列本身的方差。

也就是说,远期预测区间会稳定在“历史序列一般波动范围”这个水平上,不会无限变大。这个特性在电价预测里非常有用:你无论如何预测30天后的电价,区间宽度都会接近历史全部波动的幅度,这从统计逻辑上是自洽的。当然,如果在模型里加入了差分项,比如d=1,那区间会随步长继续增长,这也是为什么非平稳序列不适合做太长期预测的原因。

5. 一套可以直接改着用的Matlab主流程

5.1 完整流程代码骨架

把前面几个环节拼成一个完整脚本,我通常按下面的结构组织:

%% 参数设置 stepAhead = 24; modelMaxP = 4; modelMaxQ = 4; %% 数据加载与预处理 % prices为列向量,按时间顺序排列 % 缺失值用前向填充或线性插值处理 prices = fillmissing(prices, 'linear'); %% 平稳性检验 [h0, p0] = adftest(prices); d = 0; if h0 == 0 d = 1; end %% 定阶循环 trainLen = length(prices) - stepAhead; trainData = prices(1:trainLen); testData = prices(trainLen+1:end); aicMat = inf(modelMaxP+1, modelMaxQ+1); bicMat = inf(modelMaxP+1, modelMaxQ+1); for p = 0:modelMaxP for q = 0:modelMaxQ Mdl = arima(p, d, q); try [EstMdl, ~, logL] = estimate(Mdl, trainData, 'Display', 'off'); numParams = EstMdl.NumEstimatedParameters; [aic, bic] = aicbic(logL, numParams, length(trainData)); aicMat(p+1, q+1) = aic; bicMat(p+1, q+1) = bic; catch end end end [minAIC, idxA] = min(aicMat(:)); [pOptA, qOptA] = ind2sub(size(aicMat), idxA); pOptA = pOptA - 1; qOptA = qOptA - 1; [minBIC, idxB] = min(bicMat(:)); [pOptB, qOptB] = ind2sub(size(bicMat), idxB); pOptB = pOptB - 1; qOptB = qOptB - 1; % 两个准则不一致时,优先选BIC推荐的更简洁模型 pOpt = pOptB; qOpt = qOptB; %% 最终建模与预测 FinalMdl = arima(pOpt, d, qOpt); FinalEstMdl = estimate(FinalMdl, trainData, 'Display', 'off'); [forecastY, ymse] = forecast(FinalEstMdl, stepAhead, 'Y0', trainData); zScore = norminv(0.975); lowerBound = forecastY - zScore * sqrt(ymse); upperBound = forecastY + zScore * sqrt(ymse);

这个骨架可以直接套任何电价数据,只需要替换prices的读入方式。我建议把定阶部分封装成函数,下次换数据时只改一个变量名,能省不少时间。

5.2 画图:区间阴影与真实值对照

预测结果画成图更有说服力。我的标准做法是同时画出历史尾部、点预测、区间阴影、真实值四条信息:

figure; hold on; % 区间阴影 xP = 1:stepAhead; fillX = [xP, fliplr(xP)]; fillY = [upperBound', fliplr(lowerBound')]; fill(fillX, fillY, 'b', 'FaceAlpha', 0.25, 'EdgeColor', 'none'); % 点预测 plot(xP, forecastY, 'r-', 'LineWidth', 1.6); % 真实值 plot(xP, testData, 'k--', 'LineWidth', 1.2); legend('95% 置信区间', 'ARIMA预测', '真实值', 'Location', 'best'); xlabel('预测时段'); ylabel('电价'); grid on; hold off;

FaceAlpha设成0.2到0.3之间最合适,太透明看不清,太深又盖住曲线。还有一个小细节:lecture和真实值最好用不同线型,因为实际数据里它们常常非常接近,都用实线会黏在一起。

5.3 几个评估指标:RMSE、MAPE、PICP

只看图不够,还要有量化指标。我做电价预测时必看三个:RMSE看总体误差,MAPE看相对误差,PICP看区间覆盖率。

% RMSE rmse = sqrt(mean((forecastY - testData).^2)); % MAPE mape = mean(abs((forecastY - testData) ./ testData)) * 100; % PICP:区间覆盖率 inside = (testData >= lowerBound) & (testData <= upperBound); picp = sum(inside) / length(testData) * 100;

PICP这个指标很多人忽略,但它对置信区间质量是直接检验。如果95%置信区间实际覆盖率只有60%,说明模型对不确定性严重低估,区间“看起来漂亮”但没用。好的模型,PICP应该在95%左右,略低一点可以接受,低太多就要回头查模型假设是否被破坏(比如尖峰值没有处理)。

6. 实测中的翻车现场与应对办法

6.1 遇到尖峰电价时的取舍

做电价预测绕不开尖峰。一次真实的电价尖峰可能就是正常值的3到5倍,不处理它,模型的残差方差会被越抬越高,置信区间范围被迫拉大,而正常时段的预测精度开始下降。

处理尖峰常用三种办法,各有取舍:

处理方式优点缺点
直接删除残差方差回归正常信息损失,可能低估真实波动
缩尾截断保留趋势信号,压制极端值阈值选择主观,需谨慎调整
做对数变换压缩数量级差异预测值要反变换,区间不再对称

我的做法是:先判断尖峰的比例。如果占总样本1%以内,就做缩尾截断,把超过99.5%分位数的值压到分位数处。如果尖峰比例高,超过3%,则做对数变换。对数变换的代价是预测区间在反变换后是偏态的,但偏态在电价场景本身更符合实际——电价的上涨弹性比下跌弹性大得多。

6.2 节假日和突发事件让模型失灵

ARIMA本质上是“历史规律外推”,它假定过去出现的模式在未来会重复。节假日打破了这种假设:某个法定假日的工作/休息模式、负荷曲线、电价形态都可能与平时完全不同。模型没有见过这种形态,预测自然崩盘。

解决策略比较务实:节假日样本从训练集中分离出来单独建模,或者干脆在预测期跳过节假日后接续预测。如果要考虑节假日效应,更合适的是加入外生回归项的ARIMAX,但这已经超出标准ARIMA的范围,我这里不展开。

突发事件更麻烦,比如极端天气导致的全网负荷突增,模型无法提前预测。遇到这种情况,置信区间反而成了最好的预警工具:如果预测区间尾部被真实值“击穿”,说明市场存在模型外信息,此时人工干预比调整参数重要。

6.3 方差不恒定导致区间失真

ARIMA假设残差方差恒定,但电价残差明显存在“高峰时段方差大、低谷时段方差小”的特征。这个假设被破坏后,整体置信区间精度会下降:高峰时段真实值经常越界,低谷时段区间又显得过宽。

想缓解这个问题,有两个方向。方向一是分时段建模,把24小时拆开分别估计,每个时段有自己的残差方差,区间宽度自然不同。方向二是引入GARCH结构,给残差方差建一个动态模型,但Matlab里需要手动组合ARIMA和GARCH,代码复杂度上了一个台阶。

注意:如果只是做课题研究,分时段建模就够了,能做到每个预测时段对应自己方差的区间,已经可以明显提升PICP。

6.4 模型多久重估一次比较合适

ARIMA参数不是一劳永逸的,市场竞争、用电结构变化都可能导致模型系数漂移。我测试过不同重估频率:

  • 每天重估:预测最准,但计算开销大,且频繁重估可能让参数没机会收敛。
  • 每周重估:精度下降不多,计算量适中,实际项目中我用的最多。
  • 每月重估:计算省,但参数更新滞后,碰到季节切换时精度明显掉。

电价的季节性变化意味着固定参数的模型在季节转换期会有一段“错位期”。建议至少每周重估一次,同时每次加入最新一周的观测数据,让模型保持新鲜。

另外一个容易忽略的点:滚动预测时需要重新估计模型,而不是直接在旧模型上继续做forecast。因为ForecastMdl里的参数是用旧数据估计的,新旧序列结构一旦不同,预测偏差就会累积。

最后分享一点我的实际体会

跑完这套流程最大的感受是:ARIMA在电价预测里的价值不完全在点预测精度,而在它给了一个稳定、透明、可解释的不确定性度量。很多复杂模型点预测确实更好,但它们的区间要么需要大量计算才能得到,要么根本没法解释清楚。而对交易决策而言,一个可信的区间比一个漂亮的点更重要。我建议你把这里的代码跑通之后,重点看一下PICP这个数,如果实际覆盖率明显低于置信水平,别急着调模型阶数,先去处理尖峰和节假日这两个变量,它们对区间失真的影响比想象中大得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 5:36:48

提示词库系统化整理:从随手问到工程化调用的高效实践

1. 提示词库为什么值得系统化整理1.1 从“随手问”到“工程化调用”的认知转变大多数人用对话式AI的方式还停留在“随手问”阶段&#xff1a;打开对话框&#xff0c;敲一句“帮我写个周报”&#xff0c;然后对着生成结果挑挑拣拣&#xff0c;改半天还不如自己写。问题不出在模型…

作者头像 李华
网站建设 2026/10/15 5:36:46

对称加密与非对称加密:原理、选型与工程实践

1. 项目概述与核心思路拆解1.1 加密是什么&#xff0c;我为什么会研究这个先聊聊我为什么会专门写一篇关于对称加密和非对称加密的文章。这几年做后端开发&#xff0c;接触过的系统和数据链路越来越多&#xff0c;发现一个很现实的问题&#xff1a;很多开发者对加密的理解停留在…

作者头像 李华
网站建设 2026/10/15 5:36:01

LLM request timed out排查指南:OpenClaw接入第三方API超时解决方案

LLM request timed out 这个报错&#xff0c;接第三方 API 的朋友应该不陌生。OpenClaw 本身是个开源智能体框架&#xff0c;跑起来很轻&#xff0c;但一旦把模型源切到第三方兼容接口&#xff0c;超时就像定时炸弹一样时不时爆一下。这篇文章把我自己调通的全过程写出来&#…

作者头像 李华
网站建设 2026/10/15 5:35:38

AI数学手稿解析与大模型推理环境搭建指南

我不能根据该输入生成符合要求的博文。原因如下&#xff1a;输入内容不满足基本输入格式要求。根据指令&#xff0c;必须严格提供以下四部分结构化信息&#xff1a;项目标题: [标题] 项目正文: [通常比较零散、不完整的原始描述&#xff0c;可是任意领域内容] 关键词: [关键词1…

作者头像 李华
网站建设 2026/10/15 5:35:20

AI写作的最后一公里:文档导出自动化链路拆解

最近一年我和不少做内容的朋友聊&#xff0c;大家几乎都有同一个感受&#xff1a;AI写东西越来越快&#xff0c;但把AI写完的内容变成一份能交付的正式文档&#xff0c;反而成了整个流程里最磨人的环节。这个“最后一公里”涉及文档导出、格式转换、排版规范&#xff0c;很多人…

作者头像 李华