☰
交易商最优定价:两阶段随机规划、CVaR与需求弹性
2026/10/31 3:45:11 网站建设 项目流程

1. 先把问题拆开:交易商定价到底在定什么

“交易不确定性下的最优交易商定价”,这个题目看起来像一篇运筹学论文,但我第一次真正被它难住,是在一个很具体的场景里:手上有一批已经签了年度框架的用户,电量要在批发侧按月度、按日买回来,用户那边的用电量说不准,批发侧的价格更说不准,而我唯一能自己决定的,就是卖给用户的那张价目表。报高了,用户在比价里流失;报低了,一个月结算下来利润是负的。后来我把这件事拆成了两阶段随机规划的样子,用场景树刻画批发价和负荷,用条件风险价值(CVaR)压住尾部亏损,才算把“最优”这两个字从口号变成了一个能算出数的东西。这篇东西写给正在做售电、聚合、能源零售或者任何“先定价、后采购”生意的朋友,也写给做量化建模、想找一个真实落地场景练手的同学。

1.1 交易商的账本到底长什么样

要谈定价,得先把钱从哪儿来、到哪儿去搞清楚。交易商本质上做的是“批发-零售价差”的生意,但这个价差远比看起来复杂,因为它在时间维度上是错配的、在数量维度上是有偏差的。我把一个典型交易商一年的损益结构拆成下面这几项,你对照自己的业务看一眼,基本都能对上号。

项目方向说明是否受定价直接影响
零售电费收入收入电价 × 实际用电量直接,且双向影响(价格高则量少)
批发购电成本成本中长期合约 + 现货偏差间接,通过影响预测电量
偏差考核费用成本申报与实际不符产生的结算惩罚间接,通过负荷预测误差
用户获取与维系成本成本渠道、比价补贴、续约让利直接,价格竞争力决定留存
绿电/证书采购成本成本履约所需的绿色属性间接
资金占用成本成本预付款、保证金、结算周期差弱相关

这张表里最关键的一行是“零售电费收入”。它不是简单的线性关系,因为价格和电量之间是互相咬合的:你把价格降 5%,电量可能涨 8%,收入反而涨了;但也可能只涨 1%,那就是纯亏。这个咬合关系,行话叫需求价格弹性,是整个定价模型的心脏。我在下面会专门用一节讲它怎么估、怎么塞进模型,因为十个人做这个模型,有八个栽在弹性上。

另外要注意,账本上还有个隐形的项:结算周期与价格传导的时滞。你给用户的价格通常是锁一段时间的,而批发侧的成本是逐日变动的。这个时滞本身就是一种风险敞口,定价的时候必须给它留出风险溢价,否则你会遇到“价格看着挺合理,但每个月都在小亏”的慢性失血。

1.2 不确定性到底从哪几个口子漏进来

很多人一上来就说“不确定性”,但说不清是哪一种。我在建模前会强制自己列一张不确定性清单,因为不同来源的不确定性,处理方式完全不同:有的适合用场景刻画,有的适合用鲁棒集合兜底,有的压根不该进模型而应该在业务规则里解决。

不确定性来源表现形式典型量级推荐处理方式
批发价格波动现货价格日间跳跃、季节性抬升日均波动 5%~15%,极端日翻倍场景生成 + 相关性建模
用户用电量波动温度、生产排期、节假日日预测误差 3%~10%场景生成,与价格解耦处理
价格弹性不确定涨价后流失多少说不准弹性估计误差可达 ±50%鲁棒或分布鲁棒
用户流失/切换竞品降价、合约到期集中流失年流失率 5%~20%单独做留存模型,输出到弹性里
可再生出力偏差自供部分的实际出力与预测差光伏日误差 10%~20%并入净负荷场景
合约履约偏差大用户实际用电远低于申报长尾,个别用户可达 40%单独设违约惩罚项

这张表里,我个人认为最被低估的是价格弹性不确定和用户流失。批发价格波动虽然吓人,但它是外生的、可观测的、有历史数据的,建模相对容易;而弹性是你自己抬价之后才会暴露的东西,样本少、噪声大、还带幸存者偏差。我见过太多模型把弹性当成一个确定参数代进去,算出来的“最优价格”漂亮得像教科书,一上线第一个月就掉了 12% 的用户,因为现实中用户的切换决策是非线性的:小幅涨价没反应,一旦跨过某个心理阈值就成批走人。

