"""
Representation of Cashflows and Interest Rates
===============================================================================
Overview
-------------------------------------------------------------------------------
The functions in this module allow the user to create generic cashflows and
interest rates as `pandas.Series` objects under the following restrictions:
* Frequency of time series is restricted to the following values:
``'A'``, ``'BA'``, ``'Q'``, ``'BQ'``, ``'M'``, ``'BM'``, ``'CBM'``, ``'SM'``,
``'6M'``, ``'6BM'`` and ``'6CMB'``.
* Interest rates are represented as percentages (not as a fraction).
* Appropriate values must be supplied for the arguments used to create the
timestamps of the time series.
Due to generic cashflows and interest rates are `pandas.Series` objects, all
available functions for manipulating and transforming pandas time series can be
used with this package.
The ``cashflow`` function returns a `pandas.Series` object that represents a
generic cashflow. The user must supply two of the following arguments ``start``,
``end`` and ``periods`` in order to create the corresponding timestamps for the
time series. The generic cashflow is set to the value specified by the argument
``const_value``. In addition, when the value of the argument ``const_value`` is
a list, only is necessary to specify the ``start`` or the ``end`` dates.
For convenience of the user, point values of the time series can be changed
using the argument ``chgpts``. In this case, the value passed to this argument
is a dictionary where the keys are valid dates and the values are the new values
specified for the generic cashflow in these dates.
The ``interest_rate`` function returns a `pandas.Series` object in the same way
as the ``cashflow`` function. The only difference is that the dictionary passed
to the argument ``chgpts`` specifies change points in the time series, where
the value of the interest rate changes for all points ahead.
Functions in this module
-------------------------------------------------------------------------------
"""
import numpy as np
import pandas as pd
[docs]def period2pos(index, date):
"""Returns the position (index) of a timestamp vector.
Args:
index (list): timestamp vector.
date (string): date to search.
Returns:
position (int): position of date in index.
"""
x = [i for i, elem in enumerate(index) if elem == date]
if x == []:
raise ValueError('Date does not exists: ' + date.__repr__())
return x[0]
[docs]def verify_period_range(x):
""" Verify if all time series in a list have the same timestamp.
Args:
x (list): list of `pandas.Series`.
Returns:
None.
"""
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')
[docs]def textplot(cflo):
"""Text plot of a generic cashflow.
Args:
cflo (pandas.Series): Generic cashflow.
Returns:
None.
**Example**
>>> 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))
[docs]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.
**Examples**
A quarterly cashflow with a constant value 1.0 beginning in 2000Q1 can be
expressed as:
>>> 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
In the following example, the cashflow function returns a time series object
using a list for the ``const_value`` and a timestamp for the parameter ``start``.
>>> 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
The following example uses the operator ``[]`` to modify the value with
index equal to 3.
>>> 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
Indexes in the time series also can be specified using a valid timestamp.
>>> 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
The following example uses the member function ``cumsum()`` for computing
the cumulative sum of the original time series.
>>> 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
In the next examples, a change points are specified using a dictionary. The key
can be a integer or a valid timestamp.
>>> 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
[docs]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.Series` object.
**Examples**
In the following examples, the argument ``chgpts`` is used to specify chnages
in the value of the interest rate. The keys in the dictionary can be integers
or valid timestamps.
>>> 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
The parameter ``const_value`` can be a list of numbers. In this case, only
is necessary to specify the ``start`` or ``end`` arguments.
>>> 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
if __name__ == "__main__":
import doctest
doctest.testmod()