Making Better Investments with Math and JavaScript

January 18, 201912 min read

Last reviewed January 18, 2019

It is often said that one of the greatest physicists of the last century, Richard Feynman, endorsed that the best way of learning a subject is to refine a concept to be simple enough for a 5-year-old to understand. Well, I don’t expect to have any kids among my audience but I will try to explain how financial concepts NPV and IRR work using what you like (code and math).

This will set the foundations for building an online calculator and help us make better financial decisions in the future (disclaimer: don’t blame me if you go bankrupt).

Let’s start then with a simple but intriguing problem.

The Problem

Suppose that your friend Elon asks if you are interested on backing his new endeavour. He says that if you lend him $10,000 he expects to return $11,000 to you after one year. Should you accept the offer?

In other words: should you deposit $10,000 now in order to withdraw $11,000 in one year?

Of course I don’t intend to go into metaphysical questions about what you should do with your money neither to assess the risks of such transaction (as Elon’s predictions might not be true). The real goal here is to develop a tool where one can compare potential investments and then decide which one fits best given one’s condition.

Net Present Value

Now suppose the following: you know that you can easily allocate funds in government bonds that pay 5% per year. If your goal is to receive back the same amount your friend Elon is promising, how much should you invest?

Well, it’s easy to see that (1+0.05)x=11000(1 + 0.05)x = 11000, where xx is the initial investment in order to receive the same return Elon promised but with government bonds. This yields x10476x \cong 10476.

It means that, in today’s money, $11,000 equates to $10,476 if you decide to go with public bonds. But according to Elon all you need is $10,000 and hence we say that

NPV=1047610000=476NPV = 10476 - 10000 = 476

Which means that, in today’s money, you are expected to have a net profit of $476 in this transaction.

More generally, we can define NPV as:

NPV=t=0nCt(1+r)tNPV = \sum_{t=0}^n \frac{C_t}{(1 + r)^t}

Where CtC_t is the cash inflow or outflow in period tt and rr is the discount rate. Check that for C0=10000C_0 = -10000, C1=11000C_1 = 11000, n=1n = 1 and r=0.05r = 0.05 we achieve the same result.

Remember, in plain english (and according to investopedia):

NPV = (Today’s value of the expected cash flows) – (Today’s value of invested cash)

Writing a Function to Evaluate the NPV

Shall we write some code?

The goal here is to write a function that translates the above equation into bits. Thankfully with JavaScript we can write it in a single-line function:

const NPV = (cashflow, discountRate) => cashflow
  .reduce((acc, val, i) => acc + val / Math.pow((1 + discountRate), i), 0);

Beautiful, isn’t it?

If you are not familiar with arrow functions, I suggest you take some time and read this great article about it. You may also want to check .reduce reference in JavaScript.

Let’s walk through it:

First we assume that cashflow is an array of numbers, the one we want to reduce. The .reduce method takes two arguments, the first one being the reducer and the second one being an optional initial value.

const reducer = (acc, val, i) => acc + val / Math.pow((1 + discountRate), i);

On the other hand, reducer is a function which takes three args:

  • acc is the accumulator, the result of previous iterations.
  • val is the current value being evaluated.
  • i is the current index.

We are then calling the reducer for every entry in the array (here assuming the value of val), and evaluation the following:

acc + val / Math.pow((1 + discountRate), i)

Which basically is evaluating Ci(1+discountRate)i\frac{C_i}{(1 + discountRate)^i} and adding to the previously evaluated ii (remember that we initialize acc as 0).

You are now ready to test our function:

const NPV = (cashflow, discountRate) => cashflow
  .reduce((acc, val, i) => acc + val / Math.pow((1 + discountRate), i), 0);

const cashflow = [-10000, 11000];
const discountRate = 0.05;
console.log(NPV(cashflow, discountRate)); // 476.19047619047524

Great, now you know how to define NPV in english, in mathematics and in JavaScript. Tell me about being a polyglot!

Internal Rate of Return

NPV is great but still an absolute number. Is NPV=476NPV = 476 good or bad? Apart from the fact that this is a particular question (once risks are assessed differently by different people), it lacks information. For example, it is clear that this number is significantly better if the initial investment C0C_0 was $5,000, and definitely worse if t=10t = 10.