1.3 为什么“最优”不是一个数,而是一条曲线

新手最容易犯的错,是希望模型吐出一个数字:“你就告诉我该报 0.58 还是 0.62。”但真实的定价问题里,最优解是一族解,取决于你愿意承担多大风险。原因在于:期望利润最大的那个价格,往往正好落在尾部风险最大的位置。

举个例子,某个批发价场景下,你赌现货会跌,于是把零售价压得很低去抢量。这个策略在 80% 的场景里利润很高,但在剩下 20% 的场景里(现货暴涨而你的零售价被锁死),你会亏到怀疑人生。期望值上它是赢的,但你的现金流可能撑不到赢的那天。

所以正确的做法是把问题写成“给定风险偏好,求最优价格”,输出一条价格-风险前沿曲线:横轴是 CVaR(或最大回撤、亏损概率),纵轴是期望利润。决策者在这条曲线上挑一个点,而不是让模型替他挑。这个思路听起来抽象,落地其实很实在——你把 β 从 0 调到 1,模型给出的价格会从激进到保守平滑变化,你把这个变化画出来给业务负责人看,比任何解释都管用。

注意:不要试图用单一目标函数“自动”选出最优风险偏好。风险偏好是业务决策,不是模型参数。模型能做的是把取舍关系量化清楚。


2. 建模路线选型:随机规划、鲁棒还是机会约束

路线选型这一步,很多人是照着论文选的,结果选了个自己根本标定不了参数的模型。我的原则很简单:能用场景说清楚的不确定性,就用随机规划;说不清分布但知道范围的,用鲁棒;只关心越界概率的,用机会约束。下面把三条路线摊开讲。

2.1 三条主流路线的适用边界

路线核心思想优点代价什么时候选它
两阶段随机规划用有限场景近似不确定性分布,第一阶段定价、第二阶段各场景下最优购电可解释强、能直接输出利润分布、与 CVaR 天然兼容需要场景生成能力,场景数一多求解变慢有 2 年以上历史价格与负荷数据
鲁棒优化不确定性落在给定集合内,优化最坏情况不需要分布假设,结果稳健过于保守,价格常常贴到下限数据极少、或极端场景后果不可承受
机会约束约束以一定概率成立直观,直接对应“亏损概率不超过 5%”非凸,转化困难,求解不稳监管或内部有明确的越界概率红线
分布鲁棒在分布的模糊集内最坏情况兼顾稳健与数据利用参数难调,规模大有较强工程团队、场景有限

我的默认选择是两阶段随机规划 + CVaR,理由有三个。第一,它的第一阶段变量(零售价)和第二阶段变量(购电量、偏差)天然对应业务现实:价格先定、采购后做,中间有信息差,这个信息差正是风险的来源,模型结构如实反映了它。第二,场景法能顺带产出利润分布,你可以直接回答“这个价格方案有 8% 的概率季度亏损超过 X”,这对业务方极有说服力。第三,CVaR 是凸的,可以线性化,塞进 MILP 里求解稳定,不像方差那样引入二次项把问题搞成非凸。

鲁棒优化我也用,但只用在两个地方:一是弹性参数,因为它真的说不准,用盒式或椭球式不确定集兜住;二是新市场冷启动、完全没有历史数据的头三个月。

2.2 两阶段结构怎么对应到业务动作

把模型结构和业务流程对齐,是让模型能落地的前提。我通常会明确三件事:

第一阶段(定价决策):决定价格结构。可以是单一价格,也可以是分时价格、分档电量价格、峰谷组合。这一阶段的决策必须在不确定性揭晓前做出,且一旦做出就锁一段时间,比如一个季度。

第二阶段(运营决策):在每个价格场景 + 负荷场景组合下,决定批发侧怎么买、中长期合约用多少比例、偏差怎么处理。这一阶段是“事后”决策,允许看到场景结果再动。

