1. 交易商定价的战场其实在"价差里的风险溢价"
做交易商这行,定价这件事最容易被误解。外行看报价单,以为就是"进价加几个点",成本加成法一算,完事。真做过一轮完整年度的同行都清楚,成本加成法只在价格平稳、负荷稳定的环境里成立;一旦批发侧出现连续尖峰、终端负荷又因为天气大幅漂移,报价单上那个"几个点"根本不够覆盖尾部亏损,客户却已经按你的低价签了一年。我见过好几个团队,定价模型只算期望成本,前三个季度利润表好看得不行,第四季度一次寒潮直接把全年利润清零,甚至倒挂。问题的根子不在于算得准不准,而在于价差里到底应该装多少风险溢价,以及这部分溢价怎么用数学表达出来。
这篇内容想解决的问题很具体:当一个交易商(或者售电主体、中间撮合方)在批发价格、终端电量、对手履约三方面都存在不确定性的情况下,如何给自己的报价曲线定出一个"最优"的价格。这里的"最优"不是利润率最高,而是在给定风险容忍度的前提下,让期望利润和尾部损失达成一个你能签字负责的平衡。适合正在搭建定价模型、做套餐设计、或者给定价结果做评审的同行参考。哪怕你只用到其中的场景生成和CVaR约束两节,也够把现有报价单的漏洞补掉一大半。
1.1 成本加成法在波动市场里为什么会失效
成本加成法的隐含假设是:批发价格和终端电量都可以用点估计代替。实际操作中,大家一般取过去12个月的加权均价,加一个目标毛利率,再按峰平谷拆成三段。这套做法在价格分布接近正态、方差小的时候误差可控。但批发价格的实际分布是右偏厚尾的,99分位可能是中位数的三到五倍,而且高价位往往集中在少数几十个小时里。点估计取的是均值,均值被那些尖峰拉高了,于是你在低价时段报贵了、丢客户;在高价时段又报便宜了、留下巨大敞口。
更麻烦的是,价格和负荷不是独立的。极端高温那天,空调负荷冲高,同时现货价格也冲高,两边同向放大。你按独立假设算出来的方差,实际只有真实方差的一半左右,风险被系统性低估。这就是为什么很多定价模型在回测里表现不错,一到真实运行就崩——回测用的历史均价已经把这种同向放大给"平均"掉了。
我在早期做的一版模型里就踩过这个坑:用月度均价标定,跑出来峰谷价差只有实际需要的六成。上线第一个夏天,峰段客户用电量超出预期20%,那部分缺额全部按现货补,一个月的亏损吃掉了前面五个月的分时溢价。后来把标定粒度从月度改成小时级、把价格和负荷的联合分布显式建出来,峰谷价差才回到合理区间。这个教训很直白:定价的输入必须是分布,不是均值。
1.2 把定价问题还原成一个带期权的报童问题
想清楚定价的本质,有个很好用的类比:报童问题。报童每天早上要决定进多少份报纸,进多了卖不掉要折价处理,进少了少赚。交易商在某个时段之前,也要决定"锁定多少电量"——通过双边合约或者日前申报把价格固定下来;锁定之后实际用电量才知道,多了按低价卖回现货,少了按高价从现货补。区别在于,报童的报纸有固定成本和固定残值,而交易商的"残值"和"补货成本"本身是随机的,还带考核罚则。
把这个结构写清楚:
- 锁价成本为
c(合约价或日前价) - 实际用电量
q是随机变量,分布函数F - 缺额补购成本
c_u(实时高价 + 偏差考核系数) - 余量回收价格
c_o(实时低价 - 折价) - 零售报价
r
对锁定量Q求期望利润的一阶条件,得到的临界分位数是:
F(Q*) = (c_u − c) / (c_u − c_o)这个式子看着简单,信息量很大。它告诉你锁定量应该定在需求分布的哪个分位点上,完全由三个成本的相对关系决定。举例来说,如果缺额补购成本是锁价成本的两倍,余量回收价只有锁价的一半,那么F(Q*) = (2c − c)/(2c − 0.5c) = 0.667,也就是只锁定 66.7% 分位的电量,留三分之一的敞口。反过来,如果考核罚则很重、缺额成本高得离谱,c_u远大于c,F(Q*)会逼近 1,你就得近乎全额锁定,把价格风险换成量风险。
真正做定价的时候,Q和r是耦合的:r决定客户愿不愿意签、签多少量,Q又要根据q(r)的分布来定。所以完整的模型是两层嵌套——外层定报价曲线,内层定锁定策略。后面第 4 节会讲这个两层结构怎么用两阶段随机规划实现。
2. 把不确定性拆成价格、电量、对手、时序四根柱子
不确定性这个词太笼统,笼统的东西没法建模。我的习惯是把它拆到不能再拆,每一根柱子单独找分布、单独做检验,最后再合成。拆的时候有个原则:能影响到现金流的,才算一根柱子。影响不到现金流的波动,哪怕统计上显著,也不该进模型,否则模型会变得又复杂又难解释。
实际拆下来,交易商面对的不确定性主要是四类。这四类的处理方式完全不同,混在一起讨论只会让讨论失焦。
2.1 价格侧:均值回复叠跳跃,别用纯几何布朗
批发价格的第一特征是均值回复,第二特征是尖峰跳跃,第三特征是季节性。用几何布朗去拟合,会得到"价格可以无限上涨"的荒谬结论,而且完全抓不住尖峰。我一般用均值回复加上复合泊松跳跃:
dp = κ(θ − p)dt + σ·p^γ·dW + J·dNκ是回复速度,θ是长期中枢,σ是扩散项波动率,γ控制波动率对价格水平的依赖程度(γ < 1时高价段波动率相对被压低,符合实际),J是跳跃幅度,N是泊松过程。
参数标定有几个务实的做法。κ和θ可以用日频数据做 AR(1) 回归得到,κ = -ln(φ)/Δt,φ是一阶自回归系数。跳跃部分不要试图用极大似然去拟合,样本里跳跃次数太少,似然函数很平。直接用超阈值的经验分布更稳:把超过 95 分位的价格拿出来,看它们相对中位数的倍数分布,把这个经验分布直接作为J的抽样源。
提示:标定时把样本按季节分段。同一个市场,夏季和冬季的
θ可能差一倍以上,全年混在一起标定,得到的θ谁都不像。
还有个细节,价格的时间分辨率决定了模型复杂度。如果你做的是小时级报价,那至少要建 24 维的日内形态;如果只做月度套餐,可以把日内波动聚合掉,只建日均价和日内峰谷比的二维分布。别一上来就建小时级模型,除非你的客户结构确实对小时敏感。
2.2 电量侧:预测误差的分布随温度和时段变形
负荷预测误差最容易犯的错误是假设正态。实测下来,预测误差的分布是随温度和时段变形的:春秋季温和天气,误差接近正态且方差小;极端温度日,误差分布明显左偏或右偏,而且方差是平时的两到三倍。用单一正态去拟合全年,会在最需要准确的那几天给出最不靠谱的置信区间。
我的处理办法是按"温度带 × 工作日/休息日 × 时段块"做分层,每层单独估计误差分布。层数不要太多,否则每层样本不够。实际用下来,温度分五档(极冷、冷、温和、热、极热)、工作日分两类、时段分四块(深夜、早峰、日间、晚峰),一共 40 层,每层用两年数据大概有几百个样本,够用了。样本不足的层用邻近层做收缩估计。
另一个容易被忽略的点是客户结构变化带来的分布漂移。同一个客户群,今年和去年的用电形态可能完全不同,比如某类客户上了储能或者自发电,峰段用电直接掉一半。这时候历史误差分布就失效了。所以每次重新标定之前,我都要先跑一遍客户结构的变化检测:按行业大类拆开看负荷曲线的形状有没有显著变化,有变化的那一类单独重新标定,不要跟其他类混。
2.3 对手侧与结算时滞:被大多数人忽略的现金流风险
价格和电量是显性风险,对手风险是隐性风险,但它的杀伤力一点不小。具体有三种表现:
第一是履约违约。签了固定价合约的对手方,在现货价格远低于合约价的时候,有动机找各种理由减少提货量,把便宜电留给现货。你按合约锁定的量卖不出去,只能按现货低价转手,价差直接损失。
第二是结算周期错配。批发侧通常要求较短周期结算甚至预付,终端侧回款周期可能长得多。这中间的缺口要靠资金成本填补,而资金成本随市场利率波动。做年度定价的时候如果不把这部分算进去,等于白干。
第三是信用敞口累积。客户数量多了以后,单个客户的违约概率不高,但组合起来的期望损失不小。特别是当价格朝一个方向持续走的时候,处于不利方向的客户违约概率会系统性上升,这类相关性在独立假设下完全看不到。
处理办法是在定价里加一个"对手风险溢价"项,取值等于期望违约损失加上资金占用成本。具体算法后面第 5 节会讲。这里先记住一点:对手风险的定价依据不是历史违约率,而是"当前价差方向 + 客户信用分层"的组合。
2.4 四类不确定性合成同一个概率空间的做法
四类各自建好分布以后,不能简单假设独立然后相乘。合成的时候至少要保留三组相关性:
| 相关对 | 相关方向 | 处理方式 |
|---|---|---|
| 价格 × 负荷 | 同向(高温同时推高两者) | 联合场景抽样,用 Copula 或按日整块重采样 |
| 价格 × 违约率 | 同向(价格偏离越大违约越多) | 违约率写成价差的单调函数 |
| 负荷 × 结算时滞 | 弱相关 | 可近似独立,但要单独做压力测试 |
我用得最多的是按日整块重采样(block bootstrap)。把历史数据按自然日切成块,每天包含 24 小时的价格和负荷,抽样时整块抽,这样日内形态、价格负荷的日内相关性全部自动保留,不需要额外建 Copula。缺点是无法生成历史中没出现过的组合,所以还要叠加一个人工构造的极端场景集,这个在第 6 节讲。
块的长度选择有讲究。块太短,跨日的价格持续性丢失;块太长,样本多样性不足。实测下来 5 到 10 个交易日比较合适,因为价格的自相关大概在这个尺度上衰减得差不多。
3. 目标函数:从最大化期望利润到CVaR约束下的价差
模型能不能用,八成取决于目标函数写得对不对。我见过太多模型,约束写得极其精细,目标函数一行max 期望利润,跑出来的解在数学上最优,业务上没人敢用。原因很简单:期望利润最大化等价于风险中性,而交易商这个角色天生是风险厌恶的——你的资本金有限,一次爆仓就没有下一次了,期望值再高也没意义。
3.1 单纯最大化期望利润会把你送到悬崖边
举个具体的数字感受一下。假设两个报价方案:
- 方案 A:期望年利润 800 万,5% 分位利润 +300 万
- 方案 B:期望年利润 1000 万,5% 分位利润 −600 万
期望利润最大化会选 B。但如果你的资本金只有 500 万,B 方案那 5% 的概率(大概二十年一遇)会让你直接出局。而 A 方案虽然期望低 200 万,最差情况还能赚。
这个例子说明一件事:利润分布的形状比它的均值重要。定价模型必须能表达"我不想要左尾",而表达左尾最成熟的工具就是 VaR 和 CVaR。VaR 的缺点是它在左尾内部是平的,不区分"亏 600 万"和"亏 6000 万",而且作为优化目标不满足次可加性,场景离散化以后容易出现多个局部最优。CVaR 没有这两个问题,它是凸的、可线性化的,所以我在定价模型里一律用 CVaR。
3.2 风险厌恶系数的标定不能拍脑袋
目标函数一般写成这样两种形式之一:
形式一,加权:max E[Π] − λ · CVaR_α(Loss)形式二,约束:max E[Π] s.t. CVaR_α(Loss) ≤ L_max
λ或者L_max怎么定?最常见的错误是随便取个λ = 0.5。这个参数直接决定了报价高低,必须有业务依据。
我的做法是用资本金约束反推。假设公司能承受的最大年度亏损是L_max(一般取净资产的某个比例,具体比例由内部风控规则定),那么就用形式二,把L_max固定死。这样模型输出的报价水平就有了明确的业务含义:这个价格是"在最多亏L_max的前提下能给出的最低价"。
如果想用形式一,那就做一次等价换算:先按形式二求解一次,得到最优解处的CVaR边际价值,这个边际价值就是λ的合理取值。我实测下来,λ落在 0.1 到 0.4 之间居多,具体看市场波动水平和资本厚度。
α的取值也要有依据。α = 0.95意味着只看最差的 5% 场景,对应大概一年里有 18 天。如果你的结算周期是月度的,考虑到月度之间的相关性,α可以放松到 0.90。α取太高(比如 0.99),样本量不够,场景数少于 100 的时候尾部估计全是噪声。
3.3 一个可落地的目标函数写法
把前面几节的东西组装起来,完整的优化问题是:
决策变量:各时段各档位的报价 r_{t,k},各时段的锁定量 Q_t 目标:max E[Π] − λ · CVaR_α(Loss) 其中 Π = Σ_t [ Σ_k r_{t,k} · q_{t,k}(r) − c_t · Q_t − c_u,t · (q_t − Q_t)^+ + c_o,t · (Q_t − q_t)^+ ]约束包括:报价曲线的单调性(量增价降)、档位价格的上下界(不能低于成本、不能高于监管允许的某个水平)、锁定量不超过预期量的某个比例、CVaR 约束、以及各场景下的功率平衡。
这个写法有个技术难点:(q − Q)^+是非线性的,q_{t,k}(r)里的需求价格响应也是非线性的。前者用辅助变量加线性约束处理:
z ≥ q − Q, z ≥ 0 (z 就是 (q − Q)^+)后者用分段常弹性的近似,把需求函数在对数空间里离散成几段,每段内线性化。这些线性化处理后面第 4 节细说。
还有一点经验:目标函数里的 CVaR 项不要和期望利润用同一个场景集算。用同一套场景,模型会过度拟合这套场景的尾部形态,样本外表现会打折。我的做法是期望利润用全部场景算,CVaR 用削减后的核心场景集算,两套场景的生成种子不同,相当于做一个轻量的正则化。
4. 报价曲线的求解链路:场景、模型、线性化
模型写完了,接下来是工程问题。这一段是整个定价工作里最花时间的地方,也是能拉开差距的地方。很多团队卡在这里:场景生成了几千个,模型写出来了,求解器跑一晚上跑不出可行解,最后退回到手工调价。
4.1 场景生成与削减:保留日内形态比保留数量更重要
场景生成的质量决定了模型的上限。我用的流程是三步:
第一步,清洗与对齐。价格和负荷的时间戳对齐、缺失值用插值填补、异常值单独标记但不删除。异常值恰恰是尾部风险的来源,删掉它们等于主动放弃对尾部的建模。标记以后,在削减阶段给它们加权保留。
第二步,按日重采样。从历史日集合里做有放回抽样,抽N次组成N个年度场景。N一般取 1000。抽的时候按季节分层,夏季、冬季、过渡季的抽样比例跟预期业务结构保持一致。
第三步,场景削减。1000 个场景进优化模型太慢,一般削到 50 到 100 个。用快速前向选择或者 k-medoids,目标是最小化保留场景和原场景集之间的 Wasserstein 距离。
削减这步有个坑:不能只按全年的总量指标做削减。如果两个场景全年总成本一样,但一个成本集中在夏天、一个集中在冬天,它们对定价决策的影响完全不同。我一般按"月度成本向量"做削减,把每个场景表示成一个 12 维向量,在这个空间里算距离,这样月度形态不同的场景会被保留下来。
提示:削减后一定要检查保留场景的尾部覆盖。具体做法是看原场景集的 95 分位损失和削减后场景集的 95 分位损失差多少,差超过 10% 就增加保留场景数。
4.2 两阶段随机规划的变量与约束怎么写
第一阶段是"现在就要定"的决策:报价曲线r_{t,k}和锁定量Q_t。第二阶段是"信息揭晓后"的决策:各场景下实际从现货买卖多少、偏差考核罚多少。第二阶段变量带场景下标s。
核心约束组:
需求响应约束。每个场景下,客户愿意接受的电量是报价的函数:
q_{t,s} = q0_{t,s} · (r_t / r0_t)^(−β_t)q0是参考报价r0下的基准量,β_t是价格弹性。电力短期弹性很小,我一般取 0.1 到 0.3,峰段取大一些(客户的可中断负荷多),谷段取小一些。这个参数不要自己猜,用客户的历史换签数据做回归估计,换签时的价格变化和量变化能反推出弹性。
偏差结算约束。设申报量是Q_t,实际量是q_{t,s},偏差考核的门槛是±ε,超出部分按实时价的k倍结算:
dev_pos_{t,s} ≥ q_{t,s} − Q_t − ε·Q_t dev_neg_{t,s} ≥ Q_t − q_{t,s} − ε·Q_t罚金项是k · p_spot,t,s · (dev_pos + dev_neg)。ε和k的具体取值要看你所参与市场的考核规则,不同市场差别很大,这块要按实际规则逐条翻译,不能套用。
锁定量约束。Q_t不能超过期望量的某个上限,也不能低于某个下限(因为要保证供应可靠性):
θ_lo · E[q_t] ≤ Q_t ≤ θ_hi · E[q_t]θ_hi一般不超过 1.2,θ_lo不低于 0.6。
库存/电量平衡约束。每个场景下,合约量加现货净买入必须等于实际用量:
Q_t + buy_{t,s} − sell_{t,s} = q_{t,s}其中buy和sell不能同时为正,用二元变量或者用buy, sell的非负约束加一个大的互补惩罚来近似。场景数多的时候,二元变量会让求解时间爆炸,所以我一般用非负拆分加松弛,靠目标函数里的成本差自动实现互补——因为现货买卖有价差,同时买卖一定亏钱,最优解自然不会同时为正。
4.3 报价曲线分段线性化与单调性约束
报价曲线在业务上通常是阶梯式的:用电量落在某一档,全量按那一档的价格结算;或者累进式:前一段按第一档价,超出部分按第二档价。这两种在数学上写法不同,要先跟业务确认清楚,写反了模型输出的价格结构会完全错位。
累进式用 λ 变量做线性插值:
q_t = Σ_k λ_{t,k} · Qbar_{t,k} rev_t = Σ_k λ_{t,k} · Rbar_{t,k} Σ_k λ_{t,k} = 1, λ_{t,k} ≥ 0其中Qbar是各档的累积电量节点,Rbar是各档的累积收入节点。用 SOS2 约束保证λ至多两个相邻非零,得到的就是分段线性函数。
阶梯式全量结算必须上二元变量:
q_t = Σ_k q_{t,k}, q_{t,k} ≤ M · y_{t,k}, Σ_k y_{t,k} = 1 rev_t = Σ_k r_{t,k} · q_{t,k}y是档位选择变量。规模大的时候这一步是性能瓶颈,实际做法是按客户类型分组,每组用同一个报价曲线,组内客户量合并算,这样档位变量只跟组数相关,不跟客户数相关。
单调性约束一定要加:
r_{t,1} ≥ r_{t,2} ≥ ... ≥ r_{t,K}不加的话,模型有可能给出"量越大单价越高"的荒谬解,因为在高价时段它宁愿用一个高得离谱的档位价格来限制客户的用电。这类解在数学上可行,业务上必须毙掉。
4.4 规模控制与求解器选择
实际规模大致是这样:24 个时段 × 4 个档位 × 80 个场景,决策变量在十万量级,约束在几十万量级。这个规模的开源求解器跑一次大概几分钟到十几分钟,可接受。如果客户分组到 10 组以上,规模会到百万量级,需要做分解。
我的经验是三条:
第一,先把时段聚合。24 小时不一定都要单独定价。如果客户对小时级价格不敏感(大部分中小客户确实不敏感),聚合成峰、平、谷、尖四段,规模直接降到六分之一,对结果影响很小。
第二,场景数不够就加回抽样验证。用 80 个场景求解出的报价,拿到 1000 个场景上评估,如果评估结果和模型内部估计的期望差超过 5%,说明场景削减损失太大,要么增加场景数,要么换削减方法。
第三,用热启动。每次重算的时候,把上一次的最优报价作为初始点。因为参数是缓慢漂移的,相邻两期的最优解很接近,热启动能把求解时间压掉一半以上。
5. 分时套餐与偏差考核怎么内化进报价单
模型跑出最优解只是中间结果,离能发出去的报价单还有一段距离。这一段要处理的是:怎么把模型输出的数字翻译成客户看得懂、销售敢报、风控能签字的三段式价格表。
5.1 峰谷价差的三个组成部分
峰谷价差不是简单的"峰时成本减谷时成本"。完整的拆解是三层:
第一层,期望成本差。峰时段的期望批发成本减去谷时段,这是最直观的部分,占比大概六到七成。
第二层,波动溢价差。峰时段的成本波动率显著高于谷时段,风险厌恶系数乘以波动差,就是这部分溢价。实测下来这部分占总价差的一到两成,极端年份能到三成。这层是最容易被漏掉的,也是为什么很多公司的峰谷价差在平稳年份够用、在波动年份不够用的根本原因。
第三层,偏差成本差。峰时段客户用电的不确定性更高(空调负荷对温度敏感),偏差考核的期望成本也更高,这部分要单独加进去。
三层加起来才是完整的峰谷价差。给个数量级参考:假设峰时期望成本 0.6 元、谷时 0.3 元,波动溢价峰时 0.08 元、谷时 0.02 元,偏差成本峰时 0.03 元、谷时 0.01 元,那么完整价差是(0.6+0.08+0.03) − (0.3+0.02+0.01) = 0.38元,比单纯的期望成本差 0.3 元高了 27%。这个差距在竞标的时候就是生死线。
5.2 偏差考核的"保险溢价"怎么算
偏差考核的本质是:交易商替客户承担了用电量的不确定性,这相当于卖了一份保险。既然是保险,就要收保费。
保费的计算用期望损失加风险加成。期望损失是各场景下罚金的期望值,直接算。风险加成用 CVaR 的增量:加上这部分业务以后,整体组合的 CVaR 恶化多少,乘以风险厌恶系数。
这里有个反直觉的地方:偏差考核溢价不应该对所有客户一视同仁。用电稳定的客户(比如连续生产型)偏差小,溢价应该低;用电波动大的客户(比如商场、间歇生产型)溢价应该高。差异化定价不只是公平问题,更是防止逆向选择——如果统一费率,用电稳定的客户会觉得自己在补贴波动大的客户,慢慢流失,最后留下来的全是高风险客户,组合质量持续恶化。
实操上,我按历史偏差率的分布把客户分成三档,每档给不同的偏差溢价,费率差大概在 1.5 到 2 倍之间。给新客户报价时,先按行业归属给一个初始档位,运行三个月后按实际偏差率重新分档。这个动态调整机制要在合同里写清楚,否则后面调整会引起纠纷。
5.3 阶梯电量的边际定价逻辑
阶梯报价的目的是用价格信号引导客户削峰。定价逻辑是边际成本递增:客户多用的那一度电,对应的采购成本是边际成本,不是平均成本。
具体做法是把客户的用电量按分位数切档,比如前 60% 是基础档(按长期合约覆盖)、60% 到 85% 是常规档(按日前市场覆盖)、85% 以上是尖峰档(按实时市场覆盖,成本最高)。每一档的价格分别对应不同的采购来源成本加风险溢价。
这样做的副作用是:客户会倾向于把用电量控制在前两档以内,实际负荷曲线被压平。对我们来说这是好事,因为压平以后偏差小、现货敞口小。但对客户来说,如果他的生产特性决定了他必须用满三档,他会觉得价格太高。所以阶梯的分界点要按客户群的实际负荷分布来定,不能拍脑袋取 60/85 这种整数。我一般用聚类分析找分布的天然断点,让分界点落在客户用电量分布的稀疏区。
还有一个细节:阶梯的档位数量不要超过三档。超过三档,客户算不清自己的电费,销售也解释不明白,沟通成本远大于收益。
6. 回测与压力测试:把模型放到真实噪声里摩擦
模型在训练集上表现好不算数,能不能在样本外扛住真实噪声才是关键。回测这块我踩过的坑比建模还多,说几个核心的。
6.1 滚动前推回测的框架设计
绝对不能用随机切分做交叉验证。时间序列有强自相关,随机切分会导致训练集里混入了未来信息,回测结果虚高得离谱。我见过一个团队用随机切分跑出 R² 0.9 的模型,上线以后实际偏差超过 30%,根子就在这里。
正确做法是滚动前推(rolling origin):用前 18 个月标定,预测第 19 个月,然后窗口往前滚一个月,用第 2 到 19 个月标定,预测第 20 个月,以此类推。滚动次数至少 12 次,覆盖完整的年度周期,否则某些季节根本没有样本外检验。
每次滚动要记录四组指标:
| 指标 | 含义 | 警戒线 |
|---|---|---|
| 年化期望利润偏差 | 预测利润与实际利润的相对误差 | 超过 ±15% 要查原因 |
| 利润的 5% 分位 | 尾部风险的实际表现 | 低于L_max说明约束失效 |
| 最大单月回撤 | 单月最差表现 | 超过半年利润要复盘 |
| 报价命中率 | 报价被客户接受的比例 | 低于 40% 说明价格脱离市场 |
其中"报价命中率"这个指标特别值得说。它是模型输出和市场真实接受度之间唯一的连接点。如果模型说这个价最优,但报出去 100 个客户只有 10 个签,那说明模型对需求弹性的估计偏了,或者竞争环境的信息没进模型。这时候光调模型参数没用,要回去重新估弹性。
6.2 三个必须跑穿的极端场景
历史重采样生成不了历史没出现过的组合,所以必须人工构造极端场景。我固定跑这三个:
场景一,连续高价。把连续 7 天的现货价格整体乘以 3,同时负荷不变。检验的是锁定量是否足够、CVaR 约束是否真的起作用。这个场景在回测里一般会亏,但亏多少必须知道。
场景二,价格负荷双尖峰。连续 3 天,价格乘以 4,同时负荷乘以 1.3,且这两件事同时发生。这是最坏的情况,检验的是组合的极限承受能力。
场景三,大客户集中流失。前 20% 电量的客户在一个月内流失,剩余客户的固定成本分摊上升。这个场景检验的不是价格风险,是商业风险,但它对定价的反馈非常直接——如果定价太高导致客户流失,模型算出来的最优利润根本实现不了。
每个场景跑完,看两个数:单场景下的亏损额,和这个亏损占资本金的比例。后者超过 15%,说明报价整体偏低,需要上调风险溢价。
6.3 指标怎么读:别只看期望利润
回测报告里最容易误导人的就是期望利润这一个数。我一般要求同时看四个数:期望利润、5% 分位、利润的偏度、单位电量毛利。
偏度这个指标很多人不用,但它很有价值。负偏度意味着左尾比右尾长,也就是"小概率大亏"。如果两个方案的期望利润一样,一个偏度是 −0.5,一个是 +0.2,那应该选后者。定价模型优化的是 CVaR,CVaR 已经隐含了对左尾的惩罚,但偏度能提供一个额外的视角,帮你判断这个解是不是"靠少数极端场景撑起来的"。
单位电量毛利(元/千瓦时)是业务侧最关心的指标。它和期望利润的区别在于剔除了规模因素。如果期望利润涨了但单位毛利跌了,说明是靠低价冲量冲出来的,这种增长不可持续,一旦价格战打起来会立刻现原形。
7. 实操里最容易翻车的几个点
模型层面讲完了,说说工程和业务结合的地方。这些问题在论文里看不到,但每一个都能让整个项目推倒重来。
7.1 场景相关性与结算周期的错配
前面说过按日整块重采样能保留日内相关性,但这只解决了日内的问题。跨月的相关性更麻烦:如果连续两个月都是高价,损失是叠加的,不是平均的。用日块重采样生成的年度场景,跨月相关性是自然保留的,但如果你为了减少计算量把月度聚合成独立场景,这个相关性就丢了,尾部风险会被严重低估。
我的处理办法是保留日粒度做场景生成,只在优化求解阶段做时段聚合。这样相关性在生成阶段就固化了,聚合只影响求解效率,不影响统计性质。
结算周期错配的问题更隐蔽。批发侧按日结算,终端侧按月结算,中间的资金占用成本在模型里如果不体现,等于给客户做了一个免息贷款。加这一项的办法是在目标函数里减掉资金成本率 × 平均应收账款余额,平均余额可以从历史数据估出来。
7.2 报价更新频率与客户黏性的拉扯
理论上,报价应该跟着市场走,市场变了就调价。实际上,调价太频繁会严重伤害客户关系。我见过一个团队做了日频调价的系统,技术上很漂亮,结果三个月内客户流失了四成,因为客户没法做预算。
实际可行的节奏是:基础价差季度调整,附加项月度调整。基础价差反映的是长期成本结构和风险溢价,这部分变化慢,一季度调一次客户能接受。附加项(偏差溢价、燃料联动)月度调,因为这些跟短期市场直接挂钩,客户也理解。
调价还有个技巧:只调不利方向,有利方向不主动降。当市场下行、成本降低的时候,不主动降价,而是把节省下来的部分作为"年度返还"在年底结算。这样既保住了利润率,又给了客户一个正向的心理预期。当然这需要在合同里提前约定清楚,不能临时变卦。
7.3 参数漂移与模型再标定的节奏
模型参数会漂移,这个毫无疑问。问题是多久重标一次。标定太频繁,参数会跟着噪声乱跳,报价不稳定;标定太慢,模型跟不上市场结构变化。
我的节奏是分层标定:
- 波动率类参数:月度重标,用最近 6 个月的滚动窗口
- 均值回复速度、长期中枢:季度重标,用最近 24 个月
- 需求弹性:半年度重标,用换签数据
- 对手违约参数:季度重标,用实际违约记录
重标之后一定要做一件事:对比新旧参数下最优报价的差异。如果差异超过 10%,不要直接上线,先做归因分析——是数据问题、是参数估计不稳定、还是市场真的变了。我遇到过几次参数剧烈跳变,查下来都是数据源出了问题(某个月的负荷数据单位错了,差了 1000 倍),如果直接上线,报价会错得离谱。
提示:给每次重标做快照,把参数、数据版本、最优解都存下来。出问题的时候能快速回滚,也能做归因分析。这个习惯帮我省过至少两次重大事故。
最后分享一个我在实际项目里形成的判断标准:如果一个定价方案,你自己不敢用真金白银去承担它的最差情况,那这个方案就不该发给客户。模型的输出永远只是一个参考,最终的定价决策必须经过一次"人肉压力测试"——把最差场景的亏损额摆在桌上,问一句"这个数我们扛得住吗"。扛得住,报出去;扛不住,回去调风险厌恶系数,重新算。这个环节看着很土,但它拦住的问题比任何模型校验都多。