☰
基于主从博弈的售电商多元零售套餐设计与多级市场购电策略Matlab复现
2026/10/15 23:20:09 网站建设 项目流程

很多做电力市场方向的同学,看到“主从博弈”“售电商套餐设计”“购电策略”这几个关键词凑在一起,第一反应往往是“这又是哪篇顶刊的抽象模型”。等真正把论文拿过来准备复现的时候,才发现数学推导密密麻麻,代码逻辑绕来绕去,光是搞懂上层和下层谁先决策、谁在响应,就够喝一壶的了。我这次复现的,就是这样一篇典型的EI级别工作——基于主从博弈的售电商多元零售套餐设计与多级市场购电策略,用的是纯Matlab实现。整个项目做下来,最大的感受是:这个题目看着唬人,但剥开外壳之后,核心骨架其实非常清晰——一个领导者(售电商),一群跟随者(终端用户),以及一个需要同时兼顾中长期市场和现货市场的购电决策问题。今天这篇文章,我就把这个模型的数学逻辑、代码实现、参数调优过程和踩坑记录完整拆开讲一遍,给准备做类似方向复现的同学一个可以直接上手的参考。

1. 项目整体拆解与底层逻辑

1.1 先理解“主从博弈”在这套模型里到底意味着什么

主从博弈(Stackelberg Game)本质上是一种“先手和后手”的游戏。放在售电商这个场景里,售电商就是那个“先手”,它先制定零售套餐的价格和电量结构;用户的角色是“后手”,看到套餐之后再决定自己用多少电、把多少负荷从高峰挪到低谷。这个顺序很关键,因为售电商在做决策的时候,不能拍脑袋定价,它必须把用户后续的理性反应提前算进去——这就是“前瞻性”决策,也是主从博弈区别于普通单层优化的核心特征。

代码实现上,这套模型最常见的落地方式是“双层规划”(Bilevel Programming)。上层是售电商的收益最大化问题,决策变量是零售套餐的设计;下层是用户侧用电行为优化,决策变量是各类负荷的响应量。上下层之间通过价格信号和电量需求耦合在一起。我在复现的时候,上层用的(实际是YALMIP建模)调用求解器求最优解,下层则直接写成用户效益最大化的凸优化子问题。两层之间通过KKT条件强对偶转化,或者直接用迭代法逼近均衡解。这里需要特别注意一个点:下层问题如果是凸的,可以用KKT条件或者强对偶性质把它等价替换到上层的约束里,把双层问题压成单层;但如果下层问题带整数变量或者强非凸约束,那就老老实实用迭代法,别想着一步到位。

提示:判断下层问题凸不凸,是决定你后面代码写起来省事还是费劲的分水岭。我这次复现的下层模型是线性约束+二次效用函数,标准凸问题,所以走了KKT替换路线,求解速度和稳定性都好了不少。

1.2 输入数据与场景设定

这套代码的核心输入有三块。第一块是用户侧数据,主要包括用户基线负荷曲线(按小时一条,24点)、可转移负荷比例、可削减负荷的单位补偿成本,以及用户对电价的敏感系数。第二块是零售套餐结构,价格型套餐(分时电价TOU)和激励型套餐(需求响应补贴),这一块是售电商的设计选项,也就是上层优化的决策空间。第三块是市场购电数据,涵盖中长期合约分解到小时的曲线、日前现货市场预测电价,以及偏差电量考核的单位惩罚费用。

这些数据看起来不难准备,实际跑起来的时候坑全在细节里。比如用户基线的负荷形状,直接决定了套餐削峰填谷的效果,如果基线本来就很平,那你怎么定价都削不动;再比如现货电价和售电套餐的联动关系,如果现货价在某个时段冲高,但套餐里的分时电价没有同步拉开峰谷差,售电商的购电成本就会明显高于卖电收入,这一轮迭代基本就亏了。我建议自己复现的时候,先用一个简化的6节点或者单节点负荷系统跑通逻辑,再逐步把规模放大,不要一上来就上高维数据。

2. 核心建模与算法实现

2.1 上层售电商零售套餐定价模型

上层模型的目标函数很直接——售电商的净利润最大化。净利润分成三大块来拆:向用户卖电的收入,扣除在中长期市场和现货市场的购电费用,再扣掉偏差电量带来的考核成本。卖电收入这块要注意,用户负荷是分类的,有刚性负荷、可转移负荷、可削减负荷,不同类型负荷对应的套餐价格不同。刚性负荷对价格不敏感,只能赚固定度电价;可转移负荷对峰谷价差敏感,这部分要靠分时电价引导用户把负荷从高峰挪到低谷;可削减负荷则需要售电商给出激励价格,用户才会在尖峰时段主动压负荷。

