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))