if (!require("quantmod")) install.packages("quantmod", dependencies = TRUE)
## Loading required package: quantmod
## Loading required package: xts
## Loading required package: zoo
##
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
## Loading required package: TTR
## Registered S3 method overwritten by 'quantmod':
## method from
## as.zoo.data.frame zoo
if (!require("PerformanceAnalytics")) install.packages("PerformanceAnalytics", dependencies = TRUE)
## Loading required package: PerformanceAnalytics
##
## Attaching package: 'PerformanceAnalytics'
## The following object is masked from 'package:graphics':
##
## legend
if (!require("TTR")) install.packages("TTR", dependencies = TRUE)
library(quantmod)
library(PerformanceAnalytics)
library(TTR)
tickers <- c("AAPL", "MSFT", "GOOG", "AMZN", "META", "TSLA", "NVDA", "JPM", "V", "UNH")
start_date <- as.Date("2015-01-01")
end_date <- as.Date("2024-12-31")
lookback_days <- 90
top_n <- 3
rebalance_days <- 30
getSymbols(tickers, from = start_date, to = end_date, auto.assign = TRUE)
## [1] "AAPL" "MSFT" "GOOG" "AMZN" "META" "TSLA" "NVDA" "JPM" "V" "UNH"
prices <- do.call(merge, lapply(tickers, function(x) Ad(get(x))))
colnames(prices) <- tickers
rebalance_dates <- seq(from = start_date + lookback_days, to = end_date, by = rebalance_days)
portfolio_returns <- xts(order.by = index(prices))
portfolio_returns <- portfolio_returns[, 1, drop = FALSE]
colnames(portfolio_returns) <- "Strategy"
portfolio_returns[] <- NA
for (i in 1:(length(rebalance_dates) - 1)) {
lookback_start <- rebalance_dates[i] - lookback_days
lookback_end <- rebalance_dates[i]
forward_end <- rebalance_dates[i + 1]
}
momentum_scores <- sapply(tickers, function(tk) {
px <- prices[, tk]
px_lb <- px[paste0(lookback_start, "/", lookback_end)]
if (length(px_lb) < 2 || any(is.na(px_lb))) return(NA_real_)
return(as.numeric(last(px_lb)) / as.numeric(first(px_lb)) - 1)
})
momentum_scores <- as.numeric(momentum_scores)
names(momentum_scores) <- tickers
valid_scores <- sort(momentum_scores[!is.na(momentum_scores)], decreasing = TRUE)
top_tickers <- names(valid_scores)[1:min(top_n, length(valid_scores))]
if (length(top_tickers) > 0) {
forward_prices <- prices[paste0(lookback_end + 1, "/", forward_end), top_tickers]
forward_returns <- na.omit(Return.calculate(forward_prices))
if (nrow(forward_returns) > 0) {
port_ret <- Return.portfolio(forward_returns, weights = rep(1 / length(top_tickers), length(top_tickers)))
portfolio_returns[index(port_ret), ] <- port_ret
}
}
portfolio_returns <- na.omit(portfolio_returns)
charts.PerformanceSummary(portfolio_returns, main = "Momentum Strategy Backtest")

print(table.AnnualizedReturns(portfolio_returns))
## Strategy
## Annualized Return 1.0123
## Annualized Std Dev 0.2577
## Annualized Sharpe (Rf=0%) 3.9287
print(paste("Max Drawdown:", round(maxDrawdown(portfolio_returns) * 100, 2), "%"))
## [1] "Max Drawdown: 4.22 %"