实际写代码的时候,上层决策变量大概是这样的构成:

% 售电商上层决策变量 P_tou_peak = sdpvar(1, 1); % 峰时段售电价格 P_tou_flat = sdpvar(1, 1); % 平时段售电价格 P_tou_valley = sdpvar(1, 1); % 谷时段售电价格 R_incentive = sdpvar(1, 1); % 可削减负荷单位激励 X_contract = sdpvar(24, 1); % 中长期合约分时电量

这里有一个容易踩的坑:峰谷时段的划分本身也该是决策的一部分,但很多复现版本图简单,提前把所有时段划分为峰、平、谷三类,然后只优化价格。这种做法会让结果偏保守,因为最优的峰谷划分可能与固定划分差很多。我的做法是先把24小时的负荷需求曲线做宏微观观察,找到天然的高峰区间和低谷区间,再把时段划分作为外层循环的枚举,内层只优化对应的价格变量。这样算下来,整体求解时间大概增加30%-40%,但收益结果通常会好一个台阶。

2.2 下层用户负荷响应模型

下层模型做的事情,是在给定的套餐方案下,模拟用户调整用电行为来最大化自己的效用。用户效用函数一般用二次型来表示,比如:

U(q) = a * q - 0.5 * b * q^2

其中a和b是用户的偏好参数,q是用电量。用户每类负荷的用电量会受到套餐电价或者激励价格的直接影响。用户面对分时电价时,会把高电价时段的负荷往低电价时段转移,但转移不是没有成本的,这里涉及一个转移舒适度损失的成本系数,实际上就是b参数的另一种表现形式。

用户侧约束也很关键,包括转移电量的总量守恒(谷时段多用的电要等于峰时段少用的电)、负荷上下限约束(每一时刻的用电量不能超过该时段的负荷极限)、以及削减负荷的最大比例约束(不可能把全部负荷都砍掉,只能砍掉用户愿意响应的一部分)。这些约束在代码里用矩阵表达的时候,最容易出错的地方是时段耦合——转移电量往往不是独立变化的,前一时刻的转移会影响到下一时刻的上下限,我在初版代码里就因为忘了加耦合约束,导致计算结果呈现出明显的“时段锯齿”,一眼就能看出不物理。

注意:用户转移负荷通常有一个“转移总量上限”,我建议在模型里不加这一条,等仿真结果出来后,如果某个时段转移量占总负荷比例超过15%,再回头去翻一下用户参数设置。真实场景中,用户不可能毫无上限地调整用电习惯,强加转移上限会让模型偏离实际行为。

2.3 多级市场购电策略建模

售电商的购电渠道不是单一的,既要签中长期合约锁定基础电量,又要根据现货市场的价格变化灵活调整短期购电量。中长期合约的优势是价格稳定,但劣势是分解到每个小时的合约量可能和实际负荷有偏差——这就是偏差电量考核的由来。现货市场的优势是灵活,但价格波动大,需要面临实时价格风险。

这套模型里,多级市场的购电决策是这样实现的:首先,中长期合约的电价是固定的,作为输入参数给出;然后售电商根据预测的现货价格曲线,决定每一小时从现货市场购买多少电来补足总需求与合约电量的差额。这一步本质上是一个“基于预测价格的追单问题”。这里有一个常见误区:很多同学以为现货购电量就等于总负荷减去中长期合约量就行了,其实完全不对。因为还要考虑偏差考核费用——如果实际用电量和合约分解量之间的偏差超过某个阈值,售电商要为此支付额外的罚金。换句话说,购电策略不仅要尽可能用便宜的现货电,还要控制偏差在一档合理的范围里。

具体到Matlab代码,购电策略这一层的优化目标通常是:

minimize sum(price_spot .* Q_spot) + penalty * sum(max(0, abs(Q_contract - Q_load - Q_spot) - deviation_limit))

这个问题是带max函数的,不光滑,直接用yalmip建模没有问题,但如果你是自己写梯度求解,就必须先把max函数做平滑处理,否则求导那一步会直接卡壳。我在复现过程中试过用log-sum-exp作平滑替代,效果还算稳定,但对参数比较敏感,初值给得不好会收敛到较差的局部解,后来还是换回yalmip自带的建模方式,交给求解器内部处理这种不光滑约束反而更可靠。

3. 实操过程与核心环节实现

