
第一次看到“基于主从博弈的售电商多元零售套餐设计与多级市场购电策略Matlab代码实现”这个标题时我第一反应不是博弈论而是“又要花几天把别人论文里的数学符号翻译成能跑的脚本了”。但把模型、代码和算例逐条对齐之后我反倒觉得这套思路特别适合作为电力市场方向的研究起点它把售电公司的定价、用户套餐选择、中长期与现货购电这几个原本相对独立的环节放进同一个优化框架里用主从博弈描述先后决策关系再用Matlab迭代求解出均衡。说人话就是这个题目解决的是“售电公司怎么定套餐才能既吸引用户又保证利润、同时又能在不同电力市场上把电买得划算”的问题。适合正在复现论文、准备毕设或者想用一套算例支撑自己方案的人参考。1. 抓住核心这题到底在算什么1.1 不要被“EI复现”三个字吓住很多同学拿到这类题目第一反应是“这篇论文是不是很高级我是不是得把博弈论先学一遍”。实际动手以后你会发现真正支撑代码的数学工具没有想象中那么深核心就三个东西双层优化、需求响应、购电组合优化。所谓“复现”也不是让你逐行还原作者所有隐藏代码而是把模型框架、决策变量、约束条件、求解流程理清楚然后跑出一组能和论文结论对得上的结果。我习惯的做法是拿到代码包后先不看运行脚本而是先看三样东西模型假设有什么、决策变量有哪些、均衡怎么定义。这个题目里决策变量至少包含两类一类是零售套餐的价格参数比如固定电价、峰谷电价、阶梯价差另一类是各市场购电量比如中长期合约电量、日前申报电量、实时偏差电量。搞清楚这两个维度后续看代码就不会迷路。1.2 主从博弈怎么理解最省力主从博弈在电力市场里最常见的落地场景就是“售电公司先出牌用户再响应”。售电公司是领导者先发布零售套餐用户是跟随者看到套餐之后选择买哪个、用多少电、要不要把负荷从高峰挪到低谷。等用户做出反应售电公司才知道自己的套餐实际带来了多少收益然后再调整下一轮定价。这个过程和商场定折扣很像商家先贴出“满300减50”消费者看到之后决定买还是不买、凑不凑单。商家不会一开始就给出最亏的价格消费者也不会闭着眼睛选最贵的方案最终双方会在一个相对稳定的点上达成一致。对售电公司来说它要考虑的不是单方面压价而是“套餐定到什么水平用户才愿意选同时我自己还有利润”。这就是主从博弈和普通单层优化最大的区别利润不是拍脑袋算出来的而是把用户的理性选择当成约束嵌进去之后得到的。1.3 零售套餐和购电策略为什么必须放在一起设计很多入门资料会把“卖电套餐”和“买电策略”分开讲套餐归套餐购电归购电。但实际经营里这两件事是强耦合的。如果套餐定价很低、用户响应很大那售电公司就需要更多电量购电成本也会跟着涨如果套餐里设计了峰谷价差用户把负荷挪到低谷那售电公司在高峰时段的买电压力就变小现货市场暴露风险也降低。把两个环节合在一起优化的价值就在这里套餐不只是利润表里的收入项它还是改变用户负荷曲线的工具。负荷曲线一变购电计划跟着变偏差考核和现货风险也会变。所以这个题目里的“主从博弈”天然适合做这种联动分析。反过来想如果只做单层优化不考虑用户的理性选择那算出来的套餐很可能是“理论最优但没人买”的方案。2. 模型设计拆解上下层目标、约束和参数选择2.1 上层售电商的利润模型上层问题的主体是售电公司目标一般写成“总利润最大化”。总利润等于零售收入减去购电成本再减去风险成本和偏差惩罚。零售收入来自用户按套餐支付的电费购电成本则分市场叠加中长期合约、日前现货、实时平衡各自有不同的价格和结算规则。用大白话讲上层决策者要回答三个问题第一个是套餐价定多少第二个是中长期市场买多少第三个是现货市场和实时市场怎么补缺口。前一个决定了收入后两个决定了成本。如果只看收入不看成本套餐会定得很激进如果只看成本不看收入又可能把用户吓跑。所以代码里上层目标函数通常是一大串带期望值的表达式每一项都对应一个市场环节。实际操作中我建议先把目标函数拆成行来写每一个行对应一个费用项。不要一上来就把所有项揉成一个长公式不然后面调参数、加约束、查warning都会很痛苦。比如第1行各套餐零售收入之和第2行中长期合约购电成本第3行日前市场购电成本第4行实时市场偏差购电成本第5行CVaR风险成本或偏差惩罚。这样拆开之后你很快就能看出来利润是从哪一项漏掉的。2.2 下层用户的套餐选择模型下层问题是用户侧的优化。用户看到售电公司给出的套餐后会选择能让自己用电成本最低的套餐并决定是否调整用电行为。如果用户完全价格刚性那模型就退化成“算电费”而已如果用户有一定需求弹性或者允许负荷转移那用户侧的决策就会反作用于售电公司的收入。在代码里下层问题通常是一个线性规划或混合整数线性规划。其中需要小心的是“套餐选择”这个动作。用户通常只能选一个套餐所以需要0-1整数变量但如果题目允许用户把部分电量放在不同套餐下那又要用到分段或连续变量。这个细节直接决定你用linprog还是intlinprog也决定后续KKT条件能不能直接写。我当时踩过的坑是没有注意套餐的“排他性约束”导致一个用户的电量被拆到多个套餐里算出来的订阅比例和论文对不上。后来花了很长时间检查约束才发现是少写了一个“每个用户最多选一种套餐”的逻辑。2.3 多级市场购电到底怎么“多级”“多级市场”这个词听起来唬人拆开就是几个不同时间尺度的购买渠道。简单理解中长期市场像大宗批发可以提前锁量锁价日前现货市场像每日采购价格会波动实时市场只处理最后的小偏差价格通常最不稳定。三者之间的关系可以用一张表来讲清楚市场层级决策内容主要不确定性在代码里的典型变量中长期市场合约电量、合约价格未来负荷预测偏差contractPower日前现货市场日前申报电量现货价格波动dayAheadPower实时平衡市场偏差电量实时价格波动imbalancePower策略的核心逻辑是中长期买太多如果现货价很低你就会多付成本中长期买太少如果现货价暴涨你就要承受高额购电风险。所以“多级”不是简单地把三个市场的量加起来而是要在风险和成本之间做权衡。代码里经常用场景集来描述现货价格的多种可能再用期望值或者CVaR把不确定性压进目标函数。2.4 风险偏好怎么进模型CVaR复现这类论文时十有八九会遇到CVaR这个词。它的作用是把“极端情况下的亏损”纳入决策。普通期望利润只关注平均结果现货价格暴涨的个别场景可能被平均值掩盖掉。CVaR关注的是尾部风险意思就是“最差的那百分之几的场景我平均会亏多少”。如果你把CVaR当成一个惩罚项加到目标里售电公司就会主动减少对高风险市场的依赖比如多签一些中长期合约或者把套餐里的峰谷价差拉大。风险偏好系数通常用λ表示。λ越大决策越保守购电计划越倾向于“少赌现货、多签合约”λ越小决策越激进愿意为了更高期望利润承受更大波动。做参数敏感性分析的时候这个系数是必扫的。我通常从0.1开始试步长取0.05看到一个利润和风险之间的拐点再定最终算例。3. Matlab实现路线模块划分、求解器选择与关键代码骨架3.1 推荐代码结构拿到这类代码我最怕的是所有逻辑都堆在一个几十行的大脚本里。一旦要改套餐数量、改用户类别、改场景数整个文件都变得极其难动。所以复现第一步我会把代码按功能拆成五个模块参数与数据生成模块用户数量、负荷曲线、价格场景、套餐候选范围场景生成与削减模块生成现货价格场景用聚类或启发式方法削减下层用户优化模块输入套餐输出用户选择和各时段负荷上层售电公司优化模块迭代更新套餐价格和购电计划结果处理与绘图模块输出套餐结果、购电量、利润、灵敏度曲线。这样拆分之后的好处是你可以单独调试用户侧模型不用每次跑整个主循环。比如怀疑下层求解不对就直接给一个固定套餐看用户选了什么、费用是多少。如果这一步就出错上层再复杂也没意义。3.2 主从博弈求解的两种路线复现这类双层优化问题时Matlab里常见做法有两种。第一种是用KKT条件把下层问题转化成上层问题的约束然后一次性求解单层优化。这种做法理论上更严谨但对下层问题的形式要求很高。如果下层是线性规划KKT转换后还能处理如果下层里面有0-1变量KKT就不太好直接套还要引入大M法和互补松弛约束求解稳定性很依赖M的取值。第二种是迭代求解也叫“对角化方法”。先给一组初始套餐解下层用户问题得到用户响应再根据用户响应更新上层套餐反复迭代直到套餐和用户选择不再明显变化。这个做法简单直观代码好写但可能遇到震荡和不收敛。我在复现时更倾向于先用第二种方法把结果跑通确认模型逻辑没大问题后再考虑要不要用KKT做严格求解。因为迭代方法对新手更友好出问题也好定位是上层没更新好还是下层用户没收敛分开看就清楚了。3.3 关键代码骨架与索引设计正常复现时我习惯把套餐参数作为优化变量用户响应部分封装成一个函数。下面是一个典型的主程序骨架你可以直接用它当模板clear; clc; load(scenarioData.mat); % 场景数据、用户数据 lowerBound [0.3, 0.1, 0.05]; % [固定电价下限, 峰段电价下限, 谷段电价下限] upperBound [1.2, 0.8, 0.6]; % 对应上限 options optimoptions(particleswarm, ... SwarmSize, 60, ... MaxIterations, 150, ... Display, iter); [tariffBest, maxProfit] particleswarm((x) upperObjective(x), ... numel(lowerBound), lowerBound, upperBound, options);这里的upperObjective内部会调用下层用户问题。下层用户问题可以继续封装function userCost solveUserProblem(tariff, loadData, priceElasticity) % tariff: 当前套餐价格 % loadData: 用户各时段基础负荷 % 返回: 用户总费用和对应负荷调整方案 % 如果存在套餐选择0-1变量用 intlinprog options optimoptions(intlinprog, Display, off); [userCost, ~] intlinprog(f, intcon, A, b, Aeq, beq, ... lb, ub, options); end代码本身不难难的是索引对应关系。比如96个时段、3类用户、5种套餐变量到底怎么排我强烈建议提前设计好索引映射不要靠变量名猜。可以专门写一个说明注释块把“套餐类型→价格变量→用户类别→时段”的对应关系列出来不然改场景数量时很容易把矩阵尺寸写错。3.4 算例设计与结果校验复现时不要一上来就上大量场景。小算例跑通再扩大规模是更稳妥的做法。我常用的小算例规模是3类用户、4种套餐、96个时段、50个现货价格场景。这个规模在普通笔记本上几十秒到几分钟内能出结果。等模型完全跑通再扩展到10类用户、500个场景。结果校验方面我通常会做以下三件事把主从博弈结果和“固定套餐、不响应”的结果对比验证用户响应是否真的改变了利润把“多级市场购电”和“全部现货购电”对比验证中长期合约束是否降低了风险成本画套餐价格和用户订阅率的灵敏度曲线看结果是否符合直观逻辑。如果这三个对比都符合预期我基本可以确信复现结果是可信的。否则就回头检查目标函数、约束方向和求解器设置。4. 常见问题排查实录复现到天亮的原因4.1 迭代震荡、不收敛这是我复现双层优化时遇到最多的问题症状是套餐价格在相邻迭代里反复横跳就像在吵架。原因通常是上层更新步长太大一次更新就把价格从一个极端推到另一个极端。解决办法是给决策变量加阻尼例如tariffNew tariffOld alpha * (tariffTrial - tariffOld);alpha取0.5到0.7之间比较常见。每轮更新后还可以检查一下变量变化量如果变化量比阈值小就认为收敛。注意判断收敛不能只看目标函数变化因为可能出现目标函数变化不大但套餐价格、购电量还在明显漂移的情况。4.2 套餐选择全是0或者全部用户都挤在同一个套餐出现这类问题先别急着怀疑求解器而是回头检查套餐价格之间的相对关系。如果固定套餐定得特别便宜所有用户都会选它其他套餐形同虚设反过来如果峰谷套餐的峰段价格太高、谷段价格又没有足够吸引力用户宁可选择固定套餐也不会去响应峰谷价差。解决方法是把套餐参数初始值放到一个合理区间不要让某个套餐在价格上“碾压”其他套餐。通常我会先跑一版所有套餐价格相同的算例看用户如何选再逐步拉开价差观察订阅率变化。这样做的好处是能判断究竟是“套餐设计不合理”还是“求解错误”。4.3 KKT里的M值怎么给如果用KKT方法互补松弛条件需要M参数来线性化。M太大会导致数值问题M太小又会把可行域限制错。一个常用经验是先求一下对偶变量的数量级再把M设为对偶变量上界的好几倍比如10倍到100倍。不要所有约束都共用一个M最好根据每个约束的含义单独给。如果不确定可以先跑一个松弛版本删掉互补条件看看原始约束是否合理再逐步加回M。这里补一个重要提醒如果下层问题包含整数变量KKT并不是直接能用的。通常的做法是先固定整数变量把下层变成线性规划后再推导KKT然后在外层枚举或启发式搜索整数变量。否则你写出来的东西不是线性化的互补条件而是一个混合整数非线性问题Matlab自带的常规求解器根本解不动。4.4 代码跑得慢是因为场景太多场景数量一大运行时间马上翻倍。缩短时间最有效的办法不是换电脑而是做场景削减。常见手段是先聚类再抽样比如把500个现货价格场景聚类成20个典型场景每个场景带一个概率权重。削减之后结果往往和原始场景集差异不大但求解时间可以下降一个数量级。另外如果下层要解很多次那就尽量把用户侧程序中可以提前预计算的部分放到循环外面。比如基础负荷、矩阵系数等不随套餐变化的量不要每次都重新生成。这个优化看似不起眼实际上收益很大。4.5 问题速查表现象可能原因处理办法迭代震荡上层更新步长太大加入阻尼系数并缩小步长所有用户选同一套餐套餐价格差距不合理调整套餐初始值检查价差约束不满足购电量和负荷不匹配检查中长期、现货、实时三层电量平衡约束求解时间过长场景数量过多做聚类场景削减结果和论文差异大参数或目标函数系数不一致逐项核对收入和成本公式整数变量导致KKT失败直接对MILP用KKT先固定整数变量再推导KKT5. 从复现到扩展这套模型还能怎么改5.1 把用户换成“电动车聚合商”主从博弈的框架其实不限定用户一定是一般居民或工商业负荷。只要下层主体有“看到价格后调整行为”的能力就能嵌进这个框架。比如把用户侧换成电动汽车聚合商套餐就变成充电套餐分时电价引导的就是充电时段选择。这样一改题目就从“售电公司零售套餐”变成了“充电聚合商套餐与购电策略”应用场景更贴近现在的车网互动热点。改的时候不需要大动上层模型主要改动在用户侧约束原来的负荷可转移约束要变成充电需求约束还要加上电池容量、充电功率上下限等条件。下层从普通线性规划变成带储能约束的优化问题求解器选择也要跟着调整。5.2 增加零售套餐里的防风险条款实际业务中售电公司不会只做一个简单的固定电价套餐还会设计带“兜底条款”的方案比如设置套餐电量上限、超出部分按现货价结算或者加入最小用电量门槛。这些条款本质上都是在限制用户行为给售电公司带来的风险。把这类条款加进模型的方法也相对清晰新增约束条件比如“某个套餐的电量上限”“超出套餐电量的电价”。然后你会看到用户响应行为发生变化有些用户会为了规避超量惩罚而主动少用电有些用户则会卡着电量下限来选套餐。对售电公司来说这些条款可以减少收益下限被击穿的风险但也会降低套餐吸引力。怎么做取舍正好可以做成一个多目标优化问题。5.3 复现者真正应该带走的东西我做这类复现最深的体会是这套代码不是“标准答案”而是一套建模框架。你可以用同样的主从博弈逻辑把上层改成虚拟电厂运营商把下层改成分布式光伏用户或储能用户问题的骨架是不变的。真正有价值的是你掌握了“怎么描述先后决策、怎么把用户响应变成约束、怎么在多个市场之间分配电量”这一整套思路。如果你刚上手第一遍先别追求复现出和论文一模一样的图。先把小算例跑通再逐步加复杂度。对照代码时把目标函数一行一行翻译成中文写到纸上再和你读过的论文公式对照基本就能知道这套代码的“脾气”了。之后无论是做毕设还是做项目方案你都会手里有底。我个人最后再分享一个小技巧别把兴趣爱好耗在反复调参会参数上先花半小时把数据文件的单位和量纲统一了。很多“复现失败”其实是量纲问题比如有的价格单位是元/MWh有的地方却当成元/kWh。这类问题一旦发生结果会差上千倍而且特别难查。先把单位、索引、约束方向三件事做扎实后面就顺畅多了。