0
$\begingroup$

I am making an OIS curve and a SOFR curve with bloomberg quotes

When I valuate a SOFR swap in the same tenors as the SOFR inputs, the FairRates are not the same as the quotes I used to create the curve.
Can somenone please help me?

# -*- coding: utf-8 -*
import QuantLib as ql
import numpy as np
from datetime import datetime

s = datetime.now()
# EvaluationDate
ql.Settings.instance().evaluationDate = ql.Date(29,4,2024)


#%% USD OIS Construction

tenor2ql = {'B': ql.Days, 'D': ql.Days, 'M': ql.Months, 'W': ql.Weeks,
            'Y': ql.Years, 'L': ql.Weeks}

# market calendar
calendar = ql.UnitedStates(6)

# input data
str_tenors = ['1B', '1W', '2W', '3W', '1M', '2M', '3M', '4M', '5M', '6M', '9M',
              '12M', '18M', '2Y', '3Y', '4Y', '5Y', '7Y', '10Y', '12Y', '15Y',
              '20Y', '25Y', '30Y', '40Y']

tenor = [tenor2ql[ten[-1]] for ten in str_tenors]
period = [1, 1, 2, 3, 1, 2, 3, 4,
          5, 6, 9, 12, 18, 2, 3, 4,
          5, 7, 10, 12, 15, 20, 25, 30,
          40]
data = [0.0533, 0.053302, 0.053308, 0.05332, 0.053362, 0.053377,
        0.053385, 0.053279, 0.053173, 0.052985, 0.05246, 0.051868,
        0.04978 , 0.048395, 0.046192, 0.04474 , 0.043821, 0.04291,
        0.042347, 0.042266, 0.042207, 0.041692, 0.040661, 0.039605,
        0.037396]

# Deposit rates
deposits = {(period[0], tenor[0]): data[0]}

# Swap rates
n = len(period)
swaps = {}
for i in range(1,n):
    swaps[(period[i], tenor[i])] = data[i]

# Rate Quantlib.Quote objects
## desposits
for n, unit in deposits.keys():
    deposits[(n, unit)] = ql.SimpleQuote(deposits[(n, unit)])
    #Tasa cero para el primer tenor
## swap rates
for n, unit in swaps.keys():
    swaps[(n, unit)] = ql.SimpleQuote(swaps[(n, unit)])
    # SimpleQuote es el formato de rates en quantlib

# Rate helpers deposits
dayCounter = ql.Actual360()
settlementDays = 2
## deposits
depositHelpers = [ql.DepositRateHelper(ql.QuoteHandle(deposits[(n, unit)]),
                                       ql.Period(int(n), unit),
                                       settlementDays,
                                       calendar,
                                       ql.ModifiedFollowing,
                                       False,
                                       dayCounter)
                  for (n, unit) in deposits.keys()]
## swap rates
OIS_Index = ql.FedFunds()
OISHelpers = [ql.OISRateHelper(settlementDays, ql.Period(int(n), unit),
                               ql.QuoteHandle(swaps[(n,unit)]),
                               OIS_Index,
                                ql.YieldTermStructureHandle(),
                                False, 2, ql.ModifiedFollowing,
                                ql.Annual, calendar)
              for n, unit in swaps.keys()]
## helpers merge
hlprUSDOIS = depositHelpers + OISHelpers

crvUSDOIS = ql.PiecewiseLogLinearDiscount(0, calendar,
                                          hlprUSDOIS, ql.Actual360())

crvUSDOIS.enableExtrapolation()
discount_curve = ql.RelinkableYieldTermStructureHandle()
discount_curve.linkTo(crvUSDOIS)

#%% SOFR
######################## SOFR Construction ######################

calendar = ql.UnitedStates(6)
# settlement date
dt_settlement = calendar.advance(
        ql.Settings.instance().evaluationDate, ql.Period('2D'))

# input data
str_tenor = ['1B', '%2Y', '%3Y', '%4Y', '%5Y', '%6Y', '%7Y', '%8Y', '%9Y',
             '%10Y', '%12Y', '%15Y', '%20Y', '%25Y', '%30Y']
tenor_sofr = [tenor2ql[ten[-1]] for ten in str_tenor]
period_sofr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15, 20, 25, 30]
data_nonfut = [0.0531, 0.048523, 0.046355, 0.04497 , 0.044095, 0.04357,
               0.043216, 0.042959, 0.042792, 0.042682, 0.042614, 0.042585,
               0.042118, 0.041112, 0.04007]
data_fut = [0.05345, 0.05265, 0.05095, 0.04915, 0.0473]

# IborIndex
swapIndex = ql.Sofr()

# Deposit rates
deposits = {(period_sofr[0], tenor_sofr[0]): data_nonfut[0]}

