
简介这份资源面向供应链管理领域的研究人员、高校物流管理专业师生及双渠道销售企业管理者聚焦消费者跨渠道退货场景下的双渠道闭环供应链决策难题。资源以Stackelberg博弈和Nash均衡为理论框架系统对比集中式与分散式决策下的最优定价策略与利润分配并探讨跨渠道退货率与消费者渠道偏好对供应链决策的影响。压缩包内为1个PDF文件约624KB完整呈现模型假设、需求函数构建、集中式与分散式求解及结果分析等核心内容并附有可运行的Python代码及逐段解释便于读者复现研究结论。已有41人学习下载。读者可借此掌握不同权力结构下的定价规律理解退货率与定价之间的非线性关系并获取收益共享契约设计与渠道协调机制的实践参考适合作为论文复现与数据分析的实操素材。1. 跨渠道退货下的双渠道闭环供应链一份能跑通博弈模型的 Python 代码包线上买、线下退或者线下试、线上买——这种跨渠道退货行为正在把传统的双渠道定价模型搅成一团乱麻。很多做供应链优化的朋友拿到论文里的均衡解推导第一步就卡在“零售商反应函数怎么代回制造商利润”上更别提用代码把 Stackelberg 博弈和 Nash 均衡跑出数值结果。这份资源就是冲着这个痛点来的它把一篇《考虑跨渠道退货的双渠道闭环供应链决策研究》的完整复现过程拆成了可运行的 Python 脚本从需求函数、退货修正、集中式联合定价到制造商主导的 Stackelberg 博弈求解再到海塞矩阵凹性验证和敏感性分析全部有代码、有注释、有输出。适合供应链管理方向的研究生、做定价策略的运筹优化工程师以及需要快速验证渠道协调机制的产品运营。如果你正在找一份能直接改参数、看结果、验证命题的代码底稿这份东西省掉的是从公式到可执行脚本之间那层最磨人的翻译工作。2. 模型拆解需求函数、退货修正与利润构成的代码映射2.1 参数体系与需求函数的向量化写法拿到一份供应链博弈代码第一件事不是跑minimize而是把参数表和需求函数对齐。原模型里市场总需求alpha1000、消费者线下偏好theta0.6、自身价格弹性beta1.2、交叉价格弹性gamma0.8这四个数决定了两个渠道的需求基数。需求函数本身是线性的import numpy as np from scipy.optimize import minimize # 基础参数 alpha 1000 # 市场总需求 theta 0.6 # 消费者对实体店的偏好比例 beta 1.2 # 需求价格弹性自身渠道 gamma 0.8 # 需求交叉价格弹性 c_m 20 # 制造商生产成本 c_r 10 # 零售商运营成本 def demand(p_m, p_r, alpha, theta, beta, gamma): D_r alpha * theta - beta * p_r gamma * p_m D_m alpha * (1 - theta) - beta * p_m gamma * p_r return D_r, D_m这里有个容易翻车的点beta和gamma的数值关系决定了两个渠道是替代还是互补。当beta gamma时自身价格效应大于交叉效应需求函数才符合正常商品逻辑。原代码里beta1.2、gamma0.8满足这个条件所以后续优化不会出现价格越高需求越大的反直觉结果。如果你把gamma调到大于betaminimize会给你一个负价格或者直接不收敛这不是代码 bug是模型设定出了问题。2.2 退货修正从毛需求到净销售量的转换逻辑跨渠道退货的核心麻烦在于需求函数算出来的是“卖出去了多少”但利润要按“最终留下多少”来算。原模型用了一个除法修正mu 0.3 # 线下→线下退货率 phi 0.2 # 线上→线上退货率 epsilon 0.1 # 线上→线下交叉退货率 def actual_sales(D_r, D_m): Q_m D_m / (1 - phi - epsilon) Q_r D_r / (1 - mu) assert mu * D_r epsilon * D_m Q_r, 退货量超过实体店承受能力 return Q_r, Q_mQ_m D_m / (1 - phi - epsilon)这个式子的含义是线上渠道最终留下的净销量是D_m但为了达到这个净销量实际需要发出的货量要除以留存率。phi epsilon是线上渠道的总退货比例包括退回线上和跨渠道退到线下两部分。assert那行是原代码里少有的防御性编程检查线下渠道收到的退货量不能超过它的实际处理能力。我一般会把这个断言改成if加raise ValueError因为assert在python -O模式下会被跳过生产环境里容易埋雷。2.3 集中式决策的利润函数与边界约束集中式决策是把制造商和零售商当成一个整体来优化决策变量只有两个价格线上直销价p_m和线下零售价p_r。利润函数要把退货残值和处理成本都算进去delta 15 # 退货残值 h_r 5 # 线下退货处理成本 h_m 4 # 线上退货处理成本 h_mr 6 # 交叉退货处理成本 def centralized_profit_with_returns(p): p_m, p_r p D_r, D_m demand(p_m, p_r, alpha, theta, beta, gamma) Q_r, Q_m actual_sales(D_r, D_m) revenue_m (p_m - c_m) * D_m return_m (delta - h_m) * phi * Q_m (delta - h_mr) * epsilon * Q_m revenue_r (p_r - c_m) * D_r return_r (delta - h_r) * mu * Q_r total_profit revenue_m revenue_r return_m return_r return -total_profit result minimize( centralized_profit_with_returns, [50, 60], bounds[(0, None), (0, None)] ) optimal_p_m, optimal_p_r result.x print(f集中式最优定价: 线上{optimal_p_m:.2f}, 线下{optimal_p_r:.2f})bounds[(0, None), (0, None)]是必须加的否则minimize可能试探负价格导致需求函数算出超过市场总需求的荒谬值。初始猜测值[50, 60]不是随便写的它大致对应成本加成定价的直觉范围能让求解器更快收敛到有经济意义的局部最优。如果你换成[500, 600]大概率会跑到边界上去因为高价区间的利润函数斜率已经变了。3. 分散式决策Stackelberg 博弈的代码实现与求解顺序3.1 零售商反应函数的解析解与数值解对照分散式决策下制造商先动定批发价omega和直销价p_m零售商观察到这两个值后再优化自己的零售价p_r。原代码给了两种写法一种是数值优化minimize一种是解析解直接代入。先看数值版def retailer_profit(p_r, p_m, omega): D_r, _ demand(p_m, p_r, alpha, theta, beta, gamma) net_D_r D_r * (1 - r_r) return -(p_r - omega - c_r) * net_D_r def manufacturer_profit(p): p_m, omega p res minimize( retailer_profit, x060, args(p_m, omega), bounds[(0, None)] ) optimal_p_r res.x[0] D_r, D_m demand(p_m, optimal_p_r, alpha, theta, beta, gamma) net_D_m D_m * (1 - r_m) profit (omega - c_m) * D_r * (1 - r_r) (p_m - c_m) * net_D_m return -profit result minimize(manufacturer_profit, [50, 40], bounds[(0, None), (0, None)]) optimal_p_m, optimal_omega result.x这段代码的求解顺序是“嵌套优化”外层minimize每试探一组(p_m, omega)内层就要跑一次零售商的minimize。计算量是外层迭代次数乘以内层迭代次数参数多的时候会明显变慢。原代码里x060是零售价的初始猜测这个值如果离真实最优太远内层求解可能不收敛外层就会拿到一个噪声很大的利润值导致整个 Stackelberg 均衡解漂移。3.2 解析解代入从反应函数到制造商目标函数更稳的做法是用解析解。零售商利润对p_r求一阶条件可以得到def retailer_reaction(p_m, omega): numerator alpha * theta gamma * p_m beta * omega / 2 - beta * O gamma * M p_r_opt numerator / beta return p_r_opt H ((delta - h_m) * phi (delta - omega_tilde - b) * epsilon) / (1 - phi - epsilon) G (delta - omega_tilde) * mu / (1 - mu) O (omega_tilde - h_r) * mu / (1 - mu) M (omega_tilde - h_mr b) * epsilon / (1 - phi - epsilon)这里H、G、O、M是退货相关参数的聚合项原代码把它们单独拎出来是为了让反应函数的表达式更短。omega_tilde25是退货转移价格b8是制造商对零售商的跨渠道补贴。这两个参数在解析解里直接进入分子说明退货转移价格越高零售商越有动力抬高零售价——因为它从退货处理中也能分到更多收益。把retailer_reaction代回制造商利润函数后外层优化就变成了一维搜索速度快很多def manufacturer_profit_analytical(p): p_m, omega p p_r retailer_reaction(p_m, omega) D_r, D_m demand(p_m, p_r, alpha, theta, beta, gamma) Q_r, Q_m actual_sales(D_r, D_m) direct_sale (p_m - c_m) * D_m wholesale (omega - c_m) * D_r online_returns (delta - h_m) * phi * Q_m cross_returns (delta - omega_tilde) * (mu * Q_r epsilon * Q_m) subsidy_cost -b * epsilon * Q_m total_profit direct_sale wholesale online_returns cross_returns subsidy_cost return -total_profit result minimize(manufacturer_profit_analytical, [50, 40], bounds[(0, None), (0, None)]) p_m_opt, omega_opt result.x p_r_opt retailer_reaction(p_m_opt, omega_opt) print(fStackelberg均衡: p_m*{p_m_opt:.2f}, ω*{omega_opt:.2f}, p_r*{p_r_opt:.2f})3.3 海塞矩阵验证用 SymPy 确认凹性条件数值解出来之后怎么知道它真的是最大值而不是鞍点原代码用 SymPy 算海塞矩阵from sympy import symbols, diff, Matrix, simplify p_m, omega symbols(p_m omega) p_r (alpha * theta gamma * p_m beta * omega / 2 - beta * O gamma * M) / beta D_m alpha * (1 - theta) - beta * p_m gamma * p_r D_r alpha * theta - beta * p_r gamma * p_m pi_m (p_m - c_m H) * D_m (omega - c_m G) * D_r Hessian Matrix([ [diff(pi_m, p_m, p_m), diff(pi_m, p_m, omega)], [diff(pi_m, omega, p_m), diff(pi_m, omega, omega)] ]) print(海塞矩阵行列式:, simplify(Hessian.det())) print(二阶导数:, [simplify(Hessian[0, 0]), simplify(Hessian[1, 1])])判断标准是行列式大于零且两个主对角线元素都小于零。如果行列式算出来是负的说明利润函数在均衡点附近不是凹的数值解可能只是局部最优。我一般会把这个验证步骤单独写成一个check_concavity()函数每次改完参数就跑一遍避免拿着一个不稳定的均衡解去做敏感性分析。4. 避坑与排查跑通这份代码常见的五个翻车点4.1 退货率参数导致除零或负留存率现象运行actual_sales时抛出ZeroDivisionError或者Q_m算出负数。原因phi epsilon被设成大于等于 1比如phi0.6、epsilon0.5留存率1 - phi - epsilon变成负数。现实中退货率不可能超过 100%但调参时容易手滑。解决在参数定义后加一行校验assert phi epsilon 1 and mu 1把非法组合挡在求解之前。如果确实需要研究高退货率场景把epsilon上限设在 0.4 左右给留存率留出正数空间。4.2 嵌套优化不收敛导致均衡解漂移现象每次运行manufacturer_profit得到的optimal_p_m和optimal_omega都不一样波动范围超过 5%。原因内层minimize的x060离真实零售价太远或者bounds设得太窄求解器在边界上反复试探。外层拿到的利润值带有内层求解误差优化方向就被带偏了。解决优先用解析解retailer_reaction替代内层数值优化。如果必须用数值版把x0改成上一轮外层迭代得到的p_r做热启动同时把内层tol调到1e-8减少噪声。4.3 海塞矩阵行列式符号与预期相反现象Hessian.det()输出负值但论文里说利润函数是凹的。原因beta和gamma的数值关系不满足凹性条件。原模型要求beta gamma如果调参时把gamma设成 1.5、beta保持 1.2交叉价格效应压过自身价格效应利润函数就变成凸的均衡解从最大值变成最小值。解决检查beta和gamma的大小关系确保beta gamma。如果研究场景确实需要gamma beta那就要换模型设定不能硬套这份代码的凹性验证逻辑。4.4 退货补贴b的符号搞反现象制造商利润随b增大而增大和直觉相反。原因原代码里subsidy_cost -b * epsilon * Q_mb是制造商付给零售商的补贴所以是成本项。如果误写成b * epsilon * Q_m补贴就变成了收入利润函数的经济含义完全变了。解决在代码注释里明确标注每个利润项的符号方向。我习惯在参数定义区加一行# b: 制造商→零售商补贴正数表示制造商支出避免后续改代码时搞混。4.5 敏感性分析循环里参数污染现象跑epsilon_range循环时第一轮结果正常后面几轮价格突然跳变。原因循环里直接改了全局变量epsilon但H、M、O这些聚合项是在循环外算好的没有跟着更新。制造商利润函数里用的还是旧的H和M导致定价决策和实际退货率不匹配。解决把H、M、O的计算封装成函数每次循环重新算一遍def compute_aggregates(epsilon, mu, phi, delta, h_m, h_r, h_mr, omega_tilde, b): H ((delta - h_m) * phi (delta - omega_tilde - b) * epsilon) / (1 - phi - epsilon) G (delta - omega_tilde) * mu / (1 - mu) O (omega_tilde - h_r) * mu / (1 - mu) M (omega_tilde - h_mr b) * epsilon / (1 - phi - epsilon) return H, G, O, M然后在循环里调用这个函数确保每次迭代的聚合项和当前epsilon一致。5. 敏感性分析与渠道协调从数值实验到管理启示5.1 交叉退货率对定价的边际影响原代码最后给了一段敏感性分析的框架但只画了epsilon对p_m和p_r的影响。实际用的时候我建议把theta也加进去做双参数扫描import matplotlib.pyplot as plt epsilon_range np.linspace(0.05, 0.2, 20) theta_range np.linspace(0.4, 0.8, 5) results [] for theta_val in theta_range: theta theta_val p_m_list, p_r_list [], [] for eps in epsilon_range: epsilon eps H, G, O, M compute_aggregates(epsilon, mu, phi, delta, h_m, h_r, h_mr, omega_tilde, b) res minimize(manufacturer_profit_analytical, [50, 40], bounds[(0, None), (0, None)]) p_m_opt, omega_opt res.x p_r_opt retailer_reaction(p_m_opt, omega_opt) p_m_list.append(p_m_opt) p_r_list.append(p_r_opt) results.append((theta_val, p_m_list, p_r_list)) for theta_val, p_m_list, p_r_list in results: plt.plot(epsilon_range, p_m_list, labelfp_m (θ{theta_val:.1f})) plt.plot(epsilon_range, p_r_list, linestyle--, labelfp_r (θ{theta_val:.1f})) plt.xlabel(交叉退货率 ε) plt.ylabel(最优价格) plt.legend() plt.show()跑出来你会看到epsilon增大时线上价格p_m下降线下价格p_r上升。原因是交叉退货把线上渠道的退货成本部分转移到了线下线下零售商通过处理退货获得残值和补贴有动力抬高价格来筛选消费者。theta越大这个分化越明显因为线下渠道的需求基数本来就大退货处理规模效应更显著。5.2 收益共享契约的参数设计原论文提到用收益共享契约来协调分散式决策下的利润分配但代码里没有展开。我一般会补一个简单的协调验证def coordinated_profit(p_m, p_r, omega, sharing_ratio): D_r, D_m demand(p_m, p_r, alpha, theta, beta, gamma) Q_r, Q_m actual_sales(D_r, D_m) total (p_m - c_m) * D_m (p_r - c_m) * D_r total (delta - h_m) * phi * Q_m (delta - h_mr) * epsilon * Q_m total (delta - h_r) * mu * Q_r pi_m sharing_ratio * total pi_r (1 - sharing_ratio) * total return pi_m, pi_r pi_m_coord, pi_r_coord coordinated_profit(p_m_opt, p_r_opt, omega_opt, 0.6) print(f协调后制造商利润: {pi_m_coord:.2f}, 零售商利润: {pi_r_coord:.2f})sharing_ratio是制造商分到的总利润比例。协调的目标是让pi_m_coord pi_m且pi_r_coord pi_r即双方都比分散式决策下过得更好。如果找不到这样的sharing_ratio说明契约参数需要调整比如改变omega_tilde或b。5.3 一个我踩过的坑初始猜测值对均衡解的影响最后说一个血泪经验。这份代码里minimize的初始猜测值[50, 40]不是随便写的它对应的是成本加成定价的直觉范围。我有一次为了“加速收敛”把初始值改成[100, 80]结果求解器跑到了一个高价均衡利润反而比论文里的基准值低了 15%。排查了半天才发现利润函数在低价区间有一个局部最优初始值太远就跳不过去了。从那以后我每次跑这类博弈模型都强制走一遍“多初始值扫描”用np.linspace生成五组初始值分别跑minimize取利润最大的那组作为最终解。虽然多花几秒钟但能避免拿着一个局部最优当全局最优去写论文。希望帮到你。本文还有配套的精品资源点击获取