def set_accumulation(self, gr: Union[float, Rate, Accumulation, TieredBal, TieredTime]): # if float, assume compound annual effective if isinstance(gr, (float, Rate, TieredTime)): acc = standardize_acc(gr=gr) elif isinstance(gr, Accumulation): acc = standardize_acc(gr=gr) if gr.is_compound else gr elif isinstance(gr, TieredBal): acc = gr else: raise Exception("Invalid growth rate object provided.") self.gr = acc
def n_solver( gr: Union[Accumulation, float, Rate], amount: Union[float, int], sv: Union[float, int] = None, period: float = None, ) -> float: """ Given the present value, future value, growth rate, and payment interval, solves for the number of payments in a level annuity. :param gr: A growth rate object. :type gr: Accumulation, float, Rate :param amount: The present value of the annuity. :type amount: float, int :param sv: The future value of the annuity. :type sv: float, int :param period: The payment period. :type period: float :return: The number of periods. """ if sv: acc = standardize_acc(gr) i = acc.effective_rate(period) n = np.log(1 + sv / amount * i) / np.log(1 + i) else: n = None return n
def __init__( self, s0, gr, ): self.s0 = s0 self.acc = standardize_acc(gr)
def relchg(self, i, i0=None, approx=False, excl_inv=True, degree=1): if i0 is None: if self.gr is not None: i0 = self.gr else: raise ValueError("Growth rate object not set") else: i0 = standardize_acc(gr=i0) if approx: if i0.is_compound: d1 = - self.modified_duration(i=i0.interest_rate, excl_inv=excl_inv) * (i - i0.interest_rate) if degree == 1: res = d1 elif degree == 2: d2 = self.modified_convexity(i=i0.interest_rate, excl_inv=excl_inv) * ((i -i0.interest_rate) ** 2) / 2 res = d1 + d2 else: raise ValueError("Relative change approximation is only supported for 1st and 2nd degrees.") else: raise Exception("Relative change approximation is unsupported for non-compound interest.") else: if excl_inv: times = self.times.copy() amounts = self.amounts.copy() times.pop(0) amounts.pop(0) pmts = Payments(times=times, amounts=amounts, gr=self.gr) res = (pmts.npv(gr=i) - pmts.npv(gr=i0)) / pmts.npv(gr=i0) else: res = (self.npv(gr=i) - self.npv(gr=i0)) / self.npv(gr=i0) return res
def macaulay_convexity(self, gr=None, excl_inv=True): if gr is None: if self.gr is None: raise Exception("Growth rate object not set.") else: acc = self.gr else: acc = standardize_acc(gr=gr) if excl_inv: times = self.times.copy() amounts = self.amounts.copy() times.pop(0) amounts.pop(0) pmts = Payments(times=times, amounts=amounts, gr=acc) pv = pmts.npv() else: pv = self.npv(gr=gr) times = self.times amounts = self.amounts mc = sum([acc.discount_func(t=t, fv=fv) * t ** 2 / pv for t, fv in zip(times, amounts)]) return mc
def __init__( self, n1, gr1, gr2, fx, period, term ): self.n1 = n1 self.n2 = n1 * fx self.acc1 = standardize_acc(gr1) self.acc2 = standardize_acc(gr2) self.fx = fx self.period = period self.term = term
def clean(self, t: float, gr: Union[float, Rate] = None, tprac: str = 'theoretical') -> float: """ Calculates the clean value. The argument tprac can be toggled to switch between the theoretical, \ semipractical, or practical clean value. :param t: The valuation time. :type t: float :param gr: The valuation yield, if pricing a bond at a different yield. Defaults to the current bond yield. :type gr: float, Rate :param tprac: Whether you want the 'theoretical', 'semipractical', or 'practical' clean value. :type tprac: str :return: The clean value. :rtype: float """ if t == 0: t0 = 0 t1 = self.coupons.times[0] cg = self.coupons.amounts[0] else: t0 = max([x for x in self.coupons.times if x <= t]) # get next coupon time ti = self.coupons.times.index(t0) t1 = self.coupons.times[ti + 1] # get next coupon amount cg = self.coupons.amounts[ti + 1] f = (t - t0) / (t1 - t0) if gr is None: gr = self.gr else: pass jgr = standardize_acc(gr) j0 = jgr.effective_interval(t1=t0, t2=t1) if tprac == 'theoretical': dt = self.dirty(t=t, gr=gr) ct = dt - cg * ((1 + j0)**f - 1) / j0 else: dt = self.dirty(t=t, tprac='practical') ct = dt - f * cg return ct
def npv(self, gr=None): if gr is None: if self.gr is None: raise Exception("Growth rate object not set.") else: acc = self.gr else: acc = standardize_acc(gr=gr) pv = sum([acc.discount_func(t=t, fv=fv) for t, fv in zip(self.times, self.amounts)]) return pv
def eq_val(self, t: float, gr=None) -> float: if gr is None: if self.gr is None: raise Exception("Growth rate object not set.") else: acc = self.gr else: acc = standardize_acc(gr=gr) b = sum([c * acc.val(t) / acc.val(tk) for c, tk in zip(self.amounts, self.times)]) return b
def reddingtonize(fv, t, gr, terms=None, portfolio=List[Payments]): pv = standardize_acc(gr=gr).discount_func(fv=fv, t=t) if terms is not None: a = (terms[1] - t) * pv / (terms[1] - terms[0]) b = pv - a bd1 = Bond( price=a, term=terms[0], gr=gr ) bd2 = Bond( price=b, term=terms[1], gr=gr ) elif portfolio is not None: a = (portfolio[1].macaulay_duration() - t) * pv / (portfolio[1].macaulay_duration() - portfolio[0].macaulay_duration()) a_fac = a / portfolio[0].price b = pv - a b_fac = b / portfolio[1].price bd1 = Bond( red=portfolio[0].red * a_fac, face=portfolio[0].face * a_fac if portfolio[0].face is not None else None, alpha=portfolio[0].alpha, cfreq=portfolio[0].cfreq, term=portfolio[0].term, gr=portfolio[0].gr ) bd2 = Bond( red=portfolio[1].red * b_fac, face=portfolio[1].face * a_fac if portfolio[1].face is not None else None, alpha=portfolio[1].alpha, cfreq=portfolio[1].cfreq, term=portfolio[1].term, gr=portfolio[1].gr ) else: raise Exception("Unable to calculate weights.") return [bd1, bd2]
def get_r_pmt(self, gr: Union[Accumulation, Callable, float, Rate]) -> float: """ When inferring the number of payment periods, returns the number of payment periods as if fractional periods were allowed. This fractional value helps calculate the drop or balloon payments, if needed. :param gr: A growth rate object. :type gr: Accumulation, Callable, float, or Rate :return: The number of payment periods. :rtype: float """ i = standardize_acc(gr).val(self.period) - 1 r = -np.log(1 - i * self.loan / self.amount) / np.log(1 + i) return r
def get_drop(self, gr) -> float: """ If the number of payment periods does not settle to an integral number, calculates the drop payment. :param gr: A growth rate object. :type gr: Accumulation, Callable, float, or Rate :return: The drop payment. :rtype: float """ r = self.get_r_pmt(gr) n = floor(r) f = r - n i = standardize_acc(gr).val(self.period) - 1 drop = (self.amount * ((1 + i)**f - 1) / i) * (1 + i)**(1 - f) return drop
def adj_principal(self, t: float, gr: Union[float, Rate] = None, tprac: str = 'theoretical') -> float: """ Calculates the adjustment to principal in the accrued interest for a coupon. :param t: The valuation time. :type t: float :param gr: The valuation rate, if different from the yield. :type gr: float, Rate :param tprac: Whether you want to use 'theoretical' or 'practical' values. :type tprac: str :return: The adjustment to principal in the accrued interest. :rtype: float """ t1 = self.next_coupon_t(t=t) f = self.coupon_f(t=t) if tprac == 'practical': pt = self.am_prem(t=t1) adj_p = f * pt elif tprac == 'theoretical': t0 = self.last_coupon_t(t=t) if gr is None: j = self.j else: jgr = standardize_acc(gr) j = jgr.effective_interval(t1=t0, t2=t1) sv = ((1 + j)**f - 1) / j c = self.red g = self.g n = self.term adj_p = sv * c * (g - j) * (1 + j)**(-(n - t0)) else: raise ValueError("tprac must be 'theoretical' or 'practical'.") return adj_p
def accrued_interest(self, t: float, gr: Union[float, Rate] = None, tprac: str = 'theoretical') -> float: """ Calculates the accrued interest in a coupon payment. Can be toggled between the clean or practical values. :param t: The valuation time. :type t: float :param gr: The valuation yield, if pricing a bond at a different yield. Defaults to the current bond yield. :type gr: float, Rate :param tprac: Whether you want the 'theoretical' or 'practical' value. :type tprac: str :return: The accrued interest. :rtype: float """ # get the next coupon t0 = max([x for x in self.coupons.times if x <= t]) ti = self.coupons.times.index(t0) t1 = self.coupons.times[ti + 1] cg = self.coupons.amounts[ti + 1] f = (t - t0) / (t1 - t0) if tprac == "practical": at = f * cg elif tprac == "theoretical": if gr is None: gr = self.gr gr = standardize_acc(gr) j = gr.effective_interval(t1=t0, t2=t1) at = cg * (((1 + j)**f) - 1) / j else: raise ValueError("tprac must be 'theoretical' or 'practical'") return at
def dirty(self, t: float, tprac: str = 'theoretical', gr: Union[float, Rate] = None) -> float: """ Calculates the dirty value of a bond. It can be toggled to switch between theoretical and practical dirty \ values. :param t: The valuation time. :type t: float :param tprac: Whether you want the practical or theoretical dirty value. Defaults to 'theoretical'. :type tprac: str :param gr: The valuation yield, if pricing a bond at a different yield. Defaults to the current bond yield. :type gr: float, Rate :return: The dirty value. :rtype: float """ if t == 0: t0 = 0 ti = -1 t1 = self.coupons.times[0] else: t0 = max([x for x in self.coupons.times if x <= t]) # get next coupon time ti = self.coupons.times.index(t0) t1 = self.coupons.times[ti + 1] # get next coupon amount f = (t - t0) / (t1 - t0) if gr is None: j_factor = (1 + self.gr.effective_interval(t1=t0, t2=t)) balance = self.balance(t0) else: jgr = standardize_acc(gr) j_factor = (1 + jgr.effective_interval(t1=t0, t2=t)) amounts = self.coupons.amounts[(ti + 1):] times = self.coupons.times[(ti + 1):] times = [x - t0 for x in times] red_t = self.term - t0 amounts += [self.red] times += [red_t] pmts = Payments(amounts=amounts, times=times, gr=jgr) balance = pmts.npv() if tprac == 'theoretical': dt = balance * j_factor elif tprac == 'practical': dt = self.balance(t0) * (1 + self.j * f) else: raise ValueError("tprac must be 'theoretical' or 'practical'.") return dt
def __init__(self, gr: Union[Accumulation, Callable, float, Rate], amount: Union[float, int, list, Callable] = 1.0, period: float = 1, term: float = None, n: float = None, gprog: float = 0.0, aprog: Union[float, int, tuple] = 0.0, times: list = None, reinv: [Accumulation, Callable, float, Rate] = None, deferral: float = 0.0, imd: str = 'immediate', loan: float = None, drb: str = None): self.term = term self.amount = amount self.period = period self.imd = imd self.gprog = gprog if isinstance(aprog, (float, int)): self.aprog = aprog self.mprog = 1 elif isinstance(aprog, tuple): self.aprog = aprog[0] self.mprog = aprog[1] * period else: raise ValueError("Invalid value provided to aprog.") imd_ind = 1 if imd == 'immediate' else 0 self.is_level_pmt = None self.reinv = reinv self.deferral = deferral self.loan = loan self.n_payments = n self.drb_pmt = None if term is None: if self.n_payments: self.term = self.n_payments * self.period if loan is None: r = self.n_payments else: r = max(self.get_r_pmt(gr), self.n_payments) elif times: self.term = max(times) r = len(times) self.n_payments = len(times) elif period == 0: dt = standardize_acc(gr).interest_rate.convert_rate( pattern="Force of Interest") r = np.Inf self.term = np.log(1 - loan / amount * dt) / (-dt) else: r = self.get_r_pmt(gr) r = ceil(r) if drb == 'drop' else floor(r) self.term = r * self.period else: if self.period == 0: r = np.Inf else: r = self.term / self.period # perpetuity if self.term == np.inf or self.n_payments == np.Inf: def perp_series(t): start = amount factor = 1 + gprog curr = 0 while curr < t: yield start * factor**curr curr += 1 def perp_times(t): curr = 0 + 1 if imd == 'immediate' else 0 while curr < t: yield curr + 1 amounts = perp_series times = perp_times self._ann_perp = 'perpetuity' self.n_payments = np.inf if gprog == 0: self.is_level_pmt = True else: self.is_level_pmt = False # continuously paying elif self.period == 0: amounts = [] times = [] self.n_payments = np.inf self._ann_perp = 'annuity' if isinstance(amount, Callable): if round(amount(0) * self.term, 3) == round(quad(amount, 0, self.term)[0], 3): Warning( "Level continuously paying annuity detected. It's better to supply a constant to the " "amount argument to speed up computation.") self.is_level_pmt = True else: self.is_level_pmt = False else: self.is_level_pmt = True else: if self.n_payments is None and term is not None: r_payments = self.term / period n_payments = floor(r_payments) self.n_payments = n_payments f = 0 elif self.n_payments is None and term is None: r_payments = self.get_r_pmt(gr) n_payments = floor(r_payments) f = r_payments - n_payments self.n_payments = n_payments elif r > self.n_payments: self.n_payments -= 1 f = r - self.n_payments else: f = 0 if isinstance(amount, (int, float)) or (isinstance(amount, list) and len(amount)) == 1: amounts = [ self.amount * (1 + self.gprog)**x + self.aprog * floor(x * self.mprog) for x in range(self.n_payments) ] times = [ period * (x + imd_ind) for x in range(self.n_payments) ] else: amounts = amount times = times times.sort() intervals = list(np.diff(times)) intervals = [round(x, 7) for x in intervals] if intervals[1:] != intervals[:-1]: raise Exception( "Non-level intervals detected, use payments class instead." ) else: self.period = intervals[0] if min(times) == 0: self.imd = 'due' self.term = max(times) + self.period else: self.imd = 'immediate' self.term = max(times) if deferral > 0: times = [x + deferral for x in times] if 0 < f < 1: if drb == "balloon": self.drb_pmt = self.get_balloon(gr) amounts[-1] = self.drb_pmt elif drb == "drop": self.drb_pmt = self.get_drop(gr) amounts.append(self.drb_pmt) times.append(self.term) self.n_payments += 1 self.is_level_pmt = False elif 1 <= f: self.drb_pmt = self.amount + olb_r(loan=self.loan, q=self.amount, period=self.period, gr=gr, t=self.term) amounts.append(self.drb_pmt) times.append(self.term) self.is_level_pmt = False else: pass if (isinstance(amount, (int, float)) or (isinstance(amount, list) and len(amount))) == 1 and \ gprog == 0 and \ aprog == 0 and self.drb_pmt is None: self.is_level_pmt = True elif gprog != 0 or aprog != 0: self.is_level_pmt = False elif amounts[1:] == amounts[:-1]: self.is_level_pmt = True else: self.is_level_pmt = False self._ann_perp = 'annuity' Payments.__init__(self, amounts=amounts, times=times, gr=gr) self.pattern = self._ann_perp + '-' + imd if imd not in ['immediate', 'due']: raise ValueError('imd can either be immediate or due.')
def sv(self): """ Calculates the accumulated value of the annuity. The formula used to calculate the accumulated value will \ depend on the type of annuity that gets inferred from the arguments provided to the parent class. Several \ classes of annuities come with shortcut formulas which will be used in favor of the equation of value formula \ to improve computation speed: #. Annuity-immediate: :math:`\\sx{\\angln i}` #. Annuity-due: :math:`\\sx**{\\angln i}` #. Perpetuity-immediate: :math:`\\sx{\\angl{\\infty} i}` #. Perpetuity-due: :math:`\\sx**{\\angl{\\infty} i}` #. Arithmetically increasing annuity-immediate: :math:`(I_{P, Q} s)_{\\angln i}` #. Arithmetically increasing annuity-due: :math:`(I_{P, Q} \\sx**{})_{\\angln i}` #. Arithmetically increasing perpetuity-immediate: :math:`(I_{P, Q} s)_{\\angl{\\infty} i}` #. Arithmetically increasing perpetuity-due: :math:`(I_{P, Q} s)_{\\angl{\\infty} i}` #. Geometrically increasing annuity-immediate #. Geometrically increasing annuity-due #. Geometrically increasing perpetuity-immediate #. Geometrically increasing perpetuity-due :return: The accumulated value of the annuity. :rtype: float """ # continuously paying annuity if isinstance(self.amount, Callable): sv = self.pv() * self.gr.val(self.term) elif isinstance( self.gr, Accumulation ) and self.gr.is_level and self.is_level_pmt and self.reinv is None: if self.period == 0: sv = self.sbar_angln() else: i = self.gr.val(self.period) - 1 n = self.n_payments sv = self.amount * ((1 + i)**n - 1) / i elif self.aprog != 0 and self.mprog == 0: if self.period == 0: sv = self.ibar_sbar_angln() else: i = self.gr.val(self.period) - 1 n = self.n_payments q = self.aprog p = self.amount s_n = ((1 + i)**n - 1) / i sv = p * s_n + q / i * (s_n - n) # reinvestment elif self.reinv is not None: i = self.gr.val(self.period) - 1 n = self.n_payments rn = n - 1 r = standardize_acc(self.reinv) r = r.val(self.period) - 1 dr = r / (1 + r) aprog = self.amount * i i_s = aprog * (((((1 + r)**rn - 1) / dr) - rn) / r) k = self.amount * n sv = i_s + k else: sv = self.eq_val(t=self.term + self.deferral) return sv if self.deferral != 0: sv = sv * self.gr.val(self.deferral) if self.imd == 'due': sv = sv * self.gr.val(self.period) return sv
def __init__(self, gr: Union[float, Rate, TieredTime] = None, pmt: Union[float, int, Payments] = None, period: float = None, term: float = None, amt: float = None, cents: bool = False, sfr: Union[float, Rate, TieredTime] = None, sfd: float = None, sf_split: float = 1.0, sfh_gr: Union[float, Rate, TieredTime] = None, pp: float = None): self.pmt = pmt self.period = period self.term = term if gr is not None: self.gr = standardize_acc(gr) else: self.gr = None self.cents = cents if sfr: self.sfr = standardize_acc(sfr) else: self.sfr = None self.sfd = sfd self.sf_split = sf_split self.pp = pp self.pmt_is_level = None if isinstance(pmt, Payments): self.pmt_sched = pmt if pmt.amounts[1:] == pmt.amounts[:-1]: self.pmt_is_level = True else: self.pmt_is_level = False if amt is None: if sfr is None: ann = Annuity(period=self.period, term=self.term, gr=self.gr, amount=self.pmt).pv() elif sfr and sf_split == 1: ann_snk = Annuity(period=self.period, term=self.term, gr=self.sfr, amount=1).pv() sf_i = self.gr.effective_interval(t2=self.period) sf_j = self.sfr.effective_interval(t2=self.period) ann = self.pmt * (ann_snk / (((sf_i - sf_j) * ann_snk) + 1)) else: ann = self.hybrid_principal() self.amt = ann else: self.amt = amt if sfh_gr: self.sfh_gr = standardize_acc(sfh_gr) elif gr is not None and sfr is not None: self.sfh_gr = standardize_acc(self.sgr_equiv()) else: self.sfh_gr = self.gr if pmt is None: if period and term: self.pmt_sched = self.get_payments() self.pmt = self.pmt_sched.amounts[0] elif pp: self.pmt_sched = self.get_payments() self.pmt = self.pmt_sched.amounts[0] else: self.pmt_sched = Payments(times=[], amounts=[]) self.pmt = None elif pmt and isinstance(pmt, (float, int)) and period and term: n_payments = ceil(term / period) self.pmt_sched = Payments(times=[(x + 1) * period for x in range(n_payments)], amounts=[pmt] * n_payments) self.pmt_is_level = True else: self.pmt_sched = Payments(times=[], amounts=[]) if sfr is not None and sfd is None and pmt is not None: sv = Annuity(gr=self.sfr, period=self.period, term=self.term).sv() self.sfd = self.amt / sv Payments.__init__(self, amounts=[amt] + [-x for x in self.pmt_sched.amounts], times=[0] + self.pmt_sched.times, gr=self.gr)
def __init__(self, face: float = None, term: float = None, red: float = None, gr: Union[float, Rate] = None, cgr: Rate = None, alpha: Union[float, list] = None, cfreq: Union[float, list] = None, price: float = None, pd: float = None, k: float = None, fr: float = None): self.face = face self.term = term self.k = k if [cgr, alpha].count(None) == 2: # if bond is par and priced at par if price == red == face and gr is not None: alpha = standardize_acc(gr).interest_rate.convert_rate( pattern="Nominal Interest", freq=cfreq).rate self.alpha = alpha c_args = 1 else: c_args = None self.alpha = None else: c_args = len([cgr, alpha]) if c_args: cgr_dict = parse_cgr(alpha=alpha, cfreq=cfreq, cgr=cgr) self.cgr = cgr_dict['cgr'] self.alpha = cgr_dict['alpha'] self.cfreq = cgr_dict['cfreq'] self.fr_is_level = isinstance(self.alpha, (float, int)) self.fr = self.get_coupon_amt() if not self.fr_is_level: self.coupon_intervals = self.get_coupon_intervals() else: self.fr = fr self.cfreq = cfreq if [red, k].count(None) == 2: red_args = None else: red_args = len([red, k]) if term is not None: self.is_term_floor = self.term_floor() else: pass if [alpha, cfreq, fr, cgr].count(None) == 4: self.is_zero = True else: self.is_zero = False if self.is_zero: args = [gr, red_args, price, term] else: args = [gr, red_args, price, term, c_args] n_missing = args.count(None) if n_missing == 0: self.red = red self.n_coupons = self.get_n_coupons() self.gr = standardize_acc(gr) self.coupons = self.get_coupons() self.price = price elif n_missing == 1: if price is None: self.n_coupons = self.get_n_coupons() self.red = red self.gr = standardize_acc(gr) self.coupons = self.get_coupons() elif gr is None: self.n_coupons = self.get_n_coupons() self.red = red self.price = price if self.is_zero: amounts = [-price, red] elif self.fr_is_level: amounts = [-price] + [self.fr] * self.n_coupons + [red] else: amounts = [-price] cis = self.get_coupon_intervals() for afr, cf, ci in zip(self.fr, self.cfreq, cis): n = cf * ci amounts += [afr[0]] * n amounts += [red] times = [0.0] + self.get_coupon_times() + [term] pmts = Payments(amounts=amounts, times=times) irr = [x for x in pmts.irr() if x > 0] self.gr = standardize_acc(min(irr)) self.coupons = self.get_coupons() elif term is None: if self.is_zero: self.red = red self.gr = standardize_acc(gr) self.price = price self.term = Amount(gr=gr, k=price).solve_t(fv=red) else: self.n_coupons = self.get_n_coupons() self.red = red self.gr = standardize_acc(gr) self.price = price self.coupons = self.get_coupons() self.red = self.get_redemption() elif n_missing == 2: if price is None and red_args is None: if pd is not None: self.n_coupons = self.get_n_coupons() self.gr = standardize_acc(gr) self.coupons = self.get_coupons() self.red = (self.coupons.pv() - pd) / (1 - self.gr.discount_func(t=self.term)) self.price = self.red + pd else: raise Exception( "Unable to evaluate bond. Too many missing arguments.") elif price is None and term is None: if k is not None: self.gr = standardize_acc(gr) self.red = red self.g = self.fr / self.red self.price = self.makeham() self.term = self.gr.solve_t(pv=k, fv=self.red) self.n_coupons = self.get_n_coupons() self.coupons = self.get_coupons() self.is_term_floor = self.term_floor() else: raise Exception( "Unable to evaluate bond. Too many missing arguments.") elif price is None and c_args is None: if fr is not None: self.gr = standardize_acc(gr) self.red = red self.price = self.base_amount() self.fr_is_level = True self.fr = fr self.n_coupons = self.get_n_coupons() self.coupons = self.get_coupons() self.alpha = None else: raise Exception( "Unable to evaluate bond. Too many missing arguments.") else: raise Exception( "Unable to evaluate bond. Too many missing arguments.") else: raise Exception( "Unable to evaluate bond. Too many missing arguments.") if self.is_zero: amounts = [self.red] times = [self.term] else: amounts = self.coupons.amounts + [self.red] times = self.coupons.times + [self.term] Payments.__init__(self, amounts=amounts, times=times, gr=self.gr) if price is None: if self.is_term_floor: self.price = self.npv() else: if self.n_coupons == 1: j = self.gr.val(1 / self.cfreq) - 1 f = 1 - self.term / (1 / self.cfreq) self.price = (self.red + self.fr) / (1 + (1 - f) * j) - f * self.fr else: self.price = self.clean(t=0) else: pass self.amounts = [-self.price] + self.amounts self.times = [0] + self.times if self.is_zero: pass elif self.fr_is_level: self.j = self.gr.val(1 / self.cfreq) - 1 self.base = self.fr / self.j self.g = self.fr / self.red self.premium = self.price - self.red self.discount = self.red - self.price self.k = self.gr.discount_func(t=self.term, fv=self.red)