3.1 代码整体结构:主从博弈怎么在Matlab里落地

我搭的工程目录长这样,供参考:

src/ main_stackelberg.m upper_seller_model.m lower_user_model.m kkt_reformulation.m data_generation.m plot_results.m run_sensitivity_analysis.m

主函数main_stackelberg.m是整个程序的总控,它负责初始化参数、调用数据生成模块、设置求解选项、迭代上下层、输出结果和绘制曲线。核心迭代逻辑放在一个while循环里,循环体的大致风格是:

% 主从博弈求解主循环 for iter = 1:max_iter % 上层求解:固定用户响应,优化套餐 [price_result, contract_result] = upper_seller_model(user_response, spot_price, contract_price); % 下层求解:固定套餐,优化用户响应 user_response_new = lower_user_model(price_result, incentive_result); % 收敛判据 if norm(user_response_new - user_response, inf) < tol break; end user_response = user_response_new; end

有点意思的地方在于,这里我给上下层都用了YALMIP建模,而不是手写KKT条件做单层替换。原因很简单:手写KKT条件推导过程很容易出错,而且对约束条件的凸性要求很苛刻,一旦下层模型要加一个约束,整个推导就要重来。而分离建模各层、然后通过外部循环迭代,虽然求解次数多了,但是每一层模型都足够小,求解速度快,代码模块也清晰,加约束或者改参数都很方便。当然这个方案的前提是对求解精度和速度要求不那么极端,如果是要跑24小时×多场景的批量仿真,还是推荐KKT单层化,一步求到均衡解。

3.2 关键参数设置与初始化技巧

我实践下来,参数初始化这件事做得对不对,直接决定收敛快慢甚至能不能收敛。以下是我的推荐配置,作为一份参考配置投喂给后续想复现的同学:

参数名推荐初始值取值范围说明
用户价格灵敏度 a2010-50单位:元/MWh;越大越不敏感
用户效用参数 b0.050.01-0.1单位:元/MWh^2;越大衰减越快
中长期合约价400300-600单位:元/MWh;通常低于现货均价
现货价格均值450250-800单位:元/MWh;按24点曲线给定
偏差考核阈值5%2%-10%相对合约分解量的偏差比例
可转移负荷比例20%10%-40%占基线负荷的最大比例
可削减负荷比例15%5%-30%单位:%

比较关键的是价格灵敏度参数a和效用参数b的匹配关系。如果a设得太小,用户对价格几乎无感,套餐调峰策略就完全不起作用,上层再怎么优化都没收益空间;如果a设得太大,用户对价格极度敏感,会导致模型在迭代过程中出现负荷剧烈振荡,很难收敛。我跑了好几组参数对比之后发现,a在20附近、b在0.05附近,上下层博弈的力度刚好在一个“平衡点”上——用户既有响应弹性,又不至于让系统抖得太厉害。

另外,现货价格的曲线形状也值得玩味。我最初用的是纯随机生成的价格曲线,跑出来的购电策略几乎没有规律,后来把价格曲线改成“双峰谷”形态——也就是上午一个峰、傍晚一个峰、夜间一个谷、午后一个谷——购电策略的时变结构立刻变得清晰可读,也更容易说明策略逻辑。

3.3 上下层迭代求解的收敛判据与加速技巧

主从博弈层层迭代收敛判据的选择,直接关系到结果质量和计算成本。我一开始用相邻迭代解的绝对差作为判据,即前后两轮的用户用电量inf-norm小于1e-4就停。这个判据简单直观,但有个问题——它只衡量了“数值上的变化程度”,没有衡量“目标函数上的变化程度”。有时候解向量已经基本不动了,收益目标还在缓慢漂移,这时候提前停止会得到次优解。

所以我把判据改成双指标:既要用户响应向量的无穷范数小于某个阈值,又要上层目标函数的变化幅度小于相同数量级。两个条件同时满足才算收敛。这个改进之后,结果稳定性提升明显,代价是多迭代了大约10-15轮。

更重要的加速技巧是给上层施加热启动(warm start)。也就是说,在进入下一轮迭代之前,把上一轮算出的最优解作为这一轮求解器初值。YALMIP里可以给变量设置初始值,配合求解器的热启动选项,迭代轮次能明显压缩。实测下来,不加温启动大概需要20轮左右收敛,加了之后基本8-12轮就稳了。这个技巧在批量做敏感性分析的时候尤其值钱——每次只改一个参数,其他参数不变,如果每次都从零开始求解,30组参数跑下来要几十分钟;从上一组解出发热启动,总时间可以压缩到原来的一半不到。

