
前阵子帮一个工业园区做微网调度方案评审看到几乎每个方案都在谈“分时电价储能峰谷套利”。方案做得漂亮但实际问题不少光伏出力一波动固定峰谷时段立刻失真用户在谷时段拼命充电、峰时段该响应时却不响应运营方收益曲线忽高忽低甚至连续几天亏损。后来我们把模型改成了基于条件风险价值的合作型Stackelberg博弈微网动态定价与优化调度框架很多死结才慢慢解开。这篇文章我不打算做概念科普直接讲讲这个框架里最重要的问题为什么要用条件风险价值为什么是合作型Stackelberg博弈动态定价和优化调度如何揉在一起求解以及我在实际项目里踩过的几个坑。1. 从“一口价”到“动态博弈”微网定价机制的问题升级1.1 统一定价为什么在微网里失灵传统配电网里的电价模式本质上是“一口价”或“静态分时价”。对普通用户来说这种价格简单、透明、可预期没什么问题。但微网不一样。微网里通常有光伏、储能、柴油机或燃气轮机还有大量可平移负荷比如充电桩群、温控负荷、工业流水线。微网运营方的核心诉求是尽可能提高本地消纳率、降低购电成本、让储能峰谷套利产生正收益。可一旦价格信号是固定的用户根本感知不到某个时段电力的稀缺程度也就没有动力改变用电习惯。举个例子某天上午阴天、午后放晴光伏出力会在午后瞬间拉高这时候电力的机会成本非常低可静态峰谷电价还是给午后定了个高价用户自然不愿意把负荷挪到午后弃光就出现了。反过来如果某个晚高峰光伏出力骤降、负荷却高企固定价格又无法把这种紧张传导给用户用户继续敞开了用运营方只能去上级电网买高价电或者让柴油机满发成本集体爆表。说白了统一定价的问题是它把电力当成了一种静态商品但微网里的电力本质上是强随机性、强时段性的资源价格必须随供需变化。动态定价要解决的就是这个“信号传递”问题。运营商把每个调度时段的价格定出来用户根据价格调整自己的用电曲线削峰填谷、提升消纳。但动态定价本身也引入了一个新问题价格不再是运营方单方面拍脑袋的事它必须考虑用户会怎么响应。你定一个高价用户可能直接把负荷挪走你的收益反而下降你定一个低价用户全都涌过来本地电力供不应求又得外购。运营商和用户之间形成了一种典型的策略互动关系。1.2 博弈比“优化定价”更接近现实的结构如果只做“优化定价”数学上就是一个纯优化问题给定预测数据求一个最优价格序列。这忽略了事情的另一面——用户不是无生命的约束条件他们也会基于价格做出自己的最优选择。运营商的收益函数里用户购电量是变量用户的电费账单里运营商的价格是参数。两边都在优化自己谁也不能完全控制对方。这种“你中有我、我中有你”的结构就是博弈。博弈论里有各种均衡概念微网定价里最贴合实际的是Stackelberg博弈也叫主从博弈。原因很直接微网运营商先公布电价和调度计划用户随后根据这个价格调整自己的用电。行动有先后不是同时决策这正好是Stackelberg的顺序结构。运营商是领导者用户可以理解为跟随者或者一个聚合的“负荷主体”。我用一个类比来解释这就像房东定价、租客调作息。房东先报出每月房租租客再决定自己住不住、怎么安排通勤和生活节奏租客的反馈会反过来影响房东下个月怎么定价。微网里运营商就是房东用户就是租客只是“房租”变成了每个时段随时可能变化的电价“作息”变成了可平移负荷的启停时间。所以单纯把用户当成优化模型里的等式约束等于把房东和租客的问题合并成一个人自问自答结果一定跑偏。这几年不少论文把这个模型写成单层优化再求解看起来简洁但本质上忽略了博弈的层次性最后得到的“全局最优”往往在现实中根本不可能实现。真正的合作型Stackelberg博弈会在模型里显式保留这种先后层次并让两个主体分别保留自己的目标函数。2. CVaR背后的工程逻辑不止是换了个风险指标2.1 为什么期望收益模型会在极端场景下说谎微网收益是典型的随机变量。光伏出力随机、负荷随机、上级电网的现货价格也可能随机。传统调度模型里最常见的目标是最大化期望收益意思是在所有可能场景下把平均收益做到最高。问题恰恰藏在这个“平均”里。假设有100个随机场景99个场景运营方都能赚10万但剩下1个场景因为极端天气加设备故障亏损500万。期望收益算出来是9.4万看着很不错可一旦那个极端场景真的发生运营方直接资金链断裂。期望收益模型不会惩罚这种尾部风险它只关心均值完全不关心分布尾巴有多厚。做过金融的人对这类问题很熟。金融里早期用VaR风险价值核心含义是在给定置信水平下最大可能损失是多少。比如95%置信水平下VaR是100万意思是只有5%概率的损失会超过100万。但VaR有个明显缺陷它只告诉你“超过100万”这个门槛在哪没告诉你在超过门槛之后损失到底是110万还是500万。也就是说VaR量化了门槛但没有量化尾部风险的大小。CVaR条件风险价值就是来填这个坑的。CVaR的含义是当损失真的超过VaR这个门槛时损失的期望值是多少。公式可以写成CVaR 平均最坏的5%场景下的损失它衡量的是“尾部损失的平均严重程度”比VaR信息量更大。而且CVaR在数学上满足一致性风险度量的四条性质次可加性、正齐次性、单调性、平移不变性。其中次可加性非常关键意思是两个组合合并后的CVaR不会大于两者CVaR之和这让它天然适合做全局优化目标。VaR和CVaR的区别我列个表对比维度VaRCVaR定义给定置信水平下的最大损失门槛损失超过门槛后的条件期望尾部信息只给门槛位置包含尾部形状数学性质不满足次可加性组合后可能失真一致性风险度量适合优化计算难度需要分位数估计可通过线性规划转化工程适用性适合监管报告适合嵌入调度优化2.2 CVaR在微网定价调度里怎么用在微网模型里CVaR不是放在事后做评估的而是要直接写进优化目标或约束条件。常见做法是这样的第一步用历史数据或统计分布生成N个随机场景每个场景包含光伏出力曲线、负荷曲线、外购电价。每个场景赋予一个概率多数情况下可以直接当成等概率。第二步针对每个场景算出运营商的收益或成本。这组收益值构成一个分布。第三步把CVaR约束写进去。如果想控制最坏情况的下行风险可以约束“在99%置信水平下尾部损失的均值不超过某个阈值”或者直接在目标函数里用加权方式把期望收益和CVaR组合起来目标函数 (1 - λ) × 期望收益 λ × CVaRλ是风险偏好系数0到1之间。λ0就是只看期望收益λ1就是只看尾部风险实际项目中一般取0.2到0.5之间。这里有个非常实用的数学性质CVaR可以用Rockafellar-Uryasev方法线性化。核心思路是引入一个辅助变量α和一个非负偏差变量把原来不好处理的“尾部分位数期望”转成一组线性不等式。也就是说CVaR虽然听起来复杂但在模型里可以写成线性约束配合场景法之后整个调度问题依然能交给混合整数线性规划求解器处理不需要用非线性求解器硬扛。我在实际项目里部署时还遇到一个细节CVaR是按“损失”定义的但调度目标经常写成收益最大化。这时候需要把收益取负号变成损失或者直接用CVaR的一个对称版本。很多文献里写“CVaR约束下最大化收益”其实底层是把最坏场景的收益尾巴做个加权。这个小地方不搞清楚写完约束跑出来结果会非常奇怪。3. 合作型Stackelberg博弈模型怎么摆目标怎么设3.1 参与主体和决策变量这个框架里最基本的角色有两个微网运营商和用户或者负荷聚合商。我用表格梳理一下角色层级决策变量核心目标微网运营商领导者各时段售电价、分布式电源出力、储能充放电功率、外购电量在风险约束下最大化综合收益用户/负荷聚合商跟随者可平移负荷的时段分配、部分可削减负荷的用电量给定电价下最小化电费支出运营商先行动公布一组动态电价序列以及自己的调度计划。用户看到电价后调整自己的用电曲线使得电费最小。用户调整完之后整个微网的净负荷曲线发生变化运营商的收益也随之变化。这里有两个容易忽略的点。第一个储能归谁控制。实际项目中储能有时是运营商的资产有时是用户的资产。两者在模型里的位置完全不一样。如果储能属于运营商储能的充放电决策在领导层如果储能属于用户那就进跟随层用户的决策变量会变成“电费支出储能运营成本”一起最小化。建模仿真前必须先把这个产权归属定清楚。第二个用户的“需求响应积极性”怎么表达。如果用户完全不响应价格那动态定价就退化成和固定电价没区别如果用户全部都响应又会出现负荷大范围平移造成的反弹效应。一般会在用户模型里加入可平移负荷的转移窗口约束比如某条生产线只能在连续3个时段内启动一次这类约束刻画了用户的物理限制。3.2 下层用户的模型长什么样用户的目标函数很简单在给定电价p_t的情况下最小化总电费。但约束会比较多常见的包括每个时段总负荷有上限下限不可能无限平移。可平移负荷有转移窗口提前或推迟有限个时段。部分负荷连续运行不能拆开。总用电量基本守恒平移只是改变时段分布。用户模型通常是线性规划因为目标线性、约束线性。这对后续求解非常重要线性规划的下层问题可以用KKT条件等价替换也可以借助强对偶把双线性项线性化。如果下层模型写成非线性整个双层问题的求解复杂度会直接爆炸。3.3 合作型和非合作型的差别在哪既然两边都是自利的为什么标题里要强调“合作型”非合作型博弈的场景是运营商最大化自己的收益不关心用户利益用户也单纯按价格响应两边各干各的。这个模型在数学上最简单但拿到现实中很容易出现双重边际化效应——运营商定高价导致用户减少用电总的社会福利不升反降两边都受损。微网的规模通常不大运营商和用户之间的关系是长期、反复的不是一次性偶遇。非合作博弈算出来的均衡往往因为效率太低根本维持不下去。合作型博弈则加入了约束条件或激励契约。最常见的手段是引入“参与约束”也叫个体理性约束要求用户参与动态定价后电费支出不能高于不参与时的水平否则用户为什么要陪你玩运营商的模型里要有这条约束等于主动让渡一部分利益来换取用户的长期参与。从数学模型上看它就是在上层优化里多加了一个不等式约束但这个约束的作用非常大它保证均衡点是帕累托改进的。高一点的版本还会谈Shapley值分配合作剩余。也就是运营商和用户合作后整体社会福利增量怎么分。Shapley值给了一个公理化的分配比例但实际工程里往往不直接用Shapley值因为计算复杂度太高且需要枚举联盟组合。项目里更多是把合作剩余按一个固定比例分成或者通过价格折扣、用电积分等机制落实这部分已经不是优化模型能完全刻画的了。用一句话总结非合作模型回答“我该怎么定价格”合作模型回答“我该怎么定价格同时保证用户愿意留在这个系统里”。微网运营商的长期收益靠的是用户基数不是单期最大化。4. 从双层到可解动态定价与调度联合求解的实操路线4.1 双层问题的数学结构和困难点原始的优化模型是一个双层问题。上层是运营商的目标函数包含电价、调度变量还要考虑下层用户响应对自身收益的影响下层是用户的最小化电费问题以上层给的电价为参数。双层问题在计算复杂性上是出了名的难哪怕上下层都是线性规划整体也是NP-hard的。难点主要在三个地方第一上下层目标耦合无法直接拆开各自求优。 第二电价乘以用户购电量会产生双线性项模型变成非凸。 第三下层用户的解可能不唯一上层无法在不明确“用户会选哪个最优解”的情况下做决策。这些难点是绕不开的工程上只能通过合理的模型转化来处理。4.2 主流方法用KKT条件把双层拉平最常用的做法是先把用户的下层问题用KKT条件替换掉。因为用户问题是线性规划它的KKT条件既是必要条件也是充分条件。替换后原来的双层问题变成一个单层问题代价是引入了大量的互补松弛条件。互补松弛条件是非线性的但好在有标准处理手法引入二进制变量和大M参数把每一条互补松弛条件转换成两组线性不等式。比如互补松弛条件 0 ≤ a ⊥ b ≥ 0意思是a和b至少有一个为0。引入二进制变量z加约束0 ≤ a ≤ M × z 0 ≤ b ≤ M × (1 - z)这样就把非线性的“乘积为0”用线性不等式表达出来了。M的取值是个艺术取太大求解器数值稳定性差取太小可能把可行域错误截断。我一般会根据变量的边界按边界值的2到5倍去取。替换完KKT之后还有双线性项问题。上层目标里如果出现“电价乘以电量”需要进一步处理。如果电价是变量、电量也来自下层的最优解在KKT框架下这两者的乘积可以用强对偶定理转化成对偶变量和常数构成的线性表达式。这一步数学上稍微绕但每个做过双层优化的人都会经历。转化成功后整个问题变成MILP可以直接用Gurobi、CPLEX、CBC这类求解器处理。4.3 迭代方法和启发式兜底KKT转化不是万能的。场景数一大二进制变量数量爆炸求解时间会从几分钟变成几小时甚至干脆解不动。这时候要准备第二套思路固定点迭代。基本流程是这样的第一步给一组初始电价。 第二步把电价代入下层模型求解用户的用电计划。 第三步把用户用电计划代入上层模型固定用户响应重新优化运营商的调度和电价。 第四步如果新电价和旧电价差距小于阈值停止否则更新电价回到第二步。这个方法看起来直观但实践中几乎必然遇到振荡。换句话讲这轮定高价导致用户大幅减少用电下一轮算下来发现高价不好又大幅降价用户用电激增然后又定高价如此往复。处理振荡的办法是加阻尼系数也叫惯性更新新电价 旧电价 γ ×本轮计算出的新电价 - 旧电价γ一般取0.4到0.7。我做过对比同等精度下加阻尼能显著提高收敛速度不变量纯粹就是碰运气。如果阻尼迭代还是收敛不了就得上启发式算法。我在一个项目中用过粒子群搜索上层电价向量内层用LP求解器算用户响应外层用粒子群更新价格。效果还行但计算量大只适合做参考解或给小规模算例用。我放一个简化的Python伪代码框架方便理解整体结构import numpy as np from scipy.optimize import linprog def solve_user_problem(price, load_shiftable_bounds): # 用户模型给定电价最小化电费 # 这里用linprog或者调用Gurobi求解LP pass def solve_leader_problem(user_response, uncertainty_scenarios): # 上层模型给定用户响应和场景集优化电价与调度 # 包含CVaR约束通常是一个MILP pass def iterative_dynamic_pricing(initial_price, gamma0.6, max_iter50): price initial_price for i in range(max_iter): response solve_user_problem(price, load_shiftable_bounds) new_price solve_leader_problem(response, uncertainty_scenarios) if np.linalg.norm(new_price - price) 1e-3: break price price gamma * (new_price - price) # 阻尼更新 return price这段代码不是完整可运行的工程代码核心是想表达两层模型在代码里就是两个子问题来回迭代内层一个LP外层一个MILP中间用阻尼参数控制更新步长。4.4 求解完成后的验证步骤很多人跑完模型看到求解器返回“最优解”就收工了。这件事在双层优化里很危险因为KKT转化后的单层问题和原始双层问题在某些情况下并不是严格等价的尤其是下层问题存在多重最优解时。我养成一个习惯求解完把最优电价带回原始的下层用户模型重新求解一次用户的用电计划看算出来的用户响应和上层模型里假设的是不是同一个。如果发现不一致说明当前解在原始双层意义上根本不是均衡。这种情况在投影不满足KKT条件时特别常出现。验证步骤虽然多花几分钟但能救回很多离谱的结论。也用得上对偶间隙检验如果原始用户模型和KKT转化后解出的下层目标值有明显gap那基本就是建模或大M设置出问题了。5. 算例设计与避坑清单这些坑我基本都踩过5.1 置信水平别随手选CVaR的置信水平β直接决定了风险态度的激进程度。β0.99意味着你害怕到只允许最差1%的场景出现问题整个调度方案会非常保守储能常常不敢在高峰放电光伏也不敢按高比例消纳弃光率反而上升β0.85又太过激进尾部风险包不住可能算出一个收益漂亮但一碰到极端天气就扛不住的方案。我的习惯是先跑一个“收益-CVaR前沿”分析。把λ和β分别取几个档位比如λ0、0.2、0.5、0.8、1.0β0.9、0.95、0.99画出期望收益和CVaR的帕累托前沿。调度方案其实就是在风险和收益之间选一个工作点。先看清楚前沿长什么样再拍板定参数比对着文献随手填β靠谱得多。5.2 场景削减和样本数的爱恨情仇CVaR的结果对场景抽样方式非常敏感。场景太少尾部形状不稳定换个随机种子结果就大变场景太多MILP规模膨胀求解时间难以接受。我之前用100个场景和1000个场景分别跑同一个调度模型得到的储能策略差了一整条曲线。明显是100个场景时极端场景占比不够尾部根本没刻画出来。工程上的折中方案是做场景削减。主流的快速法叫同步回代消除法核心思想是不断合并距离最近的场景直到场景数量降到目标值。这个算法实现起来不难几十行代码就够。场景削减之后误差通常能控制在可接受范围MILP求解规模也能控制住。还有一个容易被忽视的细节当场景数不整除时1-β的尾部分位数量就不是整数。比如100个场景β0.95理论上取最差的5个场景算平均没问题但如果场景数是83个β0.955%就是4.15个场景就要按线性插值处理这个小数部分。很多实现里直接把小数舍掉这会对CVaR产生系统性低估。5.3 均衡唯一性与收敛检查双层博弈的一个潜在麻烦是均衡可能不唯一。上层同一个定价策略下用户可能有多个最小电费方案这意味着运营商的收益不是单值的决策也就不稳定。处理办法有两类。一类是给用户模型加“优选规则”比如在电费最小之外要求用户响应要尽量平滑、不要频繁启停设备通过正则项打破多重解。另一类是采用悲观或乐观解悲观解认为用户会选让运营商最难受的那个最优方案乐观解正好相反。工程上通常按乐观解处理但报告里一定要说明清楚否则评审专家看到结果跟常识不符会直接质疑整个模型的合理性。还有收敛检测的问题。迭代求解时不能只看两步之间电价差距还要看用户的负荷转移量是否收敛。有时候电价稳定了但用户负荷响应还在几个方案之间来回跳说明均衡并不牢靠。我一般会同时监控三个指标电价误差、用户购电量的变化、储能前后两步的充放电功率差三个都稳定才算收敛。5.4 时间尺度配合与日前日内衔接动态定价的“动态”要落在调度时段上。日前调度一般按1小时间隔划分日内滚动可以做到15分钟间隔。两层时间尺度不一致的话需要把日前价格作为日内优化的边界日内模型只能在边界附近微调不能毫无约束地乱改价。这个细节很容易被忽视但直接影响结果可落地性。如果日前和日内模型各跑各的尾校正时发现日前最优解在日内根本不可行还要重新调项目进度就会被反复来回折磨。6. 一点个人体会最后说个实际的。这个框架最锻炼人的不是建模而是“和模型对话”的能力。我早期习惯拿到数据就上大模型结果经常跑出来荒唐的价格曲线——半夜定出天价、午后白送电。后来学乖了先拿三五个场景的小算例把逻辑盘清楚确认每一层目标、每一条约束都有明确的物理含义再慢慢放大场景规模。CVaR这一层价值不只是让收益曲线更平滑而是逼着你在写模型的时候就把“不确定性”当成一个必须面向的角色而不是事后补救。如果你也要做类似的方向我建议从一个小微网开始先把参与博弈的主体限定在两个把约束拆细跑通再扩展。模型能解释每一个结果比模型本身复杂更重要。