拿到这个题目的时候,我第一反应是:这又是一篇“名字唬人、代码难复现”的典型EI论文。但真正啃下来之后,我得说,这套基于主从博弈的售电商套餐设计与购电策略框架,确实是当前售电侧竞争最核心的一道数学题。买电靠批发市场,卖电靠零售套餐,中间夹着用户响应——三层逻辑环环相扣,用对了方法就是一个完整的决策闭环。
这篇内容就是围绕这个复现项目来写的。我会把模型怎么搭、代码怎么组织、求解时踩过哪些坑,以及最终仿真能看出什么门道,全部拆开讲清楚。无论你是电力专业的研究生,还是在售电公司做策略分析的从业者,只要你需要解决“怎么定套餐价、怎么从多级市场买电”这个问题,这篇东西都能给你一个可以直接抄作业的参考框架。
1. 项目到底在解决什么问题
1.1 售电商的两头受压困境
售电商这个角色,本质上是电力市场里的“中间商”:一头从批发市场买电,一头向终端用户卖电。批发侧的价格波动剧烈,现货市场甚至会出现极端尖峰;零售侧的客户又对电价极其敏感,价格定高了用户跑掉,价格定低了利润被吃光。两头的压力同时挤过来,就需要一套精细化的决策工具。
这个项目最大的价值,就是把“售电商如何在零售侧和批发侧同时做最优决策”这个问题,从定性讨论变成了一个可求解的数学规划模型。它不再拍脑袋定套餐,而是通过博弈论框架把用户的行为反应内生进来,让每个套餐设计都有据可循。
1.2 为什么偏偏是主从博弈
我一开始也疑惑:用户选套餐明明是一个分散决策,为什么要用主从博弈(Stackelberg Game)来建模?
关键在于决策的先后顺序。售电商先公布零售套餐价格,用户再根据价格调整用电行为,这是典型的“领导-跟随”关系。售电商是领导者,拥有先动优势;用户是跟随者,会在给定价格下做出最优响应。这比单纯假设价格弹性系数要合理得多——因为用户不是机械地改负荷,而是真的在解自己的优化问题。
这个框架的好处非常明显:它把用户的理性反应当成了售电商决策的约束条件。你定高价,用户的负荷就会下降,你的售电收入可能反而减少;你定低价,用户负荷上来了,但批发侧购电成本也可能飙升。这种联动关系,只有把下层用户问题写进上层优化模型,才能真正体现。
1.3 项目复现的完整闭环
从复现角度讲,这个项目做的是一整套闭环:先建立上层售电商利润最大化模型,再建立下层用户效用最大化模型,通过KKT条件或迭代求解的方式把两层问题合并,最后用Matlab实现仿真验证。整个链路非常清晰,几乎就是论文复现的教科书级案例。
我把它拆解成几个模块:多级市场购电策略模块、零售套餐设计模块、用户响应模型模块,以及博弈求解模块。后面我展开讲每个模块怎么实现。
2. 数学模型的三个关键层次
2.1 上层:售电商利润最大化
先看上层的售电商模型。目标函数是最大化总利润,包括零售收入、批发市场购电成本,以及多级市场之间的调整成本。
简单来说,售电商在日前市场以日前价格买一部分电,在实时市场根据实际偏差再做调整,同时以零售套餐价格向用户卖电。利润就是卖电收入减去购电成本,再减去偏差考核费用。这里的关键是要把“多级市场”写进模型:不同市场的价格不同,购电决策不能只盯一个市场。
模型的决策变量包括向用户公布的零售套餐电价、日前市场购电量、实时市场调整量,以及可能涉及的储能充放电策略(如果有的话)。约束条件则涵盖电力平衡约束、购电上下限约束,以及套餐定价的合理性约束。
2.2 下层:用户用电效用最大化
下层的用户模型,是整个博弈的精髓。用户会在给定的零售电价下,调整自己的用电量来最大化效用。这里的效用函数通常设计成一个二次函数,比如:
[ U(d) = \omega d - \frac{1}{2}\theta d^2 ]
其中 (d) 是用电量,(\omega) 和 (\theta) 是用户的效用参数。用户在给定电价 (p) 下,最大化 (U(d) - p \cdot d)。对这个函数求导,就能得到用户的最优用电量对电价的响应函数:
[ d(p) = \frac{\omega - p}{\theta} ]
这个响应函数虽然形式简单,但意义重大:它把“用户会对价格做出什么反应”这个看似模糊的问题,变成了一条可解析的曲线。后续做套餐设计时,这条曲线就是售电商做决策的关键输入。
2.3 主从博弈合并:KKT条件
上下两层都是凸优化问题,所以可以用KKT条件把下层问题转化成上层问题的约束条件,变成单层的带均衡约束的数学规划(MPEC)。这样做的优势是能直接调用成熟的求解器,一性次求出最优解。
不过这里有个细节坑:下层问题的互补松弛条件是非线性约束,直接扔给求解器很难收敛。我在复现时采用的折中方案是:先做线性化处理,对互补条件引入二进制变量,通过大M法转换成线性混合整数约束。虽然求解规模变大了,但至少能保证Matlab里的求解器稳定跑完。
顺便说一句,如果你用的Matlab版本较老,solver接口可能会对这类约束支持不好。我后来的经验是:先用迭代法(比如交替优化)跑一个初值,再喂给精确求解器,收敛速度会快不少,这招在后面的复现过程中帮了我大忙。
3. Matlab代码的组织与实现细节
3.1 整体代码目录结构
拿到这个复现项目时,我第一件事就是重新梳理代码结构。原始代码算是能跑,但可读性比较差。我按功能拆分,最终形成了这样的结构:
├── main.m % 主程序入口 ├── data/ │ ├── load_forecast.m % 负荷预测数据生成 │ ├── price_scenario.m % 批发市场价格场景生成 │ └── user_params.m % 用户效用参数设置 ├── model/ │ ├── upper_model.m % 上层售电商目标函数与约束 │ ├── lower_model.m % 下层用户响应模型 │ ├── kkt_merge.m % KKT条件合并与线性化 │ └── mpec_solve.m % MPEC求解主函数 ├── strategy/ │ ├── tou_design.m % 分时套餐设计 │ ├── ladder_design.m % 阶梯套餐设计 │ └── realtime_follow.m % 实时电价联动套餐 ├── result/ │ └── plot_results.m % 可视化输出这个结构的好处是:数据和模型分离,实验时只需要改参数文件,模型代码完全不用动。如果你想扩展新的套餐类型,直接在strategy目录下加一个功能文件就行。
3.2 参数设置与基础数据
参数设置是整个复现的基础。我按实际论文里的典型数值做了初始化,但这部分是完全可以按自己的场景改的。核心参数包括:
| 参数 | 典型取值 | 说明 |
|---|---|---|
| 日前市场价格 | 均值参考系统负荷取值 | 用ARIMA或者正态分布模拟 |
| 实时市场价格 | 日前价格的1.2-1.5倍波动 | 体现偏差惩罚力度 |
| 用户效用系数 (\omega) | 0.6 - 0.9 | 用户对电价的敏感度 |
| 用户效用系数 (\theta) | 0.01 - 0.05 | 用电饱和程度 |
| 售电公司风险偏好 | 0.5 - 1.5 | 影响购电组合的风险权衡 |
这里有个非常重要的经验:不要直接用论文附录里的参数就跑仿真,至少要先检查量纲。我遇到过 (\omega) 和电价一个数量级的离谱设置,导致用户永远选择不用电,仿真彻底失真。
3.3 多级市场价格场景生成
多级市场购电之所以叫“多级”,就是因为不是单一市场买到底。在这个模型里,我设计了两层市场:日前市场为主力采购市场,实时市场作为偏差调整市场。
价格场景生成采用蒙特卡洛加场景削减的办法。先生成500个随机价格场景,再用同步回代削减法聚合成5到10个典型场景。场景削减这一步很多人会忽略,但它直接影响求解速度和结果的可靠性——场景太少会失真,太多会导致MPEC规模急剧膨胀,Matlab直接跑不动。
具体削减算法很简单:随机选择两个场景,计算KL距离或者欧式距离,把距离最近的两个场景合并,重复直到达到目标场景数。代码写起来不到50行,但效果非常好。
3.4 求解器选型与配置
Matlab里求解MPEC问题,可选的求解器不少。我试过fmincon、cplex、gurobi,最后锁定cplex做线性化后的MILP求解。原因很简单:互补条件线性化之后,整个问题变成一个大规模混合整数线性规划,正好是cplex的强项。
如果只能用Matlab自带工具箱,fmincon也能凑合,但需要把整数变量约束做松弛处理,精度会打折扣。而且fmincon处理这种带大量约束的MPEC问题,起步点极其敏感——同一个模型,换一个初值可能就跳到另一个局部最优。
我在复现时给fmincon加了一个多起点循环:随机生成20个初始点,记录每个点的求解结果,选最优的那个作为最终结果。这招虽然费时间,但能有效避免陷入坏的局部解。
4. 仿真结果与关键分析视角
4.1 套餐设计的结果对比
这个复现项目里,套餐不只是单一的分时电价,而是包含了固定套餐、分时TOU套餐、阶梯套餐和实时联动套餐四种。它们的核心区别在于价格信号对用户的传递方式不同。
仿真跑完之后,四种套餐的价格曲线和利润分布差异非常明显。固定套餐在价格平稳时段表现稳定,但在现货价格尖峰时段几乎必然亏钱;实时联动套餐正好相反,波动风险全部转移给用户,售电公司的利润曲线最平滑,但用户接受度差;分时套餐和阶梯套餐则属于折中方案,既保留一定价格信号,又给用户确定性。
一个很有意思的结论是:在用户群体具有高价格敏感度的场景下,阶梯套餐的综合效果最好。因为它能自然形成“用得多,单价高”的激励结构,引导用户自发削减峰值负荷,售电商的批发侧采购压力也随之减轻。
4.2 多级市场购电策略的核心发现
多级市场购电策略的仿真结果,让我重新理解了“买电”这件事。模型给出的最优策略并不是在日前市场买满全部电量,而是保留一定比例的实时调整空间。
为什么?因为日前价格虽然便宜,但预测总有误差。如果日前买满,实时市场一旦出现重大偏差,就需要付出极高的平衡成本;反过来,如果日前买太少,实时价格又可能飙高。最优解是在日前市场购买约85%的预测负荷,剩余部分根据实时场景动态调整。这个85%不是固定值,它会随着价格波动率变化——价格越不稳定,最优日前购电比例越低。
这个发现直接回答了“为什么要多级市场购电”的问题:不是哪个市场便宜就all in哪个,而是要看两个市场的价差分布,做一个风险最优化组合。
4.3 博弈均衡的收敛特性
主从博弈模型迭代求解时,我最关心的问题是收敛性。说实话,第一次跑迭代的时候,观察上下层目标函数曲线,前5次迭代波动很剧烈,迟迟不收敛。
问题出在用户响应函数和售电商目标之间的耦合太强。解决办法是引入阻尼因子:每次迭代后,新的电价取当前解和上一轮解的加权平均。阻尼系数设成0.35左右,收敛速度虽然会放慢,但稳定性显著提升,最终都能收敛到同一个均衡点。
另外要多说一句:验证均衡的唯一性非常困难,至少从仿真角度看,不同的初值会收敛到不同的均衡,但这些均衡下的利润差异通常不超过5%。所以在论文或项目报告里,可以用多次随机起始点的最优值作为最终结果,并注明均衡的多重性。
5. 复现过程中的坑与排查实录
5.1 常见问题速查表
| 问题 | 可能原因 | 解决办法 |
|---|---|---|
| KKT条件合并后,模型不收敛 | 互补松弛条件里的M值取得太大 | M值取价格上限的2到3倍即可,太大破坏数值稳定性 |
| 用户负荷为负 | 效用参数 (\omega) 设置过小 | 检查 (\omega) 是否大于最高套餐电价 |
| 场景削减后价格特征失真 | 削减前场景数过少 | 初始场景至少500个,削减后5到8个场景比较稳妥 |
| 求解效率极慢 | 二进制变量过多 | 先用启发式方法求可行解,再喂给精确求解器做热启动 |
| 不同初值结果差异大 | 原问题非凸 | 采用多起点求解,或把问题拆成主问题和子问题迭代 |
5.2 我用过最顺手的调试流程
复现这类博弈优化模型,我个人的调试路径特别固定,已经形成了一套流程。第一步永远是先固定套餐价格,只跑购电策略——把买电这半边验证清楚,再做零售套餐这块。第二步再固定购电策略,只调套餐参数。最后两层联动。
这样拆开调的好处是能避免“两边一起错,最后根本找不到源头”的窘境。我最初就是上、下层一起调,结果利润和负荷全不对,花了好几天才发现是下层用户模型里的一个符号写反了。先固定一半再调另一半,问题排查效率能提高一倍以上。
5.3 数值仿真的几条心得
数值仿真要做到“毕竟是个能用、能解释的结果”,不只是跑通就行。我有几条心得,随便分享下。
第一,所有价格和电量一律采用标幺值或先归一化再计算。这样不仅数值稳定性好,而且不同量纲的参数可以直接对比,不容易出低级错误。第二,每个场景下的约束是否满足,要在主程序里单独写一个检查脚本,不要完全信任求解器的输出。第三,最终绘图时如果发现用户的负荷曲线在套餐切换点出现明显跳变,先不要急着说是用户行为,很可能是节点电价解的不稳定性,把价格序列平滑一下再试。
6. 扩展思路:这个模型还能往哪走
6.1 加入储能与分布式光伏
当前模型里售电商只是纯买卖电,没有自有资产。但实际售电公司很多都配了储能或者聚合了分布式光伏。这个模型想扩展其实很直接:在约束里加储能充放电约束、光伏出力约束,目标函数里加上储能投资和运维成本。因为主从博弈框架本身就是模块化的,增加设备模型对整体求解结构没有本质冲击。
6.2 从单用户代理到多用户聚合
原模型里的下层用户是一个聚合体,相当于假设所有用户同质。现实显然不是这样。可以按用户类型拆成居民、商业、工业三类,每类用户有自己的效用参数集,下层就从单优化问题变成一个多代理均衡问题,求解难度会升级,但结果更贴近实际。
如果你有精力,还可以更深一步:把用户的响应行为从确定性改成概率性,引入MNL模型描述用户的套餐选择倾向。那模型就从确定性博弈变成了带随机选择的混合博弈,不过这个我先不提了,复杂度一下子会高很多,适合后续作为学术方向的升级版本,不适合普通复现项目一上来就做。
6.3 结合强化学习做动态决策
这个方向的诱惑力很大。原模型是两阶段的静态优化,实际运营中售电商面对的是一连串决策——每个小时都要定一次价,每天都在批发市场操作。主从博弈虽然能刻画用户响应,但无法很好地处理长期状态转移。
我设想过把上层售电商决策改成强化学习智能体,下层用户响应还是用解析模型。这样既保留博弈框架的解释性,又能享受强化学习在序贯决策上的优势。不过这只是展望了,当前复现这个项目时,我用的还是纯优化方法,毕竟Matlab生态下强化学习工具箱的成熟度还比不上直接调求解器稳定。
复现之后的一点个人感想
整个项目复现下来,我最深的体会是:这类文章的代码复杂度并不高,真正的高墙在数学建模的细节转化上。从KKT条件到互补松弛,再到线性化处理,每一步都要求既懂优化理论又对电力市场业务有感知。起初我也反复看了很多次代码推不下去,后来把注意力从“看懂每行”转移到“理解每个约束的物理含义”,进度一下子顺畅了。
最后再分享一个调试小技巧吧。Matlab运行这类MPEC问题时,我习惯在目标函数和约束函数里打印当前迭代点的关键信息。数据规模不大时可以直接用去抖动组合,比如固定随机种子来保证场景生成可复现。这些细节看着不起眼,但在审稿或给组里汇报的时候,能让结果经得起追问。希望这篇拆解对正在啃同类代码的你有帮助。