Source code for cashflows.rate

"""
Interest rate transformations
===============================================================================

Overview
-------------------------------------------------------------------------------

Transformations between nominal, effective and periodic interest rates
can be realized using **cashflows**. This module implements the
following functions:

* ``effrate``: computes the effective interest rate given the nominal interest
  rate or the periodic interest rate.
* ``nomrate``: computes the nominal interest rate given the effective interest
  rate or the periodic interest rate.
* ``perrate``: computes the periodic interest rate given the effective interest
  rate or the nominal interest rate.

In addition, it is possible to compute discount and compounidng factors.

* ``to_discount_factor``: Returns a list of discount factors calculated as 1 / (1 + r)^(t - t0).
* ``to_compound_factor``: Returns a list of compounding factors calculated as (1 + r)^(t - t0).


Finally, also it is possible to compute a fixed equivalent rate given interest
rate changing over time using ``equivalent_rate``.


Functions in this module
-------------------------------------------------------------------------------

"""


import numpy as np
import pandas as pd

from cashflows.timeseries import *
from cashflows.common import *

[docs]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, pandas.Series): Nominal interest rate. prate (float, pandas.Series): Periodic interest rate. pyr(int): Number of compounding periods per year. Returns: Effective interest rate(float, pandas.Series). **Examples** In this example, the equivalent effective interest rate for a periodic monthly interest rate of 1% is computed. >>> effrate(prate=1, pyr=12) # doctest: +ELLIPSIS 12.68... This example shows hot to compute the effective interes rate equivalent to a nominal interest rate of 10% with montlhy compounding. >>> effrate(nrate=10, pyr=12) # doctest: +ELLIPSIS 10.4713... Also it is possible to use list for some arguments of the functions as it is shown bellow. >>> 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 Also it is possible to transform interest rate time series. >>> 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
[docs]def nomrate(erate=None, prate=None, pyr=1): """ Computes the nominal interest rate given the nominal interest rate or the periodic interest rate. Args: erate (float, pandas.Series): Effective interest rate. prate (float, pandas.Series): Periodic interest rate. pyr(int): Number of compounding periods per year. Returns: Nominal interest rate(float, pandas.Series). **Examples** >>> 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 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 """ 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 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
[docs]def perrate(nrate=None, erate=None, pyr=1): """ Computes the periodic interest rate given the nominal interest rate or the effective interest rate. Args: nrate (float, pandas.Series): Nominal interest rate. erate (float, pandas.Series): Effective interest rate. pyr(int): Number of compounding periods per year. Returns: Periodic interest rate(float, pandas.Series). **Examples** >>> 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 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 """ 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 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 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 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 maxlen == 1: prate = prate[0] return prate
[docs]def to_discount_factor(nrate=None, erate=None, prate=None, base_date=None): """Returns a list of discount factors calculated as 1 / (1 + r)^(t - t0). Args: nrate (pandas.Series): Nominal interest rate per year. nrate (pandas.Series): Effective interest rate per year. prate (pandas.Series): Periodic interest rate. base_date (string): basis time. Returns: `pandas.Series` of float values. Only one of the interest rates must be supplied for the computation. **Example** In this example, a discount factor is computed for a interest rate expressed as nominal, periodic or effective interest rate. >>> 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 [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 [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 [1.0201, 1.01, 1.0, 0.990..., 0.980..., 0.970..., 0.960..., 0.951..., 0.942..., 0.932...] """ numnone = 0 if nrate is None: numnone += 1 if erate is None: numnone += 1 if prate is None: numnone += 1 if numnone != 2: 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 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] 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) div = factor[base_date] for index, _ in enumerate(factor): factor[index] = factor[index] / div return factor
[docs]def to_compound_factor(nrate=None, erate=None, prate=None, base_date=0): """Returns a list of compounding factors calculated as (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 (int, tuple): basis time. Returns: Compound factor (list) **Example** In this example, a compound factor is computed for a interest rate expressed as nominal, periodic or effective interest rate. >>> nrate = interest_rate(const_value=4, start='2000', periods=10, freq='Q') >>> erate = effrate(nrate=nrate) >>> prate = perrate(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...] >>> to_compound_factor(nrate=nrate, 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...] >>> to_compound_factor(erate=erate, 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...] """ factor = to_discount_factor(nrate=nrate, erate=erate, prate=prate, base_date=base_date) for time, _ in enumerate(factor): factor[time] = 1 / factor[time] return factor
[docs]def equivalent_rate(nrate=None, erate=None, prate=None): """Returns the equivalent interest rate over a time period. Args: nrate (TimeSeries): Nominal interest rate per year. erate (TimeSeries): Effective interest rate per year. prate (TimeSeries): Periodic interest rate. Returns: float value. Only one of the interest rate must be supplied for the computation. **Example** In this example, the equivalent rate for a periodic interest rate of 10% is computed. >>> equivalent_rate(prate=interest_rate([10]*5, start='2000Q1', freq='Q')) # doctest: +ELLIPSIS 10.0... """ numnone = 0 if nrate is None: numnone += 1 if erate is None: numnone += 1 if prate is None: numnone += 1 if numnone != 2: raise ValueError('Two of the rates must be set to `None`') if nrate is not None: pyr = getpyr(nrate) factor = 1 for element in nrate[1:]: factor *= (1 + element / 100 / pyr) return 100 * pyr * (factor**(1/(len(nrate) - 1)) - 1) if prate is not None: pyr = getpyr(prate) factor = 1 for element in prate[1:]: factor *= (1 + element / 100) return 100 * (factor**(1/(len(prate) - 1)) - 1) if erate is not None: pyr = getpyr(erate) factor = 1 for element in erate[1:]: factor *= (1 + (numpy.power(1 + element/100, 1. / pyr) - 1)) return 100 * (factor**(1/(len(value) - 1)) - 1)
if __name__ == "__main__": import doctest doctest.testmod()