Adaptive Asset Allocation Extended

This post extends the replication from the Adaptive Asset Allocation Replication post by running the analysis on OOS (out-of-sample) data from 2015 through 2023. Thanks to Dale Rosenthal for helpful comments.

The paper uses the 5 portfolios below. Each section of this post will give a short description of the portfolio construction and then focus on comparing the OOS results with the replicated and original results. See the other post for details on the data and portfolio construction methodologies.

  1. Equal weight of all asset classes
  2. Equal risk contribution of all asset classes
  3. Equal weight of highest momentum asset classes
  4. Equal risk contribution of highest momentum asset classes
  5. Minimum variance of highest momentum asset classes

The table below summarizes the date ranges for each sample period in this post.

PeriodDate Range
ReplicationFeb 1996 - Dec 2014
OOSJan 2015 - Dec 2023
2015-2021Jan 2015 - Dec 2021
FullFeb 1996 - Dec 2023

This post uses the ftblog package. You can install it using the remotes::install_github() function in the code block below. First we need to setup our environment with the necessary packages, data, and functions.

# remotes::install_github("joshuaulrich/ftblog")
suppressPackageStartupMessages({
    library(ftblog)
    library(PerformanceAnalytics)
})

data(aaa_returns, package = "ftblog")
returns <- aaa_returns[, -1]    # no cash
r_rep   <- returns["/2014"]
r_oos   <- returns["2014-07/"]  # need 6 months for 120-day lags (this is 128 days)
r_full  <- returns

# calculate strategy statistics
strat_summary <-
function(returns,
         original_results = NULL)
{
    stats <- table.AnnualizedReturns(returns)
    stats <- rbind(stats,
                   "Worst Drawdown" = -maxDrawdown(returns))

    if (!is.null(original_results)) {
        stats <- cbind(original_results, stats)
        colnames(stats)[1] <- "Original"
    }

    stats <- round(stats, 3)

    return(stats)
}

chart_performance <-
function(R,
         title = "Performance")
{
    stopifnot(all(c("Replication", "OOS") %in% colnames(R)))
    r <- R[, c("Replication", "OOS")]
    p <- chart.CumReturns(r,
                          main = title,
                          main.timespan = FALSE,
                          yaxis.right = TRUE)
    p <- addLegend("topleft", lty = 1, lwd = 1)
    p <- addSeries(r[,1], type = "h", main = "Return")
    p <- addSeries(r[,2], type = "h", on = 0, col = "red")
    p <- addSeries(Drawdowns(r), main = "Drawdown")
    p
}

1. Equal weight portfolio of all asset classes

This portfolio assumes no knowledge of expected relative asset class performance, risk, or correlation. It holds each asset class in equal weight and is rebalanced monthly.

rr_equal_weight <- as.xts(apply(returns["/2014"], 1, mean))
ro_equal_weight <- as.xts(apply(returns["2015/"], 1, mean))
rf_equal_weight <- as.xts(apply(returns, 1, mean))

monthly_returns <-
  merge(Replication = to_monthly_returns(rr_equal_weight),
        OOS = to_monthly_returns(ro_equal_weight),
        "2015-2021" = to_monthly_returns(ro_equal_weight["2015/2021"]),
        Full = to_monthly_returns(rf_equal_weight),
        check.names = FALSE)

stats <- strat_summary(monthly_returns)
chart_performance(monthly_returns, "All Assets - Equal Weight")

ReplicationOOS2015-2021Full
Annualized Return0.0790.0490.0720.069
Annualized Std Dev0.1150.1070.0910.112
Annualized Sharpe (Rf=0%)0.6840.4560.7940.614
Worst Drawdown-0.377-0.210-0.136-0.377

The OOS annualized return is significantly less than the prior results. This is largely due to the -21.0% drawdown that started in 2022 and is still ongoing. Note that the full-period results are very similar to the replication results, though the 2022 drawdown did decrease the annualized return by ~1%.

Note that this portfolio’s results from 2015-2021 are very similar to the replication results through the end of 2014. That suggests the 2022 bear market is the main cause for the lower return in the OOS results.

2. Equal risk contribution using all asset classes

The next portfolio assumes the investor has some knowledge of each asset’s risk, but still no knowledge of relative performance or correlations. So each asset in this portfolio is given a weight proportional to its historical relative risk, with the hope that each asset will contribute the same amount of risk to the overall portfolio in the future.

rr_equal_risk <- portf_return_equal_risk(r_rep, 120, 60)
ro_equal_risk <- portf_return_equal_risk(r_oos, 120, 60)
rf_equal_risk <- portf_return_equal_risk(r_full, 120, 60)

