
简介一套基于支持向量机的短期发电市场电价预测完整实现面向电力市场分析、量化交易与机器学习交叉领域的研究者与工程师。核心围绕自回归SVM模型构建与增强变量筛选采用德国/奥地利Phelix指数日度数据进行验证200天外样本预测准确率达76.12%可作为电价方向预测任务的参考基线。压缩包共8个文件包含3个Matlab脚本、2个mexw64编译模块、测试数据xlsx/mat、readme说明整体仅542KB轻量易部署。脚本覆盖特征构造、并行训练、方向预测与误差率计算等环节mexw64可加速核函数运算readme提供运行说明数据文件便于直接复现。已有532人学习下载适合具备一定机器学习基础、希望快速在Matlab中上手SVM电价建模的读者也可基于现有代码调整特征集与核参数迁移至其他电力市场场景。1. 基于SVM的短期电价预测为什么这个老模型依然是市场分析的第一课做电力市场分析的人大概率都翻过这个标题的压缩包。它听起来像是学生毕设但真正跑过一遍数据的人会发现SVM做短期电价预测并不是什么过时玩具——在样本量只有几百到几千、特征维度不过十几维的典型场景里它的泛化能力依然吊打很多花哨的深度学习模型。这个项目的核心是用历史负荷、电价、气象或日历特征训练一个支持向量回归模型去预测未来24小时或下一交易时段的市场出清价。它能解决的现实问题很具体——帮售电公司报曲线、帮电厂决策机组启停、帮大用户判断什么时候买电划算。适合谁刚入门的电力数据分析师、做量化交易策略的在校生还有那些想把机器学习落到能源行业但又不想一开始就碰LSTM和Transformer的工程师。这篇文章不给你贴论文直接讲怎么把这份研究报告变成你自己能跑、能调、能解释的预测管道。2. 先把预测问题框成SVM能吃的样子特征工程与目标编码2.1 为什么是SVR而不是分类器电价预测本质上是回归问题很多人看到SVM第一反应是分类但电价预测的标签是连续价格所以核心模型是支持向量回归Support Vector Regression, SVR。SVR的思路和分类SVM正好相反分类是最大化间隔回归是让尽可能多的样本落在一条管道epsilon-tube内管道外的点才计入损失。这个epsilon参数直接决定了预测的宽容度——设大了曲线平滑但会钝掉尖峰电价设小了又会被异常值牵着走。我一般先看训练集价格的标准差epsilon取标准差的5%到10%作为起点再交叉验证微调。回归问题的另一个关键是目标变量的分布。现货市场电价有两个臭名昭著的特征尖峰和负价。欧洲很多市场负电价已经不是新闻国内现货试点偶尔也会出现极端低价。如果你的SVR直接拿原始价格训练支持向量会被这些极端样本占据导致普通时段预测偏得离谱。常见做法是先用分位数截断或者对数变换把分布拉正我习惯用np.log1p(clip(price, lower, upper))其中lower设为价格分布的1%分位数upper设为99%分位数。预测完再指数变换还原代价是尖峰一定被压低但对于交易决策来说低估尖峰比高估常态价格危害小得多。2.2 特征矩阵怎么搭从原始市场数据到监督学习格式这个项目的压缩包里通常会有一份历史交易数据字段至少有日期、交易时段15分钟或1小时、系统负荷、电价、可能还有新能源出力或机组检修计划。要喂给SVM第一步是把它转成监督学习格式——用过去几天的同一时段特征预测今天的价格。这里有个电力市场特有的季节性问题凌晨3点和晚高峰8点的价格规律完全不同所以同一套权重不能直接跨时段共用常见做法是为每个交易时段单独训练一个模型24小时就24个模型或者把hour和weekday做one-hot编码让模型自己学。我常用的特征模板分三块滞后特征前1天、前2天、前7天同一时段的电价和负荷捕捉周期性滚动统计过去3小时平均负荷、过去24小时电价中位数捕捉趋势和水平日历特征小时、星期几、是否节假日节假日负荷曲线完全变样。代码实现大概是这个形式import pandas as pd import numpy as np def build_features(df, target_colprice, lags[1, 2, 7, 24]): df df.sort_values([date, hour]).reset_index(dropTrue) for lag in lags: df[fprice_lag_{lag}] df.groupby(hour)[target_col].shift(lag) df[fload_lag_{lag}] df.groupby(hour)[load].shift(lag) df[load_rolling_mean_3h] df.groupby(hour)[load].transform( lambda x: x.rolling(3, min_periods1).mean() ) df[price_rolling_median_24h] df.groupby(hour)[target_col].transform( lambda x: x.rolling(24, min_periods1).median() ) df[hour_sin] np.sin(2 * np.pi * df[hour] / 24) df[hour_cos] np.cos(2 * np.pi * df[hour] / 24) df[is_holiday] df[date].isin(holiday_list).astype(int) df df.dropna() return df这里为什么要按hour分组做shift因为电价序列是按时段排列的如果不分组直接shift(1)会把前一天23点的价格当成当天0点的滞后值特征顺序就乱了。hour_sin和hour_cos是电力负荷预测和电价预测里的标准做法因为23点和0点在数值上只差1但在语义上是两个完全不同的时段用三角函数编码能把这种环形连续性表达出来。滚动均值窗口设3小时是经验值如果你的交易时段是15分钟粒度这个窗口一般设成4或8个点要以你的数据频率为准。3. 模型训练与三个必调参数C、epsilon、gamma怎么设才不翻车3.1 RBF核的gamma决定模型是欠拟合还是过拟合的分水岭SVR用RBF核的时候gamma控制的是单个样本的影响力半径。gamma小每个样本只影响很近的范围决策函数平滑但容易欠拟合预测曲线会是一条几乎平的线gamma大模型会认真拟合每个训练点训练集误差很好看测试集一预测就崩。这个参数在sklearn里默认是1/n_features在电价预测这个场景几乎总是太小——因为特征里既有量纲很大的负荷几千MW又有0/1的节假日标记默认gamma会被大数值特征主导等于没用。我一般先把特征做标准化再让gamma在[0.001, 0.01, 0.1, 1]里扫。一个血泪经验如果发现网格搜索出来的最优gamma大于1你的特征标准化多半没做干净或者特征维度太少只有两三个特征时gamma确实会偏大。这种情况下模型对噪声的拟合能力很强但换一个月的测试数据就露馅。你可以故意把gamma设小一个数量级看验证集误差变化——如果只变差2%以内说明模型主要还是靠低维趋势在预测gamma大只是锦上添花可以用更小的值换取稳定性。3.2 C和epsilon的协同一个控制压力一个控制容忍度C是误分类惩罚系数在SVR里是对管道外样本的容忍度。C越大模型越努力把训练样本塞进管道内支持向量越多模型越复杂。C太小模型可能直接躺平所有点都在管道外预测结果趋近于一个常数——这在电价预测里很常见因为价格波动剧烈模型觉得猜均值损失最小。判断C是否太小有个笨办法看支持向量的比例如果超过80%那基本就是C给的太紧或者数据噪声太大模型在硬记答案。epsilon是管道的半宽。它和C是一对冤家epsilon变大管道变宽落在管道内的点变多需要的支持向量变少模型更平滑epsilon变小模型必须更精细地拟合每个点。在电价场景里我习惯先固定epsilon为价格标准差的5%然后只调C和gamma因为同时调三个参数网格会膨胀得很厉害比如3x4x448组每组还要交叉验证数据量大一点就跑到天荒地老。粗调完C和gamma之后再把epsilon往小调一档比如从5%降到3%看验证集是否明显变好——如果只变好一点点说明模型已经到瓶颈不要为了训练集rmse好看而牺牲泛化。3.3 训练一次要多久规模与耗时预估很多人以为SVM很慢其实在电价预测这个体量上完全不是问题。假设三年历史数据每小时一条一共约26000条切分后训练集大概20000条特征维度12维RBF核SVR的训练时间在sklearn的libsvm实现下大约是几秒到几十秒取决于C和gamma的复杂度。真正慢的是交叉验证——5折乘以几十组参数可能要跑十几分钟。所以我通常的做法是先用一个小的随机子集5000条做粗筛确定C和gamma的大致量级再用全量数据做最后一轮微调。这样总时间控制在10分钟以内比深度学习省心得多。4. 把SVM预测结果落地成交易决策还原真实价格、误差评估与可视化4.1 从标准化逆运算到价格还原一个都不能少训练SVR前对特征做了标准化预测出来的结果也处于标准化后的空间必须逆运算才能得到真实价格。如果目标变量是log1p变换过的要先expm1再逆标准化。这个顺序别搞反如果先逆标准化再expm1结果完全不对。我用sklearn的StandardScaler时习惯把scaler保存在joblib文件里预测阶段加载同一个scaler不要在测试脚本里重新fit——测试集的scaler和训练集的scaler不一致是线上预测误差暴增的隐藏原因之一。import joblib from sklearn.svm import SVR from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split # 假设 X_features, y_price 已经构建好 X_train, X_test, y_train, y_test train_test_split( X_features, y_price, test_size0.2, shuffleFalse ) scaler_X StandardScaler().fit(X_train) X_train_s scaler_X.transform(X_train) X_test_s scaler_X.transform(X_test) scaler_y StandardScaler().fit(y_train.values.reshape(-1, 1)) y_train_s scaler_y.transform(y_train.values.reshape(-1, 1)).ravel() model SVR(kernelrbf, C10.0, gamma0.05, epsilon0.01) model.fit(X_train_s, y_train_s) y_pred_s model.predict(X_test_s) y_pred scaler_y.inverse_transform(y_pred_s.reshape(-1, 1)).ravel() joblib.dump(model, svr_price_model.pkl) joblib.dump(scaler_X, scaler_X.pkl) joblib.dump(scaler_y, scaler_y.pkl)训练时用shuffleFalse保持时间顺序这样测试集是真正的未来能验证模型的时间泛化能力。scaler_y只对训练集的y做fit测试集的y不参与防止信息泄漏。joblib保存三个文件是必要的svr的模型参数、特征scaler、目标scaler。很多复制这个项目的人只存了model.pkl上线时才想起来目标变量做过变换又找不到scaler只能靠反推非常被动。4.2 评估指标要选对MAE、RMSE和MAPE在电价上的局限电价预测评估有三件套——MAE、RMSE、MAPE但各有各的坑。RMSE对尖峰敏感一个1000元/MWh的极端价格就能毁掉整周的RMSEMAPE在价格趋近于0的时候会爆炸你除以一个非常小的数MAE最稳健但不是交易决策最关心的。我通常看两个数整体MAE和加载尖峰时段的MAE——所谓尖峰时段是价格超过当天95%分位数的时段因为市场风险主要来自这些时段。如果你做预测只是为了写研究报告MAE够用了但如果是给交易系统用还要加一个方向准确率预测价格变动方向和实际变动方向是否一致。这个指标SVM不一定好因为SVR的回归目标是最小化误差不是最大化方向准确率。但依然值得算因为方向对了才能决定买还是卖幅度小一点只是赚少亏少方向错了就是真金白银的损失。4.3 可视化哪些图能让模型缺陷无处藏身第一个图是时序对比图——预测曲线vs实际曲线横轴是时间能直观看到模型有没有滞后。SVM这类非序列模型最常见的缺陷是滞后实际价格拐弯了模型还顺着前面的趋势直走。这通常是因为滞后特征权重太高模型本质上是在抄前一天而不是理解今天。第二个图是散点图——横轴实际价格纵轴预测价格理想情况是点全部落在45度线附近。如果一个点群在右上角高价格区间严重偏离45度线说明模型对尖峰钝化如果点群沿着水平线分布说明模型对某些特征不敏感。第三个图是残差直方图——如果残差呈现明显的双峰甚至多峰说明数据里存在模型没有捕捉到的模式通常是工作日和周末混在一起训或者负荷的周周期性没有被lag7完全表达。5. 避坑指南这五个问题让SVM电价预测翻车率飙升从现象到根因一次说清5.1 现象训练集误差0.5%测试集误差15%——过拟合但不是gamma的锅原因时间序列切分时用了随机抽样而不是按时间顺序划分。很多人构建数据集后直接train_test_split(test_size0.2)默认shuffleTrue结果是测试集里混着训练集时间段的数据模型等于见过测试样本。解决办法切分时强制shuffleFalse或者用TimeSeriesSplit做时间序列交叉验证。另外如果你特征里带了price_lag_1这种当日滞后特征随机切分等价于把同一枚硬币的正反面拆到训练测试里肯定露馅。5.2 现象预测曲线整体平移——高峰低一些低谷高一些均值重合但方差变小原因epsilon设得太宽模型偷懒很多样本直接落在管道内不参与回归只有大偏差的点才成为支持向量结果整体被缩水。解决办法把epsilon从5%降到2%重新交叉验证。如果还不够检查是否对目标变量做了太多平滑——比如用了rolling mean做标签那预测曲线天然会比真实曲线平缓。5.3 现象负荷特征明明没变价格预测却剧烈抖动原因日历特征one-hot编码后C和gamma设得过大模型把是不是星期一这种二元特征当成决定性变量。这是特征尺度不一致的问题——负荷用MW单位是几千hour_sin和hour_cos只有[-1,1]节假日是0/1。没标准化时SVM会把这些差异当成权重分配依据。解决办法先训练前打印np.unique(X_train_s.mean(axis0))确认每个特征的标准差在同一量级如果某个特征标准差恰好是0说明它差分后全是常数删掉。5.4 现象模型预测出来的价格有负值而真实市场永远是正的原因训练集包含了历史上真实的负电价很多现货市场存在模型学到了这种分布。但如果你所在的市场不允许负电价比如某些合约市场负价格预测会让规则引擎直接拒单。解决办法一个简单粗暴的方法是预测后clip(0)但更细致的是在训练目标上做变换——把price映射到log(price - min_price 1)其中min_price取训练集最低价再往下留5%余量。这样做既能保留负价信息又不会让预测值跌破物理下限。5.5 现象月初模型误差突然变大其他时间都正常原因现货结算价往往在月初调整交易参数比如月度合约比例导致价格水平和规律跳变。SVM没有在线学习能力交易规则变了它不知道。解决办法两个思路——第一滑动窗口训练只取最近90天数据训练让模型跟上最新规律第二加入月份序号或该月第几天特征让模型至少知道时间位置。第二种方法我实测效果一般但有人用看你的数据是否稳定。6. 进阶技巧用滚动预测验证模型构建一个简单的择时参考这个项目能改造成的最实用形态是做滚动预测。所谓滚动预测不是一次性把测试集全部预测完而是每次预测一个点然后把真实值加入训练集或者滑动窗口再预测下一个点。这样做更贴近真实交易环境——你在上午10点预测11点的价格到了11点拿到真实价格后可以顺手把它喂进模型更新12点的预测。由于SVM重新训练成本低几千条样本重新fit只要几秒我经常在脚本里每预测一个时段就重新训练一次这样模型能自动适应市场涨了的现实。具体落地时我会搭一个很小的调度逻辑def rolling_predict(df, model, scaler_X, scaler_y, horizon1): preds [] for i in range(horizon, len(df)): train_df df.iloc[:i] test_df df.iloc[i:i1] # 重新训练 model.fit(scaler_X.transform(train_df[X_COLS]), scaler_y.transform(train_df[TARGET_COL].values.reshape(-1,1)).ravel()) test_scaled scaler_X.transform(test_df[X_COLS]) pred_s model.predict(test_scaled) preds.append(scaler_y.inverse_transform(pred_s.reshape(-1,1)).ravel()[0]) return preds这里的关键是scaler也随着新数据更新或至少保持不变——我倾向于特征scaler不更新因为特征分布相对稳定但目标变量scaler要随训练窗口重新fit因为价格水平会漂移。如果你发现滚动模型在季度交接时依然报出离谱预测检查一下是不是节假日特征没有更新到下一年的法定节假日列表这是最隐蔽的故障。最后一个习惯是给每次预测记录置信标记。SVR的预测是点估计没有自带置信区间但你可以用交叉验证的残差标准差做一个经验区间——比如预测值为200元/MWh残差标准差为20元那就标注180-220区间。交易系统拿到区间比拿到单点数有用得多它能用来判断要不要下重注。希望这个思路能帮你在自己的数据上跑出比原项目更稳的结果也少踩几个我踩过的坑。本文还有配套的精品资源点击获取