Acorns Backtest
This is a repost of an old article that I did as an Acorns backtest. Only difference is that that time I did it in RStudio and exported it out as html. My static blog generator under Windows Linux Subsystem does not like it. This is a port over to Jupyter Notebook which Nicola natively supports.
Synopsis
I have been an investor in Acorns since around September 2017. I learned about Acorns as a “Millenial savings account.” I did a “why not” investing. Plus I like squirreling away money that is not visible to me or others.
I had chosen the “Aggressive Portfolio” instead of the recommended “Conservative Portfolio.” The reason was that I was adviced long ago that my Social Security and Pension was my bond fund.
Purpose of this post is to backtest the Acorns investment style in a reproducible way reasonably. I will admit that the backtest is flawed and oversimplified. Acorns rebalances every time you add money Acorns. Also, Acorns does a full rebalance every quarter. I will not capture tax implications. The starting date will be from January 1st, 2011 till the time of this post. The reason is that the first data for VOO is September 2010. I hope that this will have value for you.
- Large Company - VOO
- Small Company - VB
- Developed Market - VEA
- Emerging Market - VWO
- Real Estate - VNQ
- Corporate Bond - LQD
- Government Bond - SHY
- Spiders - SPY benchmark.
- ^IRX - 13 WEEK TREASURY BILL as risk free rate
First lets get the data…
library(quantmod)
library(tseries)
library(PerformanceAnalytics)
library(xts)
library(xtable)
library(lubridate)
spy <-get.hist.quote(instrument="spy",start="2011-01-01",end="2020-01-01",quote="AdjClose",compression="d")
voo <-get.hist.quote(instrument="voo",start="2011-01-01",end="2020-01-01",quote="AdjClose",compression="d")
vb <-get.hist.quote(instrument="vb",start="2011-01-01",end="2020-01-01",quote="AdjClose",compression="d")
vea <-get.hist.quote(instrument="vea",start="2011-01-01",end="2020-01-01",quote="AdjClose",compression="d")
vwo <-get.hist.quote(instrument="vwo",start="2011-01-01",end="2020-01-01",quote="AdjClose",compression="d")
vnq <-get.hist.quote(instrument="vnq",start="2011-01-01",end="2020-01-01",quote="AdjClose",compression="d")
lqd <-get.hist.quote(instrument="lqd",start="2011-01-01",end="2020-01-01",quote="AdjClose",compression="d")
shy <-get.hist.quote(instrument="shy",start="2011-01-01",end="2020-01-01",quote="AdjClose",compression="d")
tbill <- get.hist.quote(instrument="^irx",start="2011-01-01",end="2020-01-01",quote="AdjClose",compression="d")
Benchmarks
The SP500 tends to evaluate the market and market managers. I plan to use SPY since that is my benchmark. SPY is something that I can buy, and it is the litmus test to whether I put in work to manage my portfolio or buy SPY and forget about it.
Lastly only 20% of active managers beat it
Long-term, the numbers were not much better in other categories
like small-cap stocks or fixed income: “Over long-term horizons,
80 percent or more of active managers across all categories
underperformed their respective benchmarks,” the report concluded.
For the [risk free rate][https://www.investopedia.com/terms/r/risk-freerate.asp>] I plan to use the total returns for the 13 WEEK TREASURY BILL (^IRX)
Before going over the Acorn porfolios, lets do the analysis of SPY first.
returns.tbil <- na.omit(ROC(as.xts(tbill),1,"discrete"))
returns.spy <- na.omit(ROC(as.xts(spy),1,"discrete"))
names(returns.spy) <- c("Spyders")
chart.CumReturns(returns.spy,
wealth.index=TRUE,
legend.loc="bottomright",
main="Growth of $1 investment",
ylab="$")
charts.PerformanceSummary(returns.spy,
wealth.index=TRUE,
legend.loc="bottomright",
main="Growth of $1 investment",Rf = tbil.return)
# Compare return/risk
knitr::kable(rbind(table.AnnualizedReturns(returns.spy),SharpeRatio.annualized(returns.spy),CalmarRatio(returns.spy)))
knitr::kable(table.Drawdowns(returns.spy))
qqnorm(returns.spy,main="Spyders")
qqline(returns.spy,col="red")
chart.RollingPerformance(returns.spy,width=252,
legend.loc="bottomright",
main="Rolling 1yr % returns")
Portfolios
Acorns has five portfolios
Conservative
w <- c(.12, .02, .02, .40, .40, .04)
portfolio.prices <- as.xts(merge(voo,vb,vnq,shy,lqd,vea))
portfolio.returns <- na.omit(ROC(portfolio.prices,1,"discrete"))
colnames(portfolio.returns) <- c("voo","vb","vnq","shy","lqd","vea")
# calculate portfolio total returns
# rebalanced portfolio
# calculate portfolio total returns
# rebalanced portfolio
portfolio.conservative <-Return.portfolio(portfolio.returns,
rebalance_on="months",
weights=w,wealth.index=TRUE,verbose=TRUE)
returns <- portfolio.conservative$returns
names(returns) <- c("Conservative Portfolio")
# merge portfolio returns into one dataset
# label columns
# names(portfolio) <-c("Conservative")
chart.CumReturns(returns,
wealth.index=TRUE,
legend.loc="bottomright",
main="Growth of $1 investment",
ylab="$")
knitr::kable(rbind(table.AnnualizedReturns(returns),SharpeRatio.annualized(returns),CalmarRatio(returns)))
knitr::kable(table.Drawdowns(returns))
# Compare portfolio return distribution vs. normal distribution
qqnorm(returns,main="Conservative Portfolio")
qqline(returns,col="red")
# Compare rolling 1-year returns
chart.RollingPerformance(returns,width=252,
legend.loc="bottomright",
main="Rolling 1yr % returns")
Moderately Conservative
w <- c(.24, .04, .04, .30, .30, .08)
portfolio.prices <- as.xts(merge(voo,vb,vnq,shy,lqd,vea))
portfolio.returns <- na.omit(ROC(portfolio.prices,1,"discrete"))
colnames(portfolio.returns) <- c("voo","vb","vnq","shy","lqd","vea")
portfolio.moderately_conservative <-Return.portfolio(portfolio.returns,
rebalance_on="months",
weights=w,wealth.index=TRUE,verbose=TRUE)
returns <- portfolio.moderately_conservative$returns
names(returns) <- c("Moderately Conservative Portfolio")
# merge portfolio returns into one dataset
# label columns
# names(portfolio) <-c("Moderately Conservative")
chart.CumReturns(returns,
wealth.index=TRUE,
legend.loc="bottomright",
main="Growth of $1 investment",
ylab="$")
knitr::kable(rbind(table.AnnualizedReturns(returns),SharpeRatio.annualized(returns),CalmarRatio(returns)))
knitr::kable(table.Drawdowns(returns))
# Compare portfolio return distribution vs. normal distribution
qqnorm(returns,main="Moderately Conservative Portfolio")
qqline(returns,col="red")
# Compare rolling 1-year returns
chart.RollingPerformance(returns,width=252,
legend.loc="bottomright",
main="Rolling 1yr % returns")
Moderate
w <- c(.29, .10, .03, .06, .20,.20,.12)
portfolio.prices <- as.xts(merge(voo,vb,vwo,vnq,shy,lqd,vea))
portfolio.returns <- na.omit(ROC(portfolio.prices,1,"discrete"))
colnames(portfolio.returns) <- c("voo","vb","vwo","vnq","shy","lqd","vea")
portfolio.moderate <-Return.portfolio(portfolio.returns,
rebalance_on="months",
weights=w,wealth.index=TRUE,verbose=TRUE)
returns <- portfolio.moderate$returns
names(returns) <- c("Moderate Portfolio")
# merge portfolio returns into one dataset
# label columns
# names(portfolio) <-c("Moderately")
chart.CumReturns(returns,
wealth.index=TRUE,
legend.loc="bottomright",
main="Growth of $1 investment",
ylab="$")
knitr::kable(rbind(table.AnnualizedReturns(returns),SharpeRatio.annualized(returns),CalmarRatio(returns)))
knitr::kable(table.Drawdowns(returns))
# Compare portfolio return distribution vs. normal distribution
qqnorm(returns,main="Moderate Portfolio")
qqline(returns,col="red")
# Compare rolling 1-year returns
chart.RollingPerformance(returns,width=252,
legend.loc="bottomright",
main="Rolling 1yr % returns")
Moderately Aggressive
w <- c(.38, .14, .04, .08, .10,.10,.16)
portfolio.prices <- as.xts(merge(voo,vb,vwo,vnq,shy,lqd,vea))
portfolio.returns <- na.omit(ROC(portfolio.prices,1,"discrete"))
colnames(portfolio.returns) <- c("voo","vb","vwo","vnq","shy","lqd","vea")
portfolio.moderately_aggressive <-Return.portfolio(portfolio.returns,
rebalance_on="months",
weights=w,wealth.index=TRUE,verbose=TRUE)
returns <- portfolio.moderately_aggressive$returns
names(returns) <- c("Moderate Aggressive Portfolio")
# merge portfolio returns into one dataset
# label columns
chart.CumReturns(returns,
wealth.index=TRUE,
legend.loc="bottomright",
main="Growth of $1 investment",
ylab="$")
knitr::kable(rbind(table.AnnualizedReturns(returns),SharpeRatio.annualized(returns),CalmarRatio(returns)))
knitr::kable(table.Drawdowns(returns))
# Compare portfolio return distribution vs. normal distribution
qqnorm(returns,main="Moderate Aggressive Portfolio")
qqline(returns,col="red")
# Compare rolling 1-year returns
chart.RollingPerformance(returns,width=252,
legend.loc="bottomright",
main="Rolling 1yr % returns")
Aggressive
w <- c(.40, .20, .10, .10, .20)
portfolio.prices <- as.xts(merge(voo,vb,vwo,vnq,vea))
portfolio.returns <- na.omit(ROC(portfolio.prices,1,"discrete"))
colnames(portfolio.returns) <- c("voo","vb","vwo","vnq","vea")
portfolio.Aggressive <-Return.portfolio(portfolio.returns,
rebalance_on="months",
weights=w,wealth.index=TRUE,verbose=TRUE)
returns <- portfolio.Aggressive$returns
names(returns) <- c("Aggressive Portfolio")
# merge portfolio returns into one dataset
# label columns
chart.CumReturns(returns,
wealth.index=TRUE,
legend.loc="bottomright",
main="Growth of $1 investment",
ylab="$")
knitr::kable(rbind(table.AnnualizedReturns(returns),SharpeRatio.annualized(returns),CalmarRatio(returns)))
knitr::kable(table.Drawdowns(returns))
# Compare portfolio return distribution vs. normal distribution
qqnorm(returns,main="Aggressive Portfolio")
qqline(returns,col="red")
# Compare rolling 1-year returns
chart.RollingPerformance(returns,width=252,
legend.loc="bottomright",
main="Rolling 1yr % returns")
Summary
acorns.ports <- cbind(portfolio.conservative$returns,portfolio.moderately_conservative$returns,portfolio.moderate$returns,portfolio.moderately_aggressive$returns,portfolio.Aggressive$returns,returns.spy,returns.tbil)
names(acorns.ports) <- c("Conservative","Moderately Conservative","Moderate","Moderately Aggressive","Aggressive","Spyders","US 3 Month")
knitr::kable(table.CAPM(acorns.ports[,1:5],acorns.ports[,6],Rf = acorns.ports[,7]))
charts.PerformanceSummary(acorns.ports[,1:6],wealth.index = TRUE,Rf = acorns.ports[,7],main = "Summary of Acorn's Performance")
Biased and un professional Observations
In progress
Comments
Comments powered by Disqus