monthly_returns <-
  merge(to_monthly_returns(rr_equal_risk),
        to_monthly_returns(ro_equal_risk["2015/"]),
        to_monthly_returns(ro_equal_risk["2015/2021"]),
        to_monthly_returns(rf_equal_risk))
colnames(monthly_returns) <- c("Replication", "OOS", "2015-2021", "Full")

stats <- strat_summary(monthly_returns)
chart_performance(monthly_returns, "All Assets - Equal Risk")

ReplicationOOS2015-2021Full
Annualized Return0.0860.0340.0560.069
Annualized Std Dev0.0730.0820.0610.076
Annualized Sharpe (Rf=0%)1.1770.4110.9080.903
Worst Drawdown-0.142-0.194-0.071-0.194

Like the equal weight portfolio, this portfolio’s OOS annualized return is significantly lower than the replication results. This methodology only slightly reduced the 2022 drawdown to -19.4% from -21.0%. The maximum drawdown is now in 2022 instead of during the 2008 financial crisis.

In the replication, the equal risk contribution portfolio results are better than the equal weight portfolio, but the OOS equal risk portfolio did not show similar improvement. Even when 2022 is excluded, the OOS equal risk portfolio didn’t show improvement over the equal weight portfolio.

3. Equal weight portfolio of highest momentum asset classes

The next portfolio assumes the investor has some knowledge of each asset’s returns, but still no knowledge of risk or correlations. Asset returns are based on 6-month momentum (approximately 120 days). Momentum is re-estimated every month and only the top 5 assets are included in the portfolio.

rr_momo_eq_wt <- portf_return_momo(r_rep, 5, 120)
ro_momo_eq_wt <- portf_return_momo(r_oos, 5, 120)
rf_momo_eq_wt <- portf_return_momo(r_full, 5, 120)

monthly_returns <-
  merge(to_monthly_returns(rr_momo_eq_wt),
        to_monthly_returns(ro_momo_eq_wt["2015/"]),
        to_monthly_returns(ro_momo_eq_wt["2015/2021"]),
        to_monthly_returns(rf_momo_eq_wt))
colnames(monthly_returns) <- c("Replication", "OOS", "2015-2021", "Full")

stats <- strat_summary(monthly_returns)
chart_performance(monthly_returns, "Top 5 Momentum Assets - Equal Weight")

ReplicationOOS2015-2021Full
Annualized Return0.1420.0510.0810.112
Annualized Std Dev0.1140.1040.0920.111
Annualized Sharpe (Rf=0%)1.2430.4880.8841.001
Worst Drawdown-0.199-0.213-0.114-0.213

Again, the OOS annualized return is significantly worse than the replicated results. The OOS results for this portfolio show improvement in the Sharpe Ratio versus the equal risk contribution portfolio (2). The replicated results for this portfolio showed similar improvements versus portfolio (2).

In the replication, equal weight momentum results are better than the equal risk portfolio. But the OOS equal weight momentum portfolio did not show significant improvement versus the equal risk portfolio (2), and is roughly the same as the equal weight portfolio (1).

4. Equal risk contribution portfolio of highest momentum asset classes

The previous two portfolios estimated asset weights using either risk-based or momentum-based weights. This next portfolio combines estimates of momentum-based performance and accounts for asset class risk differences. It includes the top 5 asset classes based on 6-month returns and weights them using the same equal risk contribution method as portfolio (2).

rr_momo_eq_risk <- portf_return_momo_equal_risk(r_rep, 5, 120, 60)
ro_momo_eq_risk <- portf_return_momo_equal_risk(r_oos, 5, 120, 60)
rf_momo_eq_risk <- portf_return_momo_equal_risk(r_full, 5, 120, 60)

monthly_returns <-
  merge(to_monthly_returns(rr_momo_eq_risk),
        to_monthly_returns(ro_momo_eq_risk["2015/"]),
        to_monthly_returns(ro_momo_eq_risk["2015/2021"]),
        to_monthly_returns(rf_momo_eq_risk))
colnames(monthly_returns) <- c("Replication", "OOS", "2015-2021", "Full")

stats <- strat_summary(monthly_returns)
chart_performance(monthly_returns, "Top 5 Momentum Assets - Equal Risk")

ReplicationOOS2015-2021Full
Annualized Return0.1370.0500.0810.108
Annualized Std Dev0.1020.0950.0810.100
Annualized Sharpe (Rf=0%)1.3350.5280.9911.076
Worst Drawdown-0.119-0.204-0.086-0.204

