利用Python创建不同资产组合的基本框架

TUSHARE  金融与技术学习兴趣小组 

翻译整理 | Apathy 

本期编辑 | Little monster 

译者简介:国防科技大学控制工程与科学专业研二在读,目前在学习Python爬虫和量化投资。

作者:Mariano Scandizzo

本文比较了两个多元投资组合的风险调整回报,研究目标如下:

① 比较两个不同投资组合的风险调整后边际回报率,一个投资组合增加新兴市场债务,另一个投资组合增加的是黄金。

② 创建一个基本框架,利用Python分析和比较N个资产的投资组合。

③ 基于描述性统计和蒙特卡罗概念创建易于部署的可视化和模拟。

【注】由于代码较长,本文只列出了与统计分析相关的代码,跳过了与可视化相关的代码。文末已附含完整代码的GitHub链接。

投资组合和关键统计数据

【数据集来源】雅虎财经

【周期】20130429—20180430

【数据量】262

【资产】

SPY——SPDR S&P 500 ETF

QQQ——PowerShares QQQ ETF

AGG——iShares Core US Aggregate Bond ETF

GLD——SPDR Gold Shares

EMB——iShares JP Morgan USD Em Mkts Bd ETF

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns; sns.set()
plt.style.use(‘ggplot’)
semana = 52
datos = pd.read_excel(‘MasterAllocation.xlsx’,sheet_name=’Summary’,index_col=’Date’)

历史演变

数据可视化是形成初始直观的重要一步。下面是整个观察周期的历史价格演变:

资产类别的相对表现

由于每个资产类别的初始值不同,我们很难比较它们的相对表现。如果在观察期开始时在每项资产上投入1美元,会发生什么?

为了回答上面的问题,我们需要对数据进行标准化。

normalized_series = (datos/datos.iloc[0])

描述性统计

下面我们用一些数字来帮助我们描述每项资产类别的行为。

为了对每种资产的回报率和内在风险有一个直观的认识,我们可以先从年化回报率和离散度的测量开始入手。

【注】因为我们用的是周数据,需要转化为年化数据,通常假设每年有52周。

datos_returns = np.log(datos/datos.shift(1))
datos_returns.dropna(inplace=True)
stats = pd.DataFrame()
stats[‘Annualized Returns(%)’] =datos_returns.mean() * semana *100
stats[‘Annualized Volatility(%)’] = datos_returns.std() * np.sqrt(semana)*100
stats[‘Sharpe Ratio’] = stats[‘Annualized Returns(%)’] /stats[‘Annualized Volatility(%)’]
print(82*’-’)
print(‘Assets Classes Annualized Statistics — full observation period’)
stats.style.bar(color=[‘red’,’green’], align=’zero’)

回报的分散性

下一层的分析由数据的三阶和四阶矩驱动,即偏度和峰度。

作为投资组合风险管理的一部分,我们需要了解回报是否比正态分布更频繁地偏向于正值或负值,即偏度。

同样重要的是,我们要知道这些资产是否比正态分布的资产更倾向于发生极端事件,这些极端事件可以是正面的,也可以是负面的,这种现象也叫肥尾效应。

下面的图表显示了每种资产回报的直方图。 为了便于比较,绘制了一个钟形曲线,其平均值和标准偏差等于所考虑的资产。

钟形外部的值表示资产行为不能通过正态假设完全描述。为便于参考,图表中包含了偏度值和峰度值。

美国投资级债券与新兴市场政府债券

让我们具体比较投资组合中两种固定收益资产的收益分布。

很明显,与投资级美国政府债券相比,新兴市场债券回报率的分布更分散。回报率越高,波动性越大。

投资组合分析

到目前为止,我们只考虑了单个资产。

投资组合拥有的资产越多,在确定投资组合风险行为中,每个资产与投资组合中其他资产的相对行为就越重要。

模拟

【投资组合1】

美国投资级固定收益:30%(AGG)

美国股票:50%(SPY和QQQ)

黄金:20%(GLD)

【投资组合2】

美国投资级固定收益:30%(AGG)

美国股票:50%(SPY和QQQ)

EM政府债券:20%(EMB)

让我们比较一下它们的相对表现。投资组合2的风险调整回报比投资组合1更高,表明在观察期内新兴市场债券是比黄金更好的选择。

