示例#1
0
文件: bond.py 项目: genedan/TmVal
    def get_coupons(self) -> Annuity:
        """
        Calculates the coupons and returns them as an Annuity object.

        :return: The bond coupons.
        :rtype: Annuity
        """
        if self.is_zero:
            coupons = None
        elif self.fr_is_level:

            if round(self.term % (1 / self.cfreq), 5) == 0:

                coupons = Annuity(gr=self.gr,
                                  amount=self.fr,
                                  period=1 / self.cfreq,
                                  term=self.term)

            else:
                period = 1 / self.cfreq
                f = self.term - ((self.n_coupons - 1) * period)
                coupons = Annuity(gr=self.gr,
                                  amount=self.fr,
                                  n=self.n_coupons,
                                  imd='due',
                                  deferral=f)

        else:
            times = []
            amounts = []
            base = 0

            if isinstance(self.fr, Iterable)\
                    and isinstance(self.cfreq, Iterable)\
                    and isinstance(self.coupon_intervals, Iterable):

                for a, c, t in zip(self.fr, self.cfreq, self.coupon_intervals):
                    n_pmt = t * c
                    amounts += [a[0]] * n_pmt
                    times += [(x + base + 1) / c for x in range(n_pmt)]
                    base += n_pmt

            else:
                raise TypeError(
                    "fr, cfreq, and coupon_intervals must be iterable when coupons are nonlevel"
                )

            coupons = Annuity(gr=self.gr,
                              amount=amounts,
                              times=times,
                              term=self.term)

        return coupons
示例#2
0
文件: bond.py 项目: genedan/TmVal
    def balance(self, t: float, n: int = None) -> float:
        """
        Calculates the bond balance at time t, where t must coincide with a payment time. Alternatively, if you'd like \
        to know the balance just prior to the n-th coupon payment, you may supply the value to the argument n.

        :param t: The balance time.
        :type t: float
        :param n: The n-th coupon payment.
        :type n: int
        :return: The bond balance.
        :rtype: float
        """

        if t not in self.times:
            raise ValueError(
                "Bond balance only available when t occurs on a payment date. For valuations "
                "between payment dates, use the clean or dirty value formulas."
            )

        if n is not None and t is not None:
            raise ValueError("Can supply t or n, but not both.")

        if t is None:
            if n is not None:
                t = self.coupons.times[n - 1]
            else:
                raise ValueError("You need to supply a time or n-th coupon.")

        nc = self.n_coupons
        t0 = floor(t * self.cfreq)
        g = self.g
        c = self.red
        j = self.j

        if t < self.term:
            ann = Annuity(
                gr=j,
                n=nc - t0,
            )

            bt = c * (g - j) * ann.pv() + c
        elif t == self.term:
            bt = self.red
        else:
            bt = 0

        return bt
示例#3
0
    def sgr_equiv(self) -> Rate:
        """
        Calculates the sinking fund rate such that would produce a loan payment schedule equivalent to that of an
        amortized loan.

        :return: The sinking fund rate.
        :rtype: Rate
        """

        if self.pmt_is_level:

            ann = Annuity(period=self.period, term=self.term, gr=self.gr).pv()

            snn = Annuity(period=self.period, term=self.term, gr=self.sfr).sv()

            gr = Rate(rate=1 / ann - 1 / snn,
                      pattern="Effective Interest",
                      interval=self.period)
        else:
            pmts = Payments(amounts=self.pmt_sched.amounts,
                            times=self.pmt_sched.times,
                            gr=self.sfr)

            snn = Annuity(period=self.period, term=self.term, gr=self.sfr)

            i = (pmts.eq_val(t=self.term) - self.amt) / (self.amt * snn.sv())

            gr = Rate(rate=i,
                      pattern="Effective Interest",
                      interval=self.period)

        return gr
