Fetch stock prices
31 July 2025 - Written by ML
Our aim is to fetch year end closing prices for a selection of stocks. Then save the result in a csv format.
Code has been created with a mixture of models including kimi.ai, deep seek, mistral.ai, chatgpt, and, cursor.
###############################################################################
# Script Name: year_end_prices.R
#
# Description:
# This script downloads adjusted closing prices for a specified set of stock
# tickers from Yahoo Finance, covering a time range from 2017-01-01 to today.
# It extracts the closing prices for the end of each year from 2017 through 2024,
# appends the latest available closing price, reshapes the data to wide format,
# and writes the result to a CSV file.
#
# Output:
# - year_end_prices_2017_2024_latest.csv : A wide-format CSV with one row per ticker
# and columns for each year-end and the latest price.
###############################################################################
# --- Packages ------------------------------------------------------------
# Ensure required packages are installed and loaded
required <- c("quantmod", "dplyr", "lubridate")
if (!all(required %in% installed.packages()[, "Package"]))
install.packages(setdiff(required, installed.packages()[, "Package"]))
lapply(required, library, character.only = TRUE)
# --- Tickers & Date Range -----------------------------------------------
# Define the tickers to analyze and the historical date range
tickers <- c("AJG", "AON", "MUV2.DE", "^GSPC", "^DJI")
from <- "2017-01-01"
to <- Sys.Date()
# --- Download Historical Prices -----------------------------------------
# Download adjusted closing prices for each ticker from Yahoo Finance
# Use try() to continue if a ticker fails
raw_list <- setNames(vector("list", length(tickers)), tickers)
for (tk in tickers) {
cat("Fetching", tk, "...")
raw_list[[tk]] <- try(
getSymbols(tk, from = from, to = to, src = "yahoo", auto.assign = FALSE),
silent = TRUE
)
cat(ifelse(inherits(raw_list[[tk]], "try-error"), "FAILED\n", "OK\n"))
}
# Extract the closing prices from each ticker that downloaded successfully
prices_xts <- do.call(
merge,
lapply(raw_list[!sapply(raw_list, inherits, "try-error")], Cl)
)
# --- Extract Year-End Prices --------------------------------------------
# Define target year-end dates
year_ends <- as.Date(paste0(2017:2024, "-12-31"))
# Get last available closing price on or before each year-end
ye_prices <- lapply(year_ends, function(d) {
na.omit(prices_xts[paste0("/", d)])[NROW(na.omit(prices_xts[paste0("/", d)])), , drop = FALSE]
})
# Convert year-end prices to data frame
ye_df <- do.call(rbind, ye_prices) %>%
as.data.frame() %>%
mutate(date = year_ends) %>%
select(date, everything())
# --- Latest Close Price --------------------------------------------------
# Extract most recent available closing price
latest_price <- tail(prices_xts, 1)
latest_df <- data.frame(
date = as.Date(index(latest_price)),
setNames(
as.list(coredata(latest_price)),
sub("Close\\.", "Close.", colnames(latest_price))
)
)
# --- Combine Year-End and Latest Prices ----------------------------------
# Combine into single long-format data frame and annotate year
out <- bind_rows(ye_df, latest_df) %>%
arrange(date) %>%
mutate(year = lubridate::year(date)) %>%
select(year, date, everything())
# Display the combined result
print(out)
# --- Reshape to Wide Format ----------------------------------------------
# Convert long format to wide with one row per ticker, columns per date
library(tidyr)
library(dplyr)
wide_prices <- out %>%
select(-year) %>% # Remove numeric year column
select(date, ends_with(".Close")) %>% # Keep only date and Close columns
pivot_longer(-date, names_to = "ticker", values_to = "price") %>%
mutate(ticker = sub("\\.Close$", "", ticker)) %>%
pivot_wider(names_from = date, values_from = price, names_prefix = "D_") %>%
arrange(ticker)
# Display reshaped data
wide_prices
# --- Write to CSV --------------------------------------------------------
# Save wide-format year-end + latest prices to file
write.csv(wide_prices,
file = "year_end_prices_2017_2024_latest.csv",
row.names = FALSE)