Esc

    The API is under testing and may change before release.

    Payment-in-kind bond

    This page lists what you need to price a payment-in-kind (PIK) bond: the constructor, the capitalisation schedule, the accreted face and redemption, how it values off a curve, and an example you can run. A PIK pays its coupon in more bond instead of cash, so the face grows and each later coupon accrues on a larger amount.

    Quick example

    The quotes are stated in the file; the Rust tab borrows only the Sheet type from the desk sheets, so load them once and it runs as shown.

    This books a five-year EUR PIK on ten million, capitalising 8% a year on ACT/365 Fixed. It prints the capitalisation schedule, reads the face on dates between and on capitalisations, values the bond off an €STR curve stated in the file on 3 July 2028, prices it at a 10% yield, and shows that a rate that isn't positive is refused.

    Rust
    use chrono::NaiveDate;
    use helixio_docs_sheets::Sheet;
    use helixrs::{
        AnyInstrument, CollateralSpec, Currency, DayCountFraction, Frequency, MarketContext,
        PaymentInKindBond, PikError, QuoteSheet, YieldBasis, YieldCurve,
    };
    
    fn date(year: i32, month: u32, day: u32) -> NaiveDate {
        NaiveDate::from_ymd_opt(year, month, day).expect("a real calendar date")
    }
    
    const RATE: f64 = 0.08;
    const FACE: f64 = 10_000_000.0;
    
    /// €STR OIS par closes on 3 July 2028, the day the bond is valued.
    const EUR_ESTR_JULY_2028: Sheet = &[
        ("EUR-ESTR-OIS", "1Y", 0.025),
        ("EUR-ESTR-OIS", "2Y", 0.026),
        ("EUR-ESTR-OIS", "3Y", 0.027),
        ("EUR-ESTR-OIS", "5Y", 0.029),
    ];
    
    fn main() -> Result<(), Box<dyn std::error::Error>> {
        let issue = date(2026, 1, 2);
        let maturity = date(2031, 1, 2);
        let eur = Currency::new("EUR").expect("a registered currency");
        let pik = PaymentInKindBond::new(
            eur,
            issue,
            maturity,
            RATE,
            Frequency::Annual,
            DayCountFraction::Act365Fixed,
            FACE,
        )?;
    
        // Each coupon date adds the period's interest to the face instead of paying it.
        let table = pik.capitalisations();
        println!("date          year frac.  face before      capitalised    face after");
        for row in &table {
            println!(
                "{}    {:.6}    {:.2}    {:.2}    {:.2}",
                row.date,
                row.year_fraction,
                row.face_before,
                row.amount(),
                row.face_after
            );
        }
    
        // The accreted face on any date: the face at issue, then larger after each
        // capitalisation, and flat in between.
        let probes = [issue, date(2026, 7, 1), date(2027, 1, 2), date(2029, 6, 30), maturity];
        let faces: Vec<f64> = probes.iter().map(|at| pik.current_face(*at)).collect();
        for (at, value) in probes.iter().zip(&faces) {
            println!("face on {at}    {value:.2}");
        }
        let factor = faces[faces.len() - 1] / FACE;
        println!("factor at maturity    {factor:.10}");
        println!("(1 + r)^5             {:.10}", (1.0_f64 + RATE).powf(5.0));
        println!("1 + 5r                {:.10}", 1.0 + 5.0 * RATE);
    
        // A PIK pays nothing until maturity, when it redeems the accreted face. Its
        // value is that one flow at the curve's discount factor. At a yield, the
        // dirty price per 100 of par face is the redemption discounted.
        let settlement = date(2028, 7, 3);
        let sheet = QuoteSheet::from_rows(EUR_ESTR_JULY_2028.iter().copied())?;
        let market =
            MarketContext::as_of(settlement).with_recipe_quotes(&CollateralSpec::Currency(eur), &sheet)?;
        let redemption = pik.redemption_per_100();
        let lifted = market.lift::<f64>();
        let view = market.view(&lifted)?;
        let value = view.npv(&AnyInstrument::PaymentInKindBond(Box::new(pik.clone())))?;
        let annual = YieldBasis::compounded_at(1.0);
        let price_par = pik.dirty_price_on_par_face(0.10, settlement, annual);
        println!("redemption per 100    {redemption:.6}");
        println!("value off the curve   {value:.2}");
        println!("dirty per 100 at 10%  {price_par:.6}");
    
        // A zero rate capitalises nothing and a negative one would shrink the face.
        let mut refusals = 0;
        for rate in [0.0, -0.01] {
            let built = PaymentInKindBond::new(
                eur,
                issue,
                maturity,
                rate,
                Frequency::Annual,
                DayCountFraction::Act365Fixed,
                FACE,
            );
            if matches!(built, Err(PikError::NonPositiveRate)) {
                refusals += 1;
            }
        }
        println!("refused {refusals} of 2 non-positive rates");
    
        Ok(())
    }
    Python
    import datetime as dt
    
    import helixio as hlx
    
    ISSUE = dt.date(2026, 1, 2)
    MATURITY = dt.date(2031, 1, 2)
    RATE = 0.08
    FACE = 10_000_000.0
    PIK = hlx.products.payment_in_kind_bond(
        "EUR", ISSUE, MATURITY, rate=RATE, frequency=hlx.dates.Frequency.Annual,
        day_count=hlx.dates.DayCountFraction.Act365Fixed, face=FACE,
    )
    
    # €STR OIS par closes on 3 July 2028, the day the bond is valued.
    EUR_ESTR_JULY_2028 = {
        "EUR-ESTR-OIS": {"1Y": 0.025, "2Y": 0.026, "3Y": 0.027, "5Y": 0.029},
    }
    
    # Each coupon date adds the period's interest to the face instead of paying it.
    table = hlx.bonds.capitalisations(PIK).to_dict()
    print("date          year frac.  face before      capitalised    face after")
    for k, paid in enumerate(table["date"]):
        print(
            f"{paid}    {table['year_fraction'][k]:.6f}    {table['face_before'][k]:.2f}"
            f"    {table['amount'][k]:.2f}    {table['face_after'][k]:.2f}"
        )
    
    # The accreted face on any date: the face at issue, then larger after each
    # capitalisation, and flat in between.
    probes = [ISSUE, dt.date(2026, 7, 1), dt.date(2027, 1, 2), dt.date(2029, 6, 30), MATURITY]
    faces = [hlx.bonds.accreted_face(PIK, at) for at in probes]
    for at, value in zip(probes, faces):
        print(f"face on {at}    {value:.2f}")
    factor = faces[-1] / FACE
    print(f"factor at maturity    {factor:.10f}")
    print(f"(1 + r)^5             {(1.0 + RATE) ** 5:.10f}")
    print(f"1 + 5r                {1.0 + 5.0 * RATE:.10f}")
    
    # A PIK pays nothing until maturity, when it redeems the accreted face. Its
    # value is that one flow at the curve's discount factor. At a yield, the dirty
    # price per 100 of par face is the redemption discounted.
    settlement = dt.date(2028, 7, 3)
    market = hlx.MarketContext(settlement).with_recipe_quotes("EUR", EUR_ESTR_JULY_2028)
    redemption = hlx.bonds.accreted_redemption(PIK)
    value = PIK.npv(market)
    tau = (MATURITY - settlement).days / 365.0
    price_par = redemption * (1.0 + 0.10) ** -tau
    print(f"redemption per 100    {redemption:.6f}")
    print(f"value off the curve   {value:.2f}")
    print(f"dirty per 100 at 10%  {price_par:.6f}")
    
    # A zero rate capitalises nothing and a negative one would shrink the face.
    refusals = 0
    for rate in [0.0, -0.01]:
        try:
            hlx.products.payment_in_kind_bond("EUR", ISSUE, MATURITY, rate=rate, frequency="annual", day_count="ACT/365.FIXED", face=FACE)
        except ValueError:
            refusals += 1
    print(f"refused {refusals} of 2 non-positive rates")
    date          year frac.  face before      capitalised    face after
    2027-01-02    1.000000    10000000.00    800000.00    10800000.00
    2028-01-02    1.000000    10800000.00    864000.00    11664000.00
    2029-01-02    1.002740    11664000.00    935676.49    12599676.49
    2030-01-02    1.000000    12599676.49    1007974.12    13607650.61
    2031-01-02    1.000000    13607650.61    1088612.05    14696262.66
    face on 2026-01-02    10000000.00
    face on 2026-07-01    10000000.00
    face on 2027-01-02    10800000.00
    face on 2029-06-30    12599676.49
    face on 2031-01-02    14696262.66
    factor at maturity    1.4696262662
    (1 + r)^5             1.4693280768
    1 + 5r                1.4000000000
    redemption per 100    146.962627
    value off the curve   13749365.95
    dirty per 100 at 10%  115.789316
    refused 2 of 2 non-positive rates

    The face is flat between capitalisations and grows at each one. The 2028 period runs 366 days, so the factor at maturity is a little above (1+r)5(1 + r)^5, and well above simple interest. The bond has one cash flow, the accreted face at maturity, so its value and its price at a yield are that flow discounted.

    How it's priced

    Each capitalisation TiT_i multiplies the face by one plus the rate times that period's year fraction. Per 100 of par face the only flow is the redemption RR at maturity TnT_n:

    A(T)=∏i : Ti≤T(1+r τi),R=100 A(Tn),V=N100 R DF(Tn),P=R (1+y)−τ.A(T) = \prod_{i\,:\,T_i \le T} \bigl(1 + r\,\tau_i\bigr), \qquad R = 100\,A(T_n), \qquad V = \frac{N}{100}\,R\,DF(T_n), \qquad P = R\,(1 + y)^{-\tau}.

    Who issues PIK, and why the two quote bases differ, is covered in how bonds work.

    API

    Argument Type Default Meaning
    currency str required the bond's currency
    issue, maturity date required the accrual start and the redemption date
    rate float required the PIK rate; it must be positive
    frequency dates.Frequency or str required how often interest capitalises
    day_count dates.DayCountFraction or str required how each period's year fraction is counted
    face float required the original principal

    products.payment_in_kind_bond(currency, issue, maturity, *, rate, frequency, day_count, face) builds it as an Instrument; in Rust, PaymentInKindBond::new(ccy, issue, maturity, rate, frequency, day_count, face)?.

    Symbol Meaning Rust Python
    TiT_i, τi\tau_i each capitalisation and its year fraction pik.capitalisations() bonds.capitalisations(pik), columns date, year_fraction, face_before, amount, face_after
    N A(T)N\,A(T) the accreted face on a date pik.current_face(T) bonds.accreted_face(pik, T)
    RR redemption per 100 of par face pik.redemption_per_100() bonds.accreted_redemption(pik)
    VV value against a market MarketView::npv on AnyInstrument::PaymentInKindBond Instrument.npv
    PP, yy price per 100 par at a yield, and back pik.dirty_price_on_par_face, pik.yield_from_par_face the closed forms above, from bonds.accreted_redemption

    Defaults and limits

    • A rate that isn't positive is refused: ValueError in Python, PikError::NonPositiveRate in Rust. A zero rate capitalises nothing and a negative one would shrink the face.
    • Market inputs: the discount curve of the bond's currency. The accretion itself needs no market data.
    • Quote basis: prices are per 100 of par face. Divide by the accretion factor at settlement to restate one per 100 of accreted face.
    • Python yields: the price-to-yield calls are Rust only; in Python use the closed forms, since a PIK has one flow.

    See also

    • Source: crates/helixrs/src/instruments/bonds.rs
    • Related pages:
      • How bonds work, for why PIK exists and its two quote bases.
      • Fixed-rate bond, for a bond that pays its coupons in cash.
      • Bond conventions, for the day counts a period can accrue on.

    From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.