0
$\begingroup$

I would like to value an IRS. My first problem is with "RuntimeError: more than one instrument with pillar". Theres is clearly no overlapping instruments. I can't get the valuation of the floating leg. Also, is there a way to value a swap at e.g. june 2023, when it started in may 2022, if I gave past Sofr ON values and spot rates from june 2023. Normally, there shouldn't be the problem, but I am not sure how QuantLib works. THanks!

Here's the code:

import pandas as pd
import datetime as dt
import QuantLib as ql

settlement_days = 2

calendar = ql.UnitedStates(ql.UnitedStates.FederalReserve)

day_count = ql.Actual360()

sofr = ql.Sofr()

curve_dates = ["30.06.2023", "03.07.2023", "05.07.2023", "12.07.2023", "19.07.2023", "07.08.2023", "05.09.2023", "05.10.2023", "06.11.2023",
                "05.12.2023", "05.01.2024", "05.02.2024", "05.03.2024", "05.04.2024", "06.05.2024", "05.06.2024", "05.07.2024",
                "07.07.2025", "06.07.2026", "05.07.2028", "05.07.2029", "05.07.2030", "07.07.2031",
                "05.07.2033", "05.07.2034", "05.07.2035", "07.07.2036", "06.07.2037", "05.07.2039", "05.07.2040",
                "05.07.2041", "07.07.2042", "06.07.2043"]

row1 = [0.999576013, 0.999293276, 0.998284236, 0.997276216, 0.994545288, 0.990391441, 0.986043654, 0.981353845]
row2 = [0.977122975, 0.972637548, 0.968188632, 0.964045167, 0.959738687, 0.95554771, 0.951509352, 0.947635722]
row3 = [0.908583534, 0.877579008, 0.823760707, 0.798388971, 0.773888557, 0.749927777]
row4 = [0.703540185, 0.680868756, 0.658502936, 0.636587109, 0.615672676, 0.57654805, 0.558385784]
row5 = [0.541184978, 0.524737797, 0.509183223]
discount_factors = row1 + row2 + row3 + row4 + row5

qlDates = [ql.Date(int(day), int(month), int(year)) for date in curve_dates for day, month, year in [date.split('.')]]

valuationDate = ql.Date(30,6,2023)
ql.Settings.instance().evaluationdate = valuationDate

sofr = ql.Sofr()

SofrCurve = ql.DiscountCurve(qlDates, [1] + discount_factors, day_count, calendar)
SofrCurve.enableExtrapolation()

ValuationCurve = ql.RelinkableYieldTermStructureHandle(SofrCurve)
#ValuationCurve.linkTo(SofrCurve)

sofrIndex = ql.Sofr(ValuationCurve)
swapEngine = ql.DiscountingSwapEngine(ValuationCurve)

fixedRate = 0.0443
Nominal = 3_000_000

forwardStart = ql.Period("2D")

start_date = ql.Date(30,6,2023)
maturity_date = calendar.advance(start_date, 4, ql.Years)

fixed_leg_daycount = ql.Actual360()
fixed_leg_tenor = ql.Period(12, ql.Months)

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

fixed_leg_daycount = ql.Actual360()

fixed_leg_tenor = ql.Period(12, ql.Months)

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

float_spread = 0.0

ir_swap = ql.VanillaSwap(ql.VanillaSwap.Payer, Nominal,
fixed_schedule, fixedRate, fixed_leg_daycount, float_schedule, sofrIndex, float_spread, float_leg_daycount)

ir_swap.setPricingEngine(swapEngine)

#Floating Leg
pd.DataFrame(
    [(cf.date(), cf.amount()) for cf in ir_swap.leg(1)],
    columns=["Date", "Amount"],
    index=range(1, len(ir_swap.leg(1))+1)
    )

OIS swap

sofr_curve_log_cubic.nodes()[:10]
((Date(30,6,2023), 1.0),
 (Date(4,7,2023), 0.9994349085205639),
 (Date(20,9,2023), 0.9882497109370333),
 (Date(20,12,2023), 0.9749417559680691),
 (Date(20,3,2024), 0.9619089583699006),
 (Date(20,6,2024), 0.9494974442774458),
 (Date(18,9,2024), 0.9383313017859709),
 (Date(18,12,2024), 0.9280793760780555),
 (Date(19,3,2025), 0.9187319168325597),
 (Date(7,7,2025), 0.9085558942301587))

today = ql.Date(30, ql.June, 2023)
ql.Settings.instance().evaluationDate = today

sofr_curve_log_cubic = ql.PiecewiseLogCubicDiscount(0, ql.TARGET(),
                                             helpers, ql.Actual360())
sofr_curve_log_cubic.enableExtrapolation()