It’s clear that the major cause of the poorer OOS performance of this portfolio is due to how it handled the 2022 bear market. This portfolio handled the 2008 financial crisis very well, but it offered almost no protection in 2022. This indicates there was a fundamental difference in 2008 versus 2022 in the asset classes held by this portfolio.

Similar to the replicated results, the reduction in risk is the main benefit of this portfolio versus the equal weight momentum portfolio (3). That said, the OOS performance of this portfolio only showed marginal improvement versus portfolio (3). Even more notable, this portfolio didn’t improve returns versus the simple equal weight portfolio (1) during the OOS period like it did for the replication period.

5. Minimum variance portfolio of highest momentum asset classes

The final portfolio takes the above concepts and adds correlation estimates to the portfolio optimization. The previous portfolios only accounted for the relative risk between the asset classes, but not the correlation between the assets’ returns. This portfolio accounts for the correlations between asset classes by finding the minimum variance portfolio.

rr_momo_min_var <- portf_return_momo_min_var(r_rep, 5, 120, 60, "above average")
ro_momo_min_var <- portf_return_momo_min_var(r_oos, 5, 120, 60, "above average")
rf_momo_min_var <- portf_return_momo_min_var(r_full, 5, 120, 60, "above average")

monthly_returns <-
  merge(to_monthly_returns(rr_momo_min_var),
        to_monthly_returns(ro_momo_min_var["2015/"]),
        to_monthly_returns(ro_momo_min_var["2015/2021"]),
        to_monthly_returns(rf_momo_min_var))
colnames(monthly_returns) <- c("Replication", "OOS", "2015-2021", "Full")

stats <- strat_summary(monthly_returns)
chart_performance(monthly_returns, "Above Average 6mo Momentum - Min Var")

ReplicationOOS2015-2021Full
Annualized Return0.1300.0580.0820.106
Annualized Std Dev0.0990.0950.0810.098
Annualized Sharpe (Rf=0%)1.3150.6121.0081.086
Worst Drawdown-0.112-0.162-0.083-0.162

Recall that the original results for portfolio (5) showed improved return and lower maximum drawdown versus portfolio (4), while the replicated results were almost the same for both portfolios. The OOS results for these two portfolios are also very similar. In the 2015-2021 period, portfolio (5) has a slightly higher return and Sharpe ratio and lower max drawdown than portfolio (4).

Conclusion

For all 5 portfolios, the OOS results are not as good as the replicated results. This is largely due to the 2022 bear market, but the 2015-2021 results still aren’t as good as the replicated results.

Allocate Smartly has a great post about 2022 bear market performance of tactical asset allocation (TAA) strategies like this one. They find that TAA strategies did poorly in the 2022 bear market if they assumed intermediate and long-term bonds provide diversification from risky assets. Both risk assets and longer duration bonds performed poorly in 2022, and the correlation between bonds and equities was positive instead of negative like they have been historically.

In a future post, I may investigate how these portfolios would have performed if they were allowed to allocate to short-term Treasuries.

Portfolio Results by Sample Period

This section contains tables with results for all portfolios in a particular sample period.

Replication Period
Equal WeightEqual RiskMomo Eq WeightMomo Eq RiskMomo Min Var
Ann. Return0.0790.0860.1420.1370.130
Ann. Std Dev0.1150.0730.1140.1020.099
Ann. Sharpe0.6841.1771.2431.3351.315
Max Drawdown-0.377-0.142-0.199-0.119-0.112
Out-of-Sample: 2015-2023
Equal WeightEqual RiskMomo Eq WeightMomo Eq RiskMomo Min Var
Ann. Return0.0490.0340.0510.0500.058
Ann. Std Dev0.1070.0820.1040.0950.095
Ann. Sharpe0.4560.4110.4880.5280.612
Max Drawdown-0.210-0.194-0.213-0.204-0.162
Out-of-Sample: 2015-2021
Equal WeightEqual RiskMomo Eq WeightMomo Eq RiskMomo Min Var
Ann. Return0.0720.0560.0810.0810.082
Ann. Std Dev0.0910.0610.0920.0810.081
Ann. Sharpe0.7940.9080.8840.9911.008
Max Drawdown-0.136-0.071-0.114-0.086-0.083

If you love using my open-source work (e.g. quantmod, TTR, xts, IBrokers, microbenchmark, blotter, quantstrat, etc.), you can give back by sponsoring me on GitHub. I truly appreciate anything you’re willing and able to give!