使用R的PerformanceAnalytics计算ETL时出现不可靠结果的求助
印度股市投资组合ETL计算错误排查与解决
问题背景
通过Python获取印度股市多只股票每日收盘价,使用pct_change()计算日简单收益率并构建等权重投资组合,导出CSV后在R中用PerformanceAnalytics包的ETL()函数按月度分组计算预期尾部风险(Expected Tail Risk)时,触发错误:
ES calculation produces unreliable result (risk over 100%) for column: 1 : 4.05194174741596
原始Python代码
import pandas as pd import numpy as np import datetime as dt from datetime import timedelta, datetime from pandas_datareader import data as pdr from scipy.stats import norm, t import matplotlib.pyplot as plt import statistics def getData(stocks, start, end): stockData = pdr.get_data_yahoo(stocks, start=start, end=end) stockData = stockData['Close'] returns_pctchange = stockData.pct_change() return returns_pctchange stockList = ['IOC', 'RELIANCE', 'BPCL', 'HINDPETRO', 'EXIDEIND'] stocks = [stock+'.NS' for stock in stockList] endDate = dt.datetime.now() startDate = endDate - dt.timedelta(days=8000) returns = getData(stocks, start=startDate, end=endDate) returns = returns.dropna() weights = np.array([1/len(stocks) for n in stocks]) returns['portfolio'] = returns.dot(weights) returns.reset_index(inplace=True)
数据示例
Date IOC.NS RELIANCE.NS BPCL.NS HINDPETRO.NS EXIDEIND.NS portfolio 0 2002-07-02 0.001000 -0.016930 0.001264 0.009413 0.055980 0.010145 1 2002-07-03 -0.000500 0.003931 -0.003787 0.015961 0.077108 0.018543 ... ... ... ... ... ... ... ... 5010 2022-08-26 0.006298 -0.003704 0.002881 0.003733 0.020563 0.005954
原始R代码
df = read.csv('C:/Users/VaR and CVaR/returns') ss = data.frame(`Month/Year`=unique(format(as.Date(df$Date),"%b/%Y")), `Monthly Expected Shortfall`=(tapply(df$portfolio,sub("-..$","",df$Date),FUN=PerformanceAnalytics::ETL)), check.names=F,row.names=NULL)
错误原因
- 简单收益率的极端值问题:
pct_change()计算的是算术简单收益率,当某只股票单日出现极端波动(比如因拆股、退市风险等导致的异常数据),会使组合收益率出现超出合理范围的值,触发PerformanceAnalytics对ETL计算可靠性的校验警告。 - ETL计算逻辑限制:ETL(预期尾部损失)基于尾部收益率的平均值,若样本中存在极端异常值,会拉低尾部均值的计算结果,导致风险值超过100%,系统判定结果不可靠。
解决方法
改用**对数收益率(连续复利收益率)**是最合理的方案,其特性避免了极端值问题,且更适合金融时间序列的风险计算。
1. 修改Python代码计算对数收益率
import pandas as pd import numpy as np import datetime as dt from pandas_datareader import data as pdr def getData(stocks, start, end): stockData = pdr.get_data_yahoo(stocks, start=start, end=end) stockData = stockData['Close'] # 计算对数收益率:ln(今日收盘价/昨日收盘价) returns_log = np.log(stockData / stockData.shift(1)) return returns_log stockList = ['IOC', 'RELIANCE', 'BPCL', 'HINDPETRO', 'EXIDEIND'] stocks = [stock+'.NS' for stock in stockList] endDate = dt.datetime.now() startDate = endDate - dt.timedelta(days=8000) returns = getData(stocks, start=startDate, end=endDate) returns = returns.dropna() weights = np.array([1/len(stocks) for n in stocks]) # 组合对数收益率为各股票对数收益率的加权平均 returns['portfolio'] = returns.dot(weights) returns.reset_index(inplace=True) # 导出CSV,确保日期格式正确 returns.to_csv('returns_log.csv', index=False)
2. 调整R代码适配对数收益率数据
library(PerformanceAnalytics) # 读取对数收益率数据,转换日期格式 df = read.csv('returns_log.csv') df$Date = as.Date(df$Date) # 按月度分组计算ETL,明确指定置信水平(默认95%,可按需调整) monthly_etl = tapply(df$portfolio, format(df$Date, "%Y-%m"), FUN = function(x) ETL(x, p = 0.95)) # 整理结果为易读的数据框 ss = data.frame( `Month/Year` = format(as.Date(paste0(names(monthly_etl), "-01")), "%b/%Y"), `Monthly Expected Shortfall` = unname(monthly_etl), check.names = FALSE, row.names = NULL ) print(ss)
额外建议
- 若必须使用简单收益率,需先过滤异常值(例如删除单日收益率绝对值超过50%的记录),但这种方式可能丢失真实的尾部风险信息,不推荐。
- 检查原始数据中是否存在拆股、分红等事件导致的价格异常,可使用
pandas_datareader的调整后收盘价(Adj Close)计算收益率,避免因除权除息导致的虚假波动。
内容的提问来源于stack exchange,提问作者Starlord22
相关产品推荐
相关产品推荐

