diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/MANIFEST.in b/MANIFEST.in old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 1522206..e3f0ae6 --- a/README.md +++ b/README.md @@ -8,4 +8,3 @@ The library can be installed as usual using `pip`: Click [here](http://cashflows.readthedocs.io/en/latest/) to access the last documentation of the package. -Ibeth diff --git a/cashflows/__init__.py b/cashflows/__init__.py old mode 100644 new mode 100755 index 8b9c4e8..31b3ea6 --- a/cashflows/__init__.py +++ b/cashflows/__init__.py @@ -1,14 +1,13 @@ from cashflows.analysis import * +from cashflows.tvmm import * from cashflows.bond import * from cashflows.common import * from cashflows.currency import * from cashflows.depreciation import * -# from cashflows.gtimeseries import * +from cashflows.gtimeseries import * +from cashflows.rate import * from cashflows.inflation import * from cashflows.loan import * -from cashflows.rate import * from cashflows.savings import * -from cashflows.timeseries import * from cashflows.taxing import * -from cashflows.tvmm import * from cashflows.utilityfun import * diff --git a/cashflows/analysis.py b/cashflows/analysis.py old mode 100644 new mode 100755 index a0abb8a..b6bedbc --- a/cashflows/analysis.py +++ b/cashflows/analysis.py @@ -25,114 +25,14 @@ """ import numpy as np -import pandas as pd -# cashflows. -from cashflows.timeseries import * -from cashflows.rate import * +from cashflows.gtimeseries import TimeSeries, cashflow, interest_rate, verify_eq_time_range +from cashflows.rate import to_discount_factor, equivalent_rate from cashflows.common import _vars2list from cashflows.tvmm import tvmm -# from cashflows.utilityfun import exp_utility_fun, log_utility_fun, sqrt_utility_fun +from cashflows.utilityfun import exp_utility_fun, log_utility_fun, sqrt_utility_fun - - -def irr(cflo): - """Computes the internal rate of return of a generic cashflow as a periodic - interest rate. - - Args: - cflo (TimeSeries): Generic cashflow. - - Returns: - Float or list of floats. - - **Examples.** - - >>> cflo = cashflow([-200] + [100]*4, start='2000Q1', freq='Q') - >>> irr(cflo) # doctest: +ELLIPSIS - 34.90... - - >>> irr([cflo, cflo]) # doctest: +ELLIPSIS - 0 34.90... - 1 34.90... - dtype: float64 - - """ - # if isinstance(cflo, pd.Series): - # cflo = [cflo] - # retval = [] - # freq = [] - # for xcflo in cflo: - # retval.append(100 * np.irr(xcflo.tolist())) - # freq.append(xcflo.index.freq.__repr__()) - # if len(retval) == 1: - # return retval[0] - # daux = {'':'A', - # '':'BA', - # '':'Q', - # '':'BQ', - # '':'M', - # '':'BM', - # '':'CBM', - # '':'SM', - # '<6 * MonthEnds>':'6M', - # '<6 * BusinessMonthEnds>':'6BM', - # '<6 * CustomBusinessMonthEnds>':'6CBM'} - # freq = [daux[i] for i in freq] - # retval = pd.DataFrame({'IRR':retval, 'Freq':freq}) - # return retval - - - if isinstance(cflo, pd.Series): - cflo = [cflo] - retval = pd.Series([0] * len(cflo), dtype=np.float64) - for index, xcflo in enumerate(cflo): - retval[index] = (100 * np.irr(xcflo)) - if len(retval) == 1: - return retval[0] - return retval - -## modified internal rate of return -def mirr(cflo, finance_rate=0, reinvest_rate=0): - """Computes the modified internal rate of return of a generic cashflow - as a periodic interest rate. - - Args: - cflo (TimeSeries): Generic cashflow. - finance_rate (float): Periodic interest rate applied to negative values of the cashflow. - reinvest_rate (float): Periodic interest rate applied to positive values of the cashflow. - - Returns: - Float or list of floats. - - **Examples.** - - >>> cflo = cashflow([-200] + [100]*4, start='2000Q1', freq='Q') - >>> mirr(cflo) # doctest: +ELLIPSIS - 18.92... - - >>> mirr([cflo, cflo]) # doctest: +ELLIPSIS - 0 18.920712 - 1 18.920712 - dtype: float64 - - - """ - # negativos: finance_rate - # positivos: reinvest_rate - if isinstance(cflo, pd.Series): - cflo = [cflo] - retval = pd.Series([0] * len(cflo), dtype=np.float64) - for index, xcflo in enumerate(cflo): - retval[index] = (100 * np.mirr(xcflo, - finance_rate, - reinvest_rate)) - - if len(retval) == 1: - return retval[0] - return retval - def timevalue(cflo, prate, base_date=0, utility=None): """ Computes the equivalent net value of a generic cashflow at time `base_date` @@ -154,86 +54,83 @@ def timevalue(cflo, prate, base_date=0, utility=None): **Examples.** - >>> cflo = cashflow([-732.54] + [100]*8, start='2000Q1', freq='Q') - >>> prate = interest_rate([2]*9, start='2000Q1', freq='Q') + >>> cflo = cashflow([-732.54] + [100]*8, pyr=4) + >>> prate = interest_rate([2]*9, pyr=4) >>> timevalue(cflo, prate) # doctest: +ELLIPSIS 0.00... - >>> prate = interest_rate([12]*5, start='2000Q1', freq='Q') - >>> cflo = cashflow([-200]+[100]*4, start='2000Q1', freq='Q') + >>> prate = interest_rate([12]*5) + >>> cflo = cashflow([100]*5, spec = (0, -200)) >>> timevalue(cflo, prate) # doctest: +ELLIPSIS 103.73... >>> timevalue(cflo, prate, 4) # doctest: +ELLIPSIS 163.22... + >>> timevalue(cflo, prate, base_date=0, utility=exp_utility_fun(200)) # doctest: +ELLIPSIS + -84.15... + + >>> timevalue(cflo, prate, base_date=0, utility=log_utility_fun(210)) # doctest: +ELLIPSIS + 369092793... + + >>> timevalue(cflo, prate, base_date=0, utility=sqrt_utility_fun(210)) # doctest: +ELLIPSIS + 2998.12... - >>> prate = interest_rate([12]*5, start='2000Q1', freq='Q') - >>> cflo = cashflow([-200] + [100]*4, start='2000Q1', freq='Q') + + >>> prate = interest_rate([12]*5) + >>> cflo = cashflow([-200] + [100]*4) >>> timevalue(cflo=cflo, prate=prate) # doctest: +ELLIPSIS 103.73... >>> timevalue(cflo=[cflo, cflo], prate=prate) # doctest: +ELLIPSIS - 0 103.734935 - 1 103.734935 - dtype: float64 + [103.73..., 103.73...] + >>> timevalue(cflo=cflo, prate=[prate, prate]) # doctest: +ELLIPSIS + [103.73..., 103.73...] - """ + >>> timevalue(cflo=[cflo, cflo], prate=[prate, prate]) # doctest: +ELLIPSIS + [103.73..., 103.73...] - if isinstance(cflo, pd.Series): - cflo = [cflo] - if not isinstance(prate, pd.Series): - raise TypeError("`prate` must be a pandas.Series") - verify_period_range(cflo + [prate]) - retval = pd.Series([0] * len(cflo), dtype=np.float64) - factor = to_discount_factor(prate=prate, base_date=base_date) - for index, xcflo in enumerate(cflo): + >>> timevalue(cflo=[cflo, cflo], prate=[prate, prate], base_date=[4, 4]) # doctest: +ELLIPSIS + [163.22..., 163.22...] + + + """ + params = _vars2list([cflo, prate, base_date]) + cflo = params[0] + prate = params[1] + base_date = params[2] + retval = [] + for xcflo, xprate, xbase_date in zip(cflo, prate, base_date): + if not isinstance(xcflo, TimeSeries): + raise TypeError("`cflo` must be a TimeSeries") + if not isinstance(xprate, TimeSeries): + raise TypeError("`prate` must be a TimeSeries") + verify_eq_time_range(xcflo, xprate) netval = 0 + factor = to_discount_factor(prate=xprate, base_date=xbase_date) for time, _ in enumerate(xcflo): - netval += xcflo[time] * factor[time] - retval[index] = netval + if utility is None: + xcflo_aux = xcflo[time] + else: + xcflo_aux = utility(xcflo[time]) + netval += xcflo_aux * factor[time] + if utility is not None: + netval = utility(netval, inverse=True) + retval.append(netval) if len(retval) == 1: return retval[0] return retval - # params = _vars2list([cflo, prate, base_date]) - # cflo = params[0] - # prate = params[1] - # base_date = params[2] - # retval = [] - # for xcflo, xprate, xbase_date in zip(cflo, prate, base_date): - # if not isinstance(xcflo, pd.Series): - # raise TypeError("`cflo` must be a pandas.Series") - # if not isinstance(xprate, pd.Series): - # raise TypeError("`prate` must be a pandas.Series") - # verify_period_range([xcflo, xprate]) - # netval = 0 - # factor = to_discount_factor(prate=xprate, base_date=xbase_date) - # for time, _ in enumerate(xcflo): - # if utility is None: - # xcflo_aux = xcflo[time] - # else: - # xcflo_aux = utility(xcflo[time]) - # netval += xcflo_aux * factor[time] - # if utility is not None: - # netval = utility(netval, inverse=True) - # retval.append(netval) - # if len(retval) == 1: - # return retval[0] - # return retval - - - def net_uniform_series(cflo, prate, nper=1): """Computes a net uniform series equivalent of a cashflow. This is, a fixed periodic payment during `nper` periods that is equivalent to the cashflow `cflo` at the periodic interest rate `prate`. Args: - cflo (TimeSeries): Generic cashflow. + cflo (cashflow): Generic cashflow. prate (TimeSeries): Periodic interest rate. nper (int, list): Number of equivalent payment periods. @@ -242,55 +139,47 @@ def net_uniform_series(cflo, prate, nper=1): **Examples.** - >>> prate = interest_rate([2]*9, start='2000Q1', freq='Q') - >>> cflo = cashflow([-732.54] + [100]*8, start='2000Q1', freq='Q') + >>> prate = interest_rate([2]*9, pyr=4) + >>> cflo = cashflow([-732.54] + [100]*8, pyr=4) >>> net_uniform_series(cflo, prate) # doctest: +ELLIPSIS 0.00... - >>> prate = interest_rate([12]*5, start='2000Q1', freq='Q') - >>> cflo = cashflow([-200] + [100]*4, start='2000Q1', freq='Q') + >>> prate = interest_rate([12]*5) + >>> cflo = cashflow([-200] + [100]*4) >>> net_uniform_series(cflo, prate) # doctest: +ELLIPSIS 116.18... >>> net_uniform_series([cflo, cflo], prate) # doctest: +ELLIPSIS - 0 116.183127 - 1 116.183127 - dtype: float64 + [116.18..., 116.18...] + >>> net_uniform_series(cflo, [prate, prate]) # doctest: +ELLIPSIS + [116.18..., 116.18...] + >>> net_uniform_series([cflo, cflo], [prate, prate]) # doctest: +ELLIPSIS + [116.18..., 116.18...] - """ + >>> net_uniform_series([cflo, cflo], [prate, prate], nper=5) # doctest: +ELLIPSIS + [28.77..., 28.77...] - if isinstance(cflo, pd.Series): - cflo = [cflo] - if not isinstance(prate, pd.Series): - raise TypeError("`prate` must be a pandas.Series") - verify_period_range(cflo + [prate]) - retval = pd.Series([0] * len(cflo), dtype=np.float64) - erate = equivalent_rate(prate=prate) - for index, xcflo in enumerate(cflo): - netval = timevalue(cflo=xcflo, prate=prate, base_date=0) - retval[index] = (-tvmm(nrate=erate, nper=nper, pval=netval, fval=0, pmt=None)) + >>> net_uniform_series([cflo, cflo], [prate, prate], nper=[5, 5]) # doctest: +ELLIPSIS + [28.77..., 28.77...] + + + """ + params = _vars2list([cflo, prate, nper]) + cflo = params[0] + prate = params[1] + nper = params[2] + retval = [] + for xcflo, xprate, xnper in zip(cflo, prate, nper): + netval = timevalue(cflo=xcflo, prate=xprate, base_date=0) + erate = equivalent_rate(prate=xprate) + retval.append(-tvmm(nrate=erate, nper=xnper, pval=netval, fval=0, pmt=None)) if len(retval) == 1: return retval[0] return retval - - # params = _vars2list([cflo, prate, nper]) - # cflo = params[0] - # prate = params[1] - # nper = params[2] - # retval = [] - # for xcflo, xprate, xnper in zip(cflo, prate, nper): - # netval = timevalue(cflo=xcflo, prate=xprate, base_date=0) - # erate = equivalent_rate(prate=xprate) - # retval.append(-tvmm(nrate=erate, nper=xnper, pval=netval, fval=0, pmt=None)) - # if len(retval) == 1: - # return retval[0] - # return retval - - def benefit_cost_ratio(cflo, prate, base_date=0): """ Computes a benefit cost ratio at time `base_date` of a discounted cashflow @@ -298,7 +187,7 @@ def benefit_cost_ratio(cflo, prate, base_date=0): Args: prate (int float, Rate): Periodic interest rate. - cflo (TimeSeries): Generic cashflow. + cflo (cashflow, list): Generic cashflow. base_date (int, list): Time. Returns: @@ -306,31 +195,39 @@ def benefit_cost_ratio(cflo, prate, base_date=0): **Examples.** - >>> prate = interest_rate([2]*9, start='2000Q1', freq='Q') - >>> cflo = cashflow([-717.01] + [100]*8, start='2000Q1', freq='Q') + >>> prate = interest_rate([2]*9, pyr=4) + >>> cflo = cashflow([-717.01] + [100]*8, pyr=4) >>> benefit_cost_ratio(cflo, prate) # doctest: +ELLIPSIS 1.02... - >>> prate = interest_rate([12]*5, start='2000Q1', freq='Q') - >>> cflo = cashflow([-200] + [100]*4, start='2000Q1', freq='Q') + >>> prate = interest_rate([12]*5) + >>> cflo = cashflow([-200] + [100]*4) >>> benefit_cost_ratio(cflo, prate) # doctest: +ELLIPSIS 1.518... >>> benefit_cost_ratio([cflo, cflo], prate) # doctest: +ELLIPSIS - 0 1.518675 - 1 1.518675 - dtype: float64 + [1.518..., 1.518...] + + >>> benefit_cost_ratio(cflo, [prate, prate]) # doctest: +ELLIPSIS + [1.518..., 1.518...] + + >>> benefit_cost_ratio([cflo, cflo], [prate, prate]) # doctest: +ELLIPSIS + [1.518..., 1.518...] + + >>> benefit_cost_ratio([cflo, cflo], [prate, prate], [0, 0]) # doctest: +ELLIPSIS + [1.518..., 1.518...] """ - if isinstance(cflo, pd.Series): - cflo = [cflo] - if not isinstance(prate, pd.Series): - raise TypeError("`prate` must be a pandas.Series") - verify_period_range(cflo + [prate]) - retval = pd.Series([0] * len(cflo), dtype=np.float64) - for index, xcflo in enumerate(cflo): + params = _vars2list([prate, cflo, base_date]) + prate = params[0] + cflo = params[1] + base_date = params[2] + + retval = [] + for xprate, xcflo, xbase_date in zip(prate, cflo, base_date): + verify_eq_time_range(xcflo, xprate) num = 0 den = 0 num = xcflo.copy() @@ -340,86 +237,125 @@ def benefit_cost_ratio(cflo, prate, base_date=0): den[time] = 0 else: num[time] = 0 - retval[index] = -timevalue(num, prate, base_date) / timevalue(den, prate, base_date) + retval.append(-timevalue(num, xprate, xbase_date) / timevalue(den, xprate, xbase_date)) + if len(retval) == 1: return retval[0] return retval - # params = _vars2list([prate, cflo, base_date]) - # prate = params[0] - # cflo = params[1] - # base_date = params[2] - # - # retval = [] - # for xprate, xcflo, xbase_date in zip(prate, cflo, base_date): - # verify_period_range([xcflo, xprate]) - # num = 0 - # den = 0 - # num = xcflo.copy() - # den = xcflo.copy() - # for time, _ in enumerate(xcflo): - # if xcflo[time] >= 0.0: - # den[time] = 0 - # else: - # num[time] = 0 - # retval.append(-timevalue(num, xprate, xbase_date) / timevalue(den, xprate, xbase_date)) +def irr(cflo): + """Computes the internal rate of return of a generic cashflow as a periodic + interest rate. + + Args: + cflo (TimeSeries): Generic cashflow. + + Returns: + Float or list of floats. + + **Examples.** + + + >>> cflo = cashflow([-717.01] + [100]*8, pyr=4) + >>> irr(cflo) # doctest: +ELLIPSIS + 2.50... + + + >>> cflo = cashflow([-200] + [100]*4) + >>> irr(cflo) # doctest: +ELLIPSIS + 34.90... + + >>> irr([cflo, cflo]) # doctest: +ELLIPSIS + [34.90..., 34.90...] + + """ + if isinstance(cflo, TimeSeries): + cflo = [cflo] + retval = [] + for xcflo in cflo: + retval.append(100 * np.irr(xcflo.tolist())) + if len(retval) == 1: + return retval[0] + return retval + +## modified internal rate of return +def mirr(cflo, finance_rate=0, reinvest_rate=0): + """Computes the modified internal rate of return of a generic cashflow + as a periodic interest rate. + + Args: + cflo (list, cashflow): Generic cashflow. + finance_rate (float): Periodic interest rate applied to negative values of the cashflow. + reinvest_rate (float): Periodic interest rate applied to positive values of the cashflow. + + Returns: + Float or list of floats. + + **Examples.** + + >>> cflo = cashflow([-200] + [100]*4) + >>> mirr(cflo) # doctest: +ELLIPSIS + 18.92... + + >>> mirr([cflo, cflo]) # doctest: +ELLIPSIS + [18.92..., 18.92...] + + + """ + # negativos: finance_rate + # positivos: reinvest_rate + if isinstance(cflo, TimeSeries): + cflo = [cflo] + retval = [] + for xcflo in cflo: + retval.append(100 * np.mirr(xcflo.tolist(), + finance_rate, + reinvest_rate)) + + if len(retval) == 1: + return retval[0] + return retval + +def list_as_table(data): + """Prints the list `data` as a table. This function is used to produce a + human-readable format of a table for comparing financial indicators. + + Args: + data (list): List of numeric values. + + Returns: + None + + **Example.** + + >>> list_as_table(data=[1, 2, 3, 4]) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + # Value + ------------------------ + 0 1.0000 + 1 2.0000 + 2 3.0000 + 3 4.0000 + + + >>> prate = interest_rate([12]*5) + >>> cflo = cashflow([-200] + [100]*4) + >>> list_as_table(timevalue(cflo=[cflo, cflo, cflo], prate=prate)) # doctest: +NORMALIZE_WHITESPACE + # Value + ------------------------ + 0 103.7349 + 1 103.7349 + 2 103.7349 + + """ + print(' # Value') + print('------------------------') # - # if len(retval) == 1: - # return retval[0] - # return retval - - - - - -##------------------- - - - - -# -# -# -# def list_as_table(data): -# """Prints the list `data` as a table. This function is used to produce a -# human-readable format of a table for comparing financial indicators. -# -# Args: -# data (list): List of numeric values. -# -# Returns: -# None -# -# **Example.** -# -# >>> list_as_table(data=[1, 2, 3, 4]) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE -# # Value -# ------------------------ -# 0 1.0000 -# 1 2.0000 -# 2 3.0000 -# 3 4.0000 -# -# -# >>> prate = interest_rate([12]*5) -# >>> cflo = cashflow([-200] + [100]*4) -# >>> list_as_table(timevalue(cflo=[cflo, cflo, cflo], prate=prate)) # doctest: +NORMALIZE_WHITESPACE -# # Value -# ------------------------ -# 0 103.7349 -# 1 103.7349 -# 2 103.7349 -# -# """ -# print(' # Value') -# print('------------------------') -# # -# data = [round(element, 4) for element in data] -# -# for index, _ in enumerate(data): -# print(' {:<3d} {:14.4f}'.format(index, data[index])) + data = [round(element, 4) for element in data] + + for index, _ in enumerate(data): + print(' {:<3d} {:14.4f}'.format(index, data[index])) if __name__ == "__main__": diff --git a/cashflows/bond.py b/cashflows/bond.py old mode 100644 new mode 100755 index 5bb9469..c6494c9 --- a/cashflows/bond.py +++ b/cashflows/bond.py @@ -9,25 +9,31 @@ """ -import numpy as np -import pandas as pd - -# cashflows. from cashflows.tvmm import tvmm -def bond(maturity_date=None, freq='A', face_value=None, - coupon_rate=None, coupon_value=None, num_coupons=None, value=None, ytm=None): +def bond(face_value=None, coupon_rate=None, coupon_value=None, num_coupons=None, + value=None, ytm=None): """ + Evaluation of bond investments. + Args: - face_value (float, list): bond’s value at maturity. - coupon_value (float): amount of money you receive periodically as the bond matures. - num_coupons (int, list): number of coupons before maturity. - ytm (float): yield to maturity. - coupon_rate (float, list): rate of the face value that defines the coupon value. + face_value (float): the bond's value-at-maturity. + coupon_rate (float): rate for calculate the coupon payment. + coupon_value (float): periodic payment. + num_coupons (int): number of couont payments before maturity. + value (float, list): present value of the bond + ytm (float, list): yield-to-maturity. + Returns: - Float or list of floats. + None, a float value, or a list of float values: + * ``value``: when `ytm` is specified. + * ``ytm```: when `value` is specified. + * ``None``: when `ytm` and `value` are specified. Prints a sensibility table. + + When ``coupon_rate`` is defined, ``coupon_value`` is calculated automaticly. + Examples: @@ -37,413 +43,96 @@ def bond(maturity_date=None, freq='A', face_value=None, >>> bond(face_value=1000, coupon_rate=5.6, num_coupons=10, value=1000) # doctest: +ELLIPSIS 5.6... - >>> bond(face_value=[1000, 1200, 1400], coupon_value=56, num_coupons=10, value=1000) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE - Coupon_Rate Coupon_Value Face_Value Num_Coupons Value YTM - 0 5.600000 56 1000 10 1000 5.60000 - 1 4.666667 56 1200 10 1000 7.04451 - 2 4.000000 56 1400 10 1000 8.31956 - - - >>> bond(face_value=1000, coupon_value=[56., 57, 58], num_coupons=10, value=1000) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE - Coupon_Rate Coupon_Value Face_Value Num_Coupons Value YTM - 0 5.6 56.0 1000 10 1000 5.6 - 1 5.7 57.0 1000 10 1000 5.7 - 2 5.8 58.0 1000 10 1000 5.8 - - - >>> bond(face_value=1000, coupon_rate=[5.6, 5.7, 5.8], num_coupons=10, value=1000) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE - Coupon_Rate Coupon_Value Face_Value Num_Coupons Value YTM - 0 5.6 56.0 1000 10 1000 5.6 - 1 5.7 57.0 1000 10 1000 5.7 - 2 5.8 58.0 1000 10 1000 5.8 - - >>> bond(face_value=1000, coupon_rate=5.6, num_coupons=[10, 20, 30], value=1000) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE - Coupon_Rate Coupon_Value Face_Value Num_Coupons Value YTM - 0 5.6 56.0 1000 10 1000 5.6 - 1 5.6 56.0 1000 20 1000 5.6 - 2 5.6 56.0 1000 30 1000 5.6 - - - >>> bond(face_value=1000, coupon_rate=5.6, num_coupons=10, value=[800, 900, 1000]) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE - Coupon_Rate Coupon_Value Face_Value Num_Coupons Value YTM - 0 5.6 56.0 1000 10 800 8.671484 - 1 5.6 56.0 1000 10 900 7.025450 - 2 5.6 56.0 1000 10 1000 5.600000 - - >>> bond(face_value=[1000, 1100, 1200], coupon_rate=5.6, num_coupons=10, value=[800, 900, 1000]) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE - Coupon_Rate Coupon_Value Face_Value Num_Coupons Value YTM - 0 5.600000 56.0 1000 10 800 8.671484 - 1 5.600000 56.0 1000 10 900 7.025450 - 2 5.600000 56.0 1000 10 1000 5.600000 - 3 5.090909 56.0 1100 10 800 9.419301 - 4 5.090909 56.0 1100 10 900 7.772838 - 5 5.090909 56.0 1100 10 1000 6.346424 - 6 4.666667 56.0 1200 10 800 10.119360 - 7 4.666667 56.0 1200 10 900 8.472129 - 8 4.666667 56.0 1200 10 1000 7.044510 - - >>> bond(face_value=[1000, 1100, 1200], coupon_rate=[5.6, 5.7, 5.8], num_coupons=10, value=[800, 900, 1000]) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE - Coupon_Rate Coupon_Value Face_Value Num_Coupons Value YTM - 0 5.600000 56.0 1000 10 800 8.671484 - 1 5.600000 56.0 1000 10 900 7.025450 - 2 5.600000 56.0 1000 10 1000 5.600000 - 3 5.700000 57.0 1000 10 800 8.787284 - 4 5.700000 57.0 1000 10 900 7.132508 - 5 5.700000 57.0 1000 10 1000 5.700000 - 6 5.800000 58.0 1000 10 800 8.903126 - 7 5.800000 58.0 1000 10 900 7.239584 - 8 5.800000 58.0 1000 10 1000 5.800000 - 9 5.090909 56.0 1100 10 800 9.419301 - 10 5.090909 56.0 1100 10 900 7.772838 - 11 5.090909 56.0 1100 10 1000 6.346424 - 12 5.181818 57.0 1100 10 800 9.531367 - 13 5.181818 57.0 1100 10 900 7.876353 - 14 5.181818 57.0 1100 10 1000 6.443037 - 15 5.272727 58.0 1100 10 800 9.643488 - 16 5.272727 58.0 1100 10 900 7.979898 - 17 5.272727 58.0 1100 10 1000 6.539664 - 18 4.666667 56.0 1200 10 800 10.119360 - 19 4.666667 56.0 1200 10 900 8.472129 - 20 4.666667 56.0 1200 10 1000 7.044510 - 21 4.750000 57.0 1200 10 800 10.228122 - 22 4.750000 57.0 1200 10 900 8.572512 - 23 4.750000 57.0 1200 10 1000 7.138133 - 24 4.833333 58.0 1200 10 800 10.336951 - 25 4.833333 58.0 1200 10 900 8.672936 - 26 4.833333 58.0 1200 10 1000 7.231779 - - >>> bond(face_value=1000, coupon_rate=5.6, num_coupons=10, value=1000, ytm=[5.1, 5.6, 6.1]) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE - Basis_Value Change Coupon_Rate Coupon_Value Face_Value \\ - 0 1000 3.842187e+00 5.6 56.0 1000 - 1 1000 1.136868e-14 5.6 56.0 1000 - 2 1000 -3.662671e+00 5.6 56.0 1000 - - Num_Coupons Value YTM - 0 10 1038.421866 5.1 - 1 10 1000.000000 5.6 - 2 10 963.373290 6.1 - - - >>> bond(face_value=1000, coupon_rate=5.6, num_coupons=10, value=[1000, 1100], ytm=[5.1, 5.6, 6.1]) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE - Basis_Value Change Coupon_Rate Coupon_Value Face_Value \\ - 0 1000 3.842187e+00 5.6 56.0 1000 - 1 1000 1.136868e-14 5.6 56.0 1000 - 2 1000 -3.662671e+00 5.6 56.0 1000 - 3 1100 -5.598012e+00 5.6 56.0 1000 - 4 1100 -9.090909e+00 5.6 56.0 1000 - 5 1100 -1.242061e+01 5.6 56.0 1000 - - Num_Coupons Value YTM - 0 10 1038.421866 5.1 - 1 10 1000.000000 5.6 - 2 10 963.373290 6.1 - 3 10 1038.421866 5.1 - 4 10 1000.000000 5.6 - 5 10 963.373290 6.1 - - - - # >>> bond(face_value=1000, coupon_rate=5.6, num_coupons=[20], value=[1000, 1100], ytm=[5.6, 6.1]) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE - # Basis_Value Change Coupon_Rate Coupon_Value Face_Value Num_Coupons \\ - # 0 1000 0.000000 5.6 56.0 1000 20 - # 1 1000 -5.688693 5.6 56.0 1000 20 - # 2 1100 -9.090909 5.6 56.0 1000 20 - # 3 1100 -14.262448 5.6 56.0 1000 20 - # - # Value YTM - # 0 1000.000000 5.6 - # 1 943.113073 6.1 - # 2 1000.000000 5.6 - # 3 943.113073 6.1 + Also, it is possible to make sensibility analysis for bond's data. In the + following case, the present value of the bond is calculated for various + values of the yield-to-maturity. - """ + >>> bond(face_value=1000, coupon_rate=5.6, num_coupons=10, + ... ytm=[4.0, 5.0, 5.6, 6.0, 7.0]) # doctest: +ELLIPSIS + [1129.77..., 1046.33..., 1000.0..., 970.55..., 901.66...] - if coupon_rate is None and coupon_value is None: - raise ValueError('coupon_rate or coupon_value must be specified') + And for different values: - ## converts params to lists + >>> bond(face_value=1000, coupon_rate=5.6, num_coupons=10, + ... value=[900, 1000, 1100]) # doctest: +ELLIPSIS + [7.0..., 5.6..., 4.3...] - if coupon_rate is not None and not isinstance(coupon_rate, list): - coupon_rate = [coupon_rate] + When values for the yield-to-maturity and one value for present value of + the bond are supplied, the function prints a report. - if coupon_value is not None and not isinstance(coupon_value, list): - coupon_value = [coupon_value] + >>> bond(face_value=1000, coupon_rate=5.6, num_coupons=10, + ... ytm=[4.0, 5.0, 5.6, 6.0, 7.0], value=1000) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + Bond valuation analysis + Reference price: 1000 + Analysis: + Yield Value Change + (%) ($) (%) + ------------------------ + 4.00 1129.77 12.98 + 5.00 1046.33 4.63 + 5.60 1000.00 0.00 + 6.00 970.56 -2.94 + 7.00 901.67 -9.83 - if not isinstance(num_coupons, list): - num_coupons = [num_coupons] - if not isinstance(face_value, list): - face_value = [face_value] + """ - if not isinstance(num_coupons, list): - num_coupons = [num_coupons] - if ytm is not None and not isinstance(ytm, list): - ytm = [ytm] + if coupon_rate is None and coupon_value is None: + raise ValueError('coupon_rate or coupon_value must be specified') - if value is not None and not isinstance(value, list): - value = [value] + if coupon_rate is None: + coupon_rate = coupon_value / face_value - result = None - counter = 0 + if coupon_value is None: + coupon_value = coupon_rate * face_value / 100 - ## value is unknown if value is None: + value = tvmm(pmt=coupon_value, fval=face_value, + nper=num_coupons, nrate=ytm, due=0, pyr=1) + if isinstance(value, list): + value = [-x for x in value] + else: + value = -value + return value - for xface_value in face_value: - - if coupon_value is None: - coupon_value = [xrate * xface_value / 100 for xrate in coupon_rate] - - for xcoupon_value in coupon_value: - for xnum_coupons in num_coupons: - for xytm in ytm: - - xvalue = tvmm(pmt=xcoupon_value, - fval=xface_value, - nper=xnum_coupons, - nrate=xytm, - due=0, pyr=1) - - xcoupon_rate = xcoupon_value / xface_value * 100.0 - - aux = pd.DataFrame({'Face_Value': xface_value, - 'Coupon_Value': xcoupon_value, - 'Coupon_Rate': xcoupon_rate, - 'Num_Coupons':xnum_coupons, - 'YTM':xytm, - 'Value': -xvalue}, - index = [counter]) - counter += 1 - - if result is None: - result = aux - else: - result = result.append(aux, ignore_index=True) - - - if len(result) == 1: - return np.asscalar(result['Value']) - return result - - ## ytm is unknown if ytm is None: - - for xface_value in face_value: - - if coupon_value is None: - coupon_value = [xrate * xface_value / 100 for xrate in coupon_rate] - - for xcoupon_value in coupon_value: - for xnum_coupons in num_coupons: - for xvalue in value: - - xytm = tvmm(pmt=xcoupon_value, - fval=xface_value, - nper=xnum_coupons, - pval=-xvalue, - due=0, pyr=1) - - xcoupon_rate = xcoupon_value / xface_value * 100.0 - - aux = pd.DataFrame({'Face_Value': xface_value, - 'Coupon_Value': xcoupon_value, - 'Coupon_Rate': xcoupon_rate, - 'Num_Coupons':xnum_coupons, - 'YTM':xytm, - 'Value': xvalue}, - index = [counter]) - - counter += 1 - - if result is None: - result = aux - else: - result = result.append(aux, ignore_index=True) - - if len(result) == 1: - return np.asscalar(result['YTM']) - return result - - + if isinstance(value, list): + value = [-x for x in value] + else: + value = -value + return tvmm(pmt=coupon_value, fval=face_value, pval=value, nper=num_coupons, pyr=1) # # value and ytm are not None # sensibility analysis # - for basis_value in value: - - for xface_value in face_value: - - if coupon_value is None or coupon_rate is not None: - coupon_value = [xrate * xface_value / 100 for xrate in coupon_rate] - - for xcoupon_value in coupon_value: - for xnum_coupons in num_coupons: - for xytm in ytm: - - xvalue = tvmm(pmt=xcoupon_value, - fval=xface_value, - nper=xnum_coupons, - nrate=xytm, - due=0, pyr=1) - - xcoupon_rate = xcoupon_value / xface_value * 100.0 - - aux = pd.DataFrame({'Coupon_Value': xcoupon_value, - 'Coupon_Rate': xcoupon_rate, - 'Face_Value': xface_value, - 'Num_Coupons':xnum_coupons, - 'Basis_Value': basis_value, - 'YTM':xytm, - 'Value': -xvalue, - 'Change': 100 * (-xvalue-basis_value)/basis_value}, - index = [counter]) - - counter += 1 - - if result is None: - result = aux - else: - result = result.append(aux, ignore_index=True) - - - return result - - - - - - - - - - -# def bond(face_value=None, coupon_rate=None, coupon_value=None, num_coupons=None, -# value=None, ytm=None): -# """ -# -# Evaluation of bond investments. -# -# Args: -# face_value (float): the bond's value-at-maturity. -# coupon_rate (float): rate for calculate the coupon payment. -# coupon_value (float): periodic payment. -# num_coupons (int): number of couont payments before maturity. -# value (float, list): present value of the bond -# ytm (float, list): yield-to-maturity. -# -# -# Returns: -# None, a float value, or a list of float values: -# * `value`: when `ytm` is specified. -# * `ytm`: when `value` is specified. -# * `None`: when `ytm` and `value` are specified. Prints a sensibility table. -# -# When `coupon_rate` is defined, `coupon_value` is calculated automaticly. -# -# -# Examples: -# -# >>> bond(face_value=1000, coupon_value=56, num_coupons=10, ytm=5.6) # doctest: +ELLIPSIS -# 1000.0... -# -# >>> bond(face_value=1000, coupon_rate=5.6, num_coupons=10, value=1000) # doctest: +ELLIPSIS -# 5.6... -# -# -# Also, it is possible to make sensibility analysis for bond's data. In the -# following case, the present value of the bond is calculated for various -# values of the yield-to-maturity. -# -# >>> bond(face_value=1000, coupon_rate=5.6, num_coupons=10, -# ... ytm=[4.0, 5.0, 5.6, 6.0, 7.0]) # doctest: +ELLIPSIS -# [1129.77..., 1046.33..., 1000.0..., 970.55..., 901.66...] -# -# -# And for different values: -# -# >>> bond(face_value=1000, coupon_rate=5.6, num_coupons=10, -# ... value=[900, 1000, 1100]) # doctest: +ELLIPSIS -# [7.0..., 5.6..., 4.3...] -# -# When values for the yield-to-maturity and one value for present value of -# the bond are supplied, the function prints a report. -# -# >>> bond(face_value=1000, coupon_rate=5.6, num_coupons=10, -# ... ytm=[4.0, 5.0, 5.6, 6.0, 7.0], value=1000) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE -# Bond valuation analysis -# Reference price: 1000 -# Analysis: -# Yield Value Change -# (%) ($) (%) -# ------------------------ -# 4.00 1129.77 12.98 -# 5.00 1046.33 4.63 -# 5.60 1000.00 0.00 -# 6.00 970.56 -2.94 -# 7.00 901.67 -9.83 -# -# -# """ -# -# -# if coupon_rate is None and coupon_value is None: -# raise ValueError('coupon_rate or coupon_value must be specified') -# -# if coupon_rate is None: -# coupon_rate = coupon_value / face_value -# -# if coupon_value is None: -# coupon_value = coupon_rate * face_value / 100 -# -# if value is None: -# value = tvmm(pmt=coupon_value, fval=face_value, -# nper=num_coupons, nrate=ytm, due=0, pyr=1) -# if isinstance(value, list): -# value = [-x for x in value] -# else: -# value = -value -# return value -# -# if ytm is None: -# if isinstance(value, list): -# value = [-x for x in value] -# else: -# value = -value -# return tvmm(pmt=coupon_value, fval=face_value, pval=value, nper=num_coupons, pyr=1) -# -# # -# # value and ytm are not None -# # sensibility analysis -# # -# -# values = tvmm(pmt=coupon_value, fval=face_value, -# nper=num_coupons, nrate=ytm, due=0, pyr=1) -# if isinstance(values, list): -# values = [-x for x in values] -# else: -# values = -values -# -# txt = ['Bond valuation analysis'] -# txt += ['Reference price: ' + value.__str__()] -# txt += ['Analysis:'] -# txt += [' Yield Value Change'] -# txt += [' (%) ($) (%)'] -# txt += [' ------------------------'] -# -# -# if not isinstance(ytm, list): -# ytm = [ytm] -# values = [values] -# for iytm, ivalue in zip(ytm, values): -# fmt = ' {:6.2f} {:9.2f} {:6.2f}' -# txt += [fmt.format(iytm, ivalue, 100 * (ivalue-value)/value)] -# -# print('\n'.join(txt)) + values = tvmm(pmt=coupon_value, fval=face_value, + nper=num_coupons, nrate=ytm, due=0, pyr=1) + if isinstance(values, list): + values = [-x for x in values] + else: + values = -values + + txt = ['Bond valuation analysis'] + txt += ['Reference price: ' + value.__str__()] + txt += ['Analysis:'] + txt += [' Yield Value Change'] + txt += [' (%) ($) (%)'] + txt += [' ------------------------'] + + + if not isinstance(ytm, list): + ytm = [ytm] + values = [values] + for iytm, ivalue in zip(ytm, values): + fmt = ' {:6.2f} {:9.2f} {:6.2f}' + txt += [fmt.format(iytm, ivalue, 100 * (ivalue-value)/value)] + + print('\n'.join(txt)) diff --git a/cashflows/common.py b/cashflows/common.py old mode 100644 new mode 100755 index 570bc7c..b0ba9a8 --- a/cashflows/common.py +++ b/cashflows/common.py @@ -1,29 +1,4 @@ -import pandas as pd - -def getpyr(x): - """ returns the frequency as a number of periods per year""" - - if not isinstance(x, pd.Series): - msg = 'Pandas Series object expected: ' + x.__repr__() - raise ValueError(msg) - - if x.axes[0].freq not in ['A', 'BA', 'Q', 'BQ', 'M', 'BM', 'CBM', 'SM', '6M', '6BM', '6CBM']: - msg = 'Invalid freq value: ' + freq.__repr__() - raise ValueError(msg) - - if x.axes[0].freq in ['A', 'BA']: - return 1 - if x.axes[0].freq in ['6M', '6BM', '6CBM']: - return 2 - if x.axes[0].freq in ['Q', 'BQ']: - return 4 - if x.axes[0].freq in ['M', 'BM', 'CBM']: - return 12 - return 24 # 'freq = "SM"' - - - def _vars2list(params): """ Converts the variables on lists of the same length """ diff --git a/cashflows/currency.py b/cashflows/currency.py old mode 100644 new mode 100755 index 5b89f22..b6ebdb0 --- a/cashflows/currency.py +++ b/cashflows/currency.py @@ -9,10 +9,7 @@ """ -import pandas - -# cashflows. -from cashflows.timeseries import * +from cashflows.gtimeseries import * from cashflows.common import _vars2list from cashflows.rate import to_discount_factor, to_compound_factor @@ -21,7 +18,7 @@ def currency_conversion(cflo, exchange_rate=1, devaluation=None, base_date=0): """Converts a cashflow of dollars to another currency. Args: - cflo (TimeSeries): Generic cashflow. + cflo (TimeSeries): A cashflow. exchange_rate (float): Exchange rate at time `base_date`. devaluation (TimeSeries): Devaluation rate per compounding period. base_date (int): Time index for the `exchange_rate` in current dollars. @@ -32,79 +29,75 @@ def currency_conversion(cflo, exchange_rate=1, devaluation=None, base_date=0): **Examples.** - >>> cflo = cashflow(const_value=[100] * 5, start='2015', freq='A') - >>> devaluation = interest_rate(const_value=[5]*5, start='2015', freq='A') + >>> cflo = cashflow(const_value=[100] * 5) >>> currency_conversion(cflo=cflo, exchange_rate=2) # doctest: +NORMALIZE_WHITESPACE - 2015 200.0 - 2016 200.0 - 2017 200.0 - 2018 200.0 - 2019 200.0 - Freq: A-DEC, dtype: float64 - - >>> currency_conversion(cflo=cflo, exchange_rate=2, devaluation=devaluation) # doctest: +NORMALIZE_WHITESPACE - 2015 200.00000 - 2016 210.00000 - 2017 220.50000 - 2018 231.52500 - 2019 243.10125 - Freq: A-DEC, dtype: float64 + Time Series: + Start = (0,) + End = (4,) + pyr = 1 + Data = (0,)-(4,) [5] 200.00 >>> currency_conversion(cflo=cflo, exchange_rate=2, - ... devaluation=devaluation, base_date='2017') # doctest: +NORMALIZE_WHITESPACE - 2015 181.405896 - 2016 190.476190 - 2017 200.000000 - 2018 210.000000 - 2019 220.500000 - Freq: A-DEC, dtype: float64 + ... devaluation=interest_rate(const_value=[5]*5), base_date=(2,)) # doctest: +NORMALIZE_WHITESPACE + Time Series: + Start = (0,) + End = (4,) + pyr = 1 + Data = (0,) 181.41 + (1,) 190.48 + (2,) 200.00 + (3,) 210.00 + (4,) 220.50 + + + >>> cflo = cashflow(const_value=[100] * 8, pyr=4) + >>> currency_conversion(cflo=cflo, + ... exchange_rate=2, + ... devaluation=interest_rate([1]*8, pyr=4)) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 200.00 202.00 204.02 206.06 + 1 208.12 210.20 212.30 214.43 + + >>> cflo = cashflow(const_value=[100] * 5) + >>> currency_conversion(cflo=[cflo, cflo], exchange_rate=2) # doctest: +NORMALIZE_WHITESPACE + [Time Series: + Start = (0,) + End = (4,) + pyr = 1 + Data = (0,)-(4,) [5] 200.00 + , Time Series: + Start = (0,) + End = (4,) + pyr = 1 + Data = (0,)-(4,) [5] 200.00 + ] """ - if not isinstance(cflo, pandas.Series): - raise TypeError("`cashflow` must be a pandas.Series object") - result = cflo.copy() - if devaluation is None: - for time, _ in enumerate(result): - result[time] *= exchange_rate - else: - if not isinstance(devaluation, pandas.Series): - raise TypeError("`devaluation` must be a pandas.Series object") - verify_period_range([cflo, devaluation]) - factor = to_compound_factor(prate=devaluation, base_date=base_date) - for time, _ in enumerate(result): - result[time] *= exchange_rate * factor[time] - return result - - ## - ## version vectorizada - ## - - # params = _vars2list([cflo, exchange_rate, devaluation, base_date]) - # cflo = params[0] - # exchange_rate = params[1] - # devaluation = params[2] - # base_date = params[3] - # retval = [] - # for xcflo, xexchange_rate, xdevaluation, xbase_date in zip(cflo, exchange_rate, devaluation, base_date): - # if not isinstance(xcflo, pandas.Series): - # raise TypeError("`cashflow` must be a pandas.Series object") - # if xdevaluation is None: - # result = xcflo.copy() - # for time, _ in enumerate(result): - # result[time] *= xexchange_rate - # else: - # if not isinstance(xdevaluation, pandas.Series): - # raise TypeError("`devaluation` must be a pandas.Series object") - # verify_period_range([xcflo, xdevaluation]) - # factor = to_compound_factor(prate=xdevaluation, base_date=xbase_date) - # result = xcflo.copy() - # for time, _ in enumerate(result): - # result[time] *= xexchange_rate * factor[time] - # retval.append(result) - # if len(retval) == 1: - # return retval[0] - # return retval - + params = _vars2list([cflo, exchange_rate, devaluation, base_date]) + cflo = params[0] + exchange_rate = params[1] + devaluation = params[2] + base_date = params[3] + retval = [] + for xcflo, xexchange_rate, xdevaluation, xbase_date in zip(cflo, exchange_rate, devaluation, base_date): + if not isinstance(xcflo, TimeSeries): + raise TypeError("`cashflow` must be a TimeSeries") + if xdevaluation is None: + result = xcflo.copy() + for time, _ in enumerate(result): + result[time] *= xexchange_rate + else: + if not isinstance(xdevaluation, TimeSeries): + raise TypeError("`devaluation` must be a TimeSeries") + verify_eq_time_range(xcflo, xdevaluation) + factor = to_compound_factor(prate=xdevaluation, base_date=xbase_date) + result = xcflo.copy() + for time, _ in enumerate(result): + result[time] *= xexchange_rate * factor[time] + retval.append(result) + if len(retval) == 1: + return retval[0] + return retval if __name__ == "__main__": import doctest diff --git a/cashflows/depreciation.py b/cashflows/depreciation.py old mode 100644 new mode 100755 index b8ae8f2..9292c55 --- a/cashflows/depreciation.py +++ b/cashflows/depreciation.py @@ -5,106 +5,188 @@ """ -# from cashflows.gtimeseries import TimeSeries, cashflow, interest_rate, verify_eq_time_range +from cashflows.gtimeseries import TimeSeries, cashflow, interest_rate, verify_eq_time_range +def print_depr(depr, adepr, costs, begbook, endbook): + """Prints a depreciation table -import pandas as pd + Args: + cost (int, float): Initial cost of the asset + depr (list): Depreciation per period + adepr (list): Accumulated depreciation per period + begbook (list): Beginning book value + endbook (list): Ending book value + + Returns: + None + + """ -# -from cashflows.timeseries import cashflow, interest_rate, verify_period_range + len_timeid = len(costs.end.__repr__()) + len_number = max(len('{:1.2f}'.format(max(begbook))), 7) -def depreciation_sl(costs, life, salvalue=None): + fmt_timeid = '{:<' + '{:d}'.format(len_timeid) + 's}' + fmt_number = ' {:' + '{:d}'.format(len_number) + '.2f}' + fmt_header = ' {:>' + '{:d}'.format(len_number) + 's}' + + if costs.pyr == 1: + xmajor, = costs.start + xminor = 0 + else: + xmajor, xminor = costs.start + + txt = [] + header = fmt_timeid.format('t') + header += fmt_header.format('Beg.') + header += fmt_header.format('Cost') + header += fmt_header.format('Depre.') + header += fmt_header.format('Accum.') + header += fmt_header.format('End.') + txt.append(header) + + header = fmt_timeid.format('') + header += fmt_header.format('Book') + header += fmt_header.format('') + header += fmt_header.format('') + header += fmt_header.format('Depre.') + header += fmt_header.format('Book') + txt.append(header) + + header = fmt_timeid.format('') + header += fmt_header.format('Value') + header += fmt_header.format('') + header += fmt_header.format('') + header += fmt_header.format('') + header += fmt_header.format('Value') + txt.append(header) + + txt.append('-' * len_timeid + '-----' + '-' * len_number * 5) + + + for time, _ in enumerate(costs): + if costs.pyr == 1: + timeid = (xmajor,) + else: + timeid = (xmajor, xminor) + fmt = fmt_timeid + fmt_number * 5 + txt.append(fmt.format(timeid.__repr__(), + begbook[time], + costs[time], + depr[time], + adepr[time], + endbook[time])) + if costs.pyr == 1: + xmajor += 1 + else: + xminor += 1 + if xminor == costs.pyr: + xminor = 0 + xmajor += 1 + + print('\n'.join(txt)) + + + +def depreciation_sl(costs, life, salvalue=None, delay=None, noprint=True): """Computes the depreciation of an asset using straight line depreciation method. Args: - costs (TimeSeries): the cost per period of the assets. + cost (TimeSeries): the cost per period of the assets. life (TimeSeries): number of depreciation periods for the asset. - salvalue (TimeSeries): salvage value as a percentage of cost. + salvalue(TimeSeries): salvage value as a percentage of cost. + noprint (bool): when True, the procedure prints a depreciation table. Returns: - Returns a pandas DataFrame with the computations. + depreciation, accum_depreciation (TimeSeries, TimeSeries). **Examples.** - >>> costs = cashflow(const_value=0, periods=16, start='2000Q1', freq='Q') - >>> costs[0] = 1000 - >>> life = cashflow(const_value=0, periods=16, start='2000Q1', freq='Q') - >>> life[0] = 4 - >>> depreciation_sl(costs=costs, life=life) # doctest: +NORMALIZE_WHITESPACE - Beg_Book Depr Accum_Depr End_Book - 2000Q1 1000.0 250.0 250.0 750.0 - 2000Q2 750.0 250.0 500.0 500.0 - 2000Q3 500.0 250.0 750.0 250.0 - 2000Q4 250.0 250.0 1000.0 0.0 - 2001Q1 0.0 0.0 1000.0 0.0 - 2001Q2 0.0 0.0 1000.0 0.0 - 2001Q3 0.0 0.0 1000.0 0.0 - 2001Q4 0.0 0.0 1000.0 0.0 - 2002Q1 0.0 0.0 1000.0 0.0 - 2002Q2 0.0 0.0 1000.0 0.0 - 2002Q3 0.0 0.0 1000.0 0.0 - 2002Q4 0.0 0.0 1000.0 0.0 - 2003Q1 0.0 0.0 1000.0 0.0 - 2003Q2 0.0 0.0 1000.0 0.0 - 2003Q3 0.0 0.0 1000.0 0.0 - 2003Q4 0.0 0.0 1000.0 0.0 - - - >>> costs = cashflow(const_value=0, periods=16, start='2000Q1', freq='Q') - >>> costs[0] = 1000 - >>> costs[8] = 1000 - >>> life = cashflow(const_value=0, periods=16, start='2000Q1', freq='Q') - >>> life[0] = 4 - >>> life[8] = 4 - >>> depreciation_sl(costs=costs, life=life) # doctest: +NORMALIZE_WHITESPACE - Beg_Book Depr Accum_Depr End_Book - 2000Q1 1000.0 250.0 250.0 750.0 - 2000Q2 750.0 250.0 500.0 500.0 - 2000Q3 500.0 250.0 750.0 250.0 - 2000Q4 250.0 250.0 1000.0 0.0 - 2001Q1 0.0 0.0 1000.0 0.0 - 2001Q2 0.0 0.0 1000.0 0.0 - 2001Q3 0.0 0.0 1000.0 0.0 - 2001Q4 0.0 0.0 1000.0 0.0 - 2002Q1 1000.0 250.0 1250.0 750.0 - 2002Q2 750.0 250.0 1500.0 500.0 - 2002Q3 500.0 250.0 1750.0 250.0 - 2002Q4 250.0 250.0 2000.0 0.0 - 2003Q1 0.0 0.0 2000.0 0.0 - 2003Q2 0.0 0.0 2000.0 0.0 - 2003Q3 0.0 0.0 2000.0 0.0 - 2003Q4 0.0 0.0 2000.0 0.0 - - + >>> costs1 = cashflow(const_value=0, nper=16, spec=(0, 1000), pyr=4) + >>> costs2 = cashflow(const_value=0, nper=16, spec=[(0, 1000), (8, 1000)], pyr=4) + >>> life1 = cashflow(const_value=0, nper=16, spec=(0, 4), pyr=4) + >>> life2 = cashflow(const_value=0, nper=16, spec=[(0, 4), (8, 4)], pyr=4) + >>> delay12 = cashflow(const_value=0, nper=16, spec=(0, 2), pyr=4) + >>> delay22 = cashflow(const_value=0, nper=16, spec=[(0, 2), (8, 2)], pyr=4) + >>> depreciation_sl(costs=costs1, life=life1) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 0.00 250.00 250.00 250.00 + 1 250.00 0.00 0.00 0.00 + 2 0.00 0.00 0.00 0.00 + 3 0.00 0.00 0.00 0.00 + + >>> depreciation_sl(costs=costs1, life=life1, delay=delay12) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 0.00 0.00 0.00 250.00 + 1 250.00 250.00 250.00 0.00 + 2 0.00 0.00 0.00 0.00 + 3 0.00 0.00 0.00 0.00 + + >>> depreciation_sl(costs=costs2, life=life2) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 0.00 250.00 250.00 250.00 + 1 250.00 0.00 0.00 0.00 + 2 0.00 250.00 250.00 250.00 + 3 250.00 0.00 0.00 0.00 + + + >>> depreciation_sl(costs=costs2, life=life2, delay=delay22) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 0.00 0.00 0.00 250.00 + 1 250.00 250.00 250.00 0.00 + 2 0.00 0.00 0.00 250.00 + 3 250.00 250.00 250.00 0.00 + + >>> depreciation_sl(costs=costs2, life=life2, delay=delay22, noprint=False) # doctest: +NORMALIZE_WHITESPACE + t Beg. Cost Depre. Accum. End. + Book Depre. Book + Value Value + ---------------------------------------------- + (0, 0) 0.00 1000.00 0.00 0.00 1000.00 + (0, 1) 1000.00 0.00 0.00 0.00 1000.00 + (0, 2) 1000.00 0.00 0.00 0.00 1000.00 + (0, 3) 1000.00 0.00 250.00 250.00 750.00 + (1, 0) 750.00 0.00 250.00 500.00 500.00 + (1, 1) 500.00 0.00 250.00 750.00 250.00 + (1, 2) 250.00 0.00 250.00 1000.00 0.00 + (1, 3) 0.00 0.00 0.00 1000.00 0.00 + (2, 0) 0.00 1000.00 0.00 1000.00 1000.00 + (2, 1) 1000.00 0.00 0.00 1000.00 1000.00 + (2, 2) 1000.00 0.00 0.00 1000.00 1000.00 + (2, 3) 1000.00 0.00 250.00 1250.00 750.00 + (3, 0) 750.00 0.00 250.00 1500.00 500.00 + (3, 1) 500.00 0.00 250.00 1750.00 250.00 + (3, 2) 250.00 0.00 250.00 2000.00 0.00 + (3, 3) 0.00 0.00 0.00 2000.00 0.00 """ - verify_period_range([costs, life]) + verify_eq_time_range(costs, life) if salvalue is not None: - verify_period_range([costs, salvalue]) + verify_eq_time_range(costs, salvalue) else: salvalue = [0] * len(costs) + if delay is not None: + verify_eq_time_range(costs, delay) + else: + delay = [0] * len(costs) - depr = costs.copy() - adepr = costs.copy() - begbook = costs.copy() - endbook = costs.copy() - - depr[:] = 0 - adepr[:] = 0 - begbook[:] = 0 - endbook[:] = 0 + depr = [0] * len(costs) + adepr = [0] * len(costs) + begbook = [0] * len(costs) + endbook = [0] * len(costs) for index, _ in enumerate(costs): if costs[index] == 0: continue - xdepr = [(costs[index] * (100 - salvalue[index]) / 100) / int(life[index])] * int(life[index]) - for time in range(int(life[index])): - if index + time < len(costs): - depr[index + time] += xdepr[time] + if delay[index] == 0: + ValueError('Depreciation with delay 0') + xdepr = [(costs[index] * (100 - salvalue[index]) / 100) / life[index]] * life[index] + for time in range(life[index]): + if index + time + delay[index] + 1 < len(costs): + depr[index + time + delay[index] + 1] += xdepr[time] else: break @@ -116,110 +198,115 @@ def depreciation_sl(costs, life, salvalue=None): for time, _ in enumerate(depr): if time > 0: - begbook[time] = endbook[time - 1] + costs[time] - else: - begbook[0] = costs[0] - endbook[time] = begbook[time] - depr[time] - - table = pd.DataFrame({'Beg_Book': begbook, - 'Depr': depr, - 'Accum_Depr': adepr, - 'End_Book': endbook}) + begbook[time] = endbook[time - 1] + endbook[time] = begbook[time] - depr[time] + costs[time] - table = table[['Beg_Book', 'Depr', 'Accum_Depr', 'End_Book']] - table = table.round(2) - return table + if noprint is True: + retval = costs.copy() + for index, _ in enumerate(costs): + retval[index] = depr[index] + return retval + print_depr(depr, adepr, costs, begbook, endbook) -def depreciation_soyd(costs, life, salvalue=None): +def depreciation_soyd(costs, life, salvalue=None, delay=None, noprint=True): """Computes the depreciation of an asset using the sum-of-year's-digits method. Args: - costs (TimeSeries): the cost per period of the assets. + cost (TimeSeries): the cost per period of the assets. life (TimeSeries): number of depreciation periods for the asset. - salvalue (TimeSeries): salvage value as a percentage of cost. + salvalue(TimeSeries): salvage value as a percentage of cost. + noprint (bool): when True, the procedure prints a depreciation table. Returns: - Returns a pandas DataFrame with the computations. + A tuple (dep, accum) of lists (tuple): depreciation per period and accumulated depreciation per period **Examples.** - >>> costs = cashflow(const_value=0, periods=16, start='2000Q1', freq='Q') - >>> costs[0] = 1000 - >>> life = cashflow(const_value=0, periods=16, start='2000Q1', freq='Q') - >>> life[0] = 4 - >>> depreciation_soyd(costs=costs, life=life) # doctest: +NORMALIZE_WHITESPACE - Beg_Book Depr Accum_Depr End_Book - 2000Q1 1000.0 400.0 400.0 600.0 - 2000Q2 600.0 300.0 700.0 300.0 - 2000Q3 300.0 200.0 900.0 100.0 - 2000Q4 100.0 100.0 1000.0 0.0 - 2001Q1 0.0 0.0 1000.0 0.0 - 2001Q2 0.0 0.0 1000.0 0.0 - 2001Q3 0.0 0.0 1000.0 0.0 - 2001Q4 0.0 0.0 1000.0 0.0 - 2002Q1 0.0 0.0 1000.0 0.0 - 2002Q2 0.0 0.0 1000.0 0.0 - 2002Q3 0.0 0.0 1000.0 0.0 - 2002Q4 0.0 0.0 1000.0 0.0 - 2003Q1 0.0 0.0 1000.0 0.0 - 2003Q2 0.0 0.0 1000.0 0.0 - 2003Q3 0.0 0.0 1000.0 0.0 - 2003Q4 0.0 0.0 1000.0 0.0 - - - >>> costs = cashflow(const_value=0, periods=16, start='2000Q1', freq='Q') - >>> costs[0] = 1000 - >>> costs[8] = 1000 - >>> life = cashflow(const_value=0, periods=16, start='2000Q1', freq='Q') - >>> life[0] = 4 - >>> life[8] = 4 - >>> depreciation_soyd(costs=costs, life=life) # doctest: +NORMALIZE_WHITESPACE - Beg_Book Depr Accum_Depr End_Book - 2000Q1 1000.0 400.0 400.0 600.0 - 2000Q2 600.0 300.0 700.0 300.0 - 2000Q3 300.0 200.0 900.0 100.0 - 2000Q4 100.0 100.0 1000.0 0.0 - 2001Q1 0.0 0.0 1000.0 0.0 - 2001Q2 0.0 0.0 1000.0 0.0 - 2001Q3 0.0 0.0 1000.0 0.0 - 2001Q4 0.0 0.0 1000.0 0.0 - 2002Q1 1000.0 400.0 1400.0 600.0 - 2002Q2 600.0 300.0 1700.0 300.0 - 2002Q3 300.0 200.0 1900.0 100.0 - 2002Q4 100.0 100.0 2000.0 0.0 - 2003Q1 0.0 0.0 2000.0 0.0 - 2003Q2 0.0 0.0 2000.0 0.0 - 2003Q3 0.0 0.0 2000.0 0.0 - 2003Q4 0.0 0.0 2000.0 0.0 + + >>> costs1 = cashflow(const_value=0, nper=16, spec=(0, 1000), pyr=4) + >>> costs2 = cashflow(const_value=0, nper=16, spec=[(0, 1000), (8, 1000)], pyr=4) + >>> life1 = cashflow(const_value=0, nper=16, spec=(0, 4), pyr=4) + >>> life2 = cashflow(const_value=0, nper=16, spec=[(0, 4), (8, 4)], pyr=4) + >>> delay12 = cashflow(const_value=0, nper=16, spec=(0, 2), pyr=4) + >>> delay22 = cashflow(const_value=0, nper=16, spec=[(0, 2), (8, 2)], pyr=4) + >>> depreciation_soyd(costs=costs1, life=life1) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 0.00 400.00 300.00 200.00 + 1 100.00 0.00 0.00 0.00 + 2 0.00 0.00 0.00 0.00 + 3 0.00 0.00 0.00 0.00 + + >>> depreciation_soyd(costs=costs1, life=life1, delay=delay12) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 0.00 0.00 0.00 400.00 + 1 300.00 200.00 100.00 0.00 + 2 0.00 0.00 0.00 0.00 + 3 0.00 0.00 0.00 0.00 + + >>> depreciation_soyd(costs=costs2, life=life2) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 0.00 400.00 300.00 200.00 + 1 100.00 0.00 0.00 0.00 + 2 0.00 400.00 300.00 200.00 + 3 100.00 0.00 0.00 0.00 + + + >>> depreciation_soyd(costs=costs2, life=life2, delay=delay22) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 0.00 0.00 0.00 400.00 + 1 300.00 200.00 100.00 0.00 + 2 0.00 0.00 0.00 400.00 + 3 300.00 200.00 100.00 0.00 + + >>> depreciation_soyd(costs=costs2, life=life2, delay=delay22, noprint=False) # doctest: +NORMALIZE_WHITESPACE + t Beg. Cost Depre. Accum. End. + Book Depre. Book + Value Value + ---------------------------------------------- + (0, 0) 0.00 1000.00 0.00 0.00 1000.00 + (0, 1) 1000.00 0.00 0.00 0.00 1000.00 + (0, 2) 1000.00 0.00 0.00 0.00 1000.00 + (0, 3) 1000.00 0.00 400.00 400.00 600.00 + (1, 0) 600.00 0.00 300.00 700.00 300.00 + (1, 1) 300.00 0.00 200.00 900.00 100.00 + (1, 2) 100.00 0.00 100.00 1000.00 0.00 + (1, 3) 0.00 0.00 0.00 1000.00 0.00 + (2, 0) 0.00 1000.00 0.00 1000.00 1000.00 + (2, 1) 1000.00 0.00 0.00 1000.00 1000.00 + (2, 2) 1000.00 0.00 0.00 1000.00 1000.00 + (2, 3) 1000.00 0.00 400.00 1400.00 600.00 + (3, 0) 600.00 0.00 300.00 1700.00 300.00 + (3, 1) 300.00 0.00 200.00 1900.00 100.00 + (3, 2) 100.00 0.00 100.00 2000.00 0.00 + (3, 3) 0.00 0.00 0.00 2000.00 0.00 """ - verify_period_range([costs, life]) + verify_eq_time_range(costs, life) if salvalue is not None: - verify_period_range([costs, salvalue]) + verify_eq_time_range(costs, salvalue) else: salvalue = [0] * len(costs) + if delay is not None: + verify_eq_time_range(costs, delay) + else: + delay = [0] * len(costs) - depr = costs.copy() - adepr = costs.copy() - begbook = costs.copy() - endbook = costs.copy() - - depr[:] = 0 - adepr[:] = 0 - begbook[:] = 0 - endbook[:] = 0 + depr = [0] * len(costs) + adepr = [0] * len(costs) + begbook = [0] * len(costs) + endbook = [0] * len(costs) for index, _ in enumerate(costs): sumdig = life[index] * (life[index] + 1) / 2 - xdepr = [(costs[index] * (100 - salvalue[index]) / 100) * (int(life[index]) - time) / sumdig for time in range(int(life[index]))] - for time in range(int(life[index])): - if index + time < len(costs): - depr[index + time] += xdepr[time] + xdepr = [(costs[index] * (100 - salvalue[index]) / 100) * (life[index] - time) / sumdig for time in range(life[index])] + for time in range(life[index]): + if index + time + delay[index] + 1 < len(costs): + depr[index + time + delay[index] + 1] += xdepr[time] else: break @@ -231,20 +318,16 @@ def depreciation_soyd(costs, life, salvalue=None): for time, _ in enumerate(depr): if time > 0: - begbook[time] = endbook[time - 1] + costs[time] - else: - begbook[0] = costs[0] - endbook[time] = begbook[time] - depr[time] - - table = pd.DataFrame({'Beg_Book': begbook, - 'Depr': depr, - 'Accum_Depr': adepr, - 'End_Book': endbook}) + begbook[time] = endbook[time - 1] + endbook[time] = begbook[time] - depr[time] + costs[time] - table = table[['Beg_Book', 'Depr', 'Accum_Depr', 'End_Book']] - table = table.round(2) - return table + if noprint is True: + retval = costs.copy() + for index, _ in enumerate(costs): + retval[index] = depr[index] + return retval + print_depr(depr, adepr, costs, begbook, endbook) @@ -253,77 +336,108 @@ def depreciation_db(costs, life, salvalue=None, factor=1, convert_to_sl=True, de method. Args: - costs (TimeSeries): the cost per period of the assets. + cost (TimeSeries): the cost per period of the assets. life (TimeSeries): number of depreciation periods for the asset. - salvalue (TimeSeries): salvage value as a percentage of cost. + salvalue(TimeSeries): salvage value as a percentage of cost. factor (float): acelerating factor for depreciation. convert_to_sl (bool): converts to straight line method? noprint (bool): when True, the procedure prints a depreciation table. Returns: - Returns a pandas DataFrame with the computations. + A tuple (dep, accum) of lists (tuple): depreciation per period and accumulated depreciation per period **Examples.** - >>> costs = cashflow(const_value=0, periods=16, start='2000Q1', freq='Q') - >>> costs[0] = 1000 - >>> life = cashflow(const_value=0, periods=16, start='2000Q1', freq='Q') - >>> life[0] = 4 - >>> depreciation_db(costs=costs, life=life, factor=1.5, convert_to_sl=False) # doctest: +NORMALIZE_WHITESPACE - Beg_Book Depr Accum_Depr End_Book - 2000Q1 1000.00 375.00 375.00 625.00 - 2000Q2 625.00 234.38 609.38 390.62 - 2000Q3 390.62 146.48 755.86 244.14 - 2000Q4 244.14 91.55 847.41 152.59 - 2001Q1 152.59 0.00 847.41 152.59 - 2001Q2 152.59 0.00 847.41 152.59 - 2001Q3 152.59 0.00 847.41 152.59 - 2001Q4 152.59 0.00 847.41 152.59 - 2002Q1 152.59 0.00 847.41 152.59 - 2002Q2 152.59 0.00 847.41 152.59 - 2002Q3 152.59 0.00 847.41 152.59 - 2002Q4 152.59 0.00 847.41 152.59 - 2003Q1 152.59 0.00 847.41 152.59 - 2003Q2 152.59 0.00 847.41 152.59 - 2003Q3 152.59 0.00 847.41 152.59 - 2003Q4 152.59 0.00 847.41 152.59 - - >>> costs = cashflow(const_value=0, periods=16, start='2000Q1', freq='Q') - >>> costs[0] = 1000 - >>> costs[8] = 1000 - >>> life = cashflow(const_value=0, periods=16, start='2000Q1', freq='Q') - >>> life[0] = 4 - >>> life[8] = 4 - >>> depreciation_db(costs=costs, life=life, factor=1.5, convert_to_sl=False) # doctest: +NORMALIZE_WHITESPACE - Beg_Book Depr Accum_Depr End_Book - 2000Q1 1000.00 375.00 375.00 625.00 - 2000Q2 625.00 234.38 609.38 390.62 - 2000Q3 390.62 146.48 755.86 244.14 - 2000Q4 244.14 91.55 847.41 152.59 - 2001Q1 152.59 0.00 847.41 152.59 - 2001Q2 152.59 0.00 847.41 152.59 - 2001Q3 152.59 0.00 847.41 152.59 - 2001Q4 152.59 0.00 847.41 152.59 - 2002Q1 1152.59 375.00 1222.41 777.59 - 2002Q2 777.59 234.38 1456.79 543.21 - 2002Q3 543.21 146.48 1603.27 396.73 - 2002Q4 396.73 91.55 1694.82 305.18 - 2003Q1 305.18 0.00 1694.82 305.18 - 2003Q2 305.18 0.00 1694.82 305.18 - 2003Q3 305.18 0.00 1694.82 305.18 - 2003Q4 305.18 0.00 1694.82 305.18 + + >>> costs1 = cashflow(const_value=0, nper=16, spec=(0, 1000), pyr=4) + >>> costs2 = cashflow(const_value=0, nper=16, spec=[(0, 1000), (8, 1000)], pyr=4) + >>> life1 = cashflow(const_value=0, nper=16, spec=(0, 4), pyr=4) + >>> life2 = cashflow(const_value=0, nper=16, spec=[(0, 4), (8, 4)], pyr=4) + >>> delay12 = cashflow(const_value=0, nper=16, spec=(0, 2), pyr=4) + >>> delay22 = cashflow(const_value=0, nper=16, spec=[(0, 2), (8, 2)], pyr=4) + >>> depreciation_db(costs=costs1, life=life1, factor=1.5, convert_to_sl=False) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 0.00 375.00 234.38 146.48 + 1 91.55 0.00 0.00 0.00 + 2 0.00 0.00 0.00 0.00 + 3 0.00 0.00 0.00 0.00 + + >>> depreciation_db(costs=costs1, life=life1, factor=1.5, convert_to_sl=False, noprint=False) # doctest: +NORMALIZE_WHITESPACE + t Beg. Cost Depre. Accum. End. + Book Depre. Book + Value Value + ---------------------------------------------- + (0, 0) 0.00 1000.00 0.00 0.00 1000.00 + (0, 1) 1000.00 0.00 375.00 375.00 625.00 + (0, 2) 625.00 0.00 234.38 609.38 390.62 + (0, 3) 390.62 0.00 146.48 755.86 244.14 + (1, 0) 244.14 0.00 91.55 847.41 152.59 + (1, 1) 152.59 0.00 0.00 847.41 152.59 + (1, 2) 152.59 0.00 0.00 847.41 152.59 + (1, 3) 152.59 0.00 0.00 847.41 152.59 + (2, 0) 152.59 0.00 0.00 847.41 152.59 + (2, 1) 152.59 0.00 0.00 847.41 152.59 + (2, 2) 152.59 0.00 0.00 847.41 152.59 + (2, 3) 152.59 0.00 0.00 847.41 152.59 + (3, 0) 152.59 0.00 0.00 847.41 152.59 + (3, 1) 152.59 0.00 0.00 847.41 152.59 + (3, 2) 152.59 0.00 0.00 847.41 152.59 + (3, 3) 152.59 0.00 0.00 847.41 152.59 + + >>> depreciation_db(costs=costs1, life=life1, delay=delay12, factor=1.5, convert_to_sl=False) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 0.00 0.00 0.00 375.00 + 1 234.38 146.48 91.55 0.00 + 2 0.00 0.00 0.00 0.00 + 3 0.00 0.00 0.00 0.00 + + >>> depreciation_db(costs=costs2, life=life2, factor=1.5, convert_to_sl=False) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 0.00 375.00 234.38 146.48 + 1 91.55 0.00 0.00 0.00 + 2 0.00 375.00 234.38 146.48 + 3 91.55 0.00 0.00 0.00 + + >>> depreciation_db(costs=costs2, life=life2, delay=delay22, factor=1.5, convert_to_sl=False) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 0.00 0.00 0.00 375.00 + 1 234.38 146.48 91.55 0.00 + 2 0.00 0.00 0.00 375.00 + 3 234.38 146.48 91.55 0.00 + + >>> depreciation_db(costs=costs2, life=life2, delay=delay22, factor=1.5, convert_to_sl=False, noprint=False) # doctest: +NORMALIZE_WHITESPACE + t Beg. Cost Depre. Accum. End. + Book Depre. Book + Value Value + ---------------------------------------------- + (0, 0) 0.00 1000.00 0.00 0.00 1000.00 + (0, 1) 1000.00 0.00 0.00 0.00 1000.00 + (0, 2) 1000.00 0.00 0.00 0.00 1000.00 + (0, 3) 1000.00 0.00 375.00 375.00 625.00 + (1, 0) 625.00 0.00 234.38 609.38 390.62 + (1, 1) 390.62 0.00 146.48 755.86 244.14 + (1, 2) 244.14 0.00 91.55 847.41 152.59 + (1, 3) 152.59 0.00 0.00 847.41 152.59 + (2, 0) 152.59 1000.00 0.00 847.41 1152.59 + (2, 1) 1152.59 0.00 0.00 847.41 1152.59 + (2, 2) 1152.59 0.00 0.00 847.41 1152.59 + (2, 3) 1152.59 0.00 375.00 1222.41 777.59 + (3, 0) 777.59 0.00 234.38 1456.79 543.21 + (3, 1) 543.21 0.00 146.48 1603.27 396.73 + (3, 2) 396.73 0.00 91.55 1694.82 305.18 + (3, 3) 305.18 0.00 0.00 1694.82 305.18 """ - verify_period_range([costs, life]) + verify_eq_time_range(costs, life) if salvalue is not None: - verify_period_range([costs, salvalue]) + verify_eq_time_range(costs, salvalue) else: salvalue = [0] * len(costs) if delay is not None: - verify_period_range([costs, delay]) + verify_eq_time_range(costs, delay) else: delay = [0] * len(costs) if not isinstance(factor, (int, float)): @@ -331,15 +445,11 @@ def depreciation_db(costs, life, salvalue=None, factor=1, convert_to_sl=True, de if not isinstance(convert_to_sl, bool): raise TypeError('Invalid type for `convert_to_sl`') - depr = costs.copy() - adepr = costs.copy() - begbook = costs.copy() - endbook = costs.copy() + depr = [0] * len(costs) + adepr = [0] * len(costs) + begbook = [0] * len(costs) + endbook = [0] * len(costs) - depr[:] = 0 - adepr[:] = 0 - begbook[:] = 0 - endbook[:] = 0 for index, _ in enumerate(costs): if costs[index] == 0: @@ -348,11 +458,11 @@ def depreciation_db(costs, life, salvalue=None, factor=1, convert_to_sl=True, de xfactor = factor / life[index] rem_cost = costs[index] - xdepr = [0] * int(life[index]) + xdepr = [0] * life[index] sl_depr = (costs[index] - salvalue[index]) / life[index] - for time in range(int(life[index])): + for time in range(life[index]): xdepr[time] = rem_cost * xfactor if convert_to_sl is True and xdepr[time] < sl_depr: xdepr[time] = sl_depr @@ -362,9 +472,9 @@ def depreciation_db(costs, life, salvalue=None, factor=1, convert_to_sl=True, de xdepr[time] = rem_cost - salvalue[index] rem_cost = salvalue[index] - for time in range(int(life[index])): - if index + time < len(costs): - depr[index + time] += xdepr[time] + for time in range(life[index]): + if index + time + delay[index] + 1 < len(costs): + depr[index + time + delay[index] + 1] += xdepr[time] else: break @@ -372,23 +482,21 @@ def depreciation_db(costs, life, salvalue=None, factor=1, convert_to_sl=True, de if time > 0: adepr[time] = adepr[time - 1] + depr[time] else: - adepr[0] = depr[0] + adepr[time] = depr[time] for time, _ in enumerate(depr): if time > 0: - begbook[time] = endbook[time - 1] + costs[time] - else: - begbook[0] = costs[0] - endbook[time] = begbook[time] - depr[time] + begbook[time] = endbook[time - 1] + endbook[time] = begbook[time] - depr[time] + costs[time] + + if noprint is True: + retval = costs.copy() + for index, _ in enumerate(costs): + retval[index] = depr[index] + return retval - table = pd.DataFrame({'Beg_Book': begbook, - 'Depr': depr, - 'Accum_Depr': adepr, - 'End_Book': endbook}) + print_depr(depr, adepr, costs, begbook, endbook) - table = table[['Beg_Book', 'Depr', 'Accum_Depr', 'End_Book']] - table = table.round(2) - return table diff --git a/cashflows/gtimeseries.py b/cashflows/gtimeseries.py new file mode 100755 index 0000000..fb13ed3 --- /dev/null +++ b/cashflows/gtimeseries.py @@ -0,0 +1,1120 @@ +""" +Time series objects +============================================================================== + +This module implements a `TimeSeries` object that is used to represent generic +cashflows and interest rate. + +""" + + + +import calendar +# import numpy + + +def _timeid2float(xdate, pyr): + """Converts a pair (maj, min) in a float number + + >>> _timeid2float((2000,), 1) + 2000 + + >>> _timeid2float((2000, 0), 1) + 2000.0 + + >>> _timeid2float((2000, 0), 4) + 2000.0 + + >>> _timeid2float((2000, 1), 4) + 2000.25 + + >>> _timeid2float((2000, 2), 4) + 2000.5 + + >>> _timeid2float((2000, 3), 4) + 2000.75 + + """ + if isinstance(xdate, tuple) and len(xdate) == 1 and pyr == 1: + xmajor, = xdate + return xmajor + if isinstance(xdate, tuple) and len(xdate) == 2 and pyr == 1: + xmajor, xminor = xdate + if xminor == 0: + return float(xmajor * pyr + xminor / pyr) + if isinstance(xdate, tuple) and len(xdate) == 2 and pyr > 1: + xmajor, xminor = xdate + return float(xmajor + xminor / pyr) + raise TypeError("date in invalid format" + xdate.__repr__()) + + +def _float2timeid(xfloat, pyr): + """Converts a float number in a equivalent pair (maj, min) + + >>> _float2timeid(2000, pyr=1) # doctest: +NORMALIZE_WHITESPACE + (2000,) + + >>> _float2timeid(2000, pyr=3) # doctest: +NORMALIZE_WHITESPACE + (2000, 0) + + >>> _float2timeid(2000.25, pyr=4) # doctest: +NORMALIZE_WHITESPACE + (2000, 1) + + >>> _float2timeid(2000.50, pyr=4) # doctest: +NORMALIZE_WHITESPACE + (2000, 2) + + """ + if pyr == 1: + return (int(xfloat), ) + xmajor = int(xfloat) + xminor = int((xfloat - xmajor) * pyr) + return (xmajor, xminor) + + +def _timeid2index(timeid, basis, pyr): + """Converts a timeid in an integer index + + + >>> _timeid2index(timeid=(2000, 3), basis=(2000, 0), pyr=4) + 3 + + >>> _timeid2index(timeid=(2000, 0), basis=(2000, 0), pyr=4) + 0 + + """ + if not isinstance(timeid, tuple): + TypeError('Invalid type for timeid: ' + timeid.__repr__()) + if not isinstance(basis, tuple): + TypeError('Invalid type for basis: ' + basis.__repr__()) + if len(timeid) != len(basis): + TypeError('Incompatible tuples: ' + basis.__repr__() +', ' + timeid.__repr__()) + + if len(timeid) == 1: + timeid_major, = timeid + basis_major, = basis + return timeid_major - basis_major + + timeid_major, timeid_minor = timeid + basis_major, basis_minor = basis + return (timeid_major - basis_major) * pyr + (timeid_minor - basis_minor) + + + +def verify_eq_time_range(series1, series2): + + if series1.pyr != series2.pyr: + msg = 'Time series have different periods per year: ' + raise ValueError(msg + series1.pyr.__repr__() + ', ' + series2.pyr.__repr__()) + + if series1.start != series2.start: + msg = 'Time series have different start date: ' + raise ValueError(mag + series1.start.__repr__() + ', ' + series2.start.__repr__()) + + if series1.end != series2.end: + msg = 'Time series have different end date: ' + raise ValueError(mag + series1.end.__repr__() + ', ' + series2.end.__repr__()) + + + +class TimeSeries(): + """ + Time series object for representing generic cashflows and interest rates. + + **Examples.** + + >>> TimeSeries(start=(2000, 0), end=(2002, 3), nper=12, pyr=4) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 2000 0.00 0.00 0.00 0.00 + 2001 0.00 0.00 0.00 0.00 + 2002 0.00 0.00 0.00 0.00 + + >>> TimeSeries(start=(2000, 0), end=(2002, 3), pyr=4) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 2000 0.00 0.00 0.00 0.00 + 2001 0.00 0.00 0.00 0.00 + 2002 0.00 0.00 0.00 0.00 + + """ + + def __init__(self, start=None, end=None, nper=None, pyr=1): + """Creates a generic time series. + + >>> TimeSeries(nper=10) # doctest: +NORMALIZE_WHITESPACE + Time Series: + Start = (0,) + End = (9,) + pyr = 1 + Data = (0,)-(9,) [10] 0.00 + + >>> TimeSeries(start=2001, nper=10) # doctest: +NORMALIZE_WHITESPACE + Time Series: + Start = (2001,) + End = (2010,) + pyr = 1 + Data = (2001,)-(2010,) [10] 0.00 + + >>> TimeSeries(end=2010, nper=10) # doctest: +NORMALIZE_WHITESPACE + Time Series: + Start = (2001,) + End = (2010,) + pyr = 1 + Data = (2001,)-(2010,) [10] 0.00 + + >>> TimeSeries(start=2000, end=2002) # doctest: +NORMALIZE_WHITESPACE + Time Series: + Start = (2000,) + End = (2002,) + pyr = 1 + Data = (2000,)-(2002,) [3] 0.00 + + >>> TimeSeries(start=2000, end=2002, nper=3) # doctest: +NORMALIZE_WHITESPACE + Time Series: + Start = (2000,) + End = (2002,) + pyr = 1 + Data = (2000,)-(2002,) [3] 0.00 + + """ + + #pylint: disable=too-many-arguments + + def check_timeid(timeid): + # + if timeid is None: + return None + + if isinstance(timeid, (float, int)): + if pyr == 1: + return (int(timeid),) + return (int(timeid), 1) + + if isinstance(timeid, tuple): + if pyr == 1 and len(timeid) > 1: + major, minor = timeid + if minor > 1: + raise ValueError('Invalid data for minor unit: ' + minor.__repr__()) + return (int(major),) + elif pyr > 1 and len(timeid) == 1: + major, = timeid + return (major, 1) + elif pyr > 1 and len(timeid) == 2: + major, minor = timeid + if minor > pyr: + raise ValueError('Invalid data: ' + minor.__repr__()) + return timeid + + raise TypeError('Invalid type for TimeId: ' + timeid.__repr__()) + + start = check_timeid(start) + end = check_timeid(end) + + if nper is not None: + nper = int(nper) + + if start is not None and end is not None and nper is not None: + float_start = _timeid2float(start, pyr) + float_end = _timeid2float(end, pyr) + nperc = int((float_end - float_start) * pyr) + 1 + if nper != nperc: + msg = 'Invalid data for start, end and nper: ' + start.__repr__() + msg += ', ' + end.__repr__() + ', ' + nper.__repr__() + raise ValueError(msg) + elif start is not None and end is not None: # computes nper + float_start = _timeid2float(start, pyr) + float_end = _timeid2float(end, pyr) + nper = int((float_end - float_start) * pyr) + 1 + elif start is not None and nper is not None: # computes end + float_start = _timeid2float(start, pyr) + float_end = float_start + (nper - 1) / pyr + end = _float2timeid(float_end, pyr) + elif end is not None and nper is not None: # computes start + float_end = _timeid2float(end, pyr) + float_start = float_end - (nper - 1) / pyr + start = _float2timeid(float_start, pyr) + elif start is None and end is None and nper is not None: + if pyr == 1: + start = (0,) + end = (nper-1,) + else: + start = (0, 0) + end = _float2timeid((nper - 1) / pyr, pyr) + else: + raise ValueError('Invalid data for start, end or nper') + + if nper <= 1: + raise ValueError('Time Series must have a nper > 1') + + self.start = start + self.end = end + self.pyr = pyr + self.data = [0] * nper + + + def __repr__(self): + """Print the time series.""" + + if self.pyr in [4, 7, 12]: + return self.__repr4712__() + + txt = ['Time Series:'] + txt += ['Start = {:s}'.format(self.start.__repr__())] + txt += ['End = {:s}'.format(self.end.__repr__())] + txt += ['pyr = {:s}'.format(self.pyr.__repr__())] + + if self.pyr == 1: + imajor, = self.start + iminor = 0 + else: + imajor, iminor = self.start + + txt_date = [] + txt_freq = [] + txt_val = [] + + period = 0 + while period < len(self.data): + + freq = 1 + if self.pyr == 1: + beg_date = end_date = (imajor,) + else: + beg_date = end_date = (imajor, iminor) + + while period + freq < len(self.data) and \ + self.data[period] == self.data[period + freq]: + freq += 1 + iminor += 1 + if iminor >= self.pyr: + iminor = 0 + imajor += 1 + + if self.pyr == 1: + end_date = (imajor,) + else: + end_date = (imajor, iminor) + + iminor += 1 + if iminor >= self.pyr: + iminor = 0 + imajor += 1 + + if freq == 1: + +# iminor += 1 +# if iminor >= self.pyr: +# iminor = 0 +# imajor += 1 + + txt_date += ['{:s}'.format(beg_date.__str__())] + txt_freq += [' '] + txt_val += ['{:1.2f}'.format(self.data[period])] + else: + fmt = '{:s}-{:s}' + txt_date += [fmt.format(beg_date.__str__(), end_date.__str__())] + txt_freq += ['[{:d}]'.format(freq)] + txt_val += ['{:1.2f}'.format(self.data[period])] + + + + period += freq + + max_date = max_freq = max_val = 0 + for index, _ in enumerate(txt_date): + max_date = max(max_date, len(txt_date[index])) + max_freq = max(max_freq, len(txt_freq[index])) + max_val = max(max_val, len(txt_val[index])) + + fmt = ' {:' + '{:d}'.format(max_date) + 's} ' + fmt += '{:' + '{:d}'.format(max_freq) + 's} ' + fmt += '{:>' + '{:d}'.format(max_val) + 's} ' + + + for index, _ in enumerate(txt_date): + if index == 0: + txt += ['Data =' + fmt.format(txt_date[index], txt_freq[index], txt_val[index])] + else: + txt += [' ' +fmt.format(txt_date[index], txt_freq[index], txt_val[index])] + + return '\n'.join(txt)+'\n' + + + def __repr4712__(self): + """ Prints as table with quarters, days or months. + + # >>> TimeSeries(start=(1, 0), pyr=4) # doctest: +NORMALIZE_WHITESPACE + # Qtr0 Qtr1 Qtr2 Qtr3 + # 1 0.00 + + # >>> TimeSeries(start=(1, 1), pyr=7) # doctest: +NORMALIZE_WHITESPACE + # Mon Tue Wed Thu Fri Sat Sun + # 1 0.00 + + # >>> TimeSeries(start=(1, 1), pyr=12) # doctest: +NORMALIZE_WHITESPACE + # Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec + # 1 0.00 + + + """ + smajor, sminor = self.start + emajor, eminor = self.end + + imajor = smajor + iminor = 0 + iper = 0 + + maxlen = 0 + for data in self.data: + maxlen = max(maxlen, len('{:.2f}'.format(data))) + + maxlen = maxlen - 3 + + fmt_major = '{:<' + '{:= self.pyr: + iminor = 0 + imajor += 1 + txt.append(sline) + if is_ok is True: + sline = fmt_major.format(imajor) + else: + sline = '' + + txt.append(sline) + return '\n'.join(txt) + + + + def __getitem__(self, key): + """gets the item + + + + """ + if isinstance(key, tuple): + key = _timeid2index(timeid=key, basis=self.start, pyr=self.pyr) + return self.data[key] + + def __setitem__(self, key, value): + + if isinstance(key, tuple): + key = _timeid2index(timeid=key, basis=self.start, pyr=self.pyr) + self.data[key] = value + + def __len__(self): + return len(self.data) + + def __iter__(self): + return self.data.__iter__() + + def __next__(self): + return self.data.__next__() + + + def tolist(self): + """Returns the values as a list""" + return [x for x in self.data] + + def copy(self): + """returns a copy of the time series""" + result = TimeSeries(start=self.start, end=self.end, nper=len(self.data), pyr=self.pyr) + result.data = [value for value in self.data] + return result + + + def cumsum(self): + """returns the cumulative sum of the time series""" + result = TimeSeries(start=self.start, end=self.end, nper=len(self.data), pyr=self.pyr) + result.data = [value for value in self.data] + for index in range(1, len(self.data)): + result.data[index] += result.data[index - 1] + return result + + # + # mathematical operations + # + + def __abs__(self): + """Returns a new time series computed by applying the `abs` funtion + to the elements of the original time series. + + >>> abs(cashflow(const_value=[-10]*4, pyr=4)) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 10.00 10.00 10.00 10.00 + + """ + result = self.copy() + for time, _ in enumerate(result.data): + result[time] = abs(self[time]) + return result + + def __add__(self, other): + """Addition + + >>> cashflow(const_value=[1]*4, pyr=4) + cashflow(const_value=[2]*4, pyr=4) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 3.00 3.00 3.00 3.00 + + """ + if isinstance(other, (int, float)): + other = [other] * len(self) + else: + verify_eq_time_range(self, other) + result = self.copy() + for index, _ in enumerate(result.data): + result[index] += other[index] + return result + + + def __floordiv__(self, other): + """floordiv + + >>> cashflow(const_value=[6]*4, pyr=4) // cashflow(const_value=[4]*4, pyr=4) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 1.00 1.00 1.00 1.00 + + """ + if isinstance(other, (int, float)): + other = [other] * len(self) + else: + verify_eq_time_range(self, other) + result = self.copy() + for index, _ in enumerate(result.data): + result[index] //= other[index] + return result + + + def __mod__(self, other): + """ + + >>> cashflow( const_value=[6]*4, pyr=4) % cashflow( const_value=[4]*4, pyr=4) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 2.00 2.00 2.00 2.00 + + + """ + if isinstance(other, (int, float)): + other = [other] * len(self) + else: + verify_eq_time_range(self, other) + result = self.copy() + for index, _ in enumerate(result.data): + result[index] %= other[index] + return result + + + def __mul__(self, other): + """multiplication + + >>> cashflow( const_value=[2]*4, pyr=4) * cashflow( const_value=[3]*4, pyr=4) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 6.00 6.00 6.00 6.00 + + """ + if isinstance(other, (int, float)): + other = [other] * len(self) + else: + verify_eq_time_range(self, other) + result = self.copy() + for index, _ in enumerate(result.data): + result[index] *= other[index] + return result + + + def __sub__(self, other): + """Substraction + + >>> cashflow( const_value=[6]*4, pyr=4) - cashflow( const_value=[4]*4, pyr=4) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 2.00 2.00 2.00 2.00 + + """ + if isinstance(other, (int, float)): + other = [other] * len(self) + else: + verify_eq_time_range(self, other) + result = self.copy() + for index, _ in enumerate(result.data): + result[index] -= other[index] + return result + + + def __truediv__(self, other): + """ + + >>> cashflow(const_value=[6]*4, pyr=4) / cashflow(const_value=[4]*4, pyr=4) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 1.50 1.50 1.50 1.50 + + """ + if isinstance(other, (int, float)): + other = [other] * len(self) + else: + verify_eq_time_range(self, other) + result = self.copy() + for index, _ in enumerate(result.data): + result[index] /= other[index] + return result + + def __radd__(self, other): + """Reverse add function""" + if other == 0: + return self + else: + return self.__add__(other) + + + + # + # operations over sequences + # + + # def append(self, other): + # """ + # + # # >>> x = TimeSeries(data=[10]*10, start=(2001,)) + # # >>> y = TimeSeries(data=[20]*10, start=(2001,)) + # # >>> x.append(y) + # # >>> x # doctest: +NORMALIZE_WHITESPACE + # # Time Series: + # # Start = (2001,) + # # End = (2020,) + # # pyr = 1 + # # Data = (2001,)-(2020,) [20] 10.00 + # + # """ + # if self.pyr != other.pyr: + # raise ValueError("time series must have the same pyr") + # + # self.data.extend([x for x in other.data]) + # self.nper = len(self.data) + # self.end = _float2timeid(_timeid2float(self.start, self.pyr) + (self.nper - 1)/self.pyr, self.pyr) + + + + + # + # inplace operators + # + def __iadd__(self, other): + """ + + + >>> x = cashflow( const_value=[2]*4, pyr=4) + >>> x += cashflow( const_value=[3]*4, pyr=4) + >>> x # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 5.00 5.00 5.00 5.00 + + """ + if isinstance(other, (int, float)): + other = [other] * len(self) + else: + verify_eq_time_range(self, other) + for index, _ in enumerate(self.data): + self[index] += other[index] + return self + + + + # def __iconcat__(self, other): + # """ + # """ + # if self.pyr != other.pyr: + # raise ValueError("time series must have the same pyr") + # self.data += other.data + # self.nper = len(self.data) + # self.end = _float2timeid(_timeid2float(self.start, self.pyr) + (self.nper - 1)/self.pyr, self.pyr) + + def __ifloordiv__(self, other): + """ + + >>> x = cashflow( const_value=[6]*4, pyr=4) + >>> x //= cashflow( const_value=[4]*4, pyr=4) + >>> x # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 1.00 1.00 1.00 1.00 + + """ + if isinstance(other, (int, float)): + other = [other] * len(self) + else: + verify_eq_time_range(self, other) + for index, _ in enumerate(self.data): + self[index] //= other[index] + return self + + + + def __imod__(self, other): + """ + + >>> x = cashflow( const_value=[6]*4, pyr=4) + >>> x %= cashflow( const_value=[4]*4, pyr=4) + >>> x # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 2.00 2.00 2.00 2.00 + + """ + if isinstance(other, (int, float)): + other = [other] * len(self) + else: + verify_eq_time_range(self, other) + for index, _ in enumerate(self.data): + self[index] %= other[index] + return self + + + + def __imul__(self, other): + """ + >>> x = cashflow( const_value=[2]*4, pyr=4) + >>> x *= cashflow( const_value=[3]*4, pyr=4) + >>> x # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 6.00 6.00 6.00 6.00 + + """ + if isinstance(other, (int, float)): + other = [other] * len(self) + else: + verify_eq_time_range(self, other) + for index, _ in enumerate(self.data): + self[index] *= other[index] + return self + + + def __isub__(self, other): + """ + + >>> x = cashflow( const_value=[6]*4, pyr=4) + >>> x -= cashflow( const_value=[4]*4, pyr=4) + >>> x # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 2.00 2.00 2.00 2.00 + + """ + if isinstance(other, (int, float)): + other = [other] * len(self) + else: + verify_eq_time_range(self, other) + for index, _ in enumerate(self.data): + self[index] -= other[index] + return self + + + def __itruediv__(self, other): + """ + + >>> x = cashflow( const_value=[6]*4, pyr=4) + >>> x /= cashflow( const_value=[4]*4, pyr=4) + >>> x # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 1.50 1.50 1.50 1.50 + + """ + if isinstance(other, (int, float)): + other = [other] * len(self) + else: + verify_eq_time_range(self, other) + for index, _ in enumerate(self.data): + self[index] /= other[index] + return self + + + + + # def window(self, left=None, right=None): + # """Returns a sublist""" + # if left is None: + # left = 0 + # if right is None: + # right = len(self.data) + # return [self.data[t] for t in range(left, right + 1)] + + # def extend(self, left=0, right=0): + # """extendes the list""" + # self.data = [0] * left + self.data + [0] * right + # return self + + + +def cashflow(const_value=0, start=None, end=None, nper=None, pyr=1, spec=None): + """Returns a time series as a generic cashflow. + + >>> spec = ((2000, 3), 10) + >>> cashflow(const_value=1, start=(2000, 0), nper=8, pyr=4, spec=spec) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 2000 1.00 1.00 1.00 10.00 + 2001 1.00 1.00 1.00 1.00 + + >>> spec = [((2000, 3), 10), ((2001, 3), 10)] + >>> cashflow(const_value=1, start=(2000, 0), nper=8, pyr=4, spec=spec) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 2000 1.00 1.00 1.00 10.00 + 2001 1.00 1.00 1.00 10.00 + + >>> spec = (3, 10) + >>> cashflow(const_value=1, start=(2000, 0), nper=8, pyr=4, spec=spec) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 2000 1.00 1.00 1.00 10.00 + 2001 1.00 1.00 1.00 1.00 + + >>> spec = [(3, 10), (7, 10)] + >>> cashflow(const_value=1, start=(2000, 0), nper=8, pyr=4, spec=spec) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 2000 1.00 1.00 1.00 10.00 + 2001 1.00 1.00 1.00 10.00 + + >>> cashflow(const_value=[10]*10, pyr=4) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 10.00 10.00 10.00 10.00 + 1 10.00 10.00 10.00 10.00 + 2 10.00 10.00 + + >>> cashflow(const_value=[-10]*4) # doctest: +NORMALIZE_WHITESPACE + Time Series: + Start = (0,) + End = (3,) + pyr = 1 + Data = (0,)-(3,) [4] -10.00 + + >>> x = cashflow(const_value=[0, 1, 2, 3], pyr=4) + >>> x[3] = 10 + >>> x # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 0.00 1.00 2.00 10.00 + + >>> x[3] # doctest: +NORMALIZE_WHITESPACE + 10 + + >>> x[(0, 3)] = 0 + >>> x # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 0.00 1.00 2.00 0.00 + + >>> x[(0,2)] # doctest: +NORMALIZE_WHITESPACE + 2 + + >>> cashflow(const_value=[0, 1, 2, 2, 4, 5, 6, 7, 8]) # doctest: +NORMALIZE_WHITESPACE + Time Series: + Start = (0,) + End = (8,) + pyr = 1 + Data = (0,) 0.00 + (1,) 1.00 + (2,)-(3,) [2] 2.00 + (4,) 4.00 + (5,) 5.00 + (6,) 6.00 + (7,) 7.00 + (8,) 8.00 + + + >>> cashflow(const_value=0, nper=15, pyr=1, spec=[(t,100) for t in range(5,10)]) # doctest: +NORMALIZE_WHITESPACE + Time Series: + Start = (0,) + End = (14,) + pyr = 1 + Data = (0,)-(4,) [5] 0.00 + (5,)-(9,) [5] 100.00 + (10,)-(14,) [5] 0.00 + + + >>> cashflow(const_value=[0, 1, 2, 3, 4, 5]).cumsum() # doctest: +NORMALIZE_WHITESPACE + Time Series: + Start = (0,) + End = (5,) + pyr = 1 + Data = (0,) 0.00 + (1,) 1.00 + (2,) 3.00 + (3,) 6.00 + (4,) 10.00 + (5,) 15.00 + + """ + if isinstance(const_value, list) and nper is None: + nper = len(const_value) + time_series = TimeSeries(start=start, end=end, nper=nper, pyr=pyr) + start = time_series.start + for index, _ in enumerate(time_series): + if isinstance(const_value, list): + time_series[index] = const_value[index] + else: + time_series[index] = const_value + if spec is None: + return time_series + if isinstance(spec, tuple): + spec = [spec] + for xspec in spec: + timeid, value = xspec + if isinstance(timeid, int): + time = timeid + else: + time = _timeid2index(timeid=timeid, basis=start, pyr=pyr) + time_series[time] = value + return time_series + + + + +def interest_rate(const_value=0, start=None, end=None, nper=None, pyr=1, spec=None): + """Creates a time series object specified as a interest rate. + + >>> spec = ((2000, 3), 10) + >>> interest_rate(const_value=1, start=(2000, 0), nper=8, pyr=4, spec=spec) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 2000 1.00 1.00 1.00 10.00 + 2001 10.00 10.00 10.00 10.00 + + >>> spec = [((2000, 3), 10), ((2001, 1), 20)] + >>> interest_rate(const_value=1, start=(2000, 0), nper=8, pyr=4, spec=spec) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 2000 1.00 1.00 1.00 10.00 + 2001 10.00 20.00 20.00 20.00 + + >>> spec = (3, 10) + >>> interest_rate(const_value=1, start=(2000, 0), nper=8, pyr=4, spec=spec) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 2000 1.00 1.00 1.00 10.00 + 2001 10.00 10.00 10.00 10.00 + + >>> spec = [(3, 10), (6, 20)] + >>> interest_rate(const_value=1, start=(2000, 0), nper=8, pyr=4, spec=spec) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 2000 1.00 1.00 1.00 10.00 + 2001 10.00 10.00 20.00 20.00 + + >>> interest_rate(const_value=[10]*10, pyr=4) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 10.00 10.00 10.00 10.00 + 1 10.00 10.00 10.00 10.00 + 2 10.00 10.00 + + """ + if isinstance(const_value, list) and nper is None: + nper = len(const_value) + time_series = TimeSeries(start=start, end=end, nper=nper, pyr=pyr) + start = time_series.start + for index, _ in enumerate(time_series): + if isinstance(const_value, list): + time_series[index] = const_value[index] + else: + time_series[index] = const_value + if spec is None: + return time_series + if isinstance(spec, tuple): + spec = [spec] + nummod = len(spec) + starting = [None] * nummod + ending = [None] * nummod + values = [None] * nummod + for index, xspec in enumerate(spec): + timeid, value = xspec + if isinstance(timeid, int): + time = timeid + else: + time = _timeid2index(timeid=timeid, basis=start, pyr=pyr) + starting[index] = time + values[index] = value + ending[index] = len(time_series) + if index > 0: + ending[index - 1] = time + for index in range(nummod): + for time in range(starting[index], ending[index]): + time_series[time] = values[index] + return time_series + + + + +def repr_table(cols, header=None): + """ + """ + + if header is None and isinstance(cols, TimeSeries): + print(cols.__repr__()) + return + + if header is not None and isinstance(cols, TimeSeries): + print(header.__repr__()) + print(cols.__repr__()) + return + + if len(cols) > 1: + for xcol in cols[1:]: + verify_eq_time_range(cols[0], xcol) + + len_timeid = len(cols[0].end.__repr__()) + fmt_timeid = '{:<' + '{:d}'.format(len_timeid) + 's}' + + if header is None: + len_number = 0 + else: + len_number = 0 + for row in header: + for element in row: + len_number = max(len_number, len(element)) + + for xcol in cols: + for element in xcol: + len_number = max(len('{:1.2f}'.format(element)), len_number) + + fmt_number = ' {:' + '{:d}'.format(len_number) + '.2f}' + fmt_header = ' {:>' + '{:d}'.format(len_number) + 's}' + + if cols[0].pyr == 1: + xmajor, = cols[0].start + xminor = 0 + else: + xmajor, xminor = cols[0].start + + txt = [] + isfirst = True + for row in header: + if isfirst is True: + header = fmt_timeid.format('t') + isfirst = False + else: + header = fmt_timeid.format(' ') + for element in row: + header += fmt_header.format(element) + txt.append(header) + + txt.append('-' * len_timeid + '------' + '-' * len_number * len(cols)) + + for time, _ in enumerate(cols[0]): + if cols[0].pyr == 1: + timeid = (xmajor,) + else: + timeid = (xmajor, xminor) + txtrow = fmt_timeid.format(timeid.__repr__()) + for xcol in cols: + txtrow += fmt_number.format(xcol[time]) + txt.append(txtrow) + if cols[0].pyr == 1: + xmajor += 1 + else: + xminor += 1 + if xminor == cols[0].pyr: + xminor = 0 + xmajor += 1 + + return '\n'.join(txt) + + + +def cfloplot(cflo): + """Text plot of a cashflow. + + >>> cflo = cashflow(const_value=[-10, 5, 0, 20] * 3, pyr=4) + >>> cfloplot(cflo)# doctest: +NORMALIZE_WHITESPACE + time value +------------------+------------------+ + (0, 0) -10.00 ********** + (0, 1) 5.00 ***** + (0, 2) 0.00 * + (0, 3) 20.00 ******************** + (1, 0) -10.00 ********** + (1, 1) 5.00 ***** + (1, 2) 0.00 * + (1, 3) 20.00 ******************** + (2, 0) -10.00 ********** + (2, 1) 5.00 ***** + (2, 2) 0.00 * + (2, 3) 20.00 ******************** + + """ + + len_timeid = len(cflo.end.__repr__()) + fmt_timeid = '{:<' + '{:d}'.format(len_timeid) + 's}' + + len_number = 0 + for value in cflo: + len_number = max(len_number, len('{:1.2f}'.format(value))) + + fmt_number = ' {:' + '{:d}'.format(len_number) + '.2f}' + fmt_header = ' {:>' + '{:d}'.format(len_number) + 's}' + + if cflo.pyr == 1: + xmajor, = cflo.start + xminor = 0 + else: + xmajor, xminor = cflo.start + + maxval = max(abs(cflo)) + + + width = 20 + + txt = [] + txtrow = fmt_timeid.format("time") + fmt_header.format('value') + txtrow += " +" + "-" * (width - 2) + "+" + "-" * (width - 2) + "+" + txt.append(txtrow) + + for value in cflo: + + if cflo.pyr == 1: + timeid = (xmajor,) + else: + timeid = (xmajor, xminor) + + txtrow = fmt_timeid.format(timeid.__repr__()) + txtrow += fmt_number.format(value) + + # fmt_row = " * " + xlim = int(width * abs(value / maxval)) + if value < 0: + txtrow += " " + " " * (width - xlim) + '*' * (xlim) + elif value > 0: + txtrow += " " + " " * (width - 1) + "*" * (xlim) + else: + txtrow += " " + " " * (width - 1) + '*' + + txt.append(txtrow) + + if cflo.pyr == 1: + xmajor += 1 + else: + xminor += 1 + if xminor == cflo.pyr: + xminor = 0 + xmajor += 1 + + print('\n'.join(txt)) + + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/cashflows/images/analysis.png b/cashflows/images/analysis.png deleted file mode 100644 index 0c9779a..0000000 Binary files a/cashflows/images/analysis.png and /dev/null differ diff --git a/cashflows/images/antxven.png b/cashflows/images/antxven.png deleted file mode 100644 index 719df6c..0000000 Binary files a/cashflows/images/antxven.png and /dev/null differ diff --git a/cashflows/images/balance.png b/cashflows/images/balance.png deleted file mode 100644 index 67991ca..0000000 Binary files a/cashflows/images/balance.png and /dev/null differ diff --git a/cashflows/images/balance2.png b/cashflows/images/balance2.png deleted file mode 100644 index fee2f83..0000000 Binary files a/cashflows/images/balance2.png and /dev/null differ diff --git a/cashflows/images/cash-flow.png b/cashflows/images/cash-flow.png deleted file mode 100644 index 095a3cf..0000000 Binary files a/cashflows/images/cash-flow.png and /dev/null differ diff --git a/cashflows/images/cashflow-1-period.png b/cashflows/images/cashflow-1-period.png deleted file mode 100644 index a19c1a6..0000000 Binary files a/cashflows/images/cashflow-1-period.png and /dev/null differ diff --git a/cashflows/images/caso-ant.png b/cashflows/images/caso-ant.png deleted file mode 100644 index e17419d..0000000 Binary files a/cashflows/images/caso-ant.png and /dev/null differ diff --git a/cashflows/images/datos.png b/cashflows/images/datos.png deleted file mode 100644 index 74fcb27..0000000 Binary files a/cashflows/images/datos.png and /dev/null differ diff --git a/cashflows/images/devaluacion.png b/cashflows/images/devaluacion.png deleted file mode 100644 index e983772..0000000 Binary files a/cashflows/images/devaluacion.png and /dev/null differ diff --git a/cashflows/images/diagrama-vpn.png b/cashflows/images/diagrama-vpn.png deleted file mode 100644 index 7afb1a8..0000000 Binary files a/cashflows/images/diagrama-vpn.png and /dev/null differ diff --git a/cashflows/images/distr.png b/cashflows/images/distr.png deleted file mode 100644 index bccd1c3..0000000 Binary files a/cashflows/images/distr.png and /dev/null differ diff --git a/cashflows/images/ejemplo 2 equivalecia 1.png b/cashflows/images/ejemplo 2 equivalecia 1.png deleted file mode 100644 index 7fb0770..0000000 Binary files a/cashflows/images/ejemplo 2 equivalecia 1.png and /dev/null differ diff --git a/cashflows/images/ejemplo 3 equivalecia.png b/cashflows/images/ejemplo 3 equivalecia.png deleted file mode 100644 index b4e8934..0000000 Binary files a/cashflows/images/ejemplo 3 equivalecia.png and /dev/null differ diff --git a/cashflows/images/ejemplo 5 equivalecia.png b/cashflows/images/ejemplo 5 equivalecia.png deleted file mode 100644 index c940ab7..0000000 Binary files a/cashflows/images/ejemplo 5 equivalecia.png and /dev/null differ diff --git a/cashflows/images/ejemplonpv1.png b/cashflows/images/ejemplonpv1.png deleted file mode 100644 index bf600a9..0000000 Binary files a/cashflows/images/ejemplonpv1.png and /dev/null differ diff --git a/cashflows/images/ejercicio-A.png b/cashflows/images/ejercicio-A.png deleted file mode 100644 index fa5ef95..0000000 Binary files a/cashflows/images/ejercicio-A.png and /dev/null differ diff --git a/cashflows/images/ejercicioirr1.png b/cashflows/images/ejercicioirr1.png deleted file mode 100644 index 1c87ebd..0000000 Binary files a/cashflows/images/ejercicioirr1.png and /dev/null differ diff --git a/cashflows/images/eqiv-pmt-ant-finita.png b/cashflows/images/eqiv-pmt-ant-finita.png deleted file mode 100644 index aa67d26..0000000 Binary files a/cashflows/images/eqiv-pmt-ant-finita.png and /dev/null differ diff --git a/cashflows/images/equiv-pago-actual-futuro-disc.png b/cashflows/images/equiv-pago-actual-futuro-disc.png deleted file mode 100644 index 1993508..0000000 Binary files a/cashflows/images/equiv-pago-actual-futuro-disc.png and /dev/null differ diff --git a/cashflows/images/equiv-pmt-finitos.png b/cashflows/images/equiv-pmt-finitos.png deleted file mode 100644 index 3991450..0000000 Binary files a/cashflows/images/equiv-pmt-finitos.png and /dev/null differ diff --git a/cashflows/images/equiv-pmt-inf.png b/cashflows/images/equiv-pmt-inf.png deleted file mode 100644 index 1893e0e..0000000 Binary files a/cashflows/images/equiv-pmt-inf.png and /dev/null differ diff --git a/cashflows/images/equivalencia.png b/cashflows/images/equivalencia.png deleted file mode 100644 index 48ec0ac..0000000 Binary files a/cashflows/images/equivalencia.png and /dev/null differ diff --git a/cashflows/images/flujo.png b/cashflows/images/flujo.png deleted file mode 100644 index 0dfb0c9..0000000 Binary files a/cashflows/images/flujo.png and /dev/null differ diff --git a/cashflows/images/flujo2.png b/cashflows/images/flujo2.png deleted file mode 100644 index e617cd2..0000000 Binary files a/cashflows/images/flujo2.png and /dev/null differ diff --git a/cashflows/images/flujopv1.png b/cashflows/images/flujopv1.png deleted file mode 100644 index 65a9abf..0000000 Binary files a/cashflows/images/flujopv1.png and /dev/null differ diff --git a/cashflows/images/flujos-tipicos.png b/cashflows/images/flujos-tipicos.png deleted file mode 100644 index 90e8921..0000000 Binary files a/cashflows/images/flujos-tipicos.png and /dev/null differ diff --git a/cashflows/images/formula 2.png b/cashflows/images/formula 2.png deleted file mode 100644 index cd6e67f..0000000 Binary files a/cashflows/images/formula 2.png and /dev/null differ diff --git a/cashflows/images/formula 3.png b/cashflows/images/formula 3.png deleted file mode 100644 index 719dbe1..0000000 Binary files a/cashflows/images/formula 3.png and /dev/null differ diff --git a/cashflows/images/ganancias.png b/cashflows/images/ganancias.png deleted file mode 100644 index 0a432ea..0000000 Binary files a/cashflows/images/ganancias.png and /dev/null differ diff --git a/cashflows/images/graficas.pptx b/cashflows/images/graficas.pptx deleted file mode 100644 index d4ae1ef..0000000 Binary files a/cashflows/images/graficas.pptx and /dev/null differ diff --git a/cashflows/images/indiferencia.png b/cashflows/images/indiferencia.png deleted file mode 100644 index 1ca7a65..0000000 Binary files a/cashflows/images/indiferencia.png and /dev/null differ diff --git a/cashflows/images/interes-simple-y-compuesto.png b/cashflows/images/interes-simple-y-compuesto.png deleted file mode 100644 index fdbaf67..0000000 Binary files a/cashflows/images/interes-simple-y-compuesto.png and /dev/null differ diff --git a/cashflows/images/mirr.png b/cashflows/images/mirr.png deleted file mode 100644 index 7d97b82..0000000 Binary files a/cashflows/images/mirr.png and /dev/null differ diff --git a/cashflows/images/montecarlo1.png b/cashflows/images/montecarlo1.png deleted file mode 100644 index 93dc2d6..0000000 Binary files a/cashflows/images/montecarlo1.png and /dev/null differ diff --git a/cashflows/images/nomenclatura.png b/cashflows/images/nomenclatura.png deleted file mode 100644 index 2f27217..0000000 Binary files a/cashflows/images/nomenclatura.png and /dev/null differ diff --git a/cashflows/images/pago-actual-futuro-cont.png b/cashflows/images/pago-actual-futuro-cont.png deleted file mode 100644 index fc9fbba..0000000 Binary files a/cashflows/images/pago-actual-futuro-cont.png and /dev/null differ diff --git a/cashflows/images/payment-mode.png b/cashflows/images/payment-mode.png deleted file mode 100644 index 8d09a9b..0000000 Binary files a/cashflows/images/payment-mode.png and /dev/null differ diff --git a/cashflows/images/pmtfv.png b/cashflows/images/pmtfv.png deleted file mode 100644 index d0b1784..0000000 Binary files a/cashflows/images/pmtfv.png and /dev/null differ diff --git a/cashflows/images/pvfv.png b/cashflows/images/pvfv.png deleted file mode 100644 index 21d9a37..0000000 Binary files a/cashflows/images/pvfv.png and /dev/null differ diff --git a/cashflows/images/pvpmt.png b/cashflows/images/pvpmt.png deleted file mode 100644 index 29aee1d..0000000 Binary files a/cashflows/images/pvpmt.png and /dev/null differ diff --git a/cashflows/images/pyg.png b/cashflows/images/pyg.png deleted file mode 100644 index 099ee8e..0000000 Binary files a/cashflows/images/pyg.png and /dev/null differ diff --git a/cashflows/images/relacionBC.png b/cashflows/images/relacionBC.png deleted file mode 100644 index 07659d0..0000000 Binary files a/cashflows/images/relacionBC.png and /dev/null differ diff --git a/cashflows/images/sesion-1-ejemplo-1.png b/cashflows/images/sesion-1-ejemplo-1.png deleted file mode 100644 index 60b04ae..0000000 Binary files a/cashflows/images/sesion-1-ejemplo-1.png and /dev/null differ diff --git a/cashflows/images/sesion-1-ejemplo-2.png b/cashflows/images/sesion-1-ejemplo-2.png deleted file mode 100644 index 80470b7..0000000 Binary files a/cashflows/images/sesion-1-ejemplo-2.png and /dev/null differ diff --git a/cashflows/images/sesion-1-ejemplo-3.png b/cashflows/images/sesion-1-ejemplo-3.png deleted file mode 100644 index 116dbaf..0000000 Binary files a/cashflows/images/sesion-1-ejemplo-3.png and /dev/null differ diff --git a/cashflows/images/sesion-1-ejemplo-4.png b/cashflows/images/sesion-1-ejemplo-4.png deleted file mode 100644 index a5c3033..0000000 Binary files a/cashflows/images/sesion-1-ejemplo-4.png and /dev/null differ diff --git a/cashflows/images/sesion-1-ejemplo-5.png b/cashflows/images/sesion-1-ejemplo-5.png deleted file mode 100644 index 298cd0e..0000000 Binary files a/cashflows/images/sesion-1-ejemplo-5.png and /dev/null differ diff --git a/cashflows/images/sesion-1-ejemplo-6.png b/cashflows/images/sesion-1-ejemplo-6.png deleted file mode 100644 index 8656589..0000000 Binary files a/cashflows/images/sesion-1-ejemplo-6.png and /dev/null differ diff --git a/cashflows/images/sesion-2-ejemplo-1.png b/cashflows/images/sesion-2-ejemplo-1.png deleted file mode 100644 index 152dfde..0000000 Binary files a/cashflows/images/sesion-2-ejemplo-1.png and /dev/null differ diff --git a/cashflows/images/sesion-3-flujos.png b/cashflows/images/sesion-3-flujos.png deleted file mode 100644 index a595f7a..0000000 Binary files a/cashflows/images/sesion-3-flujos.png and /dev/null differ diff --git a/cashflows/images/simple-interest.png b/cashflows/images/simple-interest.png deleted file mode 100644 index fd0c1ab..0000000 Binary files a/cashflows/images/simple-interest.png and /dev/null differ diff --git a/cashflows/images/tasa-nominal-efectiva.png b/cashflows/images/tasa-nominal-efectiva.png deleted file mode 100644 index 09723fa..0000000 Binary files a/cashflows/images/tasa-nominal-efectiva.png and /dev/null differ diff --git a/cashflows/images/triangular.png b/cashflows/images/triangular.png deleted file mode 100644 index 9149438..0000000 Binary files a/cashflows/images/triangular.png and /dev/null differ diff --git a/cashflows/images/tvm.png b/cashflows/images/tvm.png deleted file mode 100644 index 0b74253..0000000 Binary files a/cashflows/images/tvm.png and /dev/null differ diff --git a/cashflows/images/tvmm.png b/cashflows/images/tvmm.png deleted file mode 100644 index 7ece77a..0000000 Binary files a/cashflows/images/tvmm.png and /dev/null differ diff --git a/cashflows/images/usomont.png b/cashflows/images/usomont.png deleted file mode 100644 index 14e2078..0000000 Binary files a/cashflows/images/usomont.png and /dev/null differ diff --git a/cashflows/images/valoracion-bono.png b/cashflows/images/valoracion-bono.png deleted file mode 100644 index 73320ca..0000000 Binary files a/cashflows/images/valoracion-bono.png and /dev/null differ diff --git a/cashflows/images/var.png b/cashflows/images/var.png deleted file mode 100644 index 3d8de5e..0000000 Binary files a/cashflows/images/var.png and /dev/null differ diff --git a/cashflows/images/vpn.png b/cashflows/images/vpn.png deleted file mode 100644 index 8124bcc..0000000 Binary files a/cashflows/images/vpn.png and /dev/null differ diff --git a/cashflows/images/wacc-explain.png b/cashflows/images/wacc-explain.png deleted file mode 100644 index adb4976..0000000 Binary files a/cashflows/images/wacc-explain.png and /dev/null differ diff --git a/cashflows/inflation.py b/cashflows/inflation.py old mode 100644 new mode 100755 index 3369d4d..614d753 --- a/cashflows/inflation.py +++ b/cashflows/inflation.py @@ -8,12 +8,11 @@ `curr2const` computes the inverse transformation. """ -import pandas as pd -# cashflows. -from cashflows.timeseries import cashflow, interest_rate, verify_period_range -from cashflows.rate import to_compound_factor, to_discount_factor + +from cashflows.gtimeseries import TimeSeries, cashflow, interest_rate, verify_eq_time_range, _timeid2index from cashflows.common import _vars2list +from cashflows.rate import to_compound_factor, to_discount_factor def const2curr(cflo, inflation, base_date=0): @@ -21,124 +20,69 @@ def const2curr(cflo, inflation, base_date=0): of the time `base_date`. Args: - cflo (TimeSeries): Generic cashflow. + cflo (TimeSeries): A cashflow. inflation (TimeSeries): Inflation rate per compounding period. - base_date (int, str): base date. + base_date (int, tuple): base date. Returns: - A cashflow in current money (pandas.Series) + A cashflow in current money (TimeSeries) **Examples.** - >>> cflo=cashflow(const_value=[100] * 5, start='2000', freq='A') - >>> inflation=interest_rate(const_value=[10, 10, 20, 20, 20], start='2000', freq='A') - >>> const2curr(cflo=cflo, inflation=inflation) # doctest: +NORMALIZE_WHITESPACE - 2000 100.00 - 2001 110.00 - 2002 132.00 - 2003 158.40 - 2004 190.08 - Freq: A-DEC, dtype: float64 - - >>> const2curr(cflo=cflo, inflation=inflation, base_date=0) # doctest: +NORMALIZE_WHITESPACE - 2000 100.00 - 2001 110.00 - 2002 132.00 - 2003 158.40 - 2004 190.08 - Freq: A-DEC, dtype: float64 - - >>> const2curr(cflo=cflo, inflation=inflation, base_date='2000') # doctest: +NORMALIZE_WHITESPACE - 2000 100.00 - 2001 110.00 - 2002 132.00 - 2003 158.40 - 2004 190.08 - Freq: A-DEC, dtype: float64 - - >>> const2curr(cflo=cflo, inflation=inflation, base_date=4) # doctest: +NORMALIZE_WHITESPACE - 2000 52.609428 - 2001 57.870370 - 2002 69.444444 - 2003 83.333333 - 2004 100.000000 - Freq: A-DEC, dtype: float64 - - >>> const2curr(cflo=cflo, inflation=inflation, base_date='2004') # doctest: +NORMALIZE_WHITESPACE - 2000 52.609428 - 2001 57.870370 - 2002 69.444444 - 2003 83.333333 - 2004 100.000000 - Freq: A-DEC, dtype: float64 + >>> const2curr(cflo=cashflow(const_value=[100] * 5), + ... inflation=interest_rate(const_value=[10, 10, 20, 20, 20])) # doctest: +NORMALIZE_WHITESPACE + Time Series: + Start = (0,) + End = (4,) + pyr = 1 + Data = (0,) 100.00 + (1,) 110.00 + (2,) 132.00 + (3,) 158.40 + (4,) 190.08 + + + >>> const2curr(cflo=cashflow(const_value=[100] * 5), + ... inflation=interest_rate(const_value=[10, 10, 20, 20, 20]), base_date=4) # doctest: +NORMALIZE_WHITESPACE + Time Series: + Start = (0,) + End = (4,) + pyr = 1 + Data = (0,) 52.61 + (1,) 57.87 + (2,) 69.44 + (3,) 83.33 + (4,) 100.00 + + + >>> const2curr(cflo=cashflow(const_value=[100] * 8, pyr=4), + ... inflation=interest_rate(const_value=1, nper=8, pyr=4)) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 100.00 101.00 102.01 103.03 + 1 104.06 105.10 106.15 107.21 """ - if not isinstance(cflo, pd.Series): - raise TypeError("cflo must be a TimeSeries object") - if not isinstance(inflation, pd.Series): - raise TypeError("inflation must be a TimeSeries object") - verify_period_range([cflo, inflation]) - factor = to_compound_factor(prate=inflation, base_date=base_date) - result = cflo.copy() - for time, _ in enumerate(result): - result[time] *= factor[time] - return result - - - # if not isinstance(cflo, pd.Series): - # raise TypeError("cflo must be a TimeSeries object") - # if not isinstance(inflation, pd.Series): - # raise TypeError("inflation must be a TimeSeries object") - # if not isinstance(base_date, list): - # base_date = [base_date] - # verify_period_range([cflo, inflation]) - # index = cflo.index.copy().to_series().astype(str) - # retval = None - # for xbase_date in base_date: - # factor = to_compound_factor(prate=inflation, base_date=xbase_date) - # result = cflo.copy() - # for time, _ in enumerate(result): - # result[time] *= factor[time] - # if isinstance(xbase_date, str): - # current_date = xbase_date - # else: - # current_date = index[xbase_date] - # if retval is None: - # retval = {current_date:result} - # else: - # retval[current_date] = result - # retval = pd.DataFrame(retval) - # if len(retval.columns) == 1: - # return retval[retval.columns[0]] - # return retval - - - - ## - ## version inicial - ## - - # params = _vars2list([cflo, inflation, base_date]) - # cflo = params[0] - # inflation = params[1] - # base_date = params[2] - # retval = [] - # for xcflo, xinflation, xbase_date in zip(cflo, inflation, base_date): - # if not isinstance(xcflo, pd.Series): - # raise TypeError("cflo must be a TimeSeries object") - # if not isinstance(xinflation, pd.Series): - # raise TypeError("inflation must be a TimeSeries object") - # verify_period_range([xcflo, xinflation]) - # factor = to_compound_factor(prate=xinflation, base_date=xbase_date) - # result = xcflo.copy() - # for time, _ in enumerate(result): - # result[time] *= factor[time] - # retval.append(result) - # if len(retval) == 1: - # return retval[0] - # return retval + params = _vars2list([cflo, inflation, base_date]) + cflo = params[0] + inflation = params[1] + base_date = params[2] + retval = [] + for xcflo, xinflation, xbase_date in zip(cflo, inflation, base_date): + if not isinstance(xcflo, TimeSeries): + raise TypeError("cflo must be a TimeSeries object") + if not isinstance(xinflation, TimeSeries): + raise TypeError("inflation must be a TimeSeries object") + verify_eq_time_range(xcflo, xinflation) + factor = to_compound_factor(prate=xinflation, base_date=xbase_date) + result = xcflo.copy() + for time, _ in enumerate(result): + result[time] *= factor[time] + retval.append(result) + if len(retval) == 1: + return retval[0] + return retval @@ -147,106 +91,64 @@ def curr2const(cflo, inflation, base_date=0): the date `base_date`. Args: - cflo (TimeSeries): Generic cashflow. + cflo (list, Cashflow): A cashflow. inflation_rate (float, Rate): Inflation rate per compounding period. base_date (int): base time.. Returns: A cashflow in constant dollars - >>> cflo = cashflow(const_value=[100] * 5, start='2015', freq='A') - >>> inflation = interest_rate(const_value=[10, 10, 20, 20, 20], start='2015', freq='A') - >>> curr2const(cflo=cflo, inflation=inflation) # doctest: +NORMALIZE_WHITESPACE - 2015 100.000000 - 2016 90.909091 - 2017 75.757576 - 2018 63.131313 - 2019 52.609428 - Freq: A-DEC, dtype: float64 - - >>> curr2const(cflo=cflo, inflation=inflation, base_date=4) # doctest: +NORMALIZE_WHITESPACE - 2015 190.08 - 2016 172.80 - 2017 144.00 - 2018 120.00 - 2019 100.00 - Freq: A-DEC, dtype: float64 - - >>> curr2const(cflo=cflo, inflation=inflation, base_date='2017') # doctest: +NORMALIZE_WHITESPACE - 2015 132.000000 - 2016 120.000000 - 2017 100.000000 - 2018 83.333333 - 2019 69.444444 - Freq: A-DEC, dtype: float64 + >>> curr2const(cflo=cashflow(const_value=[100] * 5), + ... inflation=interest_rate(const_value=[10, 10, 20, 20, 20])) # doctest: +NORMALIZE_WHITESPACE + Time Series: + Start = (0,) + End = (4,) + pyr = 1 + Data = (0,) 100.00 + (1,) 90.91 + (2,) 75.76 + (3,) 63.13 + (4,) 52.61 + + + >>> curr2const(cflo=cashflow(const_value=[100] * 5), + ... inflation=interest_rate(const_value=[10, 10, 20, 20, 20]), base_date=4) # doctest: +NORMALIZE_WHITESPACE + Time Series: + Start = (0,) + End = (4,) + pyr = 1 + Data = (0,) 190.08 + (1,) 172.80 + (2,) 144.00 + (3,) 120.00 + (4,) 100.00 + + >>> curr2const(cflo=cashflow(const_value=[100] * 8, pyr=4), + ... inflation=interest_rate(const_value=1, nper=8, pyr=4)) # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 100.00 99.01 98.03 97.06 + 1 96.10 95.15 94.20 93.27 """ - if not isinstance(cflo, pd.Series): - raise TypeError("cflo must be a TimeSeries object") - if not isinstance(inflation, pd.Series): - raise TypeError("inflation must be a TimeSeries object") - verify_period_range([cflo, inflation]) - factor = to_discount_factor(prate=inflation, base_date=base_date) - result = cflo.copy() - for time, _ in enumerate(result): - result[time] *= factor[time] - return result - - - # if not isinstance(cflo, pd.Series): - # raise TypeError("cflo must be a TimeSeries object") - # if not isinstance(inflation, pd.Series): - # raise TypeError("inflation must be a TimeSeries object") - # if not isinstance(base_date, list): - # base_date = [base_date] - # verify_period_range([cflo, inflation]) - # index = cflo.index.copy().to_series().astype(str) - # retval = None - # for xbase_date in base_date: - # factor = to_discount_factor(prate=inflation, base_date=xbase_date) - # result = cflo.copy() - # for time, _ in enumerate(result): - # result[time] *= factor[time] - # if isinstance(xbase_date, str): - # current_date = xbase_date - # else: - # current_date = index[xbase_date] - # if retval is None: - # retval = {current_date:result} - # else: - # retval[current_date] = result - # retval = pd.DataFrame(retval) - # if len(retval.columns) == 1: - # return retval[retval.columns[0]] - # return retval - - - - ## - ## version inicial - ## - - - - # params = _vars2list([cflo, inflation, base_date]) - # cflo = params[0] - # inflation = params[1] - # base_date = params[2] - # retval = [] - # for xcflo, xinflation, xbase_date in zip(cflo, inflation, base_date): - # if not isinstance(xcflo, pd.Series): - # raise TypeError("cflo must be a TimeSeries object") - # if not isinstance(xinflation, pd.Series): - # raise TypeError("inflation must be a TimeSeries object") - # verify_period_range([xcflo, xinflation]) - # factor = to_discount_factor(prate=xinflation, base_date=xbase_date) - # result = xcflo.copy() - # for time, _ in enumerate(result): - # result[time] *= factor[time] - # retval.append(result) - # if len(retval) == 1: - # return retval[0] - # return retval + params = _vars2list([cflo, inflation, base_date]) + cflo = params[0] + inflation = params[1] + base_date = params[2] + retval = [] + for xcflo, xinflation, xbase_date in zip(cflo, inflation, base_date): + if not isinstance(xcflo, TimeSeries): + raise TypeError("cflo must be a TimeSeries object") + if not isinstance(xinflation, TimeSeries): + raise TypeError("inflation must be a TimeSeries object") + verify_eq_time_range(xcflo, xinflation) + factor = to_discount_factor(prate=xinflation, base_date=xbase_date) + result = xcflo.copy() + for time, _ in enumerate(result): + result[time] *= factor[time] + retval.append(result) + if len(retval) == 1: + return retval[0] + return retval diff --git a/cashflows/loan.py b/cashflows/loan.py old mode 100644 new mode 100755 index 3b20180..c064df3 --- a/cashflows/loan.py +++ b/cashflows/loan.py @@ -2,7 +2,7 @@ Loan analysis ============================================================================== -Computes the amorization schedule for the following types of loans: +Computes the amorization schedule for the followint types of loans: * ``fixed_rate_loan``: In this loan, the interest rate is fixed and the total payments are equal during the life of the loan. @@ -19,473 +19,127 @@ """ -import numpy as np -import pandas as pd -# cashflows. from cashflows.analysis import timevalue, irr +from cashflows.gtimeseries import TimeSeries, cashflow, interest_rate, verify_eq_time_range +from cashflows.gtimeseries import repr_table from cashflows.tvmm import pvpmt -from cashflows.timeseries import * -from cashflows.common import getpyr ## ## base class for computations ## -class Loan(pd.DataFrame): - - def __init__(self, life, amount, grace, nrate, dispoints=0, orgpoints=0, - data=None, index=None, columns=None, dtype=None, copy=False): - super().__init__(data=data, index=index, columns=columns, dtype=dtype, copy=copy) - self.life = life - self.amount = amount - self.grace = grace - self.nrate = nrate - self.dispoints = dispoints - self.orgpoints = orgpoints - - def tocashflow(self, tax_rate=None): - cflo = self.nrate.copy() - cflo[:] = 0 - if tax_rate is None: - tax_rate = cflo.copy() - tax_rate[:] = 0 - # - # descuenta todos los pagos adicionales - # - cflo[0] += self.amount - cflo[0] -= self.amount * self.orgpoints / 100 - cflo[0] -= self.amount * self.dispoints / 100 - cflo[0] += self.amount * self.dispoints / 100 * tax_rate[0] / 100 - cflo -= self.Ppal_Payment - cflo -= self.Int_Payment - cflo += self.Int_Payment * tax_rate / 100 - return cflo - - def true_rate(self, tax_rate=None): - cflo = self.tocashflow(tax_rate) - return irr(cflo) * getpyr(cflo) - - - def __str__(self): - - str = [] - str.append("Amount: {:.2f}".format(self.amount)) - str.append("Total interest: {:.2f}".format(sum(self.Int_Payment))) - str.append("Total payment: {:.2f}".format(sum(self.Tot_Payment))) - str.append("Discount points: {:.2f}".format(self.dispoints)) - str.append("Origination points: {:.2f}".format(self.orgpoints)) - str = '\n'.join(str) + '\n\n' - str = str + super().__str__() - return str - - - -def fixed_ppal_loan(amount, nrate, grace=0, dispoints=0, orgpoints=0, - prepmt=None, balloonpmt=None): - """Loan with fixed principal payment. - - Args: - amount (float): Loan amount. - nrate (float, TimeSeries): nominal interest rate per year. - grace (int): number of grace periiods without paying principal. - dispoints (float): Discount points of the loan. - orgpoints (float): Origination points of the loan. - prepmt (TimeSeries): generic cashflow representing prepayments. - balloonpmt (TimeSeries): generic cashflow representing balloon payments. - - - Returns: - A object of the class ``Loan``. - - >>> nrate = interest_rate(const_value=[10]*11, start='2018Q1', freq='Q') - >>> tax_rate = interest_rate(const_value=[35]*11, start='2018Q1', freq='Q') - >>> fixed_ppal_loan(amount=1000, nrate=nrate, grace=0, dispoints=0, orgpoints=0, - ... prepmt=None, balloonpmt=None) # doctest: +NORMALIZE_WHITESPACE - Amount: 1000.00 - Total interest: 137.50 - Total payment: 1137.50 - Discount points: 0.00 - Origination points: 0.00 - - Beg_Ppal_Amount Nom_Rate Tot_Payment Int_Payment Ppal_Payment \\ - 2018Q1 0.0 10.0 0.0 0.0 0.0 - 2018Q2 1000.0 10.0 125.0 25.0 100.0 - 2018Q3 900.0 10.0 122.5 22.5 100.0 - 2018Q4 800.0 10.0 120.0 20.0 100.0 - 2019Q1 700.0 10.0 117.5 17.5 100.0 - 2019Q2 600.0 10.0 115.0 15.0 100.0 - 2019Q3 500.0 10.0 112.5 12.5 100.0 - 2019Q4 400.0 10.0 110.0 10.0 100.0 - 2020Q1 300.0 10.0 107.5 7.5 100.0 - 2020Q2 200.0 10.0 105.0 5.0 100.0 - 2020Q3 100.0 10.0 102.5 2.5 100.0 - - End_Ppal_Amount - 2018Q1 1000.0 - 2018Q2 900.0 - 2018Q3 800.0 - 2018Q4 700.0 - 2019Q1 600.0 - 2019Q2 500.0 - 2019Q3 400.0 - 2019Q4 300.0 - 2020Q1 200.0 - 2020Q2 100.0 - 2020Q3 0.0 - - >>> fixed_ppal_loan(amount=1000, nrate=nrate, grace=2, dispoints=0, orgpoints=0, - ... prepmt=None, balloonpmt=None) # doctest: +NORMALIZE_WHITESPACE - Amount: 1000.00 - Total interest: 162.50 - Total payment: 1162.50 - Discount points: 0.00 - Origination points: 0.00 - - Beg_Ppal_Amount Nom_Rate Tot_Payment Int_Payment Ppal_Payment \\ - 2018Q1 0.0 10.0 0.000 0.000 0.0 - 2018Q2 1000.0 10.0 25.000 25.000 0.0 - 2018Q3 1000.0 10.0 25.000 25.000 0.0 - 2018Q4 1000.0 10.0 150.000 25.000 125.0 - 2019Q1 875.0 10.0 146.875 21.875 125.0 - 2019Q2 750.0 10.0 143.750 18.750 125.0 - 2019Q3 625.0 10.0 140.625 15.625 125.0 - 2019Q4 500.0 10.0 137.500 12.500 125.0 - 2020Q1 375.0 10.0 134.375 9.375 125.0 - 2020Q2 250.0 10.0 131.250 6.250 125.0 - 2020Q3 125.0 10.0 128.125 3.125 125.0 - - End_Ppal_Amount - 2018Q1 1000.0 - 2018Q2 1000.0 - 2018Q3 1000.0 - 2018Q4 875.0 - 2019Q1 750.0 - 2019Q2 625.0 - 2019Q3 500.0 - 2019Q4 375.0 - 2020Q1 250.0 - 2020Q2 125.0 - 2020Q3 0.0 - - >>> pmt = cashflow(const_value=[0]*11, start='2018Q1', freq='Q') - >>> pmt['2019Q4'] = 200 - >>> fixed_ppal_loan(amount=1000, nrate=nrate, grace=2, dispoints=0, orgpoints=0, - ... prepmt=pmt, balloonpmt=None) # doctest: +NORMALIZE_WHITESPACE - Amount: 1000.00 - Total interest: 149.38 - Total payment: 1149.38 - Discount points: 0.00 - Origination points: 0.00 - - Beg_Ppal_Amount Nom_Rate Tot_Payment Int_Payment Ppal_Payment \\ - 2018Q1 0.0 10.0 0.000 0.000 0.0 - 2018Q2 1000.0 10.0 25.000 25.000 0.0 - 2018Q3 1000.0 10.0 25.000 25.000 0.0 - 2018Q4 1000.0 10.0 150.000 25.000 125.0 - 2019Q1 875.0 10.0 146.875 21.875 125.0 - 2019Q2 750.0 10.0 143.750 18.750 125.0 - 2019Q3 625.0 10.0 140.625 15.625 125.0 - 2019Q4 500.0 10.0 337.500 12.500 325.0 - 2020Q1 175.0 10.0 129.375 4.375 125.0 - 2020Q2 50.0 10.0 51.250 1.250 50.0 - 2020Q3 0.0 10.0 0.000 0.000 0.0 - - End_Ppal_Amount - 2018Q1 1000.0 - 2018Q2 1000.0 - 2018Q3 1000.0 - 2018Q4 875.0 - 2019Q1 750.0 - 2019Q2 625.0 - 2019Q3 500.0 - 2019Q4 175.0 - 2020Q1 50.0 - 2020Q2 0.0 - 2020Q3 0.0 - - >>> fixed_ppal_loan(amount=1000, nrate=nrate, grace=2, dispoints=0, orgpoints=0, - ... prepmt=None, balloonpmt=pmt) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS - Amount: 1000.00 - Total interest: 165.00 - Total payment: 1165.00 - Discount points: 0.00 - Origination points: 0.00 - - Beg_Ppal_Amount Nom_Rate Tot_Payment Int_Payment Ppal_Payment \\ - 2018Q1 0.0 10.0 0.0 0.0 0.0 - 2018Q2 1000.0 10.0 25.0 25.0 0.0 - 2018Q3 1000.0 10.0 25.0 25.0 0.0 - 2018Q4 1000.0 10.0 125.0 25.0 100.0 - 2019Q1 900.0 10.0 122.5 22.5 100.0 - 2019Q2 800.0 10.0 120.0 20.0 100.0 - 2019Q3 700.0 10.0 117.5 17.5 100.0 - 2019Q4 600.0 10.0 315.0 15.0 300.0 - 2020Q1 300.0 10.0 107.5 7.5 100.0 - 2020Q2 200.0 10.0 105.0 5.0 100.0 - 2020Q3 100.0 10.0 102.5 2.5 100.0 - - End_Ppal_Amount - 2018Q1 1000.0 - 2018Q2 1000.0 - 2018Q3 1000.0 - 2018Q4 900.0 - 2019Q1 800.0 - 2019Q2 700.0 - 2019Q3 600.0 - 2019Q4 300.0 - 2020Q1 200.0 - 2020Q2 100.0 - 2020Q3 0.0 - - - >>> x = fixed_ppal_loan(amount=1000, nrate=nrate, grace=2, dispoints=0, orgpoints=0, - ... prepmt=None, balloonpmt=pmt) - >>> x.true_rate() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS - 10.00... - - >>> x.true_rate(tax_rate) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS - 6.50... - - >>> x.tocashflow() - 2018Q1 1000.0 - 2018Q2 -25.0 - 2018Q3 -25.0 - 2018Q4 -125.0 - 2019Q1 -122.5 - 2019Q2 -120.0 - 2019Q3 -117.5 - 2019Q4 -315.0 - 2020Q1 -107.5 - 2020Q2 -105.0 - 2020Q3 -102.5 - Freq: Q-DEC, dtype: float64 - - >>> x.tocashflow(tax_rate) - 2018Q1 1000.000 - 2018Q2 -16.250 - 2018Q3 -16.250 - 2018Q4 -116.250 - 2019Q1 -114.625 - 2019Q2 -113.000 - 2019Q3 -111.375 - 2019Q4 -309.750 - 2020Q1 -104.875 - 2020Q2 -103.250 - 2020Q3 -101.625 - Freq: Q-DEC, dtype: float64 - - - >>> x = fixed_ppal_loan(amount=1000, nrate=nrate, grace=2, dispoints=0, orgpoints=10, - ... prepmt=None, balloonpmt=pmt) - >>> x # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS - Amount: 1000.00 - Total interest: 165.00 - Total payment: 1265.00 - Discount points: 0.00 - Origination points: 10.00 - - Beg_Ppal_Amount Nom_Rate Tot_Payment Int_Payment Ppal_Payment \\ - 2018Q1 0.0 10.0 100.0 0.0 0.0 - 2018Q2 1000.0 10.0 25.0 25.0 0.0 - 2018Q3 1000.0 10.0 25.0 25.0 0.0 - 2018Q4 1000.0 10.0 125.0 25.0 100.0 - 2019Q1 900.0 10.0 122.5 22.5 100.0 - 2019Q2 800.0 10.0 120.0 20.0 100.0 - 2019Q3 700.0 10.0 117.5 17.5 100.0 - 2019Q4 600.0 10.0 315.0 15.0 300.0 - 2020Q1 300.0 10.0 107.5 7.5 100.0 - 2020Q2 200.0 10.0 105.0 5.0 100.0 - 2020Q3 100.0 10.0 102.5 2.5 100.0 - - End_Ppal_Amount - 2018Q1 1000.0 - 2018Q2 1000.0 - 2018Q3 1000.0 - 2018Q4 900.0 - 2019Q1 800.0 - 2019Q2 700.0 - 2019Q3 600.0 - 2019Q4 300.0 - 2020Q1 200.0 - 2020Q2 100.0 - 2020Q3 0.0 - - >>> x.true_rate() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS - 17.1725... - - >>> x.tocashflow() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS - 2018Q1 900.0 - 2018Q2 -25.0 - 2018Q3 -25.0 - 2018Q4 -125.0 - 2019Q1 -122.5 - 2019Q2 -120.0 - 2019Q3 -117.5 - 2019Q4 -315.0 - 2020Q1 -107.5 - 2020Q2 -105.0 - 2020Q3 -102.5 - Freq: Q-DEC, dtype: float64 - - >>> x.true_rate(tax_rate) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS - 13.4232... - - >>> x.tocashflow(tax_rate) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS - 2018Q1 900.000 - 2018Q2 -16.250 - 2018Q3 -16.250 - 2018Q4 -116.250 - 2019Q1 -114.625 - 2019Q2 -113.000 - 2019Q3 -111.375 - 2019Q4 -309.750 - 2020Q1 -104.875 - 2020Q2 -103.250 - 2020Q3 -101.625 - Freq: Q-DEC, dtype: float64 - - - +class Loan(): + """ + Class for representing loans """ - #pylint: disable-msg=too-many-arguments - if not isinstance(nrate, pd.Series): - TypeError('nrate must be a pandas.Series object.') + # pylint: disable=too-many-instance-attributes - if prepmt is None: - prepmt = nrate.copy() - prepmt[:] = 0 - else: - verify_period_range([nrate, prepmt]) + def __init__(self): + """ + """ + self.life = self.intpmt = self.endppalbal = self.begppalbal = None + self.amount = self.grace = self.totpmt = None - if balloonpmt is None: - balloonpmt = nrate.copy() - balloonpmt[:] = 0 - else: - verify_period_range([nrate, balloonpmt]) - # present value of the balloon payments - balloonpv = sum(balloonpmt) - life = len(nrate) - grace - 1 + def to_cashflow(self, tax_rate=0): + """Converts the loan to the equivalent cashflow. - begppalbal = nrate.copy() - intpmt = nrate.copy() - ppalpmt = nrate.copy() - totpmt = nrate.copy() - endppalbal = nrate.copy() + For the conversion, origination points are considered as exogenous costs + and they are not taking in to account in the computation. In oposition, + discount points are considered as prepaid interest and included in the + cashflow. - begppalbal[:] = 0 - intpmt[:] = 0 - ppalpmt[:] = 0 - totpmt[:] = 0 - endppalbal[:] = 0 + When tax_rate is different from zero, tax benefits are considered.""" - pmt = (amount - balloonpv) / life # periodic ppal payment - pyr = getpyr(nrate) - # balance calculation - for time in range(grace + life + 1): + if isinstance(tax_rate, (int, float)): + tax_rate = interest_rate(const_value = [tax_rate] * (self.life + self.grace + 1)) - if time == 0: - begppalbal[time] = 0 - endppalbal[time] = amount - prepmt[time] - totpmt[time] = amount * (dispoints + orgpoints) / 100 - ### intpmt[time] = amount * dispoints / 100 - else: - begppalbal[time] = endppalbal[time - 1] - intpmt[time] = begppalbal[time] * nrate[time] / pyr / float(100) - if time <= grace: - ppalpmt[time] = prepmt[time] + balloonpmt[time] - else: - ppalpmt[time] = pmt + prepmt[time] + balloonpmt[time] - totpmt[time] = intpmt[time] + ppalpmt[time] - endppalbal[time] = begppalbal[time] - ppalpmt[time] - - if endppalbal[time] < 0: - totpmt[time] = begppalbal[time] + intpmt[time] - ppalpmt[time] = begppalbal[time] - endppalbal[time] = begppalbal[time] - ppalpmt[time] - prepmt[time] = 0 - pmt = 0 + cflo = cashflow(const_value= [0] * (self.grace + self.life + 1)) + ## + ## payments per period + ## + for time in range(self.grace + self.life + 1): + if time == 0: + cflo[0] = self.amount + cflo[time] += -self.totpmt[time] + self.intpmt[time] * tax_rate[time] / 100 - data = {'Beg_Ppal_Amount':begppalbal} - result = Loan(life=life, amount=amount, grace=grace, nrate=nrate, - dispoints=dispoints, orgpoints=orgpoints, - data=data) - result['Nom_Rate'] = nrate - result['Tot_Payment'] = totpmt - result['Int_Payment'] = intpmt - result['Ppal_Payment'] = ppalpmt - result['End_Ppal_Amount'] = endppalbal - return result + return cflo + def true_rate(self, tax_rate=0): + """Computes the true interest rate for the loan. -def bullet_loan(amount, nrate, dispoints=0, orgpoints=0, prepmt=None): - """ - In this type of loan, the principal is payed at the end for the life of the - loan. Periodic payments correspond only to interests. + For the computation, the loan is converted to the equivalent cashflow, + taking in to account the following aspects: - Args: - amount (float): Loan amount. - nrate (float, TimeSeries): nominal interest rate per year. - dispoints (float): Discount points of the loan. - orgpoints (float): Origination points of the loan. - prepmt (TimeSeries): generic cashflow representing prepayments. + * Origination points are considered as non deducible costs and they \ + are ignored in the computation. - Returns: - A object of the class ``Loan``. + * Discount points are prepaid interest and they are considered as \ + deducibles in the computation. - >>> nrate = interest_rate(const_value=[10]*11, start='2018Q1', freq='Q') - >>> bullet_loan(amount=1000, nrate=nrate, dispoints=0, orgpoints=0, prepmt=None) # doctest: +NORMALIZE_WHITESPACE - Amount: 1000.00 - Total interest: 250.00 - Total payment: 1250.00 - Discount points: 0.00 - Origination points: 0.00 - - Beg_Ppal_Amount Nom_Rate Tot_Payment Int_Payment Ppal_Payment \\ - 2018Q1 0.0 10.0 0.0 0.0 0.0 - 2018Q2 1000.0 10.0 25.0 25.0 0.0 - 2018Q3 1000.0 10.0 25.0 25.0 0.0 - 2018Q4 1000.0 10.0 25.0 25.0 0.0 - 2019Q1 1000.0 10.0 25.0 25.0 0.0 - 2019Q2 1000.0 10.0 25.0 25.0 0.0 - 2019Q3 1000.0 10.0 25.0 25.0 0.0 - 2019Q4 1000.0 10.0 25.0 25.0 0.0 - 2020Q1 1000.0 10.0 25.0 25.0 0.0 - 2020Q2 1000.0 10.0 25.0 25.0 0.0 - 2020Q3 1000.0 10.0 1025.0 25.0 1000.0 - - End_Ppal_Amount - 2018Q1 1000.0 - 2018Q2 1000.0 - 2018Q3 1000.0 - 2018Q4 1000.0 - 2019Q1 1000.0 - 2019Q2 1000.0 - 2019Q3 1000.0 - 2019Q4 1000.0 - 2020Q1 1000.0 - 2020Q2 1000.0 - 2020Q3 0.0 + * When `tax_rate` is different from zero, the After-Tax true interest \ + rate is calculated. This is, only the (1 - `tax_rate`) of paid interests \ + (including discount points) are used in the computation. - """ - if not isinstance(nrate, pd.Series): - raise TypeError("nrate must be a pandas.Series object") + """ + return irr(self.to_cashflow(tax_rate)) - balloonpmt = nrate.copy() - balloonpmt[:] = 0 - balloonpmt[-1] = amount - return fixed_ppal_loan(amount=amount, nrate=nrate, grace=0, dispoints=dispoints, - orgpoints=orgpoints, prepmt=prepmt, balloonpmt=balloonpmt) + def __repr__(self): + return repr_table(cols=[self.begppalbal, + self.nrate, + self.totpmt, + self.intpmt, + self.ppalpmt, + self.endppalbal], + header=[['Beg.', 'Per.', 'Total', 'Int.', 'Ppal', 'Ending'], + ['Ppal', 'Rate', 'Pmt', 'Pmt', 'Pmt', 'Ppal']]) -def fixed_rate_loan(amount, nrate, life, start, freq='A', grace=0, - dispoints=0, orgpoints=0, prepmt=None, balloonpmt=None): +# def x__repr__(self): +# txt = [''] +# txt.append(' t Beginning Periodic Total Interest Principal Ending') +# txt.append(' Principal Rate Payment Payment Payment Principal') +# txt.append('--------------------------------------------------------------------------') +# +# for time in range(self.grace + self.life + 1): +# fmt = ' {:3d} {:12.2f} {:12.2f} {:12.2f} {:12.2f} {:12.2f}' +# txt.append(fmt.format(time, +# self.begppalbal[time], +# self.totpmt[time], +# self.intpmt[time], +# self.ppalpmt[time], +# self.endppalbal[time])) +# return '\n'.join(txt) + + def interest(self): + """Returns the interest paid as a Cashflow object.""" + return Cashflow(constValue=self.intpmt.tolist()) + + def begbal(self): + """Returns the balance at the begining of each period as + a Cashflow object.""" + return Cashflow(constValue=self.begppalbal.tolist()) + + def endbal(self): + """Returns the balance at the ending of each period as + a Cashflow object.""" + return Cashflow(constValue=self.endppalbal.tolist()) + + def ppalpmt(self): + """Returns the principal payment for each period as + a Cashflow object.""" + return Cashflow(constValue=self.ppalpmt.tolist()) + + + +def fixed_rate_loan(amount, nrate, life, start, pyr=1, grace=0, dispoints=0, + orgpoints=0, prepmt=None, balloonpmt=None): """Fixed rate loan. Args: @@ -503,60 +157,39 @@ def fixed_rate_loan(amount, nrate, life, start, freq='A', grace=0, Returns: A object of the class ``Loan``. - >>> pmt = cashflow(const_value=0, start='2016Q1', periods=11, freq='Q') - >>> pmt['2017Q4'] = 200 - >>> fixed_rate_loan(amount=1000, nrate=10, life=10, start='2016Q1', freq='Q', - ... grace=0, dispoints=0, - ... orgpoints=0, prepmt=pmt, balloonpmt=None) # doctest: +NORMALIZE_WHITESPACE - Amount: 1000.00 - Total interest: 129.68 - Total payment: 1129.68 - Discount points: 0.00 - Origination points: 0.00 - - Beg_Ppal_Amount Nom_Rate Tot_Payment Int_Payment Ppal_Payment \\ - 2016Q1 1000.000000 10.0 0.000000 0.000000 0.000000 - 2016Q2 1000.000000 10.0 114.258763 25.000000 89.258763 - 2016Q3 910.741237 10.0 114.258763 22.768531 91.490232 - 2016Q4 819.251005 10.0 114.258763 20.481275 93.777488 - 2017Q1 725.473517 10.0 114.258763 18.136838 96.121925 - 2017Q2 629.351591 10.0 114.258763 15.733790 98.524973 - 2017Q3 530.826618 10.0 114.258763 13.270665 100.988098 - 2017Q4 429.838520 10.0 314.258763 10.745963 303.512800 - 2018Q1 126.325720 10.0 114.258763 3.158143 111.100620 - 2018Q2 15.225100 10.0 15.605727 0.380627 15.225100 - 2018Q3 0.000000 10.0 0.000000 0.000000 0.000000 - - End_Ppal_Amount - 2016Q1 1000.000000 - 2016Q2 910.741237 - 2016Q3 819.251005 - 2016Q4 725.473517 - 2017Q1 629.351591 - 2017Q2 530.826618 - 2017Q3 429.838520 - 2017Q4 126.325720 - 2018Q1 15.225100 - 2018Q2 0.000000 - 2018Q3 0.000000 + >>> pmt = cashflow(const_value=0, nper = 11, pyr=4, spec=((1, 3), 200)) + >>> fixed_rate_loan(amount=1000, nrate=10, life=10, start=None, pyr=4, grace=0, dispoints=0, + ... orgpoints=0, prepmt=pmt, balloonpmt=None) + t Beg. Per. Total Int. Ppal Ending + Ppal Rate Pmt Pmt Pmt Ppal + ------------------------------------------------------ + (0, 0) 1000.00 10.00 0.00 0.00 0.00 1000.00 + (0, 1) 1000.00 10.00 114.26 25.00 89.26 910.74 + (0, 2) 910.74 10.00 114.26 22.77 91.49 819.25 + (0, 3) 819.25 10.00 114.26 20.48 93.78 725.47 + (1, 0) 725.47 10.00 114.26 18.14 96.12 629.35 + (1, 1) 629.35 10.00 114.26 15.73 98.52 530.83 + (1, 2) 530.83 10.00 114.26 13.27 100.99 429.84 + (1, 3) 429.84 10.00 314.26 10.75 303.51 126.33 + (2, 0) 126.33 10.00 114.26 3.16 111.10 15.23 + (2, 1) 15.23 10.00 15.61 0.38 15.23 0.00 + (2, 2) 0.00 10.00 0.00 0.00 0.00 0.00 """ - if not isinstance(float(nrate), float): TypeError('nrate must be a float.') - nrate = interest_rate(const_value=nrate, start=start, periods=life+grace+1, freq=freq) + nrate = interest_rate(const_value=nrate, start=start, nper=life+grace+1, pyr=pyr) if prepmt is None: - prepmt = cashflow(const_value=0, start=start, periods=len(nrate), freq=freq) + prepmt = cashflow(start=nrate.start, end=nrate.end, pyr=nrate.pyr) else: - verify_period_range([nrate, prepmt]) + verify_eq_time_range(nrate, prepmt) if balloonpmt is None: - balloonpmt = nrate.copy() - balloonpmt[:] = 0 + balloonpmt = cashflow(start=nrate.start, end=nrate.end, pyr=nrate.pyr) else: - verify_period_range([nrate, balloonpmt]) + verify_eq_time_range(nrate, balloonpmt) # present value of the balloon payments if balloonpmt is not None: @@ -564,25 +197,17 @@ def fixed_rate_loan(amount, nrate, life, start, freq='A', grace=0, else: balloonpv = 0 - pyr = getpyr(nrate) - pmt = pvpmt(pmt=None, pval=-amount+balloonpv, nrate=nrate[0], nper=len(nrate)-1, pyr=pyr) - pmts = nrate.copy() - pmts[:] = 0 + pmt = pvpmt(pmt=None, pval=-amount+balloonpv, nrate=nrate[0], nper=len(nrate)-1, pyr=nrate.pyr) + pmts = cashflow(start=nrate.start, end=nrate.end, pyr=nrate.pyr) for time in range(1, life + 1): pmts[grace + time] = pmt # balance - begppalbal = nrate.copy() - intpmt = nrate.copy() - ppalpmt = nrate.copy() - totpmt = nrate.copy() - endppalbal = nrate.copy() - - begppalbal[:] = 0 - intpmt[:] = 0 - ppalpmt[:] = 0 - totpmt[:] = 0 - endppalbal[:] = 0 + begppalbal = TimeSeries(start=nrate.start, end=nrate.end, pyr=nrate.pyr) + intpmt = TimeSeries(start=nrate.start, end=nrate.end, pyr=nrate.pyr) + ppalpmt = TimeSeries(start=nrate.start, end=nrate.end, pyr=nrate.pyr) + totpmt = TimeSeries(start=nrate.start, end=nrate.end, pyr=nrate.pyr) + endppalbal = TimeSeries(start=nrate.start, end=nrate.end, pyr=nrate.pyr) # payments per period for time, _ in enumerate(totpmt): @@ -594,16 +219,16 @@ def fixed_rate_loan(amount, nrate, life, start, freq='A', grace=0, if time == 0: begppalbal[0] = amount endppalbal[0] = amount - totpmt[time] = amount * (dispoints + orgpoints) / 100 - ### intpmt[time] = amount * dispoints / 100 + totpmt[time] = amount * (dispoints + orgpoints) + intpmt[time] = amount * dispoints else: begppalbal[time] = endppalbal[time - 1] if time <= grace: - intpmt[time] = begppalbal[time] * nrate[time] / pyr / 100 + intpmt[time] = begppalbal[time] * nrate[time] / nrate.pyr / 100 totpmt[time] = intpmt[time] endppalbal[time] = begppalbal[time] else: - intpmt[time] = begppalbal[time] * nrate[time] / pyr / 100 + intpmt[time] = begppalbal[time] * nrate[time] / nrate.pyr / 100 ppalpmt[time] = totpmt[time] - intpmt[time] if ppalpmt[time] < 0: capint = - ppalpmt[time] @@ -618,15 +243,17 @@ def fixed_rate_loan(amount, nrate, life, start, freq='A', grace=0, pmts[time] = 0 prepmt[time] = 0 - data = {'Beg_Ppal_Amount':begppalbal} - result = Loan(life=life, amount=amount, grace=grace, nrate=nrate, - dispoints=dispoints, orgpoints=orgpoints, - data=data) - result['Nom_Rate'] = nrate - result['Tot_Payment'] = totpmt - result['Int_Payment'] = intpmt - result['Ppal_Payment'] = ppalpmt - result['End_Ppal_Amount'] = endppalbal + result = Loan() + result.life = life + result.nrate = nrate + result.grace = grace + result.amount = amount + result.begppalbal = begppalbal + result.totpmt = totpmt + result.intpmt = intpmt + result.ppalpmt = ppalpmt + result.endppalbal = endppalbal + return result @@ -647,113 +274,76 @@ def buydown_loan(amount, nrate, grace=0, dispoints=0, orgpoints=0, prepmt=None): Returns: A object of the class ``Loan``. - >>> nrate = interest_rate(const_value=10, start='2016Q1', periods=11, freq='Q', chgpts={'2017Q2':20}) + >>> nrate = interest_rate(const_value=10, nper=11, pyr=4, spec=(5, 20)) >>> buydown_loan(amount=1000, nrate=nrate, dispoints=0, orgpoints=0, prepmt=None) # doctest: +NORMALIZE_WHITESPACE - Amount: 1000.00 - Total interest: 200.99 - Total payment: 1200.99 - Discount points: 0.00 - Origination points: 0.00 - - Beg_Ppal_Amount Nom_Rate Tot_Payment Int_Payment Ppal_Payment \\ - 2016Q1 1000.000000 10.0 0.000000 0.000000 0.000000 - 2016Q2 1000.000000 10.0 114.258763 25.000000 89.258763 - 2016Q3 910.741237 10.0 114.258763 22.768531 91.490232 - 2016Q4 819.251005 10.0 114.258763 20.481275 93.777488 - 2017Q1 725.473517 10.0 114.258763 18.136838 96.121925 - 2017Q2 629.351591 20.0 123.993257 31.467580 92.525677 - 2017Q3 536.825914 20.0 123.993257 26.841296 97.151961 - 2017Q4 439.673952 20.0 123.993257 21.983698 102.009559 - 2018Q1 337.664393 20.0 123.993257 16.883220 107.110037 - 2018Q2 230.554356 20.0 123.993257 11.527718 112.465539 - 2018Q3 118.088816 20.0 123.993257 5.904441 118.088816 - - End_Ppal_Amount - 2016Q1 1.000000e+03 - 2016Q2 9.107412e+02 - 2016Q3 8.192510e+02 - 2016Q4 7.254735e+02 - 2017Q1 6.293516e+02 - 2017Q2 5.368259e+02 - 2017Q3 4.396740e+02 - 2017Q4 3.376644e+02 - 2018Q1 2.305544e+02 - 2018Q2 1.180888e+02 - 2018Q3 1.136868e-13 - - >>> pmt = cashflow(const_value=0, start='2016Q1', periods=11, freq='Q') - >>> pmt['2017Q4'] = 200 + t Beg. Per. Total Int. Ppal Ending + Ppal Rate Pmt Pmt Pmt Ppal + ------------------------------------------------------ + (0, 0) 1000.00 10.00 0.00 0.00 0.00 1000.00 + (0, 1) 1000.00 10.00 114.26 25.00 89.26 910.74 + (0, 2) 910.74 10.00 114.26 22.77 91.49 819.25 + (0, 3) 819.25 10.00 114.26 20.48 93.78 725.47 + (1, 0) 725.47 10.00 114.26 18.14 96.12 629.35 + (1, 1) 629.35 20.00 123.99 31.47 92.53 536.83 + (1, 2) 536.83 20.00 123.99 26.84 97.15 439.67 + (1, 3) 439.67 20.00 123.99 21.98 102.01 337.66 + (2, 0) 337.66 20.00 123.99 16.88 107.11 230.55 + (2, 1) 230.55 20.00 123.99 11.53 112.47 118.09 + (2, 2) 118.09 20.00 123.99 5.90 118.09 0.00 + + + >>> pmt = cashflow(const_value=0, nper = 11, pyr=4, spec=((1, 3), 200)) >>> buydown_loan(amount=1000, nrate=nrate, dispoints=0, orgpoints=0, prepmt=pmt) # doctest: +NORMALIZE_WHITESPACE - Amount: 1000.00 - Total interest: 180.67 - Total payment: 1180.67 - Discount points: 0.00 - Origination points: 0.00 - - Beg_Ppal_Amount Nom_Rate Tot_Payment Int_Payment Ppal_Payment \\ - 2016Q1 1000.000000 10.0 0.000000 0.000000 0.000000 - 2016Q2 1000.000000 10.0 114.258763 25.000000 89.258763 - 2016Q3 910.741237 10.0 114.258763 22.768531 91.490232 - 2016Q4 819.251005 10.0 114.258763 20.481275 93.777488 - 2017Q1 725.473517 10.0 114.258763 18.136838 96.121925 - 2017Q2 629.351591 20.0 123.993257 31.467580 92.525677 - 2017Q3 536.825914 20.0 123.993257 26.841296 97.151961 - 2017Q4 439.673952 20.0 323.993257 21.983698 302.009559 - 2018Q1 137.664393 20.0 50.551544 6.883220 43.668324 - 2018Q2 93.996068 20.0 50.551544 4.699803 45.851741 - 2018Q3 48.144328 20.0 50.551544 2.407216 48.144328 - - End_Ppal_Amount - 2016Q1 1.000000e+03 - 2016Q2 9.107412e+02 - 2016Q3 8.192510e+02 - 2016Q4 7.254735e+02 - 2017Q1 6.293516e+02 - 2017Q2 5.368259e+02 - 2017Q3 4.396740e+02 - 2017Q4 1.376644e+02 - 2018Q1 9.399607e+01 - 2018Q2 4.814433e+01 - 2018Q3 4.263256e-14 + t Beg. Per. Total Int. Ppal Ending + Ppal Rate Pmt Pmt Pmt Ppal + ------------------------------------------------------ + (0, 0) 1000.00 10.00 0.00 0.00 0.00 1000.00 + (0, 1) 1000.00 10.00 114.26 25.00 89.26 910.74 + (0, 2) 910.74 10.00 114.26 22.77 91.49 819.25 + (0, 3) 819.25 10.00 114.26 20.48 93.78 725.47 + (1, 0) 725.47 10.00 114.26 18.14 96.12 629.35 + (1, 1) 629.35 20.00 123.99 31.47 92.53 536.83 + (1, 2) 536.83 20.00 123.99 26.84 97.15 439.67 + (1, 3) 439.67 20.00 323.99 21.98 302.01 137.66 + (2, 0) 137.66 20.00 50.55 6.88 43.67 94.00 + (2, 1) 94.00 20.00 50.55 4.70 45.85 48.14 + (2, 2) 48.14 20.00 50.55 2.41 48.14 0.00 + """ - if not isinstance(nrate, pd.Series): - TypeError('nrate must be a pandas.Series object.') + if not isinstance(nrate, TimeSeries): + TypeError('nrate must be a TimeSeries object.') if prepmt is None: - prepmt = nrate.copy() - prepmt[:] = 0 + prepmt = cashflow(start=nrate.start, end=nrate.end, pyr=nrate.pyr) else: - verify_period_range([nrate, prepmt]) + verify_eq_time_range(nrate, prepmt) life = len(nrate) - grace - 1 - begppalbal = nrate.copy() - intpmt = nrate.copy() - ppalpmt = nrate.copy() - totpmt = nrate.copy() - endppalbal = nrate.copy() - - begppalbal[:] = 0 - intpmt[:] = 0 - ppalpmt[:] = 0 - totpmt[:] = 0 - endppalbal[:] = 0 + begppalbal = TimeSeries(start=nrate.start, end=nrate.end, pyr=nrate.pyr) + intpmt = TimeSeries(start=nrate.start, end=nrate.end, pyr=nrate.pyr) + ppalpmt = TimeSeries(start=nrate.start, end=nrate.end, pyr=nrate.pyr) + totpmt = TimeSeries(start=nrate.start, end=nrate.end, pyr=nrate.pyr) + endppalbal = TimeSeries(start=nrate.start, end=nrate.end, pyr=nrate.pyr) + if prepmt is None: + prepmt = cashflow(start=nrate.start, end=nrate.end, pyr=nrate.pyr) + else: + verify_eq_time_range(nrate, prepmt) ## ## balance calculation ## - pyr = getpyr(nrate) for time in range(grace + life + 1): if time == 0: # begppalbal[time] = amount endppalbal[time] = amount - totpmt[time] = amount * (dispoints + orgpoints) / 100 - ### intpmt[time] = amount * dispoints / 100 + totpmt[time] = amount * (dispoints + orgpoints) + intpmt[time] = amount * dispoints # else: # @@ -762,148 +352,235 @@ def buydown_loan(amount, nrate, grace=0, dispoints=0, orgpoints=0, prepmt=None): if time <= grace: begppalbal[time] = endppalbal[time - 1] - intpmt[time] = begppalbal[time] * nrate[time] / pyr / 100 + intpmt[time] = begppalbal[time] * nrate[time] / nrate.pyr / 100 totpmt[time] = intpmt[time] endppalbal[time] = begppalbal[time] else: pmt = -pvpmt(nrate=nrate[time], nper=grace+life-time+1, - pval=endppalbal[time-1], pmt=None, pyr=pyr) + pval=endppalbal[time-1], pmt=None, pyr=nrate.pyr) totpmt[time] = pmt + prepmt[time] # balance begppalbal[time] = endppalbal[time - 1] - intpmt[time] = begppalbal[time] * nrate[time] / pyr / 100 + intpmt[time] = begppalbal[time] * nrate[time] / nrate.pyr / 100 ppalpmt[time] = totpmt[time] - intpmt[time] endppalbal[time] = begppalbal[time] - ppalpmt[time] - data = {'Beg_Ppal_Amount':begppalbal} - result = Loan(life=life, amount=amount, grace=grace, nrate=nrate, - dispoints=dispoints, orgpoints=orgpoints, - data=data) - result['Nom_Rate'] = nrate - result['Tot_Payment'] = totpmt - result['Int_Payment'] = intpmt - result['Ppal_Payment'] = ppalpmt - result['End_Ppal_Amount'] = endppalbal + ## resuls + result = Loan() + result.nrate = nrate + result.life = life + result.grace = grace + result.amount = amount + result.begppalbal = begppalbal + result.totpmt = totpmt + result.intpmt = intpmt + result.ppalpmt = ppalpmt + result.endppalbal = endppalbal + return result -# class xLoan(): -# """ -# Class for representing loans -# """ -# -# # pylint: disable=too-many-instance-attributes -# -# def __init__(self): -# """ -# """ -# self.life = self.amount = self.grace = self.df = None -# # self.intpmt = self.endppalbal = self.begppalbal = None -# # self.totpmt = None -# -# def to_cashflow(self, tax_rate=0): -# """Converts the loan to the equivalent cashflow. -# -# For the conversion, origination points are considered as exogenous costs -# and they are not taking in to account in the computation. In oposition, -# discount points are considered as prepaid interest and included in the -# cashflow. -# -# When tax_rate is different from zero, tax benefits are considered.""" -# -# -# if isinstance(tax_rate, (int, float)): -# tax_rate = interest_rate(const_value = [tax_rate] * (self.life + self.grace + 1)) -# -# cflo = cashflow(const_value= [0] * (self.grace + self.life + 1)) -# -# ## -# ## payments per period -# ## -# for time in range(self.grace + self.life + 1): -# if time == 0: -# cflo[0] = self.amount -# cflo[time] += -self.totpmt[time] + self.intpmt[time] * tax_rate[time] / 100 -# -# return cflo -# -# def true_rate(self, tax_rate=0): -# """Computes the true interest rate for the loan. -# -# For the computation, the loan is converted to the equivalent cashflow, -# taking in to account the following aspects: -# -# * Origination points are considered as non deducible costs and they \ -# are ignored in the computation. -# -# * Discount points are prepaid interest and they are considered as \ -# deducibles in the computation. -# -# * When `tax_rate` is different from zero, the After-Tax true interest \ -# rate is calculated. This is, only the (1 - `tax_rate`) of paid interests \ -# (including discount points) are used in the computation. -# -# """ -# return irr(self.to_cashflow(tax_rate)) -# -# -# -# def __repr__(self): -# -# return self.df.__repr__() -# # return repr_table(cols=[self.begppalbal, -# # self.nrate, -# # self.totpmt, -# # self.intpmt, -# # self.ppalpmt, -# # self.endppalbal], -# # header=[['Beg.', 'Per.', 'Total', 'Int.', 'Ppal', 'Ending'], -# # ['Ppal', 'Rate', 'Pmt', 'Pmt', 'Pmt', 'Ppal']]) -# -# # def x__repr__(self): -# # txt = [''] -# # txt.append(' t Beginning Periodic Total Interest Principal Ending') -# # txt.append(' Principal Rate Payment Payment Payment Principal') -# # txt.append('--------------------------------------------------------------------------') -# # -# # for time in range(self.grace + self.life + 1): -# # fmt = ' {:3d} {:12.2f} {:12.2f} {:12.2f} {:12.2f} {:12.2f}' -# # txt.append(fmt.format(time, -# # self.begppalbal[time], -# # self.totpmt[time], -# # self.intpmt[time], -# # self.ppalpmt[time], -# # self.endppalbal[time])) -# # return '\n'.join(txt) -# -# def interest(self): -# """Returns the interest paid as a Cashflow object.""" -# return Cashflow(constValue=self.intpmt.tolist()) -# -# def begbal(self): -# """Returns the balance at the begining of each period as -# a Cashflow object.""" -# return Cashflow(constValue=self.begppalbal.tolist()) -# -# def endbal(self): -# """Returns the balance at the ending of each period as -# a Cashflow object.""" -# return Cashflow(constValue=self.endppalbal.tolist()) -# -# def ppalpmt(self): -# """Returns the principal payment for each period as -# a Cashflow object.""" -# return Cashflow(constValue=self.ppalpmt.tolist()) +def fixed_ppal_loan(amount, nrate, grace=0, dispoints=0, orgpoints=0, + prepmt=None, balloonpmt=None): + """Loan with fixed principal payment. + + Args: + amount (float): Loan amount. + nrate (float, TimeSeries): nominal interest rate per year. + grace (int): number of grace periiods without paying principal. + dispoints (float): Discount points of the loan. + orgpoints (float): Origination points of the loan. + prepmt (TimeSeries): generic cashflow representing prepayments. + balloonpmt (TimeSeries): generic cashflow representing balloon payments. + Returns: + A object of the class ``Loan``. + + >>> nrate = interest_rate(const_value=10, nper=11, pyr=4) + >>> fixed_ppal_loan(amount=1000, nrate=nrate, grace=0, dispoints=0, orgpoints=0, + ... prepmt=None, balloonpmt=None) # doctest: +NORMALIZE_WHITESPACE + t Beg. Per. Total Int. Ppal Ending + Ppal Rate Pmt Pmt Pmt Ppal + ------------------------------------------------------ + (0, 0) 0.00 10.00 0.00 0.00 0.00 1000.00 + (0, 1) 1000.00 10.00 125.00 25.00 100.00 900.00 + (0, 2) 900.00 10.00 122.50 22.50 100.00 800.00 + (0, 3) 800.00 10.00 120.00 20.00 100.00 700.00 + (1, 0) 700.00 10.00 117.50 17.50 100.00 600.00 + (1, 1) 600.00 10.00 115.00 15.00 100.00 500.00 + (1, 2) 500.00 10.00 112.50 12.50 100.00 400.00 + (1, 3) 400.00 10.00 110.00 10.00 100.00 300.00 + (2, 0) 300.00 10.00 107.50 7.50 100.00 200.00 + (2, 1) 200.00 10.00 105.00 5.00 100.00 100.00 + (2, 2) 100.00 10.00 102.50 2.50 100.00 0.00 + + + >>> fixed_ppal_loan(amount=1000, nrate=nrate, grace=2, dispoints=0, orgpoints=0, + ... prepmt=None, balloonpmt=None) # doctest: +NORMALIZE_WHITESPACE + t Beg. Per. Total Int. Ppal Ending + Ppal Rate Pmt Pmt Pmt Ppal + ------------------------------------------------------ + (0, 0) 0.00 10.00 0.00 0.00 0.00 1000.00 + (0, 1) 1000.00 10.00 25.00 25.00 0.00 1000.00 + (0, 2) 1000.00 10.00 25.00 25.00 0.00 1000.00 + (0, 3) 1000.00 10.00 150.00 25.00 125.00 875.00 + (1, 0) 875.00 10.00 146.88 21.88 125.00 750.00 + (1, 1) 750.00 10.00 143.75 18.75 125.00 625.00 + (1, 2) 625.00 10.00 140.62 15.62 125.00 500.00 + (1, 3) 500.00 10.00 137.50 12.50 125.00 375.00 + (2, 0) 375.00 10.00 134.38 9.38 125.00 250.00 + (2, 1) 250.00 10.00 131.25 6.25 125.00 125.00 + (2, 2) 125.00 10.00 128.12 3.12 125.00 0.00 + + >>> pmt = cashflow(const_value=0, nper = 11, pyr=4, spec=((1, 3), 200)) + >>> fixed_ppal_loan(amount=1000, nrate=nrate, grace=2, dispoints=0, orgpoints=0, + ... prepmt=pmt, balloonpmt=None) # doctest: +NORMALIZE_WHITESPACE + t Beg. Per. Total Int. Ppal Ending + Ppal Rate Pmt Pmt Pmt Ppal + ------------------------------------------------------ + (0, 0) 0.00 10.00 0.00 0.00 0.00 1000.00 + (0, 1) 1000.00 10.00 25.00 25.00 0.00 1000.00 + (0, 2) 1000.00 10.00 25.00 25.00 0.00 1000.00 + (0, 3) 1000.00 10.00 150.00 25.00 125.00 875.00 + (1, 0) 875.00 10.00 146.88 21.88 125.00 750.00 + (1, 1) 750.00 10.00 143.75 18.75 125.00 625.00 + (1, 2) 625.00 10.00 140.62 15.62 125.00 500.00 + (1, 3) 500.00 10.00 337.50 12.50 325.00 175.00 + (2, 0) 175.00 10.00 129.38 4.38 125.00 50.00 + (2, 1) 50.00 10.00 51.25 1.25 50.00 0.00 + (2, 2) 0.00 10.00 0.00 0.00 0.00 0.00 + + >>> pmt = cashflow(const_value=0, nper = 11, pyr=4, spec=((1, 3), 200)) + >>> fixed_ppal_loan(amount=1000, nrate=nrate, grace=2, dispoints=0, orgpoints=0, + ... prepmt=None, balloonpmt=pmt) # doctest: +NORMALIZE_WHITESPACE + t Beg. Per. Total Int. Ppal Ending + Ppal Rate Pmt Pmt Pmt Ppal + ------------------------------------------------------ + (0, 0) 0.00 10.00 0.00 0.00 0.00 1000.00 + (0, 1) 1000.00 10.00 25.00 25.00 0.00 1000.00 + (0, 2) 1000.00 10.00 25.00 25.00 0.00 1000.00 + (0, 3) 1000.00 10.00 125.00 25.00 100.00 900.00 + (1, 0) 900.00 10.00 122.50 22.50 100.00 800.00 + (1, 1) 800.00 10.00 120.00 20.00 100.00 700.00 + (1, 2) 700.00 10.00 117.50 17.50 100.00 600.00 + (1, 3) 600.00 10.00 315.00 15.00 300.00 300.00 + (2, 0) 300.00 10.00 107.50 7.50 100.00 200.00 + (2, 1) 200.00 10.00 105.00 5.00 100.00 100.00 + (2, 2) 100.00 10.00 102.50 2.50 100.00 0.00 + + """ + #pylint: disable-msg=too-many-arguments + + if not isinstance(nrate, TimeSeries): + TypeError('nrate must be a TimeSeries object.') + + if prepmt is None: + prepmt = cashflow(start=nrate.start, end=nrate.end, pyr=nrate.pyr) + else: + verify_eq_time_range(nrate, prepmt) + + if balloonpmt is None: + balloonpmt = cashflow(start=nrate.start, end=nrate.end, pyr=nrate.pyr) + else: + verify_eq_time_range(nrate, balloonpmt) + + # present value of the balloon payments + balloonpv = sum(balloonpmt) + + life = len(nrate) - grace - 1 + begppalbal = TimeSeries(start=nrate.start, end=nrate.end, pyr=nrate.pyr) + intpmt = TimeSeries(start=nrate.start, end=nrate.end, pyr=nrate.pyr) + ppalpmt = TimeSeries(start=nrate.start, end=nrate.end, pyr=nrate.pyr) + totpmt = TimeSeries(start=nrate.start, end=nrate.end, pyr=nrate.pyr) + endppalbal = TimeSeries(start=nrate.start, end=nrate.end, pyr=nrate.pyr) -if __name__ == "__main__": - import doctest - doctest.testmod() + pmt = (amount - balloonpv) / life # periodic ppal payment + + # balance calculation + for time in range(grace + life + 1): + + if time == 0: + begppalbal[time] = 0 + endppalbal[time] = amount - prepmt[time] + totpmt[time] = amount * (dispoints + orgpoints) + intpmt[time] = amount * dispoints + else: + begppalbal[time] = endppalbal[time - 1] + intpmt[time] = begppalbal[time] * nrate[time] / nrate.pyr / float(100) + if time <= grace: + ppalpmt[time] = prepmt[time] + balloonpmt[time] + else: + ppalpmt[time] = pmt + prepmt[time] + balloonpmt[time] + totpmt[time] = intpmt[time] + ppalpmt[time] + endppalbal[time] = begppalbal[time] - ppalpmt[time] + + if endppalbal[time] < 0: + totpmt[time] = begppalbal[time] + intpmt[time] + ppalpmt[time] = begppalbal[time] + endppalbal[time] = begppalbal[time] - ppalpmt[time] + prepmt[time] = 0 + pmt = 0 + + + + ## resuls + result = Loan() + result.life = life + result.nrate = nrate + result.grace = grace + result.amount = amount + result.begppalbal = begppalbal + result.totpmt = totpmt + result.intpmt = intpmt + result.ppalpmt = ppalpmt + result.endppalbal = endppalbal + + return result + + +def bullet_loan(amount, nrate, dispoints=0, orgpoints=0, prepmt=None): + """ + In this type of loan, the principal is payed at the end for the life of the + loan. Periodic payments correspond only to interests. + + Args: + amount (float): Loan amount. + nrate (float, TimeSeries): nominal interest rate per year. + dispoints (float): Discount points of the loan. + orgpoints (float): Origination points of the loan. + prepmt (TimeSeries): generic cashflow representing prepayments. + + Returns: + A object of the class ``Loan``. + + >>> nrate = interest_rate(const_value=10, nper=11, pyr=4) + >>> bullet_loan(amount=1000, nrate=nrate, dispoints=0, orgpoints=0, prepmt=None) # doctest: +NORMALIZE_WHITESPACE + t Beg. Per. Total Int. Ppal Ending + Ppal Rate Pmt Pmt Pmt Ppal + ------------------------------------------------------ + (0, 0) 0.00 10.00 0.00 0.00 0.00 1000.00 + (0, 1) 1000.00 10.00 25.00 25.00 0.00 1000.00 + (0, 2) 1000.00 10.00 25.00 25.00 0.00 1000.00 + (0, 3) 1000.00 10.00 25.00 25.00 0.00 1000.00 + (1, 0) 1000.00 10.00 25.00 25.00 0.00 1000.00 + (1, 1) 1000.00 10.00 25.00 25.00 0.00 1000.00 + (1, 2) 1000.00 10.00 25.00 25.00 0.00 1000.00 + (1, 3) 1000.00 10.00 25.00 25.00 0.00 1000.00 + (2, 0) 1000.00 10.00 25.00 25.00 0.00 1000.00 + (2, 1) 1000.00 10.00 25.00 25.00 0.00 1000.00 + (2, 2) 1000.00 10.00 1025.00 25.00 1000.00 0.00 + + + """ + balloonpmt = cashflow(const_value=0, start=nrate.start, end=nrate.end, pyr=nrate.pyr) + balloonpmt[-1] = amount + return fixed_ppal_loan(amount=amount, nrate=nrate, grace=0, dispoints=dispoints, + orgpoints=orgpoints, prepmt=prepmt, balloonpmt=balloonpmt) diff --git a/cashflows/rate.py b/cashflows/rate.py old mode 100644 new mode 100755 index 38651a5..88368e4 --- a/cashflows/rate.py +++ b/cashflows/rate.py @@ -5,547 +5,258 @@ """ -import numpy as np -import pandas as pd -# cashflows. -from cashflows.timeseries import * -from cashflows.common import getpyr +import numpy +from cashflows.gtimeseries import _timeid2index +from cashflows.gtimeseries import * -def effrate(nrate=None, prate=None, pyr=1): - """ - Computes the effective interest rate given the nominal interest rate or the periodic interest rate. - - Args: - nrate (float, TimeSeries): Nominal interest rate. - prate (float, TimeSeries): Periodic interest rate. - pyr(int): Number of compounding periods per year. - - - Returns: - Effective interest rate(float, TimeSeries). - - >>> effrate(prate=1, pyr=12) # doctest: +ELLIPSIS - 12.68... - - >>> effrate(nrate=10, pyr=12) # doctest: +ELLIPSIS - 10.4713... - - >>> effrate(prate=1, pyr=[3, 6, 12]) # doctest: +ELLIPSIS - 0 3.030100 - 1 6.152015 - 2 12.682503 - dtype: float64 - - >>> effrate(nrate=10, pyr=[3, 6, 12]) # doctest: +ELLIPSIS - 0 10.337037 - 1 10.426042 - 2 10.471307 - dtype: float64 - - >>> effrate(prate=[1, 2, 3], pyr=12) # doctest: +ELLIPSIS - 0 12.682503 - 1 26.824179 - 2 42.576089 - dtype: float64 - - >>> effrate(nrate=[10, 12, 14], pyr=12) # doctest: +ELLIPSIS - 0 10.471307 - 1 12.682503 - 2 14.934203 - dtype: float64 - When a rate and the number of compounding periods (`pyr`) are vectors, they - must have the same length. Computations are executed using the first rate - with the first compounding and so on. - - >>> effrate(nrate=[10, 12, 14], pyr=[3, 6, 12]) # doctest: +ELLIPSIS - 0 10.337037 - 1 12.616242 - 2 14.934203 - dtype: float64 - - >>> effrate(prate=[1, 2, 3], pyr=[3, 6, 12]) # doctest: +ELLIPSIS - 0 3.030100 - 1 12.616242 - 2 42.576089 - dtype: float64 - - - >>> nrate = interest_rate(const_value=12, start='2000-06', periods=12, freq='6M') - >>> prate = perrate(nrate=nrate) - >>> effrate(nrate = nrate) # doctest: +NORMALIZE_WHITESPACE - 2000-06 12.36 - 2000-12 12.36 - 2001-06 12.36 - 2001-12 12.36 - 2002-06 12.36 - 2002-12 12.36 - 2003-06 12.36 - 2003-12 12.36 - 2004-06 12.36 - 2004-12 12.36 - 2005-06 12.36 - 2005-12 12.36 - Freq: 6M, dtype: float64 - - >>> effrate(prate = prate) # doctest: +NORMALIZE_WHITESPACE - 2000-06 12.36 - 2000-12 12.36 - 2001-06 12.36 - 2001-12 12.36 - 2002-06 12.36 - 2002-12 12.36 - 2003-06 12.36 - 2003-12 12.36 - 2004-06 12.36 - 2004-12 12.36 - 2005-06 12.36 - 2005-12 12.36 - Freq: 6M, dtype: float64 - - """ - numnone = 0 - if nrate is None: - numnone += 1 - if prate is None: - numnone += 1 - if numnone != 1: - raise ValueError('One of the rates must be set to `None`') - - if isinstance(nrate, pd.Series): - pyr = getpyr(nrate) - erate = nrate.copy() - for index in range(len(nrate)): - erate[index] = 100 * (np.power(1 + nrate[index]/100/pyr, pyr) - 1) - return erate - - if isinstance(prate, pd.Series): - pyr = getpyr(prate) - erate = prate.copy() - for index in range(len(prate)): - erate[index] = 100 * (np.power(1 + prate[index]/100, pyr) - 1) - return erate - if nrate is not None: - ## - ## - maxlen = 1 - if isinstance(nrate, (list, type(np.array), type(pd.Series))): - maxlen = max(maxlen, len(nrate)) - if isinstance(pyr, (list, type(np.array), type(pd.Series))): - maxlen = max(maxlen, len(pyr)) - # - if isinstance(nrate, (int, float)): - nrate = [nrate] * maxlen - nrate = pd.Series(nrate, dtype=np.float64) - if isinstance(pyr, (int, float)): - pyr = [pyr] * maxlen - pyr = pd.Series(pyr) - # - if len(nrate) != len(pyr): - raise ValueError('Lists must have the same length') - ## - ## - prate = nrate / pyr - erate = 100 * (np.power(1 + prate/100, pyr) - 1) - if maxlen == 1: - erate = erate[0] - return erate - - if prate is not None: - ## - ## - maxlen = 1 - if isinstance(prate, (list, type(np.array), type(pd.Series))): - maxlen = max(maxlen, len(prate)) - if isinstance(pyr, (list, type(np.array), type(pd.Series))): - maxlen = max(maxlen, len(pyr)) - # - if isinstance(prate, (int, float)): - prate = [prate] * maxlen - prate = pd.Series(prate, dtype=np.float64) - if isinstance(pyr, (int, float)): - pyr = [pyr] * maxlen - pyr = pd.Series(pyr) - # - if len(prate) != len(pyr): - raise ValueError('Lists must have the same length') - ## - ## - erate = 100 * (np.power(1 + prate / 100, pyr) - 1) - if maxlen == 1: - erate = erate[0] - return erate - - - - -def nomrate(erate=None, prate=None, pyr=1): - """ - Computes the nominal interest rate given the nominal interest rate or the periodic interest rate. +def iconv(nrate=None, erate=None, prate=None, pyr=1): + """The function `iconv` computes the conversion among periodic, nominal + and effective interest rates. Only an interest rate (periodic, nominal or + effective) must be specified and the other two are computed. The periodic + rate is the rate used in each compounding period. The effective rate is + the equivalent rate that produces the same interest earnings that a periodic + rate when there is P compounding periods in a year. The nominal rate is + defined as the annual rate computed as P times the periodic rate. Args: - erate (float, TimeSeries): Effective interest rate. - prate (float, TimeSeries): Periodic interest rate. - pyr(int): Number of compounding periods per year. + nrate (float, list, TimeSeries): nominal interest rate per year. + erate (float, list, TimeSeries): effective interest rate per year. + prate (float, list, TimeSeries): periodic rate + pyr (int, list): number of compounding periods per year Returns: - Nominal interest rate(float, TimeSeries). - - >>> nomrate(prate=1, pyr=12) # doctest: +ELLIPSIS - 12.0 - - >>> nomrate(erate=10, pyr=[3, 6, 12]) # doctest: +ELLIPSIS - 0 9.684035 - 1 9.607121 - 2 9.568969 - dtype: float64 - - >>> nomrate(erate=10, pyr=12) # doctest: +ELLIPSIS - 9.5689... - - >>> nomrate(prate=1, pyr=[3, 6, 12]) # doctest: +ELLIPSIS - 0 3.0 - 1 6.0 - 2 12.0 - dtype: float64 - - >>> nomrate(erate=[10, 12, 14], pyr=12) # doctest: +ELLIPSIS - 0 9.568969 - 1 11.386552 - 2 13.174622 - dtype: float64 - - >>> nomrate(prate=[1, 2, 3], pyr=12) # doctest: +ELLIPSIS - 0 12.0 - 1 24.0 - 2 36.0 - dtype: float64 + A tuple: + * (**nrate**, **prate**): when **erate** is specified. + * (**erate**, **prate**): when **nrate** is specified. + * (**nrate**, **erate**): when **prate** is specified. - When a rate and the number of compounding periods (`pyr`) are vectors, they - must have the same length. Computations are executed using the first rate - with the first compounding and so on. - >>> nomrate(erate=[10, 12, 14], pyr=[3, 6, 12]) # doctest: +ELLIPSIS - 0 9.684035 - 1 11.440574 - 2 13.174622 - dtype: float64 - - >>> nomrate(prate=[1, 2, 3], pyr=[3, 6, 12]) # doctest: +ELLIPSIS - 0 3.0 - 1 12.0 - 2 36.0 - dtype: float64 - - >>> prate = interest_rate(const_value=6.00, start='2000-06', periods=12, freq='6M') - >>> erate = effrate(prate=prate) - >>> nomrate(erate=erate) - 2000-06 12.0 - 2000-12 12.0 - 2001-06 12.0 - 2001-12 12.0 - 2002-06 12.0 - 2002-12 12.0 - 2003-06 12.0 - 2003-12 12.0 - 2004-06 12.0 - 2004-12 12.0 - 2005-06 12.0 - 2005-12 12.0 - Freq: 6M, dtype: float64 - - - >>> nomrate(prate=prate) - 2000-06 12.0 - 2000-12 12.0 - 2001-06 12.0 - 2001-12 12.0 - 2002-06 12.0 - 2002-12 12.0 - 2003-06 12.0 - 2003-12 12.0 - 2004-06 12.0 - 2004-12 12.0 - 2005-06 12.0 - 2005-12 12.0 - Freq: 6M, dtype: float64 + Effective rate to nominal rate compounded monthly and monthly peridic rate. + >>> iconv(erate=10, pyr=12) # doctest: +ELLIPSIS + (9.56..., 0.79...) - """ - numnone = 0 - if erate is None: - numnone += 1 - if prate is None: - numnone += 1 - if numnone != 1: - raise ValueError('One of the rates must be set to `None`') - - if isinstance(erate, pd.Series): - pyr = getpyr(erate) - nrate = erate.copy() - for index in range(len(erate)): - nrate[index] = 100 * pyr * (np.power(1 + erate[index]/100, 1. / pyr) - 1) - return nrate - - if isinstance(prate, pd.Series): - pyr = getpyr(prate) - nrate = prate.copy() - for index in range(len(prate)): - nrate[index] = prate[index] * pyr - return nrate + >>> iconv(prate=1, pyr=12) # doctest: +ELLIPSIS + (12, 12.68...) + >>> iconv(nrate=10, pyr=12) # doctest: +ELLIPSIS + (10.47..., 0.83...) - if erate is not None: - ## - ## - maxlen = 1 - if isinstance(erate, (list, type(np.array), type(pd.Series))): - maxlen = max(maxlen, len(erate)) - if isinstance(pyr, (list, type(np.array), type(pd.Series))): - maxlen = max(maxlen, len(pyr)) - # - if isinstance(erate, (int, float)): - erate = [erate] * maxlen - erate = pd.Series(erate, dtype=np.float64) - # - if isinstance(pyr, (int, float)): - pyr = [pyr] * maxlen - pyr = pd.Series(pyr) - # - if len(erate) != len(pyr): - raise ValueError('Lists must have the same length') - ## - ## - prate = 100 * (np.power(1 + erate / 100, 1 / pyr) - 1) - nrate = pyr * prate - if maxlen == 1: - nrate = nrate[0] - return nrate - if prate is not None: - ## - ## - maxlen = 1 - if isinstance(prate, (list, type(np.array), type(pd.Series))): - maxlen = max(maxlen, len(prate)) - if isinstance(pyr, (list, type(np.array), type(pd.Series))): - maxlen = max(maxlen, len(pyr)) - # - if isinstance(prate, (int, float)): - prate = [prate] * maxlen - prate = pd.Series(prate, dtype=np.float64) - if isinstance(pyr, (int, float)): - pyr = [pyr] * maxlen - pyr = pd.Series(pyr) - # - if len(prate) != len(pyr): - raise ValueError('Lists must have the same length') - ## - ## - nrate = pyr * prate - if maxlen == 1: - nrate = nrate[0] - return nrate + `iconv` accepts Python vectors. + >>> iconv(erate=10, pyr=[3, 6, 12]) # doctest: +ELLIPSIS + ([9.68..., 9.60..., 9.56...], [3.22..., 1.60..., 0.79...]) -def perrate(nrate=None, erate=None, pyr=1): - """ - Computes the periodic interest rate given the nominal interest rate or the effective interest rate. + >>> iconv(prate=1, pyr=[3, 6, 12]) # doctest: +ELLIPSIS + ([3, 6, 12], [3.03..., 6.15..., 12.68...]) - Args: - nrate (float, TimeSeries): Nominal interest rate. - erate (float, TimeSeries): Effective interest rate. - pyr(int): Number of compounding periods per year. + >>> iconv(nrate=10, pyr=[3, 6, 12]) # doctest: +ELLIPSIS + ([10.33..., 10.42..., 10.47...], [3.33..., 1.66..., 0.83...]) + >>> iconv(erate=[10, 12, 14], pyr=12) # doctest: +ELLIPSIS + ([9.56..., 11.38..., 13.17...], [0.79..., 0.94..., 1.09...]) - Returns: - Periodic interest rate(float, TimeSeries). - - >>> perrate(nrate=10, pyr=12) # doctest: +ELLIPSIS - 0.8333... - - >>> perrate(erate=10, pyr=12) # doctest: +ELLIPSIS - 0.7974... - - >>> perrate(erate=10, pyr=[3, 6, 12]) # doctest: +ELLIPSIS - 0 3.228012 - 1 1.601187 - 2 0.797414 - dtype: float64 - - >>> perrate(nrate=10, pyr=[3, 6, 12]) # doctest: +ELLIPSIS - 0 3.333333 - 1 1.666667 - 2 0.833333 - dtype: float64 - - >>> perrate(erate=[10, 12, 14], pyr=12) # doctest: +ELLIPSIS - 0 0.797414 - 1 0.948879 - 2 1.097885 - dtype: float64 - - >>> perrate(nrate=[10, 12, 14], pyr=12) # doctest: +ELLIPSIS - 0 0.833333 - 1 1.000000 - 2 1.166667 - dtype: float64 + >>> iconv(prate=[1, 2, 3], pyr=12) # doctest: +ELLIPSIS + ([12, 24, 36], [12.68..., 26.82..., 42.57...]) + + >>> iconv(nrate=[10, 12, 14], pyr=12) # doctest: +ELLIPSIS + ([10.47..., 12.68..., 14.93...], [0.83..., 1.0, 1.16...]) When a rate and the number of compounding periods (`pyr`) are vectors, they must have the same length. Computations are executed using the first rate with the first compounding and so on. - >>> perrate(erate=[10, 12, 14], pyr=[3, 6, 12]) # doctest: +ELLIPSIS - 0 3.228012 - 1 1.906762 - 2 1.097885 - dtype: float64 - - >>> perrate(nrate=[10, 12, 14], pyr=[3, 6, 12]) # doctest: +ELLIPSIS - 0 3.333333 - 1 2.000000 - 2 1.166667 - dtype: float64 - - - >>> nrate = interest_rate(const_value=12.0, start='2000-06', periods=12, freq='6M') - >>> erate = effrate(nrate=nrate) - >>> perrate(erate=erate) # doctest: +NORMALIZE_WHITESPACE - 2000-06 6.0 - 2000-12 6.0 - 2001-06 6.0 - 2001-12 6.0 - 2002-06 6.0 - 2002-12 6.0 - 2003-06 6.0 - 2003-12 6.0 - 2004-06 6.0 - 2004-12 6.0 - 2005-06 6.0 - 2005-12 6.0 - Freq: 6M, dtype: float64 - - >>> perrate(nrate=nrate) # doctest: +NORMALIZE_WHITESPACE - 2000-06 6.0 - 2000-12 6.0 - 2001-06 6.0 - 2001-12 6.0 - 2002-06 6.0 - 2002-12 6.0 - 2003-06 6.0 - 2003-12 6.0 - 2004-06 6.0 - 2004-12 6.0 - 2005-06 6.0 - 2005-12 6.0 - Freq: 6M, dtype: float64 - + >>> iconv(erate=[10, 12, 14], pyr=[3, 6, 12]) # doctest: +ELLIPSIS + ([9.68..., 11.44..., 13.17...], [3.22..., 1.90..., 1.09...]) + + >>> iconv(nrate=[10, 12, 14], pyr=[3, 6, 12]) # doctest: +ELLIPSIS + ([10.33..., 12.61..., 14.93...], [3.33..., 2.0, 1.16...]) + + >>> iconv(prate=[1, 2, 3], pyr=[3, 6, 12]) # doctest: +ELLIPSIS + ([3, 12, 36], [3.03..., 12.61..., 42.57...]) + + `iconv` accepts TimeSeries objects + + >>> erate, prate = iconv(nrate = interest_rate(const_value=12, nper=12, pyr=2)) + >>> prate # doctest: +NORMALIZE_WHITESPACE + Time Series: + Start = (0, 0) + End = (5, 1) + pyr = 2 + Data = (0, 0)-(5, 1) [12] 6.00 + + >>> erate # doctest: +NORMALIZE_WHITESPACE + Time Series: + Start = (0, 0) + End = (5, 1) + pyr = 2 + Data = (0, 0)-(5, 1) [12] 12.36 + + >>> erate, prate = iconv(nrate = interest_rate(const_value=12, nper=12, pyr=4)) + >>> prate # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 3.00 3.00 3.00 3.00 + 1 3.00 3.00 3.00 3.00 + 2 3.00 3.00 3.00 3.00 + + >>> erate # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 12.55 12.55 12.55 12.55 + 1 12.55 12.55 12.55 12.55 + 2 12.55 12.55 12.55 12.55 + + >>> nrate, prate = iconv(erate = erate) + >>> nrate # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 12.00 12.00 12.00 12.00 + 1 12.00 12.00 12.00 12.00 + 2 12.00 12.00 12.00 12.00 + + >>> prate # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 3.00 3.00 3.00 3.00 + 1 3.00 3.00 3.00 3.00 + 2 3.00 3.00 3.00 3.00 + + >>> nrate, erate = iconv(prate = prate) + >>> nrate # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 12.00 12.00 12.00 12.00 + 1 12.00 12.00 12.00 12.00 + 2 12.00 12.00 12.00 12.00 + + >>> erate # doctest: +NORMALIZE_WHITESPACE + Qtr0 Qtr1 Qtr2 Qtr3 + 0 12.55 12.55 12.55 12.55 + 1 12.55 12.55 12.55 12.55 + 2 12.55 12.55 12.55 12.55 """ + numnone = 0 if nrate is None: numnone += 1 if erate is None: numnone += 1 - if numnone != 1: - raise ValueError('One of the rates must be set to `None`') - - if isinstance(nrate, pd.Series): - pyr = getpyr(nrate) - prate = nrate.copy() - for index in range(len(nrate)): - prate[index] = nrate[index] / pyr - return prate + if prate is None: + numnone += 1 + if numnone != 2: + raise ValueError('Two of the rates must be set to `None`') - if isinstance(erate, pd.Series): - pyr = getpyr(erate) - prate = erate.copy() - for index in range(len(erate)): - prate[index] = 100 * (np.power(1 + erate[index]/100, 1. / pyr) - 1) - return prate + if isinstance(nrate, list) and isinstance(pyr, list) and len(nrate) != len(pyr): + raise ValueError('List must have the same length') + if isinstance(erate, list) and isinstance(pyr, list) and len(erate) != len(pyr): + raise ValueError('List must have the same length') + if isinstance(prate, list) and isinstance(pyr, list) and len(prate) != len(pyr): + raise ValueError('List must have the same length') + + maxlen = 1 + if isinstance(nrate, list): + maxlen = max(maxlen, len(nrate)) + if isinstance(erate, list): + maxlen = max(maxlen, len(erate)) + if isinstance(prate, list): + maxlen = max(maxlen, len(prate)) + if isinstance(pyr, list): + maxlen = max(maxlen, len(pyr)) + + if isinstance(pyr, (int, float)): + pyr = [pyr] * maxlen + pyr = numpy.array(pyr) if nrate is not None: - ## - ## - maxlen = 1 - if isinstance(nrate, (list, type(np.array), type(pd.Series))): - maxlen = max(maxlen, len(nrate)) - if isinstance(pyr, (list, type(np.array), type(pd.Series))): - maxlen = max(maxlen, len(pyr)) - # - if isinstance(nrate, (int, float)): - nrate = [nrate] * maxlen - nrate = pd.Series(nrate, dtype=np.float64) - if isinstance(pyr, (int, float)): - pyr = [pyr] * maxlen - pyr = pd.Series(pyr) - # - if len(nrate) != len(pyr): - raise ValueError('Lists must have the same length') - ## - ## - prate = nrate / pyr - if maxlen == 1: - prate = prate[0] - return prate + if isinstance(nrate, TimeSeries): + erate = nrate.copy() + prate = nrate.copy() + for index in range(len(nrate.data)): + prate[index] = nrate[index] / nrate.pyr + for index in range(len(nrate.data)): + erate[index] = 100 * (numpy.power(1 + prate[index]/100, nrate.pyr) - 1) + return (erate, prate) + else: + if isinstance(nrate, (int, float)): + nrate = [nrate] * maxlen + nrate = numpy.array(nrate) + prate = nrate / pyr + erate = 100 * (numpy.power(1 + prate/100, pyr) - 1) + prate = prate.tolist() + erate = erate.tolist() + if maxlen == 1: + prate = prate[0] + erate = erate[0] + return (erate, prate) if erate is not None: - ## - ## - maxlen = 1 - if isinstance(erate, (list, type(np.array), type(pd.Series))): - maxlen = max(maxlen, len(erate)) - if isinstance(pyr, (list, type(np.array), type(pd.Series))): - maxlen = max(maxlen, len(pyr)) - # - if isinstance(erate, (int, float)): - erate = [erate] * maxlen - erate = pd.Series(erate, dtype=np.float64) - # - if isinstance(pyr, (int, float)): - pyr = [pyr] * maxlen - pyr = pd.Series(pyr) - # - if len(erate) != len(pyr): - raise ValueError('Lists must have the same length') - ## - ## - prate = 100 * (np.power(1 + erate / 100, 1 / pyr) - 1) + if isinstance(erate, TimeSeries): + nrate = erate.copy() + prate = erate.copy() + for index in range(len(erate.data)): + prate[index] = 100 * (numpy.power(1 + erate[index]/100, 1. / erate.pyr) - 1) + for index in range(len(erate.data)): + nrate[index] = erate.pyr * prate[index] + return (nrate, prate) + else: + if isinstance(erate, (int, float)): + erate = [erate] * maxlen + erate = numpy.array(erate) + prate = 100 * (numpy.power(1 + erate / 100, 1 / pyr) - 1) + nrate = pyr * prate + prate = prate.tolist() + nrate = nrate.tolist() + if maxlen == 1: + prate = prate[0] + nrate = nrate[0] + return (nrate, prate) + + if isinstance(prate, TimeSeries): + erate = prate.copy() + nrate = prate.copy() + for index in range(len(prate.data)): + nrate[index] = prate[index] * prate.pyr + for index in range(len(prate.data)): + erate[index] = 100 * (numpy.power(1 + prate[index]/100, prate.pyr) - 1) + return (nrate, erate) + else: + if isinstance(prate, (int, float)): + prate = [prate] * maxlen + prate = numpy.array(prate) + erate = 100 * (numpy.power(1 + prate / 100, pyr) - 1) + nrate = pyr * prate + erate = erate.tolist() + nrate = nrate.tolist() if maxlen == 1: - prate = prate[0] - return prate - - - - + erate = erate[0] + nrate = nrate[0] + return (nrate, erate) -def to_discount_factor(nrate=None, erate=None, prate=None, base_date=None): +def to_discount_factor(nrate=None, erate=None, prate=None, base_date=0): """Returns a list of discount factors calculated as 1 / (1 + r)^(t - t0). Args: nrate (TimeSeries): Nominal interest rate per year. nrate (TimeSeries): Effective interest rate per year. prate (TimeSeries): Periodic interest rate. - base_date (string): basis time. + base_date (int, tuple): basis time. Returns: - TimeSeries of float values. + List of float values Only one of the interest rates must be supplied for the computation. - >>> nrate = interest_rate(const_value=4, periods=10, start='2016Q1', freq='Q') - >>> erate = effrate(nrate=nrate) - >>> prate = perrate(nrate=nrate) - >>> to_discount_factor(nrate=nrate, base_date='2016Q3') # doctest: +ELLIPSIS + >>> nrate = interest_rate(const_value=4,nper=10, pyr=4) + >>> erate, prate = iconv(nrate=nrate) + >>> to_discount_factor(nrate=nrate, base_date=2) # doctest: +ELLIPSIS [1.0201, 1.01, 1.0, 0.990..., 0.980..., 0.970..., 0.960..., 0.951..., 0.942..., 0.932...] - >>> to_discount_factor(erate=erate, base_date='2016Q3') # doctest: +ELLIPSIS + >>> to_discount_factor(erate=erate, base_date=2) # doctest: +ELLIPSIS [1.0201, 1.01, 1.0, 0.990..., 0.980..., 0.970..., 0.960..., 0.951..., 0.942..., 0.932...] - >>> to_discount_factor(prate=prate, base_date='2016Q3') # doctest: +ELLIPSIS + >>> to_discount_factor(prate=prate, base_date=2) # doctest: +ELLIPSIS [1.0201, 1.01, 1.0, 0.990..., 0.980..., 0.970..., 0.960..., 0.951..., 0.942..., 0.932...] """ numnone = 0 @@ -559,31 +270,19 @@ def to_discount_factor(nrate=None, erate=None, prate=None, base_date=None): raise ValueError('Two of the rates must be set to `None`') if nrate is not None: - pyr = getpyr(nrate) prate = nrate.copy() - for i,_ in enumerate(nrate): - prate[i] = nrate[i] / pyr # periodic rate - + prate.data = [x/nrate.pyr for x in nrate.data] # periodic rate if erate is not None: - pyr = getpyr(erate) prate = erate.copy() - for i,_ in enumerate(erate): - prate[i] = 100 * (np.power(1 + erate[i]/100, 1. / pyr) - 1) # periodic rate - - pyr = getpyr(prate) - - factor = [x/100 for x in prate] - + prate.data = [100 * (numpy.power(1 + x/100, 1. / erate.pyr) - 1) for x in erate.data] # periodic rate + factor = [x/100 for x in prate.data] for index, _ in enumerate(factor): if index == 0: factor[0] = 1 / (1 + factor[0]) else: factor[index] = factor[index-1] / (1 + factor[index]) - - if isinstance(base_date, str): - base_date = pd.Period(base_date, freq=prate.axes[0].freq) - base_date = period2pos(prate.axes[0], base_date) - + if isinstance(base_date, tuple): + base_date = _timeid2index(base_date, basis=prate.start, pyr=prate.pyr) div = factor[base_date] for index, _ in enumerate(factor): factor[index] = factor[index] / div @@ -605,9 +304,8 @@ def to_compound_factor(nrate=None, erate=None, prate=None, base_date=0): **Examples** - >>> nrate = interest_rate(const_value=4, start='2000', periods=10, freq='Q') - >>> erate = effrate(nrate=nrate) - >>> prate = perrate(nrate=nrate) + >>> nrate = interest_rate(const_value=4,nper=10, pyr=4) + >>> erate, prate = iconv(nrate=nrate) >>> to_compound_factor(prate=prate, base_date=2) # doctest: +ELLIPSIS [0.980..., 0.990..., 1.0, 1.01, 1.0201, 1.030..., 1.040..., 1.051..., 1.061..., 1.072...] @@ -630,7 +328,7 @@ def equivalent_rate(nrate=None, erate=None, prate=None): Args: nrate (TimeSeries): Nominal interest rate per year. - erate (TimeSeries): Effective interest rate per year. + nrate (TimeSeries): Effective interest rate per year. prate (TimeSeries): Periodic interest rate. Returns: @@ -638,7 +336,7 @@ def equivalent_rate(nrate=None, erate=None, prate=None): Only one of the interest rate must be supplied for the computation. - >>> equivalent_rate(prate=interest_rate([10]*5, start='2000Q1', freq='Q')) # doctest: +ELLIPSIS + >>> equivalent_rate(prate=interest_rate([10]*5)) # doctest: +ELLIPSIS 10.0... @@ -654,28 +352,33 @@ def equivalent_rate(nrate=None, erate=None, prate=None): raise ValueError('Two of the rates must be set to `None`') if nrate is not None: - pyr = getpyr(nrate) + value = nrate.tolist() factor = 1 - for element in nrate[1:]: - factor *= (1 + element / 100 / pyr) - return 100 * pyr * (factor**(1/(len(nrate) - 1)) - 1) + for element in value[1:]: + factor *= (1 + element / 100 / nrate.pyr) + return 100 * nrate.pyr * (factor**(1/(len(value) - 1)) - 1) if prate is not None: - pyr = getpyr(prate) + value = prate.tolist() factor = 1 - for element in prate[1:]: + for element in value[1:]: factor *= (1 + element / 100) - return 100 * (factor**(1/(len(prate) - 1)) - 1) + return 100 * (factor**(1/(len(value) - 1)) - 1) if erate is not None: - pyr = getpyr(erate) + value = erate.tolist() factor = 1 - for element in erate[1:]: - factor *= (1 + (numpy.power(1 + element/100, 1. / pyr) - 1)) + for element in value[1:]: + factor *= (1 + (numpy.power(1 + element/100, 1. / erate.pyr) - 1)) return 100 * (factor**(1/(len(value) - 1)) - 1) + + + + + if __name__ == "__main__": import doctest doctest.testmod() diff --git a/cashflows/savings.py b/cashflows/savings.py old mode 100644 new mode 100755 index f5b63c6..5145eac --- a/cashflows/savings.py +++ b/cashflows/savings.py @@ -9,106 +9,267 @@ # sys.path.insert(0, os.path.abspath('..')) -import pandas as pd +from cashflows.gtimeseries import TimeSeries, cashflow, interest_rate, verify_eq_time_range -#cashflows. -from cashflows.timeseries import cashflow, interest_rate, verify_period_range -from cashflows.common import getpyr -def savings(deposits, nrate, initbal=0): +def savings(deposits, nrate, initbal=0, noprint=True): """ Computes the final balance for a savings account with arbitrary deposits and withdrawls and variable interset rate. Args: - cflo (TimeSeries): Generic cashflow. deposits (TimeSeries): deposits to the account. nrate (TimeSeries): nominal interest rate paid by the account. initbal (float): initial balance of the account. + noprint (bool): prints summary report? Return: - A pandas.DataFrame. + interest, end_balance (TimeSeries, TimeSeries) **Examples** - >>> cflo = cashflow(const_value=[100]*12, start='2000Q1', freq='Q') - >>> nrate = interest_rate([10]*12, start='2000Q1', freq='Q') - >>> savings(deposits=cflo, nrate=nrate, initbal=0) # doctest: +NORMALIZE_WHITESPACE - Beginning_Balance Deposits Earned_Interest Ending_Balance \\ - 2000Q1 0.000000 100.0 0.000000 100.000000 - 2000Q2 100.000000 100.0 2.500000 202.500000 - 2000Q3 202.500000 100.0 5.062500 307.562500 - 2000Q4 307.562500 100.0 7.689063 415.251562 - 2001Q1 415.251562 100.0 10.381289 525.632852 - 2001Q2 525.632852 100.0 13.140821 638.773673 - 2001Q3 638.773673 100.0 15.969342 754.743015 - 2001Q4 754.743015 100.0 18.868575 873.611590 - 2002Q1 873.611590 100.0 21.840290 995.451880 - 2002Q2 995.451880 100.0 24.886297 1120.338177 - 2002Q3 1120.338177 100.0 28.008454 1248.346631 - 2002Q4 1248.346631 100.0 31.208666 1379.555297 - - Nominal_Rate - 2000Q1 10.0 - 2000Q2 10.0 - 2000Q3 10.0 - 2000Q4 10.0 - 2001Q1 10.0 - 2001Q2 10.0 - 2001Q3 10.0 - 2001Q4 10.0 - 2002Q1 10.0 - 2002Q2 10.0 - 2002Q3 10.0 - 2002Q4 10.0 - - >>> cflo = cashflow(const_value=[0, 100, 0, 100, 100], start='2000Q1', freq='A') - >>> nrate = interest_rate([0, 1, 2, 3, 4], start='2000Q1', freq='A') - >>> savings(deposits=cflo, nrate=nrate, initbal=1000) # doctest: +NORMALIZE_WHITESPACE - Beginning_Balance Deposits Earned_Interest Ending_Balance \\ - 2000 1000.000 0.0 0.00000 1000.00000 - 2001 1000.000 100.0 10.00000 1110.00000 - 2002 1110.000 0.0 22.20000 1132.20000 - 2003 1132.200 100.0 33.96600 1266.16600 - 2004 1266.166 100.0 50.64664 1416.81264 - - Nominal_Rate - 2000 0.0 - 2001 1.0 - 2002 2.0 - 2003 3.0 - 2004 4.0 + >>> cflo = cashflow(const_value=[100] * 12, pyr=4) + >>> nrate = interest_rate([10] * 12, pyr=4) + >>> savings(deposits=cflo, nrate=nrate, initbal=0, noprint=False) # doctest: +NORMALIZE_WHITESPACE + t Beginning Deposit Earned Ending + Balance Interest Balance + ----------------------------------------------- + (0, 0) 0.00 100.00 0.00 100.00 + (0, 1) 100.00 100.00 2.50 202.50 + (0, 2) 202.50 100.00 5.06 307.56 + (0, 3) 307.56 100.00 7.69 415.25 + (1, 0) 415.25 100.00 10.38 525.63 + (1, 1) 525.63 100.00 13.14 638.77 + (1, 2) 638.77 100.00 15.97 754.74 + (1, 3) 754.74 100.00 18.87 873.61 + (2, 0) 873.61 100.00 21.84 995.45 + (2, 1) 995.45 100.00 24.89 1120.34 + (2, 2) 1120.34 100.00 28.01 1248.35 + (2, 3) 1248.35 100.00 31.21 1379.56 + + + >>> cflo = cashflow(const_value=[100] * 5, spec=[(0, 0), (2, 0)]) + >>> nrate = interest_rate([0, 1, 2, 3, 4]) + >>> savings(deposits=cflo, nrate=nrate, initbal=1000, noprint=False) # doctest: +NORMALIZE_WHITESPACE + t Beginning Deposit Earned Ending + Balance Interest Balance + --------------------------------------------- + (0,) 1000.00 0.00 0.00 1000.00 + (1,) 1000.00 100.00 10.00 1110.00 + (2,) 1110.00 0.00 22.20 1132.20 + (3,) 1132.20 100.00 33.97 1266.17 + (4,) 1266.17 100.00 50.65 1416.81 + """ - verify_period_range([deposits, nrate]) + verify_eq_time_range(deposits, nrate) begbal = deposits.copy() interest = deposits.copy() endbal = deposits.copy() - pyr = getpyr(deposits) for time, _ in enumerate(deposits): if time == 0: begbal[0] = initbal - interest[0] = begbal[0] * nrate[0] / 100 / pyr + interest[0] = begbal[0] * nrate[0] / 100 / nrate.pyr endbal[0] = begbal[0] + deposits[0] + interest[0] else: begbal[time] = endbal[time - 1] - interest[time] = begbal[time] * nrate[time] / 100 / pyr + interest[time] = begbal[time] * nrate[time] / 100 / nrate.pyr if deposits[time] < 0 and -deposits[time] > begbal[time] + interest[time]: deposits[time] = -(begbal[time] + interest[time]) endbal[time] = begbal[time] + deposits[time] + interest[time] - table = pd.DataFrame({'Beginning_Balance' : begbal, - 'Deposits' : deposits, - 'Nominal_Rate':nrate, - 'Earned_Interest': interest, - 'Ending_Balance': endbal }) + if noprint is True: + return (interest, endbal) + + + + len_timeid = len(deposits.end.__repr__()) + len_number = max(len('{:1.2f}'.format(endbal[-1])), len('{:1.2f}'.format(begbal[0])), 9) + + fmt_timeid = '{:<' + '{:d}'.format(len_timeid) + 's}' + fmt_number = ' {:' + '{:d}'.format(len_number) + '.2f}' + fmt_header = ' {:>' + '{:d}'.format(len_number) + 's}' + + if deposits.pyr == 1: + xmajor, = deposits.start + xminor = 0 + else: + xmajor, xminor = deposits.start + + txt = [] + header = fmt_timeid.format('t') + header += fmt_header.format('Beginning') + header += fmt_header.format('Deposit') + header += fmt_header.format('Earned') + header += fmt_header.format('Ending') + txt.append(header) + + header = fmt_timeid.format('') + header += fmt_header.format('Balance') + header += fmt_header.format('') + header += fmt_header.format('Interest') + header += fmt_header.format('Balance') + txt.append(header) + + txt.append('-' * len_timeid + '-----' + '-' * len_number * 4) + + + for time, _ in enumerate(deposits): + if deposits.pyr == 1: + timeid = (xmajor,) + else: + timeid = (xmajor, xminor) + fmt = fmt_timeid + fmt_number * 4 + txt.append(fmt.format(timeid.__repr__(), + begbal[time], + deposits[time], + interest[time], + endbal[time])) + if deposits.pyr == 1: + xmajor += 1 + else: + xminor += 1 + if xminor == deposits.pyr: + xminor = 0 + xmajor += 1 + + print('\n'.join(txt)) + + - return table +# class Savings(): +# """Creates a Savings object. +# +# Args: +# deposits (Cashflow): Deposits to the account. +# rate (float, Rate): Interest rate. +# initbal (float): Initial balance of the account. +# +# Returns: +# An Savigs object. +# +# Member functions can be used to extract information of the object. +# +# +# +# """ +# def __init__(self, deposits, rate=0, initbal=0): +# """ +# Args: +# deposits (Cashflow): deposits to the account. +# rate (float, Rate): interest rate paid by the account. +# initbal (float): initial balance of the accout. +# +# Return: +# A `Savings` object. +# +# """ +# +# if not isinstance(deposits, Cashflow): +# raise ValueError('deposits must be a Cashflow instance') +# +# nper = len(deposits) +# +# if isinstance(rate, (int, float)): +# rate = Rate(constValue=rate, nper=nper) +# +# +# begbal = Cashflow(nper=nper, constValue=0) +# interest = Cashflow(nper=nper, constValue=0) +# endbal = Cashflow(nper=nper, constValue=0) +# +# ## +# ## balance calculation +# ## +# for time in range(nper): +# +# if time == 0: +# # +# begbal[time] = initbal +# interest[time] = 0 +# endbal[time] = begbal[time] + deposits[time] + interest[time] +# # +# else: +# # +# begbal[time] = endbal[time - 1] +# interest[time] = begbal[time] * rate[time] +# +# if deposits[time] < 0 and -deposits[time] > begbal[time] + interest[time]: +# # +# deposits[time] = -(begbal[time] + interest[time]) +# # +# +# endbal[time] = begbal[time] + deposits[time] + interest[time] +# # +# +# self._begbal = begbal +# self._deposits = deposits +# self._interest = interest +# self._endbal = endbal +# +# +# ## +# ## accounts +# ## +# def interest(self): +# """Returns the earned interest as a Cashflow object.""" +# return self._interest.copy() +# +# # def deposits(self): +# # return self._deposits.copy() +# +# def begbal(self): +# """Returns the balance at the begining of each compounding period as a +# Cashflow object.""" +# return self._begbal.copy() +# +# def endbal(self): +# """Returns the balance at the ending of each compounding period as a +# Cashflow object.""" +# return self._endbal.copy() +# +# ## +# ## conversion to generic cashflow object +# ## +# def to_cashflow(self): +# """Converts the object to a equivalent cashflow.""" +# nper = len(self._interest.tolist()) +# cashflow = Cashflow(nper=nper) +# # +# for time in range(nper): +# # +# cashflow[time] = -self._deposits[time] +# # +# cashflow[0] = -self._endbal[0] +# cashflow[-1] = cashflow[-1] + self._endbal[-1] +# return cashflow +# +# +# +# def __repr__(self): +# +# txt = [''] +# txt.append('') +# +# txt.append(' t Beginning Deposit Earned Ending') +# txt.append(' balance Interest balance') +# txt.append('------------------------------------------------------------') +# +# for time in range(len(self._deposits)): +# +# fmt = ' {:<3d} {:12.2f} {:12.2f} {:12.2f} {:12.2f}' +# txt.append(fmt.format(time, +# self._begbal[time], +# self._deposits[time], +# self._interest[time], +# self._endbal[time])) +# +# return '\n'.join(txt) if __name__ == "__main__": import doctest diff --git a/cashflows/taxing.py b/cashflows/taxing.py old mode 100644 new mode 100755 index fab5415..94e3e20 --- a/cashflows/taxing.py +++ b/cashflows/taxing.py @@ -5,10 +5,8 @@ """ -import pandas as pd -# cashflows. -from cashflows.timeseries import * +from cashflows.gtimeseries import * from cashflows.common import _vars2list @@ -32,54 +30,35 @@ def after_tax_cashflow(cflo, tax_rate): **Example*** - >>> cflo = cashflow(const_value=[-50] + [100] * 4, start='2010', freq='A') - >>> tax_rate = interest_rate(const_value=[10] * 5, start='2010', freq='A') + >>> cflo = cashflow(const_value=[100] * 5, spec=(0, -100)) + >>> tax_rate = interest_rate(const_value=[10] * 5) >>> after_tax_cashflow(cflo=cflo, tax_rate=tax_rate) # doctest: +NORMALIZE_WHITESPACE - 2010 0.0 - 2011 10.0 - 2012 10.0 - 2013 10.0 - 2014 10.0 - Freq: A-DEC, dtype: float64 + Time Series: + Start = (0,) + End = (4,) + pyr = 1 + Data = (0,) 0.00 + (1,)-(4,) [4] 10.00 """ - if not isinstance(cflo, pd.Series): - raise TypeError("cashflow must be a pandas.Series") - if not isinstance(tax_rate, pd.Series): - raise TypeError("tax_rate must be a pandas.Series") - verify_period_range([cflo, tax_rate]) - result = cflo.copy() - for time, _ in enumerate(cflo): - if result[time] > 0: - result[time] *= tax_rate[time] / 100 - else: - result[time] = 0 - return result - - ## - ## version vectorizada - ## - - # params = _vars2list([cflo, tax_rate]) - # cflo = params[0] - # tax_rate = params[1] - # retval = [] - # for xcflo, xtax_rate in zip(cflo, tax_rate): - # if not isinstance(xcflo, pd.Series): - # raise TypeError("cashflow must be a TimeSeries") - # verify_period_range([xcflo, xtax_rate]) - # result = xcflo.copy() - # for time, _ in enumerate(xcflo): - # if result[time] > 0: - # result[time] *= xtax_rate[time] / 100 - # else: - # result[time] = 0 - # retval.append(result) - # if len(retval) == 1: - # return retval[0] - # return retval - - + params = _vars2list([cflo, tax_rate]) + cflo = params[0] + tax_rate = params[1] + retval = [] + for xcflo, xtax_rate in zip(cflo, tax_rate): + if not isinstance(xcflo, TimeSeries): + raise TypeError("cashflow must be a TimeSeries") + verify_eq_time_range(xcflo, xtax_rate) + result = xcflo.copy() + for time, _ in enumerate(xcflo): + if result[time] > 0: + result[time] *= xtax_rate[time] / 100 + else: + result[time] = 0 + retval.append(result) + if len(retval) == 1: + return retval[0] + return retval if __name__ == "__main__": import doctest diff --git a/cashflows/tests/__init__.py b/cashflows/tests/__init__.py old mode 100644 new mode 100755 diff --git a/cashflows/tests/basics_test.py b/cashflows/tests/basics_test.py old mode 100644 new mode 100755 index e009127..add0fcd --- a/cashflows/tests/basics_test.py +++ b/cashflows/tests/basics_test.py @@ -7,62 +7,62 @@ import unittest import numpy as np -#from cashflows.rate import iconv +from cashflows.rate import iconv # from cashflows.basics import compound -# class BasicsTestCase(unittest.TestCase): -# """Testing basic financial calculations""" -# pass -# #def setUp(self): -# # self.tvm = Widget('The widget') -# -# #def tearDown(self): -# # self.widget.dispose() -# -# # def test_compound_simple(self): -# # """Test compound calculations over numbers""" -# # pval = -729.88 -# # rate = 0.065 -# # fval = 1000.0 -# # nper = 5 -# # self.assertAlmostEqual(tvmm(pval=pval, nomrate=rate, nper=5), -# # fval, delta=0.01) -# # self.assertAlmostEqual(tvmm(fval=fval, nomrate=rate, nper=5), -# # pval, delta=0.01) -# # self.assertAlmostEqual(tvmm(pval=pval, fval=fval, nper=5), -# # rate, delta=0.01) -# # self.assertAlmostEqual(tvmm(pval=pval, nomrate=rate, fval=fval), -# # nper, delta=0.01) -# -# -# class RateProc_TestCase(unittest.TestCase): -# """Test of rate conversions""" -# -# def test_rate1(self): -# # data -# nrate = [float(12)] * 3 -# erate = [100*((1.01)**12-1)] * 3 -# prate = [float(1)] * 3 -# -# # case 1 -# (xnrate, xprate) = iconv(erate=erate, pyr=12) -# np.testing.assert_almost_equal(xnrate, nrate) -# np.testing.assert_almost_equal(xprate, prate) -# -# # case 2 -# (xerate, xprate) = iconv(nrate=nrate, pyr=12) -# np.testing.assert_almost_equal(xerate, erate) -# np.testing.assert_almost_equal(xprate, prate) -# -# # case 3 -# (xnrate, xerate) = iconv(prate=prate, pyr=12) -# np.testing.assert_almost_equal(xnrate, nrate) -# np.testing.assert_almost_equal(xerate, erate) -# -# -# -# -# if __name__ == '__main__': -# unittest.main() +class BasicsTestCase(unittest.TestCase): + """Testing basic financial calculations""" + pass + #def setUp(self): + # self.tvm = Widget('The widget') + + #def tearDown(self): + # self.widget.dispose() + + # def test_compound_simple(self): + # """Test compound calculations over numbers""" + # pval = -729.88 + # rate = 0.065 + # fval = 1000.0 + # nper = 5 + # self.assertAlmostEqual(tvmm(pval=pval, nomrate=rate, nper=5), + # fval, delta=0.01) + # self.assertAlmostEqual(tvmm(fval=fval, nomrate=rate, nper=5), + # pval, delta=0.01) + # self.assertAlmostEqual(tvmm(pval=pval, fval=fval, nper=5), + # rate, delta=0.01) + # self.assertAlmostEqual(tvmm(pval=pval, nomrate=rate, fval=fval), + # nper, delta=0.01) + + +class RateProc_TestCase(unittest.TestCase): + """Test of rate conversions""" + + def test_rate1(self): + # data + nrate = [float(12)] * 3 + erate = [100*((1.01)**12-1)] * 3 + prate = [float(1)] * 3 + + # case 1 + (xnrate, xprate) = iconv(erate=erate, pyr=12) + np.testing.assert_almost_equal(xnrate, nrate) + np.testing.assert_almost_equal(xprate, prate) + + # case 2 + (xerate, xprate) = iconv(nrate=nrate, pyr=12) + np.testing.assert_almost_equal(xerate, erate) + np.testing.assert_almost_equal(xprate, prate) + + # case 3 + (xnrate, xerate) = iconv(prate=prate, pyr=12) + np.testing.assert_almost_equal(xnrate, nrate) + np.testing.assert_almost_equal(xerate, erate) + + + + +if __name__ == '__main__': + unittest.main() diff --git a/cashflows/timeseries.py b/cashflows/timeseries.py deleted file mode 100644 index 2d806d7..0000000 --- a/cashflows/timeseries.py +++ /dev/null @@ -1,968 +0,0 @@ - -import numpy as np -import pandas as pd - -def period2pos(index, date): - x = [i for i, elem in enumerate(index) if elem == date] - if x == []: - raise ValueError('Date does not exists: ' + date.__repr__()) - return x[0] - -def verify_period_range(x): - if not isinstance(x, list): - raise ValueError('Argument must be a list: ' + x.__repr__()) - if len(x) == 1: - return - for elem in x: - if not isinstance(elem, pd.Series): - raise ValueError('pandas.Series expected: ' + elem.__repr__()) - allTrue = all(x[0].axes[0] == elem.axes[0]) - if allTrue is False: - raise ValueError('Series with different period_range') - - -def textplot(cflo): - """Text plot of a cashflow. - - Args: - cflo (TimeSeries): Generic cashflow. - - Returns: - None. - - >>> cflo = cashflow(const_value=[-10, 5, 0, 20] * 3, start='2000Q1', freq='Q') - >>> textplot(cflo)# doctest: +NORMALIZE_WHITESPACE - time value +------------------+------------------+ - 2000Q1 -10.00 ********** - 2000Q2 5.00 ***** - 2000Q3 0.00 * - 2000Q4 20.00 ******************** - 2001Q1 -10.00 ********** - 2001Q2 5.00 ***** - 2001Q3 0.00 * - 2001Q4 20.00 ******************** - 2002Q1 -10.00 ********** - 2002Q2 5.00 ***** - 2002Q3 0.00 * - 2002Q4 20.00 ******************** - - """ - - - timeid = cflo.index.to_series().astype(str) - - - len_timeid = len(timeid[-1].__repr__()) - fmt_timeid = '{:<' + '{:d}'.format(len_timeid) + 's}' - - len_number = 0 - for value in cflo: - len_number = max(len_number, len('{:1.2f}'.format(value))) - - fmt_number = ' {:' + '{:d}'.format(len_number) + '.2f}' - fmt_header = ' {:>' + '{:d}'.format(len_number) + 's}' - - # if cflo.pyr == 1: - # xmajor, = cflo.start - # xminor = 0 - # else: - # xmajor, xminor = cflo.start - - maxval = max(abs(cflo)) - width = 20 - - txt = [] - txtrow = fmt_timeid.format("time") + fmt_header.format('value') - txtrow += " +" + "-" * (width - 2) + "+" + "-" * (width - 2) + "+" - txt.append(txtrow) - - for value, timeid in zip(cflo, cflo.index.to_series().astype(str)): - - # if cflo.pyr == 1: - # timeid = (xmajor,) - # else: - # timeid = (xmajor, xminor) - - txtrow = fmt_timeid.format(timeid.__str__()) - txtrow += fmt_number.format(value) - - # fmt_row = " * " - xlim = int(width * abs(value / maxval)) - if value < 0: - txtrow += " " + " " * (width - xlim) + '*' * (xlim) - elif value > 0: - txtrow += " " + " " * (width - 1) + "*" * (xlim) - else: - txtrow += " " + " " * (width - 1) + '*' - - txt.append(txtrow) - - - print('\n'.join(txt)) - - - - -def cashflow(const_value=0, start=None, end=None, periods=None, freq='A', chgpts=None): - """Returns a generic cashflow as a pandas.Series object. - - Args: - const_value (number): constant value for all time series. - start (string): Date as string using pandas convetion for dates. - end (string): Date as string using pandas convetion for dates. - peridos (integer): Length of the time seriesself. - freq (string): String indicating the period of time series. Valid values - are `'A'`, `'BA'`, `'Q'`, `'BQ'`, `'M'`, `'BM'`, `'CBM'`, `'SM'`, `'6M'`, - `'6BM'` and `'6CMB'`. See https://pandas.pydata.org/pandas-docs/stable/timeseries.html#timeseries-offset-aliases - chgpts (dict): Dictionary indicating point changes in the values of the time series. - - Returns: - A Pandas time series object. - - - >>> cashflow(const_value=1.0, start='2000Q1', periods=8, freq='Q') # doctest: +NORMALIZE_WHITESPACE - 2000Q1 1.0 - 2000Q2 1.0 - 2000Q3 1.0 - 2000Q4 1.0 - 2001Q1 1.0 - 2001Q2 1.0 - 2001Q3 1.0 - 2001Q4 1.0 - Freq: Q-DEC, dtype: float64 - - >>> cashflow(const_value=[10]*10, start='2000Q1', freq='Q') # doctest: +NORMALIZE_WHITESPACE - 2000Q1 10.0 - 2000Q2 10.0 - 2000Q3 10.0 - 2000Q4 10.0 - 2001Q1 10.0 - 2001Q2 10.0 - 2001Q3 10.0 - 2001Q4 10.0 - 2002Q1 10.0 - 2002Q2 10.0 - Freq: Q-DEC, dtype: float64 - - >>> x = cashflow(const_value=[0, 1, 2, 3], start='2000Q1', freq='Q') - >>> x[3] = 10 - >>> x # doctest: +NORMALIZE_WHITESPACE - 2000Q1 0.0 - 2000Q2 1.0 - 2000Q3 2.0 - 2000Q4 10.0 - Freq: Q-DEC, dtype: float64 - - >>> x[3] # doctest: +NORMALIZE_WHITESPACE - 10.0 - - >>> x['2000Q4'] = 0 - >>> x # doctest: +NORMALIZE_WHITESPACE - 2000Q1 0.0 - 2000Q2 1.0 - 2000Q3 2.0 - 2000Q4 0.0 - Freq: Q-DEC, dtype: float64 - - >>> x['2000Q3'] # doctest: +NORMALIZE_WHITESPACE - 2.0 - - >>> cashflow(const_value=[0, 1, 2, 3, 4, 5], freq='Q', start='2000Q1').cumsum() # doctest: +NORMALIZE_WHITESPACE - 2000Q1 0.0 - 2000Q2 1.0 - 2000Q3 3.0 - 2000Q4 6.0 - 2001Q1 10.0 - 2001Q2 15.0 - Freq: Q-DEC, dtype: float64 - - >>> cashflow(const_value=0, freq='Q', periods=6, start='2000Q1', chgpts={2:10}) # doctest: +NORMALIZE_WHITESPACE - 2000Q1 0.0 - 2000Q2 0.0 - 2000Q3 10.0 - 2000Q4 0.0 - 2001Q1 0.0 - 2001Q2 0.0 - Freq: Q-DEC, dtype: float64 - - >>> cashflow(const_value=0, freq='Q', periods=6, start='2000Q1', chgpts={'2000Q3':10}) # doctest: +NORMALIZE_WHITESPACE - 2000Q1 0.0 - 2000Q2 0.0 - 2000Q3 10.0 - 2000Q4 0.0 - 2001Q1 0.0 - 2001Q2 0.0 - Freq: Q-DEC, dtype: float64 - - - """ - if freq not in ['A', 'BA', 'Q', 'BQ', 'M', 'BM', 'CBM', 'SM', '6M', '6BM', '6CMB']: - msg = 'Invalid freq value: ' + freq.__repr__() - raise ValueError(msg) - if isinstance(const_value, list): - periods = len(const_value) - prng = pd.period_range(start=start, end=end, periods=periods, freq=freq) - periods = len(prng) - if not isinstance(const_value, list): - const_value = [const_value] * periods - time_series = pd.Series(data=const_value, index=prng, dtype=np.float64) - - if isinstance(chgpts, dict): - keys = sorted(chgpts.keys()) - for k in keys: - if isinstance(k, int): - x = time_series.axes[0][k] - else: - x = k - time_series[x] = chgpts[k] - - return time_series - - -def interest_rate(const_value=0, start=None, end=None, periods=None, freq='A', chgpts=None): - """Creates a time series object specified as a interest rate. - - Args: - const_value (number): constant value for all time series. - start (string): Date as string using pandas convetion for dates. - end (string): Date as string using pandas convetion for dates. - peridos (integer): Length of the time seriesself. - freq (string): String indicating the period of time series. Valid values - are `'A'`, `'BA'`, `'Q'`, `'BQ'`, `'M'`, `'BM'`, `'CBM'`, `'SM'`, `'6M'`, - `'6BM'` and `'6CMB'`. See https://pandas.pydata.org/pandas-docs/stable/timeseries.html#timeseries-offset-aliases - chgpts (dict): Dictionary indicating point changes in the values of the time series. - - Returns: - A Pandas time series object. - - - >>> chgpts = {'2000Q4':10} - >>> interest_rate(const_value=1, start='2000Q1', periods=8, freq='Q', chgpts=chgpts) # doctest: +NORMALIZE_WHITESPACE - 2000Q1 1.0 - 2000Q2 1.0 - 2000Q3 1.0 - 2000Q4 10.0 - 2001Q1 10.0 - 2001Q2 10.0 - 2001Q3 10.0 - 2001Q4 10.0 - Freq: Q-DEC, dtype: float64 - - >>> chgpts = {'2000Q4':10, '2001Q2':20} - >>> interest_rate(const_value=1, start='2000Q1', periods=8, freq='Q', chgpts=chgpts) # doctest: +NORMALIZE_WHITESPACE - 2000Q1 1.0 - 2000Q2 1.0 - 2000Q3 1.0 - 2000Q4 10.0 - 2001Q1 10.0 - 2001Q2 20.0 - 2001Q3 20.0 - 2001Q4 20.0 - Freq: Q-DEC, dtype: float64 - - >>> chgpts = {3:10} - >>> interest_rate(const_value=1, start='2000Q1', periods=8, freq='Q', chgpts=chgpts) # doctest: +NORMALIZE_WHITESPACE - 2000Q1 1.0 - 2000Q2 1.0 - 2000Q3 1.0 - 2000Q4 10.0 - 2001Q1 10.0 - 2001Q2 10.0 - 2001Q3 10.0 - 2001Q4 10.0 - Freq: Q-DEC, dtype: float64 - - >>> chgpts = {3:10, 6:20} - >>> interest_rate(const_value=1, start='2000Q1', periods=8, freq='Q', chgpts=chgpts) # doctest: +NORMALIZE_WHITESPACE - 2000Q1 1.0 - 2000Q2 1.0 - 2000Q3 1.0 - 2000Q4 10.0 - 2001Q1 10.0 - 2001Q2 10.0 - 2001Q3 20.0 - 2001Q4 20.0 - Freq: Q-DEC, dtype: float64 - - >>> interest_rate(const_value=[10]*12, start='2000-1', freq='M') # doctest: +NORMALIZE_WHITESPACE - 2000-01 10.0 - 2000-02 10.0 - 2000-03 10.0 - 2000-04 10.0 - 2000-05 10.0 - 2000-06 10.0 - 2000-07 10.0 - 2000-08 10.0 - 2000-09 10.0 - 2000-10 10.0 - 2000-11 10.0 - 2000-12 10.0 - Freq: M, dtype: float64 - - """ - time_series = cashflow(const_value=const_value, start=start, end=end, periods=periods, freq=freq) - if isinstance(chgpts, dict): - keys = sorted(chgpts.keys()) - for k in keys: - if isinstance(k, int): - x = time_series.axes[0][k] - else: - x = k - for t in time_series.axes[0]: - if t >= pd.Period(x, freq=freq): - time_series[t] = chgpts[k] - return time_series - - - -#### esto ya no -# class xTimeSeries(pd.Series): -# -# def __init__(self, start=None, end=None, nper=None, freq='A'): -# """Creates a generic time series. -# -# >>> xTimeSeries(start="2001", nper=10) # doctest: +NORMALIZE_WHITESPACE -# Time Series: -# Start = 2001-12-31 -# End = 2010-12-31 -# pyr = 1 -# Data = 2001-12-31 0.0 -# 2002-12-31 0.0 -# 2003-12-31 0.0 -# 2004-12-31 0.0 -# 2005-12-31 0.0 -# 2006-12-31 0.0 -# 2007-12-31 0.0 -# 2008-12-31 0.0 -# 2009-12-31 0.0 -# 2010-12-31 0.0 -# -# # >>> xTimeSeries(end="2010", nper=10) # doctest: +NORMALIZE_WHITESPACE -# # Time Series: -# # Start = (2001,) -# # End = (2010,) -# # pyr = 1 -# # Data = (2001,)-(2010,) [10] 0.00 -# # -# # >>> xTimeSeries(start="2000", end="2002") # doctest: +NORMALIZE_WHITESPACE -# # Time Series: -# # Start = (2000,) -# # End = (2002,) -# # pyr = 1 -# # Data = (2000,)-(2002,) [3] 0.00 -# # -# # >>> xTimeSeries(start="2000", end="2002", nper=3) # doctest: +NORMALIZE_WHITESPACE -# # Time Series: -# # Start = (2000,) -# # End = (2002,) -# # pyr = 1 -# # Data = (2000,)-(2002,) [3] 0.00 -# # -# # >>> xTimeSeries(start="2000Q1", nper=4, freq='Q') # doctest: +NORMALIZE_WHITESPACE -# # Time Series: -# # Start = (2000,) -# # End = (2002,) -# # pyr = 1 -# # Data = (2000,)-(2002,) [3] 0.00 -# -# -# """ -# -# pyr = None -# -# if freq in ['A', 'BA']: -# pyr = 1 -# -# if freq in ['Q', 'BQ']: -# pyr = 4 -# -# if freq in ['M', 'BM', 'CBM']: -# pyr = 12 -# -# if freq in ['SM']: -# pyr = 24 -# -# -# if pyr is None: -# msg = 'Invalid freq value: ' + freq.__repr__() -# raise ValueError(msg) -# -# # print(start) -# # print(end) -# # print(nper) -# # print(freq) -# -# index = pd.date_range(start=start, end=end, periods=nper, freq=freq) -# nper = len(index) -# super().__init__(data=[0.0] * nper, index=index) -# self.start = index[0] -# self.end = index[-1] -# self.pyr = pyr -# self.freq = freq -# -# # self.name = '' -# #self.nper = nper -# -# #self.start = index[1] -# #self.end = index[-1] -# #self.pyr = pyr -# #self.freq = freq -# # self.data = pd.Series([[0] * nper]) -# -# -# -# def __repr__(self): -# """Print the time series.""" -# -# -# -# txt = ['Time Series:'] -# txt += [' Start = {:s}'.format(pd.to_datetime(self.start).strftime('%Y-%m-%d').__str__())] -# txt += [' End = {:s}'.format(pd.to_datetime(self.end).strftime('%Y-%m-%d').__str__())] -# txt += [' pyr = {:s}'.format(self.pyr.__repr__())] -# -# x = pd.to_datetime(self.index[0]).strftime('%Y-%m-%d').__str__() -# -# -# txt += [' Data = ' + x + ' ' + self.values[0].__str__()] -# for (t, v) in zip(self.index[1:], self.values[1:]): -# x = pd.to_datetime(t).strftime('%Y-%m-%d').__str__() -# txt += [' ' + x + ' ' + v.__str__()] -# txt = '\n'.join(txt) + '\n' -# return txt -# -# -# # to_datetime(df[['year', 'month', 'day']]) -# # return(super().__repr__()) -# -# # if self.pyr in [4, 7, 12]: -# # return self.__repr4712__() -# # -# -# # -# # if self.pyr == 1: -# # imajor, = self.start -# # iminor = 0 -# # else: -# # imajor, iminor = self.start -# # -# # txt_date = [] -# # txt_freq = [] -# # txt_val = [] -# # -# # period = 0 -# # while period < len(self.data): -# # -# # freq = 1 -# # if self.pyr == 1: -# # beg_date = end_date = (imajor,) -# # else: -# # beg_date = end_date = (imajor, iminor) -# # -# # while period + freq < len(self.data) and \ -# # self.data[period] == self.data[period + freq]: -# # freq += 1 -# # iminor += 1 -# # if iminor >= self.pyr: -# # iminor = 0 -# # imajor += 1 -# # -# # if self.pyr == 1: -# # end_date = (imajor,) -# # else: -# # end_date = (imajor, iminor) -# # -# # iminor += 1 -# # if iminor >= self.pyr: -# # iminor = 0 -# # imajor += 1 -# # -# # if freq == 1: -# # -# # # iminor += 1 -# # # if iminor >= self.pyr: -# # # iminor = 0 -# # # imajor += 1 -# # -# # txt_date += ['{:s}'.format(beg_date.__str__())] -# # txt_freq += [' '] -# # txt_val += ['{:1.2f}'.format(self.data[period])] -# # else: -# # fmt = '{:s}-{:s}' -# # txt_date += [fmt.format(beg_date.__str__(), end_date.__str__())] -# # txt_freq += ['[{:d}]'.format(freq)] -# # txt_val += ['{:1.2f}'.format(self.data[period])] -# # -# # -# # -# # period += freq -# # -# # max_date = max_freq = max_val = 0 -# # for index, _ in enumerate(txt_date): -# # max_date = max(max_date, len(txt_date[index])) -# # max_freq = max(max_freq, len(txt_freq[index])) -# # max_val = max(max_val, len(txt_val[index])) -# # -# # fmt = ' {:' + '{:d}'.format(max_date) + 's} ' -# # fmt += '{:' + '{:d}'.format(max_freq) + 's} ' -# # fmt += '{:>' + '{:d}'.format(max_val) + 's} ' -# # -# # -# # for index, _ in enumerate(txt_date): -# # if index == 0: -# # txt += ['Data =' + fmt.format(txt_date[index], txt_freq[index], txt_val[index])] -# # else: -# # txt += [' ' +fmt.format(txt_date[index], txt_freq[index], txt_val[index])] -# # -# # return '\n'.join(txt)+'\n' -# # -# # -# # def __repr4712__(self): -# # """ Prints as table with quarters, days or months. -# # -# # # >>> TimeSeries(start=(1, 0), pyr=4) # doctest: +NORMALIZE_WHITESPACE -# # # Qtr0 Qtr1 Qtr2 Qtr3 -# # # 1 0.00 -# # -# # # >>> TimeSeries(start=(1, 1), pyr=7) # doctest: +NORMALIZE_WHITESPACE -# # # Mon Tue Wed Thu Fri Sat Sun -# # # 1 0.00 -# # -# # # >>> TimeSeries(start=(1, 1), pyr=12) # doctest: +NORMALIZE_WHITESPACE -# # # Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec -# # # 1 0.00 -# # -# # -# # """ -# # smajor, sminor = self.start -# # emajor, eminor = self.end -# # -# # imajor = smajor -# # iminor = 0 -# # iper = 0 -# # -# # maxlen = 0 -# # for data in self.data: -# # maxlen = max(maxlen, len('{:.2f}'.format(data))) -# # -# # maxlen = maxlen - 3 -# # -# # fmt_major = '{:<' + '{:= self.pyr: -# # iminor = 0 -# # imajor += 1 -# # txt.append(sline) -# # if is_ok is True: -# # sline = fmt_major.format(imajor) -# # else: -# # sline = '' -# # -# # txt.append(sline) -# # return '\n'.join(txt) -# # -# # -# # -# # def __getitem__(self, key): -# # """gets the item -# # -# # -# # -# # """ -# # if isinstance(key, tuple): -# # key = _timeid2index(timeid=key, basis=self.start, pyr=self.pyr) -# # return self.data[key] -# # -# # def __setitem__(self, key, value): -# # -# # if isinstance(key, tuple): -# # key = _timeid2index(timeid=key, basis=self.start, pyr=self.pyr) -# # self.data[key] = value -# # -# # def __len__(self): -# # return len(self.data) -# # -# # def __iter__(self): -# # return self.data.__iter__() -# # -# # def __next__(self): -# # return self.data.__next__() -# # -# # -# # def tolist(self): -# # """Returns the values as a list""" -# # return [x for x in self.data] -# # -# # def copy(self): -# # """returns a copy of the time series""" -# # result = TimeSeries(start=self.start, end=self.end, nper=len(self.data), pyr=self.pyr) -# # result.data = [value for value in self.data] -# # return result -# # -# # -# # def cumsum(self): -# # """returns the cumulative sum of the time series""" -# # result = TimeSeries(start=self.start, end=self.end, nper=len(self.data), pyr=self.pyr) -# # result.data = [value for value in self.data] -# # for index in range(1, len(self.data)): -# # result.data[index] += result.data[index - 1] -# # return result -# # -# # # -# # # mathematical operations -# # # -# # -# # def __abs__(self): -# # """Returns a new time series computed by applying the `abs` funtion -# # to the elements of the original time series. -# # -# # >>> abs(cashflow(const_value=[-10]*4, pyr=4)) # doctest: +NORMALIZE_WHITESPACE -# # Qtr0 Qtr1 Qtr2 Qtr3 -# # 0 10.00 10.00 10.00 10.00 -# # -# # """ -# # result = self.copy() -# # for time, _ in enumerate(result.data): -# # result[time] = abs(self[time]) -# # return result -# # -# # def __add__(self, other): -# # """Addition -# # -# # >>> cashflow(const_value=[1]*4, pyr=4) + cashflow(const_value=[2]*4, pyr=4) # doctest: +NORMALIZE_WHITESPACE -# # Qtr0 Qtr1 Qtr2 Qtr3 -# # 0 3.00 3.00 3.00 3.00 -# # -# # """ -# # if isinstance(other, (int, float)): -# # other = [other] * len(self) -# # else: -# # verify_eq_time_range(self, other) -# # result = self.copy() -# # for index, _ in enumerate(result.data): -# # result[index] += other[index] -# # return result -# # -# # -# # def __floordiv__(self, other): -# # """floordiv -# # -# # >>> cashflow(const_value=[6]*4, pyr=4) // cashflow(const_value=[4]*4, pyr=4) # doctest: +NORMALIZE_WHITESPACE -# # Qtr0 Qtr1 Qtr2 Qtr3 -# # 0 1.00 1.00 1.00 1.00 -# # -# # """ -# # if isinstance(other, (int, float)): -# # other = [other] * len(self) -# # else: -# # verify_eq_time_range(self, other) -# # result = self.copy() -# # for index, _ in enumerate(result.data): -# # result[index] //= other[index] -# # return result -# # -# # -# # def __mod__(self, other): -# # """ -# # -# # >>> cashflow( const_value=[6]*4, pyr=4) % cashflow( const_value=[4]*4, pyr=4) # doctest: +NORMALIZE_WHITESPACE -# # Qtr0 Qtr1 Qtr2 Qtr3 -# # 0 2.00 2.00 2.00 2.00 -# # -# # -# # """ -# # if isinstance(other, (int, float)): -# # other = [other] * len(self) -# # else: -# # verify_eq_time_range(self, other) -# # result = self.copy() -# # for index, _ in enumerate(result.data): -# # result[index] %= other[index] -# # return result -# # -# # -# # def __mul__(self, other): -# # """multiplication -# # -# # >>> cashflow( const_value=[2]*4, pyr=4) * cashflow( const_value=[3]*4, pyr=4) # doctest: +NORMALIZE_WHITESPACE -# # Qtr0 Qtr1 Qtr2 Qtr3 -# # 0 6.00 6.00 6.00 6.00 -# # -# # """ -# # if isinstance(other, (int, float)): -# # other = [other] * len(self) -# # else: -# # verify_eq_time_range(self, other) -# # result = self.copy() -# # for index, _ in enumerate(result.data): -# # result[index] *= other[index] -# # return result -# # -# # -# # def __sub__(self, other): -# # """Substraction -# # -# # >>> cashflow( const_value=[6]*4, pyr=4) - cashflow( const_value=[4]*4, pyr=4) # doctest: +NORMALIZE_WHITESPACE -# # Qtr0 Qtr1 Qtr2 Qtr3 -# # 0 2.00 2.00 2.00 2.00 -# # -# # """ -# # if isinstance(other, (int, float)): -# # other = [other] * len(self) -# # else: -# # verify_eq_time_range(self, other) -# # result = self.copy() -# # for index, _ in enumerate(result.data): -# # result[index] -= other[index] -# # return result -# # -# # -# # def __truediv__(self, other): -# # """ -# # -# # >>> cashflow(const_value=[6]*4, pyr=4) / cashflow(const_value=[4]*4, pyr=4) # doctest: +NORMALIZE_WHITESPACE -# # Qtr0 Qtr1 Qtr2 Qtr3 -# # 0 1.50 1.50 1.50 1.50 -# # -# # """ -# # if isinstance(other, (int, float)): -# # other = [other] * len(self) -# # else: -# # verify_eq_time_range(self, other) -# # result = self.copy() -# # for index, _ in enumerate(result.data): -# # result[index] /= other[index] -# # return result -# # -# # def __radd__(self, other): -# # """Reverse add function""" -# # if other == 0: -# # return self -# # else: -# # return self.__add__(other) -# # -# # -# # -# # # -# # # operations over sequences -# # # -# # -# # # def append(self, other): -# # # """ -# # # -# # # # >>> x = TimeSeries(data=[10]*10, start=(2001,)) -# # # # >>> y = TimeSeries(data=[20]*10, start=(2001,)) -# # # # >>> x.append(y) -# # # # >>> x # doctest: +NORMALIZE_WHITESPACE -# # # # Time Series: -# # # # Start = (2001,) -# # # # End = (2020,) -# # # # pyr = 1 -# # # # Data = (2001,)-(2020,) [20] 10.00 -# # # -# # # """ -# # # if self.pyr != other.pyr: -# # # raise ValueError("time series must have the same pyr") -# # # -# # # self.data.extend([x for x in other.data]) -# # # self.nper = len(self.data) -# # # self.end = _float2timeid(_timeid2float(self.start, self.pyr) + (self.nper - 1)/self.pyr, self.pyr) -# # -# # -# # -# # -# # # -# # # inplace operators -# # # -# # def __iadd__(self, other): -# # """ -# # -# # -# # >>> x = cashflow( const_value=[2]*4, pyr=4) -# # >>> x += cashflow( const_value=[3]*4, pyr=4) -# # >>> x # doctest: +NORMALIZE_WHITESPACE -# # Qtr0 Qtr1 Qtr2 Qtr3 -# # 0 5.00 5.00 5.00 5.00 -# # -# # """ -# # if isinstance(other, (int, float)): -# # other = [other] * len(self) -# # else: -# # verify_eq_time_range(self, other) -# # for index, _ in enumerate(self.data): -# # self[index] += other[index] -# # return self -# # -# # -# # -# # # def __iconcat__(self, other): -# # # """ -# # # """ -# # # if self.pyr != other.pyr: -# # # raise ValueError("time series must have the same pyr") -# # # self.data += other.data -# # # self.nper = len(self.data) -# # # self.end = _float2timeid(_timeid2float(self.start, self.pyr) + (self.nper - 1)/self.pyr, self.pyr) -# # -# # def __ifloordiv__(self, other): -# # """ -# # -# # >>> x = cashflow( const_value=[6]*4, pyr=4) -# # >>> x //= cashflow( const_value=[4]*4, pyr=4) -# # >>> x # doctest: +NORMALIZE_WHITESPACE -# # Qtr0 Qtr1 Qtr2 Qtr3 -# # 0 1.00 1.00 1.00 1.00 -# # -# # """ -# # if isinstance(other, (int, float)): -# # other = [other] * len(self) -# # else: -# # verify_eq_time_range(self, other) -# # for index, _ in enumerate(self.data): -# # self[index] //= other[index] -# # return self -# # -# # -# # -# # def __imod__(self, other): -# # """ -# # -# # >>> x = cashflow( const_value=[6]*4, pyr=4) -# # >>> x %= cashflow( const_value=[4]*4, pyr=4) -# # >>> x # doctest: +NORMALIZE_WHITESPACE -# # Qtr0 Qtr1 Qtr2 Qtr3 -# # 0 2.00 2.00 2.00 2.00 -# # -# # """ -# # if isinstance(other, (int, float)): -# # other = [other] * len(self) -# # else: -# # verify_eq_time_range(self, other) -# # for index, _ in enumerate(self.data): -# # self[index] %= other[index] -# # return self -# # -# # -# # -# # def __imul__(self, other): -# # """ -# # >>> x = cashflow( const_value=[2]*4, pyr=4) -# # >>> x *= cashflow( const_value=[3]*4, pyr=4) -# # >>> x # doctest: +NORMALIZE_WHITESPACE -# # Qtr0 Qtr1 Qtr2 Qtr3 -# # 0 6.00 6.00 6.00 6.00 -# # -# # """ -# # if isinstance(other, (int, float)): -# # other = [other] * len(self) -# # else: -# # verify_eq_time_range(self, other) -# # for index, _ in enumerate(self.data): -# # self[index] *= other[index] -# # return self -# # -# # -# # def __isub__(self, other): -# # """ -# # -# # >>> x = cashflow( const_value=[6]*4, pyr=4) -# # >>> x -= cashflow( const_value=[4]*4, pyr=4) -# # >>> x # doctest: +NORMALIZE_WHITESPACE -# # Qtr0 Qtr1 Qtr2 Qtr3 -# # 0 2.00 2.00 2.00 2.00 -# # -# # """ -# # if isinstance(other, (int, float)): -# # other = [other] * len(self) -# # else: -# # verify_eq_time_range(self, other) -# # for index, _ in enumerate(self.data): -# # self[index] -= other[index] -# # return self -# # -# # -# # def __itruediv__(self, other): -# # """ -# # -# # >>> x = cashflow( const_value=[6]*4, pyr=4) -# # >>> x /= cashflow( const_value=[4]*4, pyr=4) -# # >>> x # doctest: +NORMALIZE_WHITESPACE -# # Qtr0 Qtr1 Qtr2 Qtr3 -# # 0 1.50 1.50 1.50 1.50 -# # -# # """ -# # if isinstance(other, (int, float)): -# # other = [other] * len(self) -# # else: -# # verify_eq_time_range(self, other) -# # for index, _ in enumerate(self.data): -# # self[index] /= other[index] -# # return self -# # -# # -# # -# # -# # # def window(self, left=None, right=None): -# # # """Returns a sublist""" -# # # if left is None: -# # # left = 0 -# # # if right is None: -# # # right = len(self.data) -# # # return [self.data[t] for t in range(left, right + 1)] -# # -# # # def extend(self, left=0, right=0): -# # # """extendes the list""" -# # # self.data = [0] * left + self.data + [0] * right -# # # return self -# # -# -# -# -# # M - month end frequency -# # SM semi month -# # BM bussines month frequency -# # Q quarters - - - - - - - - - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/cashflows/tvmm.py b/cashflows/tvmm.py new file mode 100755 index 0000000..d1d2b17 --- /dev/null +++ b/cashflows/tvmm.py @@ -0,0 +1,542 @@ +""" +Time value of money models +=============================================================================== + +This module contains functions for computing the time value of the money. + +* ``pvfv``: computes the missing value in the equation + ``fval = pval * (1 + rate) ** nper``, that represents the following + cashflow. + +.. image:: ./images/pvfv.png + :width: 350px + :align: center + +* ``pvpmt``: computes the missing value (`pmt`, `pval`, `nper`, `nrate`) in a + model relating a present value and + a finite sequence of payments made at the end of the period (payments + in arrears, or ordinary annuities), as indicated in the following cashflow + diagram: + +.. image:: ./images/pvpmt.png + :width: 350px + :align: center + +* ``pmtfv``: computes the missing value (`pmt`, `fval`, `nper`, `nrate`) in a + model relating a finite sequence of payments in advance (annuities due) + and a future value, as indicated in the following diagram: + +.. image:: ./images/pmtfv.png + :width: 350px + :align: center + +* ``tvmm``: computes the missing value (`pmt`, `fval`, `pval, `nper`, `nrate`) + in a model relating a finite sequence of payments made at the beginning or at + the end of the period, a present value, a future value, and an interest rate, + as indicated in the following diagram: + +.. image:: ./images/tvmm.png + :width: 650px + :align: center + + +""" + +import numpy +from cashflows.common import _vars2list +from cashflows.rate import iconv + + +def tvmm(pval=None, fval=None, pmt=None, nrate=None, nper=None, due=0, pyr=1, noprint=True): + """Computes present and future values, periodic payments, nominal interest + rate or number of periods. + + Args: + pval (float, list): Present value. + fval (float, list): Future value. + pmt (float, list): Periodic payment. + nrate (float, list): Nominal interest rate per year. + nper (int, list): Number of compounding periods. + due (int): When payments are due. + pyr (int, list): number of periods per year. + noprint (bool): prints enhanced output + + + Returns: + Argument set to None in the function call. + + Effective interest rate per period is calculated as `nrate` / `pyr`. + + + **Examples.** + + In this example shows how to find different values for a loan of 5000, with a + monthly payment of 130 at the end of the month, a life of 48 periods, and a + interest rate of 0.94 per month (equivalent to 11.32% nominal) + + * Periodic payment: + + >>> pmt = tvmm(pval=5000, nrate=11.32/12, nper=48, fval=0) + >>> pmt # doctest: +ELLIPSIS + -130.00... + + * Future value: + + >>> tvmm(pval=5000, nrate=11.32/12, nper=48, pmt=pmt) # doctest: +ELLIPSIS + -0.0... + + * Present value: + + >>> tvmm(nrate=11.32/12, nper=48, pmt=pmt, fval = 0.0) # doctest: +ELLIPSIS + 5000... + + * Rate: + + >>> tvmm(pval=5000, nper=48, pmt=pmt, fval = 0.0) # doctest: +ELLIPSIS + 0.94... + + * Number of periods: + + >>> tvmm(pval=5000, nrate=11.32/12, pmt=pmt, fval=0.0) # doctest: +ELLIPSIS + 48.0... + + * Periodic paymnts: + + >>> tvmm(pval=5000, nrate=11.32, nper=48, fval=0, pyr=12) # doctest: +ELLIPSIS + -130.00... + + * Nominal rate: + + >>> tvmm(pval=5000, nper=48, pmt=pmt, fval = 0.0, pyr=12) # doctest: +ELLIPSIS + 11.32... + + >>> tvmm(pval=5000, nrate=11.32, nper=48, fval=0, pyr=12, noprint=False) # doctest: +ELLIPSIS + Present Value: ....... 5000.00 + Future Value: ........ 0.00 + Payment: ............. -130.01 + Due: ................. END + No. of Periods: ...... 48.00 + Compoundings per Year: 12 + Nominal Rate: ....... 11.32 + Effective Rate: ..... 11.93 + Periodic Rate: ...... 0.94 + + >>> tvmm(pval=[5, 500, 5], nrate=11.32, nper=48, fval=0, pyr=12, noprint=False) # doctest: +ELLIPSIS + # pval fval pmt nper nrate erate prate due + ------------------------------------------------------ + 0 5.00 0.00 -0.13 48.00 11.32 11.93 0.94 END + 1 500.00 0.00 -0.13 48.00 11.32 11.93 0.94 END + 2 5.00 0.00 -0.13 48.00 11.32 11.93 0.94 END + """ + + #pylint: disable=too-many-arguments + #pylint: disable=too-many-branches + + numnone = 0 + numnone += 1 if pval is None else 0 + numnone += 1 if fval is None else 0 + numnone += 1 if nper is None else 0 + numnone += 1 if pmt is None else 0 + numnone += 1 if nrate is None else 0 + + if numnone > 1: + raise ValueError('One of the params must be set to None') + + if numnone == 0: + pmt = None + + if pmt == 0.0: + pmt = 0.0000001 + + nrate = numpy.array(nrate) + + if pval is None: + result = numpy.pv(rate=nrate/100/pyr, nper=nper, fv=fval, pmt=pmt, when=due) + elif fval is None: + result = numpy.fv(rate=nrate/100/pyr, nper=nper, pv=pval, pmt=pmt, when=due) + elif nper is None: + result = numpy.nper(rate=nrate/100/pyr, pv=pval, fv=fval, pmt=pmt, when=due) + elif pmt is None: + result = numpy.pmt(rate=nrate/100/pyr, nper=nper, pv=pval, fv=fval, when=due) + else: + result = numpy.rate(pv=pval, nper=nper, fv=fval, pmt=pmt, when=due) * 100 * pyr + + if noprint is True: + if isinstance(result, numpy.ndarray): + return result.tolist() + return result + + + nrate = nrate.tolist() + + if pval is None: + pval = result + elif fval is None: + fval = result + elif nper is None: + nper = result + elif pmt is None: + pmt = result + else: + nrate = result + + params = _vars2list([pval, fval, nper, pmt, nrate]) + pval = params[0] + fval = params[1] + nper = params[2] + pmt = params[3] + nrate = params[4] + + # raise ValueError(nrate.__repr__()) + + erate, prate = iconv(nrate=nrate, pyr=pyr) + + if len(pval) == 1: + if pval is not None: + print('Present Value: ....... {:8.2f}'.format(pval[0])) + if fval is not None: + print('Future Value: ........ {:8.2f}'.format(fval[0])) + if pmt is not None: + print('Payment: ............. {:8.2f}'.format(pmt[0])) + if due is not None: + print('Due: ................. {:s}'.format('END' if due == 0 else 'BEG')) + print('No. of Periods: ...... {:8.2f}'.format(nper[0])) + print('Compoundings per Year: {:>5d}'.format(pyr)) + print('Nominal Rate: ....... {:8.2f}'.format(nrate[0])) + print('Effective Rate: ..... {:8.2f}'.format(erate)) + print('Periodic Rate: ...... {:8.2f}'.format(prate)) + + else: + if due == 0: + sdue = 'END' + txtpmt = [] + for item, _ in enumerate(pval): + txtpmt.append(pmt[item][-1]) + else: + sdue = 'BEG' + txtpmt = [] + for item, _ in enumerate(pval): + txtpmt.append(pmt[item][0]) + + + maxlen = 5 + for value1, value2, value3, value4 in zip(pval, fval, txtpmt, nper): + maxlen = max(maxlen, len('{:1.2f}'.format(value1))) + maxlen = max(maxlen, len('{:1.2f}'.format(value2))) + maxlen = max(maxlen, len('{:1.2f}'.format(value3))) + maxlen = max(maxlen, len('{:1.2f}'.format(value4))) + + len_aux = len('{:d}'.format(len(pval))) + + fmt_num = ' {:' + '{:d}'.format(maxlen) + '.2f}' + fmt_num = '{:<' + '{:d}'.format(len_aux) + 'd}' + fmt_num * 7 + ' {:3s}' + # fmt_shr = '{:' + '{:d}'.format(len_aux) + 's}' + fmt_hdr = ' {:>' + '{:d}'.format(maxlen) + 's}' + fmt_hdr = '{:' + '{:d}'.format(len_aux) + 's}' + fmt_hdr * 7 + ' due' + + + txt = fmt_hdr.format('#', 'pval', 'fval', 'pmt', 'nper', 'nrate', 'erate', 'prate') + print(txt) + print('-' * len_aux + '-' * maxlen * 7 + '-' * 7 + '----') + for item, _ in enumerate(pval): + print(fmt_num.format(item, + pval[item], + fval[item], + txtpmt[item], + nper[item], + nrate[item], + erate[item], + prate[item], + sdue)) + + +def pvfv(pval=None, fval=None, nrate=None, nper=None, pyr=1, noprint=True): + """Computes the missing argument (set to None) in the function call. + + Args: + pval (float, list): Present value. + fval (float, list): Future value. + nrate (float, list): Nominal interest rate per year. + nper (int, list): Number of compounding periods. + pyr (int, list): number of periods per year. + noprint (bool): prints enhanced output + + Returns: + The value of the parameter set to None in the function call. + + Effective interest rate per period is calculated as `nrate` / `pyr`. + + """ + return tvmm(pval=pval, fval=fval, pmt=0, nrate=nrate, nper=nper, due=0, pyr=pyr, noprint=noprint) + + +def pmtfv(pmt=None, fval=None, nrate=None, nper=None, pyr=1, noprint=True): + """CComputes the missing argument (set to None) in the function call. + + Args: + pmt (float, list): Periodic payment. + fval (float, list): Future value. + nrate (float, list): Nominal rate per year. + nper (int, list): Number of compounding periods. + pyr (int, list): number of periods per year. + noprint (bool): prints enhanced output + + Returns: + The value of the parameter set to None in the function call. + + Effective interest rate per period is calculated as `nrate` / `pyr`. + + """ + return tvmm(pval=0, fval=fval, pmt=pmt, nrate=nrate, nper=nper, due=1, pyr=pyr, noprint=noprint) + + +def pvpmt(pmt=None, pval=None, nrate=None, nper=None, pyr=1, noprint=True): + """Computes the missing argument (set to None) in the function call. + + Args: + pmt (float, list): Periodic payment. + pval (float, list): Present value. + nrate (float, list): Nominal interest rate per year. + nper (int, list): Number of compounding periods. + pyr (int, list): number of periods per year. + noprint (bool): prints enhanced output + + Returns: + The value of the parameter set to None in the function call. + + Effective interest rate per period is calculated as `nrate` / `pyr`. + + """ + return tvmm(pval=pval, fval=0, pmt=pmt, nrate=nrate, nper=nper, due=0, pyr=pyr, noprint=noprint) + + +def amortize(pval=None, fval=None, pmt=None, nrate=None, nper=None, due=0, pyr=1, noprint=True): + """Amortization schedule of a loan. + + Args: + pval (float): present value. + fval (float): Future value. + pmt (float): periodic payment per period. + nrate (float): nominal interest rate per year. + nper (int): total number of compounding periods. + due (int): When payments are due. + pyr (int, list): number of periods per year. + noprint (bool): prints enhanced output + + Returns: + A tuple: (principal, interest, payment, balance) + + + **Examples.** + + >>> pmt = tvmm(pval=100, nrate=10, nper=5, fval=0) # doctest: +ELLIPSIS + >>> amortize(pval=100, nrate=10, nper=5, fval=0, noprint=False) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + t Beginning Periodic Interest Principal Final + Principal Payment Payment Repayment Principal + Amount Amount Amount + -------------------------------------------------------------------- + 0 100.00 0.00 0.00 0.00 100.00 + 1 100.00 -26.38 10.00 -16.38 83.62 + 2 83.62 -26.38 8.36 -18.02 65.60 + 3 65.60 -26.38 6.56 -19.82 45.78 + 4 45.78 -26.38 4.58 -21.80 23.98 + 5 23.98 -26.38 2.40 -23.98 0.00 + + + + >>> amortize(pval=-100, nrate=10, nper=5, fval=0, noprint=False) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + t Beginning Periodic Interest Principal Final + Principal Payment Payment Repayment Principal + Amount Amount Amount + -------------------------------------------------------------------- + 0 -100.00 0.00 0.00 0.00 -100.00 + 1 -100.00 26.38 -10.00 16.38 -83.62 + 2 -83.62 26.38 -8.36 18.02 -65.60 + 3 -65.60 26.38 -6.56 19.82 -45.78 + 4 -45.78 26.38 -4.58 21.80 -23.98 + 5 -23.98 26.38 -2.40 23.98 -0.00 + + >>> amortize(pval=100, nrate=10, nper=5, fval=0, due=1, noprint=False) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + t Beginning Periodic Interest Principal Final + Principal Payment Payment Repayment Principal + Amount Amount Amount + -------------------------------------------------------------------- + 0 100.00 -23.98 0.00 -23.98 76.02 + 1 76.02 -23.98 7.60 -16.38 59.64 + 2 59.64 -23.98 5.96 -18.02 41.62 + 3 41.62 -23.98 4.16 -19.82 21.80 + 4 21.80 -23.98 2.18 -21.80 0.00 + 5 0.00 0.00 0.00 0.00 0.00 + + >>> amortize(pval=-100, nrate=10, nper=5, fval=0, due=1, noprint=False) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + t Beginning Periodic Interest Principal Final + Principal Payment Payment Repayment Principal + Amount Amount Amount + -------------------------------------------------------------------- + 0 -100.00 23.98 0.00 23.98 -76.02 + 1 -76.02 23.98 -7.60 16.38 -59.64 + 2 -59.64 23.98 -5.96 18.02 -41.62 + 3 -41.62 23.98 -4.16 19.82 -21.80 + 4 -21.80 23.98 -2.18 21.80 -0.00 + 5 -0.00 0.00 -0.00 -0.00 -0.00 + + + >>> principal, interest, payment, balance = amortize(pval=100, + ... nrate=10, nper=5, fval=0) # doctest: +ELLIPSIS + + >>> principal # doctest: +ELLIPSIS + [0, -16.37..., -18.01..., -19.81..., -21.80..., -23.98...] + + >>> interest # doctest: +ELLIPSIS + [0, 10.0, 8.36..., 6.56..., 4.57..., 2.39...] + + >>> payment # doctest: +ELLIPSIS + [0, -26.37..., -26.37..., -26.37..., -26.37..., -26.37...] + + >>> balance # doctest: +ELLIPSIS + [100, 83.62..., 65.60..., 45.78..., 23.98..., 1...] + + >>> principal, interest, payment, balance = amortize(pval=100, + ... nrate=10, nper=5, pmt=pmt) # doctest: +ELLIPSIS + + >>> sum(interest) # doctest: +ELLIPSIS + 31.89... + + >>> sum(principal) # doctest: +ELLIPSIS + -99.99... + + >>> principal, interest, payment, balance = amortize(fval=0, + ... nrate=10, nper=5, pmt=pmt) # doctest: +ELLIPSIS + + >>> sum(interest) # doctest: +ELLIPSIS + 31.89... + + >>> sum(principal) # doctest: +ELLIPSIS + -99.99... + + >>> principal, interest, payment, balance = amortize(pval=100, + ... fval=0, nper=5, pmt=pmt) # doctest: +ELLIPSIS + + >>> sum(interest) # doctest: +ELLIPSIS + 31.89... + + >>> sum(principal) # doctest: +ELLIPSIS + -99.99... + + + >>> amortize(pval=100, fval=0, nrate=10, pmt=pmt, noprint=False) # doctest: +ELLIPSIS + t Beginning Periodic Interest Principal Final + Principal Payment Payment Repayment Principal + Amount Amount Amount + -------------------------------------------------------------------- + 0 100.00 0.00 0.00 0.00 100.00 + 1 100.00 -26.38 10.00 -16.38 83.62 + 2 83.62 -26.38 8.36 -18.02 65.60 + 3 65.60 -26.38 6.56 -19.82 45.78 + 4 45.78 -26.38 4.58 -21.80 23.98 + 5 23.98 -26.38 2.40 -23.98 0.00 + + >>> principal, interest, payment, balance = amortize(pval=100, + ... fval=0, nrate=10, pmt=pmt) # doctest: +ELLIPSIS + + >>> sum(interest) # doctest: +ELLIPSIS + 31.89... + + >>> sum(principal) # doctest: +ELLIPSIS + -99.99... + + + """ + + #pylint: disable=too-many-arguments + + + numnone = 0 + numnone += 1 if pval is None else 0 + numnone += 1 if fval is None else 0 + numnone += 1 if nper is None else 0 + numnone += 1 if pmt is None else 0 + numnone += 1 if nrate is None else 0 + + + if numnone > 1: + raise ValueError('One of the params must be set to None') + + if numnone == 0: + pmt = None + + if pmt == 0.0: + pmt = 0.0000001 + + if pval is None: + pval = tvmm(fval=fval, pmt=pmt, nrate=nrate, nper=nper, due=due, pyr=pyr) + elif fval is None: + fval = tvmm(pval=pval, pmt=pmt, nrate=nrate, nper=nper, due=due, pyr=pyr) + elif nper is None: + nper = tvmm(pval=pval, fval=fval, pmt=pmt, nrate=nrate, due=due, pyr=pyr) + elif pmt is None: + pmt = tvmm(pval=pval, fval=fval, nrate=nrate, nper=nper, due=due, pyr=pyr) + else: + nrate = tvmm(pval=pval, fval=fval, pmt=pmt, nper=nper, due=due, pyr=pyr) + + erate = nrate / pyr / 100 + + if int(nper) != nper: + nper = int(nper + 0.9) + + # variable definition + begbal = [0] * (nper + 1) + ipmt = [0] * (nper + 1) + ppmt = [0] * (nper + 1) + rembal = [0] * (nper + 1) + + # calcula el pmt periodico + pmt = [pmt] * (nper + 1) + + if due == 0: # vencido + pmt[0] = 0 + + if due == 1: # anticipado + pmt[nper] = 0 + + begbal[0] = pval + + + for period in range(nper + 1): + + if period == 0: + rembal[0] = begbal[0] + pmt[0] + ppmt[0] = + pmt[0] + else: + begbal[period] = rembal[period-1] + ipmt[period] = begbal[period] * erate + ppmt[period] = pmt[period] + ipmt[period] + rembal[period] = begbal[period] + ppmt[period] + + if noprint is True: + return (ppmt, ipmt, pmt, rembal) + + txt = ['t Beginning Periodic Interest Principal Final', + ' Principal Payment Payment Repayment Principal', + ' Amount Amount Amount', + '--------------------------------------------------------------------'] + + for time in range(nper + 1): + + fmt = '{:<3d} {:12.2f} {:12.2f} {:12.2f} {:12.2f} {:12.2f}' + + + txt.append(fmt.format(time, + begbal[time], + pmt[time], + ipmt[time], + ppmt[time], + rembal[time])) + print('\n'.join(txt)) + return None + + + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/cashflows/utilityfun.py b/cashflows/utilityfun.py old mode 100644 new mode 100755 diff --git a/docs/Makefile b/docs/Makefile old mode 100644 new mode 100755 index ab043ea..945ad81 --- a/docs/Makefile +++ b/docs/Makefile @@ -7,7 +7,6 @@ SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build - # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index 2f7efbe..0000000 --- a/docs/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-minimal \ No newline at end of file diff --git a/docs/analysis.rst b/docs/analysis.rst old mode 100644 new mode 100755 diff --git a/docs/bond.rst b/docs/bond.rst old mode 100644 new mode 100755 diff --git a/docs/conf.py b/docs/conf.py old mode 100644 new mode 100755 index 57dc8bd..6212872 --- a/docs/conf.py +++ b/docs/conf.py @@ -52,7 +52,7 @@ # General information about the project. project = 'Cashflows - A Package for Investment Modeling and Advanced Engineering Economics using Python' -copyright = '2018, Juan D. Velasquez & Ibeth K. Vergara' +copyright = '2017, Juan D. Velasquez & Ibeth K. Vergara' author = 'Juan D. Velasquez & Ibeth K. Vergara' # The version info for the project you're documenting, acts as replacement for @@ -60,9 +60,9 @@ # built documents. # # The short X.Y version. -version = '0.0.4' +version = '0.0.3' # The full version, including alpha/beta/rc tags. -release = '0.0.4' +release = '0.0.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/currency.rst b/docs/currency.rst old mode 100644 new mode 100755 diff --git a/docs/depreciation.rst b/docs/depreciation.rst old mode 100644 new mode 100755 diff --git a/gtimeseries.rst b/docs/gtimeseries.rst old mode 100644 new mode 100755 similarity index 100% rename from gtimeseries.rst rename to docs/gtimeseries.rst diff --git a/docs/images/pmtfv.png b/docs/images/pmtfv.png old mode 100644 new mode 100755 diff --git a/docs/images/pvfv.png b/docs/images/pvfv.png old mode 100644 new mode 100755 diff --git a/docs/images/pvpmt.png b/docs/images/pvpmt.png old mode 100644 new mode 100755 diff --git a/docs/images/tvmm.png b/docs/images/tvmm.png old mode 100644 new mode 100755 diff --git a/docs/inflation.rst b/docs/inflation.rst old mode 100644 new mode 100755 diff --git a/docs/loan.rst b/docs/loan.rst old mode 100644 new mode 100755 diff --git a/docs/make.bat b/docs/make.bat old mode 100644 new mode 100755 diff --git a/docs/rate.rst b/docs/rate.rst old mode 100644 new mode 100755 diff --git a/docs/savings.rst b/docs/savings.rst old mode 100644 new mode 100755 diff --git a/docs/taxing.rst b/docs/taxing.rst old mode 100644 new mode 100755 diff --git a/docs/tvmm.rst b/docs/tvmm.rst old mode 100644 new mode 100755 index 40ef41d..b8da0cd --- a/docs/tvmm.rst +++ b/docs/tvmm.rst @@ -1,4 +1,4 @@ .. automodule:: cashflows.tvmm - :members: - :undoc-members: - :show-inheritance: + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/utilityfun.rst b/docs/utilityfun.rst old mode 100644 new mode 100755 diff --git a/gtimeseries.py b/gtimeseries.py deleted file mode 100644 index cba166d..0000000 --- a/gtimeseries.py +++ /dev/null @@ -1,1120 +0,0 @@ -""" -Time series objects -============================================================================== - -This module implements a `TimeSeries` object that is used to represent generic -cashflows and interest rate. - -""" - - - -# import calendar -# # import numpy -# -# -# def _timeid2float(xdate, pyr): -# """Converts a pair (maj, min) in a float number -# -# >>> _timeid2float((2000,), 1) -# 2000 -# -# >>> _timeid2float((2000, 0), 1) -# 2000.0 -# -# >>> _timeid2float((2000, 0), 4) -# 2000.0 -# -# >>> _timeid2float((2000, 1), 4) -# 2000.25 -# -# >>> _timeid2float((2000, 2), 4) -# 2000.5 -# -# >>> _timeid2float((2000, 3), 4) -# 2000.75 -# -# """ -# if isinstance(xdate, tuple) and len(xdate) == 1 and pyr == 1: -# xmajor, = xdate -# return xmajor -# if isinstance(xdate, tuple) and len(xdate) == 2 and pyr == 1: -# xmajor, xminor = xdate -# if xminor == 0: -# return float(xmajor * pyr + xminor / pyr) -# if isinstance(xdate, tuple) and len(xdate) == 2 and pyr > 1: -# xmajor, xminor = xdate -# return float(xmajor + xminor / pyr) -# raise TypeError("date in invalid format" + xdate.__repr__()) -# -# -# def _float2timeid(xfloat, pyr): -# """Converts a float number in a equivalent pair (maj, min) -# -# >>> _float2timeid(2000, pyr=1) # doctest: +NORMALIZE_WHITESPACE -# (2000,) -# -# >>> _float2timeid(2000, pyr=3) # doctest: +NORMALIZE_WHITESPACE -# (2000, 0) -# -# >>> _float2timeid(2000.25, pyr=4) # doctest: +NORMALIZE_WHITESPACE -# (2000, 1) -# -# >>> _float2timeid(2000.50, pyr=4) # doctest: +NORMALIZE_WHITESPACE -# (2000, 2) -# -# """ -# if pyr == 1: -# return (int(xfloat), ) -# xmajor = int(xfloat) -# xminor = int((xfloat - xmajor) * pyr) -# return (xmajor, xminor) -# -# -# def _timeid2index(timeid, basis, pyr): -# """Converts a timeid in an integer index -# -# -# >>> _timeid2index(timeid=(2000, 3), basis=(2000, 0), pyr=4) -# 3 -# -# >>> _timeid2index(timeid=(2000, 0), basis=(2000, 0), pyr=4) -# 0 -# -# """ -# if not isinstance(timeid, tuple): -# TypeError('Invalid type for timeid: ' + timeid.__repr__()) -# if not isinstance(basis, tuple): -# TypeError('Invalid type for basis: ' + basis.__repr__()) -# if len(timeid) != len(basis): -# TypeError('Incompatible tuples: ' + basis.__repr__() +', ' + timeid.__repr__()) -# -# if len(timeid) == 1: -# timeid_major, = timeid -# basis_major, = basis -# return timeid_major - basis_major -# -# timeid_major, timeid_minor = timeid -# basis_major, basis_minor = basis -# return (timeid_major - basis_major) * pyr + (timeid_minor - basis_minor) -# -# -# -# def verify_eq_time_range(series1, series2): -# -# if series1.pyr != series2.pyr: -# msg = 'Time series have different periods per year: ' -# raise ValueError(msg + series1.pyr.__repr__() + ', ' + series2.pyr.__repr__()) -# -# if series1.start != series2.start: -# msg = 'Time series have different start date: ' -# raise ValueError(mag + series1.start.__repr__() + ', ' + series2.start.__repr__()) -# -# if series1.end != series2.end: -# msg = 'Time series have different end date: ' -# raise ValueError(mag + series1.end.__repr__() + ', ' + series2.end.__repr__()) -# -# -# -# class TimeSeries(): -# """ -# Time series object for representing generic cashflows and interest rates. -# -# **Examples.** -# -# >>> TimeSeries(start=(2000, 0), end=(2002, 3), nper=12, pyr=4) # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 2000 0.00 0.00 0.00 0.00 -# 2001 0.00 0.00 0.00 0.00 -# 2002 0.00 0.00 0.00 0.00 -# -# >>> TimeSeries(start=(2000, 0), end=(2002, 3), pyr=4) # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 2000 0.00 0.00 0.00 0.00 -# 2001 0.00 0.00 0.00 0.00 -# 2002 0.00 0.00 0.00 0.00 -# -# """ -# -# def __init__(self, start=None, end=None, nper=None, pyr=1): -# """Creates a generic time series. -# -# >>> TimeSeries(nper=10) # doctest: +NORMALIZE_WHITESPACE -# Time Series: -# Start = (0,) -# End = (9,) -# pyr = 1 -# Data = (0,)-(9,) [10] 0.00 -# -# >>> TimeSeries(start=2001, nper=10) # doctest: +NORMALIZE_WHITESPACE -# Time Series: -# Start = (2001,) -# End = (2010,) -# pyr = 1 -# Data = (2001,)-(2010,) [10] 0.00 -# -# >>> TimeSeries(end=2010, nper=10) # doctest: +NORMALIZE_WHITESPACE -# Time Series: -# Start = (2001,) -# End = (2010,) -# pyr = 1 -# Data = (2001,)-(2010,) [10] 0.00 -# -# >>> TimeSeries(start=2000, end=2002) # doctest: +NORMALIZE_WHITESPACE -# Time Series: -# Start = (2000,) -# End = (2002,) -# pyr = 1 -# Data = (2000,)-(2002,) [3] 0.00 -# -# >>> TimeSeries(start=2000, end=2002, nper=3) # doctest: +NORMALIZE_WHITESPACE -# Time Series: -# Start = (2000,) -# End = (2002,) -# pyr = 1 -# Data = (2000,)-(2002,) [3] 0.00 -# -# """ -# -# #pylint: disable=too-many-arguments -# -# def check_timeid(timeid): -# # -# if timeid is None: -# return None -# -# if isinstance(timeid, (float, int)): -# if pyr == 1: -# return (int(timeid),) -# return (int(timeid), 1) -# -# if isinstance(timeid, tuple): -# if pyr == 1 and len(timeid) > 1: -# major, minor = timeid -# if minor > 1: -# raise ValueError('Invalid data for minor unit: ' + minor.__repr__()) -# return (int(major),) -# elif pyr > 1 and len(timeid) == 1: -# major, = timeid -# return (major, 1) -# elif pyr > 1 and len(timeid) == 2: -# major, minor = timeid -# if minor > pyr: -# raise ValueError('Invalid data: ' + minor.__repr__()) -# return timeid -# -# raise TypeError('Invalid type for TimeId: ' + timeid.__repr__()) -# -# start = check_timeid(start) -# end = check_timeid(end) -# -# if nper is not None: -# nper = int(nper) -# -# if start is not None and end is not None and nper is not None: -# float_start = _timeid2float(start, pyr) -# float_end = _timeid2float(end, pyr) -# nperc = int((float_end - float_start) * pyr) + 1 -# if nper != nperc: -# msg = 'Invalid data for start, end and nper: ' + start.__repr__() -# msg += ', ' + end.__repr__() + ', ' + nper.__repr__() -# raise ValueError(msg) -# elif start is not None and end is not None: # computes nper -# float_start = _timeid2float(start, pyr) -# float_end = _timeid2float(end, pyr) -# nper = int((float_end - float_start) * pyr) + 1 -# elif start is not None and nper is not None: # computes end -# float_start = _timeid2float(start, pyr) -# float_end = float_start + (nper - 1) / pyr -# end = _float2timeid(float_end, pyr) -# elif end is not None and nper is not None: # computes start -# float_end = _timeid2float(end, pyr) -# float_start = float_end - (nper - 1) / pyr -# start = _float2timeid(float_start, pyr) -# elif start is None and end is None and nper is not None: -# if pyr == 1: -# start = (0,) -# end = (nper-1,) -# else: -# start = (0, 0) -# end = _float2timeid((nper - 1) / pyr, pyr) -# else: -# raise ValueError('Invalid data for start, end or nper') -# -# if nper <= 1: -# raise ValueError('Time Series must have a nper > 1') -# -# self.start = start -# self.end = end -# self.pyr = pyr -# self.data = [0] * nper -# -# -# def __repr__(self): -# """Print the time series.""" -# -# if self.pyr in [4, 7, 12]: -# return self.__repr4712__() -# -# txt = ['Time Series:'] -# txt += ['Start = {:s}'.format(self.start.__repr__())] -# txt += ['End = {:s}'.format(self.end.__repr__())] -# txt += ['pyr = {:s}'.format(self.pyr.__repr__())] -# -# if self.pyr == 1: -# imajor, = self.start -# iminor = 0 -# else: -# imajor, iminor = self.start -# -# txt_date = [] -# txt_freq = [] -# txt_val = [] -# -# period = 0 -# while period < len(self.data): -# -# freq = 1 -# if self.pyr == 1: -# beg_date = end_date = (imajor,) -# else: -# beg_date = end_date = (imajor, iminor) -# -# while period + freq < len(self.data) and \ -# self.data[period] == self.data[period + freq]: -# freq += 1 -# iminor += 1 -# if iminor >= self.pyr: -# iminor = 0 -# imajor += 1 -# -# if self.pyr == 1: -# end_date = (imajor,) -# else: -# end_date = (imajor, iminor) -# -# iminor += 1 -# if iminor >= self.pyr: -# iminor = 0 -# imajor += 1 -# -# if freq == 1: -# -# # iminor += 1 -# # if iminor >= self.pyr: -# # iminor = 0 -# # imajor += 1 -# -# txt_date += ['{:s}'.format(beg_date.__str__())] -# txt_freq += [' '] -# txt_val += ['{:1.2f}'.format(self.data[period])] -# else: -# fmt = '{:s}-{:s}' -# txt_date += [fmt.format(beg_date.__str__(), end_date.__str__())] -# txt_freq += ['[{:d}]'.format(freq)] -# txt_val += ['{:1.2f}'.format(self.data[period])] -# -# -# -# period += freq -# -# max_date = max_freq = max_val = 0 -# for index, _ in enumerate(txt_date): -# max_date = max(max_date, len(txt_date[index])) -# max_freq = max(max_freq, len(txt_freq[index])) -# max_val = max(max_val, len(txt_val[index])) -# -# fmt = ' {:' + '{:d}'.format(max_date) + 's} ' -# fmt += '{:' + '{:d}'.format(max_freq) + 's} ' -# fmt += '{:>' + '{:d}'.format(max_val) + 's} ' -# -# -# for index, _ in enumerate(txt_date): -# if index == 0: -# txt += ['Data =' + fmt.format(txt_date[index], txt_freq[index], txt_val[index])] -# else: -# txt += [' ' +fmt.format(txt_date[index], txt_freq[index], txt_val[index])] -# -# return '\n'.join(txt)+'\n' -# -# -# def __repr4712__(self): -# """ Prints as table with quarters, days or months. -# -# # >>> TimeSeries(start=(1, 0), pyr=4) # doctest: +NORMALIZE_WHITESPACE -# # Qtr0 Qtr1 Qtr2 Qtr3 -# # 1 0.00 -# -# # >>> TimeSeries(start=(1, 1), pyr=7) # doctest: +NORMALIZE_WHITESPACE -# # Mon Tue Wed Thu Fri Sat Sun -# # 1 0.00 -# -# # >>> TimeSeries(start=(1, 1), pyr=12) # doctest: +NORMALIZE_WHITESPACE -# # Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec -# # 1 0.00 -# -# -# """ -# smajor, sminor = self.start -# emajor, eminor = self.end -# -# imajor = smajor -# iminor = 0 -# iper = 0 -# -# maxlen = 0 -# for data in self.data: -# maxlen = max(maxlen, len('{:.2f}'.format(data))) -# -# maxlen = maxlen - 3 -# -# fmt_major = '{:<' + '{:= self.pyr: -# iminor = 0 -# imajor += 1 -# txt.append(sline) -# if is_ok is True: -# sline = fmt_major.format(imajor) -# else: -# sline = '' -# -# txt.append(sline) -# return '\n'.join(txt) -# -# -# -# def __getitem__(self, key): -# """gets the item -# -# -# -# """ -# if isinstance(key, tuple): -# key = _timeid2index(timeid=key, basis=self.start, pyr=self.pyr) -# return self.data[key] -# -# def __setitem__(self, key, value): -# -# if isinstance(key, tuple): -# key = _timeid2index(timeid=key, basis=self.start, pyr=self.pyr) -# self.data[key] = value -# -# def __len__(self): -# return len(self.data) -# -# def __iter__(self): -# return self.data.__iter__() -# -# def __next__(self): -# return self.data.__next__() -# -# -# def tolist(self): -# """Returns the values as a list""" -# return [x for x in self.data] -# -# def copy(self): -# """returns a copy of the time series""" -# result = TimeSeries(start=self.start, end=self.end, nper=len(self.data), pyr=self.pyr) -# result.data = [value for value in self.data] -# return result -# -# -# def cumsum(self): -# """returns the cumulative sum of the time series""" -# result = TimeSeries(start=self.start, end=self.end, nper=len(self.data), pyr=self.pyr) -# result.data = [value for value in self.data] -# for index in range(1, len(self.data)): -# result.data[index] += result.data[index - 1] -# return result -# -# # -# # mathematical operations -# # -# -# def __abs__(self): -# """Returns a new time series computed by applying the `abs` funtion -# to the elements of the original time series. -# -# >>> abs(cashflow(const_value=[-10]*4, pyr=4)) # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 0 10.00 10.00 10.00 10.00 -# -# """ -# result = self.copy() -# for time, _ in enumerate(result.data): -# result[time] = abs(self[time]) -# return result -# -# def __add__(self, other): -# """Addition -# -# >>> cashflow(const_value=[1]*4, pyr=4) + cashflow(const_value=[2]*4, pyr=4) # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 0 3.00 3.00 3.00 3.00 -# -# """ -# if isinstance(other, (int, float)): -# other = [other] * len(self) -# else: -# verify_eq_time_range(self, other) -# result = self.copy() -# for index, _ in enumerate(result.data): -# result[index] += other[index] -# return result -# -# -# def __floordiv__(self, other): -# """floordiv -# -# >>> cashflow(const_value=[6]*4, pyr=4) // cashflow(const_value=[4]*4, pyr=4) # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 0 1.00 1.00 1.00 1.00 -# -# """ -# if isinstance(other, (int, float)): -# other = [other] * len(self) -# else: -# verify_eq_time_range(self, other) -# result = self.copy() -# for index, _ in enumerate(result.data): -# result[index] //= other[index] -# return result -# -# -# def __mod__(self, other): -# """ -# -# >>> cashflow( const_value=[6]*4, pyr=4) % cashflow( const_value=[4]*4, pyr=4) # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 0 2.00 2.00 2.00 2.00 -# -# -# """ -# if isinstance(other, (int, float)): -# other = [other] * len(self) -# else: -# verify_eq_time_range(self, other) -# result = self.copy() -# for index, _ in enumerate(result.data): -# result[index] %= other[index] -# return result -# -# -# def __mul__(self, other): -# """multiplication -# -# >>> cashflow( const_value=[2]*4, pyr=4) * cashflow( const_value=[3]*4, pyr=4) # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 0 6.00 6.00 6.00 6.00 -# -# """ -# if isinstance(other, (int, float)): -# other = [other] * len(self) -# else: -# verify_eq_time_range(self, other) -# result = self.copy() -# for index, _ in enumerate(result.data): -# result[index] *= other[index] -# return result -# -# -# def __sub__(self, other): -# """Substraction -# -# >>> cashflow( const_value=[6]*4, pyr=4) - cashflow( const_value=[4]*4, pyr=4) # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 0 2.00 2.00 2.00 2.00 -# -# """ -# if isinstance(other, (int, float)): -# other = [other] * len(self) -# else: -# verify_eq_time_range(self, other) -# result = self.copy() -# for index, _ in enumerate(result.data): -# result[index] -= other[index] -# return result -# -# -# def __truediv__(self, other): -# """ -# -# >>> cashflow(const_value=[6]*4, pyr=4) / cashflow(const_value=[4]*4, pyr=4) # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 0 1.50 1.50 1.50 1.50 -# -# """ -# if isinstance(other, (int, float)): -# other = [other] * len(self) -# else: -# verify_eq_time_range(self, other) -# result = self.copy() -# for index, _ in enumerate(result.data): -# result[index] /= other[index] -# return result -# -# def __radd__(self, other): -# """Reverse add function""" -# if other == 0: -# return self -# else: -# return self.__add__(other) -# -# -# -# # -# # operations over sequences -# # -# -# # def append(self, other): -# # """ -# # -# # # >>> x = TimeSeries(data=[10]*10, start=(2001,)) -# # # >>> y = TimeSeries(data=[20]*10, start=(2001,)) -# # # >>> x.append(y) -# # # >>> x # doctest: +NORMALIZE_WHITESPACE -# # # Time Series: -# # # Start = (2001,) -# # # End = (2020,) -# # # pyr = 1 -# # # Data = (2001,)-(2020,) [20] 10.00 -# # -# # """ -# # if self.pyr != other.pyr: -# # raise ValueError("time series must have the same pyr") -# # -# # self.data.extend([x for x in other.data]) -# # self.nper = len(self.data) -# # self.end = _float2timeid(_timeid2float(self.start, self.pyr) + (self.nper - 1)/self.pyr, self.pyr) -# -# -# -# -# # -# # inplace operators -# # -# def __iadd__(self, other): -# """ -# -# -# >>> x = cashflow( const_value=[2]*4, pyr=4) -# >>> x += cashflow( const_value=[3]*4, pyr=4) -# >>> x # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 0 5.00 5.00 5.00 5.00 -# -# """ -# if isinstance(other, (int, float)): -# other = [other] * len(self) -# else: -# verify_eq_time_range(self, other) -# for index, _ in enumerate(self.data): -# self[index] += other[index] -# return self -# -# -# -# # def __iconcat__(self, other): -# # """ -# # """ -# # if self.pyr != other.pyr: -# # raise ValueError("time series must have the same pyr") -# # self.data += other.data -# # self.nper = len(self.data) -# # self.end = _float2timeid(_timeid2float(self.start, self.pyr) + (self.nper - 1)/self.pyr, self.pyr) -# -# def __ifloordiv__(self, other): -# """ -# -# >>> x = cashflow( const_value=[6]*4, pyr=4) -# >>> x //= cashflow( const_value=[4]*4, pyr=4) -# >>> x # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 0 1.00 1.00 1.00 1.00 -# -# """ -# if isinstance(other, (int, float)): -# other = [other] * len(self) -# else: -# verify_eq_time_range(self, other) -# for index, _ in enumerate(self.data): -# self[index] //= other[index] -# return self -# -# -# -# def __imod__(self, other): -# """ -# -# >>> x = cashflow( const_value=[6]*4, pyr=4) -# >>> x %= cashflow( const_value=[4]*4, pyr=4) -# >>> x # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 0 2.00 2.00 2.00 2.00 -# -# """ -# if isinstance(other, (int, float)): -# other = [other] * len(self) -# else: -# verify_eq_time_range(self, other) -# for index, _ in enumerate(self.data): -# self[index] %= other[index] -# return self -# -# -# -# def __imul__(self, other): -# """ -# >>> x = cashflow( const_value=[2]*4, pyr=4) -# >>> x *= cashflow( const_value=[3]*4, pyr=4) -# >>> x # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 0 6.00 6.00 6.00 6.00 -# -# """ -# if isinstance(other, (int, float)): -# other = [other] * len(self) -# else: -# verify_eq_time_range(self, other) -# for index, _ in enumerate(self.data): -# self[index] *= other[index] -# return self -# -# -# def __isub__(self, other): -# """ -# -# >>> x = cashflow( const_value=[6]*4, pyr=4) -# >>> x -= cashflow( const_value=[4]*4, pyr=4) -# >>> x # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 0 2.00 2.00 2.00 2.00 -# -# """ -# if isinstance(other, (int, float)): -# other = [other] * len(self) -# else: -# verify_eq_time_range(self, other) -# for index, _ in enumerate(self.data): -# self[index] -= other[index] -# return self -# -# -# def __itruediv__(self, other): -# """ -# -# >>> x = cashflow( const_value=[6]*4, pyr=4) -# >>> x /= cashflow( const_value=[4]*4, pyr=4) -# >>> x # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 0 1.50 1.50 1.50 1.50 -# -# """ -# if isinstance(other, (int, float)): -# other = [other] * len(self) -# else: -# verify_eq_time_range(self, other) -# for index, _ in enumerate(self.data): -# self[index] /= other[index] -# return self -# -# -# -# -# # def window(self, left=None, right=None): -# # """Returns a sublist""" -# # if left is None: -# # left = 0 -# # if right is None: -# # right = len(self.data) -# # return [self.data[t] for t in range(left, right + 1)] -# -# # def extend(self, left=0, right=0): -# # """extendes the list""" -# # self.data = [0] * left + self.data + [0] * right -# # return self -# -# -# -# def cashflow(const_value=0, start=None, end=None, nper=None, pyr=1, spec=None): -# """Returns a time series as a generic cashflow. -# -# >>> spec = ((2000, 3), 10) -# >>> cashflow(const_value=1, start=(2000, 0), nper=8, pyr=4, spec=spec) # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 2000 1.00 1.00 1.00 10.00 -# 2001 1.00 1.00 1.00 1.00 -# -# >>> spec = [((2000, 3), 10), ((2001, 3), 10)] -# >>> cashflow(const_value=1, start=(2000, 0), nper=8, pyr=4, spec=spec) # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 2000 1.00 1.00 1.00 10.00 -# 2001 1.00 1.00 1.00 10.00 -# -# >>> spec = (3, 10) -# >>> cashflow(const_value=1, start=(2000, 0), nper=8, pyr=4, spec=spec) # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 2000 1.00 1.00 1.00 10.00 -# 2001 1.00 1.00 1.00 1.00 -# -# >>> spec = [(3, 10), (7, 10)] -# >>> cashflow(const_value=1, start=(2000, 0), nper=8, pyr=4, spec=spec) # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 2000 1.00 1.00 1.00 10.00 -# 2001 1.00 1.00 1.00 10.00 -# -# >>> cashflow(const_value=[10]*10, pyr=4) # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 0 10.00 10.00 10.00 10.00 -# 1 10.00 10.00 10.00 10.00 -# 2 10.00 10.00 -# -# >>> cashflow(const_value=[-10]*4) # doctest: +NORMALIZE_WHITESPACE -# Time Series: -# Start = (0,) -# End = (3,) -# pyr = 1 -# Data = (0,)-(3,) [4] -10.00 -# -# >>> x = cashflow(const_value=[0, 1, 2, 3], pyr=4) -# >>> x[3] = 10 -# >>> x # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 0 0.00 1.00 2.00 10.00 -# -# >>> x[3] # doctest: +NORMALIZE_WHITESPACE -# 10 -# -# >>> x[(0, 3)] = 0 -# >>> x # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 0 0.00 1.00 2.00 0.00 -# -# >>> x[(0,2)] # doctest: +NORMALIZE_WHITESPACE -# 2 -# -# >>> cashflow(const_value=[0, 1, 2, 2, 4, 5, 6, 7, 8]) # doctest: +NORMALIZE_WHITESPACE -# Time Series: -# Start = (0,) -# End = (8,) -# pyr = 1 -# Data = (0,) 0.00 -# (1,) 1.00 -# (2,)-(3,) [2] 2.00 -# (4,) 4.00 -# (5,) 5.00 -# (6,) 6.00 -# (7,) 7.00 -# (8,) 8.00 -# -# -# >>> cashflow(const_value=0, nper=15, pyr=1, spec=[(t,100) for t in range(5,10)]) # doctest: +NORMALIZE_WHITESPACE -# Time Series: -# Start = (0,) -# End = (14,) -# pyr = 1 -# Data = (0,)-(4,) [5] 0.00 -# (5,)-(9,) [5] 100.00 -# (10,)-(14,) [5] 0.00 -# -# -# >>> cashflow(const_value=[0, 1, 2, 3, 4, 5]).cumsum() # doctest: +NORMALIZE_WHITESPACE -# Time Series: -# Start = (0,) -# End = (5,) -# pyr = 1 -# Data = (0,) 0.00 -# (1,) 1.00 -# (2,) 3.00 -# (3,) 6.00 -# (4,) 10.00 -# (5,) 15.00 -# -# """ -# if isinstance(const_value, list) and nper is None: -# nper = len(const_value) -# time_series = TimeSeries(start=start, end=end, nper=nper, pyr=pyr) -# start = time_series.start -# for index, _ in enumerate(time_series): -# if isinstance(const_value, list): -# time_series[index] = const_value[index] -# else: -# time_series[index] = const_value -# if spec is None: -# return time_series -# if isinstance(spec, tuple): -# spec = [spec] -# for xspec in spec: -# timeid, value = xspec -# if isinstance(timeid, int): -# time = timeid -# else: -# time = _timeid2index(timeid=timeid, basis=start, pyr=pyr) -# time_series[time] = value -# return time_series -# -# -# -# -# def interest_rate(const_value=0, start=None, end=None, nper=None, pyr=1, spec=None): -# """Creates a time series object specified as a interest rate. -# -# >>> spec = ((2000, 3), 10) -# >>> interest_rate(const_value=1, start=(2000, 0), nper=8, pyr=4, spec=spec) # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 2000 1.00 1.00 1.00 10.00 -# 2001 10.00 10.00 10.00 10.00 -# -# >>> spec = [((2000, 3), 10), ((2001, 1), 20)] -# >>> interest_rate(const_value=1, start=(2000, 0), nper=8, pyr=4, spec=spec) # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 2000 1.00 1.00 1.00 10.00 -# 2001 10.00 20.00 20.00 20.00 -# -# >>> spec = (3, 10) -# >>> interest_rate(const_value=1, start=(2000, 0), nper=8, pyr=4, spec=spec) # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 2000 1.00 1.00 1.00 10.00 -# 2001 10.00 10.00 10.00 10.00 -# -# >>> spec = [(3, 10), (6, 20)] -# >>> interest_rate(const_value=1, start=(2000, 0), nper=8, pyr=4, spec=spec) # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 2000 1.00 1.00 1.00 10.00 -# 2001 10.00 10.00 20.00 20.00 -# -# >>> interest_rate(const_value=[10]*10, pyr=4) # doctest: +NORMALIZE_WHITESPACE -# Qtr0 Qtr1 Qtr2 Qtr3 -# 0 10.00 10.00 10.00 10.00 -# 1 10.00 10.00 10.00 10.00 -# 2 10.00 10.00 -# -# """ -# if isinstance(const_value, list) and nper is None: -# nper = len(const_value) -# time_series = TimeSeries(start=start, end=end, nper=nper, pyr=pyr) -# start = time_series.start -# for index, _ in enumerate(time_series): -# if isinstance(const_value, list): -# time_series[index] = const_value[index] -# else: -# time_series[index] = const_value -# if spec is None: -# return time_series -# if isinstance(spec, tuple): -# spec = [spec] -# nummod = len(spec) -# starting = [None] * nummod -# ending = [None] * nummod -# values = [None] * nummod -# for index, xspec in enumerate(spec): -# timeid, value = xspec -# if isinstance(timeid, int): -# time = timeid -# else: -# time = _timeid2index(timeid=timeid, basis=start, pyr=pyr) -# starting[index] = time -# values[index] = value -# ending[index] = len(time_series) -# if index > 0: -# ending[index - 1] = time -# for index in range(nummod): -# for time in range(starting[index], ending[index]): -# time_series[time] = values[index] -# return time_series -# -# -# -# -# def repr_table(cols, header=None): -# """ -# """ -# -# if header is None and isinstance(cols, TimeSeries): -# print(cols.__repr__()) -# return -# -# if header is not None and isinstance(cols, TimeSeries): -# print(header.__repr__()) -# print(cols.__repr__()) -# return -# -# if len(cols) > 1: -# for xcol in cols[1:]: -# verify_eq_time_range(cols[0], xcol) -# -# len_timeid = len(cols[0].end.__repr__()) -# fmt_timeid = '{:<' + '{:d}'.format(len_timeid) + 's}' -# -# if header is None: -# len_number = 0 -# else: -# len_number = 0 -# for row in header: -# for element in row: -# len_number = max(len_number, len(element)) -# -# for xcol in cols: -# for element in xcol: -# len_number = max(len('{:1.2f}'.format(element)), len_number) -# -# fmt_number = ' {:' + '{:d}'.format(len_number) + '.2f}' -# fmt_header = ' {:>' + '{:d}'.format(len_number) + 's}' -# -# if cols[0].pyr == 1: -# xmajor, = cols[0].start -# xminor = 0 -# else: -# xmajor, xminor = cols[0].start -# -# txt = [] -# isfirst = True -# for row in header: -# if isfirst is True: -# header = fmt_timeid.format('t') -# isfirst = False -# else: -# header = fmt_timeid.format(' ') -# for element in row: -# header += fmt_header.format(element) -# txt.append(header) -# -# txt.append('-' * len_timeid + '------' + '-' * len_number * len(cols)) -# -# for time, _ in enumerate(cols[0]): -# if cols[0].pyr == 1: -# timeid = (xmajor,) -# else: -# timeid = (xmajor, xminor) -# txtrow = fmt_timeid.format(timeid.__repr__()) -# for xcol in cols: -# txtrow += fmt_number.format(xcol[time]) -# txt.append(txtrow) -# if cols[0].pyr == 1: -# xmajor += 1 -# else: -# xminor += 1 -# if xminor == cols[0].pyr: -# xminor = 0 -# xmajor += 1 -# -# return '\n'.join(txt) -# -# -# -# def cfloplot(cflo): -# """Text plot of a cashflow. -# -# >>> cflo = cashflow(const_value=[-10, 5, 0, 20] * 3, pyr=4) -# >>> cfloplot(cflo)# doctest: +NORMALIZE_WHITESPACE -# time value +------------------+------------------+ -# (0, 0) -10.00 ********** -# (0, 1) 5.00 ***** -# (0, 2) 0.00 * -# (0, 3) 20.00 ******************** -# (1, 0) -10.00 ********** -# (1, 1) 5.00 ***** -# (1, 2) 0.00 * -# (1, 3) 20.00 ******************** -# (2, 0) -10.00 ********** -# (2, 1) 5.00 ***** -# (2, 2) 0.00 * -# (2, 3) 20.00 ******************** -# -# """ -# -# len_timeid = len(cflo.end.__repr__()) -# fmt_timeid = '{:<' + '{:d}'.format(len_timeid) + 's}' -# -# len_number = 0 -# for value in cflo: -# len_number = max(len_number, len('{:1.2f}'.format(value))) -# -# fmt_number = ' {:' + '{:d}'.format(len_number) + '.2f}' -# fmt_header = ' {:>' + '{:d}'.format(len_number) + 's}' -# -# if cflo.pyr == 1: -# xmajor, = cflo.start -# xminor = 0 -# else: -# xmajor, xminor = cflo.start -# -# maxval = max(abs(cflo)) -# -# -# width = 20 -# -# txt = [] -# txtrow = fmt_timeid.format("time") + fmt_header.format('value') -# txtrow += " +" + "-" * (width - 2) + "+" + "-" * (width - 2) + "+" -# txt.append(txtrow) -# -# for value in cflo: -# -# if cflo.pyr == 1: -# timeid = (xmajor,) -# else: -# timeid = (xmajor, xminor) -# -# txtrow = fmt_timeid.format(timeid.__repr__()) -# txtrow += fmt_number.format(value) -# -# # fmt_row = " * " -# xlim = int(width * abs(value / maxval)) -# if value < 0: -# txtrow += " " + " " * (width - xlim) + '*' * (xlim) -# elif value > 0: -# txtrow += " " + " " * (width - 1) + "*" * (xlim) -# else: -# txtrow += " " + " " * (width - 1) + '*' -# -# txt.append(txtrow) -# -# if cflo.pyr == 1: -# xmajor += 1 -# else: -# xminor += 1 -# if xminor == cflo.pyr: -# xminor = 0 -# xmajor += 1 -# -# print('\n'.join(txt)) -# -# -# if __name__ == "__main__": -# import doctest -# doctest.testmod() diff --git a/pavement.py b/pavement.py old mode 100644 new mode 100755 index f9fe1ec..463099c --- a/pavement.py +++ b/pavement.py @@ -33,7 +33,7 @@ def local(): def sphinx(): """Document creation using Shinx""" sh('cd docs; make html; cd ..') - + @needs('nosetests', 'pylint', 'sphinx') @task def default(): diff --git a/prjflow.py b/prjflow.py deleted file mode 100644 index 732db6e..0000000 --- a/prjflow.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -Project cashflow computation -=============================================================================== - -""" - -# from cashflows.gtimeseries import * -# from cashflows.taxing import * -# -# def prjcashflow(totrev, invest, totexp, totint, ppal, tax_rate, depreciation, salvalue): -# """Computes the net cashflow of a investment project. -# -# Args: -# totrev (TimeSeries): Total revenue. -# invest (TimeSeries): Total investment costs -# totexp (TimeSeries): Total expenses -# totint (TimeSeries): Total payment interests -# tax_rate (TimeSeries): Tax rate per period -# ppal (TimeSeries): Principal payments -# depreciation (TimeSeries): Total depreciation per period. -# salvalue (TimeSeries): Salvament value per period as percentage. -# -# Returns: -# Project cashflow (TimeSeries) -# -# -# **Example** -# -# >>> totrev = cashflow(const_value=[200, 100, 100, 100, 100, 100]) -# >>> invest = cashflow(const_value=[400, 0, 0, 0, 0, 0]) -# >>> totexp = cashflow(const_value=[ 0, 30, 30, 30, 30, 30]) -# >>> totint = cashflow(const_value=[ 0, 5, 5, 5, 5, 5]) -# >>> tax_rate = cashflow(const_value=[ 0, 30, 30, 30, 30, 30]) -# >>> ppal = cashflow(const_value=[ 0, 30, 30, 30, 30, 30]) -# >>> depreciation = cashflow(const_value=[ 0, 15, 15, 15, 15, 15]) -# >>> salvalue = cashflow(const_value=[ 0, 0, 0, 0, 0, 50]) -# >>> prjcashflow(totrev, invest, totexp, totint, ppal, tax_rate, depreciation, salvalue) # doctest: +NORMALIZE_WHITESPACE -# (Time Series: -# Start = (0,) -# End = (5,) -# pyr = 1 -# Data = (0,) 0.00 -# (1,)-(5,) [5] 15.00 -# , Time Series: -# Start = (0,) -# End = (5,) -# pyr = 1 -# Data = (0,) -200.00 -# (1,)-(5,) [5] 20.00 -# ) -# -# """ -# -# if not isinstance(totrev, TimeSeries): -# raise TypeError("totrev must be a TimeSeries object") -# -# if not isinstance(invest, TimeSeries): -# raise TypeError("invest must be a TimeSeries object") -# -# if not isinstance(totexp, TimeSeries): -# raise TypeError("totexp must be a TimeSeries object") -# -# if not isinstance(totint, TimeSeries): -# raise TypeError("totexp must be a TimeSeries object") -# -# if not isinstance(tax_rate, TimeSeries): -# raise TypeError("tax_rate must be a TimeSeries object") -# -# if not isinstance(ppal, TimeSeries): -# raise TypeError("ppal must be a TimeSeries object") -# -# if not isinstance(depreciation, TimeSeries): -# raise TypeError("depreciation must be a TimeSeries object") -# -# if not isinstance(salvalue, TimeSeries): -# raise TypeError("salvalue must be a TimeSeries object") -# -# verify_eq_time_range(totrev, invest) -# verify_eq_time_range(totrev, totexp) -# verify_eq_time_range(totrev, tax_rate) -# verify_eq_time_range(totrev, totint) -# verify_eq_time_range(totrev, salvalue) -# verify_eq_time_range(totrev, ppal) -# -# -# cflo = totrev - invest - totexp - totint - depreciation + invest * salvalue -# -# taxes = after_tax_cashflow(cflo = cflo, tax_rate = tax_rate) -# -# before_tax_cflo = cflo + depreciation - ppal -# after_tax_cflo = before_tax_cflo - taxes -# -# return (taxes, after_tax_cflo) diff --git a/pylintrc b/pylintrc old mode 100644 new mode 100755 diff --git a/requirements.txt b/requirements.txt old mode 100644 new mode 100755 index 89f60af..ed61514 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ numpy -pandas paver diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index 31d8b68..570b693 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ def readme(): return f.read() setup(name='cashflows', - version='0.0.4', + version='0.0.3', description='Investment modeling and advanced engineering economics using Python', long_description='Investment modeling and advanced engineering economics using Python', classifiers=[