2

I wrote some code to calculate IRR and it's works fine...

import scipy.optimize as optimize
import datetime


def npv(cf, rate=0.1):
    if len(cf) >= 2:
        first_date = min([x[0] for x in cf])
        dcf = [x[1] * (1 /
                       ((1 + rate) ** ((x[0] - first_date).days / 365))) for x in cf]
        return sum(dcf)
    elif len(cf) == 1:
        return cf[0][1]
    else:
        return 0


def irr(cf):
    f = lambda x: npv(cf, rate=x)
    r = optimize.newton(f, 0, maxiter=70)
    return r

...but when I try this cashflow

cf=[(datetime.datetime(2018, 1, 10), -51089.94),
    (datetime.datetime(2022, 10, 6), 4941.0)]

I get this error:

File "/Users/maxim/Dropbox/Python/FinProject/fintrack/main/models.py", line 503, in getIRR
    return irr(cf)
  File "/Users/maxim/Dropbox/Python/FinProject/fintrack/main/models.py", line 37, in irr
    r = optimize.newton(f, 0, maxiter=70)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/scipy/optimize/zeros.py", line 204, in newton
    q1 = func(p1, *args)
  File "/Users/maxim/Dropbox/Python/FinProject/fintrack/main/models.py", line 36, in <lambda>
    f = lambda x: npv(cf, rate=x)
  File "/Users/maxim/Dropbox/Python/FinProject/fintrack/main/models.py", line 27, in npv
    ((1 + rate) ** ((x[0] - first_date).days / 365))) for x in cf]
  File "/Users/maxim/Dropbox/Python/FinProject/fintrack/main/models.py", line 27, in <listcomp>
    ((1 + rate) ** ((x[0] - first_date).days / 365))) for x in cf]
OverflowError: complex exponentiation
[30/Nov/2018 21:28:36] "GET /inv/19/ HTTP/1.1" 500 299065

However I know that correct answer is -38.912..% I got this result by Excel. What is wrong here? With other data I get the same result as Excel IRR function... Should I use other function to find parameter?

P.S.: here is list of iteration's parameters and results (OMG complex numbers in finance %-) ):

rate= 0.0 result= -46148.94
rate= 0.0001 result= -46151.281226688276
rate= -1.9711435988300456 result= (-54972.27265283515-4141.40178622848j)
rate= (8.450859228811169-3.967580022971747j) result= (-51089.96465099011+0.07614432462298902j)
rate= (46.548868303534285-96.82120737804672j) result= (-51089.93999938349-9.638432563347345e-07j)
rate= (63880696.05472335+4880892.707757121j) result= (-51089.94-1.7420169038104924e-34j)
rate= (-1.3534185437764045e+18-2.52038641964956e+18j) result= (-51089.94-5.721141417411886e-85j)
rate= (-7.391799165398238e+56+3.969311207511089e+56j) result= (-51089.94+9.185514002355334e-269j)
rate= (3.5446051170119047e+145+6.600895665730368e+145j) result= ERROR!!!
Max Maximum
  • 23
  • 1
  • 3
  • 1
    Could be wrong, but judging by those error messages this isn't technically your fault. The function used by newton simply can't handle the exponents passed. Hopefully someone can recommend a better import for IRR. – KuboMD Nov 30 '18 at 19:34
  • Newton cannot handle the problem because when using an IRR < -100% (which is mathematically feasable), the operation `(1 + rate) ** time` becomes complex, which is the error you get. In fact, at rate = -1 you have a discontinuity. You can use a diferent optimization method which allows the constraint `rate > -100%`(or `rate >= 0`), or use a bounded method (e.g. `brentq` or `secant`) in a known interval, or use `newton` starting at an x0 which you know is below the IRR value (e.g. `x0 = -0.95`) – Tarifazo Dec 05 '18 at 13:08

2 Answers2

3

Seems that newton cannot handle this properly (not sure about the reason). You can, however, use root which gives you the expected outcome and requires only a very small modification in your code:

import scipy.optimize as optimize
import datetime


def npv(cf, rate=0.1):
    if len(cf) >= 2:
        first_date = min([x[0] for x in cf])
        dcf = [x[1] * (1 /
                       ((1 + rate) ** ((x[0] - first_date).days / 365))) for x in cf]
        return sum(dcf)
    elif len(cf) == 1:
        return cf[0][1]
    else:
        return 0


def irr(cf):
    f = lambda x: npv(cf, rate=x)
    r = optimize.root(f, [0])
    return r

cf = [(datetime.datetime(2018, 1, 10), -51089.94),
      (datetime.datetime(2022, 10, 6), 4941.0)]

print(irr(cf))

This will print:

    fjac: array([[-1.]])
     fun: array([-2.91038305e-11])
 message: 'The solution converged.'
    nfev: 18
     qtf: array([2.37701897e-06])
       r: array([396401.23327105])
  status: 1
 success: True
       x: array([-0.38912302])

as you can see, x contains the expected -0.38912.

Cleb
  • 25,102
  • 20
  • 116
  • 151
0

Working code is:

import scipy.optimize as optimize
import datetime


def npv(cf, rate=0.1):
    if len(cf) >= 2:
        first_date = min([x[0] for x in cf])
        dcf = [x[1] * (1 /
                       ((1 + rate) ** ((x[0] - first_date).days / 365))) for x in cf]
        return sum(dcf)
    elif len(cf) == 1:
        return cf[0][1]
    else:
        return 0


def irr(cf):
    f = lambda x: npv(cf, rate=x)
    r = optimize.root(f, [0])
    return r

Great thanks to Cleb!!!

Max Maximum
  • 23
  • 1
  • 3
  • It does not make much sense to add a duplicate answer. Common practice is to upvote helpful answers - if you have sufficient reputation - and to accept the one that actually solved the issue (by clicking on the small check next to the answer which then turns green). – Cleb Nov 30 '18 at 21:13