QuantLib cpibond bond example in Python
Question: I am trying to get the official C++ cpibond example working in Python. The
original example is here:
<https://github.com/lballabio/quantlib/blob/master/QuantLib/test-
suite/inflationcpibond.cpp> and for scala here:
<https://github.com/lballabio/quantlib/blob/master/QuantLib-
SWIG/Scala/examples/CPIBond.scala>

When I run what I have attempted I get this error:

> RuntimeError: 1st iteration: failed at 1st alive instrument, maturity
> September 1st, 2010, reference date September 1st, 2009: 2nd leg: Missing UK
> RPI fixing for September 1st, 2009

Here is my attempt:

    
    
    import QuantLib as ql
    calendar = ql.UnitedKingdom()
    dayCounter = ql.ActualActual();
    convention = ql.ModifiedFollowing
    
    today = ql.Date(20, 11, 2009)
    evaluationDate = calendar.adjust(today)
    ql.Settings.instance().setEvaluationDate(evaluationDate)        
    yTS = ql.YieldTermStructureHandle(ql.FlatForward(evaluationDate, 0.05, dayCounter))
    
    from_date = ql.Date(20, ql.July, 2007);
    to_date   = ql.Date(20, ql.November, 2009);
    tenor = ql.Period(1, ql.Months)
    rpiSchedule = ql.Schedule(from_date, to_date, tenor, calendar, 
                                   convention, convention,
                                   ql.DateGeneration.Backward, False)
    cpiTS = ql.RelinkableZeroInflationTermStructureHandle()
    inflationIndex = ql.UKRPI(False, cpiTS)
    fixData = [206.1, 207.3, 208.0, 208.9, 209.7, 210.9,
                        209.8, 211.4, 212.1, 214.0, 215.1, 216.8,
                        216.5, 217.2, 218.4, 217.7, 216,
                        212.9, 210.1, 211.4, 211.3, 211.5,
                        212.8, 213.4, 213.4, 213.4, 214.4,213.4, 214.4]
    dte_fixings=[dtes for dtes in rpiSchedule]
    print len(dte_fixings)
    print len(fixData)
    #must be the same length
    inflationIndex.addFixings(dte_fixings, fixData) 
    observationLag = ql.Period(2, ql.Months)
    zciisData =[( ql.Date(25, ql.November, 2010), 3.0495 ),
                  ( ql.Date(25, ql.November, 2011), 2.93 ),
                  ( ql.Date(26, ql.November, 2012), 2.9795 ),
                  ( ql.Date(25, ql.November, 2013), 3.029 ),
                  ( ql.Date(25, ql.November, 2014), 3.1425 ),
                  ( ql.Date(25, ql.November, 2015), 3.211 ),
                  ( ql.Date(25, ql.November, 2016), 3.2675 ),
                  ( ql.Date(25, ql.November, 2017), 3.3625 ),
                  ( ql.Date(25, ql.November, 2018), 3.405 ),
                  ( ql.Date(25, ql.November, 2019), 3.48 ),
                  ( ql.Date(25, ql.November, 2021), 3.576 ),
                  ( ql.Date(25, ql.November, 2024), 3.649 ),
                  ( ql.Date(26, ql.November, 2029), 3.751 ),
                  ( ql.Date(27, ql.November, 2034), 3.77225),
                  ( ql.Date(25, ql.November, 2039), 3.77 ),
                  ( ql.Date(25, ql.November, 2049), 3.734 ),
                  ( ql.Date(25, ql.November, 2059), 3.714 )]
    
    lRates=[rtes/100.0 for rtes in zip(*zciisData)[1]]
    baseZeroRate = lRates[0]
    
    zeroSwapHelpers = [ql.ZeroCouponInflationSwapHelper(a[1]/100,observationLag,
        a[0], calendar, convention, dayCounter, inflationIndex) for a in zciisData]
    
    
    cpiTS.linkTo(ql.PiecewiseZeroInflation(          
      evaluationDate, calendar, dayCounter, observationLag, 
      inflationIndex.frequency(), inflationIndex.interpolated(), 
      baseZeroRate,
      yTS, zeroSwapHelpers, 1.0e-12, ql.Linear()))
    
    
    notional = 1000000
    
    
    fixedRates = [0.1]    
    
    fixedDayCounter = ql.Actual365Fixed()
    fixedPaymentConvention = ql.ModifiedFollowing
    fixedPaymentCalendar = ql.UnitedKingdom()
    contractObservationLag = ql.Period(3, ql.Months)
    observationInterpolation = ql.CPI.Flat
    settlementDays = 3
    growthOnly = True
    
    baseCPI = 206.1
    startDate = ql.Date(2, 10, 2007)
    endDate = ql.Date(2, 10, 2052)
    
    fixedSchedule = ql.Schedule(startDate, endDate, 
                      ql.Period(6, ql.Months), fixedPaymentCalendar,
                      ql.Unadjusted,
                      ql.Unadjusted,
                      ql.DateGeneration.Backward, False)
    
    bond = ql.CPIBond(settlementDays, notional, growthOnly,
                           baseCPI, contractObservationLag,
                           inflationIndex, observationInterpolation,
                           fixedSchedule, fixedRates, fixedDayCounter, 
                           fixedPaymentConvention)
    
    bondEngine=ql.DiscountingBondEngine(yTS)
    bond.setPricingEngine(bondEngine)
    print bond.NPV() 
    print bond.cleanPrice()
    

