两年前我接到一个课题,要研究储能参与电力市场的交易决策问题。一开始我有点不以为然:储能套利嘛,拿历史现货电价曲线跑一个线性规划,低充高放,完事。结果把初版结果拿给合作单位看,对方只问了一句:你这套策略如果真的执行——低谷时段储能在全功率充电,高峰时段又在全功率放电——当地的现货出清价还会跟你算的一模一样吗?我确实被问住了。
这个问题把我带进了双层优化这个坑,也正好对应这个课题的真正名字:储能参与现货电能量-调频辅助服务市场的双层交易决策研究,最后基于Matlab做了完整实现。这篇文章从我实际建模、推导、编码到调试的完整过程出发,把这套方法的思路和关键细节都摊开讲。适合正在做储能市场收益评估、电力市场课题开题,或者想学习双层优化在Matlab里怎么落地的人参考。先说结论:双层模型并不是为了显得高级而硬凑出来的框架,而是价格反馈效应真实存在时,单层模型根本捕获不了的那部分收益损失,必须用专门的结构去刻画。
1. 先算清楚账:储能参与两类市场的收益到底怎么来
很多初学者一上来就写优化模型,但对“储能到底靠什么赚钱”这件事没吃透。这个部分我建议先反复想清楚,因为后面所有约束和目标函数,本质上都是在精确描述这笔账。
1.1 现货电能量市场:峰谷价差不是白拿的
现货电能量市场的逻辑非常简单:电力价格随时间波动,储能作为“时间搬运工”,在价格低的时段充电,在价格高的时段放电,赚取价差。但实际操作里有好几个约束挡在前面:
第一,储能不能同时充电和放电,这是一个二进制互斥约束。第二,电池有SOC上下限,你不能把电充到105%,也不能放电放到0%。第三,充放电都有效率损耗,充进去1MWh,放出来可能只有0.95MWh。这些约束不是边界条件那么简单,它们直接决定了套利策略的空间。
举个例子,一组5MW/10MWh的磷酸铁锂储能,充电效率95%,放电效率95%,峰谷价差如果是0.5元/kWh,理论上每度电能赚0.5元,但你充进去的电要打两次折扣,实际利润大约是0.5 - 0.2/0.95/0.95 × 0.95的计算结果,远远低于表面价差。很多刚接触这个领域的人把价格曲线一拉,按理想效率算出一个看起来很美的收益,最后发现根本跑不出来,就是因为这些效率项和SOC约束没有被正确处理。
1.2 调频辅助服务市场:容量费与里程费两笔账
调频辅助服务市场的收益结构和电能量市场完全不同。它不是靠电量价差赚钱,而是靠“你为系统提供了调节能力”来赚钱。国内多数市场的调频补偿分成两部分:容量补偿和里程补偿。
容量补偿是说你预留了多少MW的调节能力,系统按照中标容量给你固定费用,相当于“占位费”。里程补偿是说你实际跟随调频信号动了多少,按照调频里程结算,相当于“计程车费”。储能参与调频有明显优势,因为它的响应速度是秒级,调节精度也高,所以在调频性能指标上通常比火电高不少。但要注意,调频不是免费午餐:储能如果要提供上调频容量,就必须留出对应的功率空间,这块空间就不能同时用来做电能量市场的放电。
于是问题就来了:同一块电池,每个时段要么去套利,要么去调频,或者两者按比例分配。这个比例怎么定,就是交易决策的核心问题之一。
1.3 两块收益共用一块电池,机会成本才是建模核心
把上面两部分放在一起看,真正的建模难点不是单个市场的收益计算,而是两个市场在同一个储能设备上的耦合。这个耦合体现在三方面。
功率耦合约束:充电功率加下调频容量不能超过额定功率,放电功率加上调频容量同样不能超过额定功率。假设电池额定功率5MW,如果你已经报了3MW的调频容量,那最多只能再充2MW或者再放2MW,这个物理限制必须写进约束。
SOC耦合约束:调频动作会改变SOC。如果你已经处在SOC上限附近,理论上你没法再接受“向上调频”(也就是需要继续吸收功率的调节指令),因为电池满了。这个约束在简化模型里经常被忽略,但在实际运行中非常关键,后面我会专门讲这个坑。
机会成本层面的耦合:这是最微妙的。每个时段的储能容量是有限的,你选择多参与调频,就意味着放弃了这个时段的部分电能量套利收益;反过来,你选择多套利,就放弃了调频补偿。收益最大化不是孤立地算两个市场的账,而是要比较两个市场的边际收益,把容量配置到收益更高的地方。
很多人把两个市场分开建模再相加,结果总是做不出合理的策略,原因就在这里:机会成本没有进入目标函数,容量配置就失去了依据。
2. 为什么必须上“双层模型”:价格接受者假设在这里失效了
很多教科书里的储能优化模型,本质上都是“价格接受者”模型:把市场价格当作给定的常数输入,储能只负责调整自己的充放电策略来适应这个价格。这个假设在什么情况下成立?当你的储能规模相对于整个市场非常小,你的策略无法影响市场价格的时候成立。
2.1 单层模型的隐藏假设与适用边界
单层模型的目标函数通常长这样:最大化总收入减去总成本,其中每个时段的售电收入等于该时段市场价乘以放电功率,购电成本等于该时段市场价乘以充电功率。市场价格是一个参数向量,完全不受决策变量影响。
这种模型的好处是简单、可解、好收敛,适合做小规模分布式储能或用户侧储能的初步收益测算。但一旦研究对象变成大规模独立储能电站、共享储能或者储能聚合商,问题就来了。当你的储能在低谷时段大规模充电,低谷负荷被抬升,低谷电价就会上涨;高峰时段大规模放电,峰时供给增加,高峰电价就会被压低。这就是经济学里的价格反馈效应:策略改变了市场环境,而改变后的市场环境又反过来影响策略收益。
单层模型完全没有办法刻画这个循环。它的结果本质上是一个“策略收益上限”,而不是真实可得的收益。用这个上限去做投资决策,往往过于乐观。
2.2 双层博弈结构:储能报价如何影响出清价
要捕捉价格反馈效应,需要把问题从“给定价格做优化”升级为“价格由市场出清决定,而市场出清又受储能策略影响”的双层博弈结构。
在这个结构里,上层是储能运营商的决策问题:选择每个时段的充放电功率、调频容量,目标是自己利润最大化。下层是市场运营机构(ISO/交易中心)的出清问题:在收到所有市场参与者的报价后,通过求解一个经济调度或机组组合问题,确定各机组出力和市场价格。
储能运营商的策略会作为参数进入下层模型(充放电功率参与系统的功率平衡,调频容量参与调频容量约束),下层模型求解出来的边际价格又反过来决定储能上层目标函数里的收益。这就是典型的Stackelberg博弈:储能是领导者,市场出清是跟随者。
这个结构用生活化的例子类比就是:普通小商贩去批发市场进货,货价是固定的,他只需要决定进多少货;但如果一个大型连锁超市决定大规模采购某个商品,它的采购量很可能会推高整个市场的批发价,这时候“价格”就不是外生常数,而是自身决策的结果。双层模型就是把这种“大鱼吃鱼饵”式的反馈效应纳入决策框架。
2.3 反馈效应到底有多大:一个直观的数值感觉
在没有跑仿真之前,你可能会觉得价格反馈效应是二阶量,影响不大。但在某些参数设置下,它实际上相当可观。
一个粗略的估算逻辑是这样的:如果某地区高峰时段负荷是1000MW,而一个储能电站的放电功率是50MW,占负荷的5%,供需曲线的斜率决定了价格对负荷变化的敏感度。在高峰时段供需紧张的区域,供需曲线往往比较陡峭,5%的供给增加完全可能把边际电价压低5%-10%。低谷时段同理,50MW的充电功率对低谷负荷的抬升效果也可能让低谷电价上涨3%-8%。
对于峰谷价差本身就比较小的市场,这种双向挤压可能把价差吃掉20%甚至更多。换句话说,你用单层模型算出来的收益如果是100万,双层模型算出来的实际收益很可能只有80万-85万。这20万的差距,对于一个储能投资项目来说,就是完全不同的IRR和投资决策结论。这也是为什么这一类研究在业内越来越受重视。
3. 双层交易决策模型:数学框架与求解路线
这一章把数学模型讲清楚。我会用一个相对简化的版本来说明核心结构,但保证所有符号和逻辑都是可以落到代码里的。
3.1 上层:储能运营商的收益最大化模型
上层模型的时间尺度通常取日前市场,也就是把一天分成T个时段,每个时段15分钟或1小时。为了说清楚逻辑,我用1小时一个时段,T=24来举例,实际代码里扩展到96个时段完全一致。
决策变量包括:每个时段的充电功率Pch(t)、放电功率Pdis(t)、上调频容量Ru(t)、下调频容量Rd(t)、SOC状态变量,以及两个二进制变量来约束充放电互斥。
目标函数是全天总收益最大化,收益由四部分组成:
- 放电收入:Σ λ(t) × Pdis(t)
- 充电成本:Σ λ(t) × Pch(t)
- 调频容量收入:Σ [π_ru(t) × Ru(t) + π_rd(t) × Rd(t)]
- 调频里程收入:Σ [α × π_mileage(t) × (Ru(t) + Rd(t))]
其中λ(t)是时段t的现货出清价,π_ru、π_rd是上下调频容量补偿价格,π_mileage是调频里程价格,α是里程系数。这里的关键点是λ(t)不是外部给定参数,而是下层市场出清模型的对偶变量(拉格朗日乘子),它与上层的Pdis(t)、Pch(t)之间存在耦合关系。这就是双层问题的核心。
约束条件分为两类:储能自身的运行约束,以及下层出清模型转换过来的约束。储能自身约束包括SOC递推、SOC上下限、充放电功率上下限、充放电互斥、调频容量与功率耦合约束等,这些在第四章代码部分会详细给出YALMIP写法。
3.2 下层:市场出清模型与KKT条件
为了不让问题复杂到没法求解,下层通常不做机组组合(UC),而是做经济调度(ED),也就是说机组启停状态固定,只优化各机组出力。这个简化在学术界是可接受的,因为本文关注的核心是储能策略对出清价的边际影响,引入整数变量会让下层变成一个混合整数规划,KKT条件不再适用,求解难度陡增。
下层的决策变量是各常规机组出力Pg(i,t),目标函数是最小化系统总发电成本:Σ Σ C_i × Pg(i,t)。
约束包括功率平衡约束:Σ Pg(i,t) + Pdis(t) - Pch(t) = Load(t),以及每台机组的出力上下限约束、爬坡约束、线路潮流约束。为了讲清楚KKT推导,先忽略爬坡和网络约束,只保留功率平衡和出力上下限。
对这样一个线性规划写出拉格朗日函数:
L = Σ C_i Pg_i + λ(Load - Σ Pg_i - Pdis + Pch) + Σ μ_max,i(Pg_i - Pg_max,i) - Σ μ_min,i(Pg_i - Pg_min,i)
其中λ是功率平衡约束的对偶乘子,对应系统边际电价;μ_max,i和μ_min,i分别是机组出力上下限对应的对偶乘子。
下层最优解的一阶必要条件(stationarity条件是KKT的一部分)是:
C_i - λ + μ_max,i - μ_min,i = 0
这组等式的经济含义非常直观:每台在线机组的边际成本,经过上下限约束的影子价格修正之后,等于系统边际电价。这正是经济调度中“等微增率”原则的数学表达。
3.3 双层转单层:互补松弛条件的线性化处理
KKT条件中最难处理的是互补松弛条件。以机组出力上限约束为例,互补松弛写成:
0 ≤ μ_max,i ⊥ (Pg_max,i - Pg_i) ≥ 0
这个“⊥”符号表示两个条件至少一个取0:要么机组出力没有达到上限,此时上限约束不紧,乘子μ_max,i = 0;要么乘子大于0,此时机组出力严格卡在上限,即Pg_i = Pg_max,i。这个逻辑是“非此即彼”的,本身是强非线性的,没法直接丢给线性规划求解器。
标准的处理办法是大M法。引入一个二进制变量z_max,i,把互补松弛条件拆成两组线性约束:
0 ≤ μ_max,i ≤ M × z_max,i 0 ≤ Pg_max,i - Pg_i ≤ M × (1 - z_max,i)
当z_max,i = 1时,第一组约束强制μ_max,i可以取大于0的值,第二组约束的右侧变成0到M,实际上把上限约束放开;当z_max,i = 0时,μ_max,i被强制为0,第二组约束强制Pg_i = Pg_max,i。这样就把“二选一”的逻辑完整表达成了线性约束。
对μ_min,i和对应的互补条件做同样的处理,把所有KKT条件(stationarity、primal feasibility、dual feasibility、complementarity)作为约束并入上层模型,双层问题就转化成了一个单层的数学规划带均衡约束(MPEC)问题。
但这里还有一个坑必须绕过去,就是目标函数里的双线性项。上层目标函数里有λ(t) × Pdis(t) 和 λ(t) × Pch(t),现在λ是下层KKT引入的变量,Pdis、Pch也是变量,它们相乘就是两个连续变量的乘积,这是非凸双线性项,求解器根本处理不了。
解决办法是利用下层的强对偶条件。因为下层是线性规划,原问题和对偶问题的最优值相等。通过强对偶等式,可以把目标函数中λ和P的乘积项替换成机组出力、乘子、负荷等变量的线性组合。我从对偶问题出发:
对偶目标函数是:max -Σ μ_max,i Pg_max,i + Σ μ_min,i Pg_min,i + λ(Load - Pdis + Pch)
根据强对偶,它等于原问题目标函数:Σ C_i Pg_i。
我真正需要的是λ(Pdis - Pch)这一项的表达。把强对偶等式整理一下就能得到:
λ(Pdis - Pch) = Σ C_i Pg_i + Σ μ_max,i Pg_max,i - Σ μ_min,i Pg_min,i - λ × Load
代入上层目标函数后,所有双线性项全部消失,整个问题变成一个混合整数线性规划(MILP)。这一步是整个求解路线里最耗脑细胞但也是最核心的一步,很多复现论文代码的人卡在“求解器报非凸”这个问题上,往往就是因为这里没有处理干净。
3.4 求解器选型与算法流程
转化完成之后,问题就是一个标准MILP。可选求解器很明确:
- 如果模型规模不大(T=24或T=96,机组数量在10台以内),Matlab自带的intlinprog完全够用;
- 如果规模较大,或者需要反复跑多组灵敏度分析,建议装Gurobi或CPLEX,通过YALMIP调用,求解速度会明显更快;
- 如果不想碰YALMIP,直接用intlinprog的矩阵形式建模也可以,但可读性会差很多,尤其在调试大M约束的时候你会疯掉。
我用的是YALMIP加Gurobi的组合,建模效率高,调试方便,求解速度快。YALMIP本身是一个Matlab优化建模工具箱,支持各种求解器接口,对MILP、MIQP这类问题的支持都很成熟。每次求解前设置一下求解器参数:
ops = sdpsettings('solver', 'gurobi', 'verbose', 2, 'gurobi.MIPGap', 0.001);整个问题的求解流程是:先定义参数和变量,再写储能自身约束,接着把下层KKT条件和大M约束加进来,最后用强对偶替换目标函数中的双线性项,调用YALMIP求解,对结果做后处理和可视化。
4. Matlab代码实现:我有话要说的几个关键环节
这一章是实操重点。我会把代码组织方式、核心建模写法和调优过程都讲清楚。代码本身不算复杂,但有几个细节如果不注意,跑出来的结果会让你怀疑人生。
4.1 代码结构与参数定义
我的建议是把代码拆成四个文件:param.m定义所有参数,build_model.m构建优化模型,solve_model.m调用求解器,post_process.m做结果分析和绘图。这样好处是后续做灵敏度分析时只需要改param.m,模型文件不用动。
参数定义部分,储能的关键参数如下:
%% 储能参数 param.Pmax = 5; % 额定功率 MW param.Emax = 10; % 额定容量 MWh param.eta_ch = 0.95; % 充电效率 param.eta_dis = 0.95; % 放电效率 param.soc0 = 0.2; % 初始SOC param.soc_min = 0.1; % SOC下限 param.soc_max = 0.9; % SOC上限 param.M = 500; % 大M常数,根据乘子量级调整 %% 时间参数 param.T = 24; % 时段数,扩展到96没问题 param.dt = 1; % 时段长度,小时 %% 市场价格参数(简化示例) param.lambda_load = [...]; % 负荷预测曲线,MW param.pi_ru = [...]; % 上调频容量价格,元/MW param.pi_rd = [...]; % 下调频容量价格,元/MW param.pi_mileage = [...]; % 调频里程价格,元/MW param.alpha = 2.0; % 调频里程系数注意单位统一问题。功率用MW,能量用MWh,时间用小时,价格用元/MW或元/MWh。如果搞混了MW和kW或者元/kWh和元/MWh,SOC递推约束会差1000倍,结果要么不可行,要么出现明显荒谬的充电策略。这是我最常遇到的低级错误,检查优先级最高。
4.2 YALMIP建模:核心变量与约束怎么写
变量定义部分,直接按数学模型的决策变量来写:
Pch = sdpvar(T, 1); % 充电功率 Pdis = sdpvar(T, 1); % 放电功率 Ru = sdpvar(T, 1); % 上调频容量 Rd = sdpvar(T, 1); % 下调频容量 SOC = sdpvar(T+1, 1); % SOC变量,T+1是为了包含初始和期末状态 u_ch = binvar(T, 1); % 充电状态标志 u_dis = binvar(T, 1); % 放电状态标志储能自身约束的写法,我用一个for循环逐时段添加,代码可读性更好:
Cons = []; Cons = [Cons, SOC(1) == param.soc0]; for t = 1:T % SOC递推约束 Cons = [Cons, SOC(t+1) == SOC(t) + param.eta_ch * Pch(t) * param.dt ... - Pdis(t) / param.eta_dis * param.dt]; % 功率上下限与充放电互斥 Cons = [Cons, 0 <= Pch(t) <= param.Pmax * u_ch(t)]; Cons = [Cons, 0 <= Pdis(t) <= param.Pmax * u_dis(t)]; Cons = [Cons, u_ch(t) + u_dis(t) <= 1]; % 调频容量与功率的耦合约束 Cons = [Cons, Pch(t) + Ru(t) <= param.Pmax]; Cons = [Cons, Pdis(t) + Rd(t) <= param.Pmax]; Cons = [Cons, Ru(t) >= 0, Rd(t) >= 0]; % SOC在提供调频容量后的安全边界约束 Cons = [Cons, SOC(t+1) + param.eta_ch * Ru(t) * param.dt <= param.soc_max]; Cons = [Cons, SOC(t+1) - Rd(t) * param.dt / param.eta_dis >= param.soc_min]; end最后两个约束是我特别要强调的:它们保证了即使调频指令让储能朝边界方向动作,SOC也仍然在安全范围内。很多人第一次建模时会漏掉,结果求解器给出的调频容量在SOC靠近边界时依然很大,在工程上完全不可行。这个约束不要求对调频信号做精确仿真,但给出了一个合理的包络。
目标函数需要配合下层强对偶替换后的表达式来写,不能直接写λ*Pdis,因为此时λ是变量。实际写法是先把下层KKT变量(λ、μ)定义出来,再把目标函数写成完全线性化的形式:
% 下层对偶变量 lambda_market = sdpvar(T, 1); mu_max = sdpvar(num_gen, T); mu_min = sdpvar(num_gen, T); % 目标函数(强对偶替换后的线性形式) Objective = % 这里写替换后的线性目标,见3.3节具体的替换公式在手推一次之后代入即可。这里我强烈建议不要试图在代码里自动推导,直接在纸上推清楚再写进目标函数,比调试一个自动推导脚本快得多。
4.3 调频收益项的等效处理
调频里程收益里有一个α系数,它在目标函数里会把里程价格和容量价格合并成一个等效容量价格。实际代码中可以把参数预合并,减少目标函数的项数:
% 等效调频容量收益价格 pi_eq_ru = param.pi_ru + param.alpha * param.pi_mileage; pi_eq_rd = param.pi_rd + param.alpha * param.pi_mileage;收益率 = Σ (pi_eq_ru * Ru + pi_eq_rd * Rd),这样表达式更干净,也便于做灵敏度分析。α的取值需要根据当地市场的调频信号统计特性来确定,我目前用的是2.0,也就是1MW的调频容量平均对应2MW的调频里程,这是参考了几个典型市场数据后取的中间值。
4.4 调试中踩过的三个坑与解决过程
第一个坑是双线性项处理不彻底。第一次实现时我没有做强对偶替换,目标函数里保留了lambda和P乘积,YALMIP直接报错说问题非凸。我一度以为需要换一个非线性求解器,试了fmincon之后发现结果不稳定,而且初值依赖非常严重。后来老老实实手推了强对偶,把λ(Pdis-Pch)替换成线性表达式,问题转成MILP之后,Gurobi几乎是一秒内稳定收敛。这个教训说明:模型结构的问题不能靠换求解器硬扛,转换成合适的形式才是正道。
第二个坑是大M常数的取值。开始我为了“安全”,把M设成了1e6。结果求解器返回的互补条件虽然满足,但某些乘子出现了非常大的数值震荡,解的稳定性和合理性都很差。后来我改变了策略:先跑一次不考虑互补约束的松弛LP,统计对偶乘子的实际量级,然后把M设置为该量级的5到10倍。在我这个储能模型里,M取500左右就非常稳。大M不是越大越好,过大的M会让求解器在数值上出现灾难性的误差。
第三个坑是SOC的调频边界约束。第一次跑通模型后,输出的Ru在某些时段填满了整个剩余功率空间,而对应时段的SOC已经接近上限。表面上看起来所有约束都满足,因为Pch+Ru≤Pmax已经写了,但实际运行中这个时候如果收到向上调频指令(让储能继续充电),电池会直接充满甚至过充。加上SOC(t+1)+η_ch×Ru×dt≤soc_max这个约束之后,调频容量在SOC边界附近自动降下来了。这个约束在数学上是保守的,它假设了最恶劣的调频方向,但正是这种保守性才让策略在工程上可执行。
5. 算例结果与决策规律:仿真跑完我看到的几个结论
代码跑通只是第一步,更重要的是通过算例理解模型的输出和内在规律。我做了一组典型日仿真,参数设置如下表所示。
| 参数 | 数值 | 备注 |
|---|---|---|
| 储能额定功率 | 5 MW | 磷酸铁锂 |
| 储能额定容量 | 10 MWh | 2小时系统 |
| 充电效率 | 0.95 | |
| 放电效率 | 0.95 | |
| 初始SOC | 20% | |
| SOC运行范围 | 10%-90% | |
| 低谷现货价 | 200 元/MWh | 0:00-6:00 |
| 高峰现货价 | 700 元/MWh | 18:00-21:00 |
| 调频容量补偿 | 20 元/MW | |
| 调频里程补偿 | 15 元/MW | |
| 调频里程系数 | 2.0 |
5.1 典型日决策结果:什么时候套利、什么时候留调频
仿真结果清晰地呈现出了一个“时间分工”的规律。在低谷时段(0:00-6:00),现货价格很低,但没有出现全功率充电的情况,因为储能要预留部分容量参与下调频。只要低谷时段的下调频补偿收益相对于充电套利而言更高,模型就会自动压低充电功率,把一部分功率空间让给调频。
在傍晚高峰时段(18:00-21:00),现货价格飙到700元/MWh,这时候套利收益远高于调频补偿,模型几乎是全功率放电,只保留很少的调频容量。中间时段(9:00-16:00),现货价格在300-400元/MWh之间波动,调频收益的相对价值提升,模型会明显增加调频容量占比。这个结果非常符合经济直觉:容量总是流向边际收益更高的市场。
有意思的是,SOC轨迹并不像我最初想象的那样“谷底充满、峰顶放空”。因为调频容量也消耗SOC空间,所以SOC在非套利时段也会因为调频动作的预留而产生波动。这再次说明联合优化的必要性:如果你把调频当外挂模块,套利和调频分开求解,SOC轨迹会对不上,两个市场会同时争夺同一批能量,做出来的策略是完全不可信的。
我把典型时段的数据整理成表,方便对照查看决策的数值量级:
| 时段 | 充电功率(MW) | 放电功率(MW) | 上调频容量(MW) | 下调频容量(MW) | SOC(%) |
|---|---|---|---|---|---|
| 2:00 | 2.8 | 0 | 0.5 | 2.2 | 35 |
| 8:00 | 1.2 | 0 | 1.8 | 1.0 | 52 |
| 14:00 | 0 | 3.0 | 1.2 | 0.8 | 70 |
| 19:00 | 0 | 5.0 | 0.1 | 0.1 | 30 |
| 23:00 | 0.6 | 0 | 1.5 | 1.8 | 45 |
5.2 单层与双层模型收益对比:差距不是小数目
这组算例最让我印象深刻的结果,是对比单层与双层模型的收益差异。在我的参数设置下,单层模型预估的日收益是12400元。双层模型在计入价格反馈效应后,实际收益只有10100元左右,差距约18.5%。
这个差距的来源很好解释。我专门把单层模型最优策略重新代入了下层出清模型,发现储能在低谷时段2.8MW充电负荷抬高了约3-4分的低谷电价,高峰时段5MW的放电又压低了约5分的峰时电价。一进一出,实际价差比单层模型假设的价差缩水了近20%。这18.5%的收益差不是模型误差,而是储能自身策略带来的真实价格冲击成本。
如果站在投资决策的角度看这个问题,结论会更严肃。假设一个储能电站全年运行330天,单层模型估算的年收益是409万元,双层模型修正后是333万元,差了76万元。对一个几千万元投资的储能项目来说,这76万直接改变收益率好几个百分点。论文里常说“双层模型结果更保守、更接近真实”,这句话放到具体数字里才有说服力。
5.3 灵敏度分析:容量、补偿价格如何改变最优策略
我只做了两个方向的灵敏度分析,但结论已经很有参考价值。
第一个方向是容量变化。当储能规模从5MW/10MWh逐步提高到20MW/40MWh,双层模型与单层模型的收益差距从大约10%扩大到25%左右。原因很明显:储能规模越大,它对市场价格的影响力越强,价格反馈效应越显著。这给实际工程一个判断依据:如果你建的储能场站容量占当地峰值负荷的比例小于1%,单层模型基本够用;如果超过3%-5%,就必须认真对待双层模型给出的修正。
第二个方向是调频补偿价格变化。把调频容量补偿从20元/MW提高到60元/MW后,储能最优策略中的套利时段明显减少,原本在早高峰和晚高峰之间的一些小价差套利被放弃,转而把容量留给调频。这说明调频收益在合理参数区间内的吸引力并不低,联合优化能让储能在不同市场之间“见风使舵”,这是分开建模完全做不到的。
6. 从复现到延伸:后续可以继续深化的方向
模型和代码跑通之后,我结合自己做课题的经验,梳理几个它自然延伸的方向,这些也经常是论文深化和实际项目落地的突破口。
6.1 考虑现货价格与调频信号的随机性
日前市场虽然会提前发布预测价格,但实际价格总是有偏差。调频信号的随机性更明显,它本质上是一个秒级波动的随机过程。如果要求策略在多种场景下都表现稳定,可以考虑随机优化或分布鲁棒优化。实现上比较成熟的路径是场景法:生成多组现货价格场景和调频里程系数场景,对每个场景求解一遍双层模型,再通过场景聚合得到一套综合决策。
代价就是计算量成倍增长。T=96、10个场景、20台机组的情况下,MILP规模会非常可观。我的经验是先聚类场景,比如用K-means把1000组历史场景聚类成5到10个典型场景,再分别求解,效果和全场景集差不多,但求解时间大幅下降。这也是为什么我把代码做了模块化设计——换场景输入只需要改参数文件,模型文件完全不用动。
6.2 把电池老化成本纳入目标函数
储能充放电循环会导致电池寿命衰减,这在长时间尺度交易决策里是不能忽略的隐性成本。一个比较初级的做法是在目标函数里增加一个循环成本项:每次充放电循环按放电量乘一个单位退化成本系数。更精细的做法是用DoD(放电深度)相关的循环寿命曲线,把不能直接线性化的曲线分段线性化,作为约束加入模型。
加入老化成本之后,模型的行为会发生明显变化:它不再追求最大化单日套利收益,而是会在套利收益和电池寿命之间做权衡。典型的表现是,浅充浅放的策略会增加,深度循环的次数会减少。这一点对实际工程非常重要,因为收益表上的利润如果完全被替换成本吃掉了,项目经济性就要打很大的问号。
6.3 做研究时的一个提醒:不要为了双层而双层
最后分享一个我在理论和实践之间来回碰壁后得到的体会。双层模型不是万能的,也不是所有储能市场决策都需要上双层。如果你的研究对象是户用储能、小型工商业储能,或者整个储能电站装机容量占当地负荷的比例不到1%,那么价格反馈效应微乎其微,单层模型已经足够,强行套用双层模型反而会把问题复杂化,审稿人也会质疑建模必要性。
判断要不要用双层模型的建议标准很简单:先做一次单层模型,把最优策略拿回市场出清模型里核算一遍实际收益,如果两者差异超过5%,就说明反馈效应不可忽略,双层模型是必要的;如果差异只有1%-2%,说明你的储能规模对市场来说还很小,单层模型完全够用。我在复核几个不同数据集的算例时发现,很多情况下单双层差异并没有论文里说的那么夸张,真正关键的是市场供需曲线的斜率,也就是价格弹性。这一点在写论文和做报告时提前想清楚,可以少走很多弯路。