资讯动态

基于CVaR的省间交易商最优购电模型:Matlab+Cplex实现

发布时间:2026/10/6 9:55:13 来源:尧图企业网站定制
省间交易商最优购电模型这个词在电力市场仿真圈里不算新鲜但真要用Matlab加Cplex跑出一版能解释问题、能支撑分析结论的实现还是有不少门道。我最近处理两级市场环境下的购电策略问题时恰好需要一个“计及风险”的决策框架交易商代表省内用电需求同时参与省间电能量交易和省内市场的组织采购而两边价格都存在明显波动。把这套逻辑写成优化模型后求解规模虽然不大但对建模方式和求解器配合的要求不低。这篇文章把我的完整做法拆开来讲重点放在风险度量的选型、模型主干的数学化、Matlab调用Cplex的工程细节以及结果解读和实战排雷上。这篇内容适合两类人看。一类是电力市场方向的研究生手里需要一个能直接跑通的中等规模算例来做方法验证另一类是省级交易中心或大型售电公司的策略分析人员需要在省间、省内两个层级市场上做购电组合优化同时要向上解释清楚“为什么这样买电”。如果你是刚接触Matlab和Cplex的新手建议先跑一个线性规划练练手再来看这套随机场景下的建模框架否则后面部分内容会有一定门槛。1. 省间交易商的真实处境两级市场下购电决策的核心矛盾1.1 两级市场中的角色定位要理解模型得先理解省间交易商在干什么。所谓“两级市场”第一级是省间电力市场交易对象是跨省富余电力送受双方在交易机构撮合下成交形成省间成交价格第二级是省内电力市场省内各发电机组按自己的报价曲线出清形成省内价格。省间交易商的角色比较特殊它不单纯是发电方或用户而是代表省内购电侧在省间市场统一购电的主体本质上是在替全省负荷当“买手”。这个买手买到的电要通过省间联络线和省内主干网送到用户侧所以它的决策不能只盯着省间价格看还要同时回应省内负荷曲线、省内备用要求和输电阻塞情况。在同一时刻它可能一边在省间市场买入跨省电一边在省内市场组织本地采购还要考虑来水、风电、光伏出力的随机波动。把这些因素全部压进一个模型就是在做购电组合优化在哪个市场买、买多少、愿意为更低价格承担多大的风险。1.2 三个必须同时平衡的决策维度我归纳下来交易商的购电决策必须同时处理三个维度任何一个单独拿出来都会让结果走偏成本维度。省间价格和省内价格经常不同步部分时段跨省电可能明显低于省内边际机组出清价但也可能因为通道阻塞出现倒挂。模型要做的是在“便宜但可能买不到”和“贵但确定性高”之间做全局取舍而不是简单选便宜。供需匹配维度。省间购入电量加上省内购入电量要精确匹配负荷预测值同时留出合理备用。负荷预测本身有误差新能源出力又有随机性所以电量平衡不能当成一个确定等式而要通过多场景构造来处理。风险维度。成本最低的购电方案往往把仓位都压在价格最低的时段和市场上可一旦这些时段的省间价格上浮或通道受阻购电成本会大幅偏离预期。所以风险度量必须进目标函数比如要求“95%置信水平下的极端成本不超过某个上限”否则一次极端行情就能让月度利润全部吃掉。这三个维度互相牵制追求成本最低风险必然上升风险约束压得太紧被迫在省内买高价电成本又下不来。把这层关系落到优化模型里就成了带随机场景的均值-CVaR问题。2. 度量风险为什么CVaR比方差和VaR都更贴合交易商的诉求2.1 为什么期望成本模型不够用如果目标函数只写“购电成本的期望最小”模型会给出一个很激进的方案把所有电量投到期望价格最低的市场和时段。这个方案对价格震荡几乎没有抵抗力。现实中电力市场的价格波动有明显的厚尾特征极端价格事件虽然概率不高但单次冲击就可能导致购电成本超过预算30%甚至更多交易商运营时根本不敢只看期望值。把方差作为风险惩罚项是很多初学者会走的路线但方差有一个尴尬的问题它对“低于预期的好消息”和“高于预期的坏消息”是对称看待的。电力购电场景里交易商只关心损失端不希望因为偶尔出现的便宜电价而在优化目标里“被扣分”。因此需要只度量下行风险的指标把注意力集中在成本分布的左尾还是右尾看你怎么定义损失。这里取购电成本为损失那么要压住的就是高成本那条尾巴。2.2 从VaR到CVaR的取舍风险价值VaR的定义很直观在给定置信水平α下最坏情况下不超过某个分位数的损失值。交易商说“95%条件下购电成本不超过预算的120%”用的就是VaR语言。但VaR有两个硬伤。第一它不满足次可加性不是一致性风险度量这意味着组合分散化之后的风险反而可能被高估与“组合可以降低风险”的基本常识冲突。第二VaR完全不关心超过分位点之后那条尾巴的形状两个购电方案可能VaR相同但一个尾部损失平缓另一个尾部深度下探风险差异极大。条件风险价值CVaR补上了这个缺口它定义为损失超过VaR值的条件期望通俗讲就是“一旦突破95%分位线平均要亏多少”。CVaR满足次可加性、单调性和正齐次性是电力市场风险研究中更常用的一致性风险度量。更关键的是CVaR可以通过辅助变量线性化不会破坏线性规划的可解性这一点对Cplex求解非常友好。所以我在模型目标里使用了期望购电成本加风险权重乘CVaR的经典结构用一条公式同时表达成本和风险偏好。3. 模型主干的数学化拆解决策变量、目标函数和核心约束3.1 决策变量与目标函数模型要落地先得把决策变量定义清楚。我把一天分成若干个交易时段t在场景s下省间交易商可以做的决策有三组在省间市场买入电量x_{s,t}对应省间成交价格p_{s,t}在省内市场买入电量y_{s,t}对应省内出清价格q_{s,t}在双边合同和集中竞价都无法满足需求时通过高价补电z_{s,t}兜底这部分单价显著更高。每个场景s下购电总成本C_s是三个市场电量支出之和。目标函数不再是简单的期望成本最小化而是期望成本加上风险权重λ倍的CVaR风险权重λ由交易商的风险偏好决定。完整形式如下min (1/S) * Σ_s C_s λ * ( η (1 / ((1 - α) * S)) * Σ_s u_s ) 其中 C_s Σ_t ( p_{s,t} * x_{s,t} q_{s,t} * y_{s,t} r_{s,t} * z_{s,t} )这里的η是辅助变量对应VaR的近似值u_s是场景s超过η的部分两者一起把CVaR变成线性表达。α是置信水平S是场景总数。公式里那个1/(1-α)的系数是CVaR定义里“尾部期望”的分母项初看觉得绕多看两遍就能习惯。3.2 核心约束与CVaR线性化约束条件按功能可以分成四组。第一组是每个场景、每个时段的电量平衡省间购电、省内购电和兜底补电之和必须覆盖负荷需求D_t和备用容量R_t第二组是省间通道传输上限跨省购电不能超过联络线容量第三组是省内购电上限省内可采购量受电网可用容量约束第四组是CVaR的辅助约束保证u_s能正确捕获尾部损失。电量平衡: x_{s,t} y_{s,t} z_{s,t} D_t R_t 省间上限: 0 ≤ x_{s,t} ≤ T_t 省内上限: 0 ≤ y_{s,t} ≤ G_t CVaR线性化: u_s ≥ C_s - η, u_s ≥ 0这套公式最关键的地方在于电量平衡约束用的是等号也就是每一层市场买入的电量必须刚好满足负荷和备用。实际项目里如果某些时段省内供给吃紧求解器会通过高价补电z_{s,t}来保证可行所以目标函数里必须把高电价设置为一个足够大的惩罚值但也不要大到失真否则高价补电时段会异常扎眼。场景生成方面我用的是比较标准的样本均值近似SAA思路从历史价格预测误差和负荷预测误差的分布中抽样生成S个等概率场景。实际中几百个场景足够让期望成本收敛初期调试用30到50个场景即可跑正式算例再提高到300到500个。这样既能保证统计精度又不至于让求解时间膨胀到难以接受。4. Matlab调用Cplex做求解的工程细节Yalmip建模与求解器调优4.1 为什么选择Yalmip作为建模中间层Matlab下面是直接调Cplex API还是用Yalmip做中间层我在这两种方式之间来回折腾过最后的结论是Yalmip更合适。直接调Cplex API的优势是省掉一层转换开销但代价是要手写大量的矩阵系数拼接场景数一多光是构建A矩阵就非常痛苦而且公式改动时容易在索引对位上出bug。Yalmip允许像写数学公式一样声明变量和约束模型可读性高变更场景数或新增约束只需改几行求解时通过sdpsettings指定用Cplex后端即可。核心求解代码骨架大致如下% 参数设置 T 24; % 交易时段数 S 500; % 场景数 alpha 0.95; % 置信水平 lambda 0.3; % 风险权重 % 场景化数据 p prov_inter_price; % 省间价格S x T q prov_intra_price; % 省内价格S x T D load_profile; % 负荷需求T x 1 R reserve_profile; % 备用需求T x 1 % 决策变量 x sdpvar(S, T, full); % 省间购电量 y sdpvar(S, T, full); % 省内购电量 z sdpvar(S, T, full); % 高价补充电量 eta sdpvar(1, 1); u sdpvar(S, 1, full); % 场景总成本按行加总 C sum(p .* x q .* y 300 * z, 2); % 目标函数期望成本 lambda * CVaR objective sum(C) / S lambda * (eta (1 / ((1 - alpha) * S)) * sum(u)); % 约束 Constraints []; Constraints [Constraints, u C - eta, u 0]; Constraints [Constraints, x y z repmat(D R, 1, S)]; Constraints [Constraints, 0 x T_limit]; Constraints [Constraints, 0 y G_limit]; Constraints [Constraints, z 0]; % 求解 ops sdpsettings(solver, cplex, verbose, 2); result optimize(Constraints, objective, ops);这段代码里的repmat转置那行是把负荷曲线从T×1扩展成S×T后再转置确保和x/y/z的维度对齐。维度问题是用Yalmip最容易犯的错所以我会特意把每个变量的维度写在注释里排查时先从维度下手。4.2 求解器参数调整与几个容易忽视的性能开关基础模型是线性规划如果用纯连续变量Cplex求解通常很快主要瓶颈在场景数和约束条数的乘积。实际操作中我把verbose调到2可以观察预求解阶段是否自动消掉了多余变量。预求解在Cplex里的作用比很多人想象的大场景数据存在大量零元素时预求解能显著压缩矩阵规模。如果模型后续扩展到机组组合需要引入0/1启停变量那时问题就变成MILP速度会明显下降。我的建议是先用连续变量版本跑通逻辑再逐步加入整数约束。加入整数后要关注两件事一是MIP间隙容忍度二是求解时间上限。Cplex默认的MIP间隙是0意味着要在绝对最优解附近磨对大规模场景很不划算。我通常在ops.cplex.mip.tolerances.mipgap里设置1e-3或1e-4能接受千分之一的次优偏差求解时间可以从分钟级压到秒级。另一个容易被忽略的坑是Cplex的内存。场景数突破800时约束矩阵会变得相当大如果机器内存有限可以启用presolve参数的节点文件压缩功能让Cplex在需要时把节点信息写入磁盘而不是全部驻留内存。不过这只在复杂整数模型里触发纯LP基本用不上。5. 数值实验与结果解读风险偏好怎么改变省间和省内购电比例5.1 测试场景设置为了验证模型行为是否符合预期我构造了一组典型日数据做算例。省间市场在凌晨和午间时段价格偏低但波动大存在明显的负价格时段省内市场价格相对平稳但整体水平略高负荷高峰出现在晚上备用需求在高峰前两小时达到顶点。场景数取500置信水平0.95风险权重从0开始逐步增加到0.5。对照组设计是理解模型的关键。我把λ0作为纯期望成本基准它等价于完全无视风险只追逐最低成本然后逐步提高λ观察购电组合怎么变。所有场景和约束保持完全一致只改λ一个变量这样结果差异完全来自风险偏好不存在数据干扰。5.2 关键结果与风险转移路径相同场景下三个λ档位的典型结果如下风险权重λ省间购电占比省内购电占比期望成本相对值CVaR相对值082%18%1001320.266%34%1031160.551%49%106109λ0时模型把大部分电量放在省间期望成本最低但遇到省间价格突升的尾部场景时整体成本飙升CVaR相对值来到132。随着λ增加模型主动把一部分电量从省间移到省内用稍高一点的省内价格换取尾部风险的显著下降。λ0.5时省间和省内基本五五开CVaR只比期望成本高9%代价是期望成本上升了6%。这个结果在业务层面非常有价值。很多交易中心对购电成本的考核并不只看平均值还设置了极端成本上限比如“全年单日最大购电成本不得超过月均值的130%”。这时候单一期望成本模型根本没有解释力而带风险权重的模型可以直接告诉你如果要保证95%情况下成本不超出某个预算线需要把省间购电比例控制在什么水平。场景数对结果稳定性也有影响。我分别试过50、200、500、1000个场景500个以上期望成本和CVaR的波动都收敛到很小的区间50个场景则会出现明显抖动。所以正式算例建议至少保留300个场景初期调试可以降到50但别拿50个场景的结果直接写报告。6. 数据准备清单与实战排雷复现这套代码前必须知道的几件事6.1 数据清单与单位规范化复现这类模型数据准备往往是比代码更耗时的一环。整理下来核心数据有五类每一类缺失都会让结果失真负荷曲线数据最好细分到小时或15分钟并区分最大负荷日和典型日省间市场价格历史优先取同类交易品种的成交均价没有成交记录时用出清价替代省内市场价格历史同样注意时间段对齐省间联络线可用传输容量最好考虑检修计划和通道利用率不能只看铭牌容量历史预测误差数据用于生成价格和负荷场景这步容易被忽略但非常重要。单位规范化是我反反复复踩过的坑。电量统一用MWh价格统一用元/MWh时间索引统一按自然时间对齐。如果省间市场数据按15分钟粒度过省内市场按小时粒度过负荷预测又是30分钟粒度过直接拼进去会让求解器在索引对位时报出各种奇怪错误。最稳妥的做法是在数据清洗阶段把所有序列重采样到同一个时间粒度。6.2 三个容易翻车的细节第一个坑是CVaR线性化方向写反。u_s ≥ C_s - η这个约束一定不能写成u_s ≥ η - C_s否则目标函数会把“高成本场景”变成“低成本奖励”优化结果会完全反着走。我吃过这个亏后来加了一个快速校验把某个极端场景的全部电量强制放到高价市场观察目标函数是否显著上升如果不是说明约束写错了。第二个坑是负电价处理。高比例新能源地区的省间价格经常出现负电价直接把负价格代入模型模型会把所有负荷都塞给负电价时段形成极度失真的购电比例。实际处理时我给省间购电量加上最低比例约束或者把负价格时段视为限量交易品种不允许无限制购买。第三个坑是场景生成和模型验证脱节。我见过不少代码把场景生成和优化求解焊死在一个脚本里一旦结果异常根本分不清是场景数据的问题还是模型约束的问题。我的做法是先把场景数据单独导出做一次均值、方差和极端分位数的统计检查再跑优化模型并把结果的场景期望成本和数据统计做交叉验证。这样出了问题至少能快速定位是数据错了还是模型错了。代码调试顺序也建议固定。先从单场景确定性版本开始确认目标函数和约束数量都对再切换到少量场景验证CVaR逻辑最后扩大到完整场景集。每走一步都打印目标函数值和各购电比例和手工预期做对比确认无误后再进入下一阶段。这样能最大程度减少拿着错误模型跑大量数据、最后只能推倒重来的情况。我个人体会是把两级市场购电问题做成一个可求解的优化模型难点不在数学本身而在三件事场景怎么生成才贴近实际、CVaR怎么线性化才不会把模型搞崩、结果怎么解读才能让业务人员信服。Matlab加Cplex这套组合前两点通过Yalmip和求解器参数调试基本都能解决最后一点需要把敏感性分析和置信水平结果放进报告让决策者看到不同风险偏好下成本的变化区间。如果你正在做类似的购电模型强烈建议从单场景确定性模型起步逐步加入风险项和随机场景每一步都保留一个对照基准等全套逻辑跑通后再往中长期购电合同、碳排放约束这些方向扩展。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