A Stock Price Example

I. Ozkan

Spring 2025

Download/Visualize/Manipulate Stock Price Time Series Data

In this example, quantmod and lubridate packages are used. quantmod package is a non-complete package used for Quantitative Financial Modelling Framework. It depends xts and zoo packages hence we do not need to load these packages explicitely. Here the aim is to give an example for lubridate package usage.

It is not easy to manipulate time series data since all observations have taken through time. In order to perform some operations one may need to index time values properly. lubridate package is quite useful to deal with this type of data. As an example, we will use real world data to show some functions fo this package.

IBM Stock Price Series

First let’s downlad IBM stock price series. quantmod package provides getSymbols function that downloads historical data from some sources. See help page (?getSymbols).

# Load libraries
library(lubridate)
library(quantmod)

# Get Stock Price Series from yahoo finance (finance.yahoo.com)
getSymbols("IBM", from="2010-01-01")
## [1] "IBM"
# First Few observations. Data OHLC type financial data
head(IBM)
##            IBM.Open IBM.High  IBM.Low IBM.Close IBM.Volume IBM.Adjusted
## 2010-01-04 125.4111 127.1224 125.0956  126.6252    6438444     74.26977
## 2010-01-05 125.8891 126.0516 124.3786  125.0956    7156104     73.37260
## 2010-01-06 124.9331 125.7075 124.1013  124.2830    5863144     72.89598
## 2010-01-07 124.1587 124.5220 123.2409  123.8528    6109268     72.64365
## 2010-01-08 123.3939 125.1625 123.3748  125.0956    4390271     73.37260
## 2010-01-11 125.2964 125.2964 123.0115  123.7859    5993998     72.60437
# Last Few observations
tail(IBM)
##            IBM.Open IBM.High IBM.Low IBM.Close IBM.Volume IBM.Adjusted
## 2025-02-19   262.00   264.36  260.09    264.32    3718700       264.32
## 2025-02-20   263.65   265.09  262.15    264.74    4884800       264.74
## 2025-02-21   263.85   264.83  261.10    261.48    5667900       261.48
## 2025-02-24   261.50   263.85  259.58    261.87    4398100       261.87
## 2025-02-25   261.08   263.48  256.77    257.75    6292200       257.75
## 2025-02-26   258.10   258.33  254.41    255.84    3458800       255.84
# Plot IBM data
plot(as.zoo(IBM))

# Plot IBM series expect Volume. 
plot(IBM[,-5])

Financial Data

Financial Data generally represented in a special format; Open-High-Low-Close-Volume (OHLC) format. Yahoo provides Adjusted price column. Please do refer to yahoo-finance web site.

Let’s use wday():day of Week, mday() day of month, and month() functions of lubridate package.

# First few days without labelling..
head(wday(index(IBM), label=F))
## [1] 2 3 4 5 6 2
# First few days with labelling..
head(wday(index(IBM), label=T))
## [1] Mon Tue Wed Thu Fri Mon
## Levels: Sun < Mon < Tue < Wed < Thu < Fri < Sat
# result is factor object..
str(head(wday(index(IBM), label=T)))
##  Ord.factor w/ 7 levels "Sun"<"Mon"<"Tue"<..: 2 3 4 5 6 2
is.factor(head(wday(index(IBM), label=T)))
## [1] TRUE
# Get the first few days as character (instead of factor)
paste(head(wday(index(IBM), label=T)))
## [1] "Mon" "Tue" "Wed" "Thu" "Fri" "Mon"
# days of year
yday(Sys.Date())
## [1] 58
yday(index(first(IBM["200303"], "1 week")))
## numeric(0)
# days of quarter
qday(Sys.Date())
## [1] 58
head(qday(index(IBM)))
## [1]  4  5  6  7  8 11
quarter(Sys.Date()) # quarter of the date
## [1] 1
# days of month
head(mday(index(IBM)))
## [1]  4  5  6  7  8 11
# month of first few obs.
head(month(index(IBM)))
## [1] 1 1 1 1 1 1
# Convert to monthly data using to.monthly() function. Then get the first few months
head(month(index(to.monthly(IBM)), label=T))
## [1] Jan Feb Mar Apr May Jun
## 12 Levels: Jan < Feb < Mar < Apr < May < Jun < Jul < Aug < Sep < ... < Dec
# Just names only..
paste(head(month(index(to.monthly(IBM)), label=T)))
## [1] "Jan" "Feb" "Mar" "Apr" "May" "Jun"
# last weekdays of months and of course weekdaysdate
# See http://stackoverflow.com/questions/33088424/r-find-last-weekday-of-month
last_weekday_o_month <- function(dx) {
  ave( 
  paste(wday(dx, label=T, abbr=F)), 
  paste(month(dx, label=T, abbr = F)), 
  FUN = function(x) tail(x[ !(x %in% c("Saturday","Sunday")) ], 1) 
  ) 
}