4. 仿真结果分析与边界条件测试

4.1 套餐设计结果怎么看:峰谷价差与用户响应

跑完主体仿真之后,最直接的输出就是峰谷时段售电价格和激励补贴数值。以我这次复现的参数配置为例,优化结果给出的峰时电价大约比谷时电价高出150元/MWh左右,这个峰谷价差刚好落在用户响应的敏感区。如果价差太小,用户没有动机转移负荷;价差太大,用户转移过猛,会导致低谷时段负荷堆积,反而拉高低谷时段购电成本。

从用户响应曲线可以明显看到,原始负荷在傍晚18点左右的尖峰被削掉了一部分,转移到了凌晨2-5点的谷段。这个削峰填谷的效果用峰谷差率来衡量,从优化前的约40%下降到了优化后的约25%,效果相当可观。而激励型套餐的用户,主要在尖峰时段削减了约10%-15%的负荷,响应力度符合预期。

这部分结果如果要做成汇报图表,我建议画三张图:一张是优化前后的24点负荷曲线对比,一张是现货价格与购电量的对应关系,还有一张是套餐价格与用户响应的灵敏度曲线。三张图放在一起,基本就能把“售电商设计了什么样的套餐、用户如何响应、购电策略如何配合”这条逻辑线讲完。

4.2 多级市场购电策略的联动逻辑

购电策略部分的结果,我观察到一个很有意思的规律:中长期合约电量分解曲线,优化后不再是简单地按比例分摊到24小时,而是呈现出“跟着现货价格走”的趋势——现货价格高的时段,合约电量占比反而高(因为用便宜的合约电去对冲贵的现货电);现货价格低的时段,合约电量占比低(因为直接去现货市场上买更划算)。这个联动逻辑,恰恰是这套“多级市场购电策略”的精髓——合约电量和现货购电量不是独立的,它们共同服务于“总量满足负荷需求、总成本最小”的目标。

偏差考核费用在总成本里的占比,也是值得关注的一项。我跑的初始参数下,偏差费用接近总购电成本的5%。后来我把中长期合约的分解方式从不考虑价格因素的等比例分解,改成了与现货价格负相关的优化分解,偏差费用占比降到了2%左右,效果非常明显。这个改进在模型里其实只有几行约束的变动,但对结果的影响是决定性的,也说明多级市场联动的意义不仅在于“买到便宜电”,更在于“买得准”。

4.3 边界条件与敏感性测试

复现完主场景之后,我又做了两组敏感性测试。第一组是令用户的价格灵敏度参数a从10变化到40,看均衡结果如何演变。趋势很清晰:a越小(用户不敏感),售电商能获得的利润空间越大,但削峰填谷的效果越差;a越大(用户敏感),用户响应明显,但售电商的定价反而趋于保守,利润增速放缓。这其实揭示了一个底层的平衡逻辑——售电商并不能无限压榨用户的响应潜力,用户越聪明,售电商的套餐设计空间反而越小。

第二组测试是调整偏差考核阈值从2%到10%,看多级市场购电策略的响应。阈值越小,中长期合约电量和现货购电量的搭配就越精细,优化问题的可行域越窄,计算耗时明显增加;阈值越大,购电策略越粗放,现货市场的参与程度降低,总成本小幅上升。这个测试的实践意义在于:如果实际场景里偏差考核很严格,那就值得花大力气去优化合约分解;如果考核很宽松,购电策略的重心就应该转向现货市场的博弈。

5. 常见问题与排查技巧实录

5.1 求解器选型与YALMIP建模避坑

这是一个无数同学踩过的坑:YALMIP的默认求解器,在处理二次约束和含有max函数的目标函数时,经常报“Numerical issues”或者干脆返回无解。我这次复现用的求解器是Cplex。如果你手头没有Cplex,用Gurobi也行,但需要注意Gurobi对二次目标的支持比Cplex激进,部分二次约束可能被线性化或忽略,结果可能与Cplex不完全一致,建议以Cplex作为基准。

另外一个容易忽略的问题是变量的定界。求解器对无界变量非常敏感,特别是在双层迭代初期,用户响应变量的初值偏离真实解太远时,目标函数里的二次项可能变成巨大的数值,直接把求解器的预求解阶段搞崩。稳妥的做法是给所有决策变量加上显式的上下界约束,哪怕你认为问题约束里已经隐含了边界,显式加上也不会错。我遇到过一次求解器返回“Infeasible problem”的情况,排查了两小时,最后发现是某个用户参数把负荷上限设成了负数,边界检查没拦住。

