In the fast-paced world of technology stocks, the allure of high growth often comes with the trade-off of heightened volatility, making risk management a complex yet crucial task. While reducing risk in such a dynamic sector may seem daunting, our recent analysis shows that with the right strategies, it’s possible to strike a balance between capturing substantial returns and managing potential downturns.
This investigation explored the performance and risk management of a portfolio heavily weighted in technology stocks, using various optimization strategies like mean-variance optimization and Expected Shortfall minimization. By analyzing the portfolio’s returns from 2020 to 2024, we assessed the effectiveness of these strategies in balancing risk and return amidst the inherent volatility of high-growth tech stocks. Despite the challenges posed by this sector’s unpredictability, the portfolio managed to achieve notable returns in several years, demonstrating the potential of well-structured risk management techniques. The results highlight both the complexities and opportunities in navigating a tech-focused investment strategy.
The data for this investigation was pulled at a daily periodicity from Yahoo Finance using the quantmod package in R, covering the period from January 2020 to the present. The portfolio consists of major technology stocks, including Apple, Amazon, Meta, Microsoft, and Alphabet. Daily returns were calculated based on adjusted closing prices, providing a detailed and granular view of the portfolio’s performance across various market conditions. This data set served as the foundation for analyzing the effectiveness of different portfolio optimization strategies, with a focus on managing risk in the volatile tech sector.
stocks <- c("META","AAPL","MSFT","GOOG", "AMZN")
weights <- c(0.4, 0.15, 0.1, 0.1, 0.25)
portfolioclose <- NULL
for(stock in stocks) {
portfolioclose <- cbind(portfolioclose,
getSymbols(stock, src = "yahoo", periodicity = 'daily', from = '2020-01-01', auto.assign = FALSE)[,4])
}
sp500price <- getSymbols("SPY", src = "yahoo", periodicity = 'daily', from = '2020-01-01', auto.assign = FALSE)[,4]
head(portfolioclose)
## META.Close AAPL.Close MSFT.Close GOOG.Close AMZN.Close
## 2020-01-02 209.78 75.0875 160.62 68.3685 94.9005
## 2020-01-03 208.67 74.3575 158.62 68.0330 93.7485
## 2020-01-06 212.60 74.9500 159.03 69.7105 95.1440
## 2020-01-07 213.06 74.5975 157.58 69.6670 95.3430
## 2020-01-08 215.22 75.7975 160.09 70.2160 94.5985
## 2020-01-09 218.30 77.4075 162.09 70.9915 95.0525
head(sp500price)
## SPY.Close
## 2020-01-02 324.87
## 2020-01-03 322.41
## 2020-01-06 323.64
## 2020-01-07 322.73
## 2020-01-08 324.45
## 2020-01-09 326.65
portfolioclose <- portfolioclose
portfolioreturns <- na.omit(ROC(portfolioclose))
sp500returns <- na.omit(ROC(sp500price))
plot_dataframe <- data.frame(portfolioclose) %>%
rownames_to_column("Date") %>%
mutate(Date = as.Date(Date)) %>%
pivot_longer(
cols = ends_with(".Close"),
names_to = "Stock",
values_to = "Closing Price"
)
head(portfolioreturns)
## META.Close AAPL.Close MSFT.Close GOOG.Close AMZN.Close
## 2020-01-03 -0.005305309 -0.009769603 -0.012529922 -0.0049193493 -0.012213314
## 2020-01-06 0.018658447 0.007936666 0.002581482 0.0243581503 0.014775871
## 2020-01-07 0.002161310 -0.004714140 -0.009159579 -0.0006242443 0.002089436
## 2020-01-08 0.010086962 0.015958275 0.015802857 0.0078495086 -0.007839287
## 2020-01-09 0.014209511 0.021018372 0.012415580 0.0109839096 0.004787695
## 2020-01-10 -0.001100034 0.002258152 -0.004637797 0.0069485075 -0.009455156
head(sp500returns)
## SPY.Close
## 2020-01-03 -0.007601048
## 2020-01-06 0.003807793
## 2020-01-07 -0.002815738
## 2020-01-08 0.005315384
## 2020-01-09 0.006757764
## 2020-01-10 -0.002881854
ggplot(plot_dataframe, aes(x = Date, color = Stock)) +
geom_line(aes(y = `Closing Price`)) +
labs(
title = "Closing Price of Constituent Stocks",
x = "Date",
y = "Closing Price ($)"
) +
theme_minimal()
pf_Return <- Return.portfolio(portfolioreturns, weights = weights)
head(pf_Return)
## portfolio.returns
## 2020-01-03 -0.0083858197
## 2020-01-06 0.0150565118
## 2020-01-07 -0.0002748319
## 2020-01-08 0.0068135016
## 2020-01-09 0.0124065634
## 2020-01-10 -0.0021667063
rf <- 0.0382
rf_period = rf/252
p <- 0.95
Beta_Bench <- CAPM.beta(pf_Return, sp500returns, rf_period)
SimulatedReturn1 <- CAPM.jensenAlpha(pf_Return, sp500returns, rf_period)
Sharpe1 <- SharpeRatio(pf_Return, Rf = rf, p = p)
cat("Sharpe Ratio:", Sharpe1[1])
## Sharpe Ratio: -1.898544
cat("Beta:", Beta_Bench)
## Beta: 1.179815
cat("Jensen's Alpha:", SimulatedReturn1)
## Jensen's Alpha: 0.001630192
The analysis reveals a Sharpe Ratio of -1.896, indicating that the portfolio has generated returns that are lower than the risk-free rate when adjusted for volatility, reflecting poor risk-adjusted performance. The portfolio’s Beta of 1.18 suggests it is more volatile than the market, implying a higher level of risk compared to the benchmark. However, the positive Jensen’s Alpha of 0.0019 indicates that the portfolio slightly outperformed the expected return based on its Beta, suggesting some level of excess return. Overall, despite the portfolio’s higher risk and underwhelming Sharpe Ratio, it managed to achieve a marginally positive excess return over the market expectation.
Calendarized1 <- table.CalendarReturns(pf_Return)
Calendarized1
## Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
## 2020 -0.8 0.9 -0.1 3.6 0.6 2.4 5.1 0.7 0.4 -4.8 -0.3 -0.1
## 2021 -2.3 0.9 1.7 -0.9 -0.5 -0.5 -2.4 0.0 -0.5 0.2 -1.8 -1.4
## 2022 3.0 0.2 -2.1 -6.6 0.8 -2.0 3.6 -0.1 -1.8 -2.2 5.5 -0.1
## 2023 1.7 0.8 1.7 -0.4 -0.2 1.8 -0.4 0.6 -0.2 0.0 -0.6 -0.7
## 2024 -3.0 1.2 -0.7 -2.0 -0.2 -2.2 1.7 1.1 0.7 NA NA NA
## portfolio.returns
## 2020 7.7
## 2021 -7.2
## 2022 -2.4
## 2023 4.0
## 2024 -3.6
The portfolio returns from 2020 to 2024 exhibit significant volatility, with a notable peak in 2020 at 7.7% followed by a substantial decline in 2021, resulting in a -7.2% return. Although the portfolio partially recovered in 2023 with a 4.0% return, it again faced a sharp downturn in 2024, showing a -4.8% return by mid-year. The erratic performance across these years highlights the portfolio’s sensitivity to market fluctuations, suggesting a need for a more stable investment strategy.
overall1 = cbind(pf_Return, sp500returns)
charts.PerformanceSummary(overall1, main = "P/L of Benchmark Strategy", legend = NULL)
portf <- portfolio.spec(assets = colnames(portfolioreturns))
portf <- add.constraint(portf, type ="full_investment")
portf <- add.constraint(portf, type ="box", min = 0.10, max = 0.3)
portf <- add.objective(portf, type = "return", name = "mean")
portf <- add.objective(portf, type = "risk", name = "StdDev")
optimum1 <- optimize.portfolio(
portfolioreturns,
portf,
optimize_method = "ROI",
trace = TRUE
)
optimum1
## ***********************************
## PortfolioAnalytics Optimization
## ***********************************
##
## Call:
## optimize.portfolio(R = portfolioreturns, portfolio = portf, optimize_method = "ROI",
## trace = TRUE)
##
## Optimal Weights:
## META.Close AAPL.Close MSFT.Close GOOG.Close AMZN.Close
## 0.1 0.3 0.3 0.2 0.1
##
## Objective Measure:
## mean
## 0.0007704
##
##
## StdDev
## 0.01844
chart.Weights(optimum1, main = "Change in Weights After MVO Optimization")
This mean-variance optimization strategy adjusts the weights of the portfolio’s constituent stocks to maximize returns while minimizing risk. It enforces full investment of capital, with each stock’s allocation constrained between 10% and 30% to ensure diversification. The goal is to achieve the best possible balance between return and risk, optimizing the portfolio’s performance.
weights2 = c(0.1, 0.5, 0.2, 0.1, 0.1)
rf <- 0.0382
rf_period = rf/252
p <- 0.95
pf_Return2 <- Return.portfolio(portfolioreturns, weights = weights2)
Beta_MVO <- CAPM.beta(pf_Return2, sp500returns, rf_period)
SimulatedReturn2 <- CAPM.jensenAlpha(pf_Return2, sp500returns, rf_period)
Sharpe2 <- SharpeRatio(pf_Return2, Rf = rf, p = p)
cat("Sharpe Ratio:", Sharpe2[1])
## Sharpe Ratio: -2.027493
cat("Beta:", Beta_MVO)
## Beta: 1.171797
cat("Jensen's Alpha:", SimulatedReturn2)
## Jensen's Alpha: 0.05282401
The mean-variance optimization resulted in a Sharpe Ratio of -2.023, indicating that the portfolio’s returns, when adjusted for risk, are below the risk-free rate, reflecting poor risk-adjusted performance. The portfolio’s Beta of 1.17 suggests that it is more volatile than the market, indicating a higher level of systematic risk. However, the positive Jensen’s Alpha of 0.0558 shows that the portfolio managed to generate a return that exceeds the expected return based on its Beta, indicating some successful excess performance. Despite the negative Sharpe Ratio, the portfolio still achieved a modest outperformance relative to its risk, as indicated by the positive Jensen’s Alpha.
Calendarized2 <- table.CalendarReturns(pf_Return2)
Calendarized2
## Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
## 2020 -2.4 0.8 -0.4 2.3 0.4 1.6 5.9 1.6 1.1 -4.2 0.7 -0.4
## 2021 -3.1 0.6 1.7 -1.0 -0.3 0.0 -1.0 -0.4 -0.8 -0.4 0.5 -0.7
## 2022 2.3 0.2 -1.9 -4.7 0.0 -1.8 3.1 -0.8 -2.5 -1.7 5.2 0.0
## 2023 1.4 -0.1 1.6 0.4 -0.3 2.0 0.0 0.2 0.2 0.2 -0.1 -0.4
## 2024 -2.8 0.5 -0.7 -2.2 0.2 -1.7 1.0 0.4 0.3 NA NA NA
## portfolio.returns
## 2020 6.9
## 2021 -4.9
## 2022 -3.0
## 2023 5.1
## 2024 -4.9
The calendarized results of the mean-variance optimization (MVO) strategy show that the portfolio experienced significant volatility, with strong positive returns in 2020 (6.9%) and 2023 (5.1%), but negative returns in 2021 (-4.9%), 2022 (-3.0%), and 2024 year-to-date (-4.6%). The portfolio’s performance appears to fluctuate considerably year-over-year, indicating that while the MVO strategy occasionally produced positive returns, it struggled to consistently deliver stable results, especially during down markets. This suggests that the strategy might be sensitive to market conditions and may require further adjustment to achieve more consistent performance.
overall2 <- cbind(pf_Return2, pf_Return, sp500returns)
charts.PerformanceSummary(overall2, main = "P/L of Benchmark, Mean-Variance, and S&P500", legend = "bottomright")
portf2 <- portfolio.spec(assets = colnames(portfolioreturns))
portf2 <- add.constraint(portf2, type ="full_investment")
portf2 <- add.constraint(portf2, type ="box", min = 0.10, max = 0.3)
portf2 <- add.objective(portf2, type = "risk_budget", name = "ES")
optimum2 <- optimize.portfolio(
portfolioreturns,
portf2,
optimize_method = "ROI",
trace = TRUE
)
optimum2
## ***********************************
## PortfolioAnalytics Optimization
## ***********************************
##
## Call:
## optimize.portfolio(R = portfolioreturns, portfolio = portf2,
## optimize_method = "ROI", trace = TRUE)
##
## Optimal Weights:
## META.Close AAPL.Close MSFT.Close GOOG.Close AMZN.Close
## 0.1000 0.2739 0.3000 0.1818 0.1443
##
## Objective Measure:
## ES
## 0.04332
chart.Weights(optimum2, main = "Change in Weights After ES Optimization")
This strategy is designed to minimize portfolio risk by focusing on risk budgeting through Expected Shortfall (ES), which measures potential losses in extreme market conditions. The full_investment constraint ensures that all capital is allocated across the assets, while the box constraint limits each asset’s weight to between 10% and 30%, promoting diversification. By setting Expected Shortfall as the objective, the strategy aims to allocate weights in a way that minimizes potential severe losses, rather than just focusing on overall variance, providing a more robust risk management approach.
weights3 = c(0.1, 0.2739, 0.3, 0.1818, 0.1443)
rf <- 0.0382
rf_period = rf/252
p <- 0.95
pf_Return3 <- Return.portfolio(portfolioreturns, weights = weights3)
Beta_ES <- CAPM.beta(pf_Return3, sp500returns, rf_period)
SimulatedReturn3 <- CAPM.jensenAlpha(pf_Return3, sp500returns, rf_period)
Sharpe3 <- SharpeRatio(pf_Return3, Rf = rf, p = p)
cat("Sharpe Ratio:", Sharpe3[1])
## Sharpe Ratio: -2.06431
cat("Beta:", Beta_ES)
## Beta: 1.160948
cat("Jensen's Alpha:", SimulatedReturn3)
## Jensen's Alpha: 0.03855039
The results of the strategy focusing on minimizing Expected Shortfall (ES) indicate a Sharpe Ratio of -2.061, which suggests that the portfolio’s risk-adjusted returns are still below the risk-free rate, reflecting suboptimal performance relative to risk. The Beta of 1.16 shows that the portfolio is slightly less volatile than in previous strategies but still more volatile than the market, indicating exposure to higher systematic risk. The positive Jensen’s Alpha of 0.0413 suggests that, despite the negative Sharpe Ratio, the portfolio achieved a modest excess return over what would be expected based on its Beta, indicating some degree of effective risk management. Overall, while the strategy has slightly improved risk management, it still struggles with achieving positive risk-adjusted returns.
Calendarized3 <- table.CalendarReturns(pf_Return3)
Calendarized3
## Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
## 2020 -1.4 1.2 -0.4 2.1 0.7 2.0 3.7 0.7 0.9 -3.1 0.0 -0.2
## 2021 -2.6 0.8 1.5 -0.8 -0.2 -0.2 -1.5 -0.2 -0.7 0.3 -0.7 -0.9
## 2022 2.1 0.3 -1.9 -5.3 0.3 -1.9 3.0 -0.7 -2.3 -1.7 5.4 -0.1
## 2023 1.6 0.0 1.7 0.2 -0.5 1.8 -0.1 0.2 0.1 0.1 -0.3 -0.3
## 2024 -3.3 1.0 -0.4 -2.4 0.1 -1.7 0.7 0.8 0.3 NA NA NA
## portfolio.returns
## 2020 6.2
## 2021 -5.1
## 2022 -3.2
## 2023 4.5
## 2024 -5.0
The calendarized results of the strategy focused on minimizing Expected Shortfall (ES) show that the portfolio experienced positive returns in 2020 (6.2%) and 2023 (4.5%), but faced negative returns in 2021 (-5.1%), 2022 (-3.2%), and 2024 year-to-date (-5.5%). While the strategy managed to reduce some volatility and achieve moderate gains in certain years, it struggled to protect against losses during downturns, particularly in 2024. This suggests that while the strategy may mitigate extreme losses, it still encounters challenges in consistently delivering positive performance across varying market conditions.
overall3 <- cbind(pf_Return3, pf_Return2, pf_Return, sp500returns)
charts.PerformanceSummary(overall3, main = "P/L of Benchmark, Mean-Variance, and S&P500", legend = "bottomright")
portf3 <- portfolio.spec(assets = colnames(portfolioreturns))
portf3 <- add.constraint(portf3, type = "full_investment")
portf3 <- add.constraint(portf3, type = "box", min = 0.10, max = 0.3)
portf3 <- add.objective(portf3, type = "return", name = "mean")
portf3 <- add.objective(portf3, type = "risk", name = "StdDev")
optimum3 <- optimize.portfolio.rebalancing(
R = portfolioreturns,
portfolio = portf3,
optimize_method = "ROI",
search_size = 20000,
trace = TRUE,
rebalance_on = "months",
training_period = 1,
rolling_window = 10
)
## Warning: executing %dopar% sequentially: no parallel backend registered
optimum3
## **************************************************
## PortfolioAnalytics Optimization with Rebalancing
## **************************************************
##
## Call:
## optimize.portfolio.rebalancing(R = portfolioreturns, portfolio = portf3,
## optimize_method = "ROI", search_size = 20000, trace = TRUE,
## rebalance_on = "months", training_period = 1, rolling_window = 10)
##
## Number of rebalancing dates: 57
## First rebalance date:
## [1] "2020-01-31"
## Last rebalance date:
## [1] "2024-09-09"
##
## Annualized Portfolio Rebalancing Return:
## [1] 0.1668407
##
## Annualized Portfolio Standard Deviation:
## [1] 0.3064095
chart.Weights(optimum3, main = "Weights After Dynamic Rebalancing")
This strategy implements a dynamic portfolio optimization with monthly rebalancing to maximize returns while minimizing risk, subject to full investment and asset weight constraints (between 10% and 30%). By using a rolling 10-month window for optimization, the strategy adapts to changing market conditions while maintaining diversification through the “box” constraint. The use of both return and risk objectives ensures a balance between growth potential and volatility control. The visualization of asset weights over time helps assess the strategy’s consistency in maintaining optimal allocations.
weights4 <- extractWeights(optimum3)
rf <- 0.0382
rf_period = rf/252
p <- 0.95
pf_Return4 <- Return.portfolio(portfolioreturns, weights = weights4)
Beta_Dyn <- CAPM.beta(pf_Return4, sp500returns, rf_period)
Alpha_Dyn <- CAPM.jensenAlpha(pf_Return4, sp500returns, rf_period)
Sharpe_Dyn <- SharpeRatio(pf_Return4, Rf = rf, p = p)
cat("Sharpe Ratio:", Sharpe2[1])
## Sharpe Ratio: -2.027493
cat("Beta:", Beta_MVO)
## Beta: 1.171797
cat("Jensen's Alpha:", SimulatedReturn2)
## Jensen's Alpha: 0.05282401
The portfolio shows a negative Sharpe Ratio (-2.03), indicating that it underperformed relative to the risk-free rate when adjusted for risk, suggesting poor risk-adjusted returns. With a beta of 1.17, the portfolio is more volatile than the market, meaning it tends to move more than the market during fluctuations. However, Jensen’s Alpha of 0.0528 suggests the portfolio generated some excess returns over the market, indicating potential skill in selecting securities.
Calendarized4 <- table.CalendarReturns(pf_Return4)
Calendarized4
## Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
## 2020 NA 1.2 0.1 2.9 0.7 1.9 3.7 1.2 0.6 -3.5 -0.6 0.0
## 2021 -2.6 0.9 1.5 -0.7 -0.3 -0.5 -1.1 -0.2 -0.6 -0.4 -1.2 -0.9
## 2022 2.3 0.3 -2.0 -5.7 0.1 -1.9 4.3 -0.5 -1.9 -2.0 5.7 -0.2
## 2023 1.7 1.0 1.7 0.2 -0.2 1.8 -0.4 0.4 -0.1 -0.1 -0.3 -0.5
## 2024 -4.0 1.4 -0.5 -2.5 -0.1 -1.8 1.3 1.3 0.2 NA NA NA
## portfolio.returns
## 2020 8.3
## 2021 -6.2
## 2022 -2.0
## 2023 5.3
## 2024 -4.9
The portfolio experienced significant variability in returns across different years. Notably, 2020 showed strong performance with an 8.3% return, while 2021 saw a sharp decline of -6.2%. After a moderate loss in 2022 (-2.0%), the portfolio rebounded in 2023 with a positive 5.3% return, only to drop again in 2024 with a -4.9% return by mid-year. The portfolio’s returns seem highly volatile, alternating between gains and losses, reflecting fluctuating market conditions.
overall3 <- cbind(pf_Return4, pf_Return3, pf_Return2, pf_Return, sp500returns)
charts.PerformanceSummary(overall3, main = "P/L of Benchmark, MVO, ES, Dynamic Rebalancing and S&P500", legend = "bottomright")
Technology stocks, being high-growth assets, are inherently more volatile, which makes it challenging to significantly reduce Value at Risk (VaR) through traditional risk management strategies. Despite this natural volatility, the portfolio optimization strategies implemented still managed to achieve periods of positive returns, particularly in years like 2020 and 2023. The strategies, although unable to completely shield the portfolio from downturns, demonstrated an ability to manage some risk while capturing growth potential. This highlights the complexity of balancing risk and return in a portfolio dominated by high-growth technology stocks but also underscores the potential for achieving favorable results even in such a volatile sector.
Explore Alternative Risk Measures: Consider incorporating additional risk measures like Conditional Value at Risk (CVaR) or downside risk in the optimization process to further manage extreme losses, especially given the high volatility of technology stocks.
Diversification Across Sectors: Introduce a more diversified set of assets, including less volatile sectors, to balance the high growth and volatility of technology stocks, potentially improving the overall risk-return profile of the portfolio.