Run this chunk once if packages are not yet installed.
# All packages below are available on CRAN
install.packages(c(
"tidyquant",
"PerformanceAnalytics",
"xts",
"tidyverse",
"lubridate",
"timetk"
))
# NOTE: PortfolioAnalytics is NOT on CRAN. It is not used in this backtest.
# If you need it in the future, install from GitHub:
# remotes::install_github("braverock/PortfolioAnalytics")tickers <- c(
"NVDA",
"MSFT",
"META",
"AMZN",
"AAPL",
"V",
"JNJ",
"XOM"
)
# Portfolio weights (must sum to 1.0)
weights <- c(
0.22, # NVDA
0.18, # MSFT
0.14, # META
0.12, # AMZN
0.10, # AAPL
0.06, # V
0.05, # JNJ
0.13 # XOM
)
# Sanity check
stopifnot("Weights must sum to 1" = abs(sum(weights) - 1) < 1e-8)
# Benchmark
benchmark <- "SPY"# Portfolio stock returns (daily log returns)
returns_tbl <- prices %>%
group_by(symbol) %>%
tq_transmute(
select = adjusted,
mutate_fun = periodReturn,
period = "daily",
type = "log",
col_rename = "returns"
)
# Convert to wide format
returns_wide <- returns_tbl %>%
select(date, symbol, returns) %>%
pivot_wider(
names_from = symbol,
values_from = returns
)
# BUG FIX: tq_as_xts() does not exist; use tk_xts() from timetk instead
returns_xts <- tk_xts(returns_wide, date_var = date)
# Remove missing values
returns_xts <- na.omit(returns_xts)
# BUG FIX: pivot_wider does not guarantee column order matches tickers/weights.
# Reorder columns explicitly so weights align correctly with Return.portfolio().
returns_xts <- returns_xts[, tickers]benchmark_returns_tbl <- benchmark_prices %>%
tq_transmute(
select = adjusted,
mutate_fun = periodReturn,
period = "daily",
type = "log",
col_rename = "SPY"
)
# BUG FIX: tq_as_xts() does not exist; use tk_xts() from timetk instead
benchmark_returns <- tk_xts(benchmark_returns_tbl, date_var = date)
benchmark_returns <- na.omit(benchmark_returns)## AI_Portfolio SPY
## Cumulative Return 1.095816 0.8063895
## AI_Portfolio SPY
## Annualized Return 0.2818 0.2195
## Annualized Std Dev 0.2067 0.1507
## Annualized Sharpe (Rf=0%) 1.3638 1.4563
## AI_Portfolio SPY
## Worst Drawdown 0.2266467 0.1920899
alpha <- CAPM.alpha(
Ra = portfolio_returns,
Rb = benchmark_returns,
Rf = 0
)
beta <- CAPM.beta(
Ra = portfolio_returns,
Rb = benchmark_returns,
Rf = 0
)
cat("Alpha:\n"); print(alpha)## Alpha:
## [1] 7.532374e-05
##
## Beta:
## [1] 1.194913
# BUG FIX: SortinoRatio() and CalmarRatio() return matrix objects with different
# dimensions than StdDev.annualized(), making rbind() fail or silently misalign.
# Compute each separately and combine into a clean data frame instead.
risk_metrics <- data.frame(
Metric = c("Annualized Std Dev", "Sortino Ratio", "Calmar Ratio"),
AI_Portfolio = c(
as.numeric(StdDev.annualized(portfolio_returns)),
as.numeric(SortinoRatio(portfolio_returns)),
as.numeric(CalmarRatio(portfolio_returns))
),
SPY = c(
as.numeric(StdDev.annualized(benchmark_returns)),
as.numeric(SortinoRatio(benchmark_returns)),
as.numeric(CalmarRatio(benchmark_returns))
)
)
print(risk_metrics)## Metric AI_Portfolio SPY
## 1 Annualized Std Dev 0.2066578 0.1507106
## 2 Sortino Ratio 0.1212481 0.1295169
## 3 Calmar Ratio 1.2434829 1.1425762
chart.RollingPerformance(
portfolio_returns,
width = 126,
FUN = "SharpeRatio.annualized",
main = "Rolling Sharpe Ratio (6-Month Window)"
)# BUG FIX: maxDrawdown() returns a named numeric vector, not a matrix.
# Wrap it in as.data.frame() via t() so it saves cleanly as a CSV row.
write.csv(as.data.frame(cum_returns), "cumulative_returns.csv")
write.csv(as.data.frame(annualized_returns), "annualized_returns.csv")
write.csv(as.data.frame(sharpe_ratio), "sharpe_ratio.csv")
write.csv(as.data.frame(t(mdd)), "max_drawdown.csv") # BUG FIX: t() to make it a proper row
cat("All CSV outputs saved.\n")## All CSV outputs saved.
## ====================================
## BACKTEST COMPLETED SUCCESSFULLY
## ====================================
## Generated Outputs:
## - Performance Comparison Chart
## - Cumulative Returns
## - Annualized Returns
## - Sharpe Ratio
## - Maximum Drawdown
## - Alpha & Beta
## - Additional Risk Metrics (Std Dev, Sortino, Calmar)
## - Rolling Sharpe Ratio Chart
## - Drawdown Analysis Chart