5.2 两层迭代不收敛的常见原因与对策

两层迭代发散,最典型的症状是用户响应序列在两个状态之间来回震荡——这轮峰时负荷特别小,下轮又特别大,循环往复。这种情况通常说明上层定价参数变化太快。对策有两个:一是加入松弛迭代——新的用户响应不直接替换旧的,而是取新旧解的加权平均,权重从0.5开始,随迭代轮次逐渐增大至0.9,这是一种很实用的阻尼策略;二是限制上层决策变量的单步变化步长,比如这一轮峰时电价相对上一轮的调整幅度不能超过20元/MWh。这两招我都实验过,阻尼策略对振荡问题的抑制效果更明显,基本两轮内就能把振荡压住。

如果迭代从一开始就发散的,那问题基本出在初始化。建议把第一轮的用户响应设成基线负荷,而不是随意置零。有一次我发现,把用户响应初始化为0会让上层优化出来的套餐价格直接飙到天价——因为用户不买电,售电商收益为负,上层为了提升收益只能抬价,然后下层在极高价格下几乎不响应,就形成了一个“价格战”的死循环。

5.3 问题速查表

症状可能原因解决方案
求解器报无解变量无显式边界给所有决策变量加上下界约束
收益结果为负套餐定价过低检查套餐价格下限,建议设为当前市场价的80%以上
负荷曲线锯齿状转移电量时段耦合约束缺失补上转移前后时段的电量限额约束
迭代震荡不收敛上层参数步长过大引入阻尼迭代或者单步变化限幅
偏差费用极高合约分解未匹配价格把合约分解改为与现货价格联动的优化变量
用户响应太小价格灵敏度参数a过小或b过大调整a和b至用户响应比例在合理区间

5.4 调试顺序与数据生成的心得

搞这个模型的调试过程,我最想提醒的一条经验是:先从最简单的场景开始验证,不要一上来就满负荷24节点跑。我的调试顺序是:先固定所有用户参数为相同值,把问题简化为一个“典型用户”模型,上下层单轮迭代就能收敛,用于验证数学推导和代码逻辑;然后放开用户的异质性,变成三类用户群体,验证多类用户的负荷聚合是否正确;最后才上完整的24小时曲线和多级市场购电模型。每走一步,都会画一张负荷曲线和收益曲线做核对,确认结果在物理上说得通,再继续下一步。这套顺序帮我省掉了至少十个小时的无效排错时间。

数据生成方面也有一点经验。不要用真正的随机函数生成现货电价,这样产出的价格曲线太“毛躁”,会让购电策略丧失结构性。我推荐基于一个基础形状——比如正弦波叠加高斯扰动——来生成价格曲线,保证曲线具有真实的峰谷形态和适度的波动幅度。这样不仅模型跑起来稳定,画图展示时也容易看出策略与价格之间的联动关系,汇报效果远比纯随机曲线好。

6. 扩展方向与改编思路

这套框架的适应面其实很宽。如果你想做更深的工作,至少有三个方向可以扩展:一是加入储能系统,让售电商不仅能买电卖电,还能在低谷时段充电、高峰时段放电,这时候主从博弈的下层会多一个“储能调度”的决策变量,问题的维度和复杂度都会上一个台阶;二是把用户的响应建模从单轮静态改成跨时段的动态响应,比如引入用户“学习效应”,用户在一段时间后会对价格产生适应,这会让模型的时序耦合更强;三是把上层售电商的目标从“利润最大化”扩展成“利润与用户满意度”的多目标权衡,这样更贴近真实零售商的运营诉求,但对求解算法提出了新的要求,原有的单层转化思路需要调整。

另外,如果你的研究方向偏向市场机制设计,还可以把模型扩展到“多个售电商同时竞争”的场景,这时候就不再是单个主从博弈,而是更复杂的“多领导者-多跟随者”博弈,需要用到均衡求解的思路,难度会显著增加。但底层的Matlab建模思路还是一致的——把每一层优化问题封装成独立函数,通过迭代交互逼近均衡,只要把这个框架搭好了,后续换成多主体、多市场结构都只是加模块的问题。

按照我自己的经验,这个方向最值得投入精力的其实不是某一篇论文的复现,而是把这套“主从博弈+双层规划+Matlab实现”的通用框架吃透。框架一旦扎实了,EI论文的复现基本就是沿着固定的路走一遍,该遇见的坑都是相似的,处理起来也就越来越顺手了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询