swapType = ql.OvernightIndexedSwap.Receiver
nominal = 500_000
schedule = ql.MakeSchedule(ql.Date(13,5,2022), 
                           ql.Date(13, 5,2027), 
                           ql.Period('1Y'), 
                           calendar=ql.TARGET())
fixedRate = 0.03
fixedDC = ql.Actual360()
ValuationCurve = ql.RelinkableYieldTermStructureHandle()
ValuationCurve.linkTo(sofr_curve_log_cubic)
overnightIndex = ql.Sofr(ValuationCurve)

# Here are dates and fixings from the start of the swap up until valuation date
for date, fixing in zip(SofrON_qlDates, SofrON_rates):
    overnightIndex.addFixing(date, fixing)

ois_swap = ql.OvernightIndexedSwap(swapType, 
                                   nominal, 
                                   schedule, 
                                   fixedRate, 
                                   fixedDC, 
                                   overnightIndex)



swapEngine = ql.DiscountingSwapEngine(ValuationCurve)
ois_swap.setPricingEngine(swapEngine)

# Unfortunately, the swap is not pricing with the schedule on the valuation date


```
$\endgroup$
4
  • 1
    $\begingroup$ As it is, the code is giving different errors than the one you report. First, you're setting the valuation date to August 30th 2023, which causes an error because you get a negative length for the first few dates. I guess you meant June 30th 2023 instead? If I use that valuation date, Python then complains that float_schedule is not defined. If I also fix that, I don't have a pillar error, but rather a convergence error. May you edit your code so that it reproduces your problem? Thanks! $\endgroup$ Commented Mar 19 at 12:23
  • 2
    $\begingroup$ I'm guessing you also need import QuantLib as ql, import pandas as pd and import datetime as dt—is that correct? $\endgroup$ Commented Mar 19 at 12:28
  • 1
    $\begingroup$ This said, if you already have calculated discount factors at a set of dates, you don't need to convert them into rates and go through bootstrapping; you can use DiscountCurve and interpolate them directly. $\endgroup$ Commented Mar 19 at 12:30
  • $\begingroup$ Thanks Luigi for your response! I corrected the code above. Still there are errors and have no idea how to resolve them. Also could you answer me how ql.sofr() is calculated and whether there is a way to calculate the swap value during its life with past historical rates up until the valuation day? Thank you! $\endgroup$
    – Kid000
    Commented Mar 19 at 14:00

1 Answer 1

1
$\begingroup$

After you edits, and after initializing float_schedule and float_leg_daycount (right now you're initializing fixed_schedule twice instead) the errors you're getting say that you need past historical fixings. You can store them with

sofrIndex.addFixing(Date(30,6,2023), 0.0509)
sofrIndex.addFixing(Date(29,6,2023), 0.0506)
sofrIndex.addFixing(Date(28,6,2023), 0.0506)

and so on.

However: if you're using SOFR, you probably don't really want to use VanillaSwap, because it creates floating-rate coupons that fix at the beginning of the coupon, and not coupons that average overnight SOFR over the life of the coupon. If that's what you want (as I suspect) use OvernightIndexedSwap instead. It takes similar parameters.

$\endgroup$
5
  • $\begingroup$ Luigi, Thank you for your immense help. Cold you guide me in one thing? I built a SOFR curve. I also built a OvernightIndexedSwap. I want to price this swap during its life, thus I have also past ON Sofr fixings. How to price this swap? I can't help myself with errors. I added the code in the question in second part, because the comments are too short. Thank you again $\endgroup$
    – Kid000
    Commented Mar 20 at 12:40
  • $\begingroup$ Unfortunately, the swap is not pricing with the schedule on the valuation date. It is showing in the schedule the payment in may 2023, even though I am trying to price it in June. And I am trying to price this swap with past ON rates up until valuation date and projection curve from valuation date. $\endgroup$
    – Kid000
    Commented Mar 20 at 14:59
  • $\begingroup$ I'm not sure I understand what you mean. You created a swap with a schedule that pays in May. When you set the evaluation date to June, those payments will still be in May of course but will be discounted to your evaluation date. What is it that you're trying to do instead? $\endgroup$ Commented Mar 20 at 16:08
  • $\begingroup$ I want to price the swap in June 2023, with annual schedule starting may 2022, so with one payment made. The swap is floating SOFR (paid) and fixed (received). I am trying to do this with fixed ON dates up until valuation (June 2023) and a projection curve from now on. Did I do this correctly? $\endgroup$
    – Kid000
    Commented Mar 21 at 9:05
  • $\begingroup$ It looks correct. Is there any chance you can include the complete code you're using so we can run it? $\endgroup$ Commented Mar 21 at 9:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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