交易商最优定价:两阶段随机规划、CVaR与需求弹性
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$ 处的电量$\varepsilon0$ 是弹性系数。这个形式的好处是参数只有一个标定简单而且经济含义清晰$\varepsilon0.6$ 表示价格涨 1%需求降 0.6%。坏处是它让目标函数变成非线性。$p \cdot D_s(p) p^{1-\varepsilon} \cdot \text{const}$当 $\varepsilon1$ 时是凹增函数当 $\varepsilon1$ 时是凸的。这就导致一个问题如果你直接把 $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)$ 在 $\varepsilon1$ 时是凹的凸组合会低估收入。两个偏差方向相反、部分抵消但不会完全抵消。我的经验是如果弹性小于 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]$ 是风险厌恶权重$\beta0$ 就是纯期望最大化$\beta1$ 就是纯尾部保护。$\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 元你可以做成平段价、峰谷两档、三段分时、带基本电费的两部制、或者带阶梯电量的分档价。这几种结构在模型里的复杂度差别很大。平段价最简单一个变量适合做基线对比。两段分时价格是两个变量加一个约束峰段不低于谷段某比例规模和求解难度增加不多但在用户侧的接受度通常更好因为它给了用户一个“省钱的动作”。三段以上就要小心了用户侧的响应行为会变得很不线性你的弹性模型得跟着细化否则算出来的量价关系是假的。两部制基本电费 电量电价在工商业场景很常见它的好处是能把固定成本前置、降低电量电价的感知提升价格竞争力。但它在模型里会引入“用户会不会为了避开基本电费而降低申报容量”的额外决策这块要单独建模不能简单套用单一弹性。分段线性化的通用技巧我总结成三条先确定网格范围再定段数。范围用历史价格的 5% 和 95% 分位数确定段数用 10~20 段这样每一段的宽度是自然形成的不用硬凑。优先在线性化误差大的区域加密。误差主要集中在价格偏离参考价远的区域因为那里割线和原函数分离最厉害。给线性化加校验。每次改完网格拿几个已知价格点回代看模型算出的需求和你手算的差多少超过 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, withinpyo.NonNegativeReals) # 分段线性权重 m.q pyo.Var(S, withinpyo.NonNegativeReals) # 现货购电量 m.dp pyo.Var(S, withinpyo.NonNegativeReals) # 正偏差 m.dn pyo.Var(S, withinpyo.NonNegativeReals) # 负偏差 m.eta pyo.Var(withinpyo.Reals) # VaR 辅助变量 m.u pyo.Var(S, withinpyo.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, rulelambda m, s: profit(s)) # 电量平衡现货 合约 需求 正偏差 - 负偏差 m.balance pyo.Constraint(S, rulelambda 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, rulelambda m, s: sum(m.lam[s, k] for k in K) 1) # CVaR 线性化u_s eta - profit_s m.tail pyo.Constraint(S, rulelambda 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)), sensepyo.maximize) solver pyo.SolverFactory(cbc) solver.options[ratioGap] 0.005 solver.solve(m, teeFalse) # 反解最优价格用非零 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 固定毛利率)行业最常用的土办法必须打赢它纯期望最大化$\beta0$ 的模型衡量风险管理的价值去年同期实际价格历史价格直接沿用衡量“什么都不做”的水平评价指标不只看总利润还要看利润的波动率、最大单月亏损、以及用户留存率。我见过一个方案总利润比基准高 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模型再准也没用——定价部门为了抢量压价采购部门为了控风险多签合约两边一叠加实际利润远低于模型预期。这件事不是技术能解决的但如果你的模型能把两个部门的决策放在同一张损益表上呈现推动起来会容易很多。这套东西我前后迭代了三年从最初只能算一个静态价格到现在能做分时定价、能出风险前沿、能和采购策略联动。如果你正准备起手做我的建议是从最小闭环开始一个价格、十个场景、一个弹性、一个基准对比两周内跑出第一版结果然后拿着它去找业务方聊。聊出来的问题比你在电脑前想出来的问题值钱得多。