电力市场双层优化:售电商主从博弈套餐设计与购电策略
去年我在复现某篇EI索引论文时遇到了一个特别有意思的题目基于主从博弈的售电商多元零售套餐设计与多级市场购电策略。当时第一反应是这题听起来很大又是“主从博弈”又是“多级市场”结果做下来发现真正的难点不在数学公式而在怎么把零售端的用户行为和批发端的市场时序拧在一起建模再用Matlab把整个双层优化问题稳定解出来。这篇东西适合两类人看一是刚接触电力市场优化、想用博弈论做个有深度课题的研究生二是企业里做售电套餐设计或能源运营的工程师想理解套餐定价和购电策略之间到底有什么联动。我尽量把每一步背后的“为什么”也讲清楚而不是只给一段能跑的代码。1. 为什么“零售套餐设计”和“购电决策”不能分开做1.1 零售端的套餐设计决定了用户侧的用电弹性很多初步接触售电业务的人会把套餐设计当成一个简单的营销问题给用户几档电价有人选便宜量大的有人选贵但稳的最后算一算平均收益就行。但在这类EI论文的框架里套餐不是拍脑袋定的而是直接影响用户用电行为的传导器。比如你推出一款“夜间半价、白天溢价”的分时套餐用户就会把可平移负荷挪到夜里。如果客户数量足够多这种负荷转移不仅会改变零售侧的收入结构还会反过头来影响售电商在批发市场购买电力的时段分布。零售套餐的每个价格参数本质上都是一根杠杆撬动的是用户侧的需求响应弹性。而弹性一旦被撬动批发侧的购电曲线就必须跟着调整否则就可能出现“套餐卖得越多、亏损越大”的尴尬局面。1.2 批发端的多级市场结构让购电成本不再是单点决策国内外的电力市场普遍存在合约市场、日前市场和实时市场。合约市场可以提前锁定中长期价格但锁定多了现货价格下跌时就会吃亏日前市场能根据次日预测精准申报但预测总有偏差实时市场虽然最接近真实供需但价格波动极大只能作为平衡手段。所以售电商的购电策略本质上是一个多时间尺度的组合优化问题。你在合约市场签多少比例在日前市场报多少曲线留多少空间给实时市场修正这些比例互相牵制。更麻烦的是购电策略不是独立的它必须和零售套餐一起优化。因为套餐决定了用户总用电量和分时分布而购电成本取决于批发侧的分时价格两者强耦合。当年读这篇论文时我最大的顿悟就在于此标题里的“多元零售套餐设计”和“多级市场购电策略”并不是并列的两个模块而是一个整体。零售端每改一个价格参数批发端的购电比例就要重新算一遍。只有把它们放到同一个优化模型里同时求才算真正理解了这个问题。2. 主从博弈建模谁先动谁后动均衡状态怎么定义2.1 售电商作为Stackelberg领导者的决策变量与收益函数主从博弈又叫Stackelberg博弈核心思想是“领导者先宣布策略跟随者根据领导者的策略做最优响应”。放到这个场景里售电商是领导者用户是跟随者。售电商先发布一组零售套餐比如普通固定电价套餐、分时电价套餐、阶梯电价套餐每种套餐有自己的价格参数。用户看到这些套餐后会选择一种能最大化自身效用的套餐并决定自己在每个时段的用电量。售电商的收益是零售收入减去批发购电成本所以它需要预测用户会如何反应然后倒推一个最优套餐组合。这里有个容易搞混的点售电商不是直接控制用户用电量而是通过套餐价格引导用户。所以模型里售电商的决策变量是套餐价格参数而用户用电量是内生变量。收益函数大致可以写为售电商总利润等于各时段零售收入减去各时段购电成本。但真正复杂的在于用户问题自身是一个优化问题售电商的利润函数里包含着用户的最优响应函数这就形成了一个“外层优化内层优化”的双层结构。2.2 用户作为跟随者的用电决策模型用户的用电行为通常用一个效用函数来描述。经典做法是二次效用函数用户从用电量中获得满意度但满意度边际递减。用户会在给定电价的条件下选择合适的用电量来最大化“效用减去电费支出”。如果同时提供多种套餐用户还要做一次离散选择。为了建模方便很多论文会把用户按类型分组比如居民用户、商业用户、工业用户每组用户对电价敏感度不同。每种用户会计算各套餐给自己带来的净收益然后选择最大的那一个。这里有个微妙的设计用户不仅选择“用多少电”还选择“用哪个套餐”。这两种决策是嵌套的。如果只是简单的连续用电量优化问题会退化成单层一旦引入套餐选择就变成混合整数双层问题难度陡增。我在复现时发现作者非常聪明地假设用户是同质的或者按少数几类聚合这样离散选择就可以通过比较净收益大小来确定而不需要显式引入整数变量。这种简化虽然牺牲了一部分精细度但让主从博弈模型具备了可解性。3. 多级市场购电的数学建模从日前到实时的时序衔接3.1 合约市场、日前市场与实时市场的互补关系购电策略的建模首先要理清三个市场的角色。合约市场通常在日前市场关闭之前很久就签订一旦签订就是物理合同必须执行。它起到价格对冲和风险规避作用。日前市场允许售电商根据负荷预测申报第二天的分段购电曲线价格按出清价结算。实时市场则针对实际负荷与日前申报的偏差按实时价格多退少补。这三个市场构成了一个时间链合约锁定基础量日前申报计划量实时修正偏差量。售电商的总购电成本由三部分组成但三部分的购电价格性质完全不同。合约价格相对稳定日前价格波动较大实时价格可能剧烈跳动。在模型里售电商要决策的是合约电量占总需求的比重、日前市场在不同时段的申报电量、以及预留的实时调整空间。约束条件包括各时段购电量之和必须等于该时段总负荷需求合约电量不能超过某个比例上限实时购电电量可以在某个范围内等。3.2 购电组合约束与风险偏好表达在实际建模中风险因素通常用一个权重项来表达。有些论文会用CVaR条件风险价值来衡量实时市场价格暴涨带来的损失有些则简单采用加权平均成本。我复现的那篇论文没有用太复杂的风险度量而是在目标函数里给实时购电价格设定了一个惩罚系数相当于变相表达了售电商对实时市场的风险厌恶。这种做法在复现时比较容易处理因为它保持了目标函数的线性或二次可解性。购电组合约束里最容易忽略的是时序一致性合约市场和日前市场的决策在时间上是先后发生的但在数学建模里如果没有显式的时间耦合变量很容易把三个市场的电量写成彼此独立的决策变量。实际上日前申报必须基于对合约电量的已知信息实时修正又必须基于日前中标结果。这种嵌套关系需要我们用“滚动决策”或“级联约束”的方式表达否则解出来的策略在实际运营中根本无法执行。我记得自己第一次建模时把三市场购电量都写成同时决策的变量结果得到一个理论上最优但实际完全不可行的方案。后来看了论文里的决策顺序图才明白必须把决策变量按时间分层描述外层是合约比例中层是日前申报内层是实时调整。4. Matlab复现路线KKT条件、强对偶与MPEC求解4.1 将用户问题替换为KKT条件的推导细节这种双层优化问题最常用的解法学派是“下层替换法”。用户侧优化问题通常是一个凸优化问题因此它的最优解完全等价于它的KKT条件。把用户问题写出拉格朗日函数对用电量求导加上不等式约束的互补松弛条件就能把内层问题转化为一组方程组和互补约束。替换之后外层售电商优化问题就变成了一个带互补约束的数学规划问题也就是MPECMathematical Program with Equilibrium Constraints。在Matlab里做这一步时最关键的是求导符号别算错。用户的效用函数如果带二次项导数是线性的价格参数会出现在约束里所以KKT条件里会出现外层决策变量和内层决策变量的乘积项这就是非线性互补约束的来源。我建议先手推一遍内层KKT条件再用符号计算工具验算最后再把结果手工写成约束矩阵。不要省这一步直接套现成的大规模优化工具箱往往会把非线性约束遗漏。4.2 通过强对偶定理避免非线性互补约束MPEC中的互补约束是非光滑的直接丢给求解器很容易报错或收敛到假的局部最优。业界常用的办法是利用强对偶性质把用户问题的目标函数值用其对偶目标函数等价替代从而消去互补约束中的非线性项。具体来说用户问题是一个凸二次规划在满足Slater条件下原问题最优值等于对偶问题最优值。把用户目标函数替换成对偶表达式之后互补约束中的乘积项会被一组线性等式替代这样MPEC就变成了一个单层的混合整数线性规划或二次约束规划。这一步是整个复现里最“魔法”的地方。我第一次接触时完全看不懂为什么能这样代换后来拿一个只有两个时段的小例子手算了一遍发现其实就是利用了凸优化中“强对偶0”的双层等值关系。在写Matlab代码时这一步通常体现为用Yalmip定义完上层变量和约束后把内层用户问题的目标函数用表达式替换掉然后交给求解器处理。4.3 用Yalmip和Gurobi求解的工程实现在Matlab平台上我习惯用Yalmip建模求解器选择Gurobi。Yalmip的优点是建模语言接近数学公式代码可读性高而且支持线性化互补约束的内置函数。实际写代码时我通常把数据准备好后先建模外层问题的基础变量和约束再嵌入内层KKT条件替换过来的线性约束。需要注意转化后的模型中可能含有一个二元变量用来处理互补约束中的非凸部分Gurobi可以求解MILP所以整体复杂度可控。我还准备了一个简化版本的测试算例只有3个时段、2类用户、2种套餐。在这个小算例上验证模型逻辑正确后再扩展到96时段、5类用户、4种套餐。如果一上来就跑大算例出了问题根本没法定位。下面是我的代码框架片段省略了具体数据生成部分保留核心建模结构% 定义售电商决策变量 p_price sdpvar(nTime, nTariff); % 套餐价格 p_contract sdpvar(1,1); % 合约购电比例 % 用户用电量由KKT条件决定通常写成变量 q_user sdpvar(nTime, nUser); % 约束KKT条件转换后的线性约束 Constraints [KKT_stationarity, KKT_complementarity_linearized]; % 目标函数售电商利润 Profit sum(sum(q_user .* p_price_actual)) - cost_contract - cost_dayahead - cost_real; Objective -Profit; % 最小化负利润 ops sdpsettings(solver, gurobi, verbose, 2); optimize(Constraints, Objective, ops);很多初学者会在互补约束的线性化上卡住。我的建议是不要直接用complements()命令而是手动引入一个二元变量把互补条件“要么左边为0要么右边为0”转化为大M约束。虽然多了整数变量但求解稳定性明显提升。5. 复现过程中最容易踩的坑5.1 参数初值敏感性与多均衡问题主从博弈模型有个老毛病可能存在多个均衡解。尤其是当用户效用函数和价格参数表达不够严格凹时KKT条件可能有多组解Gurobi找到的不一定是论文里那个。我在复现时对比原论文的图发现数值总是差一点。后来重新读论文发现作者在参数设定里有一行很关键的小字所有价格变量初值均采用上一轮迭代结果。这不只给了一个初值影响的提示还暗示他们用的可能是某种迭代求解方法而不是一次性MPEC。如果直接用MPEC求可能走到一个局部解。解决方法是给价格变量设置上下界或者通过多起点尝试。我建议在代码里加一个简单的循环随机生成几组初始价格记录每次求得的利润值选择最优的那个。这个方法虽然笨但对于复现研究很有效。5.2 数值问题大M取值、标幺制与迭代次数互补约束线性化时需要引入一个足够大的M值。M太大会导致数值病态Gurobi的求解精度降低M太小则可能错误地把可行解剪掉。这个M值到底取多少没有统一标准必须根据电价和用电量单位来估计。我的经验是先算一下所有变量的量级上限。例如实时电价一般在几百到几千元/兆瓦时用电量在几十到几百兆瓦时两者乘积的上限大概是百万级。那么M取这个上限的10到100倍就足够了。代码里最好把M定义成和基础单位相关的公式而不是写死一个常数。另一个坑是功率单位不一致。有的论文里用兆瓦时有的用千瓦时数据量级相差很大直接代入模型会导致系数矩阵条件数极差。我在复现时把所有数据统一成标幺制以某个基准负荷和基准电价作为1这样所有决策变量都在0.1到10之间求解器收敛速度快得多。5.3 如何校验复现结果与原始论文保持一致论文复现最怕的不是模型跑不通而是跑通了但结果对不上。我一般会按以下顺序逐项校验先用论文提供的基准参数跑出一个完整结果检查目标函数值和论文图表里的数量级是否一致。再看关键决策变量的趋势比如套餐价格是否在某个时段升高合约购电比例是否在某个区间波动。最后做敏感性分析改变用户价格弹性系数观察利润和均衡价格是上升还是下降。如果趋势一致但数值略有偏差很可能是求解器精度或终止误差导致的问题不大。如果趋势都反了那一定是模型公式抄错了必须回到KKT推导阶段检查。我在复现过程中最常犯的错误是漏掉用户用电量上下限约束。很多论文为了逼真会设置用户每个时段的最小用电量和最大用电量这会造成互补约束里出现“下限或上限绑定”的情况。如果漏掉这些约束均衡解里可能会出现负用电量这种明显不合理的结果。6. 从均衡结果看运营策略套餐价格、购电比例、用户弹性的联动关系6.1 基准场景下的均衡结果分析跑通模型后我习惯先复现论文的基准算例。这个算例通常设置一个典型日的96个时段用户分三到四类套餐两到三种。从均衡结果里你能清楚看到两个关键现象。第一分时套餐的价格高峰低谷差比普通固定套餐大得多。因为用户有弹性售电商敢在峰时抬高价格引导用户少用同时在谷时降低价格鼓励用户多消纳。这样一来用户侧的总负荷曲线被削峰填谷售电商批发侧的购电均价也随之下降。第二合约购电比例不是越高越好。在基准参数下合约比例通常落在一个区间过低的话日前市场暴露风险太大过高的话当现货价格走低时售电商会因锁定高价合约而吃亏。均衡比例实际上是用户弹性、现货价格波动和风险偏好三者博弈的结果。6.2 敏感性分析电价波动、套餐种类、用户数量复现论文时敏感性分析是验证模型逻辑的重要手段。我通常改变三个参数实时市场价格波动率波动率增大时售电商倾向于提高合约比例同时对实时购电量设置更严格的惩罚零售套餐的峰谷价差也会拉大。套餐种类数量从两种套餐增加到四种套餐用户细分程度提升整体社会福利和售电商利润通常都会上升但多出来的收益并不平均有些用户群会被“价格歧视”。用户弹性系数弹性越大售电商越不敢定过高的价格因为用户会快速流失。弹性低时售电商可以把价格定得更高利润空间更大。通过敏感性分析研究者还能画出“模糊区域”或者“阈值点”这些内容往往是EI论文里最出彩的部分。我复现时最喜欢做这类图表因为它们能直观展示模型行为也容易让读者理解主从博弈的意义。7. 复现过程中的其他心得与后续扩展方向7.1 个人体会把“理论模型”翻译成“可计算模型”的桥这次复现经历让我对电力市场中的双层优化模型有了更深的理解。很多论文里推导出的漂亮公式直接丢给Matlab往往是跑不动的必须经过一系列等价转化才有实际可解性。这中间最关键的能力就是能把“均衡”这种抽象概念翻译成“一组约束条件加上一个目标函数”。另外调试时的耐心也很重要。第一次真正跑通全模型并复现出论文关键图表时我花了两天时间定位一个符号错误——就是把“用户电价敏感系数”写反了。这种错误不仔细看根本发现不了但会导致所有结果趋势都反过来。从那以后我养成了一个习惯每次修改模型后先跑一个极简场景手动验证几个节点的数值合理性再放大算例。7.2 可以继续扩展的方向如果你也想在这个方向深入我建议可以试试以下几件事把用户侧的离散选择行为真正建模成混合整数约束而不是用净收益比较简化。这会显著增加求解难度但能更真实地刻画用户切换套餐的成本。引入多个售电商同时竞争的博弈场景把单领导者-单跟随者结构扩充成多领导者-多跟随者这类模型通常只能用启发式算法或分布式求解器。把低碳约束纳入模型比如绿电消费比例、碳排放配额这会改变购电策略的可行域也会影响套餐设计。从工程应用角度看这类模型虽然调度颗粒度较粗但它提供了一个很好的框架能帮助售电公司从“拍脑袋定价”走向“基于用户响应和市场价格的联合优化”。如果你手头也正在复现类似的电力市场博弈论文或者在设计零售套餐时感到定价和购电策略相互打架欢迎在评论区聊聊你踩过的具体问题。模型细节也许各不相同但核心逻辑大多相似多交流几次你就能少走不少弯路。