Example #1
0
 def test_coupon_on_date(self):
     initial_notional = 1000
     coup1_start_date = date(2017, 12, 8)
     coup1_date = date(2018, 3, 9)
     # note start coupon date != prev coupon date! due to holidays.
     # but coupon value disregards this start date and takes it from
     # prev. coupon date
     coup2_start_date = date(2018, 3, 12)
     coup2_date = date(2018, 6, 8)
     coupons = [
         CouponScheduleEntry(coup1_date, None, coup1_start_date, 0.0, 11.7),
         CouponScheduleEntry(coup2_date, None, coup2_start_date, 0.0, 11.7)
     ]
     amortizations = [
         AmortizationScheduleEntry(date(2018, 1, 1), 30.0, 300.0),
         AmortizationScheduleEntry(coup2_date, 70.0, 700.0)
     ]
     b = Bond(initial_notional=initial_notional,
              coupons=coupons,
              amortizations=amortizations)
     notional_on_coup2 = b.notional_on_date(coup2_date)
     assert notional_on_coup2 == 700.0
     # note we find days between coupon date 1 & 2, not from coupon 2 start/end date, because otherwise they
     # won't match due to different day counts due to holidays
     coupon_days = (coup2_date - coup1_date).days
     expected_coupon1 = round(
         (coupon_days / YEAR_BASE) * (coupons[0].yearly_prc / 100.0) *
         notional_on_coup2, 2)
     coupon_days_wrong = (coup2_date - coupons[1].start_date).days
     expected_coupon1_wrong = round(
         (coupon_days_wrong / YEAR_BASE) * (coupons[0].yearly_prc / 100.0) *
         notional_on_coup2, 2)
     assert b.accrued_coupon_on_date(coup2_date) == pytest.approx(
         expected_coupon1)
     assert expected_coupon1 != pytest.approx(expected_coupon1_wrong)
Example #2
0
 def test_cannot_create_bond_with_not_increasing_amort_dates(self):
     with pytest.raises(ValueError):
         amortizations = [
             AmortizationScheduleEntry(date(2019, 9, 6), 30.0, 300.0),
             # note date goes before first date, this is wrong
             AmortizationScheduleEntry(date(2019, 9, 3), 70.0, 700.0)
         ]
         Bond(coupons=[], amortizations=amortizations)
Example #3
0
 def test_payments_since_date(self):
     coup1_start_date = date(2017, 12, 8)
     coup1_date = date(2018, 3, 9)
     coup2_start_date = date(2018, 3, 12)
     coup2_date = date(2018, 6, 8)
     coupons = [
         CouponScheduleEntry(coup1_date, None, coup1_start_date, 0.0, 11.7),
         CouponScheduleEntry(coup2_date, None, coup2_start_date, 0.0, 11.7)
     ]
     amortizations = [
         AmortizationScheduleEntry(date(2018, 1, 1), 30.0, 300.0),
         AmortizationScheduleEntry(coup2_date, 70.0, 700.0)
     ]
     b = Bond(coupons=coupons, amortizations=amortizations)
     cps, ams = b.payments_since_date(date(2018, 5, 8))
     assert cps == [coupons[1]]
     assert ams == [amortizations[1]]
Example #4
0
 def test_cannot_create_bond_with_not_increasing_coupon_dates(self):
     with pytest.raises(ValueError):
         amortizations = [
             AmortizationScheduleEntry(date(2019, 9, 3), 100.0, 1000.0)
         ]
         coupons = [
             CouponScheduleEntry(date(2018, 1, 1), None, date(2017, 1, 1),
                                 10.0, 10.0),
             # note date goes before first date, this is wrong
             CouponScheduleEntry(date(2017, 12, 1), None,
                                 date(2017, 11, 30), 10.0, 10.0)
         ]
         Bond(coupons=coupons, amortizations=amortizations)
