sim_portfoptim: Simulate a portfolio optimization strategy using online...

View source: R/RcppExports.R

sim_portfoptimR Documentation

Simulate a portfolio optimization strategy using online (recursive) updating of the covariance matrix.

Description

Simulate a portfolio optimization strategy using online (recursive) updating of the covariance matrix.

Usage

sim_portfoptim(rets, dimax, lambda, lambdacov, lambdaw)

Arguments

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 dimax = 0 - standard matrix inverse using all the eigen values).

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.

Details

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.

Value

A matrix of strategy returns and the portfolio weights, with the same number of rows as the argument rets.

Examples

## 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
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)


algoquant/HighFreq documentation built on Feb. 9, 2024, 8:15 p.m.