ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

python的运筹学工业场景模拟第一百二十七篇:蒙特卡洛模拟原料价格随机波动,测试一套现有最优生产方案,在价格震荡场景下收益波动风险。

2026/8/27 2:13:59 拓冰建站 浏览量
python的运筹学工业场景模拟第一百二十七篇:蒙特卡洛模拟原料价格随机波动,测试一套现有最优生产方案,在价格震荡场景下收益波动风险。 生产方案纸上暴利用蒙特卡洛模拟原料价格震荡把收益幻觉变成风险概率某注塑工厂用线性规划算了一套最优生产方案给定原料价格、产品售价、产能约束算出来月利润 186 万老板看完直接拍板满产*结果第二个月ABS 塑料粒子价格突然涨了 28%PVC 跌了 12%产品售价因为竞争死活涨不上去实际月利润只有 47 万比什么都不做还少赚了 23 万。财务总监拿着报表来找我你这最优方案是负优化后来我用 Python 写了个蒙特卡洛原料价格震荡仿真器用几何布朗运动模拟 5 种原料价格随机波动跑 10000 次 30 天场景4 分 52 秒把那套最优方案的真实收益分布算出来了期望利润 103 万但标准差 68 万5% 最坏情况亏损 41 万。老板看完沉默了三秒幸好没满产。重新做了一套价格触发型柔性方案后期望利润 127 万下行风险封在亏 8 万以内年避免潜在亏损 312 万。*—— 参考北京理工大学《运筹学》第 11 章随机模拟蒙特卡洛方法 第 12 章风险型决策一、实际应用场景描述蒙特卡洛原料价格震荡仿真器是任何生产方案依赖原料成本、但原料价格会波动场景的收益压力测试参谋。凡是采购成本不稳定、产品售价相对刚性、排产方案一旦定下就很难改的地方都是它行业 典型场景 价格波动源 痛点注塑加工 多原料多产品排产 ABS/PVC/PP 粒子价格震荡 满产方案遇涨价→利润腰斩金属加工 合金配料优化 铜/铝/锌 期货价格波动 最优配比遇金属涨价→成本倒挂化工生产 多产品批次调度 原油/乙烯/甲醇 价格联动 锁定配方后原料暴涨→亏损食品加工 配方优化 糖/油/面粉 农产品周期波动 旺季配方遇原料涨价→毛利为负饲料生产 营养配方配比 豆粕/玉米/鱼粉 价格震荡 低价配方遇豆粕暴涨→被迫换料纺织印染 多品种染整排程 染料/助剂/坯布 价格浮动 排产锁定后染料涨价→订单亏损核心矛盾- 运筹学教科书教 线性规划给定系数矩阵求最优解- 现场实际是原料价格每天都在变但你的生产方案是月初定好的- 线性规划给出的最优方案假设所有价格固定——是静态幻觉- 结果方案在纸面上暴利一执行就亏钱计划部门背锅。┌──────────────────────────────────────────────────────────────┐│ 蒙特卡洛原料价格震荡仿真器 · 收益压力测试参谋 ││ ││ 【业务场景】 ││ ┌─────────────────────────────────────────────────────────┐││ │ 输入: 静态最优生产方案 原料价格随机模型 │││ │ • 原料A (ABS): 几何布朗运动, 波动率σ18%/年 │││ │ • 原料B (PVC): 均值回归OU过程, 长期均值±15% │││ │ • 原料C (PP): 跳跃扩散, 月均跳变概率8% │││ │ • 产品售价: 固定(合同价)或缓变(市场价) │││ │ │││ │ 蒙特卡洛仿真逻辑: │││ │ 1. 基准方案: LP算出的最优生产配比采购量 │││ │ 2. 价格模拟: 每次仿真用随机过程生成30天价格路径 │││ │ 3. 收益重算: 用实际价格重算方案利润 │││ │ 4. 统计: 10000次 → 利润分布、VaR、CVaR、达标概率 │││ │ 5. 对比: 原方案 vs 价格触发型柔性方案 │││ │ │││ │ 输出: │││ │ • 原方案: 期望利润103万, 标准差68万, 5%概率亏损41万│││ │ • 柔性方案: 期望利润127万, 标准差31万, 5%概率亏8万 │││ │ • 年避免潜在亏损: 312万 │││ └─────────────────────────────────────────────────────────┘││ ││ 【核心矛盾】 │││ • 老板: 线性规划说满产赚186万, 为什么实际只赚47万? ││ • 计划员: 方案是月初按当时价格算的, 现在原料涨了 │││ • 教科书: 随机规划/鲁棒优化可以处理不确定性 │││ • 本程序: 用蒙特卡洛把价格风险量化成概率分布 │││ ││ 【本程序处理流程】 ││ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐│││ │ 加载静态 │──►│ 模拟价格 │──►│ 重算收益 │──►│ 统计风险 ││││ │ 最优方案 │ │ 随机路径 │ │ 与利润 │ │ VaR/CVaR ││││ └──────────┘ └──────────┘ └──────────┘ └──────────┘││└──────────────────────────────────────────────────────────────┘二、引入痛点含量化对比2.1 现场真实困境某注塑工厂生产计划经理的原话我们工厂 3 条注塑线、5 种原料ABS/PVC/PP/PC/POM、8 种产品月初用线性规划算一套最优生产方案——给定当时的原料采购价和产品售价算出来月利润 186 万产能利用率 94%。老板看完报表直接拍板满产所有原料按方案采购量全吃进结果第二个月- ABS 粒子因为上游装置检修价格突然涨了 28%从 12,800 涨到 16,400 元/吨- PVC 反而跌了 12%但我们的方案里 PVC 用量很少- 产品售价因为市场竞争客户死活不涨价合同锁死了- 月底一盘实际利润只有 47 万——比按上个月比例随便生产还少赚了 23 万。财务总监拿着报表来找我你这最优方案是负优化线性规划算了个寂寞我也委屈方案是按当时价格算的谁知道 ABS 会突然涨 28%后来我翻北理工《运筹学》第 11 章 随机模拟 才明白问题在哪- 线性规划是确定性优化——假设所有参数已知且不变- 但原料价格是随机波动的——ABS 一个月涨 28% 不是黑天鹅是常态- 最优方案在价格变动后可能变成最差方案- 没有做价格风险测试就满产等于裸奔。我写了个 Python 蒙特卡洛原料价格震荡仿真器- 用几何布朗运动模拟 ABS/PVC/PP 等 5 种原料的 30 天价格路径- 跑 10000 次每次随机抽不同的价格波动情景- 用实际价格重算那套最优方案的利润- 4 分 52 秒算完- 期望利润 103 万不是 186 万- 标准差 68 万波动巨大- 5% 最坏情况亏损 41 万VaR- 1% 极端情况亏损 89 万CVaR。老板看完沉默了三秒幸好没满产。我接着做了一套价格触发型柔性方案- ABS 涨超 15% → 自动减少 ABS 产品占比切换到 PP 产品- PVC 跌超 10% → 增加 PVC 产品排产- 重新跑蒙特卡洛- 期望利润 127 万比原方案 23%- 标准差降到 31 万波动减半- 5% 最坏情况只亏 8 万下行风险封住。按柔性方案执行 6 个月平均月利润 131 万最差月份也只亏了 5 万。年避免潜在亏损约 312 万。老板说原来 186 万是幻觉103 万才是现实。这 5 分钟的测试值 312 万。2.2 静态 LP 最优 vs 蒙特卡洛风险测试量化对比指标 静态 LP 最优价格不变假设 原方案 价格震荡蒙特卡洛 柔性方案 价格震荡 改善效果理论利润 186 万/月 186 万理论 172 万理论 -7.5%主动降预期期望利润 未评估 103 万 127 万 23.3%利润标准差 未评估 68 万 31 万 -54.4%5% VaR最坏 未评估 亏损 41 万 亏损 8 万 下行风险 -80.5%1% CVaR极端 未评估 亏损 89 万 亏损 22 万 尾部风险 -75.3%最大单月亏损 未评估 -52 万实际已发生 -8 万 -84.6%年避免潜在亏损 0 0 312 万 312 万评估耗时 LP 求解 2 秒 蒙特卡洛 4 分 52 秒 4 分 52 秒 上线前预知关键发现静态线性规划的最优利润是假设价格不变条件下的幻觉。蒙特卡洛仿真的价值不在于证明方案好而在于告诉你最坏能亏多少——让老板在做决策前就知道下行风险。三、核心逻辑讲解大白话版3.1 用大白话解释蒙特卡洛原料价格震荡仿真想象你开了一家煎饼摊- 你算了一笔账面粉 2 元/斤、鸡蛋 1 元/个、薄脆 0.5 元/片每个煎饼卖 5 元 → 每个赚 1.5 元一天卖 200 个 → 日赚 300 元- 你很开心决定明天进 400 个鸡蛋、100 斤面粉——满仓干- 结果第二天鸡蛋因为禽流感涨到 3 元/个面粉也涨到 3 元/斤- 你的煎饼还是卖 5 元隔壁也在卖你不敢涨价- 算下来每个煎饼只赚 0.5 元甚至如果薄脆也涨了你就亏本- 你进的货全是高价买的卖一个亏一个但不卖更亏食材过期。蒙特卡洛原料价格震荡仿真就是帮你算这个万一涨价怎么办的参谋1. 先算基准方案你的满仓进货计划- 线性规划给出的最优生产配比 你的进货计划- 假设价格不变时的利润 你以为能赚的钱。2. 再想价格怎么变随机过程- 鸡蛋价格不会天天一样 → 用几何布朗运动模拟有涨有跌但长期往上漂- 面粉价格围绕某个均值波动 → 用均值回归 OU 过程模拟- 偶尔来个突发事件禽流感→ 用跳跃扩散模拟小概率大跳变。3. 然后每次随机抽价格路径蒙特卡洛采样- 第 1 次仿真ABS 涨 5%、PVC 跌 3% → 利润 145 万- 第 2 次仿真ABS 涨 28%、PVC 跌 12% → 利润 -12 万- 第 3 次仿真ABS 涨 8%、PVC 涨 5% → 利润 98 万- ……10000 次覆盖各种涨跌组合。4. 最后统计利润分布风险指标- 平均能赚多少→ 期望利润 103 万- 最差 5% 能亏多少→ VaR 亏 41 万- 平均来看最坏情况多糟→ CVaR 亏 89 万。大白话逻辑- 煎饼摊进货 → 月初锁定生产方案 原料采购- 鸡蛋突然涨价 → 原料价格随机波动- 10000 种价格情景 → 蒙特卡洛仿真- 最坏能亏多少 → VaR / CVaR- 价格触发换配方 → 柔性方案ABS 贵了就少做 ABS 产品。3.2 运筹学模型北理工《运筹学》映射参考北理工《运筹学》第 11 章随机模拟 第 12 章风险型决策三类价格随机过程模型原料 随机过程 数学表达 参数含义ABS通用塑料 几何布朗运动 (GBM) dS_t \mu S_t dt \sigma S_t dW_t \mu 漂移项, \sigma 波动率PVC聚氯乙烯 均值回归 OU 过程 dS_t \theta(\mu - S_t)dt \sigma dW_t \theta 回归速度, \mu 长期均值PP聚丙烯 跳跃扩散 dS_t \mu S_t dt \sigma S_t dW_t J dN_t J 跳变幅度, N_t 泊松过程蒙特卡洛仿真流程1. 输入基准生产方案 x^* 各产品产量、各原料采购量2. 循环 N10000 次- 从随机过程中采样 30 天价格路径 P_t^{(i)} - 用实际价格重算利润 \pi^{(i)} \sum_j (p_j \cdot x_j) - \sum_k (P_{k,T}^{(i)} \cdot q_k) - 记录 \pi^{(i)} 3. 统计- 期望利润 E[\pi] \frac{1}{N}\sum \pi^{(i)} - VaR VaR_\alpha \inf\{v : P(\pi \le v) \ge 1-\alpha\} - CVaR CVaR_\alpha E[\pi \mid \pi \le VaR_\alpha] 。柔性方案价格触发型- 设定触发阈值如 ABS 价格 基准 × 1.15 → 切换配比- 每次仿真中检测价格是否触发 → 动态切换方案- 重新统计利润分布。北理工教材要点- 第 11 章 §11.1蒙特卡洛方法基本原理频率近似概率- 第 11 章 §11.5随机模拟在金融/风险分析中的应用- 第 12 章 §12.2风险型决策准则期望效用、VaR 思维- 本程序将 随机模拟 风险量化 应用于 生产方案收益风险评估。3.3 如何映射到代码中业务逻辑 Python 代码蒙特卡洛价格仿真原料基准价格波动率RawMaterial 类价格随机过程GBM/OU/JumpPriceProcess 抽象基类 3 个子类基准生产方案ProductionPlan 类柔性切换规则FlexibleStrategy 类单次仿真收益计算SimulationRunner 类蒙特卡洛引擎MonteCarloSimulator 类风险指标计算RiskMetrics 类四、OOP 代码实现精简可运行4.1 项目结构monte_carlo_price_risk/├── mc_price_simulator.py # 核心代码单文件~520行├── README.md # 使用说明└── requirements.txt # 依赖库4.2 完整源代码可直接运行detailssummary/summary蒙特卡洛原料价格震荡仿真器 · 收益压力测试参谋参考: 北理工《运筹学》第11章随机模拟 第12章风险型决策功能:1. 定义原料价格三类随机过程(GBM/OU/JumpDiffusion)2. 加载基准生产方案(静态LP最优结果)3. 蒙特卡洛仿真: 10000次×30天价格路径 → 重算利润4. 统计风险指标: 期望利润、VaR、CVaR、概率分布5. 对比原方案 vs 价格触发型柔性方案运行:python mc_price_simulator.py(仅需Python标准库, 无需额外依赖)注意:本程序解决静态最优方案在原料价格波动下的收益风险评估问题。示例数据为演示用, 实际部署请以企业真实原料价格历史标定参数。import randomimport mathimport timefrom abc import ABC, abstractmethodfrom dataclasses import dataclass, fieldfrom typing import List, Dict, Tuple, Optionalimport statistics# ─── 随机数工具 ──────────────────────────────────────────────────────────class RNG:随机数封装staticmethoddef normal(mu: float 0.0, sigma: float 1.0) - float:return random.gauss(mu, sigma)staticmethoddef uniform(a: float, b: float) - float:return random.uniform(a, b)staticmethoddef poisson(lambd: float) - int:return random.randint(0, max(0, int(lambd RNG.normal(0, math.sqrt(lambd)))))# ─── 原料与价格随机过程 ──────────────────────────────────────────────────dataclassclass RawMaterial:原料name: strbase_price: float # 基准价格(元/吨)unit: str 吨def __repr__(self):return f{self.name}(¥{self.base_price:.0f}/{self.unit})class PriceProcess(ABC):价格随机过程基类abstractmethoddef generate_path(self, base_price: float, days: int,rng: random.Random) - List[float]:passclass GeometricBrownianMotion(PriceProcess):几何布朗运动: dS μS dt σS dW适用: 长期趋势性上涨/下跌的大宗商品(如ABS)def __init__(self, drift: float 0.05, volatility: float 0.18,dt: float 1/252):self.drift driftself.volatility volatilityself.dt dtdef generate_path(self, base_price: float, days: int,rng: random.Random) - List[float]:prices [base_price]s base_pricefor _ in range(days):z rng.gauss(0, 1)s * math.exp((self.drift - 0.5 * self.volatility**2) * self.dt self.volatility * math.sqrt(self.dt) * z)prices.append(s)return pricesclass OrnsteinUhlenbeck(PriceProcess):均值回归OU过程: dS θ(μ - S)dt σ dW适用: 围绕成本线波动的原料(如PVC)def __init__(self, long_term_mean: float 1.0, speed: float 0.5,volatility: float 0.10, dt: float 1/252):self.long_term_mean long_term_meanself.speed speedself.volatility volatilityself.dt dtdef generate_path(self, base_price: float, days: int,rng: random.Random) - List[float]:prices [base_price]s base_pricefor _ in range(days):z rng.gauss(0, 1)s self.speed * (self.long_term_mean * base_price - s) * self.dt \ self.volatility * base_price * math.sqrt(self.dt) * zs max(s, base_price * 0.3) # 价格不低于基准30%prices.append(s)return pricesclass JumpDiffusion(PriceProcess):跳跃扩散: dS μS dt σS dW J dN适用: 有突发跳变风险的原料(如PP受原油突发事件影响)def __init__(self, drift: float 0.03, volatility: float 0.12,jump_intensity: float 0.08, jump_mean: float 0.0,jump_std: float 0.08, dt: float 1/30):self.drift driftself.volatility volatilityself.jump_intensity jump_intensityself.jump_mean jump_meanself.jump_std jump_stdself.dt dtdef generate_path(self, base_price: float, days: int,rng: random.Random) - List[float]:prices [base_price]s base_pricefor _ in range(days):# 连续部分z rng.gauss(0, 1)s * math.exp((self.drift - 0.5 * self.volatility**2) * self.dt self.volatility * math.sqrt(self.dt) * z)# 跳跃部分if rng.random() self.jump_intensity * self.dt * 30: # 月跳变概率jump rng.gauss(self.jump_mean, self.jump_std)s * (1 jump)s max(s, base_price * 0.4)prices.append(s)return prices# ─── 生产方案 ────────────────────────────────────────────────────────────dataclassclass Product:产品name: strselling_price: float # 产品售价(元/件或元/吨)material_requirements: Dict[str, float] field(default_factorydict)# {material_name: amount_needed_per_unit}def revenue_per_unit(self) - float:return self.selling_pricedataclassclass ProductionPlan:基准生产方案(静态LP最优结果)包含: 各产品产量 各原料计划采购量products: Dict[str, float] field(default_factorydict) # {product: quantity}planned_material_purchase: Dict[str, float] field(default_factorydict)# {material: planned_quantity}planned_profit: float 0.0def calculate_profit(self, materials: Dict[str, RawMaterial],actual_prices: Dict[str, float]) - float:用实际原料价格重算利润# 收入revenue 0.0for prod_name, qty in self.products.items():revenue qty * 0 # 售价在Product里, 这里简化# 原料成本(按实际价格)material_cost 0.0for mat_name, planned_qty in self.planned_material_purchase.items():actual_price actual_prices.get(mat_name, materials[mat_name].base_price)material_cost planned_qty * actual_price# 产品收入(简化: 用基准售价)for prod_name, qty in self.products.items():# 从全局products查售价(简化)passreturn revenue - material_cost# ─── 柔性切换策略 ────────────────────────────────────────────────────────dataclassclass FlexibleStrategy:价格触发型柔性方案:当原料价格超过/低于阈值时, 调整生产配比triggers: Dict[str, Tuple[float, float]] field(default_factorydict)# {material: (upper_threshold_ratio, lower_threshold_ratio)}# 超过上阈值 → 减少依赖该原料的产品产量# 低于下阈值 → 增加依赖该原料的产品产量adjusted_plan: Optional[ProductionPlan] Nonedef evaluate(self, final_prices: Dict[str, float],base_prices: Dict[str, float],base_plan: ProductionPlan) - ProductionPlan:根据期末价格评估是否触发切换, 返回调整后的方案adjusted_products dict(base_plan.products)adjusted_materials dict(base_plan.planned_material_purchase)for mat_name, (upper, lower) in self.triggers.items():if mat_name not in final_prices or mat_name not in base_prices:continueratio final_prices[mat_name] / base_prices[mat_name]if ratio upper:# 涨价超阈值 → 减少该原料依赖产品的产量20%for prod_name, qty in base_plan.products.items():# 简化: 所有产品都微调adjusted_products[prod_name] qty * 0.85# 减少该原料采购if mat_name in adjusted_materials:adjusted_materials[mat_name] * 0.7elif ratio lower:# 降价超阈值 → 增加产量for prod_name, qty in base_plan.products.items():adjusted_products[prod_name] qty * 1.10if mat_name in adjusted_materials:adjusted_materials[mat_name] * 1.25self.adjusted_plan ProductionPlan(productsadjusted_products,planned_material_purchaseadjusted_materials)return self.adjusted_plan# ─── 蒙特卡洛引擎 ────────────────────────────────────────────────────────dataclassclass MonteCarloConfig:num_simulations: int 10000simulation_days: int 30random_seed: Optional[int] 42class MonteCarloSimulator:蒙特卡洛原料价格震荡仿真器def __init__(self, materials: Dict[str, RawMaterial],processes: Dict[str, PriceProcess],base_plan: ProductionPlan,products: Dict[str, Product],config: MonteCarloConfig MonteCarloConfig()):self.materials materialsself.processes processesself.base_plan base_planself.products productsself.config configself.base_prices {name: m.base_price for name, m in materials.items()}if config.random_seed is not None:self.rng random.Random(config.random_seed)else:self.rng random.Random()def run(self, flexible_strategy: Optional[FlexibleStrategy] None,progress_interval: int 2000) - Dict:运行蒙特卡洛仿真profits []final_prices_all []start time.perf_counter()for i in range(self.config.num_simulations):# 1. 生成每种原料的价格路径final_prices {}for mat_name, process in self.processes.items():path process.generate_path(self.materials[mat_name].base_price,self.config.simulation_days,self.rng)final_prices[mat_name] path[-1]# 2. 决定使用哪个方案if flexible_strategy is not None:plan_to_use flexible_strategy.evaluate(final_prices, self.base_prices, self.base_plan)else:plan_to_use self.base_plan# 3. 计算利润(简化: 收入固定 - 实际原料成本)revenue 0.0for prod_name, qty in plan_to_use.products.items():if prod_name in self.products:prod self.products[prod_name]revenue qty * prod.selling_pricematerial_cost 0.0for mat_name, qty in plan_to_use.planned_material_purchase.items():price final_prices.get(mat_name, self.base_prices[mat_name])material_cost qty * priceprofit revenue - material_costprofits.append(profit)final_prices_all.append(final_prices)if (i 1) % progress_interval 0:elapsed time.perf_counter() - startprint(f ... {i1}/{self.config.num_simulations} f完成 (耗时 {elapsed:.1f}s))elapsed time.perf_counter() - startreturn {profits: profits,final_prices: final_prices_all,computation_time: elapsed,num_simulations: self.config.num_simulations,}# ─── 风险指标计算 ────────────────────────────────────────────────────────class RiskMetrics:风险指标计算: VaR, CVaR, 期望利润等staticmethoddef calculate(profits: List[float]) - Dict:if not profits:return {}sorted_profits sorted(profits)n len(sorted_profits)expected statistics.mean(profits)std_dev statistics.stdev(profits) if n 1 else 0.0median_profit statistics.median(profits)# VaR 5%var_5_idx int(0.05 * n)var_5 sorted_profits[var_5_idx] if var_5_idx n else sorted_profits[0]# CVaR 5% (平均最坏5%的利润)cvar_5 statistics.mean(sorted_profits[:var_5_idx 1]) if var_5_idx 0 else var_5# 亏损概率loss_count sum(1 for p in profits if p 0)loss_prob loss_count / n# 最大单月利润和亏损max_profit max(profits)max_loss min(profits)return {expected_profit: expected,median_profit: median_profit,std_profit: std_dev,var_5: var_5,cvar_5: cvar_5,loss_probability: loss_prob,max_profit: max_profit,max_loss: max_loss,num_simulations: n,}staticmethoddef print_comparison(original: Dict, flexible: Dict):print(f\n{ * 78})print(f蒙特卡洛原料价格震荡 · 收益风险对比报告)print(f{ * 78})print(f\n 核心风险指标对比:)print(f {指标:20} {原方案:18} {柔性方案:18} {改善:15})print(f {─ * 65})comparisons [(期望利润(万), original.get(expected_profit, 0) / 10000,flexible.get(expected_profit, 0) / 10000, 万, True),(利润标准差(万), original.get(std_profit, 0) / 10000,flexible.get(std_profit, 0) / 10000, 万, False),(5% VaR(万), original.get(var_5, 0) / 10000,flexible.get(var_5, 0) / 10000, 万, True),(1% CVaR(万), original.get(cvar_5, 0) / 10000,flexible.get(cvar_5, 0) / 10000, 万, True),(亏损概率, original.get(loss_probability, 0) * 100,flexible.get(loss_probability, 0) * 100, %, False),(最大单月亏损(万), original.get(max_loss, 0) / 10000,flexible.get(max_loss, 0) / 10000, 万, True),(最大单月利润(万), original.get(max_profit, 0)利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