示例#4
0
    def get_payments(self) -> Payments:
        """
        Takes the arguments supplied and creates the payment schedule for the loan. The return type is a \
        :class:`.Payments` object, so it contains the payment times and amounts.

        :return: The payment schedule.
        :rtype: Payments
        """

        if self.sfr:
            if self.sfd is not None:
                interest_due = self.gr.effective_interval(
                    t2=self.period) * self.amt
                n_payments = ceil(self.term / self.period)

                final_pmt = self.sf_final()
                pmts = Payments(amounts=[self.sfd + interest_due] *
                                (n_payments - 1) + [final_pmt],
                                times=[(x + 1) * self.period
                                       for x in range(n_payments)])

            else:
                interest_due = self.sfh_gr.effective_interval(
                    t2=self.period) * self.amt
                n_payments = ceil(self.term / self.period)

                sv_ann = Annuity(gr=self.sfr.effective_rate(self.period),
                                 period=self.period,
                                 term=self.term).sv()

                sfd = self.amt / sv_ann
                amt = interest_due + sfd
                self.sfd = sfd
                pmts = Payments(amounts=[amt] * n_payments,
                                times=[(x + 1) * self.period
                                       for x in range(n_payments)])

        elif self.pp is not None:
            pmts = self.fixed_principal()

        else:

            pmts_dict = get_loan_pmt(loan_amt=self.amt,
                                     period=self.period,
                                     term=self.term,
                                     gr=self.gr.gr,
                                     cents=self.cents)

            pmts = Payments(amounts=pmts_dict['amounts'],
                            times=pmts_dict['times'])

        return pmts
示例#5
0
    def sf_final(self, payments: Payments = None) -> float:
        """
        Calculates the final payment required to settle a sinking fund loan. You may supply payments, if they differed \
        from the original payment schedule.

        :param payments: A list of payments, if different from the original payment schedule.
        :type payments: Payments, optional
        :return: The final sinking fund payment.
        :rtype: float
        """

        if self.sfr is None:
            raise Exception("sf_final only applicable to sinking fund loans.")

        if payments:
            bal = self.amt
            t0 = 0
            sf_amounts = []
            sf_times = []
            for amount, time in zip(payments.amounts, payments.times):
                interest_due = bal * self.gr.effective_interval(t1=t0, t2=time)
                if amount >= interest_due:
                    sf_deposit = amount - interest_due
                else:
                    sf_deposit = 0
                    bal += interest_due - amount
                sf_amounts += [sf_deposit]
                sf_times += [time]
                t0 = time
            sf_payments = Payments(amounts=sf_amounts,
                                   times=sf_times,
                                   gr=self.sfr)

            sv = sf_payments.eq_val(self.term)

            final_pmt = bal * (
                1 + self.gr.effective_interval(t1=t0, t2=self.term)) - sv
        else:
            sv = Annuity(amount=self.sfd,
                         gr=self.sfr,
                         period=self.period,
                         term=self.term - self.period).eq_val(self.term)

            final_pmt = self.amt - sv

        return final_pmt
示例#6
0
    def rc_yield(self) -> list:
        """
        Calculates the yield rate based off replacement of capital.

        :return: The yield rate based off replacement of capital.
        :rtype: list
        """
        n_payments = ceil(self.term / self.period)
        if self.pmt_is_level:
            extra = [self.pmt - self.sfd] * n_payments

            sv = Annuity(amount=self.sfd,
                         gr=self.sfr,
                         term=self.term,
                         period=self.period).sv()

            pmts = Payments(amounts=[-self.amt] + extra + [sv],
                            times=[0.0] + [(x + 1) * self.period
                                           for x in range(n_payments)] +
                            [self.term],
                            gr=self.sfr)

        else:
            sf_deps = self.sinking()['sf_deposit']

            extra = []
            for amount, sfd in zip(self.pmt_sched.amounts, sf_deps[1:]):
                extra_i = amount - sfd
                extra += [extra_i]

            sv = self.sinking()['sf_bal'][-1]

            pmts = Payments(amounts=[-self.amt] + extra + [sv],
                            times=[0.0] + [(x + 1) * self.period
                                           for x in range(n_payments)] +
                            [self.term],
                            gr=self.sfr)

        return pmts.irr()
示例#7
0
    def hybrid_principal(self) -> float:
        """
        Calculates the loan amount based on a hybrid amortized/sinking fund loan.

        :return: The loan amount.
        :rtype: float
        """
        ann_am = Annuity(term=self.term, gr=self.gr, period=self.period)

        ann_sf = Annuity(term=self.term, gr=self.sfr, period=self.period)

        if self.sfh_gr:
            sfh = self.sfh_gr.effective_interval(t2=self.period)
        else:
            sfh = self.gr.effective_interval(t2=self.period)

        return self.pmt / (sfh * self.sf_split + self.sf_split / ann_sf.sv() +
                           (1 - self.sf_split) / ann_am.pv())
示例#8
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)