Analysing Stock Prices — Styled Page & Modelling Upgrades

This page keeps your original data source and date range exactly the same, and adds clearer styling plus incremental analytics: a normalised 100 index, daily returns, rolling (30-day) annualised volatility, drawdowns, a compact performance table, and a correlation matrix — all derived from the same prices object.

Load necessary libraries

R
# Load necessary libraries
library(tidyquant)
library(ggplot2)
library(dplyr)
library(lubridate)

Set period & tickers

R
# Set dates for analysis
today     <- Sys.Date()
last_year <- today - years(1)

# Define stock tickers (unchanged)
tickers <- c("ASIA.AX", "XRO.AX", "APX.AX", "BRK-B", "CBA.AX",
             "BHP.AX", "AGL.AX", "KGN.AX", "NDQ.AX", "AMD")

Fetch stock prices

R
# Fetch stock prices from Yahoo Finance (same data pipeline)
prices <- tq_get(tickers, from = last_year, to = today)

Generate commentary (warning-free)

Fixes the deprecated cur_data() call and ensures the symbol prints correctly.

R
# Function to provide basic commentary on stock performance
stock_commentary <- function(data) {
  symbol_chr   <- as.character(unique(data$symbol))[1]
  latest_price  <- tail(data$adjusted, 1)
  initial_price <- head(data$adjusted, 1)
  price_change  <- (latest_price - initial_price) / initial_price * 100
  trend         <- ifelse(price_change >= 0, "increased", "decreased")

  paste(
    "The stock price of", symbol_chr, "has", trend, "by",
    round(price_change, 2), "% over the past year, from",
    round(initial_price, 2), "to", round(latest_price, 2), "."
  )
}

# Warning-free dplyr usage (no cur_data())
comments <- prices %>%
  dplyr::group_by(symbol) %>%
  dplyr::reframe(commentary = stock_commentary(dplyr::pick(dplyr::everything())))

print(comments)

Baseline visualisation

Original line plot with LOESS trend, faceted by symbol.

R
p <- prices %>%
  ggplot(aes(x = date, y = adjusted, color = symbol)) +
  geom_line() +
  facet_wrap(~ symbol, scales = "free_y") +
  geom_smooth(method = "loess", se = FALSE) +
  ylab("Adjusted Closing Price") +
  ggtitle("Stock Prices Over the Last Year") +
  theme_bw() +
  theme(axis.text.x = element_text(angle = 90, hjust = 1))

print(p)

Better modelling (same prices data)

  • Index 100: normalise each series to start at 100 for quick relativity checks.
  • Returns & risk: daily returns, 30-day rolling annualised volatility.
  • Drawdowns: peak-to-trough losses over time.
  • Performance table: total return, CAGR, volatility, Sharpe (0-rf), max drawdown.
  • Correlations: cross-asset correlation (daily returns).
1) Normalised price index (start = 100)
R
library(scales)

norm <- prices %>%
  group_by(symbol) %>%
  arrange(date, .by_group = TRUE) %>%
  mutate(Index100 = adjusted / first(adjusted) * 100)

ggplot(norm, aes(date, Index100, colour = symbol)) +
  geom_line(linewidth = .7, alpha = .9) +
  facet_wrap(~ symbol, scales = "free_y") +
  scale_y_continuous(labels = number_format(accuracy = 1)) +
  labs(title = "Normalised Prices (Start = 100)",
       y = "Index (100 = first observation)", x = NULL) +
  theme_minimal(base_size = 12)
2) Daily returns, rolling volatility, drawdowns
R
library(tidyr)
library(slider)

rets <- prices %>%
  group_by(symbol) %>%
  arrange(date, .by_group = TRUE) %>%
  mutate(
    ret    = adjusted / lag(adjusted) - 1,
    logret = log(adjusted / lag(adjusted))
  ) %>%
  filter(!is.na(ret))

# 30-day rolling annualised vol (trading days ~252)
roll <- rets %>%
  group_by(symbol) %>%
  mutate(
    roll_vol = slide_dbl(logret, ~ sd(.x, na.rm = TRUE) * sqrt(252),
                         .before = 29, .complete = TRUE)
  )

# Drawdown series
dd <- rets %>%
  group_by(symbol) %>%
  mutate(
    equity = cumprod(1 + ret),
    dd     = equity / cummax(equity) - 1
  )
3) Performance table
R
perf <- rets %>%
  group_by(symbol) %>%
  summarise(
    start_price     = first(adjusted),
    end_price       = last(adjusted),
    total_return    = end_price / start_price - 1,
    ann_return_cagr = (end_price / start_price)^(252 / n()) - 1,
    ann_vol         = sd(logret, na.rm = TRUE) * sqrt(252),
    sharpe_0rf      = mean(logret, na.rm = TRUE) / sd(logret, na.rm = TRUE) * sqrt(252),
    max_drawdown    = min(dd$dd[dd$symbol == first(symbol)], na.rm = TRUE)
  ) %>%
  mutate(
    total_return    = scales::percent(total_return, accuracy = 0.1),
    ann_return_cagr = scales::percent(ann_return_cagr, accuracy = 0.1),
    ann_vol         = scales::percent(ann_vol, accuracy = 0.1),
    sharpe_0rf      = round(sharpe_0rf, 2),
    max_drawdown    = scales::percent(max_drawdown, accuracy = 0.1)
  )

perf
4) Rolling volatility & drawdowns
R
# Rolling volatility plot
ggplot(roll, aes(date, roll_vol, colour = symbol)) +
  geom_line(linewidth = .7) +
  facet_wrap(~ symbol, scales = "free_y") +
  scale_y_continuous(labels = percent_format(accuracy = 0.1)) +
  labs(title = "30-Day Rolling Annualised Volatility", y = "Volatility", x = NULL) +
  theme_minimal(base_size = 12)

# Drawdown plot
ggplot(dd, aes(date, dd, colour = symbol)) +
  geom_hline(yintercept = 0, linewidth = .4, colour = "#4a5568") +
  geom_line(linewidth = .7) +
  facet_wrap(~ symbol, scales = "free_y") +
  scale_y_continuous(labels = percent_format(accuracy = 1)) +
  labs(title = "Drawdowns from Peak", y = "Drawdown", x = NULL) +
  theme_minimal(base_size = 12)
5) Cross-asset correlation (daily returns)
R
library(reshape2)

wide_rets <- rets %>%
  select(date, symbol, ret) %>%
  pivot_wider(names_from = symbol, values_from = ret)

corr_mat <- cor(wide_rets |> dplyr::select(-date), use = "pairwise.complete.obs")

# Simple heatmap (ggplot)
corr_df <- reshape2::melt(corr_mat, varnames = c("x","y"), value.name = "corr")

ggplot(corr_df, aes(x, y, fill = corr)) +
  geom_tile() +
  scale_fill_gradient2(low = "#d84e5f", mid = "#2d3748", high = "#60d394",
                       midpoint = 0, limits = c(-1,1)) +
  coord_equal() + labs(title = "Correlation of Daily Returns", x = NULL, y = NULL) +
  theme_minimal(base_size = 12) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))