1 Project Objective

The objective of this project is to leverage Artificial Intelligence (AI) tools to assist in investment decision-making. Students will move from strategy formulation and asset selection to quantitative backtesting, ultimately validating whether the proposed strategy can generate abnormal returns (Alpha).


2 Project Constraints

  • Asset Limit: The portfolio must consist of no more than 10 individual stocks or ETFs.
  • Role of AI: Students must explicitly demonstrate how AI was utilized for:
    • Macroeconomic analysis or industry trend forecasting.
    • Fundamental factor filtering or sentiment analysis.
    • Weight optimization (e.g., Mean-Variance, Risk Parity, or Black-Litterman).
  • Core Logic: A clear investment philosophy (e.g., Momentum, Value, Low Volatility) must be established to justify the theoretical basis for achieving “Abnormal Returns.”

3 Part I: Investment Thesis & Logic

3.1 Strategy Description

TODO: Detail the logic behind your portfolio construction. What is your specific source of Alpha?

3.2 AI Collaboration Process

TODO: Document your interaction with AI. What specific prompts were used? How did the AI assist in narrowing down the universe of stocks or optimizing weights?


4 Part II: Portfolio Construction

4.1 Asset List

# Define Assets and Benchmark
tickers          <- c('AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA',
                      'META', 'NVDA', 'JPM',  'V',    'UNH')
benchmark_ticker <- 'SPY'

# Display as a table (weights will be filled in after optimization)
asset_table <- data.frame(
  Ticker = tickers,
  Company = c("Apple", "Microsoft", "Alphabet", "Amazon", "Tesla",
              "Meta", "NVIDIA", "JPMorgan Chase", "Visa", "UnitedHealth"),
  Initial_Weight = rep(NA_real_, length(tickers))   # updated after optimization
)

knitr::kable(asset_table, caption = "Selected Portfolio Assets (max 10)")
Selected Portfolio Assets (max 10)
Ticker Company Initial_Weight
AAPL Apple NA
MSFT Microsoft NA
GOOGL Alphabet NA
AMZN Amazon NA
TSLA Tesla NA
META Meta NA
NVDA NVIDIA NA
JPM JPMorgan Chase NA
V Visa NA
UNH UnitedHealth NA

4.2 Benchmark Selection

The benchmark is SPY (SPDR S&P 500 ETF Trust), which tracks the S&P 500 index.

TODO: Justify why this benchmark is a relevant comparison for your strategy.


5 Part III: Backtesting & Performance Analysis

5.1 Load Required Libraries

# Install if needed:
# install.packages(c("tidyquant", "PortfolioAnalytics",
#                    "ROI", "ROI.plugin.glpk", "ROI.plugin.quadprog"))

library(tidyquant)
library(PortfolioAnalytics)
library(ROI)
library(ROI.plugin.glpk)
library(ROI.plugin.quadprog)
library(PerformanceAnalytics)
library(tidyr)
library(xts)

5.2 Download Historical Data (Past 3 Years)

start_date <- Sys.Date() - lubridate::years(3)

# Portfolio prices
prices <- tq_get(tickers,
                 from = start_date,
                 get  = "stock.prices")

# Daily returns – wide xts format
returns_wide <- prices |>
  dplyr::group_by(symbol) |>
  tq_transmute(select     = adjusted,
               mutate_fun = periodReturn,
               period     = "daily",
               col_rename = "Ra") |>
  tidyr::pivot_wider(names_from  = symbol,
                     values_from = Ra)

returns_multi <- xts(returns_wide[, -1], order.by = returns_wide$date)

head(returns_multi)
##                     AAPL         MSFT        GOOGL         AMZN       TSLA
## 2023-05-26  0.0000000000  0.000000000  0.000000000  0.000000000 0.00000000
## 2023-05-30  0.0106595237 -0.005046638 -0.007543615  0.012904863 0.04136256
## 2023-05-31 -0.0002819645 -0.008514343 -0.006468639 -0.008877214 0.01377008
## 2023-06-01  0.0160224769  0.012759281  0.006917759  0.018162173 0.01760414
## 2023-06-02  0.0047754980  0.008479083  0.007678711  0.012055090 0.03108133
## 2023-06-05 -0.0075712360  0.001610044  0.010748329  0.008450729 0.01701173
##                    META         NVDA          JPM            V          UNH
## 2023-05-26  0.000000000  0.000000000  0.000000000  0.000000000  0.000000000
## 2023-05-30  0.001831879  0.029913451  0.003797185 -0.014977069 -0.003468282
## 2023-05-31  0.008380280 -0.056767570 -0.012730599 -0.002752022  0.015400876
## 2023-06-01  0.029805024  0.051171020  0.013779143  0.024747623  0.013114760
## 2023-06-02  0.000000000 -0.011139042  0.021006140  0.010110429  0.012053367
## 2023-06-05 -0.004475180 -0.003966906 -0.009824180 -0.008829134 -0.002782263

5.3 Portfolio Optimization (Maximum Sharpe Ratio)

# Define portfolio specification
port_spec <- portfolio.spec(assets = tickers) |>
  add.constraint(type = "full_investment") |>
  add.constraint(type = "long_only")       |>
  add.objective(type = "return", name = "mean")   |>
  add.objective(type = "risk",   name = "StdDev")

# Run optimization
opt_weights <- optimize.portfolio(
  R                = returns_multi,
  portfolio        = port_spec,
  optimize_method  = "ROI",
  max_sharpe       = TRUE
)

