Computing real returns
For a given month, n, the real return is returnn - inflationRaten, hence the following formula:
We are going to create a new function in Returns that creates VariableReturns using Vector[EquityData] and Vector[InflationData]. Add the following unit test to ReturnsSpec:
"Returns.fromEquityAndInflationData" should {
"compute real total returns from equity and inflation data" in {
val equities = Vector(
EquityData("2117.01", 100.0, 10.0),
EquityData("2117.02", 101.0, 12.0),
EquityData("2117.03", 102.0, 12.0))
val inflations = Vector(
InflationData("2117.01", 100.0),
InflationData("2117.02", 102.0),
InflationData("2117.03", 102.0))
val returns = Returns.fromEquityAndInflationData(equities,
inflations)
returns should ===(VariableReturns(Vector(
VariableReturn("2117.02", (101.0 + 12.0 / 12) / 100.0 - 102.0 /
100.0),
VariableReturn("2117.03", (102.0 + 12.0 / 12) / 101.0 - 102.0 /
102.0))))
}
}
We create two small Vector instances of EquityData and InflationData, and calculate the expected value using the preceding formula.
Here is the implementation of fromEquityAndInflationData in Returns.scala:
object Returns {
def fromEquityAndInflationData(equities: Vector[EquityData],
inflations: Vector[InflationData]):
VariableReturns = {
VariableReturns(equities.zip(inflations).sliding(2).collect {
case (prevEquity, prevInflation) +: (equity, inflation) +:
Vector() =>
val inflationRate = inflation.value / prevInflation.value
val totalReturn =
(equity.value + equity.monthlyDividend) / prevEquity.value
val realTotalReturn = totalReturn - inflationRate
VariableReturn(equity.monthId, realTotalReturn)
}.toVector)
}
Firstly, we zip the two Vectors to create a collection of tuples, (EquityData, InflationData). This operation brings our two collections together as if we were zipping a jacket. It is a good habit to play around with the Scala Console to get a sense of what it does:
scala> Vector(1,2).zip(Vector("a", "b", "c"))
res0: scala.collection.immutable.Vector[(Int, String)] = Vector((1,a), (2,b))
Note that the resulting Vector has a size that is the minimum size of the two arguments. The last element, "c", is lost, because there is nothing to zip it with!
This is a good start, as we could now iterate through a collection that can give us pricen, dividendsn, and inflationn. But in order to calculate our formula, we also need the previous data on n-1. For this, we use sliding(2). I encourage you to read the documentation on sliding. Let's try it in the console:
scala> val it = Vector(1, 2, 3, 4).sliding(2)
it: Iterator[scala.collection.immutable.Vector[Int]] = non-empty iterator
scala> it.toVector
res0: Vector[scala.collection.immutable.Vector[Int]] =
Vector(Vector(1, 2), Vector(2, 3), Vector(3, 4))
scala> Vector(1).sliding(2).toVector
res12: Vector[scala.collection.immutable.Vector[Int]] = Vector(Vector(1))
sliding(p) creates an Iterator which will produce collections of size p. Each collection will have a new iterated element plus all the previous p-1 elements. Notice that if the collection size n is lower than p, the produced collection will have a size of n.
Next, we iterate through the sliding collections using collect. collect is similar to map: it allows you to transform the elements of a collection, but with the added capability of filtering them. Basically, whenever you want to filter and map a collection, you can use collect instead. The filtering is performed using pattern matching. Anything that does not match any pattern is filtered out:
scala> val v = Vector(1, 2, 3)
v: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3)
scala> v.filter(i => i != 2).map(_ + 1)
res15: scala.collection.immutable.Vector[Int] = Vector(2, 4)
scala> v.collect { case i if i != 2 => i + 1 }
res16: scala.collection.immutable.Vector[Int] = Vector(2, 4)
Notice that, in the preceding code, we used map(_ + 1) instead of map(i => i + 1). This is a shorthand notation for an anonymous function. Whenever you use a parameter once in your anonymous function, you can replace it with _.
Finally, we pattern match on our zipped and sliding elements using the following:
case (prevEquity, prevInflation) +: (equity, inflation) +: Vector() =>
This has the benefit of filtering out sliding elements of size 0 or 1, if we were passing equities or inflation arguments of size 0 or 1. We will not write a unit test for this edge case in this book, but I encourage you to do so as an excercise.
The rest of the function is straightforward: we use the matched variables to compute the formula and create a new VariableReturn instance. The resulting iterator is converted to Vector, and we instantiate a VariableReturns case class using it.