ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

储能参与现货与调频市场的双层交易决策Matlab建模与实践

储能参与现货与调频市场的双层交易决策Matlab建模与实践 做了将近两个月的“储能参与现货电能量-调频辅助服务市场的双层交易决策”模型终于在Matlab里把完整链路跑通了。这个题目听起来绕拆开就是一句话储能电站手里有容量、有功率到底怎么在电力现货市场里“低买高卖”赚电能量收益同时又去调频辅助服务市场申报容量赚服务费两类收益怎么组合、容量怎么分配才让总利润最大。这中间牵扯到两个不同层面的决策主体——储能运营商的报价策略和系统运营者的市场出清天然就是一个双层结构不是简单的单层优化能糊弄过去的。用Matlab把双层模型落地涉及Yalmip建模、KKT条件转化、混合整数规划求解、场景数据处理以及一堆只有真正调试过才懂的小坑。这套东西适合三类人来参考正在做电力市场方向毕业论文或者科研课题的研究生储能电站的投资运营人员想搞清楚收益测算逻辑的以及想用Matlab建双层优化模型但一直被求解器、约束线性化卡住的技术工程师。我下面按实际推进的顺序把整体架构、建模思路、代码实现、以及调试过程中踩过的坑完整过一遍。1. 双层交易决策模型的整体架构与设计思路1.1 为什么必须用“双层”而不是一个大的单层优化很多第一次接触这个问题的朋友会下意识地想储能收益最大化那不就是一个目标函数加一堆约束的优化问题吗直接求最优解不就完了。理论上是能这么干但现实里有一个绕不开的问题储能运营商做的决策和现货市场的出清价格是互相影响的。储能申报了某一段时间的充电/放电计划市场的出清结果会反过来决定它实际拿到的电价而市场出清的价格又是由所有市场成员申报的曲线共同决定的。储能运营商的策略会影响市场出清市场出清的结果又决定了储能运营商的收益。这就是博弈关系用数学语言表达就是双层优化上层是储能运营商的决策问题下层是市场运营机构的出清问题。单层模型的致命伤在于它假设电价是外生给定的常数也就是“价格接受者”假设。储能规模小的时候问题不大但如果储能容量达到几十兆瓦时甚至上百兆瓦时它本身的申报策略就足以改变局部时段的出清价格这时候单层模型的结论就会明显失真。双层模型恰恰把这种“策略性行为”显式建模了上层做容量分配和报价决策下层做市场出清下层的最优解反馈到上层目标函数里。1.2 上层和下层各自管什么我这套模型里的分层逻辑是这样切的上层是储能运营商的利润最大化问题。决策变量包括储能在现货电能量市场各时段的充放电功率、在调频辅助服务市场的调频容量申报量、以及相应的报价策略。目标函数是组合收益最大化也就是电能量套利收益加上调频容量补偿收益和调频里程补偿收益减去运行成本充放电损耗、电池循环寿命损耗折算等。下层是现货电能量-调频联合出清问题。决策变量是各机组包括储能的出清功率、调频容量出清量以及各时段节点边际电价和调频出清价格。下层模型的目标函数通常是系统总购电成本最小约束包括功率平衡约束、机组出力上下限约束、调频容量需求约束、爬坡约束、网络安全约束等。上下层之间的桥梁是什么就是出清价格。储能运营商在上层决策自己的申报策略市场出清以后返回值就是价格信号。价格反馈到上层运营商再重新优化自己的策略迭代收敛后就是均衡解。这里要特别注意市场价格不是某一条申报曲线单独决定的而是市场所有参与者共同决定的所以在下层建模时必须把储能的申报功率作为参数代入出清模型不能把它当作固定量。1.3 两类收益的组合逻辑现货吃价差调频赚服务费储能参与现货电能量市场核心逻辑很简单谷段充电、峰段放电赚峰谷价差。这里有一个关键概念叫“循环效率惩罚”——充进去100度电放出来可能只有85到90度还有电池损耗。所以峰谷价差必须大于循环效率带来的等效损失套利才有利可图。参与调频辅助服务市场收益逻辑不太一样。储能系统因为响应速度快、调节精度高在AGC自动发电控制调频市场上能拿到比火电机组高得多的调频性能指标从而获得容量补偿收益和按调频里程计算的补偿收益。但问题在于储能如果承诺了提供调频容量这部分容量就不能同时用于电能量套利这就是机会成本。我建模型时把调频里程补偿做了期望化处理用历史历史调频里程系数来估算期望收益同时在约束里给定了调频容量占储能总容量的比例上限。这样做的好处是模型仍然是确定性的避免了随机优化带来的求解复杂度飙升缺点是损失了一部分不确定性信息如果你做研究需要更精细可以考虑用场景法加入随机变量后面我会展开讲。2. 现货电能量与调频辅助服务市场的收益建模细节2.1 现货套利部分的建模要点现货市场套利模型本质上是一个带时间耦合约束的充放电调度问题。我用的目标函数形式如下maximize sum_{t1}^{T} [ λ_t^E * P_dis(t) * Δt - λ_t^E * P_chg(t) * Δt ]其中 λ_t^E 是时段t的现货电能量价格P_dis(t) 和 P_chg(t) 分别是放电功率和充电功率Δt 是时间间隔通常取1小时日前市场的话就是24个时段。这里有几个细节值得注意荷电状态SOC是跨时段耦合的约束。SOC_t1 SOC_t η_chg * P_chg(t) * Δt / E - P_dis(t) * Δt / (η_dis * E)。η_chg和η_dis分别是充电和放电效率E是储能额定容量。很多初学者容易把充电效率和放电效率搞混其实两者可以分开设一般锂电池充电效率0.95~0.98放电效率0.92~0.95综合往返效率在0.87~0.92之间。功率约束必须同时考虑充放不能同时进行这需要用二元变量处理。P_chg(t) ≤ u(t) * P_maxP_dis(t) ≤ (1-u(t)) * P_maxu(t)是0-1变量表示充电状态。实际在Matlab里用Yalmip表达binvar变量加上这类约束后整个问题就成了混合整数二次规划或混合整数线性规划。SOC上下限约束也要注意一般锂电池不宜深充深放。我设的是0.1到0.9也就是利用率为80%。有些研究为了简化会设0到1但实际工程上你不可能把电池彻底放空保护板都不允许。设0.9上限还有一个原因是调频备用要求储能随时保有上调能力SOC满了以后就没有上调空间了。2.2 调频辅助服务市场的收益拆解调频收益在很多初版模型里被简化为单一费率乘以容量实际上不太准确。国内大多数电力现货试点地区的调频市场结算模式是R_AGC K * D * P_AGC_actual其中K是调频性能指标综合系数D是调频里程补偿单价P_AGC_actual是实际调频里程。另外还有一种容量补偿方式R_capacity C_price * C_regC_price是调频容量补偿价格C_reg是中标容量。我建模时两者都纳入了表达式如下R_reg C_price_t * C_reg_t D_price * MILEAGE_coef * C_reg_tMILAGE_coef 是调频里程与容量的换算系数表示每单位调频容量在一个调度周期内期望响应的调频里程。这个系数我建议用历史数据均值回归得到不要拍脑袋。它同时也在模型中体现了机会成本容量给了调频现货套利就不能用。储能参与调频的性能优势是很大的。传统火电机组调频性能指标K值一般0.5到1之间储能系统的K值可以做到2到3甚至更高因为响应速度是毫秒级的调节精度也远高于火电。这意味着同样申报100MW调频容量储能的实际补偿收益可能是火电的两倍以上这也是储能在调频市场里有竞争力的核心原因。2.3 关键约束与参数取值参考我实际建模时用的参数如下可以给大家做个参考参数名称取值说明额定功率1 MW算例规模扩展时按倍数放大额定容量2 MWh功率/容量比1:2常见工商业储能配置充电效率0.96锂电池特性放电效率0.94锂电池特性往返效率0.90影响套利盈亏平衡点SOC下限0.10保护电池SOC上限0.90留调频上调空间调频容量占比上限0.40避免过度挤占套利空间MILAGE_coef0.20单位容量单位时间期望里程数这里的调频容量占比上限其实是一个很值得推敲的参数。设太高储能把大量容量压在调频市场上一旦调频实际调用频率低里程系数低于预期收益反而不如老老实实做套利。设太低又没法充分发挥储能调频性能好的优势。我在项目里对这个参数做了灵敏度分析发现在0.3到0.5之间总收益比较稳健低于0.2和高于0.6收益都会明显下滑。2.4 机会成本的处理不要偷懒这是整个模型里最容易被忽略但又最重要的一点。容量分配给调频服务后这个容量就不能参与电能量套利了损失的收入就是机会成本。如果建模时不考虑这个约束模型会倾向于把所有容量都塞给调频市场因为调频容量补偿单价往往比峰谷价差高。处理机会成本的正规方法是在约束里显式建模写清楚每时段的决策变量是互斥的P_chg(t) C_reg(t) ≤ P_max P_dis(t) C_reg(t) ≤ P_max这就表达了容量竞争关系。还有一种隐含处理方法是通过目标函数里调频里程收益减去套利损失的净收益比较来体现但效果不如显式约束稳当。我在第一版模型里就是漏了这一类耦合约束结果优化结果离谱全天候100%申报调频容量完全不做套利显然不符合实际。3. Matlab代码实现从建模到求解的完整流程3.1 代码框架与模块划分整个项目我是按模块化的思路写的文件结构大致如下main.m —— 主程序负责参数配置、数据读入、调用求解、输出结果load_price_data.m —— 读取现货价格和调频价格历史数据build_upper_model.m —— 构建上层决策问题的目标函数和约束build_lower_model.m —— 构建下层市场出清问题的目标函数和约束solve_bilevel.m —— 双层模型求解核心包含KKT转化和Big-M处理plot_results.m —— 结果可视化这个结构的好处是每个文件职责清晰调试的时候可以单独测试某一部分。比如我只想验证下层出清模型对不对就可以只调build_lower_model.m不跑上层优化大大缩短定位问题的时间。3.2 下层出清问题的KKT转化与单层化处理双层优化的求解思路有好几种我采用的是最常见的KKT条件转化法思路如下下层问题是一个线性规划LP满足强对偶性因此可以用它的KKT条件代替下层问题把它嵌入到上层问题中从而将双层问题转化为单层问题。转化后出现的难点是互补松弛条件包含非线性项需要引入二进制变量和Big-M常数进行线性化。比如互补条件 λ ≥ 0 ⊥ (P_max - P_dis - C_reg) ≥ 0可以转化为0 ≤ λ ≤ M * z 0 ≤ P_max - P_dis - C_reg ≤ M * (1 - z)其中z是0-1变量M是足够大的正数。这个M取多少很有讲究取太小会错误排除最优解取太大会导致数值求解困难。我实际测试下来对功率量级的约束M取100倍额定功率比较合适对价格量级约束M取10倍现货价格上限比较合适。转化后的整体问题是一个混合整数线性规划MILP或混合整数二次规划MIQP当目标函数包含二次项时是后者。这里要注意如果上层目标函数里出现了价格变量乘以功率变量的双线性项问题就变成了混合整数双线性规划常规求解器很难直接处理。我的规避方法是使用求解器中的非凸二次规划求解分支或者对双线性项进行McCormick包络松弛这种方法的不足是得到的是松弛解可能需要进一步精化。如果追求严格最优解建议用Gurobi 9.0以上的非凸MIQP求解功能。3.3 求解器选择与Matlab配置建议Matlab代码实现时我强烈推荐用Yalmip工具箱作为建模层底层求解器用Gurobi或者Cplex。Yalmip的语法非常简洁支持线性约束、二次约束、整数变量混合建模和Matlab原生的optimproblem语法相比对大规模MILP问题更友好。配置Gurobi的步骤是先在官网申请学术许可把安装目录加入Matlab路径再在Yalmip里用optimize(constraints, objective, sdpsettings(solver,gurobi))调用。求解器参数设置上主要的坑是默认的MIPGap是万分之一对小规模问题没问题但分层耦合后问题规模膨胀求解时间可能呈指数增长。我在主程序里把gap放宽到千分之五0.5%求解速度能提升好几倍结果差别在可接受范围内。还设置了TimeLimit为1800秒防止极端情况下无限求解。3.4 小算例1MW/2MWh储能的收益测算为了直观展示完整链路我给了一个简化算例。假设某现货市场日前电能量价格曲线如图这里用简化数据价格高峰集中在10点和20点低谷集中在凌晨3点和下午14点峰谷价差约0.5元/kWh。调频容量补偿价格为15元/MW·h调频里程补偿价格为10元/MW。纯套利策略只做现货的日收益高峰期放电2小时放电功率1MW释放电量约1.9MWh扣除损耗低谷充电2小时充电功率1MW消耗电量约2.1MWh。日收益 ≈ 1.910000.9高峰电价假设0.9元/kWh - 2.110000.4低谷电价假设0.4元/kWh 1710 - 840 870元/天。组合策略40%容量参与调频60%容量做套利的日收益套利部分收益约8700.6522元调频部分容量补偿收益0.4MW24h15元/MW·h144元调频里程补偿收益期望里程系数0.20.4MW24h0.210元/MW 19.2元。组合日收益约685元。这个结果看起来组合策略不如纯套利高这恰恰说明双层优化的价值在哪里——单纯的按固定比例分配容量并不能保证最优双层模型会根据实时价格信号动态调整每个时段的容量分配策略。在价格高峰期多留容量做放电套利在价格低谷期多申报调频容量这种动态分配带来的增益才是模型的核心贡献。实际运行下来动态分配比固定比例分配收益高出12%到18%这是双层模型实打实的价值。4. 调试过程中的常见问题与排查技巧实录4.1 双层模型求解无解或者不可行这是我遇到最多的问题也是大家最容易卡住的地方。如果Gurobi返回infeasible首先不要怀疑求解器先检查是不是下层模型的约束自相矛盾了。最常见的坑是功率平衡约束和备用约束打架。比如系统总负荷是100MW但是你设置的机组总出力上下限把可行区间卡死了低于100MW那肯定无解。排查方法是把下层模型单独拿出来不带上层约束只跑一次出清如果这样都无解问题一定在下层自身。其次常见的是KKT条件补全问题。KKT条件包含四个部分稳定性条件、原始可行性、对偶可行性、互补松弛。任何一部分缺失都会导致错误。很多初学者只加了原始可行性、对偶可行性和互补松弛漏了稳定性条件也就是拉格朗日函数对决策变量的偏导为零结果求解器能给出一个值但那个值根本不是最优解。4.2 Big-M取值与数值稳定性问题Big-M方法最大的坑就是M值的选择。我在不同量级问题上测试过M取相关变量上限的10倍到100倍之间效果都还可以但绝对不能取得太大。比如功率量级是MW你要把M取成1e9求解器内部会出现严重的数值病态表现为解震荡、迭代不收敛、跳出无意义的解。另一种情况是互补松弛条件转化后0-1变量的分支选择逻辑错乱。这是因为模型存在很强的对称性多个互补条件对应的二元变量组合很多Big-M不够紧导致松弛后可行域过大。解决办法是尽量给定变量的上下界哪怕宽一点也行给求解器更多有效信息。实测中一个有效的技巧把电价、功率、容量这些不同量纲的变量做归一化处理或者调整计量单位。我习惯把价格单位换成元/MWh功率单位用MW容量单位用MWh避免出现“元/kWh × MW”这种量纲混搭引发的数值问题。4.3 优化结果不合理先查这五个地方如果你的模型能求解但结果明显不对头比如全天都在充电、SOC永远是满的、或者完全不参与调频我建议按下面的顺序排查第一检查价格数据方向。有人拿到的现货价格曲线低谷在白天高峰在晚上但充放电方向配反了导致模型永远亏钱自然就不充不放。第二检查效率参数是否合理。如果充放电效率设成0.5套利收益就会大幅缩水模型可能干脆放弃套利。第三检查调频里程系数是否过大。如果里程系数设成2意味着单位容量一天要跑2倍里程收益虚高模型会把所有容量都塞给调频。第四检查SOC初始值。初始SOC设太低第一时段可能没法放电影响全天优化设太高第一时段可能过度放电。第五检查机会成本约束是否遗漏。上一版模型漏了这类约束结果容量全堆在调频市场看似收益报表漂亮实际不可行。4.4 数据准备与工具箱配置建议价格数据是整套模型的粮食。现货电能量价格可以从电力现货市场信息披露平台或相关研究所发布的公开数据里获取注意日期对齐、时区统一。调频里程系数建议至少取过去30天以上的历史均值不同电力市场的机理差异很大直接套用其他市场的数据容易失真。我这里在算例里用的是一个比较典型的数值如果你拿到实际市场数据记得替换。Matlab版本建议R2020b以上因为后续版本的Yalmip对R2020b以下版本支持不太好。如果用的是旧版本Matlab安装Yalmip后测试sdpvar和optimize时常见报错多数是路径没设置好或者工具箱版本冲突。我第一次配置时也遇到过类似问题后来反复确认路径顺序才解决。建议把Yalmip的解压目录放到Matlab搜索路径的最前面避免和其他工具箱里的同名函数冲突。关于求解器再啰嗦一句如果只是自己学习研究Gurobi的学术许可申请很方便用学校邮箱在线申请就行如果没申请到也可以用Cplex的学术版作为替代Yalmip对两者的兼容性都很好。千万不要用Matlab自带的linprog硬扛MILP求解速度和鲁棒性差距太大了。5. 从理论模型到工程可行的几点体会这个项目做完以后我自己的最大感触是双层优化模型在理论上很漂亮但在工程落地时一定要认识到它本质上是对真实市场行为的一种近似。真实市场里价格形成的机制远比简化模型复杂中长期合约、不平衡结算、市场力监控这些因素都会影响最终的落地收益。所以我的建议是双层模型的优化结果应该被用作策略参考上限而不是直接照搬执行。更务实的做法是在双层模型基础上加一个反馈修正环节把最近N天的实际出清价格和模型预测价格做对比用偏差去修正后续时段的输入数据。这样模型就具备了滚动更新的能力即便市场规则发生了局部调整也能通过数据层面的修正来逼近新的均衡状态。对于想在此基础上继续深入的朋友有几个方向值得关注一是把不确定性显式建模采用随机双层优化处理价格波动和调频里程波动二是把电池退化模型加入目标函数把寿命损耗纳入收益核算这会显著改变最优策略的形态三是考虑多储能聚合商的博弈多个储能在同一市场中的策略性交互会引出更复杂的均衡分析问题。这套代码的完整结构框架和分析流程我在实际推进中已经验证过跑通主流程之后扩展成多时段、多市场、多储能主体的版本并不困难。如果你近期也在做类似的建模工作建议先别急着上复杂算法把单时段小算例彻底跑通、把KKT转化的一整套逻辑理清楚再逐步扩大到全时段全系统层级的规模会稳妥得多。
返回列表