domokane / FinancePy

A Python Finance Library that focuses on the pricing and risk-management of Financial Derivatives, including fixed-income, equity, FX and credit derivatives.
GNU General Public License v3.0
2.16k stars 322 forks source link

Division with wrong day count convention causes incorrect forward rates #195

Closed Nollad closed 1 year ago

Nollad commented 1 year ago

The calculation for the floating leg payments in IborSwap is incorrect, leading to a sizable NPV difference with QuantLib. This was discussed in issue #188 and dismissed but I still think there is an issue here.

The issue is in the following line in swap_float_leg.py https://github.com/domokane/FinancePy/blob/845f1c994b2652f0b097e0ec436896dbe2f54dd5/financepy/products/rates/swap_float_leg.py#L191

The division by index_alpha is incorrect here, it should be the pay_alpha, i.e. the year fraction associated with the instrument as opposed to the curve. It was pointed out in #188 that this causes valuation differences in the notebook https://github.com/domokane/FinancePy/blob/845f1c994b2652f0b097e0ec436896dbe2f54dd5/notebooks/products/rates/FINIBORSWAP_ComparisonWithQLExample.ipynb

Using the same source for QuantLib valuation as in the linked notebook (http://gouthamanbalaraman.com/blog/interest-rate-swap-quantlib-python.html) we can validate that the correct forward rate is the one dividing by pay_alpha as follows

# set up as in linked source first
start, end = ql.Date(27, 1, 2016), ql.Date(27, 4, 2016)
pay_alpha = (end - start) / 360.0
index_alpha = (end - start) / 365.0

df1 = libor_curve.discount(start)
df2 = libor_curve.discount(end)

fwd_pay = float_spread + (df1 / df2 - 1.0) / pay_alpha
fwd_index = float_spread + (df1 / df2 - 1.0) / index_alpha

actual_value = 60098.65

print("Difference with QuantLib")
print(f"With division by pay alpha: {actual_value - fwd_pay * pay_alpha * notional}")
print(f"With division index alpha: {actual_value - fwd_index * pay_alpha * notional}")

The variables fwd_pay, fwd_index follow the same methodology for generating the forward rate as in swap_float_leg.py, with one using the index daycount and the other using the payment daycount. The results are as follows

Difference with QuantLib
With division by pay alpha: 0.002299979802046437
With division index alpha: -694.2690415328107

Hopefully I've formatted this correctly. First time using the github issues page :)

Best, Oscar

Nollad commented 1 year ago

This analysis is incorrect on my part. Seems that QuantLib does use the index daycounter to establish fixings. Please disregard