I'm having trouble getting the same nodes when evaluating Fair Rates for a Mexican TIIE swap.
I think my problem is in the MXNOIS curve creation, but I'm not sure.
For evaluating, I am creating the MXNOIS curve using both USDOIS curve and USDSOFR curve.
¿Does Anyone know where the problem is?
import QuantLib as ql
import numpy as np
# EvaluationDate
ql.Settings.instance().evaluationDate = ql.Date(24,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(1)
# 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)
for n, unit in swaps.keys()]
## helpers merge
hlprUSDOIS = depositHelpers + OISHelpers
crvUSDOIS = ql.PiecewiseLogLinearDiscount(0, ql.UnitedStates(1),
hlprUSDOIS, ql.Actual360())
crvUSDOIS.enableExtrapolation()
discount_curve = ql.RelinkableYieldTermStructureHandle()
discount_curve.linkTo(crvUSDOIS)
######################## SOFR Construction ######################
# 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 = [tenor2ql[ten[-1]] for ten in str_tenor]
period = [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[0], tenor[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)
swaps = {}
for i in range(1, n):
swaps[(period[i], tenor[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
)
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,
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, ql.UnitedStates(1),
hlprSOFR,
ql.Actual360())
crvSOFR.enableExtrapolation()
crv_usdswp = ql.RelinkableYieldTermStructureHandle()
crv_usdswp.linkTo(crvSOFR)
########################## MXN_OIS Construction ############################
# data
def f(x):
if x[-1]=='L':
return int(x[1:-1])*4
else:
return int(x[1:-1])
# Calendars
calendar_mx = ql.Mexico()
spotfx = 17.0628
# TIIE Quotes
data_tiie = [0.11245, 0.1042005, 0.10025, 0.0978005, 0.09640125,
0.0952005, 0.09489, 0.0954, 0.09543, 0.095305]
str_tenors_tiie = ['%1L', '%26L', '%39L', '%52L', '%65L',
'%91L', '%130L', '%195L', '%260L', '%390L']
tenors_tiie = [tenor2ql[ten[-1]] for ten in str_tenors_tiie]
tiie_period = [f(t) for t in str_tenors_tiie]
# Fwd Quotes
str_tenors_fwds = ['%3M', '%6M', '%9M', '%1Y']
fwds_period = [3, 6, 9, 13]
data_fwds = [0.25095, 0.50925, 0.7563 , 1.0026 ]
# Basis Quotes
basis_period = [2, 3, 6, 9, 13, 26, 39, 52, 65, 91, 130, 195, 260, 390]
data_basis = [-0.00170628, 0.00464522, 0.00443489, 0.00400474, 0.00303945,
0.002, 0.0023, 0.00275, 0.00285, 0.0031, 0.00335, 0.00286,
0.0019925, 0.0019]
tenor = ql.EveryFourthWeek
# Basis swaps
basis_usdmxn = {}
n_basis = len(basis_period)
for i in range(1, n_basis):
basis_usdmxn[(basis_period[i], tenor)] = data_basis[i]
# Forward Points
fwdpts = {}
n_fwds = len(fwds_period)
for i in range(n_fwds):
fwdpts[(fwds_period[i], tenor)] = data_fwds[i]
# Deposit rates
deposits = {(tiie_period[0], tenors_tiie[0]): data_tiie[0]}
# TIIE Swap rates
swaps_tiie = {}
n_tiie = len(tiie_period)
for i in range(1, n_tiie):
swaps_tiie[(tiie_period[i], tenors_tiie[i])] = data_tiie[i]
# Qauntlib.Quote objects
for n, unit in basis_usdmxn.keys():
basis_usdmxn[(n, unit)] = ql.SimpleQuote(basis_usdmxn[(n, unit)])
for n, unit in fwdpts.keys():
fwdpts[(n, unit)] = ql.SimpleQuote(fwdpts[(n, unit)])
for n, unit in deposits.keys():
deposits[(n, unit)] = ql.SimpleQuote(deposits[(n, unit)])
for n, unit in swaps_tiie.keys():
swaps_tiie[(n, unit)] = ql.SimpleQuote(swaps_tiie[(n, unit)])
# Deposit rate helper
dayCounter = ql.Actual360()
settlementDays = 1
depositHelpers = [ql.DepositRateHelper(ql.QuoteHandle(deposits[(n, unit)]),
ql.Period(int(n), ql.Weeks),
settlementDays, calendar_mx,
ql.Following, False, dayCounter)
for n, unit in deposits.keys()]
# FX Forwards helper
fxSwapHelper = [ql.FxSwapRateHelper(ql.QuoteHandle(fwdpts[(n,u)]),
ql.QuoteHandle(ql.SimpleQuote(spotfx)),
ql.Period(int(n*4), ql.Weeks), 2,
calendar_mx, ql.Following,
False, True, discount_curve)
for n,u in fwdpts.keys()]
# Swap rate helpers
settlementDays = 2
fixedLegFrequency = ql.EveryFourthWeek
fixedLegAdjustment = ql.Following
fixedLegDayCounter = ql.Actual360()
# SOFR #
fxIborIndex = ql.Sofr(crv_usdswp)
swapHelpers = [ql.SwapRateHelper(ql.QuoteHandle(swaps_tiie[(n, unit)]),
ql.Period(int(n), ql.Weeks),
calendar_mx,
fixedLegFrequency,
fixedLegAdjustment,
fixedLegDayCounter,
fxIborIndex,
ql.QuoteHandle(
basis_usdmxn[(n/4, tenor)]),
ql.Period(0, ql.Days))
for n, unit in swaps_tiie.keys()]
# Rate helpers merge
hlprMXNOIS = depositHelpers + fxSwapHelper + swapHelpers
# Bootstrap
crvMXNOIS = ql.PiecewiseNaturalLogCubicDiscount(0, ql.Mexico(),
hlprMXNOIS,
ql.Actual360())
crvMXNOIS.enableExtrapolation()
crv_mxnois = ql.RelinkableYieldTermStructureHandle()
crv_mxnois.linkTo(crvMXNOIS)
########################## MXNTIIE Construction #############################
# Data
str_tenors = ['%1L', '%3L', '%6L', '%9L', '%13L', '%26L', '%39L', '%52L',
'%65L', '%91L', '%130L', '%195L', '%260L', '%390L']
period = [f(t) for t in str_tenors]
tenors = [tenor2ql[t[-1]] for t in str_tenors]
data = [0.11245, 0.1123025, 0.11165, 0.11065, 0.10935, 0.1042005, 0.10025,
0.0978005, 0.09640125, 0.0952005, 0.09489, 0.0954, 0.09543, 0.095305]
# Deposits
deposits = {(period[0], tenors[0]): data[0]}
swaps = {}
for i in range(1, len(str_tenors)):
swaps[(period[i], tenors[i])] = data[i]
# Rate Qauntlib.Quote objects
## desposits
for n, unit in deposits.keys():
deposits[(n, unit)] = ql.SimpleQuote(deposits[(n, unit)])
## swap rates
for n, unit in swaps.keys():
swaps[(n, unit)] = ql.SimpleQuote(swaps[(n, unit)])
# Deposit rate helpers
dayCounter = ql.Actual360()
settlementDays = 1
depositHelpers = [ql.DepositRateHelper(
ql.QuoteHandle(deposits[(n, unit)]), ql.Period(n, unit),
settlementDays, calendar_mx, ql.Following, False, dayCounter)
for n, unit in deposits.keys()]
# Swap rate helpers
settlementDays = 1
fixedLegFrequency = ql.EveryFourthWeek
fixedLegAdjustment = ql.Following
fixedLegDayCounter = ql.Actual360()
ibor_MXNTIIE = ql.IborIndex('TIIE',
ql.Period(13), settlementDays,
ql.MXNCurrency(), calendar_mx,
ql.Following, False, ql.Actual360(),
crv_mxnois)
swapHelpers = [ql.SwapRateHelper(ql.QuoteHandle(swaps[(n,unit)]),
ql.Period(int(n), unit), calendar_mx,
fixedLegFrequency, fixedLegAdjustment,
fixedLegDayCounter, ibor_MXNTIIE)
for n, unit in swaps.keys()]
# helpers merge
hlprTIIE = depositHelpers + swapHelpers
# Curve creation
crvTIIE = ql.PiecewiseNaturalLogCubicDiscount(0, ql.Mexico(), hlprTIIE,
ql.Actual360())
crvTIIE.enableExtrapolation()
########################## Ibor TIIE #######################
ibor_tiie_crv = ql.RelinkableYieldTermStructureHandle()
ibor_tiie_crv.linkTo(crvTIIE)
ibor_tiie = ql.IborIndex('TIIE',
ql.Period(13),
1,
ql.MXNCurrency(),
ql.Mexico(),
ql.Following,
False,
ql.Actual360(),
ibor_tiie_crv)
#%%
######################## SWAP VALUATION #################################
# Swap starts tomorrow as TIIE swaps have +1 day settlement
# We will evaluate a 91 Lunar months swap
start = ql.Date(26,4,2024)
maturity = start + 91*28
notional = 100_000_000
rate = .0950
typ = -1
rule = ql.DateGeneration.Backward
cal = ql.Mexico()
legDC = ql.Actual360()
cpn_tenor = ql.Period(13)
convention = ibor_tiie.businessDayConvention()
termDateConvention = ibor_tiie.businessDayConvention()
rule = rule
isEndOfMonth = False
# fix-float leg schedules
fixfltSchdl = ql.Schedule(start, maturity, cpn_tenor, cal, convention,
termDateConvention, rule, isEndOfMonth)
# swap
swap = ql.VanillaSwap(typ, notional, fixfltSchdl, rate, legDC, fixfltSchdl,
ibor_tiie, 0, legDC)
### Discount Engine ###
discount_engine = ql.DiscountingSwapEngine(crv_mxnois)
swap.setPricingEngine(discount_engine)
print('Node for 91L Tenor: ', 9.52005)
print('Calculated 91L Tenor: ', np.round(swap.fairRate()*100,6))
I am valuating a 91 lunar month swap (2548 days) that pays every 28 days
The Output is:
Node for 91L Tenor: 9.52005
Calculated 91L Tenor: 9.515049