0
$\begingroup$

I am trying to reperform a vanilla swap. The problem is that the vanilla swap object does not seem to be using the exact fixings of the underlying index.

import pandas as pd
import QuantLib as ql

calendar = ql.SouthAfrica()

notional = 481000000
fixed_rate = 0.0565
day_count = ql.Actual365Fixed()
settlementDays = 0

# %%
curve=[
('2022/03/04',0.038662937),
('2022/03/07',0.038662937),
('2022/04/04',0.041096174000000006),
('2022/05/04',0.041746752000000005),
('2022/06/06',0.042385545000000004),
('2022/07/04',0.044799741),
('2022/08/04',0.044673466),
('2022/09/06',0.046401243),
('2022/10/04',0.047449248),
('2022/11/04',0.048448323999999994),
('2022/12/05',0.049288543000000004),
('2023/01/04',0.050770971),
('2023/02/06',0.051373198999999994),
('2023/03/06',0.052326463),
('2023/04/04',0.053330200999999994),
('2023/05/08',0.053975877),
('2023/06/06',0.054701456999999995),
('2023/07/04',0.055552298),
('2023/08/04',0.055982044),
('2023/09/05',0.056613829000000004),
('2023/10/04',0.05728623),
('2023/11/06',0.057683783),
('2023/12/04',0.058114147000000005),
('2024/01/04',0.058739921),
('2024/02/06',0.059047349),
('2024/03/04',0.059449816),
('2025/03/04',0.063389835),
('2026/03/04',0.066562792),
('2027/03/04',0.069682955),
('2028/03/06',0.072745578),
('2029/03/05',0.075888138),
('2030/03/04',0.078560623),
('2031/03/04',0.080713083),
('2032/03/04',0.082602893),
('2034/03/06',0.085132561),
('2037/03/04',0.08649955699999999),
('2042/03/04',0.08760520899999999),
('2047/03/04',0.087014442),
('2052/03/04',0.08559789699999999),
('2057/03/04',0.08559789699999999),
('2062/03/04',0.08559789699999999),
('2067/03/04',0.08559789699999999)]

curve_df = pd.DataFrame(curve, columns=['StartDate', 'BidRate'])
curve_df['qlvalDate'] = curve_df.apply(
    lambda row: ql.Date(row['StartDate'], '%Y-%m-%d'), axis=1)

dates = curve_df.qlvalDate.values

rates = curve_df.BidRate.values

# %%
zc = ql.ZeroCurve(
        dates,
        rates,
        ql.Actual365Fixed(),
        calendar,
        ql.Linear(),
        ql.Compounded,
        ql.Semiannual
    )

yts = ql.YieldTermStructureHandle(
    zc
)

discounts = [yts.discount(d) for d in dates]
# %%
inception_date = ql.Date(4, 3, 2022)
valuation_date = ql.Date(4, 3, 2022)
ql.Settings.instance().evaluationDate = valuation_date
maturity_date = ql.Date('2023-12-04', "%Y-%m-%d")
fixed_leg_tenor = ql.Period(3, ql.Months)

# %%

fixed_schedule = ql.Schedule(inception_date, maturity_date,
                             fixed_leg_tenor, calendar,
                             ql.ModifiedFollowing, ql.ModifiedFollowing,
                             ql.DateGeneration.Forward, False)

day_count = ql.Actual365Fixed()
float_spread = 0
jibar3M_index = ql.Jibar(ql.Period(3, ql.Months), yts)
jibar3M_index.addFixing(ql.Date(4, 3, 2022), 0.04217)

fixings = [jibar3M_index.fixing(d) for d in fixed_schedule.dates()]

swap = ql.VanillaSwap(ql.VanillaSwap.Receiver, notional,fixed_schedule,fixed_rate,day_count, fixed_schedule, jibar3M_index, 0, day_count)

cashflows = pd.DataFrame({
    'nominal': cf.nominal(),
    'accrualStartDate': cf.accrualStartDate().ISO(),
    'accrualEndDate': cf.accrualEndDate().ISO(),
    'accrualPeriod': cf.accrualPeriod(),
    'rate': cf.rate(),
    'amount': cf.amount(),
    'forward': cf.indexFixing()
} for cf in map(ql.as_floating_rate_coupon, swap.leg(1)))

cashflows['expectedFixing'] = fixings[:-1]
cashflows['rateDiff*1000'] = (cashflows['expectedFixing']-cashflows['forward'])*1000

cashflows[['accrualStartDate', 'amount', 'indexFixing', 'expectedFixing','rateDiff*1000']]

this gives me the below table

index accrualStartDate amount indexFixing expectedFixing rateDiff*1000
0 2022-03-04 5223765.424657535 0.04217 0.04217 0.0
1 2022-06-06 6012318.654694998 0.05013585042525129 0.05019999932091297 0.06414889566168369
2 2022-09-05 6583643.403629088 0.05490004437469139 0.05490004437469139 0.0
3 2022-12-05 7327145.736813547 0.061100002146100035 0.061100002146100035 0.0
4 2023-03-06 7635693.818594573 0.06367293970407391 0.06370000036701452 0.02706066294061449
5 2023-06-05 7882520.176965427 0.06573118879149165 0.06575001767263097 0.01882888113932668
6 2023-09-04 8004699.021793886 0.06675002040060242 0.06675002040060242 0.0

Where is the difference between my expected fixing, and my cf.indexFixing() coming from?

$\endgroup$

1 Answer 1

4
$\begingroup$

That's because the default of the library is to estimate floating-rate coupons at par on the curve. This can cause the end dates of the underlying forward rates to differ from the ones implied by the index.

It's possible to change the default behavior by executing the line:

ql.IborCoupon.createIndexedCoupons()

before creating the swap. This will cause the coupons to use the fixing from the index instance.

A more detailed explanation is in this video. More on the underlying C++ implementation is in this post in particular, but also in the ones that precede it in the blog.

One last note: the line

fixings = [jibar3M_index.fixing(d) for d in fixed_schedule.dates()]

works because the JIBAR happens to have 0 fixing days. In general, the fixings should be extracted with

fixings = [jibar3M_index.fixing(jibar3M_index.fixingDate(d))
           for d in fixed_schedule.dates()]

which takes care of the fixing days, if any.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.