这个结构的关键假设是:价格不随场景变化。现实中当然有动态调价,但如果你的合约锁价期是季度级的,那这个假设就是对的。如果做的是月度甚至周度调价,就把第一阶段拆成多个时间块,每个块一个价格变量,块与块之间加平滑约束防止价格来回跳。

实操心得:不要在模型里加太多价格档位。我见过一个模型设了 24 个时段 × 5 个电量档 = 120 个价格变量,结果求解两天没收敛,业务方直接放弃。先从“单一价格 + 分时两档”起步,跑通了再加维度。

2.3 需求价格弹性如何塞进模型

这是整个模型里最容易出错的地方,我单独说透。需求函数我一般用常数弹性形式:

$$D_s(p) = D_s^{0}\left(\frac{p}{p_0}\right)^{-\varepsilon}$$

其中 $D_s^{0}$ 是场景 $s$ 下的基准电量(在参考价 $p_0$ 处的电量),$\varepsilon>0$ 是弹性系数。这个形式的好处是参数只有一个,标定简单,而且经济含义清晰:$\varepsilon=0.6$ 表示价格涨 1%,需求降 0.6%。

坏处是它让目标函数变成非线性。$p \cdot D_s(p) = p^{1-\varepsilon} \cdot \text{const}$,当 $\varepsilon<1$ 时是凹增函数,当 $\varepsilon>1$ 时是凸的。这就导致一个问题:如果你直接把 $p$ 当连续变量丢给非线性求解器,求出来的局部最优很可能是假的。

我的处理办法是分段线性化。把价格区间 $[p^{\min}, p^{\max}]$ 切成 K 段(一般 10~20 段就够),在每个节点 $p_k$ 上预先算好对应的需求 $D_s(p_k)$ 和收入 $R_s(p_k) = p_k D_s(p_k)$,然后用凸组合表示:

$$p = \sum_{k} \lambda_k p_k,\quad D_s = \sum_k \lambda_k D_s(p_k),\quad \sum_k \lambda_k = 1,\ \lambda_k \ge 0$$

再加上 SOS2 约束(只有相邻两个 $\lambda_k$ 非零),就得到精确的分段线性函数。这里有个必须提醒的方向性偏差:$D_s(p)$ 关于 $p$ 是凸函数,用割线做凸组合会高估需求;而收入函数 $R_s(p)$ 在 $\varepsilon<1$ 时是凹的,凸组合会低估收入。两个偏差方向相反、部分抵消,但不会完全抵消。我的经验是:如果弹性小于 1,收入端低估更明显,模型会偏保守,价格略低于真实最优;如果弹性大于 1,需求端高估更明显,模型会偏激进。

要修正这个偏差,最土但最有效的办法是:把价格网格在业务最可能取值的区间加密,比如把 60% 的网格点集中在 $p_0$ 上下 15% 的范围内。分段越密,割线越贴近原函数,偏差可以压到 1% 以内,对定价决策完全够用。

关于弹性系数的经验量级,我把自己踩过的坑整理一下,仅供参考,必须以你自己的数据标定为准:

用户类型短期弹性常见区间长期弹性常见区间备注
居民用户0.2 ~ 0.60.6 ~ 1.2短期对价格不敏感,续约时集中反应
中小商业0.5 ~ 1.01.0 ~ 1.8有比价行为,决策周期短
大型工业0.8 ~ 2.01.5 ~ 3.0对电价极度敏感,可自备、可错峰
数据中心类0.1 ~ 0.30.3 ~ 0.6负荷刚性,但迁移成本高、黏性强

需要强调的是,这些数字是“常见量级”,不是“标准答案”。同一个城市、同一个行业,不同年份估出来的弹性可能差一倍。有一种情况特别容易估偏:幸存者偏差。你只有留下来的用户的数据,走了的用户你根本看不到他们的反应,所以直接回归会系统性低估弹性。修正办法是用切换/流失数据单独建一个离散选择模型,把“走不走”的概率折算成等效弹性,再和留存量价数据融合。


3. 模型落地:目标函数与约束逐项拆

3.1 目标函数:期望利润和风险惩罚怎么加权

目标函数我一般写成均值-CVaR 的形式:

