除了用蒙特卡洛模拟进行投资组合求解外,也可以通过python的scipy.optimize库进行最优化求解。接着上一篇文章,我们继续使用scipy.optimize进行投资组合最优化求解工作。
import scipy.optimize as sco
# 定义计算投资组合的状态函数如下
def portfolio_status(weights):
weights = array(weights)[:,newaxis]
port_rets = weights.T @ array(returns.mean() * 250)[:,newaxis]
port_vols = sqrt(multi_dot([weights.T, returns.cov()*250, weights]))
# 返回组合收益率,组合波动率和夏普比率
return array([port_rets, port_vols, port_rets/port_vols]).flatten()
用最优化module求解最大夏普比率对应的组合
# 定义夏普比率(取负号)函数
def sharpe_ratio(weights):
return -portfolio_status(weights)[2] #取return结果中的第3个数
# 设置每一只股票权重范围为0<=x<=1
tuple((0,1) for x in range(numofasset))
# 明确组合最优化约束条件和股票权重范围
cons = ({'type':'eq','fun': lambda x: sum(x)-1})
bnds = tuple((0,1) for x in range(numofasset))
initial_wts = numofasset*[1./numofasset] #初始化weights的值便于优化算法快速完成计算
# 用最优化module求解最大夏普比率,opt_sharpe(目标函数, 入参, 优化方法, 边界条件, 约束条件)
opt_sharpe = sco.minimize(sharpe_ratio, initial_wts, method='SLSQP',
bounds = bnds, constraints = cons)
opt_sharpe
# 最大夏普比率组合权重
list(zip(symbols, around(opt_sharpe['x']*100, 2))) #两个list对应元素生成一个tuple
# 最大夏普比率组合数
status = ['Returns', 'Volatility', 'Sharpe Ratio']
list(zip(status, around(portfolio_status(opt_sharpe['x']), 4)))
用最优化module求解最小波动率组合
# 定义波动率(方差)函数
def variance(weights):
return portfolio_status(weights)[1]**2
# 用最优化module求解最小方差
opt_var = sco.minimize(variance, initial_wts, method='SLSQP',
bounds = bnds, constraints = cons)
opt_var
# 最小方差组合权重
list(zip(symbols, around(opt_var['x']*100,2)))
# status = ['Returns', 'Volatility', 'Sharp Ratio']
list(zip(status, around(portfolio_status(opt_var['x']),4)))
用最优化module求解有效边界(effective frontier)
# 定义波动率(标准差)函数
def volatility(weights):
return portfolio_status(weights)[1] #portfolio_status(weights)函数返回列表第2个元素为vol
# 设定有效边界参数
targetrets = linspace(0.04, 0.42, 500) # 将目标收益率进行500等分
targetvols_ = []
for i in tqdm(targetrets):
ef_cons = ({'type':'eq','fun': lambda x: sum(x)-1},
{'type':'eq','fun': lambda x: portfolio_status(x)[0]-i}) #两个约束条件
opt_ef = sco.minimize(volatility, initial_wts, method='SLSQP',
bounds=bnds, constraints=ef_cons)
targetvols_.append(opt_ef['fun'])
targetvols = array(targetvols_)
# 记录有效边界数据到DataFrame
ef_port = pd.DataFrame({
'targetrets': around(targetrets*100, 2),
'targetvols': around(targetvols*100, 2),
'targetsharpe': around(targetrets/targetvols, 2)
})
ef_port.head()
# 有效边界画图
fig = px.scatter(
ef_port, x='targetvols', y='targetrets', color='targetsharpe',
labels={'targetrets': '预期收益率', 'targetvols': '预期波动率',
'targetsharpe': '夏普比率'},
title='投资组合有效边界'
).update_traces(mode='markers', marker=dict(symbol='circle'))
# 标出最大夏普比率组合(市场组合)
fig.add_scatter(mode='markers',
x=[100*portfolio_status(opt_sharpe['x'])[1]], #[1]为vols
y=[100*portfolio_status(opt_sharpe['x'])[0]], #[0]为rets
marker=dict(color='Blue', size=14, symbol='square'),
name = '市场组合'
).update(layout_showlegend=False)
# 标出最小方差(波动率)组合
fig.add_scatter(mode='markers',
x=[100*portfolio_status(opt_var['x'])[1]], #[1]为vols
y=[100*portfolio_status(opt_var['x'])[0]], #[0]为rets
marker=dict(color='Red', size=14, symbol='square'),
name = '最小波动率组合'
).update(layout_showlegend=False)
fig.update_xaxes(showspikes=True)
fig.update_yaxes(showspikes=True)
最后可视化结果如下其中红色方块和蓝色方块分别为最小波动率组合和最大夏普比率组合所在的位置