Example #5
0
    def test_notional_on_date(self):
        initial_notional = 1000
        am_dt1 = date(2019, 9, 6)
        am_dt2 = date(2020, 9, 4)
        am_dt3 = date(2021, 9, 3)
        amortizations = [
            AmortizationScheduleEntry(am_dt1, 30.0, 300.0),
            AmortizationScheduleEntry(am_dt2, 30.0, 300.0),
            AmortizationScheduleEntry(am_dt3, 40.0, 400.0)
        ]
        b = Bond(initial_notional=initial_notional,
                 coupons=[],
                 amortizations=amortizations)
        # date before first amortization - notional should equal initial notional
        one_day = timedelta(days=1)

        assert b.notional_on_date(am_dt1 - one_day) == initial_notional
        # on amortization date - notional still stays the same, it affects only next coupons
        assert b.notional_on_date(am_dt1) == initial_notional
        notional_after_am_dt1 = initial_notional * (
            1 - amortizations[0].value_prc / 100.0)
        assert b.notional_on_date(am_dt1 + one_day) == pytest.approx(
            notional_after_am_dt1)

        assert b.notional_on_date(am_dt2 - one_day) == notional_after_am_dt1
        assert b.notional_on_date(am_dt2) == notional_after_am_dt1
        notional_after_am_dt2 = initial_notional * (
            1 -
            (amortizations[0].value_prc + amortizations[0].value_prc) / 100.0)
        assert b.notional_on_date(am_dt2 + one_day) == pytest.approx(
            notional_after_am_dt2)

        assert b.notional_on_date(am_dt3 - one_day) == notional_after_am_dt2
        assert b.notional_on_date(am_dt3) == notional_after_am_dt2
        # last amortization date is settlement date
        notional_after_settlement = 0.0
        assert b.notional_on_date(am_dt3 + one_day) == pytest.approx(
            notional_after_settlement)
Example #6
0
 def test_ytm(self):
     coup1_start_date = date(2017, 3, 20)
     coup1_date = date(2018, 3, 20)
     coup2_start_date = date(2018, 3, 21)
     coup2_date = date(2019, 3, 20)
     coupons = [
         CouponScheduleEntry(coup1_date, None, coup1_start_date, 100.0,
                             0.0),
         CouponScheduleEntry(coup2_date, None, coup2_start_date, 100.0, 0.0)
     ]
     amortizations = [AmortizationScheduleEntry(coup2_date, 100.0, 1000.0)]
     b = Bond(coupons=coupons, amortizations=amortizations)
     # assume we buy slightly after last coupon period start
     buy_price = 103.0
     buy_date = date(2018, 5, 20)
     ytm = b.yield_to_maturity(buy_price, buy_date, date(2018, 5, 21))
     assert ytm == pytest.approx(0.0821)
     # assert ytm definition holds
     yf = ((coup2_date - buy_date).days / YEAR_BASE)
     accrued = b.accrued_coupon_on_date(buy_date)
     fv_initial_investment = (buy_price * 10 + accrued) * (1.0 + ytm)**yf
     # no need to accrue since they already are on correct date
     fv_payments = coupons[1].value + amortizations[0].value
     assert fv_initial_investment == pytest.approx(fv_payments, rel=5E-5)
Example #7
0
 def test_cannot_create_bond_with_not_fully_amortized_notional(self):
     with pytest.raises(ValueError):
         amortizations = [
             AmortizationScheduleEntry(date(2019, 9, 3), 100.0, 999.0)
         ]
         Bond(coupons=[], amortizations=amortizations)
Example #8
0
def __parse_am_entry(am_entry) -> AmortizationScheduleEntry:
    str_date = am_entry.get("amortdate")
    am_date = datetime.date.fromisoformat(str_date)
    value_prc = float(am_entry.get("valueprc"))
    value = float(am_entry.get("value"))
    return AmortizationScheduleEntry(am_date, value_prc, value)
