Refactoring simulatePlan

As we changed the signature of futureCapital, we also need to change the callers of that function. The only caller is simulatePlan. Before introducing variable rates, the implementation was straightforward: we just had to call futureCapital for the accumulation and decumulation phases with the same fixed rate argument. However, with variable rate, we must make sure that the decumulation phase uses the rates that follow the rates of the accumulation phase.

For instance, consider that you started saving in 1950, and retired in 1975. For the accumulation phase, you need to use the returns from 1950 to 1975, and for the decumulation, you must use the returns from 1975. We created a new unit test to make sure that we are using different returns for the two phases:

val params = RetCalcParams(
nbOfMonthsInRetirement = 40 * 12,
netIncome = 3000,
currentExpenses = 2000,
initialCapital = 10000)

"RetCalc.simulatePlan"
should {
"calculate the capital at retirement and the capital after death" in {
val (capitalAtRetirement, capitalAfterDeath) =
RetCalc.simulatePlan(
returns = FixedReturns(0.04), params, nbOfMonthsSavings = 25*12)

capitalAtRetirement should === (541267.1990)
capitalAfterDeath should === (309867.5316)
}

"use different returns for capitalisation and drawdown" in {
val nbOfMonthsSavings = 25 * 12
val returns = VariableReturns(
Vector.tabulate(nbOfMonthsSavings +
params.nbOfMonthsInRetirement)(i =>
if (i < nbOfMonthsSavings)
VariableReturn(i.toString, 0.04 / 12)
else
VariableReturn(i.toString, 0.03 / 12)))
val (capitalAtRetirement, capitalAfterDeath) =
RetCalc.simulatePlan(returns, params, nbOfMonthsSavings)
capitalAtRetirement should ===(541267.1990)
capitalAfterDeath should ===(-57737.7227)
}
}

Since simulatePlan has quite a lot of parameters apart from the returns parameter, we decided to put them in a case class called RetCalcParams. This way, we are able to reuse the same parameters for different unit tests. We will also be able to reuse it in nbMonthsSaving. As seen previously, we use the function tabulate to generate values for our variable returns.

The expected value for capitalAtRetirement can be obtained with Excel by using -FV(0.04/12, 25*12, 1000, 10000). The expected value for capitalAfterDeath can be obtained by using -FV(0.03/12, 40*12, -2000, 541267.20).

Here is the implementation in RetCalc:

case class RetCalcParams(nbOfMonthsInRetirement: Int,
netIncome: Int,
currentExpenses: Int,
initialCapital: Double)

object RetCalc {
def simulatePlan(returns: Returns, params: RetCalcParams,
nbOfMonthsSavings: Int)
: (Double, Double) = {
import params._
val capitalAtRetirement = futureCapital(
returns = returns,
nbOfMonths = nbOfMonthsSavings,
netIncome = netIncome, currentExpenses = currentExpenses,
initialCapital = initialCapital)

val capitalAfterDeath = futureCapital(
returns = OffsetReturns(returns, nbOfMonthsSavings),
nbOfMonths = nbOfMonthsInRetirement,
netIncome = 0, currentExpenses = currentExpenses,
initialCapital = capitalAtRetirement)

(capitalAtRetirement, capitalAfterDeath)
}

The first line, import params._, brings all the parameters of RetCalcParams into scope. This way, you can directly use, for instance, netIncome without having to prefix it with params.netIncome . In Scala, you can not only import classes from a package, but also functions or values from an object.

In the second call to futureCapital, we introduce a new subclass called OffsetReturns, which will shift the starting month. We need to write a new unit test for it in ReturnsSpec:

"Returns.monthlyReturn" should {
"return a fixed rate for a FixedReturn" in {...}

val variableReturns = VariableReturns(
Vector(VariableReturn("2000.01", 0.1), VariableReturn("2000.02", 0.2)))

"return the nth rate for VariableReturn" in {...}

"return an error if n > length" in {...}

"return the n+offset th rate for OffsetReturn" in {
val returns = OffsetReturns(variableReturns, 1)
Returns.monthlyRate(returns, 0).right.value should ===(0.2)
}
}

And the corresponding implementation in Returns.scala is as follows:

sealed trait Returns
case class
FixedReturns(annualRate: Double) extends Returns
case class
VariableReturn(monthId: String, monthlyRate: Double)
case class
OffsetReturns(orig: Returns, offset: Int) extends Returns

object Returns {
def monthlyRate(returns: Returns, month: Int): Double = returns match {
case FixedReturns(r) => r / 12
case VariableReturns(rs) => rs(month % rs.length).monthlyRate
case OffsetReturns(rs, offset) => monthlyRate(rs, month + offset)
}
}

For an offset return, we call monthlyRate recursively and add the offset to the requested month.

Now, you can compile everything with cmd + F9 and rerun the unit tests. They should all pass.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset