sim_portfoptim | R Documentation |
Simulate a portfolio optimization strategy using online (recursive) updating of the covariance matrix.
sim_portfoptim(rets, dimax, lambda, lambdacov, lambdaw)
rets |
A time series or matrix of asset returns. |
dimax |
An integer equal to the number of eigen
values used for calculating the reduced inverse of the covariance
matrix (the default is |
lambda |
A decay factor which multiplies the past asset returns. |
lambdacov |
A decay factor which multiplies the past covariance. |
lambdaw |
A decay factor which multiplies the past portfolio weights. |
The function sim_portfoptim()
simulates a portfolio optimization
strategy. The strategy calculates the maximum Sharpe portfolio weights
in-sample at every point in time, and applies them in the
out-of-sample time interval. It updates the trailing covariance
matrix recursively, instead of using past batches of data. The function
sim_portfoptim()
uses three different decay factors for averaging past
values, to reduce the variance of its forecasts.
The function sim_portfoptim()
first scales the returns by their
trailing volatilities:
r^s_t = \frac{r_t}{\sigma_{t-1}}
Returns scaled by their volatility are more stationary so they're easier to model.
Then at every point in time, the function sim_portfoptim()
calls
the function HighFreq::push_covar()
to update the trailing
covariance matrix of the returns:
\bar{r}_t = \lambda_c \bar{r}_{t-1} + (1-\lambda_c) r^s_t
\hat{r}_t = r^s_t - \bar{r}_t
{cov}_t = \lambda_c {cov}_{t-1} + (1-\lambda_c) \hat{r}^T_t \hat{r}_t
Where \lambda_c
is the decay factor which multiplies the past mean
and covariance.
It then calls the function HighFreq::calc_inv()
to calculate the
reduced inverse of the covariance matrix using its eigen
decomposition:
\strong{C}^{-1} = \strong{O}_{dimax} \, \Sigma^{-1}_{dimax} \, \strong{O}^T_{dimax}
See the function HighFreq::calc_inv()
for details.
It then calculates the in-sample weights of the maximum Sharpe portfolio, by multiplying the inverse covariance matrix times the trailing means of the asset returns:
\bar{r}_t = \lambda \bar{r}_{t-1} + (1-\lambda) r^s_t
\strong{w}_t = \strong{C}^{-1} \bar{r}_t
Note that the decay factor \lambda
is different from the decay
factor \lambda_c
used for updating the trailing covariance
matrix.
It then scales the weights so their sum of squares is equal to one:
\strong{w}_t = \frac{\strong{w}_t}{\sqrt{\sum{\strong{w}^2_t}}}
It then calculates the trailing mean of the weights:
\bar{\strong{w}}_t = \lambda_w \bar{\strong{w}}_{t-1} + (1-\lambda_w) \strong{w}_t
Note that the decay factor \lambda_w
is different from the decay
factor \lambda
used for updating the trailing means.
It finally calculates the out-of-sample portfolio returns by multiplying the trailing mean weights times the scaled asset returns:
r^p_t = \bar{\strong{w}}_{t-1} r^s_t
Applying weights to scaled returns means trading stock amounts with unit
dollar volatility. So if the weight is equal to 2
then we should
purchase an amount of stock with dollar volatility equal to 2
dollars. Trading stock amounts with unit dollar volatility improves
portfolio diversification.
The function sim_portfoptim()
uses three different decay factors
for averaging past values, to reduce the variance of its forecasts. The
value of the decay factor \lambda
must be in the range between
0
and 1
.
If \lambda
is close to 1
then the decay is weak and past
values have a greater weight, so the trailing values have a greater
dependence on past data. This is equivalent to a long look-back
interval.
If \lambda
is much less than 1
then the decay is strong and
past values have a smaller weight, so the trailing values have a weaker
dependence on past data. This is equivalent to a short look-back
interval.
The function sim_portfoptim()
returns multiple columns of data,
with the same number of rows as the input argument rets
. The first
column contains the strategy returns and the remaining columns contain the
portfolio weights.
A matrix of strategy returns and the portfolio weights, with
the same number of rows as the argument rets
.
## Not run:
# Load ETF returns
retp <- rutils::etfenv$returns[, c("VTI", "TLT", "DBC", "USO", "XLF", "XLK")]
retp <- na.omit(retp)
datev <- zoo::index(retp) # dates
# Simulate a portfolio optimization strategy
dimax <- 6
lambdaf <- 0.978 # Decay factor
lambdacov <- 0.995
lambdaw <- 0.9
pnls <- HighFreq::sim_portfoptim(retp, dimax, lambdaf, lambdacov, lambdaw)
colnames(pnls) <- c("pnls", "VTI", "TLT", "DBC", "USO", "XLF", "XLK")
pnls <- xts::xts(pnls, order.by=datev)
# Plot dygraph of strategy
wealthv <- cbind(retp$VTI, pnls$pnls*sd(retp$VTI)/sd(pnls$pnls))
colnames(wealthv) <- c("VTI", "Strategy")
endd <- rutils::calc_endpoints(wealthv, interval="weeks")
dygraphs::dygraph(cumsum(wealthv)[endd], main="Portfolio Optimization Strategy Returns") %>%
dyOptions(colors=c("blue", "red"), strokeWidth=2) %>%
dyLegend(width=300)
# Plot dygraph of weights
symbolv <- "VTI"
stockweights <- cbind(cumsum(get(symbolv, retp)), get(symbolv, pnls))
colnames(stockweights)[2] <- "Weight"
colnamev <- colnames(stockweights)
endd <- rutils::calc_endpoints(pnls, interval="weeks")
dygraphs::dygraph(stockweights[endd], main="Returns and Weight") %>%
dyAxis("y", label=colnamev[1], independentTicks=TRUE) %>%
dyAxis("y2", label=colnamev[2], independentTicks=TRUE) %>%
dySeries(axis="y", label=colnamev[1], strokeWidth=2, col="blue") %>%
dySeries(axis="y2", label=colnamev[2], strokeWidth=2, col="red")
## End(Not run)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.