$$\max\ (1-\beta),\mathbb{E}[\Pi] + \beta \cdot \text{CVaR}_{\alpha}(\Pi)$$

其中 $\Pi$ 是利润随机变量,$\beta \in [0,1]$ 是风险厌恶权重,$\beta=0$ 就是纯期望最大化,$\beta=1$ 就是纯尾部保护。$\alpha$ 一般取 0.9 或 0.95,表示只看最差 10% 或 5% 的场景。

CVaR 的线性化用 Rockafellar-Uryasev 那个经典变换:

$$\text{CVaR}\alpha(\Pi) = \max{\eta}\left{\eta - \frac{1}{1-\alpha}\mathbb{E}\left[(\eta - \Pi)^{+}\right]\right}$$

引入辅助变量 $u_s \ge \eta - \Pi_s$、$u_s \ge 0$,就变成纯线性:

$$\max\ (1-\beta)\sum_s \pi_s \Pi_s + \beta\left(\eta - \frac{1}{1-\alpha}\sum_s \pi_s u_s\right)$$

这里 $\pi_s$ 是场景概率,$\eta$ 是自由变量(最优时等于 VaR)。这段代码写错的人特别多,最常见的错误是把 $u_s$ 的约束方向写反,写成 $u_s \le \eta - \Pi_s$,结果模型会疯狂压低 $u_s$,CVaR 项变成一个大正数,求解器给出一个虚假的高利润方案。我第一次写这个的时候也栽过,跑出来年化利润是同行的三倍,高兴了十分钟才发现是符号问题。

3.2 关键约束:电量平衡、合约比例与偏差考核

目标函数只是外壳,真正决定模型行为的是约束。我把核心约束按重要性排一下。

电量平衡约束是最基本的:

$$q_s + \sum_{j} c_{j,s} = D_s(p) + d_s^{+} - d_s^{-}$$

其中 $q_s$ 是现货购电量,$c_{j,s}$ 是第 $j$ 份中长期合约在场景 $s$ 下的交割量,$d_s^{+}$、$d_s^{-}$ 是正负偏差。这个式子表达的意思很朴素:买回来的电加上合约交割的电,必须等于用户实际用的电加上偏差。

偏差考核约束是交易商的心头肉。很多市场规则对申报偏差有一个免罚区间,超出后阶梯式加价。这个阶梯是分段线性的,可以直接写进模型,但要注意它在零点附近是非光滑的,需要用正负偏差两个变量拆开,不能直接用绝对值。

合约比例约束体现的是风险对冲:

$$c^{\min} \cdot \hat{D}s \le \sum_j c{j,s} \le c^{\max}\cdot \hat{D}_s$$

$\hat{D}_s$ 是预测电量。$c^{\min}$ 一般不低于 60%,$c^{\max}$ 不超过 100%,中间这个区间就是你的对冲空间。这个约束看似无关紧要,实际上是决定价格激进程度的关键阀门:允许的对冲比例越高,模型越敢定低价抢量,因为它知道风险能被合约锁住。

价格上下限约束必须有,而且要设得贴近现实。上限通常由竞品价格和用户承受力决定,下限由成本和品牌定位决定。我见过有人不设上下限,模型给出一个负价格(为了抢量算总账),然后拿着这个结果去汇报,场面一度十分尴尬。

留存率约束是我加的一个业务化约束,形式可以很松:涨价幅度超过某个阈值时,需求下降按更陡的弹性计算。这本质上是把非线性弹性用分段的方式近似进去,比单一弹性更贴近现实。

3.3 报价结构的选择与线性化技巧

价格结构的选择比很多人想象的更影响结果。同样是平均价格 0.6 元,你可以做成平段价、峰谷两档、三段分时、带基本电费的两部制、或者带阶梯电量的分档价。这几种结构在模型里的复杂度差别很大。

平段价最简单,一个变量,适合做基线对比。两段分时价格是两个变量加一个约束(峰段不低于谷段某比例),规模和求解难度增加不多,但在用户侧的接受度通常更好,因为它给了用户一个“省钱的动作”。三段以上就要小心了,用户侧的响应行为会变得很不线性,你的弹性模型得跟着细化,否则算出来的量价关系是假的。

