|
13 | 13 |
|
14 | 14 | from decimal import Decimal
|
15 | 15 |
|
16 |
| -import numba as nb |
17 | 16 | import numpy as np
|
18 | 17 |
|
19 | 18 | __all__ = ['fv', 'pmt', 'nper', 'ipmt', 'ppmt', 'pv', 'rate',
|
@@ -47,36 +46,6 @@ def _convert_when(when):
|
47 | 46 | return [_when_to_num[x] for x in when]
|
48 | 47 |
|
49 | 48 |
|
50 |
| -def _return_ufunc_like(array): |
51 |
| - try: |
52 |
| - # If size of array is one, return scalar |
53 |
| - return array.item() |
54 |
| - except ValueError: |
55 |
| - # Otherwise, return entire array |
56 |
| - return array |
57 |
| - |
58 |
| - |
59 |
| -def _is_object_array(array): |
60 |
| - return array.dtype == np.dtype("O") |
61 |
| - |
62 |
| - |
63 |
| -def _use_decimal_dtype(*arrays): |
64 |
| - return any(_is_object_array(array) for array in arrays) |
65 |
| - |
66 |
| - |
67 |
| -def _to_decimal_array_1d(array): |
68 |
| - return np.array([Decimal(x) for x in array.tolist()]) |
69 |
| - |
70 |
| - |
71 |
| -def _to_decimal_array_2d(array): |
72 |
| - decimals = [Decimal(x) for row in array.tolist() for x in row] |
73 |
| - return np.array(decimals).reshape(array.shape) |
74 |
| - |
75 |
| - |
76 |
| -def _get_output_array_shape(*arrays): |
77 |
| - return tuple(array.shape[0] for array in arrays) |
78 |
| - |
79 |
| - |
80 | 49 | def fv(rate, nper, pmt, pv, when='end'):
|
81 | 50 | """Compute the future value.
|
82 | 51 |
|
@@ -856,27 +825,6 @@ def irr(values, *, guess=None, tol=1e-12, maxiter=100, raise_exceptions=False):
|
856 | 825 | return np.nan
|
857 | 826 |
|
858 | 827 |
|
859 |
| -@nb.njit(parallel=True) |
860 |
| -def _npv_native(rates, values, out): |
861 |
| - for i in nb.prange(rates.shape[0]): |
862 |
| - for j in nb.prange(values.shape[0]): |
863 |
| - acc = 0.0 |
864 |
| - for t in range(values.shape[1]): |
865 |
| - acc += values[j, t] / ((1.0 + rates[i]) ** t) |
866 |
| - out[i, j] = acc |
867 |
| - |
868 |
| - |
869 |
| -# We require ``forceobj=True`` here to support decimal.Decimal types |
870 |
| -@nb.jit(forceobj=True) |
871 |
| -def _npv_decimal(rates, values, out): |
872 |
| - for i in range(rates.shape[0]): |
873 |
| - for j in range(values.shape[0]): |
874 |
| - acc = Decimal("0.0") |
875 |
| - for t in range(values.shape[1]): |
876 |
| - acc += values[j, t] / ((Decimal("1.0") + rates[i]) ** t) |
877 |
| - out[i, j] = acc |
878 |
| - |
879 |
| - |
880 | 828 | def npv(rate, values):
|
881 | 829 | r"""Return the NPV (Net Present Value) of a cash flow series.
|
882 | 830 |
|
@@ -944,58 +892,16 @@ def npv(rate, values):
|
944 | 892 | >>> np.round(npf.npv(rate, cashflows) + initial_cashflow, 5)
|
945 | 893 | 3065.22267
|
946 | 894 |
|
947 |
| - The NPV calculation may be applied to several ``rates`` and ``cashflows`` |
948 |
| - simulatneously. This produces an array of shape |
949 |
| - ``(len(rates), len(cashflows))``. |
950 |
| -
|
951 |
| - >>> rates = [0.00, 0.05, 0.10] |
952 |
| - >>> cashflows = [[-4_000, 500, 800], [-5_000, 600, 900]] |
953 |
| - >>> npf.npv(rates, cashflows).round(2) |
954 |
| - array([[-2700. , -3500. ], |
955 |
| - [-2798.19, -3612.24], |
956 |
| - [-2884.3 , -3710.74]]) |
957 |
| -
|
958 |
| - The NPV calculation also supports `decimal.Decimal` types, for example |
959 |
| - if using Decimal ``rates``: |
960 |
| -
|
961 |
| - >>> rates = [Decimal("0.00"), Decimal("0.05"), Decimal("0.10")] |
962 |
| - >>> cashflows = [[-4_000, 500, 800], [-5_000, 600, 900]] |
963 |
| - >>> npf.npv(rates, cashflows) |
964 |
| - array([[Decimal('-2700.0'), Decimal('-3500.0')], |
965 |
| - [Decimal('-2798.185941043083900226757370'), |
966 |
| - Decimal('-3612.244897959183673469387756')], |
967 |
| - [Decimal('-2884.297520661157024793388430'), |
968 |
| - Decimal('-3710.743801652892561983471074')]], dtype=object) |
969 |
| -
|
970 |
| - This also works for Decimal cashflows. |
971 |
| -
|
972 | 895 | """
|
973 |
| - rates = np.atleast_1d(rate) |
974 | 896 | values = np.atleast_2d(values)
|
975 |
| - |
976 |
| - if rates.ndim != 1: |
977 |
| - msg = "invalid shape for rates. Rate must be either a scalar or 1d array" |
978 |
| - raise ValueError(msg) |
979 |
| - |
980 |
| - if values.ndim != 2: |
981 |
| - msg = "invalid shape for values. Values must be either a 1d or 2d array" |
982 |
| - raise ValueError(msg) |
983 |
| - |
984 |
| - dtype = Decimal if _use_decimal_dtype(rates, values) else np.float64 |
985 |
| - |
986 |
| - if dtype == Decimal: |
987 |
| - rates = _to_decimal_array_1d(rates) |
988 |
| - values = _to_decimal_array_2d(values) |
989 |
| - |
990 |
| - shape = _get_output_array_shape(rates, values) |
991 |
| - out = np.empty(shape=shape, dtype=dtype) |
992 |
| - |
993 |
| - if dtype == Decimal: |
994 |
| - _npv_decimal(rates, values, out) |
995 |
| - else: |
996 |
| - _npv_native(rates, values, out) |
997 |
| - |
998 |
| - return _return_ufunc_like(out) |
| 897 | + timestep_array = np.arange(0, values.shape[1]) |
| 898 | + npv = (values / (1 + rate) ** timestep_array).sum(axis=1) |
| 899 | + try: |
| 900 | + # If size of array is one, return scalar |
| 901 | + return npv.item() |
| 902 | + except ValueError: |
| 903 | + # Otherwise, return entire array |
| 904 | + return npv |
999 | 905 |
|
1000 | 906 |
|
1001 | 907 | def mirr(values, finance_rate, reinvest_rate, *, raise_exceptions=False):
|
|
0 commit comments