scipy.minimize最小化投资组合跟踪误差结果异常问题排查
跟踪误差最小化优化异常的原因分析
我有一个形状为(250,10)的pd.DataFrame存储每日股票收益,以及一个形状为(250,)的pd.Series存储基准每日收益。目标是最小化股票投资组合与基准之间的跟踪误差(定义为(投资组合收益-基准收益)的标准差),但使用scipy.minimize时结果完全不符合预期——本该高度相似的累积收益曲线差异极大,而最大化收益等其他目标函数运行正常,scipy也未抛出错误。请问该目标函数为何会给scipy带来困扰?
最小可复现代码
import numpy as np import pandas as pd from scipy.optimize import minimize def portfolio_te(weights, rets, bm_rets): port_returns = np.dot(rets, weights) te = np.sqrt(np.mean((port_returns - bm_rets)**2)) return te stocks_returns = pd.DataFrame(np.random.normal(0, 0.02, (250, 10))) benchmark_returns = pd.Series(np.random.normal(0, 0.02, 250)) result = minimize(portfolio_te, x0=[0.1 for i in range(10)], bounds=[(0,0.3) for i in range(10)], method='SLSQP', args=(stocks_returns, benchmark_returns)) port_returns = pd.Series(np.dot(stocks_returns, result.x)) ts = pd.concat([(1+port_returns).cumprod(), (1+benchmark_returns).cumprod()], axis=1) ts.plot()
问题根源与解决方案
1. 缺少核心约束:权重和为1
当前优化仅设置了单权重的上下界,但未添加投资组合最关键的权重和等于1的等式约束。没有这个约束,优化器可能输出权重和远小于1的结果,导致组合收益整体被缩放,自然无法匹配基准收益的量级。
需要在minimize中补充约束:
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
并在调用时传入constraints=constraints参数。
2. 目标函数的冗余运算
跟踪误差是(组合收益-基准收益)的标准差,即方差的平方根。由于平方根是单调递增函数,最小化方差等价于最小化标准差,去掉根号可以简化计算,避免潜在的数值精度问题,同时不改变优化结果:
def portfolio_te(weights, rets, bm_rets): port_returns = np.dot(rets, weights) # 直接返回方差,等价于最小化标准差 return np.mean((port_returns - bm_rets)**2)
3. 优化方法的适配性
SLSQP方法对目标函数平滑性和约束处理的要求较高,若添加约束后仍有异常,可尝试切换到trust-constr方法,它在处理带约束的优化问题时稳定性更强。
修正后的完整代码
import numpy as np import pandas as pd from scipy.optimize import minimize def portfolio_te(weights, rets, bm_rets): port_returns = np.dot(rets, weights) # 最小化方差等价于最小化标准差,去掉根号减少计算 return np.mean((port_returns - bm_rets)**2) stocks_returns = pd.DataFrame(np.random.normal(0, 0.02, (250, 10))) benchmark_returns = pd.Series(np.random.normal(0, 0.02, 250)) # 添加权重和为1的等式约束 constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1}) result = minimize(portfolio_te, x0=[0.1 for i in range(10)], bounds=[(0,0.3) for i in range(10)], method='SLSQP', args=(stocks_returns, benchmark_returns), constraints=constraints) port_returns = pd.Series(np.dot(stocks_returns, result.x)) ts = pd.concat([(1+port_returns).cumprod(), (1+benchmark_returns).cumprod()], axis=1) ts.plot()
内容的提问来源于stack exchange,提问作者Quastiat
相关产品推荐
相关产品推荐