两部制(基本电费 + 电量电价)在工商业场景很常见,它的好处是能把固定成本前置、降低电量电价的感知,提升价格竞争力。但它在模型里会引入“用户会不会为了避开基本电费而降低申报容量”的额外决策,这块要单独建模,不能简单套用单一弹性。

分段线性化的通用技巧我总结成三条:

  1. 先确定网格范围再定段数。范围用历史价格的 5% 和 95% 分位数确定,段数用 10~20 段,这样每一段的宽度是自然形成的,不用硬凑。
  2. 优先在线性化误差大的区域加密。误差主要集中在价格偏离参考价远的区域,因为那里割线和原函数分离最厉害。
  3. 给线性化加校验。每次改完网格,拿几个已知价格点回代,看模型算出的需求和你手算的差多少,超过 2% 就回去加密。

4. 实操:从场景生成到求解的完整流水线

4.1 数据准备与场景生成

数据这块,最少要有三样:批发侧的历史价格序列、用户侧的历史用电量序列、用户的价量变动记录(用来估弹性)。时间跨度上,我一般要求至少 24 个月,因为要覆盖一个完整的季节周期;如果只有 12 个月,做出来的场景会严重低估冬季或夏季的尾部。

场景生成我用的是“边缘分布 + 相关性”的两步法。先分别对价格和负荷做边缘分布拟合,价格用对数正态或者带跳的 AR(1),负荷用 AR(1) 加温度回归残差。然后关键一步:用 Copula 把两者的相关性接起来。这一步不能省,因为价格高的时候往往也是负荷高的时候(都是极端天气驱动的),忽略相关性会严重低估尾部风险。

场景缩减我用快速前向选择法,从 500 个原始场景缩减到 20~30 个代表性场景。缩减的判据用 Kantorovich 距离,实现起来就是反复挑“离已有场景集合最远的那个场景加进来”。20 个场景对于 MILP 求解是比较舒服的规模,50 个以上就要开始考虑分解算法了。

场景概率不要用等概率。我一般会根据近 6 个月的实际情况做一次贝叶斯更新,把近期出现的价格水平略微加权,避免模型完全被两年前的极端行情主导。

4.2 代码骨架

下面是我常用的骨架,用 Pyomo 写,求解器用开源的 CBC 或者商用求解器都行。代码不长,但每个部分都对应前面讲的一个决策。