last_weekdaydate_o_month <- function(dx){
  ave( 
    dx, 
#     paste(month(dx, label=T, abbr = F)), 
    month(dx, label=T),
    year(dx),
    FUN = function(x) tail(x[ !(wday(x, label=T, abbr=F) %in% c("Saturday","Sunday")) ], 1) 
  )
}  

# Get the last 25 end of months weekdays Adjusted close value..

Ad(IBM[tail(unique(last_weekdaydate_o_month(index(IBM))), 15),])
##            IBM.Adjusted
## 2023-12-29     156.7993
## 2024-01-31     176.0793
## 2024-02-29     179.0100
## 2024-03-28     184.7471
## 2024-04-30     160.7926
## 2024-05-31     163.0239
## 2024-06-28     168.9840
## 2024-07-31     187.7339
## 2024-08-30     199.2222
## 2024-09-30     217.8996
## 2024-10-31     203.7462
## 2024-11-29     225.9050
## 2024-12-31     218.3752
## 2025-01-31     254.0078
## 2025-02-26     255.8400

Close, Adjusted Close, Day-of-week

Ok. Now lets obtain Adjusted price series and check the prices for some specific day of the weeks. (Here Ad() functin is used. But you may get the very same data with referencing this column by its number or name; IBM[,6], or IBM[,"IBM.Adjusted"])

ibm <- Ad(IBM)

# Get Mondays
head(ibm[wday(index(ibm))==2])
##            IBM.Adjusted
## 2010-01-04     74.26977
## 2010-01-11     72.60437
## 2010-01-25     70.72028
## 2010-02-01     69.90721
## 2010-02-08     68.64843
## 2010-02-22     71.44778
# Mondays.. 
wday(head(ibm[wday(index(ibm))==2]), label=T)
## [1] Mon Mon Mon Mon Mon Mon
## Levels: Sun < Mon < Tue < Wed < Thu < Fri < Sat
# Fridays.. 
wday(head(ibm[wday(index(ibm))==6]), label=T)
## [1] Fri Fri Fri Fri Fri Fri
## Levels: Sun < Mon < Tue < Wed < Thu < Fri < Sat
# Get return series (cont. compounding, log(xt/xt-1)=log(xt)-log(xt-1))
ibm.ret <- diff(log(ibm))

# Mondays returns..
head(ibm.ret[wday(index(ibm.ret))==2])
##            IBM.Adjusted
## 2010-01-04           NA
## 2010-01-11 -0.010525410
## 2010-01-25  0.004928166
## 2010-02-01  0.018457325
## 2010-02-08 -0.008903237
## 2010-02-22 -0.002676876
# Data and return series together..
head(merge(ibm, ibm.ret))
##            IBM.Adjusted IBM.Adjusted.1
## 2010-01-04     74.26977             NA
## 2010-01-05     73.37260   -0.012153450
## 2010-01-06     72.89598   -0.006517020
## 2010-01-07     72.64365   -0.003467471
## 2010-01-08     73.37260    0.009984490
## 2010-01-11     72.60437   -0.010525410
# Head of mondays returns with levels.
head(merge(ibm, ibm.ret)[wday(merge(ibm, ibm.ret))==2])
##            IBM.Adjusted IBM.Adjusted.1
## 2010-01-04     74.26977             NA
## 2010-01-11     72.60437   -0.010525410
## 2010-01-25     70.72028    0.004928166
## 2010-02-01     69.90721    0.018457325
## 2010-02-08     68.64843   -0.008903237
## 2010-02-22     71.44778   -0.002676876

