TushareID:469107
大家好,本期给大家带来的python作业分享是:如何运用Tuhare进行opts最优化算法算出上证50的最优投资组合(当然有两个限制条件:第一个就是特定时段的上证50组合,第二个就是特定历史时段的股票价格数据)
第一步
在tushare上获取相关的信息
这一步在上次的作业里面有明确提到过,因此此次不再赘述,只贴出相关的代码并作出相应的解释。
#提取上证50的成分股
pro = ts.pro_api('你的tushare token')
date_start_50=int(input('上证50成分筛选开始时段(8位):'))
date_end_50=int(input('上证50成分筛选结束时段(8位):'))
df=pro.index_weight(index_code='000016.SH', start_date=date_start_50, end_date=date_end_50)
df=df.iloc[:,[0,1]]
#df.rename(columns={'index_code':'index','con_code':'code'},inplace=True)
1、在上面的代码段中,date_start_50和date_end_50是定义选取上证50成分股的时段,为什么要加这个时段呢?这是因为上证50的成分股一直在更新,此处我推荐大家选择的时间区间是一个月,例如20210101-20210201
2、其中iloc选择的是上证50的代码和成分股代码,大家一试便知。
date_start_history=int(input('根据历史数据的选股开始时段(8位):'))
date_end_history=int(input('根据历史数据的选股结束时段(8位):'))
future_start=int(input('验证最优组合的开始时段(8位):'))
future_end=int(input('验证最优组合的结束时段(8位):'))
上面的代码片中,四个键入实际上选择优化计算最优权重的历史数据和测试段数据,
history是历史数据,future是预测数据。
第二步
对数据的清洗和整理
#数据的转化和清洗
len_df=len(df)#测量df的长度,下方循环时选择下标会用到
#下面是获取和处理指数数据
df_index=pro.index_daily(ts_code='399300.SZ', start_date=date_start_history, end_date=date_end_history,fields='trade_date,pct_chg')#获取指数数据
df_index['trade_date']=df_index['trade_date'].astype('str')#转化为时间序列-1
df_index['trade_date']=pd.to_datetime(df_index['trade_date'])#转化为时间序列-2
df_index=df_index.sort_values('trade_date',ascending=True)
#下面是获取成分股的股票数据
daily_trade=pro.daily(ts_code=df.iloc[4,1], start_date=date_start_history, end_date=date_end_history,fields='trade_date,close')
#注意,获取股票的价格数据的处理我们是选择了一个比较笨的方法,但我们认为是很好用的,也就是按照成分股代码一个一个的遍历然后取数据
for i in range(1,len_df):
daily_trade1=pro.daily(ts_code=df.iloc[i,1], start_date=date_start_history, end_date=date_end_history,fields='close')
daily_trade[df.iloc[i,1]]=daily_trade1#将之前columns中close的名称改为股票代码,因为这样的话方便我们下方处理
daily_trade.rename(columns={'close':df.iloc[0,1]},inplace=True)#聚合之后第一列股票的收盘价还没有更改columns的名字,需要改为第一个
daily_trade['trade_date']=daily_trade['trade_date'].astype('str')
daily_trade['trade_date']=pd.to_datetime(daily_trade['trade_date'])
daily_trade=daily_trade.fillna(method='pad',axis=0)#将Nan数据按照按前一个数据进行填充
daily_trade=daily_trade.sort_values('trade_date',ascending=True)
print(daily_trade)#此时获取的是历史时段的股票数据
第三步
计算相关需要用到的值,例如方差、均值、收益率等等
#成分股对数收益率的计算
data=daily_trade.set_index('trade_date')
r=np.log(data/data.shift(1))#shift方法是往下一列进行平滑移动
r=r.dropna()#计算收益率时第一行是空值,删掉第一行
print('计算收益之后的数据表',r)
#print(r.describe())#描述数据
#对成分股数据的处理
#计算成分股历史数据收益率的均值
mean_history_array=[]
for i in range(0,50):
mm=np.mean(r.iloc[:,i])#50个成分股,50个均值
mean_history_array.append(mm)
print('成分股收益率平均值为',mean_history_array)
#计算成分股历史数据收益率的标准差
std_history_array=[]
for i in range(0,50):
dd=np.std(r.iloc[:,i])#50个成分股,50个标准差
std_history_array.append(dd)
print('成分股收益率标准差为',std_history_array)
#对指数数据的处理
#计算指数历史数据收益率的均值
hs_ret_mean=np.mean(df_index.iloc[:,1])
hs_ret_std=np.std(df_index.iloc[:,1])
print('指数收益率均值为',hs_ret_mean)
print('指数收益率标准差为',hs_ret_std)
第四步——最为关键
开始进行opt优化
#opt优化
#准备做最优化的要件:目标函数----投资组合的历史平均收益率
from scipy.optimize import minimize
def RET(weights): #目标函数
weights=np.array(weights)
return-np.dot(mean_history_array,weights)
#以下几个参数下面都会用到,所以一定要
delta_s=0.5#自己设定的
l=np.ones(50)#全是1的数组
n=50#50个标的
对于上述的delta_s系数解释一下,该系数代表的是所承担的风险是上证50数据的多少倍,是自己设定的
cons=({'type':'eq','fun':lambda x:np.dot(x,l)-1},#组合权重等于一
{'type':'ineq','fun':lambda x:np.dot(mean_history_array,x)-hs_ret_mean},#收益率大于0
{'type':'ineq','fun':lambda x:hs_ret_std-delta_s*np.dot(std_history_array,x)})#方差大于0
bnds=tuple((0,1) for x in range(n))
x0=n*[1./n]#初值
opts=minimize(RET,x0,method='SLSQP',bounds=bnds,constraints = cons)
print('最优解组合状态',opts.success)
print('最优解',opts.x)
1、cons=…这几行代码代表的是三个做优化的约束条件:第1个约束条件是权重总和加起来为0,第2个约束条件是按计算出来的最优权重计算出的收益大于上证50的收益,第3个约束条件是按最优权重构成组合的方差小于上证50的方差,此处方差代表的是风险。
2、x0=n*[1./n],该行代码的含义是给与需要计算的最优权重附一个初值,让其在初值的基础上去试算,此处初始我们给的是等权重。
3、bnds=tuple((0,1) for x in range(n))这一行代码我需要特别解释一下的是,改行代码代表的是权重不能小于0,即不允许做空,更加符合我国市场情况。
4、此处还需要提醒大家的该方法的自变量只能以数组为格式,这也解释了为什么我们在cons的约束条件中要用np.dot的点乘形式。
#将权重加如之前的df数据集
#opts.x=round(opts.x,4)
df=df.drop(columns={'index_code'})
df['weight']=np.array(opts.x)#将股票代码和计算出的权重组合在一起
df=df.sort_values('weight',ascending=False)
df=df.iloc[:4,:]
print('按特定要求选取的前四大权重股',df)
上述的代码是打印按最优化算法计算出来的前四大权重,我们会根据这四个股票和其对应的权重构建组合。
第五步
计算综合收益率
#按照价格开始计算综合收益
r_weight=data[df.iloc[:,0]]#取权重前四的个股名称
len_weight1=len(df)#对于筛选出来的权重股计数
len_weight2=len(daily_trade)#计算len_weight1的长度
r_weight['profit']=0
for i in range(len_weight2-1):
r_weight.iloc[i+1,-1]=((((r_weight.iloc[i+1,0]-r_weight.iloc[0,0])/r_weight.iloc[0,0])*df.iloc[0,1]+
((r_weight.iloc[i+1,1]-r_weight.iloc[0,1])/r_weight.iloc[0,1])*df.iloc[1,1]+
((r_weight.iloc[i+1,2]-r_weight.iloc[0,2])/r_weight.iloc[0,2])*df.iloc[2,1]+
((r_weight.iloc[i+1,3]-r_weight.iloc[0,3])/r_weight.iloc[0,3])*df.iloc[3,1])*100)
print('打印计算加权综合收益率后的表(历史数据)',r_weight)
上述是根据筛选出的股票按权重构建投资组合。
我们如何衡量这个组合呢?我们在此处采取的方法是算出股票在给定的测试时段内其相对于测试日期第一天的收益率并按权重加权计算出一个总收益。
#获取指数收益率
df_index_price=pro.index_daily(ts_code='399300.SZ', start_date=date_start_history, end_date=date_end_history,fields='trade_date,close')
df_index_price['trade_date']=df_index_price['trade_date'].astype('str')
df_index_price['trade_date']=pd.to_datetime(df_index_price['trade_date'])
df_index_price=df_index_price.sort_values('trade_date',ascending=True)
此处是获取指数每日价格,我们将在下方绘图时用一个算式来解决收益率的计算问题:(((df_index_price.iloc[:,1]-df_index_price.iloc[0,1])/df_index_price.iloc[0,1])*100)
第六步
对于历史的收益率的可视化
#对于历史的收益率的绘图
fig, ax = plt.subplots()
x=r_weight.index # 创建x的取值范围
y=r_weight.iloc[:,4]
ax.plot(x,y,label='按权重组成的投资组合')
x1=x
y1=(((df_index_price.iloc[:,1]-df_index_price.iloc[0,1])/df_index_price.iloc[0,1])*100)
ax.plot(x1,y1,label='沪深300指数收益')
ax.set_xlabel('日期') #设置x轴名称 x label
ax.set_ylabel('收益率') #设置y轴名称 y label
ax.set_title('按最优化权重构成的投资组合与沪深300基于历史数据的验证') #设置图名为Simple Plot
ax.legend()
plt.grid(color='r',linestyle='-',linewidth=0.1)
plt.xticks(rotation=30)
plt.show()
第七步
对于给定测试时段内组合的收益率表现的可视化
原理如上,不再赘述
#获取验证数据并处理
df_weight=len(df)
future_weight=pro.daily(ts_code=df.iloc[0,0], start_date=future_start, end_date=future_end,fields='trade_date,close')
for i in range(1,df_weight):
future_weight1=pro.daily(ts_code=df.iloc[i, 0], start_date=future_start, end_date=future_end,fields='close')
future_weight[df.iloc[i,0]]=future_weight1
future_weight.rename(columns={'close':df.iloc[0,0]},inplace=True)
future_weight['trade_date']=future_weight['trade_date'].astype('str')
future_weight['trade_date']=pd.to_datetime(future_weight['trade_date'])
future_weight=future_weight.sort_values('trade_date',ascending=True)
future_weight=future_weight.set_index('trade_date')
#计算
len_futureweight=len(future_weight)#计算future_weight的长度
future_weight['profit_future']=0
for i in range(len_futureweight-1):
future_weight.iloc[i+1,4]=((((future_weight.iloc[i+1,0]-future_weight.iloc[0,0])/future_weight.iloc[0,0])*df.iloc[0,1]+
((future_weight.iloc[i+1,1]-future_weight.iloc[0,1])/future_weight.iloc[0,1])*df.iloc[1,1]+
((future_weight.iloc[i+1,2]-future_weight.iloc[0,2])/future_weight.iloc[0,2])*df.iloc[2,1]+
((future_weight.iloc[i+1,3]-future_weight.iloc[0,3])/future_weight.iloc[0,3])*df.iloc[3,1])*100)
print('打印计算加权综合收益率后的表(验证数据)',future_weight)
#指数未来收益率
df_index_future=pro.index_daily(ts_code='399300.SZ', start_date=future_start, end_date=future_end,fields='trade_date,close')
df_index_future['trade_date']=df_index_future['trade_date'].astype('str')
df_index_future['trade_date']=pd.to_datetime(df_index_future['trade_date'])
df_index_future=df_index_future.sort_values('trade_date',ascending=True)
#对于未来验证的收益率的绘图
fig, ax = plt.subplots()
x=future_weight.index # 创建x的取值范围
y=future_weight.iloc[:,-1]
ax.plot(x,y,label='按权重组成的投资组合')
x1=x
y1=(((df_index_future.iloc[:,1]-df_index_future.iloc[0,1])/df_index_future.iloc[0,1])*100)
ax.plot(x1,y1,label='沪深300指数收益')
ax.set_xlabel('日期') #设置x轴名称 x label
ax.set_ylabel('收益率') #设置y轴名称 y label
ax.set_title('按最优化权重构成的投资组合与沪深300在未来时段的验证') #设置图名为Simple Plot
ax.legend()
plt.grid(color='black',linestyle='-',linewidth=0.1)
plt.xticks(rotation=30)
plt.show()
##################################################
可视化结果如下:
相关打印结果如下:
**1、**最优解组合状态 True
**2、**最优解 [3.83526119e-17 1.00000000e+00 8.09702244e-18 0.00000000e+00
2.47812608e-17 0.00000000e+00 0.00000000e+00 1.66389183e-17
5.24283153e-18 0.00000000e+00 0.00000000e+00 3.41244807e-17
1.37008501e-17 0.00000000e+00 1.89124936e-17 1.13316871e-18
0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
0.00000000e+00 2.11767906e-18 0.00000000e+00 9.20435272e-18
5.67522288e-18 0.00000000e+00 1.66899995e-17 0.00000000e+00
1.90103020e-17 1.45697203e-17 7.48313719e-18 0.00000000e+00
0.00000000e+00 1.28748978e-17 1.22315014e-17 0.00000000e+00
0.00000000e+00 0.00000000e+00 1.58535270e-17 1.55481270e-16
2.38965369e-17 3.03270803e-18 0.00000000e+00 2.19269047e-15
3.13257514e-17 2.78092624e-17 0.00000000e+00 3.41097203e-18
0.00000000e+00 0.00000000e+00]
**3、**按特定要求选取的前四大权重股
con_code weight
1 603501.SH 1.000000e+00
43 600036.SH 2.192690e-15
39 600196.SH 1.554813e-16
0 603986.SH 3.835261e-17
**4、**打印计算加权综合收益率后的表(历史数据)
603501.SH 600036.SH 600196.SH 603986.SH profit
trade_date
2021-01-04 230.05 43.17 52.97 74.85 0.000000
2021-01-05 253.06 42.18 52.60 72.80 10.002173
2021-01-06 264.00 44.15 53.81 72.31 14.757661
2021-01-07 269.36 45.90 52.24 72.95 17.087590
2021-01-08 266.00 46.60 52.37 71.18 15.627038
… … … … … …
2021-04-26 291.26 52.07 49.48 49.64 26.607259
2021-04-27 293.78 52.07 48.68 49.13 27.702673
2021-04-28 298.96 51.76 53.55 49.23 29.954358
2021-04-29 304.92 53.28 54.76 51.62 32.545099
2021-04-30 302.51 52.70 60.24 49.93 31.497501
[79 rows x 5 columns]
打印计算加权综合收益率后的表(验证数据)
603501.SH 600036.SH 600196.SH 603986.SH profit_future
trade_date
2021-05-06 293.15 53.67 54.22 187.38 0.000000
2021-05-07 264.26 54.18 54.65 177.95 -9.855023
2021-05-10 266.72 53.90 60.12 169.46 -9.015862
2021-05-11 270.95 53.84 63.14 166.05 -7.572915
2021-05-12 266.82 53.69 63.21 170.01 -8.981750
… … … … … …
2021-10-25 249.97 54.59 51.29 152.60 -14.729661
2021-10-26 256.59 55.30 50.91 158.01 -12.471431
2021-10-27 252.39 54.52 49.73 157.56 -13.904145
2021-10-28 264.00 54.22 49.72 164.30 -9.943715
2021-10-29 266.00 53.97 50.00 169.51 -9.261470