Most of my problem is that I am finding it difficult to get to grips with how
the objects fit together.


Answer: got the above example working:

    
    
    import QuantLib as ql
    import datetime as dt
    calendar = ql.UnitedKingdom()
    dayCounter = ql.ActualActual();
    convention = ql.ModifiedFollowing
    lag = 3
    
    today = ql.Date(5,3,2008)
    evaluationDate = calendar.adjust(today)
    issue_date = calendar.advance(evaluationDate,-1, ql.Years)
    maturity_date = ql.Date(2,9,2052)
    fixing_date = calendar.advance(evaluationDate,-lag, ql.Months)
    
    ql.Settings.instance().setEvaluationDate(evaluationDate)        
    yTS = ql.YieldTermStructureHandle(ql.FlatForward(evaluationDate, 0.05, dayCounter))
    
    tenor = ql.Period(1, ql.Months)
    
    from_date = ql.Date(20, ql.July, 2007);
    to_date   = ql.Date(20, ql.November, 2009);
    rpiSchedule = ql.Schedule(from_date, to_date, tenor, calendar, 
                                   convention, convention,
                                   ql.DateGeneration.Backward, False)
    
    # this is the going to be holder the inflation curve.
    cpiTS = ql.RelinkableZeroInflationTermStructureHandle()
    inflationIndex = ql.UKRPI(False, cpiTS)
    fixData = [206.1, 207.3, 208.0, 208.9, 209.7, 210.9,
                209.8, 211.4, 212.1, 214.0, 215.1, 216.8,
                216.5, 217.2, 218.4, 217.7, 216,
                212.9, 210.1, 211.4, 211.3, 211.5,
                212.8, 213.4, 213.4, 213.4, 214.4]
    dte_fixings=[dtes for dtes in rpiSchedule]
    print len(fixData)
    print len(dte_fixings[:len(fixData)])
    #must be the same length
    #inflationIndex.addFixings(dte_fixings[:len(fixData)], fixData) 
    #Current CPI level
    #last observed rate
    fixing_rate = 214.4
    
    
    inflationIndex.addFixing(fixing_date, fixing_rate)
    
    observationLag = ql.Period(lag, ql.Months)
    zciisData =[( ql.Date(25, ql.November, 2010), 3.0495 ),
                  ( ql.Date(25, ql.November, 2011), 2.93 ),
                  ( ql.Date(26, ql.November, 2012), 2.9795 ),
                  ( ql.Date(25, ql.November, 2013), 3.029 ),
                  ( ql.Date(25, ql.November, 2014), 3.1425 ),
                  ( ql.Date(25, ql.November, 2015), 3.211 ),
                  ( ql.Date(25, ql.November, 2016), 3.2675 ),
                  ( ql.Date(25, ql.November, 2017), 3.3625 ),
                  ( ql.Date(25, ql.November, 2018), 3.405 ),
                  ( ql.Date(25, ql.November, 2019), 3.48 ),
                  ( ql.Date(25, ql.November, 2021), 3.576 ),
                  ( ql.Date(25, ql.November, 2024), 3.649 ),
                  ( ql.Date(26, ql.November, 2029), 3.751 ),
                  ( ql.Date(27, ql.November, 2034), 3.77225),
                  ( ql.Date(25, ql.November, 2039), 3.77 ),
                  ( ql.Date(25, ql.November, 2049), 3.734 ),
                  ( ql.Date(25, ql.November, 2059), 3.714 )]
    
    #lRates=[rtes/100.0 for rtes in zip(*zciisData)[1]]
    #baseZeroRate = lRates[0]
    
    zeroSwapHelpers = [ql.ZeroCouponInflationSwapHelper(rate/100,observationLag,
        date, calendar, convention, dayCounter, inflationIndex) for date,rate in zciisData]
    
    
    # the derived inflation curve
    jj=ql.PiecewiseZeroInflation(          
                                  evaluationDate, calendar, dayCounter, observationLag, 
                                  inflationIndex.frequency(), inflationIndex.interpolated(), 
                                  zciisData[0][1],#baseZeroRate,
                                  yTS, zeroSwapHelpers, 1.0e-12, ql.Linear())
    
    cpiTS.linkTo(jj)
    
    notional = 1000000
    
    fixedRates = [0.1]    
    
    fixedDayCounter = ql.Actual365Fixed()
    fixedPaymentConvention = ql.ModifiedFollowing
    fixedPaymentCalendar = ql.UnitedKingdom()
    contractObservationLag = ql.Period(3, ql.Months)
    observationInterpolation = ql.CPI.Flat
    settlementDays = 3
    growthOnly = False
    
    baseCPI = 206.1
    
    
    fixedSchedule = ql.Schedule(issue_date,
                      maturity_date, 
                      ql.Period(ql.Semiannual),
                      fixedPaymentCalendar,
                      ql.Unadjusted,
                      ql.Unadjusted,
                      ql.DateGeneration.Backward,
                      False)
    
    bond = ql.CPIBond(settlementDays,
                        notional,
                        growthOnly,
                        baseCPI,
                        contractObservationLag,
                        inflationIndex,
                        observationInterpolation,
                        fixedSchedule,
                        fixedRates,
                        fixedDayCounter, 
                        fixedPaymentConvention)
    
    #bond2= ql.QuantLib.C
    
    bondEngine=ql.DiscountingBondEngine(yTS)
    bond.setPricingEngine(bondEngine)
    print bond.NPV() 
    print bond.cleanPrice()
    compounding = ql.Compounded
    yield_rate = bond.bondYield(fixedDayCounter,compounding,ql.Semiannual)
    y_curve = ql.InterestRate(yield_rate,fixedDayCounter,compounding,ql.Semiannual)
    ##Collate results
    print "Clean Price:", bond.cleanPrice()
    print "Dirty Price:", bond.dirtyPrice()
    print "Notional:", bond.notional()
    print "Yield:", yield_rate
    print "Accrued Amount:", bond.accruedAmount()
    print "Settlement Value:", bond.settlementValue()
    
    #suspect there's more to this for TIPS
    print "Duration:", ql.BondFunctions.duration(bond,y_curve)
    print "Convexity:", ql.BondFunctions.convexity(bond,y_curve)
    print "Bps:", ql.BondFunctions.bps(bond,y_curve)
    print "Basis Point Value:", ql.BondFunctions.basisPointValue(bond,y_curve)
    print "Yield Value Basis Point:", ql.BondFunctions.yieldValueBasisPoint(bond,y_curve)
    
    print "NPV:", bond.NPV()
    
    # get the cash flows:
    #cf_list=[(cf.amount(),cf.date()) for cf in bond.cashflows()]
    
    def to_datetime(d):
        return dt.datetime(d.year(),d.month(), d.dayOfMonth())
    
    for cf in bond.cashflows():
        try:
            amt=cf.amount()
            rte=jj.zeroRate(cf.date())
            zc=yTS.zeroRate(cf.date(),fixedDayCounter,compounding,ql.Semiannual).rate()
        except:
            amt=0
            rte=0
            zc=0
        print to_datetime(cf.date()),amt,rte,zc
    

The issue it seems was that the inflationIndex object needed one date instead
of multiple index points. My assumption was it would pull out the latest valid
point.

The way the pricer works is that the real coupons are increased by the
inflation rate curve term structure, zciisData. The result therefore becomes a
nominal future cash flow. To price then the bondpricer simply discounts these
by the nominal term structure. I added some additional code to print the
determined cash flows and the "growth factor" and then the discount rate.