Histogram, Day-of-Week Histogram

Histogram for IBM stock price return series. This example includes comparing different day-of-week returns.

# Histogram: IBM returns.
hist(ibm.ret, breaks=100, main="Ibm's Cont. Comp. Returns", xlab="Returns")

# Histogram for mondays returns.
hist(ibm.ret[wday(index(ibm.ret))==2], breaks=50, main="Ibm's Monday Cont. Comp. Returns", xlab="Returns")

# Histogram for fridays returns. (slightly better shape?)
hist(ibm.ret[wday(index(ibm.ret))==6], breaks=50, main="Ibm's Friday Cont. Comp. Returns", xlab="Returns")

# Empirical Cumulative Distribution of Fridays Return
plot(ecdf(as.vector(ibm.ret[wday(index(ibm.ret))==6])), main="Friday ECDF")

# Comparison pf Empirical Cumulative Distributions of Monday (red) and Fridays (black) Return
plot(ecdf(as.vector(ibm.ret[wday(index(ibm.ret))==6])), main="ECDF Distributions")
lines(ecdf(as.vector(ibm.ret[wday(index(ibm.ret))==2])), col="red")

# What about mean returns of weekdays..
mean.ret <- cbind(
mean(ibm.ret[wday(index(ibm.ret))==2], na.rm=T),
mean(ibm.ret[wday(index(ibm.ret))==3], na.rm=T),
mean(ibm.ret[wday(index(ibm.ret))==4], na.rm=T),
mean(ibm.ret[wday(index(ibm.ret))==5], na.rm=T),
mean(ibm.ret[wday(index(ibm.ret))==6], na.rm=T))
colnames(mean.ret) <- levels(wday(index(ibm.ret), label=T))[2:6]
rownames(mean.ret) <- "Mean Returns"

# Lets see up to 6 digits..
round(mean.ret, 6)
##                   Mon     Tue      Wed      Thu       Fri
## Mean Returns 0.000935 8.4e-05 0.000331 0.000448 -0.000125

Time Series Plots

Let’s plot only Mondays Adjusted closes.

plot(ibm[wday(index(ibm))==2], main="IBM Mondays Prices", ylab="$")

# zoo type..
plot(as.zoo(ibm[wday(index(ibm))==2]), main="IBM Mondays Prices", ylab="$", xlab="Date")

Monthly Series

Now let’s play with monthly series..

# Convert to monthly series
ibm.m <- Ad(to.monthly(IBM))
# Get the return series similar to above
ibm.mret <- diff(log(ibm.m))
# The mean of all montly returns.. (up to 6 digits)
round(mean(ibm.mret, na.rm=T), 6)
## [1] 0.00727
# What about the yearly mean returns calculated from monthly returns.. (up to 6 digits).. Seems better than T-Bonds.. Remember utility functions..
round((1+mean(ibm.mret, na.rm=T))^12-1, 6)
## [1] 0.090812
# Here is the mean yearly return calculated from data
round(mean(diff(log(Ad(to.yearly(IBM)))), na.rm=T), 6)
## [1] 0.074354
# Get some mean return value fo specific months.. 
head(ibm.mret[month(index(ibm.mret))==1], na.rm=T)
##          IBM.Adjusted
## Jan 2010           NA
## Jan 2011   0.09879775
## Jan 2012   0.04633200
## Jan 2013   0.05840194
## Jan 2014  -0.05981150
## Jan 2015  -0.04545790
# Mean January Returns.. (check your R and OS language for different month labels..)
mean(ibm.mret[month(index(ibm.mret))==1], na.rm=T)
## [1] 0.03461933
# February..
mean(ibm.mret[month(index(ibm.mret))==2], na.rm=T)
## [1] 0.007347965
# All January returns since 1972
barplot(ibm.mret[month(index(ibm.mret))==1])

An Example: Comparing Return Distribution

One can compare returns distributions by using Kolmogorov-Smirnov Tests. See ?ks.test