import numpy as np import pyomo.environ as pyo # ---------- 1. 场景数据(示意,实际从场景生成模块读入) ---------- n_scen = 20 p_grid = np.linspace(0.35, 0.75, 21) # 价格网格,元/kWh eps = 0.6 # 弹性系数,需标定 p0, d0 = 0.55, 100.0 # 参考价与参考电量(MWh/日) scen_price = np.random.uniform(0.30, 0.65, n_scen) # 现货价 scen_base = np.random.uniform(0.80, 1.20, n_scen) # 负荷倍率 scen_prob = np.ones(n_scen) / n_scen def demand_at(p, base): return base * d0 * (p / p0) ** (-eps) # 预计算每个场景、每个网格点的需求与收入 Dem = {(s, k): demand_at(p_grid[k], scen_base[s]) for s in range(n_scen) for k in range(len(p_grid))} Rev = {(s, k): p_grid[k] * Dem[(s, k)] for s in range(n_scen) for k in range(len(p_grid))} # ---------- 2. 模型 ---------- m = pyo.ConcreteModel() S = range(n_scen) K = range(len(p_grid)) m.lam = pyo.Var(S, K, within=pyo.NonNegativeReals) # 分段线性权重 m.q = pyo.Var(S, within=pyo.NonNegativeReals) # 现货购电量 m.dp = pyo.Var(S, within=pyo.NonNegativeReals) # 正偏差 m.dn = pyo.Var(S, within=pyo.NonNegativeReals) # 负偏差 m.eta = pyo.Var(within=pyo.Reals) # VaR 辅助变量 m.u = pyo.Var(S, within=pyo.NonNegativeReals) # 尾部损失辅助变量 beta, alpha = 0.5, 0.90 c_spot = 0.02 # 现货额外交易成本,元/kWh c_pen = 0.15 # 偏差惩罚,元/kWh hedge = 0.70 # 中长期覆盖率 def profit(s): revenue = sum(Rev[(s, k)] * m.lam[s, k] for k in K) cost = (scen_price[s] + c_spot) * m.q[s] + c_pen * (m.dp[s] + m.dn[s]) return revenue - cost m.profit = pyo.Expression(S, rule=lambda m, s: profit(s)) # 电量平衡:现货 + 合约 = 需求 + 正偏差 - 负偏差 m.balance = pyo.Constraint(S, rule=lambda m, s: m.q[s] + hedge * sum(Dem[(s, k)] * m.lam[s, k] for k in K) == sum(Dem[(s, k)] * m.lam[s, k] for k in K) + m.dp[s] - m.dn[s]) # 分段线性:权重和为 1,并限制相邻两点(SOS2 的线性化写法) m.sum_lam = pyo.Constraint(S, rule=lambda m, s: sum(m.lam[s, k] for k in K) == 1) # CVaR 线性化:u_s >= eta - profit_s m.tail = pyo.Constraint(S, rule=lambda m, s: m.u[s] >= m.eta - m.profit[s]) m.obj = pyo.Objective(expr= (1 - beta) * sum(scen_prob[s] * m.profit[s] for s in S) + beta * (m.eta - sum(scen_prob[s] * m.u[s] for s in S) / (1 - alpha)), sense=pyo.maximize) solver = pyo.SolverFactory('cbc') solver.options['ratioGap'] = 0.005 solver.solve(m, tee=False) # 反解最优价格:用非零 lam 对应的网格点加权 for s in range(n_scen): w = [pyo.value(m.lam[s, k]) for k in K] price = sum(w[k] * p_grid[k] for k in K) print(f"场景 {s}: 价格 {price:.4f}, 利润 {pyo.value(m.profit[s]):.2f}")

这段代码有几个地方需要按你的实际情况改。一是需求函数的形式,如果你的用户以工商业为主,用线性需求 $D = a - bp$ 可能更贴合;二是合约覆盖率的处理,我这里简化成固定比例,实际应该做成变量并加成本项;三是 SOS2 约束我没显式写,因为很多求解器对 SOS2 支持不一致,实践中更稳的做法是加一个“相邻性”的线性约束,或者干脆接受凸组合的近似(误差可控)。

注意:m.eta声明为Reals而不是NonNegativeReals,因为 VaR 可能为负(最差场景下亏损)。这个细节不注意,模型会在亏损场景集中的时候给出错误结果。

4.3 参数标定与回测口径

参数标定这块,我把最关键的三个参数的标定方法列出来。

弹性 $\varepsilon$:用价量面板数据做回归,但要处理内生性。简单的最小二乘会把弹性估得偏小,因为价格和需求同时受季节性影响。我的做法是加时间固定效应,或者用竞品价格作为工具变量。如果数据量实在不够(很多中小交易商确实没有),就退一步用业务侧的判断:做个内部小调研,“如果电价涨 5%,你觉得会走多少用户”,把答案折算成弹性,再打个保守折扣(把弹性估大 20%),因为低估弹性的代价比高估更大。

批发价格波动率与跳跃强度:波动率直接从历史对数收益率算,跳跃强度用阈值法识别(超过 3 倍标准差算跳跃),分别拟合。这里要注意剔除掉那些一次性的极端事件,否则跳跃强度会被严重高估,导致模型过度保守。

CVaR 的 $\alpha$ 与 $\beta$:$\alpha$ 由业务的风险容忍度定,我一般从 0.9 起步;$\beta$ 不能拍脑袋,方法是做敏感性分析,把 $\beta$ 从 0 到 1 扫一遍,看价格和期望利润怎么变,然后和业务负责人一起在曲线上选点。这个过程本身就是最好的沟通工具,比讲半天数学管用得多。

回测的口径要提前定死,不然很容易自欺欺人。我用的是滚动窗口样本外回测:用前 18 个月的数据标定,预测第 19 个月;然后窗口滑动一个月,重复。对比三个基准:

