How do I turn prices into returns?

Turn a vector of closing prices into daily returns, first with a loop and then in one line, and compound those returns into a single figure.

A return is today’s price over yesterday’s price, minus one. Three prices give two returns, because the first price has no day before it.

In this lesson I compute returns twice on the same closes: once with a loop, so every piece of the arithmetic is on the page, and once with a single line of R. Then I compound them into the return for the whole stretch.

Step 1. The loop, so the arithmetic is visible

numeric(k) makes a vector of k zeros. I make it the right size up front and fill each slot in turn, rather than growing the vector as I go.

seq_along(closes) gives the positions 1 to n, and putting [-1] after it drops the first of those positions. So the loop runs over 2 upward. Position 1 is skipped: there is nothing before it to compare against, and on a single price the loop does not run at all.

closes  <- c(100, 110, 99)      # three closing prices in order
n       <- length(closes)       # how many prices I have
returns <- numeric(n - 1)       # room for n - 1 returns, zeros for now

for (i in seq_along(closes)[-1]) {                              # i = 2, 3
  returns[i - 1] <- (closes[i] - closes[i - 1]) / closes[i - 1] # today minus yesterday, over yesterday
}

print(returns)                  # -> [1]  0.1 -0.1
[1]  0.1 -0.1
print(length(closes))           # -> [1] 3
[1] 3
print(length(returns))          # -> [1] 2
[1] 2

The price went 100 to 110, a gain of 10%, then 110 to 99, a fall of 10%. Return i - 1 sits in slot i - 1, so the returns vector is one shorter than the prices vector.

Step 2. The same thing in one line

diff(x) returns the gaps between neighbouring elements: x[2] - x[1], x[3] - x[2], and so on. head(x, -1) returns every element except the last, which is exactly the list of yesterdays. Divide one by the other and you have every return at once.

I switch to five closes here, picked so you can check each return in your head.

closes <- c(100, 110, 99, 99, 108.9)  # five closes, easy numbers on purpose

print(diff(closes))                   # -> [1]  10.0 -11.0   0.0   9.9
[1]  10.0 -11.0   0.0   9.9
print(head(closes, -1))               # -> [1] 100 110  99  99
[1] 100 110  99  99

diff() gave four gaps and head(closes, -1) gave four denominators, so the division lines up element by element.

r <- diff(closes) / head(closes, -1)  # every return in one line
print(r)                              # -> [1]  0.1 -0.1  0.0  0.1
[1]  0.1 -0.1  0.0  0.1
print(round(r * 100, 2))              # -> [1]  10 -10   0  10
[1]  10 -10   0  10

Up 10%, down 10%, flat, up 10%.

Now run the Step 1 loop on these same five closes and compare. all.equal() checks two numeric vectors match to within floating point tolerance, which is what you want here rather than ==.

loop_r <- numeric(length(closes) - 1)                            # same preallocation as Step 1
for (i in seq_along(closes)[-1]) {                               # same loop, 2 to 5
  loop_r[i - 1] <- (closes[i] - closes[i - 1]) / closes[i - 1]   # same arithmetic
}

print(loop_r)                       # -> [1]  0.1 -0.1  0.0  0.1
[1]  0.1 -0.1  0.0  0.1
print(all.equal(loop_r, r))         # -> [1] TRUE
[1] TRUE

Same four numbers. The one line replaces the loop, and from here on I use the one line.

Step 3. Compound the returns into one number

To get the return over the whole stretch, multiply the growth factors 1 + r together and take the starting 1 back off. prod() multiplies a vector down to a single number.

total <- prod(1 + r) - 1              # 1.1 * 0.9 * 1.0 * 1.1, minus 1
print(total)                          # -> [1] 0.089
[1] 0.089

That figure has to equal the last close over the first close, minus 1, because everything in between cancels.

first_last <- closes[length(closes)] / closes[1] - 1   # 108.9 / 100 - 1
print(first_last)                                      # -> [1] 0.089
[1] 0.089
print(all.equal(total, first_last))                    # -> [1] TRUE
[1] TRUE

closes[length(closes)] is how you reach the last element in R. A negative index drops elements instead of counting from the end, as [-1] did to the positions in Step 1, so closes[-1] would give you the last four prices, not the last one.

Now add the same four returns instead of multiplying them.

added <- sum(r)                       # the four returns added, not compounded

print(round(c(compounded = total, added = added) * 100, 4))
compounded      added 
       8.9       10.0 
# -> compounded      added
# ->        8.9       10.0

The prices went from 100 to 108.9, a move of 8.9%. prod(1 + r) - 1 gives 8.9%. sum(r) gives 10%. Simple returns compound, so use prod(1 + r) - 1.

Your turn

Take closes <- c(50, 55, 55, 44). Build the returns with diff() and head(), compound them, and check the result against the last close over the first. How many returns do four prices give?

closes <- c(50, 55, 55, 44)                       # four prices
r      <- diff(closes) / head(closes, -1)         # three returns

print(r)                                          # -> [1]  0.1  0.0 -0.2
print(length(r))                                  # -> [1] 3

total <- prod(1 + r) - 1                          # 1.1 * 1.0 * 0.8, minus 1
print(total)                                      # -> [1] -0.12
print(closes[length(closes)] / closes[1] - 1)     # -> [1] -0.12

print(round(c(compounded = total, added = sum(r)) * 100, 4))
# -> compounded      added
# ->        -12        -10