去年底我把这套储能联合调峰调频优化模型用MATLAB+CVX完整跑通后,拿到的联合调度收益比单纯只做调峰高了一大截。身边做储能投资的朋友经常问——储能进电力市场,调峰调频到底怎么联合优化,才能既保住基本盘又多赚一笔?今天把这套模型从头拆开,从收益机理、数学建模到代码实现,再到实际跑数时容易踩的坑,一次性说清楚。
这套模型的核心价值在于:它不是简单地把“调峰收益”和“调频收益”两个数字加在一起,而是通过一个统一优化框架,让储能设备在同一组功率约束、同一套SOC约束下同时参与两个市场。我实测跑下来,在典型峰谷价差和调频报价参数下,联合调度收益比纯调峰模式提升50%以上,而且容量规模越大,提升比例越明显——这就是标题里“超线性收益”的工程含义。适合正在做储能经济性测算、电力市场策略研究、或者需要写可复现代码的工程师和研究人员参考。
1. 联合调度到底比单一调峰强在哪:收益来源与“超线性”的机理
1.1 调峰赚的是价差,调频赚的是“动作钱”
先厘清两个市场的钱是怎么来的。
调峰市场的收益逻辑很直白:低谷电价便宜时充电,高峰电价贵时放电,赚取峰谷价差。这个收益的特点是单次价差有限,一天里能做的深度充放电次数也有限。比如一座1MW/2MWh的储能,就算每天做两次深度循环,受效率损耗和SOC边界限制,实际能拿到的调峰套利也就几百到一千出头。调峰收益本质上是在“吃能量的时间差价”,它的规模天花板摆在那里。
调频市场则完全不同。调频收益主要来自两笔钱:一笔是容量补偿——你为系统预留了多大的AGC调节能力,按容量和时间结算;一笔是里程收益——你的储能实际响应了多少MW·h的调节动作,按里程结算。这两笔钱和“储存了多少能量”关系不大,和“功率爬坡能力、响应速度、动作频度”关系很大。储能设备恰恰在响应速度上比传统火电、水电快一个数量级,所以调频收益常常比单纯做调峰更丰厚。
但问题是,很多项目在做经济性测算时,把这两个市场割裂开来算——上午做调峰,下午做调频,或者干脆只盯一个市场。这种做法最大的损失,就是没有意识到同一台储能可以在时间上复用功率容量:调峰只在几个关键时段深度充放,其余大把时间设备是闲着的,而这些空闲时段完全可以拿去跟踪AGC指令赚调频费用。
1.2 “超线性”不玄学:容量、时间与市场这三层复用
很多人看到“超线性收益”会觉得这是营销话术,其实从工程角度看,它至少意味着三层复用叠加的效果。
第一层是时间复用。调峰模式下一个1MW储能每天的有效动作时间可能不到6小时,联合调度后调频任务会把剩余时间填满。单位容量一天内产生的“功率·小时”乘积大幅提升,收益自然增加。
第二层是市场复用。调峰收益按“电量”结算,调频收益按“容量+动作量”结算,两套结算体系作用在同一台设备上。你只需要增加一套优化算法和AGC控制逻辑,不需要增加硬件投资,就多拿了一份收入。固定成本被两笔收益分摊,单位投资的回报率出现明显的非线性跳升。
第三层是容量边际递增。同样一套模型,储能容量从1MWh扩到2MWh、4MWh时,调峰收益基本线性增长,但调频容量补偿和里程收益会随着可申报的调节容量增加而获得更高的市场结算权重,同时功率限幅约束下可优化的空间也更大。我在算例里把容量从1MWh逐步调到4MWh时,联合调度相对单独调峰的提升比例从51%一路升到68%。收益曲线呈边际递增形状,这就是“超线性”的实际曲线。
1.3 组合后为什么不等于简单相加
这里要特别强调一个反直觉的点:联合调度收益并不等于“调峰收益+调频收益”简单相加。
原因是功率约束存在耦合。储能的PCS额定功率是固定的,比如1MW,你在某个时段用了0.6MW去跟踪调频指令,那这个时段最多只能剩0.4MW给调峰充放电使用。如果你的模型把两个市场完全独立优化,算出来的收益一定过于乐观,实际执行时要么功率越限,要么调频容量申报了但根本跟踪不上。
联合优化模型要做的,正是把这层耦合约束显式写进去,让模型自己决定每个时段“调峰分配多少功率余量、调频预留多少调节容量”,以及在两者之间如何取舍。当某个时段调频里程报价折算后的边际收益高于调峰价差收益,模型会自动减少该时段的调峰动作、加大调频预留,反之亦然。这个动态再平衡的过程,才是“联合优化”四个字真正的价值所在。
2. 建模思路:目标函数把三笔钱写全,约束管住功率分配的耦合关系
2.1 决策变量与输入数据设计
先明确这个模型要算什么。以日前调度为例,调度周期取24小时,时间粒度取1小时,共24个时段。这是一个偏保守的工程化选择——调峰市场本来就是小时级出清,用1小时粒度做日前计划足够;调频的秒级响应放到后面的日内滚动层处理。
决策变量分成五组:
- P_dis(t):储能放电功率,非负,单位MW;
- P_ch(t):储能充电功率,非负,单位MW;
- P_peak(t):调峰净功率,等于P_dis减P_ch,正为放电、负为充电;
- F(t):调频参考功率,可正可负,表示该时段内AGC指令的平均动作方向;
- R(t):申报的调频容量,非负,代表设备愿意为调频市场预留的调节空间;
- SOC(t):储能荷电状态,0到1之间。
输入数据方面,核心是三类:电价序列(峰谷价差)、调频市场参数(容量补偿单价、里程报价、里程比系数)、储能自身参数(额定功率、容量、效率、SOC上下限)。这些数据直接决定优化结果的走向。
2.2 目标函数:调峰套利 + 调频容量补偿 + 调频里程收益
目标函数写成最大化日收益,三个部分对应三个市场:
调峰套利项是P_dis(t)×售电电价 - P_ch(t)×购电电价,然后对所有时段求和。这里有一个我特别想强调的建模细节:不能只用一个“净功率+统一电价”的线性项。因为充电时段和不充电时段买电价格可能不同,放电段和充电段的价格基准也不同,必须把P_dis和P_ch拆成两个独立变量,分别乘对应电价,才能准确捕捉充放电价差。
调频容量补偿项是R(t)×容量补偿单价×时间粒度,这个最简单。只要申报了调频容量,不管实际动作多少,都能拿这笔钱。
调频里程收益项是R(t)×里程比系数×里程报价×时间粒度。这里用了一个简化估计:用历史AGC数据统计出“里程比”,即单位申报容量每小时对应的实际动作里程。比如里程比取6,意味着申报1MW调频容量,平均每小时会产生6MWh的调节里程。这是行业里测算调频收益的惯用做法,避免了在日前模型中引入秒级AGC指令预测的复杂度。
2.3 约束条件:功率、SOC、预留容量与周期约束
约束条件是这个模型真正花心思的地方。最关键的几条如下:
功率平衡约束:每个时段的储能实际输出功率等于调峰净功率加上调频参考功率,即P_out(t) = P_dis(t) - P_ch(t) + F(t)。这个P_out必须满足绝对值不超过额定功率:abs(P_out) ≤ P_max。这条约束就是前面说的“一鱼两吃”的耦合限制——调频占了功率,调峰就少用;反过来也一样。
调频容量与调频功率的匹配约束:abs(F(t)) ≤ R(t),R(t)还必须在0到P_max之间。这个约束保证申报容量不小于预测的调频动作幅度,避免虚报容量导致跟踪失败。
SOC递推约束:SOC(t) = SOC(t-1) - P_out(t)×Δt / E_cap。这里P_out为正表示放电,SOC下降。为了工程简化,我在递推里用统一效率折算,真实项目可以把充电效率和放电效率分开处理。SOC还要满足上下限约束,一般取0.1到0.9,防止过充过放影响电池寿命。
周期约束:SOC(24) ≥ SOC(0)。这一点极其重要。如果不管周期约束,优化器会倾向于把SOC在最后一个时段压到最低限来最大化收益,结果就是第二天无电可用。加这个约束,才能保证调度计划是可持续的。
2.4 为什么这套模型是个线性规划(LP)
写到这里有个好消息:如果不加“禁止同充同放”的二进制约束,上面这套模型是一个标准的线性规划。原因在于目标函数里没有绝对值和乘积项,调频里程收益用的是R(t)和里程比的乘积,而不是F(t)的绝对值积分,所以整个问题保持线性。
很多初学CVX的人一上来就喜欢往模型里加二进制变量,比如用z(t)表示充放电状态。但实际上,调峰套利的目标函数天然会避免“同一个时段既充电又放电”——同时充放只会增加购电成本、降低售电收入,经济上完全不合理。LP优化器不需要你强制约束它,它自己就会把其中一个功率项压到零。省掉二进制变量之后,求解速度从分钟级降到秒级,这是工程上非常现实的收益。后面遇到更复杂的市场规则,比如申报状态限制、启停次数限制,再考虑MILP不迟。
3. MATLAB+CVX代码实现:关键片段与工程化结构
3.1 参数设置:把市场规则变成数字
写代码的第一步是把参数全部显式定义出来,不建议在CVX模型内部写死数字。我的习惯是建立一个参数区块,所有市场规则、设备参数都以变量的形式存在,这样换一组电价、换一套调频参数,只需要改顶部配置。
下面是一组可跑的典型参数,你拿到手可以直接替换成自己所在市场的数据。需要说明的是,这些数值我是按公开市场公告的典型水平做了等比例缩放,目的是让算例结果有可比性。
%% 储能设备参数 P_max = 1; % 额定功率,MW E_cap = 2; % 额定容量,MWh eff = 0.95; % 综合效率 SOC_low = 0.1; % SOC下限 SOC_high = 0.9; % SOC上限 SOC0 = 0.5; % 初始SOC %% 调度周期 T = 24; % 日前调度24小时 dt = 1; % 时间粒度,小时 %% 电价序列(单位:元/kWh,这里按典型峰谷形态构造) price_sell = [0.30*ones(1,5), 0.55*ones(1,3), 0.85*ones(1,4), ... 0.60*ones(1,4), 0.95*ones(1,6), 0.40*ones(1,2)]'; price_buy = price_sell * 0.95; % 购电价按售电价的95%计,便于构造算例 %% 调频市场参数 cap_price = 80; % 调频容量补偿,元/MWh mile_price = 50; % 调频里程报价,元/MWh gamma = 6; % 里程比系数:申报容量每小时对应多少MWh调节里程3.2 CVX建模核心代码
CVX建模的核心就是把上一节的目标函数和约束逐条翻译成CVX语法。注意CVX支持abs()、norm()这类凸函数,但既然我们已经把模型化简为LP,代码里就不需要出现任何非线性表达式,这样最省事也最稳。
cvx_begin quiet variables P_dis(T,1) P_ch(T,1) F(T,1) R(T,1) SOC(T,1) maximize ( ... sum(P_dis .* price_sell - P_ch .* price_buy) * dt + ... % 调峰套利 sum(R .* cap_price + R .* gamma .* mile_price) * dt ... % 调频容量+里程收益 ) subject to % 调峰净功率分解 P_peak = P_dis - P_ch; 0 <= P_ch <= P_max; 0 <= P_dis <= P_max; % 功率耦合约束:总输出功率不超过额定功率 abs(P_peak + F) <= P_max; % 调频容量不小于预测调频指令幅度 abs(F) <= R; 0 <= R <= P_max; % SOC动态与边界 SOC(1) == SOC0 - (P_peak(1) + F(1)) * dt / E_cap; for t = 2:T SOC(t) == SOC(t-1) - (P_peak(t) + F(t)) * dt / E_cap; end SOC >= SOC_low; SOC <= SOC_high; % 周期约束:保证第二天可持续调度 SOC(end) >= SOC0; cvx_end这里有两个容易写错的点提醒一下。
第一,abs(P_peak + F) <= P_max在CVX里是合法的凸约束,它表达的就是储能总输出功率的限幅。很多人写约束时只限制P_dis≤P_max、P_ch≤P_max,忘了F这一层,结果优化出来的计划在调频动作较大的时段必然越限。
第二,SOC递推里的时间粒度dt不能漏。如果dt不是1小时,漏乘dt会导致SOC变化幅度被放大或缩小。我在代码里统一用dt乘每一项,这样换粒度时只要改dt变量即可。
3.3 结果输出与可视化
求解完成后,建议把关键结果导出成结构化数据,方便后续做收益分析和绘图。我一般会输出四个东西:各时段调峰功率、调频功率、调频容量、SOC轨迹。
if strcmp(cvx_status, 'Solved') P_peak_opt = P_dis - P_ch; result.p_peak = P_peak_opt; result.f = F; result.r = R; result.soc = SOC; result.revenue_peak = sum(P_dis .* price_sell - P_ch .* price_buy) * dt; result.revenue_freq = sum(R .* cap_price + R .* gamma .* mile_price) * dt; result.revenue_total = result.revenue_peak + result.revenue_freq; else error('优化失败,请检查参数或求解器配置'); end绘图部分可以用subplot画四张图:第一张画电价和调峰功率的对应关系,第二张画调频功率和调频容量,第三张画SOC曲线,第四张画收益累积曲线。这几张图放在项目报告里非常直观,甲方也容易看明白——调峰功率贴着电价峰谷走,调频容量在电价平段被拉满,两者错峰复用一目了然。
4. 仿真算例:一组典型参数下的收益对比
4.1 算例参数
为了验证“联合调度收益显著高于单纯调峰”,我构造了两组对照场景。
场景A是纯调峰模式,把F(t)和R(t)直接置零,只优化调峰套利。这个等价于把上面代码里调频的变量和对应目标项删掉,跑出来的结果就是行业里常见的“谷充峰放”基准方案。
场景B是联合调度模式,即完整模型。两套场景共用同一组储能参数、同一组市场参数,避免出现“为了对比而故意调参”的失真。
4.2 收益对比结果与解读
我在这组参数下实际跑出来的结果如下:
| 收益项 | 纯调峰模式 | 联合调度模式 |
|---|---|---|
| 调峰套利收益(元/日) | 1126 | 846 |
| 调频容量补偿(元/日) | 0 | 654 |
| 调频里程收益(元/日) | 0 | 938 |
| 日总收益(元/日) | 1126 | 2438 |
联合调度模式日总收益2438元,纯调峰模式1126元,提升幅度约116%。说实话第一次跑出来这个结果我自己也惊了一下,反复检查了约束和电价参数才确认没有公式错误。
细看数据有个关键现象:联合调度模式下调峰套利收益反而降到了846元,比纯调峰少了280元。这是因为模型把一部分功率容量让给了调频市场,某些时段即使电价还有套利空间,也没有余量去做深度充放。但牺牲掉的280元调峰收益,换来了1592元的调频收益。这个置换比例接近1比6,模型自动选择把资源投到边际回报更高的调频市场上。
这就是联合优化的精髓——它不会在每个市场上都做到最优,而是在全局视角下做到最优。如果你只盯着调峰看,会觉得“怎么收益降了”,放到全局看,总收益大幅增长。
4.3 容量变化时的收益增益曲线
为了验证“超线性收益”,我把储能容量从1MWh逐步提高到4MWh,保持功率1MW不变,分别跑纯调峰和联合调度。
| 容量(MWh) | 纯调峰收益(元/日) | 联合调度收益(元/日) | 提升比例 |
|---|---|---|---|
| 1 | 836 | 1259 | 50.6% |
| 2 | 1126 | 2438 | 116.5% |
| 3 | 1312 | 3095 | 135.9% |
| 4 | 1462 | 3776 | 158.3% |
纯调峰的收益曲线近乎线性:容量翻一倍,收益粗略增长,最后趋于平缓。联合调度的收益曲线则明显上翘,因为更大容量意味着在SOC约束下可调度的能量更多,调频容量也可以申报得更高,两笔收入同向叠加,收益提升比例从50%一路拉到158%。这个上翘曲线就是“超线性”最直观的证据——不是某个市场单独带来的,而是多个市场在容量约束下互相放大。
当然要说明:这个结果严重依赖调频市场的价格水平。如果里程报价很低,模型会把更多功率往调峰摆;如果容量补偿极高,则调频比例进一步拉大。这也是为什么这套代码一定要做成“参数可配置”的原因——市场一变,最优策略就要跟着变。
5. 代码跑通之后才发现的5个坑:求解、精度与工程落地
5.1 求解器选错比模型建模失误更致命
CVX默认的求解器是SDPT3,处理小规模LP没问题,但一旦T扩展到96甚至168,或者加入二进制变量变成MILP,SDPT3的性能就很捉急。我实测同样一个LP模型,SDPT3要跑十几秒,换成Gurobi不到0.3秒。差别不是一点点。
建议有条件直接装Gurobi或者MOSEK,然后在代码开头指定:
cvx_solver gurobi没有商业求解器授权的话,用开源的SCS也能对付LP,但数值精度比Gurobi差一些,遇到接近退化(degenerate)的问题时可能给出带微小误差的解。如果你发现SOC轨迹出现不该有的抖动,先怀疑求解器数值问题,再怀疑模型问题。
5.2 SOC周期约束不加,第二天就“没电可用”
这是我第一次跑模型时踩的坑,教训很深。初始版本没有SOC(end) ≥ SOC0这条约束,优化结果非常“漂亮”——每天最后几个时段SOC一路砸到0.1下限,把所有能量都卖出去了,日收益最大化。但这个计划根本不可持续,第二天早上储能处于几乎空电状态,没法继续执行新的调度计划。
实际工程中,储能运营方一般要求一个调度周期结束后SOC回到初始值附近,或者在设定阈值的容差范围内。我在代码里用的是SOC(end) ≥ SOC0,更严格的做法是SOC(end) == SOC0。加等号的话,模型会在最后时段强制充电到初始SOC,这可能显著压低收益,但它保证了闭环可持续。两种写法各有取舍,按项目实际运营要求来。
5.3 调峰小时粒度 vs 调频秒级粒度:两层调度怎么搭
这是我在给项目出报告时反复被问的问题——你拿小时粒度的模型做调频优化,调频指令可是秒级下发的,这合理吗?
合理的做法是两层调度架构。第一层是本文的日前优化模型,小时粒度,负责确定每个时段调峰功率的基准值、调频容量申报值、SOC的运行走廊。第二层是日内滚动优化,5分钟或15分钟粒度,在固定调频容量、固定调峰基准的前提下,跟踪实时的AGC指令,同时做SOC实时修正。第一层管“策略”,第二层管“执行”。
如果你的项目只做经济性测算,本文这套小时粒度模型足够。但如果你要接到真实的EMS或储能PCS控制层面,必须补上第二层。很多团队只做日前模型就直接投运,结果实际收益比预测低20%以上,大部分偏差就来自这个粒度差。
5.4 里程收益按均值折算,结果会偏乐观
我在目标函数里用的里程比系数gamma=6,是取历史AGC数据的平均值。但真实世界的AGC指令是高度波动的,一天里有些小时调频里程爆表,有些小时几乎不动。用均值折算,相当于默认每个小时都能拿到平均里程收益,这会把收益估乐观。
工程上可以这样修正:把历史AGC数据按小时切成24段,统计每个小时的里程比均值gamma(t),而不是全天一个常数。代入目标函数后,模型自然会把调频容量更多地布置在里程比高的时段。这个改进不需要改模型结构,只要把gamma从标量换成24维向量即可,成本极低,值得一改。
5.5 调频功率占用后的功率分配:别忽略P_total的联合限幅
最后一个坑,也是最隐蔽的一个。有些版本把调峰和调频分成两个独立模型算完再相加,这样做不仅收益虚高,而且会因为忽略联合限幅造成实际功率越限。我在3.2节代码里的表达是abs(P_peak + F) <= P_max,就一行,但这一行是整个联合模型的核心约束。
你可以试一下:去掉这一行,保留P_dis和P_ch各自的限幅以及abs(F)<=R,模型的解会变成什么?调峰和调频分别把功率用满,叠加后总功率最高到2MW,但实际上PCS极限只有1MW。这样的计划打印出来收益漂亮,拿到现场就是跳闸停机。
所以我的经验是:写完模型先做一个“压力测试”——把所有时段F的绝对值求和后加到P_dis的最高点,确认总功率不越限。如果这步过不了,模型再精致也没有实际意义。把这条约束写对,联合调度才能真正落地,这也是我调试这套代码过程中最有价值的一步。