基准定义作用
成本加成法批发均价 × (1 + 固定毛利率)行业最常用的土办法,必须打赢它
纯期望最大化$\beta=0$ 的模型衡量风险管理的价值
去年同期实际价格历史价格直接沿用衡量“什么都不做”的水平

评价指标不只看总利润,还要看利润的波动率、最大单月亏损、以及用户留存率。我见过一个方案总利润比基准高 8%,但最大单月亏损是基准的三倍,这种方案在业务上是不可接受的。

4.4 结果怎么读:什么时候该涨、什么时候该让

模型跑出来之后,最有价值的不是那个最终价格,而是价格对各个参数的敏感度。我总结了几条读结果的规律,都是实际跑出来反复验证过的。

当中长期合约可得比例上升时,模型给出的价格会下降。原因很直接:合约锁住了成本,风险敞口变小,模型敢用低价换量。这个信号在业务上对应的是“赶紧去谈长约”。

当批发价格的场景离散度变大(也就是波动加剧)时,价格会上升,而且上升幅度通常超过成本上升幅度。多出来的那部分是风险溢价。这时候如果竞品没跟着涨,你会很难受,正确的动作不是跟着压价,而是缩紧价格承诺期限,把风险敞口的时间长度降下来。

当弹性估计值变大时,价格会下降。这个直觉上有点反,很多人以为弹性大就该涨价(因为用户对价格不敏感)。恰恰相反,弹性大意味着降价能带来更多增量,所以最优价格更低。这里最容易搞反,务必注意。

当风险厌恶权重 $\beta$ 上升时,价格通常上升,但上升的路径不是单调的,中间会有平台期。平台期的含义是:在这个区间内,期望利润的损失很小但风险下降很快,是性价比最高的区间。我一般推荐业务方在平台区间的右端点附近选点。


5. 常见问题与排查技巧实录

5.1 求解异常:先查哪三件事

模型跑不出来或者跑出来明显不对,按这个顺序查,能解决八成问题。

第一,查约束是否冲突。最常见的是价格上下限和需求函数打架:价格下限对应的需求乘以合约比例,超过了合约可得上限,导致电量平衡无解。排查方法很简单,把目标函数换成常数 0,只求可行解,如果不可行,就是约束冲突。

第二,查场景概率和符号。CVaR 项的符号错误、$u_s$ 约束方向错误、$\eta$ 定义域错误,这三个是最常见的。快速验证方法:把 $\beta$ 设成 0,模型应该退化成纯期望最大化;再把 $\beta$ 设成 1,价格应该显著变保守。如果两者结果一样,说明 CVaR 项没起作用,八成是符号或约束写错了。

第三,查分段线性化的网格。如果最优解总是落在网格的端点上(价格贴到 $p^{\min}$ 或 $p^{\max}$),说明网格范围设窄了,真实最优在范围外。把范围放宽再跑一遍。

5.2 价格贴边:诊断树

价格贴边是最常见也最让人沮丧的问题。我整理了一个诊断树。

现象最可能的原因验证方法修正
价格贴到下限弹性估计过小,模型认为降价能无限增量把弹性调大 50% 看价格是否上移重新标定弹性,加留存约束
价格贴到下限批发价场景整体偏高,成本压力大看场景均值与近期实际价格检查场景生成是否有偏
价格贴到上限弹性估计过大,模型认为涨价不丢量把弹性调小 50% 看是否下移同上
价格贴到上限风险厌恶权重过高把 β 设为 0 重跑下调 β 或调整 α
价格贴到上限偏差惩罚系数过大检查 c_pen 与市场规则是否一致校准惩罚系数
价格贴边但利润也低模型有 bug,多半是 CVaR 符号错误检查 u 约束方向修正符号

我特别想说的是最后一行。价格贴边加上利润异常高或异常低,几乎一定是模型 bug,而不是“市场就是这样”。这种情况下不要试图通过调参数去“修”它,老老实实回去读约束。

5.3 上线之后才会遇到的坑

模型在回测里表现良好,上线后出问题,我遇到过几次,原因都很有代表性。

