恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
储能双层交易决策模型:现货与调频市场联合优化Matlab实现
首页
资讯中心
/
储能双层交易决策模型:现货与调频市场联合优化Matlab实现
储能双层交易决策模型:现货与调频市场联合优化Matlab实现
发布时间:2026/9/9 19:39:27
你手里有一座 10 MW / 40 MWh 的电化学储能电站如果只在现货电能量市场里“低充高放”一年下来收益能有多少如果同时把一部分容量留给调频辅助服务市场情况又会变成什么样这两个问题背后其实是一个典型的储能双层交易决策问题。我最近用 Matlab 把这套“上层定策略、下层出价格”的模型完整跑通了一遍顺手把建模思路、代码结构和踩过的坑都整理了出来。这篇文章我按“背景逻辑 - 双层模型设计 - Matlab 实现 - 算例结果 - 调试经验”的顺序来讲。适合两类人看一是刚接触电力市场优化方向的研究生想快速理解双层优化到底在干什么二是已经开始做储能经济性分析、想通过代码把多市场联合决策真正落地的工程师。读完你至少能明白一件事储能参与市场不是拍脑袋决定“多充一点还是少放一点”而是要在系统出清规则的约束下把能量市场和调频市场的收益一次性算清楚。1. 储能参与电力市场的核心逻辑1.1 现货电能量市场与调频辅助服务市场分别解决什么问题现货电能量市场通俗点说就是电力系统里的“大宗批发市场”。发电厂报量报价用户报需求系统在一个统一规则下算出每一时段的出清电价这个价格通常每 15 分钟或者每小时更新一次。储能在这类市场里的玩法大家应该很熟悉电价低的时候充电电价高的时候放电赚的就是峰谷价差业内管这个叫能量套利。调频辅助服务市场解决的是另一个问题。电力系统要求频率稳定在 50 Hz 附近一旦功率不平衡频率就会漂移这时候需要一些具备快速调节能力的资源在几秒到几十秒内顶上容量或增加出力。调频市场就是为这种“分钟级甚至秒级的快速响应”付费。储能机组天然适合这个赛道——动作快、精度高、可以双向调节很多地方做调频的收益甚至比纯套利还要高。这里顺手把概念理顺现货电能量市场买的是“量”按 kWh 结算调频市场付的是“容量可用性”和“实际调节里程”按 MW 和调节幅度结算。两者的物理基础是同一套储能设备这就给决策带来了耦合。1.2 为什么只参与现货电能量市场远远不够储能的前期投资非常大一座 10 MW / 4 h 的磷酸铁锂电站系统成本通常在 3000 万到 5000 万元。如果只靠峰谷套利一天充放两次假设峰谷价差在 0.60.8 元/kWh一天的现货收益也就在几万元上下回本周期非常长。调频市场给了一个新的空间。储能的优势是响应速度快可以在充电和放电之间来回切换这跟常规火电比是碾压性的。很多实际的调频项目中储能资源拿到的容量价格和里程价格都比火电高。但问题也随之而来储能的总功率和总容量是固定的如果一部分功率拿去调频就意味着同一时间不能再用这部分功率去现货市场充放电。这就是我说的“容量冲突”。所以储能参与市场的优化不能简单当成“现货收益 调频收益”两个独立项相加必须放到一个联合决策框架里搞清楚每一时刻每一兆瓦功率和每一兆瓦时容量到底应该分给谁。这个角度恰恰是很多人一开始容易忽略的。1.3 双层优化到底在描述什么博弈关系常规做储能收益评估很多人的做法是直接给一组预测电价然后做单层优化储能只是一个价格接受者——市场价格是多少就按多少结算储能决策不影响价格。这在储能规模很小的时候基本成立比如一台 1 MW 的分布式储能对整个省级市场来说无足轻重。但如果储能规模比较大或者一个聚合商手上掌握了几十上百兆瓦的资源呢这时候储能申报的充放电曲线会实实在在改变系统出力进而改变节点边际电价和调频出清价格。储能和市场价格是互相影响的关系再用“固定电价”做单层优化就会产生偏差。双层优化的思路正好用来描述这种博弈关系。上层是储能运营商的收益最优化决定每个时段的充放电功率、调频容量报多少、SOC 怎么滚动下层是市场运营机构的出清模型在给定储能申报和系统约束的前提下以系统总购电成本最小为目标算出市场出清价格。储能的目标函数里用到的电价就是下层出清产生的对偶变量。这样“上层决策影响下层出清下层出清反馈给上层收益”的闭环就是双层问题的核心结构。2. 双层交易决策模型的建模过程2.1 上层模型储能运营商的收益最大化上层模型相对好理解目标函数是储能参与两类市场的综合收益最大化。我建模时用的是典型的一天 24 个时段也可以扩展成 96 个时段只是变量和计算量要成倍增加。目标函数包含这么几项现货电能量市场套利收益、调频容量收益、调频里程收益再减去储能充放电循环带来的衰减成本。现货收益本质上是放电收入减去充电成本调频容量收益是按照申报的上调频容量和下调频容量的容量价格结算里程收益则和实际调频动作幅度有关通常用一个里程系数乘以容量收益。储能运行的物理约束必须写完整包括SOC 的离散递推方程也就是上一时段剩余电量加上本时段充入电量、减去放出的电量再扣掉损耗充放电功率上限制约同一个时刻充电功率、放电功率、上调频容量、下调频容量加在一起不能超过储能额定功率SOC 上下限一般在 10% 到 90% 之间不能满充满放。这里有一个比较容易漏的地方调频容量和充放电功率不是完全独立的关系。如果你的储能正在满功率放电那它基本没有能力再向上调频因为已经放到了上限。所以我们把“同一时刻所有功率需求之和不超过额定功率”写在约束里。这个约束做不对后面很多结果都是畸形的。2.2 下层模型电能量-调频联合出清下层模型模拟市场运营机构做的事情在收到所有市场主体的申报曲线后以系统总成本最小为目标优化各机组出力和系统需要的调频容量额度。下层模型我做了必要简化但保持了市场出清的核心机制。目标函数是最小化所有机组在 24 个时段的发电成本之和约束条件包括系统功率平衡约束、常规机组出力上下限、机组爬坡约束以及系统调频容量需求约束。下层优化完成后会得到一系列对偶变量其中最重要的两个就是现货电能量市场的节点电价 λ_t 和调频容量出清价格 π_t。在双层模型里来自下层的 λ_t 和 π_t 会直接出现在上层目标函数中。如果直接写成双层YALMIP 这类工具是没法直接求解的必须想办法把它改写成单层优化问题。换句话说下层模型不是独立求解一次就完事而是要把它的最优性条件嵌入到上层模型里让上层在决策时就“预测”到市场出清的结果。2.3 从双层到单层KKT 条件、强对偶与大 M 法这是整个建模过程中最核心、也最容易做错的一步。怎么把双层问题变成一个 Matlab 能吃进去的单层优化标准做法是因为下层是线性规划它的 KKT 条件既是必要条件也是充分条件所以可以用下层模型的 KKT 条件替换下层模型。KKT 条件包括四部分一是拉格朗日函数对下层变量的偏导等于零也就是“平稳性条件”二是下层所有原始约束保证解是可行解三是所有对偶变量的非负限制四是互补松弛条件即“若约束不被激活则对应对偶变量为零”。光有前三个条件还不够。互补松弛条件本质上是一个“两个非负变量相乘等于零”的非线性约束YALMIP 和 Gurobi 不能直接处理。我常用的办法是大 M 法引入一组 0-1 整数变量把互补松弛条件改写成带 0-1 变量和大 M 常数的线性不等式。比如一个简单的互补条件 μ·(G_max - G) 0可以等价写成 0 ≤ μ ≤ M·z0 ≤ G_max - G ≤ M·(1 - z)。这里的 z 就是一个二进制变量。引入 0-1 变量后整个模型就从线性规划变成了混合整数线性规划只要大 M 取值合理用 Gurobi 或 CPLEX 都能求解。这里要特别提醒不同的下层模型结构大 M 的取值范围必须单独检查。取值太大数值稳定性会很差求解器对偶变量报告各种奇怪结果取值太小有可能把真正的最优解卡在可行域外面。后面我会专门讲怎么调这个大 M。3. Matlab 代码实现与关键模块解析3.1 运行环境与工具箱准备我在本地用的是 Matlab R2022b求解器配的 Gurobi建模层用的是 YALMIP。YALMIP 最大的好处是可以把优化变量、约束条件和目标函数写成非常接近数学论文的形式不用自己手动去拼大规模矩阵这点在做双层优化研究时特别省心。如果没有 Gurobi也可以尝试用 Cplex 或者开源的 Cbc但大 M 加整数变量的混合整数规划问题Gurobi 的求解效率和鲁棒性明显好一些。装配过程不复杂但有两件事必须确认。第一YALMIP 的路径要在 Matlab 里 addpath 成功第二Gurobi 能正常被 Matlab 调用最稳妥的验证方法是在命令行里输入yalmiptest如果看到 Gurobi 那一栏显示 OK就说明环境没问题。3.2 数据准备与参数设置建模之前先把基础参数孤立出来放在脚本最前面方便统一调整。电池参数、系统参数、市场参数各一组。电池这块包括额定功率、额定容量、初始 SOC、充放电效率、SOC 上下限系统参数包括机组个数、各机组边际成本、出力上下限市场参数包括调频容量需求、里程系数、现货市场预测电价曲线。我习惯把每个时段的现货电价先单独画一遍确认峰谷分布是不是合理。调频容量价格在有些算例里也是外生的可以直接当成常数矩阵放进下层模型。下面这段代码是基础参数段的典型写法%% 基础参数 T 24; % 时段数 P_max 10; % 储能额定功率 MW E_max 40; % 储能额定容量 MWh soc_min 0.1 * E_max; soc_max 0.9 * E_max; soc0 0.2 * E_max; % 初始SOC eta 0.95; % 充放电效率 G_max 60; % 火电机组出力上限 MW G_min 10; % 火电机组出力下限 MW c_g 300; % 机组边际成本 元/MWh3.3 核心代码结构与双层转化写法真正建模时我的代码分三大块上层决策变量定义、下层出清约束和 KKT 互补条件、目标函数与求解调用。为了可读性我通常会写成多个脚本文件而不是一个超长脚本。主脚本main.m负责调用数据文件和建模求解子函数build_model.m负责返回约束和目标。上层变量声明部分长这样%% 上层变量 p_ch sdpvar(T,1); % 每时段充电功率 p_dis sdpvar(T,1); % 每时段放电功率 r_up sdpvar(T,1); % 上调频容量 r_dn sdpvar(T,1); % 下调频容量 soc sdpvar(T1,1); % 各时段SOC下层出清价格通过 KKT 条件被当成变量引进来λ_t 就是现货能量市场出清价格π_t 是调频容量价格。在模型里它们不是固定参数而是在 KKT 条件约束下求解出来的影子价格这是双层优化区别于“先用单个市场出清算价格、再固定价格做优化”的关键点。核心约束会包含下面这些逻辑功率平衡约束、机组上下限约束、调频容量需求约束、储能功率互斥约束、SOC 递推约束以及 KKT 转换后引入的互补松弛条件。完整的代码结构大体会像这样%% 约束集合 constraints []; % 系统功率平衡 constraints [constraints, G1 G2 p_dis - p_ch D]; % 储能功率约束 constraints [constraints, 0 p_ch P_max]; constraints [constraints, 0 p_dis P_max]; constraints [constraints, p_ch p_dis r_up r_dn P_max]; % SOC递推 constraints [constraints, soc(2:T1) soc(1:T) ... p_ch * eta - p_dis / eta]; constraints [constraints, soc_min soc soc_max];目标函数部分则把现货收益、调频收益和衰减成本组合在一起%% 目标函数 revenue_energy sum(lambda .* (p_dis - p_ch)); revenue_reg sum(pi_cap .* (r_up r_dn)); cost_degrad sum(deg_coef .* (p_ch p_dis)); objective revenue_energy revenue_reg - cost_degrad;求解调用用 YALMIP 的 optimize 函数%% 求解 ops sdpsettings(solver, gurobi, verbose, 1, ... gurobi.TimeLimit, 300); result optimize(constraints, -objective, ops);这里用-objective是因为 YALMIP optimize 统一是求最小化而我们要的是收益最大化。求解完第一件事不是去看结果而是检查result.problem是否等于 0。如果不为 0别急着画图先把问题定位清楚。3.4 结果提取与可视化求解完成后用value()函数把变量提取出来。我习惯一次性输出一个结构体把充放电功率、调频容量、SOC、市场出清价格全部装进去方便后面做算例统计分析。画图方面我一般会画三张图第一张是现货电价和储能充放电功率的组合图可以直观看出储能是不是做到了“低价充电、高价放电”第二张是 SOC 时序变化曲线确认没有越界第三张是调频容量和充放电功率的堆叠图看同一时刻容量分配是否合理。每次改完参数这三张图是我必看的。4. 算例分析不同策略下的收益对比4.1 算例配置与对比方案拿一个典型算例来说明。系统里有两台常规火电机组一台边际成本 280 元/MWh最大出力 60 MW另一台边际成本 420 元/MWh最大出力 40 MW。储能额定功率 10 MW容量 40 MWh初始 SOC 20%SOC 运行范围 10%90%。系统峰值负荷设在 80 MW调频容量需求设为 8 MW。现货电能量市场采用节点边际电价调频容量价格取一组随时间变化的曲线白天系统负荷高、频率偏差风险大的时段调频价格更高。我设置了三种对比方案。方案 A 储能只参与现货电能量市场进行套利不提供调频方案 B 储能只参与调频辅助服务市场不参与能量套利方案 C 就是本文讨论的双层联合优化储能可以同时申报现货能量曲线和调频容量由模型给出最优分配。4.2 基本结果与收益组成在设定参数下三种方案的一天收益结果大致如下表。注意这只是演示算例不同市场价格参数下结果会有明显变化不要直接当成实际投资决策依据。方案日综合收益元现货套利收益元调频容量收益元调频里程收益元A仅现货套利126001260000B仅调频182000134004800C双层联合优化239008600113004100方案 C 的综合收益明显超过前两者但值得注意的不是“联合优化最高”这个结论而是容量分配的变化。方案 C 里储能在现货市场不再激进地满充满放而是把一部分功率和容量留给了调频。白天某个高电价时段现货放电功率比方案 A 少了约 2 MW同时把那 2 MW 申报成上调频容量这部分容量收益加上里程收益反而超过了多放 2 MW 电的边际收入。这说明一个道理储能在市场里的优化不是“越多越好”而是“边际收益相等”。当调频容量价格处于高位时储能应该主动减少现货套利功率把资源挪给调频反过来当电价尖峰特别高、调频价格一般时储能就应该狠一点多放电少报调频。这种动态切换能力只有联合优化模型才能给你。4.3 灵敏度分析储能容量与调频价格水平的影响我额外跑了三组灵敏度分析。第一组修改储能容量倍率从 1 小时逐步增加到 4 小时第二组把调频容量价格整体乘以 0.5、1.0、2.0第三组调整现货市场峰谷价差。结果呈现出几个典型特征。储能容量倍率从 1 小时增加到 4 小时时综合收益从 19200 元/日逐步上升到 23900 元/日但收益增幅明显递减。原因在于调频容量受功率限制更多增加容量倍率对调频收益贡献有限而现货套利在 2 小时倍率附近已经基本把峰谷时段用满了继续增加容量只能多充一两次谷电边际贡献很小。调频容量价格翻倍时方案 C 的收益提升幅度比方案 A 大很多原因是模型自动把更多功率分配给调频鼓励储能牺牲一部分现货电量来换调频收入。这组结果表明双层模型对市场价格信号的响应非常敏感储能申报曲线的斜率直接受价格比影响。做实际决策的时候对调频价格预测曲线的准确性要求很高模型再漂亮输入价格预测错了结果也没意义。5. 常见问题与调试经验5.1 大 M 参数怎么取才能不改坏模型这是双层次优化转单层时最典型的坑。大 M 本质是给互补松弛条件“足够大”的上界让二进制变量能区分约束是否激活。取太大求解器会在对偶变量和互补条件的数值判断上出错有时候明明是最优解求解器报出来的却是“不可行”取太小又可能把不可行的情况误判成可行解出来的东西压根不是原问题的最优解。我的经验是大 M 不能随便固定成 1e6最好根据具体约束维度来定。比如出力约束的互补条件大 M 取最大出力值的 510 倍就差不多了价格相关约束的大 M可以用绝对电价上限乘上一个安全系数通常取 1e4 到 1e5。每次改完模型重新审视一遍大 M 的量级不要图省事套用上一版。5.2 KKT 转化后求解失败我从哪里开始排查KKT 转化后模型出问题头号嫌疑是互补松弛条件写错了。一个常见的低级错误是把互补条件的方向写反导致求解器出来的对偶变量符号不对目标函数变成负收益。排查时我第一步会先把二进制变量去掉光看原始约束和对偶可行性单独交给求解器检查约束是否一致第二步再看平稳性条件这个最容易因为下标写错出问题第三步才检查互补条件和大 M 取值。另外一个容易忽略的点是下层的凸性。下层如果不是线性规划KKT 条件只是必要条件不是充分条件转化后得到的解可能不是全局最优。这也是我强调下层出清模型要尽量保持线性的原因加入太多非线性约束会直接影响最终结果的可信度。5.3 调频里程收益和机会成本耦合容易漏很多早期版本的模型只计调频容量收益不计调频里程收益这会严重低估联合优化的价值。调频里程收入虽然不像容量收入那样稳定可预期但在实际市场中占比并不小。代价是调频动作会消耗储能电量和增加循环次数这部分损耗如果不在目标函数里体现模型就可能过度调频。把这部分耦合写进模型后你的目标函数会增加一项里程系数乘以调频容量减一个储能循环衰减成本系数乘以充放电总量。这两个系数的取值直接决定储能是偏保守还是偏激进。我在算例里把里程系数设为容量价格的 0.3衰减成本系数按 0.05 元/kWh 计入跑出来的结果整体是稳健的。你也可以根据自己的电池循环寿命数据和度电衰减成本去修正。这套流程跑完之后我个人最大的体会是储能参与市场的决策优化本质上是在不同收益来源之间做边际权衡。单市场的分析永远只能讲清一半的故事只有把现货电能量和调频辅助服务放进同一个双层框架里市场价格、储能申报、系统出清三者之间的互动关系才能真正体现出来。模型的套路并不复杂真正费时间的是调参数和排查那些藏在 KKT 转化细节里的坑。如果你也在做类似的储能市场决策问题不妨先按我这个框架搭一个最小版本跑通以后再逐步加复杂度这样后续扩展会顺很多。