# Futures rates
n_fut = len(data_fut)
imm = ql.IMM.nextDate(dt_settlement)
imm = dt_settlement
futures = {}
for i in range(n_fut):
    imm = ql.IMM.nextDate(imm)
    futures[imm] = 100-data_fut[i]*100

# Swap rates
n = len(period_sofr)
swaps = {}
for i in range(1, n):
    swaps[(period_sofr[i], tenor_sofr[i])] = data_nonfut[i]

# Rate Qauntlib.Quote objects
## desposits

for n, unit in deposits.keys():
    deposits[(n, unit)] = ql.SimpleQuote(deposits[(n, unit)])
## futures
for d in futures.keys():
    futures[d] = futures[d]
## swap rates
for n, unit in swaps.keys():
    swaps[(n, unit)] = ql.SimpleQuote(swaps[(n, unit)])

# Rate helpers deposits
dayCounter = ql.Actual360()
settlementDays = 2
## deposits
depositHelpers = [ql.DepositRateHelper(ql.QuoteHandle(deposits[(n, unit)]),
                                       ql.Period(int(n), unit),
                                       settlementDays,
                                       calendar,
                                       ql.ModifiedFollowing,
                                       False,
                                       dayCounter)
                  for n, unit in deposits.keys()]
## futures
months = 3
futuresHelpers = [ql.FuturesRateHelper(
    ql.QuoteHandle(ql.SimpleQuote(futures[d])),
    d, months, calendar,
    ql.ModifiedFollowing, True, dayCounter,
    ql.QuoteHandle(ql.SimpleQuote(0.0)))
    for d in futures.keys()
]

## swap rates
fixedLegFrequency = ql.Annual
fixedLegAdjustment = ql.ModifiedFollowing
fixedLegDayCounter = ql.Actual360()
## swaphelper
swapHelpers = [ql.SwapRateHelper(
    ql.QuoteHandle(swaps[(n,unit)]),
    ql.Period(int(n), unit),
    calendar,
    fixedLegFrequency,
    fixedLegAdjustment,
    fixedLegDayCounter,
    # swapIndex)
    swapIndex,
    ql.QuoteHandle(),
    ql.Period(2, ql.Days),
    discount_curve
    )
    for n, unit in swaps.keys()
]

## helpers merge
hlprSOFR = depositHelpers + futuresHelpers + swapHelpers

# Bootstrap
# crvSOFR = ql.PiecewiseNaturalLogCubicDiscount(0, calendar, hlprSOFR,
#                                               ql.Actual360())
crvSOFR = ql.PiecewiseCubicZero(0, calendar, hlprSOFR, ql.Actual360())

crvSOFR.enableExtrapolation()

crv_usdswp = ql.RelinkableYieldTermStructureHandle()
crv_usdswp.linkTo(crvSOFR)


###### SWAP VALUATION #######

for i in range(len(tenor_sofr)):

    legDC = ql.Actual360()
    start =  ql.Date(1,5,2024)
    maturity = ql.UnitedStates(6).advance(start, ql.Period(period_sofr[i], tenor_sofr[i]))
    cal = ql.UnitedStates(6)
    convention = ql.ModifiedFollowing
    termDateConvention = ql.ModifiedFollowing
    rule = ql.DateGeneration.Backward
    isEndOfMonth = False
    cpn_tenor = ql.Period('1Y')
    ibor = ql.Sofr(crv_usdswp)
    
    fixfltSchdl = ql.Schedule(start, maturity, cpn_tenor, cal, convention,
                            termDateConvention, rule, isEndOfMonth)
    
    swap = ql.VanillaSwap(typ, notional, fixfltSchdl, rate, legDC, fixfltSchdl,
                          ibor, 0, legDC)
    
    
    discount_engine = ql.DiscountingSwapEngine(discount_curve)
    swap.setPricingEngine(discount_engine)
    print((np.round(swap.fairRate(),6)-data_nonfut[i])*10000)

The Output is the difference in basis points between the SOFR fair rates and the inputs:

0.0
-0.059999999999990616
-0.07000000000000062
-0.010000000000010001
0.010000000000010001
0.020000000000020002
0.030000000000030003
0.020000000000020002
0.030000000000030003
0.050000000000050004
0.049999999999980616
0.059999999999990616
0.07000000000000062
0.07000000000000062
0.07000000000000062

$\endgroup$
2
  • 1
    $\begingroup$ Wrong classes. For SOFR you need OISRateHelper and OvernightIndexedSwap, not SwapHelper and VanillaSwap. The latter pay a single fixing, the former pay daily compounded fixings. $\endgroup$ Commented May 8 at 11:34
  • $\begingroup$ Thanks! It works now $\endgroup$
    – Fiesteban
    Commented May 10 at 15:49

0

Your Answer

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

Browse other questions tagged or ask your own question.