1- """Some simple financial calculations
1+ """Some simple financial calculations.
22
33patterned after spreadsheet computations.
44
1010Functions support the :class:`decimal.Decimal` type unless
1111otherwise stated.
1212"""
13- from __future__ import absolute_import , division , print_function
1413
1514from decimal import Decimal
1615
1716import numpy as np
1817
1918__all__ = ['fv' , 'pmt' , 'nper' , 'ipmt' , 'ppmt' , 'pv' , 'rate' ,
2019 'irr' , 'npv' , 'mirr' ,
21- 'NoRealSolutionException ' , 'IterationsExceededException ' ]
20+ 'NoRealSolutionError ' , 'IterationsExceededError ' ]
2221
2322_when_to_num = {'end' : 0 , 'begin' : 1 ,
2423 'e' : 0 , 'b' : 1 ,
2827 'finish' : 0 }
2928
3029
31- class NoRealSolutionException (Exception ):
32- """ No real solution to the problem. """
33- pass
30+ class NoRealSolutionError (Exception ):
31+ """No real solution to the problem."""
3432
3533
36- class IterationsExceededException (Exception ):
37- """ Maximum number of iterations reached. """
38- pass
34+ class IterationsExceededError (Exception ):
35+ """Maximum number of iterations reached."""
3936
4037
4138def _convert_when (when ):
@@ -50,8 +47,7 @@ def _convert_when(when):
5047
5148
5249def fv (rate , nper , pmt , pv , when = 'end' ):
53- """
54- Compute the future value.
50+ """Compute the future value.
5551
5652 Given:
5753 * a present value, `pv`
@@ -143,11 +139,11 @@ def fv(rate, nper, pmt, pv, when='end'):
143139 fv_array [zero ] = - (pv [zero ] + pmt [zero ] * nper [zero ])
144140
145141 rate_nonzero = rate [nonzero ]
146- temp = (1 + rate_nonzero )** nper [nonzero ]
142+ temp = (1 + rate_nonzero ) ** nper [nonzero ]
147143 fv_array [nonzero ] = (
148- - pv [nonzero ] * temp
149- - pmt [nonzero ] * (1 + rate_nonzero * when [nonzero ]) / rate_nonzero
150- * (temp - 1 )
144+ - pv [nonzero ] * temp
145+ - pmt [nonzero ] * (1 + rate_nonzero * when [nonzero ]) / rate_nonzero
146+ * (temp - 1 )
151147 )
152148
153149 if np .ndim (fv_array ) == 0 :
@@ -158,8 +154,7 @@ def fv(rate, nper, pmt, pv, when='end'):
158154
159155
160156def pmt (rate , nper , pv , fv = 0 , when = 'end' ):
161- """
162- Compute the payment against loan principal plus interest.
157+ """Compute the payment against loan principal plus interest.
163158
164159 Given:
165160 * a present value, `pv` (e.g., an amount borrowed)
@@ -244,17 +239,16 @@ def pmt(rate, nper, pv, fv=0, when='end'):
244239 """
245240 when = _convert_when (when )
246241 (rate , nper , pv , fv , when ) = map (np .array , [rate , nper , pv , fv , when ])
247- temp = (1 + rate )** nper
242+ temp = (1 + rate ) ** nper
248243 mask = (rate == 0 )
249244 masked_rate = np .where (mask , 1 , rate )
250245 fact = np .where (mask != 0 , nper ,
251- (1 + masked_rate * when )* (temp - 1 )/ masked_rate )
252- return - (fv + pv * temp ) / fact
246+ (1 + masked_rate * when ) * (temp - 1 ) / masked_rate )
247+ return - (fv + pv * temp ) / fact
253248
254249
255250def nper (rate , pmt , pv , fv = 0 , when = 'end' ):
256- """
257- Compute the number of periodic payments.
251+ """Compute the number of periodic payments.
258252
259253 :class:`decimal.Decimal` type is not supported.
260254
@@ -321,8 +315,8 @@ def nper(rate, pmt, pv, fv=0, when='end'):
321315 nonzero_rate = rate [nonzero ]
322316 z = pmt [nonzero ] * (1 + nonzero_rate * when [nonzero ]) / nonzero_rate
323317 nper_array [nonzero ] = (
324- np .log ((- fv [nonzero ] + z ) / (pv [nonzero ] + z ))
325- / np .log (1 + nonzero_rate )
318+ np .log ((- fv [nonzero ] + z ) / (pv [nonzero ] + z ))
319+ / np .log (1 + nonzero_rate )
326320 )
327321
328322 return nper_array
@@ -332,13 +326,11 @@ def _value_like(arr, value):
332326 entry = arr .item (0 )
333327 if isinstance (entry , Decimal ):
334328 return Decimal (value )
335- else :
336- return np .array (value , dtype = arr .dtype ).item (0 )
329+ return np .array (value , dtype = arr .dtype ).item (0 )
337330
338331
339332def ipmt (rate , per , nper , pv , fv = 0 , when = 'end' ):
340- """
341- Compute the interest portion of a payment.
333+ """Compute the interest portion of a payment.
342334
343335 Parameters
344336 ----------
@@ -439,7 +431,7 @@ def ipmt(rate, per, nper, pv, fv=0, when='end'):
439431 # If paying at the beginning we need to discount by one period.
440432 per_gt_1_and_begin = (when == 1 ) & (per > 1 )
441433 ipmt_array [per_gt_1_and_begin ] = (
442- ipmt_array [per_gt_1_and_begin ] / (1 + rate [per_gt_1_and_begin ])
434+ ipmt_array [per_gt_1_and_begin ] / (1 + rate [per_gt_1_and_begin ])
443435 )
444436
445437 if np .ndim (ipmt_array ) == 0 :
@@ -450,7 +442,8 @@ def ipmt(rate, per, nper, pv, fv=0, when='end'):
450442
451443
452444def _rbl (rate , per , pmt , pv , when ):
453- """
445+ """Remaining balance on loan.
446+
454447 This function is here to simply have a different name for the 'fv'
455448 function to not interfere with the 'fv' keyword argument within the 'ipmt'
456449 function. It is the 'remaining balance on loan' which might be useful as
@@ -460,8 +453,7 @@ def _rbl(rate, per, pmt, pv, when):
460453
461454
462455def ppmt (rate , per , nper , pv , fv = 0 , when = 'end' ):
463- """
464- Compute the payment against loan principal.
456+ """Compute the payment against loan principle.
465457
466458 Parameters
467459 ----------
@@ -489,8 +481,7 @@ def ppmt(rate, per, nper, pv, fv=0, when='end'):
489481
490482
491483def pv (rate , nper , pmt , fv = 0 , when = 'end' ):
492- """
493- Compute the present value.
484+ """Compute the present value.
494485
495486 Given:
496487 * a future value, `fv`
@@ -579,9 +570,10 @@ def pv(rate, nper, pmt, fv=0, when='end'):
579570 """
580571 when = _convert_when (when )
581572 (rate , nper , pmt , fv , when ) = map (np .asarray , [rate , nper , pmt , fv , when ])
582- temp = (1 + rate )** nper
583- fact = np .where (rate == 0 , nper , (1 + rate * when )* (temp - 1 )/ rate )
584- return - (fv + pmt * fact )/ temp
573+ temp = (1 + rate ) ** nper
574+ fact = np .where (rate == 0 , nper , (1 + rate * when ) * (temp - 1 ) / rate )
575+ return - (fv + pmt * fact ) / temp
576+
585577
586578# Computed with Sage
587579# (y + (r + 1)^n*x + p*((r + 1)^n - 1)*(r*w + 1)/r)/(n*(r + 1)^(n - 1)*x -
@@ -592,13 +584,13 @@ def pv(rate, nper, pmt, fv=0, when='end'):
592584def _g_div_gp (r , n , p , x , y , w ):
593585 # Evaluate g(r_n)/g'(r_n), where g =
594586 # fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate * ((1+rate)**nper - 1)
595- t1 = (r + 1 ) ** n
596- t2 = (r + 1 ) ** ( n - 1 )
597- g = y + t1 * x + p * (t1 - 1 ) * (r * w + 1 ) / r
598- gp = (n * t2 * x
599- - p * (t1 - 1 ) * (r * w + 1 ) / (r ** 2 )
600- + n * p * t2 * (r * w + 1 ) / r
601- + p * (t1 - 1 ) * w / r )
587+ t1 = (r + 1 ) ** n
588+ t2 = (r + 1 ) ** ( n - 1 )
589+ g = y + t1 * x + p * (t1 - 1 ) * (r * w + 1 ) / r
590+ gp = (n * t2 * x
591+ - p * (t1 - 1 ) * (r * w + 1 ) / (r ** 2 )
592+ + n * p * t2 * (r * w + 1 ) / r
593+ + p * (t1 - 1 ) * w / r )
602594 return g / gp
603595
604596
@@ -609,9 +601,18 @@ def _g_div_gp(r, n, p, x, y, w):
609601# where
610602# g(r) is the formula
611603# g'(r) is the derivative with respect to r.
612- def rate (nper , pmt , pv , fv , when = 'end' , guess = None , tol = None , maxiter = 100 , * , raise_exceptions = False ):
613- """
614- Compute the rate of interest per period.
604+ def rate (
605+ nper ,
606+ pmt ,
607+ pv ,
608+ fv ,
609+ when = 'end' ,
610+ guess = None ,
611+ tol = None ,
612+ maxiter = 100 ,
613+ * ,
614+ raise_exceptions = False ):
615+ """Compute the rate of interest per period.
615616
616617 Parameters
617618 ----------
@@ -675,29 +676,28 @@ def rate(nper, pmt, pv, fv, when='end', guess=None, tol=None, maxiter=100, *, ra
675676 close = False
676677 while (iterator < maxiter ) and not np .all (close ):
677678 rnp1 = rn - _g_div_gp (rn , nper , pmt , pv , fv , when )
678- diff = abs (rnp1 - rn )
679+ diff = abs (rnp1 - rn )
679680 close = diff < tol
680681 iterator += 1
681682 rn = rnp1
682683
683684 if not np .all (close ):
684685 if np .isscalar (rn ):
685686 if raise_exceptions :
686- raise IterationsExceededException ('Maximum number of iterations exceeded.' )
687+ raise IterationsExceededError ('Maximum number of iterations exceeded.' )
687688 return default_type (np .nan )
688689 else :
689690 # Return nan's in array of the same shape as rn
690691 # where the solution is not close to tol.
691692 if raise_exceptions :
692- raise IterationsExceededException (f'Maximum number of iterations exceeded in '
693- f'{ len (close )- close .sum ()} rate(s).' )
693+ raise IterationsExceededError (f'Maximum iterations exceeded in '
694+ f'{ len (close ) - close .sum ()} rate(s).' )
694695 rn [~ close ] = np .nan
695696 return rn
696697
697698
698699def irr (values , * , guess = None , tol = 1e-12 , maxiter = 100 , raise_exceptions = False ):
699- """
700- Return the Internal Rate of Return (IRR).
700+ r"""Return the Internal Rate of Return (IRR).
701701
702702 This is the "average" periodically compounded rate of return
703703 that gives a net present value of 0.0; for a more complete explanation,
@@ -781,8 +781,8 @@ def irr(values, *, guess=None, tol=1e-12, maxiter=100, raise_exceptions=False):
781781 same_sign = np .all (values > 0 ) if values [0 ] > 0 else np .all (values < 0 )
782782 if same_sign :
783783 if raise_exceptions :
784- raise NoRealSolutionException ('No real solution exists for IRR since all '
785- 'cashflows are of the same sign.' )
784+ raise NoRealSolutionError ('No real solution exists for IRR since all '
785+ 'cashflows are of the same sign.' )
786786 return np .nan
787787
788788 # If no value is passed for `guess`, then make a heuristic estimate
@@ -820,14 +820,13 @@ def irr(values, *, guess=None, tol=1e-12, maxiter=100, raise_exceptions=False):
820820 g -= delta
821821
822822 if raise_exceptions :
823- raise IterationsExceededException ('Maximum number of iterations exceeded.' )
823+ raise IterationsExceededError ('Maximum number of iterations exceeded.' )
824824
825825 return np .nan
826826
827827
828828def npv (rate , values ):
829- """
830- Returns the NPV (Net Present Value) of a cash flow series.
829+ r"""Return the NPV (Net Present Value) of a cash flow series.
831830
832831 Parameters
833832 ----------
@@ -905,8 +904,7 @@ def npv(rate, values):
905904
906905
907906def mirr (values , finance_rate , reinvest_rate , * , raise_exceptions = False ):
908- """
909- Modified internal rate of return.
907+ r"""Return the modified internal rate of return.
910908
911909 Parameters
912910 ----------
@@ -943,9 +941,9 @@ def mirr(values, finance_rate, reinvest_rate, *, raise_exceptions=False):
943941 neg = values < 0
944942 if not (pos .any () and neg .any ()):
945943 if raise_exceptions :
946- raise NoRealSolutionException ('No real solution exists for MIRR since'
947- ' all cashflows are of the same sign.' )
944+ raise NoRealSolutionError ('No real solution exists for MIRR since'
945+ ' all cashflows are of the same sign.' )
948946 return np .nan
949- numer = np .abs (npv (reinvest_rate , values * pos ))
950- denom = np .abs (npv (finance_rate , values * neg ))
951- return (numer / denom )** ( 1 / (n - 1 ))* (1 + reinvest_rate ) - 1
947+ numer = np .abs (npv (reinvest_rate , values * pos ))
948+ denom = np .abs (npv (finance_rate , values * neg ))
949+ return (numer / denom ) ** ( 1 / (n - 1 )) * (1 + reinvest_rate ) - 1
0 commit comments