Scipy fsolve递归求解标量方程根时的死循环问题及期权收益方程求根解决方案咨询
解决fsolve递归死循环并正确获取期权收益方程的所有根
你遇到的核心问题有两个:一是没验证fsolve返回的结果是否真的是方程的根,导致非根的起始点被误加入列表;二是递归终止条件太脆弱,仅靠“新根和前一个相同”判断,完全没考虑无效结果的情况。再加上期权收益函数是分段线性的,根的位置其实有规律,盲目递归反而效率低还容易出问题。
下面是具体的解决方案,分步骤来:
1. 核心改进思路
- 先验证,再收录:fsolve返回结果后,必须检查该点的函数值是否接近0(设置合理容差,比如
1e-6),确认是有效根再加入列表 - 去重处理:不同起始点可能找到同一个根,要避免重复添加
- 用迭代替代递归:递归容易栈溢出,迭代更可控,也方便处理终止逻辑
- 利用期权函数特性缩小搜索范围:期权收益函数的分段点都是行权价(strike),根只会出现在这些分段点附近或者函数的线性区间内,我们可以把起始点集中在这些关键位置,不用盲目遍历
2. 改进后的代码实现
第一步:完善根的验证逻辑
先写一个辅助函数,用来判断某个点是否是有效根:
def is_valid_root(func, x, tol=1e-6): # 计算函数值,判断是否接近0 return abs(func(x)) < tol
第二步:重写根搜索函数(迭代+验证+去重)
结合你的期权场景,我们先收集所有行权价,再在这些价格的附近设置起始点,同时覆盖你原来的搜索范围:
import numpy as np from scipy.optimize import fsolve def find_all_option_roots(func, df_group, start_multiplier=0.7, end_multiplier=1.3, increment=1.0, tol=1e-6): roots = [] # 收集所有唯一行权价,作为关键起始点 strikes = df_group['strike'].unique() # 构建起始点列表:行权价本身、行权价上下浮动5%,以及原有的范围区间点 start_points = [] # 添加行权价相关点 for strike in strikes: start_points.extend([strike, strike*0.95, strike*1.05]) # 添加原有的范围区间(从min_strike*0.7到max_strike*1.3,按步长生成) min_strike = df_group['strike'].min() max_strike = df_group['strike'].max() start_range = np.arange(min_strike*start_multiplier, max_strike*end_multiplier, increment) start_points.extend(start_range.tolist()) # 去重起始点,避免重复计算 start_points = list(set(start_points)) # 遍历所有起始点找根 for start in start_points: # 用fsolve求解,限制最大迭代次数避免无效计算 sol = fsolve(func, start, maxfev=1000) sol_val = sol[0] # fsolve返回数组,取第一个元素 # 验证是否是有效根,并且不在已找到的根列表中(容差内视为同一个根) if is_valid_root(func, sol_val, tol): duplicate = False for root in roots: if abs(sol_val - root) < tol: duplicate = True break if not duplicate: roots.append(sol_val) # 对根排序,方便查看 roots.sort() return roots
第三步:调整调用逻辑
把原来的递归调用替换成这个迭代函数,同时注意calculate_one_payoff需要包装成只接受stock_price的单参数函数(因为fsolve要求目标函数是单参数的):
# 包装calculate_one_payoff,绑定其他固定参数 def payoff_func(stock_price): return self.calculate_one_payoff(stock_price, df_long_call, df_short_call, df_long_put, df_short_put) # 调用新的根搜索函数 all_roots = find_all_option_roots(payoff_func, df_group, increment=1.0) print("找到的所有有效根:", all_roots)
3. 额外优化建议
- 针对分段线性函数的高效解法:期权收益函数是分段线性的,其实可以直接遍历每个分段区间,解方程
a*x + b = 0,判断根是否在区间内,这种方法比fsolve更高效,完全避免迭代求解的不确定性 - 过滤无效起始点:比如不要设置负数股价这类明显不合理的起始点,减少不必要的计算
- 调整容差:可以根据你的业务需求调整
tol参数,比如如果收益计算允许一定误差,容差可以设为1e-3
为什么原来的递归会出问题?
你的递归逻辑没有验证返回值是否是根,当fsolve因为起始点附近没有根、直接返回起始点时,这个非根的点被不断加入列表,而递归终止条件是“新根和前一个相同”——但这个相同的点根本不是根,所以会无限循环下去。改进后的逻辑从根源上解决了这个问题:只有经过验证的有效根才会被收录,而且遍历有限的合理起始点,不会无限循环。
内容的提问来源于stack exchange,提问作者Lei
相关产品推荐
相关产品推荐