投资组合的波动性

每个投资组合的风险状况由其波动率值描述。

datos_returns.corr(‘pearson’)

用全周期相关矩阵(Pearson公式),计算年化投资组合收益率和波动率。

Expected_Return_noEM = np.sum(datos_returns.mean()* allocation.No_EM)* semana
Expected_Std_noEM = np.sqrt(np.dot(allocation.No_EM.T,np.dot(datos_returns.cov()*semana,
 allocation.No_EM)))
Sharpe_noEM = Expected_Return_noEM / Expected_Std_noEM
Expected_Return_EM = np.sum(datos_returns.mean()* allocation.EM)* semana
Expected_Std_EM = np.sqrt(np.dot(allocation.EM.T,np.dot(datos_returns.cov()*semana,
 allocation.EM)))
Sharpe_EM = Expected_Return_EM / Expected_Std_EM
print(‘Key Stats: Portfolio with no EM Securities ‘)
print(82*’=’)
print(‘Annualized Returns: {:.3%}’.format(Expected_Return_noEM))
print(‘Annualized Volatility: {:.3%}’.format(Expected_Std_noEM))
print(‘Sharpe Ratio: {:.4}’.format(Sharpe_noEM))
print(82*’-’)
print(‘Key Stats: Portfolio with EM Securities ‘)
print(82*’=’)
print(‘Annualized Returns: {:.3%}’.format(Expected_Return_EM))
print(‘Annualized Volatility: {:.3%}’.format(Expected_Std_EM))
print(‘Sharpe Ratio: {:.4}’.format(Sharpe_EM))
print(82*’-’)

采用和之前可视化单个资产类别回报的分散性相同的方法,我们观察一下这两个投资组合的收益符合怎样的正态分布。

蒙特卡罗模拟

最后,我们模拟一下马科维茨有效边界。我们不仅要计算最优投资组合,还要计算内部(次优)投资组合。

为了计算每个投资组合,我们将随机改变资产权重,同时保持每个投资组合的资产类别不变。

该练习将为每个投资组合运行2,500次模拟。此外,色标基于每个投资组合的夏普比率,通过风险调整效率的程度在视觉上区分投资组合。

pretsEM = []
pvolsEM = []
prets_noEM = []
pvols_noEM = []
[[‘AGG’,’SPY’,’QQQ’,’EMB’]]
[[‘AGG’,’SPY’,’QQQ’,’GLD’]]
for p in range(2500):
 weights = np.random.random(len(allocation)-1)
 weights /= np.sum(weights)
 pretsEM.append(np.sum(datos_returns[[‘AGG’,’SPY’,’QQQ’,’EMB’]].mean()* weights)* semana)
 pvolsEM.append(np.sqrt(np.dot(weights.T,np.dot(datos_returns[[‘AGG’,’SPY’,’QQQ’,’EMB’]].cov()*semana,
 weights))))
pretsEM = np.array(pretsEM)
pvolsEM = np.array(pvolsEM)
for p in range(2500):
 weights = np.random.random(len(allocation)-1)
 weights /= np.sum(weights)
 prets_noEM.append(np.sum(datos_returns[[‘AGG’,’SPY’,’QQQ’,’GLD’]].mean()* weights)* semana)
 pvols_noEM.append(np.sqrt(np.dot(weights.T,np.dot(datos_returns[[‘AGG’,’SPY’,’QQQ’,’GLD’]].cov()*semana,
 weights))))
prets_noEM = np.array(prets_noEM)
pvols_noEM = np.array(pvols_noEM)
# the charts
fig8 = plt.figure(figsize = (12,16))
plt.subplots_adjust(wspace=.5)
plt.subplot(211)
plt.scatter(pvolsEM, pretsEM, c = pretsEM / pvolsEM, marker = ‘o’,cmap=’coolwarm’)
plt.grid(True)
plt.xlabel(‘expected volatility’)
plt.ylabel(‘expected return’)
plt.colorbar(label = ‘Sharpe Ratio’)
plt.title(‘Monte Carlo Simulation Efficient Frontier with EM’)
plt.subplot(212)
plt.scatter(pvols_noEM, prets_noEM, c = prets_noEM / pvols_noEM, marker = ‘o’,cmap=’viridis’)
plt.grid(True)
plt.xlabel(‘expected volatility’)
plt.ylabel(‘expected return’)
plt.colorbar(label = ‘Sharpe Ratio’)
plt.title(‘Monte Carlo Simulation Efficient Frontier with no EM’)
plt.show();
fig8.savefig(‘frontiers.png’,dpi=fig8.dpi)

