Пример #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)
Пример #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)
Пример #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]]
Пример #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)
Пример #5
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)
Пример #6
0
def parse_coupon_schedule_xml(data: str) -> Bond:
    root = ET.fromstring(data)
    first_row = next(root.iter("row"))
    isin = first_row.get("isin")
    name = first_row.get("name")
    # note: reply rows contain "facevalue" but it's incorrect, it's "current facevalue"
    initial_notional = float(first_row.get("initialfacevalue"))
    notional_ccy = first_row.get("faceunit")

    am_schedule = [
        __parse_am_entry(am_entry)
        for am_entry in root.findall(".//data[@id='amortizations']//row")
    ]
    cp_schedule = [
        __parse_coupon_entry(cp_entry)
        for cp_entry in root.findall(".//data[@id='coupons']//row")
    ]

    return Bond(isin=isin,
                name=name,
                initial_notional=initial_notional,
                notional_ccy=notional_ccy,
                coupons=cp_schedule,
                amortizations=am_schedule)
Пример #7
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)
Пример #8
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)