基于主从博弈的售电商多元零售套餐与多级市场购电策略复现

发布时间:2026/9/12 2:11:21
基于主从博弈的售电商多元零售套餐与多级市场购电策略复现 做电力市场方向的算法复现最怕遇到两类题目一类是模型稀碎论文里全是“显然可得”代码根本对不上另一类是模型完整但求解器一跑就是十几个小时收敛了算你运气好。这个“基于主从博弈的售电商多元零售套餐设计与多级市场购电策略”就属于后者——模型结构清晰博弈层级分明但真正动手用Matlab复现一遍坑全藏在细节里。我算了一下这套代码跑通之后正好覆盖了售电公司决策链路上最核心的两个环节对下怎么给用户设计零售套餐对上怎么在批发市场里分配购电比例。中间用主从博弈把两个环节串起来形成一个上下层联动的闭环决策模型。这个思路放在今天依然不过时而且很适合新手入门博弈论与电力市场交叉方向的第一个完整项目。这篇文章把我从读论文到写出可运行Matlab代码过程中踩过的坑、摸出来的路完整走一遍希望能给正在啃这个方向的同学一点参考。1. 先说清楚这个复现项目到底在做什么1.1 标题拆解三个关键词背后是一整套决策链路“主从博弈”“售电商”“多元零售套餐”“多级市场购电策略”这四个词组拆开看都不难理解但合在一起代表的是一个上下两层、环环相扣的优化问题。“主从博弈”是Stackelberg博弈的中文学术叫法核心思想是决策有先后一个领导者先表态跟随者再根据领导者的决策做自己的优化。在电力零售场景里角色分工非常自然——售电商先公布套餐电价用户看到电价后调整自己的用电行为。售电商做决策时又必须预期到用户的这种反应所以不能拍脑袋定价必须把“用户怎么响应价格”这件事解出来再放进自己的利润优化里。“售电商”就是电力零售商从批发市场买电然后卖给终端用户。它既不是发电厂也不是用户属于中间商环节。中间商的难点在于两头受挤上游购电价格有波动下游用户有选择权利润空间需要靠精细化的价格策略“抠”出来。“多元零售套餐”对应的是售电商对下游用户的价格设计。用户可以选固定电价套餐也可以选分时电价套餐不同套餐对应不同的价格结构目的是差异化引导用户改变用电行为实现削峰填谷同时提升售电商利润。“多级市场购电策略”对应的是售电商对上游批发市场的购电决策。电力批发市场通常不是单一市场而是分多个时间尺度——中长期合同、日前市场、实时市场等等。购电策略就是决定在哪个市场买多少电既要保证成本低又要控制价格波动风险。所以这个题目本质上不是在做一个孤立的定价模型而是在建一个一套完整的售电公司决策链路上游购电决策、下游零售定价、两者之间的博弈联动一步到位。1.2 选中这个题目的理由为什么主从博弈特别适合售电定价做这个方向之前我也想过售电公司定价不就是个利润最大化问题吗直接建个优化模型解一下不就行了问题没这么简单。如果直接把用户需求当成固定参数来优化电价得到一个价格然后把这个价格扔给用户用户实际反应会和你的假设完全不同。用户的用电量会随着电价变化而变化——电价高了就用得少电价低了就用得多。这个弹性效应如果不在定价时考虑进去算出来的“最优价格”只是纸上谈兵。主从博弈正好给了这个问题一个规范的数学框架。它把决策拆成两层领导者售电商先给出价格跟随者用户根据价格做用电量优化。核心是我们回到了一个博弈问题决策是序贯的售电商定价时就要“站在用户的角度”预判用户会如何反应。这种结构比单层优化更贴近真实市场运行方式而且从学术上说模型的解释力强很多。再往下想如果能把这个双层结构跑通这个方法论完全可以平移去做其他领域的定价问题比如共享出行平台定价、云资源定价、电信流量套餐设计。这也是我当初愿意投入时间复现这个题目的原因——模型的可迁移性很强学一次到处用。2. 核心模型框架主从博弈是如何运作的2.1 Stackelberg博弈的核心思想与数学表达Stackelberg博弈的核心关键词就两个序贯决策和逆向归纳。序贯决策是说决策有先后逆向归纳是说要先算清楚跟随者的最优反应再回头确定领导者的最优决策。放到售电场景里过程是这样的领导者售电商选择零售套餐价格策略跟随者用户看到价格后在预算和用电效用之间做权衡决定用多少电用户的用电量汇总起来又决定了售电商的总售电量售电商再根据这个总售电量去批发市场安排购电计划。数学上主从博弈可以写成这样一个双层优化框架上层目标售电商利润最大化 max 利润 零售收入 - 购电成本 决策变量是套餐价格下层问题用户效用最大化 max 用户效用 - 电费支出 决策变量是各时段用电量这里最关键的难点是上层的问题里嵌套着下层的最优化问题不能直接把下层变量当常数来优化。2.2 双层模型的构建上层定价下层响应下层用户建模是整个模型的地基。大多数论文会把用户的用电效用定义成一个关于用电量的二次凹函数U(q) a * q - 0.5 * b * q^2其中a和b是描述用户偏好的参数。用户要最大化“用电效用 - 电费支出”max [a * q - 0.5 * b * q^2 - p * q]对q求导并令导数为零得到a - b * q - p 0也就是说q (a - p) / b这个形式太重要了。它把用户对价格的反应直接变成了一个线性需求函数价格越高用电越少参数b决定了用户对价格的敏感程度。b越大用户越倾向于节省电需求变化对价格越敏感。有意思的是这个线性需求函数在后面代码实现的时候给我们省了很大的麻烦。因为下层优化如果是线性或二次凸问题KKT条件就有闭式表达整个求解过程能大大简化。这也是为什么很多论文都采用二次效用函数——它既符合经济学直觉又保证了数学上的可解性。上层问题是售电商的利润最大化。利润等于零售收入减去购电成本。零售收入是各套餐价格乘以对应用户的用电量购电成本则依赖各市场购电量与价格。上层问题的决策变量是各类套餐的定价同时还要满足一些约束比如价格设定不能导致用户效用为负否则用户就不会选择这个套餐。2.3 求解路径KKT条件与MPEC转化双层优化最大的难点在于上层问题的约束里包含下层最优化问题没法直接喂给求解器。解决这个问题的标准手法是用下层问题的KKT条件替换下层优化问题得到单层但带互补约束的数学规划问题简称MPEC。MPEC问题的麻烦之处在于互补约束也就是这种形式0 λ ⊥ F(x) 0意思是λ和F(x)至少有一个等于0。这种约束让整个问题不再满足经典的约束规范求解器无法直接处理。处理互补约束的常见办法是大M法引入一个足够大的常数M把互补约束转成混合整数线性约束F(x) 0λ 0F(x) M * zλ M * (1 - z)z是0-1变量这样MPEC问题就变成了一个MILP或MIQP问题可以交给Gurobi或CPLEX这类商用求解器直接求解。这里藏着一个实际经验大M的取值非常讲究。取太小会错误地截掉可行域取太大会引起数值病态导致求解精度下降。我在复现过程中试过从100到1万的不同取值最稳定的区间大概在价格的5到10倍量级。如果你发现同样的模型在不同M值下结果差异很大首先要怀疑的就是大M取值不当。除了直接用KKT条件转化还有另一条路线用迭代法在上层和下层之间反复横跳。先给定一个价格求解下层用户响应得到用电量后带回上层更新价格再传给下层如此循环直到价格收敛。这条路线实现简单但收敛性没有保证遇到多均衡场景容易震荡。3. 多元零售套餐设计与多级市场购电策略拆解3.1 套餐设计价格信号如何引导用户改变用电行为多元零售套餐是这个模型的下游模块核心目标是用价格杠杆引导用户调整用电行为。现实中的用户不是铁板一块有的对价格敏感有的更看重用电稳定有的用电时间长、有的集中在峰期。一套价格走天下必然损失利润多元套餐本质上是对用户群体的细分。我实现的模型里加入了几类典型套餐固定电价套餐一天24小时价格不变适合风险厌恶型用户。这类用户不希望电价波动愿意接受一个中等偏高的统一定价换取确定性。分时电价套餐TOU把一天分成峰、平、谷三个时段峰时电价高、谷时电价低。这类套餐鼓励用户把用电从高峰时段挪到低谷时段。实时电价套餐RTP直接跟随批发市场价格波动批发价高零售价就高批发价低零售价就低。用户承担了价格波动的风险但有可能获得更低的平均电价。不同套餐的设计不是孤立的它们共享同一个用户群。用户在多个套餐之间做选择会尽量挑选让自己效用更高的那个。也就是说上层定价时必须同时考虑套餐间的竞争关系确保每一类特定套餐都至少能吸引到一部分特定属性的用户否则这个套餐就是无效的。算例中我设置了一个比较常见的分时电价情境峰时段电价1.2元/kWh平时段0.75元/kWh谷时段0.4元/kWh固定电价套餐取0.9元/kWh。用户需求弹性较大时分时电价能把一部分高峰负荷挪到低谷从系统角度看是削峰填谷从售电商角度看是降低了高峰时段的高价购电压力。3.2 购电策略批发市场的层级结构与购电组合优化多级市场购电策略是这个模型的上游模块。真实电力批发市场不是一天只买一次电而是按照不同时间尺度滚动交易中长期市场提前几个月到几周签购电合同价格相对稳定锁定基础电量控制风险。日前市场运行日前一天确定第二天的发用电计划价格波动开始显现。实时市场运行实时平衡偏差电量价格波动最大但只涉及少量电量。购电策略就是决定这三个市场各自购多少电。逻辑上中长期购电比例高则成本稳定但可能错过低价机会实时市场比例高则有机会吃到低价红利但风险也大。一般思路是中长期锁定70%到80%的基础电量剩下在日前和实时市场里优化分配。模型里把购电成本拆成三部分之和各市场的购电价格用场景集合描述通过条件风险价值或者方差项来控制购电成本的波动风险。这里有一个很容易踩的坑不要把所有用户负荷都当成一个总负荷来匹配购电策略。因为套餐结构不同分时电价用户和固定电价用户的负荷曲线差异很大。分时用户的负荷会明显向谷时段迁移如果购电策略还用原始的聚合负荷曲线会高估峰时段购电需求低估谷时段购电需求成本算出来是偏高的。正确做法是先算出各套餐用户的优化后负荷曲线再加总去匹配购电决策。3.3 两级决策的联动逻辑套餐定价与市场购电是同一个硬币的两面很多第一次做这个题目的人最容易把“零售套餐”和“购电策略”当成两个独立的问题先后去解。先随便定个套餐价格算出来用户负荷再做购电优化——这个顺序做出来基本是垃圾结果。原因是这两个问题有强耦合购电成本取决于总负荷曲线总负荷曲线取决于用户用电量用户用电量取决于套餐价格套餐价格又必须覆盖购电成本设定利润空间。任何一个环节变动都会顺着链条传到底。举个具体例子如果购电成本上升售电商需要提高零售电价来维持利润。但电价一提高价格敏感型用户就会降低用电量甚至改选其它套餐导致总售电量下降。如果售电量下降幅度过大提高电价反而可能导致收入降低。这就是经济学里的“需求弹性反噬”。主从博弈模型的精妙之处就在这里它把这两件事放在同一个框架下一并解决。上层优化里零售价格和购电分配同时作为决策变量出现下层用户响应既是定价的依据也是购电需求的计算前提。两个决策在同一个优化问题里互相制约、互相支撑最终同时输出套餐定价方案和购电分配方案不是两步走而是一步到位。4. Matlab代码实现从模型到可运行代码的关键步骤4.1 代码整体架构与文件规划拿到题目后我没有立刻开写而是先盘了一下代码结构。Matlab项目最怕所有变量堆在一个m文件里跑的时候各种命名冲突和调试地狱。我的经验是函数化、模块化一个功能一个文件主程序只做流程控制。复现这套代码文件规划大致如下文件名功能main.m主流程控制初始化参数调用求解函数输出结果data_para.m所有参数的集中定义包括用户参数、市场电价、套餐初始值lower_user.m下层用户响应求解函数输入价格输出各时段用电量upper_retailer.m上层售电商利润函数输入价格和电量输出利润值solve_mp ec.m将双层问题转成MPEC后用求解器求解iterate_solve.m迭代求解方案上层给价格、下层响应、更新价格循环output_plot.m结果可视化与数据导出核心变量我用struct封装比如data.users里放用户数、参数a和bdata.market里放各市场价格曲线data.para里放迭代容差、大M值等关键参数。这样一个struct穿梭在各个函数之间传参方便后期调整参数也不用到处找。4.2 参数设置与数据预处理参数设置是复现的重头戏也是结果好坏的分水岭。论文原文给的参数往往不够齐全很多细节需要自己补。我这套代码的关键参数如下时间尺度24时段模拟一天用户数大规模场景设500个用户小规模测试用100个用户用户弹性参数b基准值0.2灵敏度分析时在0.05到0.8之间取值用户偏好参数a根据基准负荷反推确保在基准价格下电量处于合理区间套餐类型固定电价、分时电价、实时电价三类批发市场层级中长期、日前、实时三个市场用户参数预处理的逻辑值得多说一句。很多复现卡在“参数对不齐”上——基准价格下算出来的用电量跟论文差得离谱本质是因为参数a和b设置不匹配。建议用这个方法校准先设定一个基准价格p0和基准电量q0然后让需求函数过这个点反解a p0 b * q0。这样你控制了b后a自然适配基准场景结果不会跑偏。市场电价数据默认用高斯分布生成场景集。中长期价格均值取0.45元/kWh标准差0.02日前市场均值0.55元/kWh标准差0.08实时市场波动最大均值0.6元/kWh标准差0.15。标准差越大反映市场不确定性越强购电策略在风险成本上的倾向就越保守。4.3 主循环上下层迭代求解的实操细节我实现的代码里面默认提供两种求解路径一是直接求解MPEC版本用YALMIP建模调Gurobi解MILP二是双层迭代版本不依赖商用求解器也能跑通。先说迭代版本这个实现思路直观适合Debug。伪代码结构如下% 初始化 price_init [0.9; 1.0; 0.85]; % 各套餐初始电价 price price_init; alpha 0.25; % 阻尼系数防止价格震荡 tol 1e-4; max_iter 100; for k 1:max_iter % 给定价格求解下层用户响应 [q_user, ~] lower_user(price, data); % 根据用户响应电量求解上层购电优化 [q_market, cost_total, profit_current] upper_retailer(q_user, data); % 上层还需要同步调整个别套餐价格 % 用梯度方向更新并用阻尼系数限制步长 new_price update_price(price, q_user, cost_total, data); % 价格收敛判据 if norm(new_price - price) tol price new_price; break; end % 阻尼更新避免震荡 price price alpha * (new_price - price); end这里有个非常实操的细节阻尼系数alpha一定要加。不加阻尼的时候价格更新特别容易出现振荡上一轮价格1.0下一轮跳到1.3再下一轮又跌回0.9来回晃就是不收敛。加了个0.25的阻尼之后价格步长被压住稳定性明显提升。再说MPEC路线的关键代码思路。用YALMIP建模时先定义连续变量价格p、电量q再定义0-1变量z来处理互补约束。核心代码结构如下% 决策变量 p sdpvar(3, 1); % 三类套餐价格 q sdpvar(24, 1); % 24时段用户总用电量 z binvar(3, 24); % 互补约束线性化辅助0-1变量 % 目标函数零售收入 - 购电成本 - 风险项 profit sum(p .* q) - sum(cost_market .* q_market) - risk_cost; % 约束KKT条件的互补松弛线性化 for i 1:3 for t 1:24 Constraints [Constraints, ...]; % a - b*q_t - p_i M1*(1-z(i,t)) % lambda_i 0 % ... end end ops sdpsettings(solver, gurobi, verbose, 2); optimize(Constraints, -profit, ops);这条路径的优点是精度高一次求解直接得到最优解不用像迭代法那样碰运气。缺点是对求解器有依赖YALMIP必须正确配好Gurobi或CPLEX而且模型规模上去后求解时间会剧增。我的体感是这样的小算例100用户24时段3套套餐Gurobi几秒内就能解出来用户规模上到1000时段扩展到96个点求解时间会暴涨到几十秒甚至几分钟。如果只是想验证模型思路用100用户24时段就够了没有必要一上来就跑大规模。5. 常见问题与排查技巧实录5.1 迭代不收敛时的排查方向迭代求解不收敛是复现这类博弈模型最折磨人的问题。我整理了一份排查清单按优先级排列先看价格是否在两个值之间来回震荡。震荡代表没有阻尼或者阻尼太小把alpha从0.25降到0.1试试。再看下层用户响应是否有闭式解。如果你用的是数值优化求解下层问题每次求到的解有微小差异上层基于这些差异更新价格很难收敛。检查用户参数b是否过小。b太小意味着用户对价格极度不敏感需求量基本不随价格变动博弈问题在数学上接近退化迭代自然难收敛。排查是否存在多均衡解。可以试试从不同的初始价格出发多次运行看最终收敛到哪个均衡。如果不同初值收敛到不同结果说明系统有多均衡你需要结合具体场景解释哪个均衡更合理。5.2 求解器选型与性能对比Matlab环境下的求解器选型我试过三种路线YALMIP Gurobi最推荐。模型建模灵活求解MILP和MIQP性能强尤其在非线性约束线性化之后大规模问题也能应对。YALMIP CPLEX同样可靠调用方式几乎一样区别在于Gurobi在某些二次约束场景下面表现更好。Matlab自带quadprog/intlinprog适合小规模教学演示不用额外装工具箱。400用户以下跑起来没问题用户规模一大就力不从心了。选求解器的核心原则是先判断问题类型。如果模型全部线性化是MILPintlinprog也能凑合如果保留了二次项是MIQP就上Gurobi或CPLEX。不要没搞清问题类型就开始写建模代码否则后面全是“求解器不支持”的报错。5.3 参数灵敏度分析中的几个坑跑灵敏度分析时我踩过一个很经典的坑对b用户价格敏感系数做灵敏度扫描得到的结果曲线是锯齿状的。一开始以为是模型写错了检查了很久才发现原因是每个b值下模型都存在多个局部最优解求解器每次都随机收敛到不同的解。解决办法是给求解器设置固定的随机种子或者从上一个解的热启动值继续搜索这样相邻的灵敏度点结果连续曲线就平滑了。另一个坑是综合购电成本的市场风险权重参数。这个参数在目标函数里控制购电成本波动的惩罚力度。我试过把风险权重从0.1扫描到10结果发现权重太小购电策略倾向于全压中长期市场风险暴露高但成本低权重太大策略变得极度保守几乎所有电量都在中长期锁定失去了在日前市场博弈低价的收益空间。这个参数没有“正确答案”完全取决于售电商的风险偏好水平。最后分享一个工具层面的小经验如果你也在复现这类双电优化模型建议在Matlab代码目录下建一个result文件夹每次运行自动把价格、电量、利润结果导出为Excel或CSV。我吃过无数次亏——跑了一小时得到一个很不错的解结果忘了保存后面调参把代码覆盖了最优解再也找不回来。数据落地是复现项目的最后一道保险。这个Topic后续还可以继续深挖的方向也很多多时段联动定价、多类用户差异化套餐、考虑用户转换成本、结合强化学习的动态定价每一个都够开一个新题目来做。但前提是你先把这套主从博弈的底子打牢——模型结构吃透了后面怎么扩展都走得稳。