希望有人可以帮助或经历过类似的情况,为我指出问题所在的方向。
这是我的设置(请参阅下面的希望可重现的代码):
- 建立一个符号列表
- 通过雅虎的 FinancialInstrument 获取仪器数据
- 从 Quandl 获取 EURUSD 汇率 - 需要身份验证令牌
- 将价格重新命名为投资组合的基础货币
- 从 2004 年 3 月 31 日到今天,为其货币资产和重新计价的价格(此处为欧元)建立每月回报
- 通过重新平衡运行 ROI 优化
问题:
在按月重新平衡时,使用重新计价价格的回报似乎会在优化中产生不正确的结果(见图),因为回报不能保证这样的回报曲线,因为大部分投资组合都投资于“TLT” - 20 年期国债。
这给出了优化的结果,如下图所示:
index
这是一个更大系统的一部分,但我希望我创建了一个可重现的代码,该代码显示了似乎仅在使用重新平衡时才适用的问题,这向我表明,或日期可能存在问题。但是,当将两者都返回 xts 到 Excel 时,我看不出有任何区别。
我在代码末尾添加了多个图表,因为目前我不允许发布超过两张图片。
任何帮助或提示非常感谢指出我正在发生的事情......
packages <- c('quantmod', 'FinancialInstrument', 'PortfolioAnalytics', 'Quandl')
for(i in 1:length(packages))
library(packages[i], character.only = TRUE, quietly = TRUE)
.baseOptions <- list()
# set base CCY
.baseOptions$portf$portfolio.base.ccy <- "EUR"
# set date from which the analysis should be started
.baseOptions$portf$analyse.from <- "2004-03-31/"
# set symbols
symbol.list <- c("LQD", #iShares Investment Grade Corporate Bonds
"SHY", #iShares 1-3 year TBonds
"IEF", #iShares 3-7 year TBonds
"TLT" #iShares 20+ year Bonds
)
# get symbols while adjusting to dividends & splits
getSymbols(symbol.list, auto.assign = TRUE, from = "1990-01-01", to = as.character(Sys.Date()), adjust = TRUE)
# set CCY
currency(c("USD", "EUR"))
# set exchange_rate
exchange_rate("EURUSD")
# get exchange_rate
EURUSD <- Quandl('ECB/EURUSD', type = "xts", collapse = "daily", order = "asc")
stock(symbol.list, currency = "USD")
# get Fininstrument Data
update_instruments.yahoo(symbol.list)
#View(instrument.table(ls_instruments()))
# build price xts - usually part of larger sytem with different CCY incl. JPY, GBP, USD, HKD etc.
asset.CCY.prices <- foreach(i=1:length(symbol.list), .combine = 'cbind', .packages=c('quantmod')) %dopar% {
asset.CCY.prices <- Cl(get(symbol.list[i]))
}
# set to analysis period & monthly
asset.CCY.prices <- asset.CCY.prices[endpoints(asset.CCY.prices, on = "months")][.baseOptions$portf$analyse.from]
# redenominate to EUR
base.CCY.prices <- foreach (i=1:length(colnames(asset.CCY.prices)), .combine = 'cbind', .packages=c('FinancialInstrument')) %dopar% {
current.instrument <- gsub(".Close", "", colnames(asset.CCY.prices)[i])
current.instrument.CCY <- getInstrument(gsub(".Close", "", colnames(asset.CCY.prices)[i]))$currency
if(current.instrument.CCY != .baseOptions$portf$portfolio.base.ccy)
base.CCY.prices <- redenominate(asset.CCY.prices[,i],
new_base = .baseOptions$portf$portfolio.base.ccy,
old_base = current.instrument.CCY)
}
rm(current.instrument, current.instrument.CCY, i, packages)
# set the colnames in the basee.CCY price .xts
colnames(base.CCY.prices) <- colnames(asset.CCY.prices) # unlist(lapply(symbol.list, function(x) paste(x, .baseOptions$portf$portfolio.base.ccy, sep = ".")))
# build returns
asset.CCY.R <- ROC(asset.CCY.prices)
base.CCY.R <- ROC(base.CCY.prices)
# portfolio optimization
#
#
#-----------------------------------
# Specify initial portfolio
if(exists("portf.init")) rm(portf.init)
portf.init <- portfolio.spec(assets=colnames(asset.CCY.R))
portf.init <- add.constraint(portfolio=portf.init, type="weight_sum", min_sum=0.99, max_sum=1.01)
portf.init <- add.constraint(portfolio=portf.init, type="long_only")
#portf.init <- add.constraint(portfolio=portf.init, type="position_limit", max_pos=15)
#' Add objective to maximize mean
portf.init <- add.objective(portfolio=portf.init, type="return", name="mean")
#----------------------------------
# Global Minimum Variance Portfolio
#
if(exists("GMV")) rm(GMV)
GMV <- add.constraint(portfolio=portf.init, type="weight_sum", min_sum=0.90, max_sum=1.01, indexnum = 1)
# Add box constraint
GMV <- add.constraint(GMV, type="box", min=0, max=0.99)
# Add var objective - risk is always minimised
GMV <- add.objective(GMV, type = "risk", name = "var")
# optimization in asset.CCY
opt.asset.CCY = optimize.portfolio.rebalancing(R = asset.CCY.R, portfolio = GMV, rebalance_on = "months", optimize_method = "ROI")
port.data.asset.CCY <- Return.portfolio(R = asset.CCY.R, weights = extractWeights(opt.asset.CCY), verbose=TRUE)
#optimization in base.CCY
opt.base.CCY = optimize.portfolio.rebalancing(R = base.CCY.R, portfolio = GMV, rebalance_on = "months", optimize_method = "ROI")
port.data.base.CCY <- Return.portfolio(R = base.CCY.R, weights = extractWeights(opt.base.CCY), verbose=TRUE)
opt.base.CCY.simple = optimize.portfolio(R = base.CCY.R, portfolio = GMV, optimize_method = "ROI")
port.data.base.CCY.simple <- Return.portfolio(R = base.CCY.R, weights = extractWeights(opt.base.CCY.simple), verbose=TRUE)
#--------- START EDIT 04.01.2016 ---------
#
class(index(asset.CCY.R))
indexTZ(asset.CCY.R)
# Indexed by Class Date - XTS though shows TZ = UTC
# this should however not cause any issues
all.equal(index(asset.CCY.R), index(base.CCY.R))
# TRUE
# Weights after rebalancing
class(index(extractWeights(opt.asset.CCY)))
# "POSIXct" "POSIXt" which should be fine as well
all.equal(index(extractWeights(opt.asset.CCY)),
index(extractWeights(opt.base.CCY)))
# TRUE
#
#--------- END EDIT 04.01.2016 ---------
# charts
#
op <- par(mfrow = c(2, 2), pty = "m")
# chart the time series
chart.TimeSeries(asset.CCY.prices, main = 'Timeseries - Asset.CCY', legend.loc = "top")
# chart the performance summaries
chart.CumReturns(asset.CCY.R, main = "Cum Returns - Asset.CCY")
# chart the time series
chart.TimeSeries(base.CCY.prices, main = 'Base.CCY', legend.loc = "top")
# chart the performance summaries
chart.CumReturns(base.CCY.R, main = "Cum Returns - Base.CCY")
# Optimized returns - rebalancing
chart.CumReturns(port.data.asset.CCY$returns, main=paste("Asset.CCY", "Opt Return" , sep= " - "))
chart.CumReturns(port.data.base.CCY$returns, main=paste("Base.CCY", "Opt Return" , sep= " - "))
# Optimized returns - no rebalancing
chart.CumReturns(port.data.base.CCY.simple$returns, main=paste("Base.CCY.simple", "Opt Return" , sep= " - "))
# optimizes EOP Value Charts
chart.StackedBar(port.data.asset.CCY$EOP.Value[ , !apply(port.data.asset.CCY$EOP.Value==0,2,all)], main=paste("Asset.CCY", "Value" , sep= " - "))
chart.StackedBar(port.data.base.CCY$EOP.Value[ , !apply(port.data.base.CCY$EOP.Value==0,2,all)], main=paste("Base.CCY", "Value" , sep= " - "))
# Optimized EOP Value - no rebalancing
chart.StackedBar(port.data.base.CCY.simple$EOP.Value[ , !apply(port.data.base.CCY.simple$EOP.Value==0,2,all)], main=paste("Base.CCY.simple", "Value" , sep= " - "))
# optmized Contribution charts
chart.StackedBar(port.data.asset.CCY$contribution[ , !apply(port.data.asset.CCY$contribution==0,2,all)], main=paste("Asset.CCY", "Contribution" , sep= " - "))
chart.StackedBar(port.data.base.CCY$contribution[ , !apply(port.data.base.CCY$contribution==0,2,all)], main=paste("Base.CCY", "Contribution" , sep= " - "))
# optmized Contribution charts - no rebalancing
chart.StackedBar(port.data.base.CCY.simple$contribution[ , !apply(port.data.base.CCY.simple$contribution==0,2,all)], main=paste("Base.CCY.simple", "Contribution" , sep= " - "))
# restore usual charting
par(op)
sessionInfo()
R version 3.2.2 (2015-08-14)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 8 x64 (build 9200)
locale:
[1] LC_COLLATE=English_United Kingdom.1252 LC_CTYPE=English_United Kingdom.1252 LC_MONETARY=English_United Kingdom.1252
[4] LC_NUMERIC=C LC_TIME=English_United Kingdom.1252
attached base packages:
[1] parallel stats graphics grDevices utils datasets methods base
other attached packages:
[1] PortfolioAnalytics_1.0.3636 XML_3.98-1.3 FinancialInstrument_1.2.0 RColorBrewer_1.1-2
[5] reshape2_1.4.1 doParallel_1.0.10 iterators_1.0.8 rCharts_0.4.5
[9] RcppDE_0.1.4 timeDate_3012.100 sqldf_0.4-10 RSQLite_1.0.0
[13] DBI_0.3.1 gsubfn_0.6-6 proto_0.3-10 stringr_1.0.0
[17] ggplot2_1.0.1 xlsx_0.5.7 xlsxjars_0.6.1 rJava_0.9-7
[21] quantmod_0.4-5 TTR_0.23-0 Quandl_2.7.0 ProjectTemplate_0.6
[25] PerformanceAnalytics_1.4.3541 foreach_1.4.3 xts_0.9-7 zoo_1.7-12
编辑 2016年 4 月 1 日刚刚再次研究了这个问题,因为重新命名的测试组合仍然存在问题。
我认为该问题可能是由某些时区影响引起的,并添加了一些检查(参见图表之前的代码)。return-xts-objects 和 weight-xts-objects 的索引保持相等(all.equal() 返回 TRUE)。
调试到 return.portfolio() 函数,
- base.CCY.R 和asset.CCY.R 被标识为每月频率和开始日期2004-03-30。
- 权重被标识为 xts(第 55 行),并提供以下第一个索引。
以下检查对于资产或基础货币的权重都是相等的:
Sys.timezone()
# [1] "Europe/Berlin"
first(index(weights))
# [1] "2007-03-30 CEST"
as.numeric(first(index(weights)))
# [1] 1175205600
class(index(weights))
# [1] "POSIXct" "POSIXt"
as.Date(first(index(weights))
# [1] "2007-03-29"
根据 as.Date 的帮助页面,这假定“POSIXct”为“UTC”,因此我假设存在差异,但以下也返回 29:
as.Date(first(index(weights), tz = "Europe/Berlin"))
# [1] "2007-03-29"
这对我来说并不清楚,但它对于权重 xts 是一致的,而不是这个问题的一部分。
在第 72 行,return.portfolio 现在通过执行以下命令将返回 xts 减少到相同的起始索引:
R <- R[paste0(as.Date(index(weights[1, ])) + 1, "/")]
在此之后,调用 Return.portfolio.geometric() 并在此处显示重新命名价格中优化的权重导致权重低于最大值 1.01。
也可以通过对提取的权重执行 rowSum() 来查看:
#--------- START EDIT 04.01.2016 ---------
rowSums(extractWeights(opt.asset.CCY))
# [1] 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01
# [12] 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01
# [23] 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01
# [34] 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01
# [45] 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01
# [56] 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01
# [67] 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01
# [78] 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01
# [89] 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01 1.01
# [100] 1.01 1.01 1.01 1.01 1.01 1.01 1.01
rowSums(extractWeights(opt.base.CCY))
# [1] 0.9000000 0.9000000 0.9000000 0.9000000 0.9000000
# [6] 0.9000000 0.9000000 0.9000000 0.9000000 0.9000000
# [11] 0.9000000 0.9000000 0.9000000 0.9000000 0.9000000
# [16] 0.9000000 0.9000000 0.9000000 0.9000000 1.0100000
# [21] 1.0100000 1.0100000 1.0100000 1.0100000 1.0100000
# [26] 1.0100000 0.9000000 0.9000000 0.9000000 0.9000000
# [31] 0.9000000 0.9000000 0.9000000 0.9000000 0.9937606
# [36] 1.0100000 1.0100000 1.0100000 1.0100000 1.0100000
# [41] 1.0100000 1.0100000 1.0100000 1.0100000 1.0100000
# [46] 1.0100000 1.0100000 1.0100000 0.9666945 0.9000000
# [51] 1.0100000 1.0100000 1.0100000 1.0100000 1.0100000
# [56] 1.0100000 1.0100000 1.0100000 1.0100000 1.0100000
# [61] 1.0100000 1.0100000 1.0100000 1.0100000 1.0100000
# [66] 1.0100000 1.0100000 1.0100000 1.0100000 1.0100000
# [71] 1.0100000 1.0100000 1.0100000 1.0100000 1.0100000
# [76] 1.0100000 1.0100000 1.0100000 1.0100000 1.0100000
# [81] 1.0100000 1.0100000 1.0100000 1.0100000 1.0100000
# [86] 1.0100000 1.0100000 1.0100000 1.0100000 1.0100000
# [91] 1.0100000 1.0100000 1.0100000 1.0100000 1.0100000
# [96] 1.0100000 1.0100000 1.0100000 1.0100000 1.0100000
# [101] 1.0100000 1.0100000 1.0100000 1.0100000 1.0100000
# [106] 1.0100000
#--------- END EDIT 04.01.2016 ---------
编辑 06/01/2016
在进一步查看由 Return.portfolio() 调用的 PerformanceAnaytics 包中的 Return.portfolio.geometric() 代码后:
- 迭代和返回计算
在第 49 行:
ret[k] = eop_value_total[k]/end_value - 1
这将计算投资组合回报,在示例中它将是 0.877/1 - 1,这显然必须导致 -12.3% 的回报。
如果我没看错,这意味着期望权重等于 100%,因为 end_value 如果用户在 Return.portfolio() 调用期间的值设置中未提供,则设置为 1 或 100%。
下一次迭代的 end_value 为 0.877
end_value = eop_value_total[k]
- 迭代并返回计算
下一期的新投资组合起始值 0.877 乘以下一期的权重 90% 或 0.90 = 0.7893
bop_value[k, ] = end_value * weights[i, ]
与第一次迭代相同,由于 weight_sum 约束,PortfolioAnalytics 包计算的总权重为 90%,因此负回报将再次被夸大。
ret[k] = eop_value_total[k]/end_value - 1
转换为 0.78/0.877 - 1 = -0.111
当时我的问题是:
- 当权重为 <> 100% 或 1 时,如何计算正确的回报?
- <> 100% 的 weight_sum 约束真的有意义吗?在现实生活中,缺失的百分比可能是现金/短期债券?
- 基于此 - 选项可能是在使用 full_investment 约束时将现金回报添加到优化中,或者调整几何回报函数以警告权重 <> 100% 或包含权重 <> 100% 的逻辑不是一个选项?
最后但并非最不重要的一点是,或者我的日期索引仍然有问题?
不确定是否有人仍在阅读本文,但再次感谢任何支持或见解。非常感谢!