坑一:竞品反应没进模型。模型假设需求只对自己的价格敏感,但现实中竞品会跟着调价,你降价它也降,最后双方都没抢到量还都亏了钱。这个博弈结构模型里没有,除非你做 Stackelberg 博弈建模,但那样复杂度会暴涨。我的折中办法是在弹性上加一个“竞争强度系数”,从历史数据里估:自己降价 1% 时,竞品平均跟降多少,把净增量折算进去。

坑二:用户结构漂移。模型是用去年的用户组合标定的,今年高弹性用户占比上升了,但模型不知道,还是按老弹性定价,结果一涨价跑掉一大片。解决办法是每个月重新估一次弹性,并且按用户分群分别估,不要用一个全局平均。

坑三:结算规则变更。偏差考核的阶梯变了、结算周期变了,模型里的参数没跟着改。这类坑最冤,但发生频率不低。我的做法是把所有和市场规则相关的参数抽到一个配置文件里,每次规则变动时强制过一遍。

坑四:价格的心理阈值没建模。用户对价格的感知不是连续的。0.58 和 0.59 在模型里只差 1.7%,但在用户眼里可能跨过了一个“整数关口”。我在模型里加了一个“心理价位惩罚项”:价格落在某些特定数值附近时,需求额外打折。具体哪些数值,靠业务经验和 A/B 测试确定,不靠数据挖掘。

坑五:模型输出太复杂,业务方不用。这是最致命的。你给业务方一份 20 页的敏感度分析,他最后还是拍脑袋定价。解决方法是把模型包装成一个简单的工具:输入几个业务参数(目标利润率、风险偏好档位、竞品价格),输出 3 个候选价格方案和各自的预期结果,让他三选一。模型的价值在于把取舍讲清楚,不在于给出唯一答案。


6. 我在实际项目里踩出来的几条经验

做这类模型,技术上的难点其实都能克服,真正难的是让模型和业务节奏对上。我分享几条不太会写在论文里、但直接影响项目成败的东西。

先做一个能跑通的粗模型,再谈精度。我第一个版本只用了 5 个场景、单一价格、线性需求、没有 CVaR,代码不到 80 行,跑出来结果粗糙,但它让业务方第一次看到“价格、销量、利润”三者的定量关系,讨论立刻从“我觉得”变成了“数据显示”。后面所有精细化都是在这个共识基础上加的。反过来做——先花两个月做精细模型再拿去汇报——大概率死在第一轮评审。

场景数不是越多越好。我做过对比,20 个场景和 200 个场景算出来的最优价格差异通常在 2% 以内,但求解时间差 50 倍以上。真正影响结果的是场景的代表性,尤其是尾部场景有没有被覆盖到。与其堆数量,不如手工补几个“历史极端情况”的场景进去,比如去年最热的那一周、前年价格最疯的那三天。

弹性是模型的命门,值得花一半的时间。我现在的项目时间分配大概是:数据清洗 20%、弹性标定 40%、模型求解 15%、结果解读和沟通 25%。很多人反过来,模型调得很漂亮,弹性随便填个数,结果全盘皆输。

留一个“人工干预”的口子。模型给的是基准价,业务侧总会有些模型不知道的信息,比如某个大客户正在谈续约、某个区域在搞招商。我在系统里留了一个手动调整系数,允许业务方在基准价上下浮动一定比例,但要求填写理由。运行一年下来,这些理由本身就是极好的模型改进输入——如果某类理由反复出现,说明模型缺了对应的变量。

最后一点,也是最容易被忽略的:把定价和采购绑在一起看。定价模型输出的不只是一个价格,还有对应的最优购电策略和合约比例。如果定价和采购是两个部门、两套 KPI,模型再准也没用——定价部门为了抢量压价,采购部门为了控风险多签合约,两边一叠加,实际利润远低于模型预期。这件事不是技术能解决的,但如果你的模型能把两个部门的决策放在同一张损益表上呈现,推动起来会容易很多。

这套东西我前后迭代了三年,从最初只能算一个静态价格,到现在能做分时定价、能出风险前沿、能和采购策略联动。如果你正准备起手做,我的建议是从最小闭环开始:一个价格、十个场景、一个弹性、一个基准对比,两周内跑出第一版结果,然后拿着它去找业务方聊。聊出来的问题,比你在电脑前想出来的问题值钱得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询