Source code for cashflows.gtimeseries

"""
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)



[docs]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__())
[docs]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 = '{:<' + '{:<d}'.format(len(emajor.__str__())) + 'd}' fmt_minor = ' {:>' + '{:d}'.format(maxlen+3) + '.2f}' fmt_void = ' ' * (4+len(max(self.data).__str__())) fmt_head = ' {:>' + '{:d}'.format(maxlen+3) + 's}' sline = ' ' * len(emajor.__str__()) if self.pyr == 4: names = ['Qtr0', 'Qtr1', 'Qtr2', 'Qtr3'] elif self.pyr == 7: names = calendar.day_abbr else: names = calendar.month_abbr[1:13] for index in range(self.pyr): sline += fmt_head.format(names[index]) txt = [sline] sline = '' sline = fmt_major.format(imajor) is_ok = True while is_ok is True: if imajor == smajor and iminor < sminor: sline += fmt_void else: sline += fmt_minor.format(self.data[iper]) iper += 1 if imajor == emajor and iminor == eminor: is_ok = False iminor += 1 if iminor >= 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__()
[docs] def tolist(self): """Returns the values as a list""" return [x for x in self.data]
[docs] 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
[docs] 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
[docs]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
[docs]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
[docs]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)
[docs]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()