回报率和波动性的进一步解读

到目前为止,我们已经使用了相同的收益和相关矩阵。覆盖整个时间周期的计算,自然会使时间序列的波动性更小,从而使结果更平滑。

为了更好地描绘资产行为,让我们计算每个投资组合的3个月拖尾相关性。

你会注意到回报率和波动率的平均值会回归到历史平均水平,因此,如果你使用更短的时间周期来计算,整体结论将不会改变,而只是会增加随机性的程度。

【注】

对于那些刚开始从事金融工作的人来说,Excel可能是唯一可供选择的工具。当试图计算效率边界时,你可能可以计算出如下形状的矩阵乘法:

[1x5] * [5x5] * [5x1] = Portfolio volatility 

其实,只计算一次并不是很难,但如果你想在Excel里面像我们这样运行250次,嗯……不掉几根头发是搞不定的!!!这里我想强调一下numpy Tensordot的强大功能,只需一步,就可以运行250次矩阵乘法,相当惊人!!!

def trailing_ret(retornos, window, weights, annualization = 52):
 roll_ret = retornos.rolling(window=window).mean()
 roll_ret = roll_ret.dropna()
 roll_ret = (roll_ret * weights)* annualization
 roll_ret = roll_ret.sum(axis =1)
 roll_ret = roll_ret.to_frame()
 roll_ret.rename(columns ={0:’returns’}, inplace = True)
 return roll_ret
def trailing_cov2(retornos, window, weights, annualization = 52):
 retornos_length = len(retornos)
 retornos_width = len(retornos.columns)
 roll_cov = retornos.rolling(window=window).cov()
 roll_cov_dates = np.unique(roll_cov.index.get_level_values(0).values)
 roll_cov_dates = roll_cov_dates[window-1:]
 roll_cov = roll_cov.values.reshape(retornos_length,retornos_width,retornos_width)
 roll_cov = roll_cov[window-1:] * annualization
 weights = weights.values.reshape(len(weights),1)
 step1 = np.tensordot(roll_cov,weights,axes=[1,0])
 step2 = np.tensordot(weights,step1, axes=[0,1])
 volatility = np.sqrt(step2)
 volatility = volatility.reshape((retornos_length-(window-1)),1)
 trailing_vol = pd.DataFrame()
 trailing_vol[‘date’] = roll_cov_dates
 trailing_vol[‘volatility’] = volatility
 trailing_vol.set_index(‘date’,inplace = True)
 return trailing_vol
def full_analysis(retornos, window, weights, annualization = 52):
 volatilidad = trailing_cov2(retornos = retornos, window = window, weights = weights, annualization = annualization)
 retornos = trailing_ret(retornos = retornos, window = window, weights = weights, annualization = annualization)
 fusion = pd.merge(volatilidad, retornos, left_index=True,right_index=True)
 fusion[‘sharpe’] = fusion[‘returns’]/fusion[‘volatility’]
 return fusion

结论

1、与黄金相比,新兴市场债券提高了投资组合风险调整后的回报率。

2、我们创建了一个灵活的模型来比较不同的资产,分别应用于单个资产和投资组合,并在进行战略资产配置时可视化风险和收益影响。

3、初步分析表明,滚动时间框架,如上面验证的完整周期和短周期3个月,不会改变战略结论。

4、该框架可作为一个初始基础来不断扩展投资组合分析,例如VAR计算、压力测试等,留给读者去利用现有的模型继续扩展这个定量方法。

END

更多内容请关注“挖地兔”公众号。

【参考链接】

网页链接

网页链接【Github源码】

【扩展阅读】

被动市场有效性是否真的有作用?

如何正确使用Pandas库提升项目的运行速度?

如何聪明的配置Smart Beta?

这些方法解决了数据清洗80%的工作量

利用Python玩转PDF,简单实用

雪球转发:4回复:1喜欢:7

全部评论

数说大A股05-31 09:37

我刚打赏了这个帖子 ¥50,也推荐给你。