Example #9
0
    def test_can_parse_bond(self, sample_bond_xml: str):
        bond = m.parse_coupon_schedule_xml(sample_bond_xml)
        assert bond.isin == "RU000A0JWSQ7"
        assert bond.name == "Мордовия 34003 обл."
        assert bond.initial_notional == 1000
        assert bond.notional_ccy == "RUB"

        assert bond.coupons == [
            CouponScheduleEntry(date(2016, 12, 9), None, date(2016, 9, 9),
                                29.17, 11.7),
            CouponScheduleEntry(date(2017, 3, 10), None, date(2016, 12, 9),
                                29.17, 11.7),
            CouponScheduleEntry(date(2017, 6, 9), None, date(2017, 3, 10),
                                29.17, 11.7),
            CouponScheduleEntry(date(2017, 9, 8), None, date(2017, 6, 9),
                                29.17, 11.7),
            CouponScheduleEntry(date(2017, 12, 8), date(2017, 12, 7),
                                date(2017, 9, 8), 29.17, 11.7),
            CouponScheduleEntry(date(2018, 3, 9), date(2018, 3, 7),
                                date(2017, 12, 8), 29.17, 11.7),
            CouponScheduleEntry(
                date(2018, 6, 8),
                date(2018, 6, 7),
                # note start coupon date != prev coupon date! due to holidays.
                # but coupon value disregards this start date and takes it from
                # prev. coupon date
                date(2018, 3, 12),
                29.17,
                11.7),
            CouponScheduleEntry(date(2018, 9, 7), date(2018, 9, 6),
                                date(2018, 6, 8), 29.17, 11.7),
            CouponScheduleEntry(date(2018, 12, 7), date(2018, 12, 6),
                                date(2018, 9, 7), 29.17, 11.7),
            CouponScheduleEntry(date(2019, 3, 8), date(2019, 3, 7),
                                date(2018, 12, 7), 29.17, 11.7),
            CouponScheduleEntry(date(2019, 6, 7), date(2019, 6, 6),
                                date(2019, 3, 8), 29.17, 11.7),
            CouponScheduleEntry(date(2019, 9, 6), date(2019, 9, 5),
                                date(2019, 6, 7), 29.17, 11.7),
            CouponScheduleEntry(date(2019, 12, 6), date(2019, 12, 5),
                                date(2019, 9, 6), 20.42, 11.7),
            CouponScheduleEntry(date(2020, 3, 6), date(2020, 3, 5),
                                date(2019, 12, 6), 20.42, 11.7),
            CouponScheduleEntry(date(2020, 6, 5), date(2020, 6, 4),
                                date(2020, 3, 6), 20.42, 11.7),
            CouponScheduleEntry(date(2020, 9, 4), date(2020, 9, 3),
                                date(2020, 6, 5), 20.42, 11.7),
            CouponScheduleEntry(date(2020, 12, 4), date(2020, 12, 3),
                                date(2020, 9, 4), 11.67, 11.7),
            CouponScheduleEntry(date(2021, 3, 5), date(2021, 3, 4),
                                date(2020, 12, 4), 11.67, 11.7),
            CouponScheduleEntry(date(2021, 6, 4), date(2021, 6, 3),
                                date(2021, 3, 5), 11.67, 11.7),
            CouponScheduleEntry(date(2021, 9, 3), date(2021, 9, 2),
                                date(2021, 6, 4), 11.67, 11.7)
        ]

        assert bond.amortizations == [
            AmortizationScheduleEntry(date(2019, 9, 6), 30.0, 300.0),
            AmortizationScheduleEntry(date(2020, 9, 4), 30.0, 300.0),
            AmortizationScheduleEntry(date(2021, 9, 3), 40.0, 400.0)
        ]

        # assert our calculations match those from exchange
        for coupon in bond.coupons:
            try:
                assert coupon.value == bond.accrued_coupon_on_date(
                    coupon.coupon_date)
            except:
                print(coupon)
                raise