You then start looking for another metric. Instead of thinking about how much money you would make with such investment, you try to figure out what the discount rate is. You know that the government pays you 5%, but how much is Elon paying you given his predictions become true?

Again, it’s easy to see that (1+r)10000=11000(1 + r)10000 = 11000, where rr is the discount rate for this investment with the projected cash inflow. This yields r=0.1r = 0.1, or 10%.

And now we are talking! You have a very concrete way of comparing (this is the key word) two investments: government bonds paying 5%, Elon paying 10%. Are Elon’s projections real? Well, for twice the rate you find in the market, you may consider give it a shot.

Meet IRR — the internal return rate for a given initial investment and a projected cash inflow or outflow over a period of time. We can define IRR as:

0=t=0nCt(1+r)t0 = \sum_{t=0}^n \frac{C_t}{(1 + r)^t}

Where CtC_t is the cash inflow or outflow over period tt and rr is the internal return rate. Check that for C0=10000C_0 = -10000, C1=11000C_1 = 11000 and n=1n = 1 the formula yields r=0.1r = 0.1.

IRR is the discount rate that, for a projected cash inflow or outflow, yields NPV=0NPV = 0.

And now things are making more sense (hopefully).

A More Complex Problem

Elon’s example was deliberately easy to follow but — as many things in life — there is not always the case.

You are a businessman and as such you understand that immediate returns are not usually big. Is your understanding that, in order to extract maximum value, you need to reinvest your money for at least 5 years.

Your friend Jeff then approaches with a different opportunity: you invest initally $10,000 and reinvest all generated cash for the next 5 years. His estimates are that this business will generate $11,000 in year 1, $12,000 in year 2, $13,000 in year 3, $14,000 in year 4 and finally $15,000 in year 5.

That sounds good, but how good exactly? You then use the IRR formula you just learned in order to evaluate the expected rate of growth for this project:

0=10000(1+r)0+11000(1+r)1+12000(1+r)2+13000(1+r)3+14000(1+r)4+15000(1+r)50 = \frac{10000}{(1 + r)^0} + \frac{11000}{(1 + r)^1} + \frac{12000}{(1 + r)^2} + \frac{13000}{(1 + r)^3} + \frac{14000}{(1 + r)^4} + \frac{15000}{(1 + r)^5}

You quickly realize that things look more elegant if you let x=11+rx = \frac{1}{1 + r}:

15000x5+14000x4+13000x3+12000x2+11000x10000=015000x^5 + 14000x^4 + 13000x^3 + 12000x^2 + 11000x - 10000 = 0

Which is quintic polynomial. Well, you definitely know how to solve quadratic polynomials (your friend Bhaskhara), cubic polynomials (Cardano-Tartaglia’s method) and quartic polynomials (which is ugly but doable). But it happens that for 5th degree polynomials a fellow named Évariste Galois explains that you simply can’t have a nice formula (as Bhaskara is). His ideas were used to proof the Abel-Ruffini theorem (Galois later died at age of 20 in a duel, but this is another story).

Anyway, we live in the computer era and therefore we shall be able solve the above equation.

Newton’s Method

Couple centuries ago one guy named Isaac Newton was born and did some great stuff. That should ring a bell.

Among thousand other things, Newton figured out a way to find roots of an equation. You can find a full reference about it in Wikipedia but I’ll try to give some intuition here.

First, we are not trying to find all solutions. We are specially interested on the solution that is semantically coherent, meaning that we should be able to put our discount rate in a fair range — I know that Jeff’s operation is expected to be lucrative, therefore r>0r > 0. On the other hand, it’s certainly not multiplying my capital by a factor of 5, so r<4r < 4. Intuitively, then, rr must exist and be somewhere in the middle.

Now consider the above polynomial as the function:

f(x)=15000x5+14000x4+13000x3+12000x2+11000x10000f(x) = 15000x^5 + 14000x^4 + 13000x^3 + 12000x^2 + 11000x - 10000

For x=0x = 0 we have f(0)=10000f(0) = -10000 and for x=1x = 1 we have f(1)=55000f(1) = 55000. What does that mean?

