|
From: Kenji O. <for...@gm...> - 2025-09-20 03:26:22
|
When I calculate the couponLegNPV of a CDS using a toy model, the result is
*-86,462.44*.
However, when I compute it from the cashflow table, I obtain *-86,421.46*,
which differs by about 40 USD.
On the other hand, the value of defaultLegNPV matches exactly with the one
obtained from the cashflow table.
(This calculation from the cashflow table also matches Bloomberg’s result.)
Below I attach my code.
Any explanation or guidance would be greatly appreciated.
# =====================================================
import QuantLib as ql; import pandas as pd; import numpy as np
# (1) QL abbreviation and trade data
dcA365, dcA360, pdFreqQ, calWK
= \
ql.Actual365Fixed(), ql.Actual360(), ql.Period(ql.Quarterly), ql.
WeekendsOnly()
FLLW, unADJ, dtGENf, EoMf, bP
= \
ql.Following, ql.Unadjusted, ql.DateGeneration.Forward, False, ql.Protection
.Buyer
def sqHDL(xx): return ql.QuoteHandle(ql.SimpleQuote(xx))
#
matDT, ntlAMT, rfRT, cdsQT,
=\
ql.Date(20,9,2023), 10_000_000, 0.10, 0.13
tradeDT = ql.Date(19,9,2022) ; ql.Settings.instance().evaluationDate =
tradeDT
cdsSCD = ql.Schedule(tradeDT+1, matDT, pdFreqQ, calWK, FLLW, unADJ, dtGENf,
EoMf)
# (2) DF,hazardRate
dsCvOBJ = ql.FlatForward (tradeDT, rfRT, dcA365)
dsCvHDL = ql.YieldTermStructureHandle (dsCvOBJ)
hzdRT = cdsQT/(1-0.35)
hzCvOBJ = ql.FlatHazardRate (tradeDT,sqHDL(hzdRT), dcA365)
hzCvHDL = ql.DefaultProbabilityTermStructureHandle(hzCvOBJ)
# (3).cds legNPV
cdsOBJ = ql.CreditDefaultSwap(bP, ntlAMT, 0.01, cdsSCD, FLLW, dcA360)
midENG = ql.MidPointCdsEngine(hzCvHDL, 0.35, dsCvHDL)
cdsOBJ.setPricingEngine(midENG)
impHzdRT = cdsOBJ.impliedHazardRate(cdsOBJ.NPV(), dsCvHDL, dcA365, 0.35)
# print
np.set_printoptions(precision=6)
print(
f'couponLegNPV :{cdsOBJ.couponLegNPV() :,.2f}, '
f'defaultLegNPV :{cdsOBJ.defaultLegNPV():,.2f}, '
f'NPV :{cdsOBJ.NPV() :,.2f} ' '\n'
f'tradeDate :{cdsOBJ.tradeDate().ISO():}, '
f'hazardRate :{hzdRT :.6%}, '
f'imp.HazardRate :{impHzdRT :.6%} ' )
#=====================================================
# (4) make Cashflow table function
def makeDataFrame() :
dfCDS = pd.DataFrame({
'payDate': cpn.date(),
'coupon': cpn.rate(),
'accStt': cpn.accrualStartDate().ISO(),
'accEnd': cpn.accrualEndDate(),
'days': cpn.amount()/(ntlAMT*cpn.rate() /360),
'YF': cpn.amount()/(ntlAMT*cpn.rate()*365/360),
'amount': cpn.amount(),
} for cpn in map(ql.as_coupon, cdsOBJ.coupons()))
# effective Date
dfEFF = pd.DataFrame([{'payDate': cdsOBJ.protectionStartDate(),
'accEnd': cdsOBJ.protectionStartDate()}],columns
=dfCDS.columns)
dfCDS = pd.concat([dfEFF, dfCDS], ignore_index=True)
# DF
fuDF = [dsCvOBJ.discount(dt) for dt in dfCDS.accEnd] #future DF
dfCDS = pd.concat([dfCDS, pd.DataFrame(fuDF, columns=['DF']) ], axis=1)
# Q and dQ
fuQ = [hzCvOBJ.survivalProbability(dt) for dt in dfCDS.accEnd]
dfCDS = pd.concat([dfCDS, pd.DataFrame(fuQ, columns=['Q']) ], axis=1)
dQ = np.insert(np.diff(dfCDS.Q)*(-1), 0, 0)
dfCDS = pd.concat([dfCDS, pd.DataFrame( dict(dQ=dQ)) ], axis=1)
# midpoint calc
midDTs = dfCDS.payDate[:-1]+(np.diff(dfCDS.payDate)/2).astype('int')
midDFs = [dsCvOBJ.discount(dd) for dd in midDTs]
midQs = [hzCvOBJ.survivalProbability(dd) for dd in midDTs]
dfDFQm = pd.DataFrame( dict(mDate=midDTs, mDF=midDFs, mQ=midQs))
dfDFQm.mDate = dfDFQm.mDate.map(lambda x: x.ISO())
dfDFQm = pd.concat([pd.DataFrame(columns=['mDate'],index=[0]), #empty
row
dfDFQm],ignore_index
=True)
dfCDS = pd.concat([dfCDS, dfDFQm ], axis=1)
return dfCDS
#(5) NPV handCalc
dfCDS = makeDataFrame()
hcCpnLEG = -(dfCDS.amount * dfCDS.DF * dfCDS.mQ).sum()
hcDftLEG = (1-0.35) *(dfCDS.mDF* dfCDS.dQ).sum()
hcNPV = hcCpnLEG + hcDftLEG*ntlAMT
print(f'(hc)cpnLegNPV: {hcCpnLEG:,.2f}, '
f'(hc)dftLegNPV: {hcDftLEG*ntlAMT:,.2f}, '
f'(hc)NPV: {hcNPV:,.2f}')
display(dfCDS)
#=====================================================
|