Esempio n. 1
0
    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
Esempio n. 2
0
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
Esempio n. 3
0
 def __init__(
     self,
     s0,
     gr,
 ):
     self.s0 = s0
     self.acc = standardize_acc(gr)
Esempio n. 4
0
    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
Esempio n. 5
0
    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
Esempio n. 6
0
    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
Esempio n. 7
0
    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
Esempio n. 8
0
    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
Esempio n. 9
0
    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
Esempio n. 10
0
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]
Esempio n. 11
0
    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
Esempio n. 12
0
    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
Esempio n. 13
0
    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
Esempio n. 14
0
    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
Esempio n. 15
0
    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
Esempio n. 16
0
    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.')
Esempio n. 17
0
    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
Esempio n. 18
0
    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)
Esempio n. 19
0
    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)