optimized_w <- extractWeights(opt_weights)
print(round(optimized_w, 4))
##   AAPL   MSFT  GOOGL   AMZN   TSLA   META   NVDA    JPM      V    UNH 
## 0.0000 0.0000 0.1757 0.0000 0.0000 0.0000 0.8243 0.0000 0.0000 0.0000
# Update asset table with optimized weights
asset_table$Initial_Weight <- round(optimized_w[tickers], 4)
knitr::kable(asset_table, caption = "Optimized Portfolio Weights")
Optimized Portfolio Weights
Ticker Company Initial_Weight
AAPL Apple 0.0000
MSFT Microsoft 0.0000
GOOGL Alphabet 0.1757
AMZN Amazon 0.0000
TSLA Tesla 0.0000
META Meta 0.0000
NVDA NVIDIA 0.8243
JPM JPMorgan Chase 0.0000
V Visa 0.0000
UNH UnitedHealth 0.0000

5.4 Backtesting

# Portfolio returns with optimized weights
portfolio_returns <- Return.portfolio(returns_multi,
                                      weights = optimized_w)
colnames(portfolio_returns) <- "AI_Portfolio"

# Benchmark returns
bmark_raw <- tq_get(benchmark_ticker, from = start_date) |>
  tq_transmute(select     = adjusted,
               mutate_fun = periodReturn,
               period     = "daily",
               col_rename = "Benchmark")

benchmark_returns <- xts(bmark_raw[, "Benchmark"], order.by = bmark_raw$date)

# Merge
combined_returns <- merge.xts(portfolio_returns, benchmark_returns)

5.5 Performance Charts

charts.PerformanceSummary(
  combined_returns,
  main        = "Portfolio vs Benchmark Performance",
  colorset    = c("#2196F3", "#FF5722"),
  legend.loc  = "topleft"
)

5.6 Performance Metrics

# Annualised returns, Sharpe, volatility
ann_table <- table.AnnualizedReturns(combined_returns, scale = 252, Rf = 0)
knitr::kable(round(ann_table, 4), caption = "Annualised Performance Metrics")
Annualised Performance Metrics
AI_Portfolio Benchmark
Annualized Return 0.7295 0.2285
Annualized Std Dev 0.4342 0.1513
Annualized Sharpe (Rf=0%) 1.6799 1.5098
# Maximum Drawdown
mdd_table <- table.DrawdownsRatio(combined_returns)
knitr::kable(round(mdd_table, 4), caption = "Drawdown Statistics")
Drawdown Statistics
AI_Portfolio Benchmark
Sterling ratio 1.5859 0.7945
Calmar ratio 2.0265 1.2181
Burke ratio 1.1190 0.9446
Pain index 0.0648 0.0197
Ulcer index 0.0942 0.0347
Pain ratio 11.2506 11.5960
Martin ratio 7.7448 6.5757
# Alpha & Beta vs benchmark
capm_table <- table.CAPM(portfolio_returns, benchmark_returns, scale = 252, Rf = 0)
knitr::kable(round(capm_table, 4), caption = "CAPM: Alpha & Beta")
CAPM: Alpha & Beta
AI_Portfolio to Benchmark
Alpha 0.0009
Beta 1.9657
Alpha Robust 0.0007
Beta Robust 1.8112
Beta+ 1.9126
Beta- 1.8265
Beta+ Robust 1.7974
Beta- Robust 1.6778
R-squared 0.4692
R-squared Robust 0.4187
Annualized Alpha 0.2402
Correlation 0.6850
Correlation p-value 0.0000
Tracking Error 0.3485
Active Premium 0.5010
Information Ratio 1.4377
Treynor Ratio 0.3711

6 Part IV: Critical Reflection

6.1 AI Insights vs Traditional Analysis

TODO: What insights did the AI provide that might have been overlooked by traditional analysis?

6.2 Backtesting Results vs Initial Hypothesis

TODO: Do the backtesting results align with your initial hypothesis? If there is a discrepancy, what are the potential causes?


7 Appendix: AI Prompt Logs

7.1 Prompt A – Strategy Ideation & Logic Construction

“Act as a Quantitative Researcher. I want to build a portfolio of 10 US-listed ETFs based on a ‘Quality Momentum’ factor. Explain the economic rationale for why high-quality companies with positive price momentum should deliver abnormal returns over the next 12 months.”

AI Response Summary: (paste or summarise the AI’s response here)

7.2 Prompt B – Stock/ETF Selection (Fundamental Filtering)

“I have a list of 50 technology stocks. Using the most recent quarterly earnings data, identify which 10 stocks have the highest Free Cash Flow yield and a Debt-to-Equity ratio below 0.5. Provide a summary of the AI’s reasoning for each pick.”

AI Response Summary: (paste or summarise the AI’s response here)

7.3 Prompt C – Portfolio Optimization Code Generation

“Write a Python script using the cvxpy or PyPortfolioOpt library to calculate the ‘Maximum Sharpe Ratio’ weights for a list of 10 tickers. Use the last 3 years of daily returns and apply a constraint where no single asset exceeds 20% of the total weight.”

AI Response Summary: (paste or summarise the AI’s response here)


8 Grading Rubric

Criterion Weight Description
Logical Rigor 30% Is the strategy grounded in sound financial theory?
AI Integration 30% Does the student demonstrate effective collaboration with AI rather than passive reliance?
Data Accuracy 20% Are the backtesting charts clear and the calculations correct?
Presentation 20% How effectively does the student communicate the strategy’s potential?

Rendered with R R version 4.5.1 (2025-06-13 ucrt) on 2026-05-26.