Recall that a root of a function is a xx in which f(x)=0f(x) = 0. But also consider that, for x>0x > 0, 15000x5+14000x4+13000x3+12000x2+11000x15000x^5 + 14000x^4 + 13000x^3 + 12000x^2 + 11000x is always positive and therefore f(x)f(x) is crescent in this interval.

So if I evaluate ff for x=0.1x = 0.1 it must be bigger than f(0)f(0). I also know that ff is crescent, f(0)<0f(0) < 0 and f(1)>0f(1) > 0. Then if I keep increasing xx in baby steps, eventually I’m expected to get close to 0, i.e., close to the root.

In other words, knowing that all polynomials are continuous, in order to go from a negative number to a positive number, I necessarily need to cross zero, that is, the root.

This, my friends, is the root (pun intended) of Newton’s Method.

Writing a Function to Evaluate the IRR

We already understand NPV and how it is needed to evaluate the IRR. But also discovered that isn’t that simple to find roots of big polynomials. We therefore simplified Newton’s Method by adjusting it to our use-case. How to turn this into code?

That’s how:

const IRR = (cashflow, initialGuess = 0.1) => {
  const maxTries = 10000;
  const delta = 0.001;
  let guess = initialGuess;
  const multiplier = NPV(cashflow, guess) > 0 ? 1 : -1;
  let i = 0;
  while ( i < maxTries ) {
    const guessedNPV = NPV(cashflow, guess);
    if ( multiplier * guessedNPV > delta ) {
      guess += (multiplier * delta);
      i += 1;
    }
    else break;
  }
  console.log(`Found IRR = ${guess} in ${i} trials`);
}

Let it sink a bit. But don’t worry, we are going to cover it all:

The first part basically defines the necessary variables. Here cashflow is the array of cash inflow or outflow and initialGuess our starting point for the Search of the Root™.

  • maxTries defines the maximum number of iterations. Eventually the algo can get stuck or our guess was too bad and we don’t want this to take forever;
  • delta is the iteration step and also how close to 0 we are satisfied to get;
  • guess is the current guess for the root is in a particular iteration;
  • i is the iteration counter;
  • multiplier is ±1±1. In short, it is +1+1 when we need to increase xx (decrease rr) in the next step and 1-1 when our guess was too big and we need to decrease xx (increase rr). Recall that we made the transformation x=11+rx = \frac{1}{1 + r} and therefore xx is inversely proportional to rr.

Next lines should be easy. guessedNPV is the NPV value for the current guess. Recall this is what we are trying to approximate to zero.

We then check in the following lines if our guessed value is bigger than the designated delta, increase the iteration and go again. If smaller — bingo! We have found a xx that gets as close as desirable to 0 and we shall stop.

Go ahead and test the code:

// Elon's proposition: IRR = 10%
IRR([-10000, 11000], 0.05); // Found IRR = 0.10000000000000005 in 50 trials

// Jake's proposition: IRR = 115%
IRR([-10000, 11000, 12000, 13000, 14000, 15000]); // Found IRR = 1.150999999999984 in 1051 trials

With our brand new code we are able to evaluate how much our IRR is for Jeff’s project: 115%. Not bad, huh?

Wrapping Up

Both NPV and IRR are comparative metrics you might want to use when deciding between investments. For a given initial amount, you expect the investments with higher projected NPV and IRR to theoretically be better. In real life that’s not always the case.

  1. Although IRR gives you a number to compare, it says nothing about the risks involved. For example, Jeff may be more reliable than Elon. His projections may be more solid. His business model more consistent. So if investment A yields 20% and investment B yields 24% but with a riskier model, you may want to put your money on A.
  2. Liquidity matters. IRR tells about returns, but don’t forget that investments may have different durations. If investment A yields 20% in 2 years and investment B yields 24% in 5 years, you may want to go with A.
  3. IRR assumes all available money in the end of each tt will be reinvested. In real life that rarely is the case.

Now that our knowledge about IRR and NPV is solid, we can move forward. In this post we have made assumptions (about convergence, for example) that won’t always be true. In the next post we will cover those gaps and build together a React app that evaluates the IRR of a given investment.

Stay tuned!


Rafael Quintanilha

Rafael Quintanilha is a Brazilian software engineer who enjoys writing about technology, web development, front-end, React, data science, and whatever comes to his mind.
Twitter - GitHub - LinkedIn - QuantBrasil