# An example.. Compare Mondays and Tuesdays..
ks.test(ibm.ret[wday(index(ibm.ret))==2], ibm.ret[wday(index(ibm.ret))==3])
## 
##  Asymptotic two-sample Kolmogorov-Smirnov test
## 
## data:  ibm.ret[wday(index(ibm.ret)) == 2] and ibm.ret[wday(index(ibm.ret)) == 3]
## D = 0.062899, p-value = 0.1049
## alternative hypothesis: two-sided
# Compare Mondays and Fridays..
ks.test(ibm.ret[wday(index(ibm.ret))==2], ibm.ret[wday(index(ibm.ret))==6])
## 
##  Asymptotic two-sample Kolmogorov-Smirnov test
## 
## data:  ibm.ret[wday(index(ibm.ret)) == 2] and ibm.ret[wday(index(ibm.ret)) == 6]
## D = 0.058942, p-value = 0.1545
## alternative hypothesis: two-sided
# Let's obtain all.. Create 5*5 matrix.. We use only upper-triangle
# See the structure of ks.test result first..
p.matr <- matrix(rep(0, 5*5), nrow=5)
colnames(p.matr) <- rownames(p.matr) <- levels(wday(index(ibm.ret), label=T))[2:6]

for (i in 2:6){
  for (j in i:6){
    p.matr[i-1,j-1] <- ks.test(as.numeric(ibm.ret[wday(index(ibm.ret))==i]), as.numeric(ibm.ret[wday(index(ibm.ret))==j]))$p.value
  }
}

# p-vales matrix of  Kolmogorov-Smirnov Tests
round(p.matr, 3)
##     Mon   Tue   Wed   Thu   Fri
## Mon   1 0.177 0.651 0.303 0.425
## Tue   0 1.000 0.675 0.082 0.554
## Wed   0 0.000 1.000 0.689 0.971
## Thu   0 0.000 0.000 1.000 0.662
## Fri   0 0.000 0.000 0.000 1.000
# Let's obtain all months returns.. Create 12*12 matrix.. We use only upper-triangle
# See the structure of ks.test result first..
pm.matr <- matrix(rep(0, 12*12), nrow=12)
colnames(pm.matr) <- rownames(pm.matr) <- levels(month(index(ibm.mret), label=T))

for (i in 1:12){
  for (j in i:12){
    pm.matr[i,j] <- ks.test(as.numeric(ibm.mret[month(index(ibm.mret))==i]), as.numeric(ibm.mret[month(index(ibm.mret))==j]))$p.value
  }
}

# p-vales matrix of  Kolmogorov-Smirnov Tests
round(pm.matr, 3)
##     Jan   Feb   Mar   Apr   May   Jun   Jul   Aug   Sep   Oct   Nov   Dec
## Jan   1 0.114 0.678 0.184 0.075 0.026 0.678 0.026 0.386 0.184 0.386 0.026
## Feb   0 1.000 0.817 0.729 0.630 0.630 0.520 0.331 0.552 0.043 0.134 0.630
## Mar   0 0.000 1.000 0.386 0.386 0.386 1.000 0.184 0.938 0.008 0.678 0.678
## Apr   0 0.000 0.000 1.000 0.938 0.678 0.386 0.938 0.678 0.386 0.184 0.678
## May   0 0.000 0.000 0.000 1.000 1.000 0.184 0.938 0.678 0.184 0.184 1.000
## Jun   0 0.000 0.000 0.000 0.000 1.000 0.184 0.938 0.938 0.075 0.184 1.000
## Jul   0 0.000 0.000 0.000 0.000 0.000 1.000 0.075 0.386 0.075 0.678 0.386
## Aug   0 0.000 0.000 0.000 0.000 0.000 0.000 1.000 0.678 0.386 0.184 0.938
## Sep   0 0.000 0.000 0.000 0.000 0.000 0.000 0.000 1.000 0.026 0.678 0.938
## Oct   0 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 1.000 0.002 0.075
## Nov   0 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 1.000 0.386
## Dec   0 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 1.000

ASSIGNMENT

Repeat the above steps for BIST100 series that can be downloaded with: getSymbols("XU100.IS", return.class="xts")