I am trying to value the Latin Americans swaps. But CLP-TNA valuation is far off from the actual valuation. Please suggest, what I am missing in below methodology to compute NPV.

# construct discount curve and libor curve
risk_free_rate = 0.01
libor_rate = 0.02
day_count = ql.Actual360()

discount_curve = ql.YieldTermStructureHandle(
     ql.FlatForward(calculation_date, risk_free_rate, day_count)

  libor_curve = ql.YieldTermStructureHandle(
  ql.FlatForward(calculation_date, libor_rate, day_count)
# CLP index
 CLP_index = ql.OvernightIndex('CLP', 0, ql.CLPCurrency(), ql.WeekendsOnly(), ql.Actual360())

 calendar = ql.WeekendsOnly()
settle_date = calendar.advance(calculation_date, 5, ql.Days)
maturity_date = calendar.advance(settle_date, 10, ql.Years)

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

  float_schedule = ql.Schedule (settle_date, maturity_date, 
                      float_leg_tenor, calendar,
                      ql.ModifiedFollowing, ql.ModifiedFollowing,
                      ql.DateGeneration.Forward, False)

  notional = 10000000
 fixed_rate = 0.025
fixed_leg_daycount = ql.Actual360()
float_spread = 0.004
 float_leg_daycount = ql.Actual360()

 ir_swap = ql.VanillaSwap(ql.VanillaSwap.Payer, notional, fixed_schedule, 
       fixed_rate, fixed_leg_daycount, float_schedule,
       libor3M_index, float_spread, float_leg_daycount )

 swap_engine = ql.DiscountingSwapEngine(discount_curve)
  • $\begingroup$ I think it would be a useful exercise for someone to add to QL distro examples of pricing Chile Cámara swap, Brazil CDI, Colombia TRM, etc. $\endgroup$ Jun 17 at 22:21
  • 1
    $\begingroup$ By "actual valuation" do you mean the actual market value? You'll need proper interest-rate curves to get that right. $\endgroup$ Jun 18 at 8:36
  • $\begingroup$ Thanks @Luigi Ballabio. By Actual valuation means, am comparing the values with bloomberg valuation. In the post, I mentioned the curve construction using 'ql.overnightindex'. My question is, if CLP follows the same method 'Plain vanilla swap' methodology for valuation or there is different method for CLP. $\endgroup$
    – robin
    Jun 21 at 13:04
  • $\begingroup$ Thanks @Dimitri Vulis. Yes. It will be useful, to see some examples of the latin americas indexes valuations. $\endgroup$
    – robin
    Jun 21 at 13:06
  • $\begingroup$ If it pays an overnight index (compounded over the life of the coupon, I guess?) you probably want to use ql.OvernightIndexedSwap. But my point on the curves was that you're using a 2% flat curve to forecast the index and a 1% flat curve to discount (the two FlatForward instances). Bloomberg probably uses real market curves to calculate its prices. $\endgroup$ Jun 21 at 14:22

Here is an example that might point you in the right direction. As Luigi said the comments, you can't really expect to arrive at comparable values if you are just using flat curves.

So the first step would be to build a curve comparable to Bloomberg. I don't really have any experience with CLP buy looking at the info on BBG it looks like these swap are zero coupon until 18M and pay semi after that.

import QuantLib as ql
today = ql.Date(25,6,2021)
calendar = ql.WeekendsOnly()
ql.Settings.instance().evaluationDate = today
spot = calendar.advance(today, 2, ql.Days)
dayCount = ql.Actual360()

cop_ois_quotes = [
    ('3M', 0.780),
    ('6M', 1.140),
    ('9M', 1.435),
    ('12M', 1.770),
    ('18M', 2.145),
    ('2Y',  2.430),
    ('3Y',  2.745),
    ('4Y',  3.01),
    ('5Y',  3.28),
    ('6Y',  3.53),
    ('7Y',  3.715),
    ('8Y',  3.835),
    ('9Y',  3.93),
    ('10Y', 4.055),
    ('15Y', 4.365),
    ('20Y', 4.465),

helpers = []
clp_ois_yts = ql.RelinkableYieldTermStructureHandle()

index = ql.OvernightIndex('CLICP', 0, ql.CLPCurrency(), ql.WeekendsOnly(), dayCount, clp_ois_yts)
for tenor, value in cop_ois_quotes:
    value /= 100
    quote = ql.QuoteHandle(ql.SimpleQuote(value))
    period = ql.Period(tenor)
    paymentFrequency = ql.Semiannual if period.units() > 2 else ql.Once
    helper = ql.OISRateHelper(2, period, quote, index, paymentFrequency=paymentFrequency)
clp_ois_crv = ql.PiecewiseLogLinearDiscount(spot, helpers, ql.ActualActual())

You can then test your curve by pricing some swaps. Here I'm pricing the input instruments so it is expected to yield the same results but you can play around with other dates.

swapType = ql.OvernightIndexedSwap.Payer
nominal = 100

engine = ql.DiscountingSwapEngine(clp_ois_yts)

for tenor, value in cop_ois_quotes:
    value /= 100
    maturity = calendar.advance(spot, ql.Period(tenor))
    freq = '18M' if ql.Period(tenor).units() == 2 else '6M'
    schedule = ql.MakeSchedule(spot, maturity, ql.Period(freq), calendar=calendar)
    ois_swap = ql.OvernightIndexedSwap(swapType, nominal, schedule, 0.0, dayCount, index)
    print(f"{tenor}: {ois_swap.fairRate()*100:.4f}, {value*100:.4f}")

3M: 0.7800, 0.7800
6M: 1.1400, 1.1400
9M: 1.4350, 1.4350
12M: 1.7700, 1.7700
18M: 2.1450, 2.1450
2Y: 2.4300, 2.4300
3Y: 2.7479, 2.7450
4Y: 3.0100, 3.0100
5Y: 3.2800, 3.2800
6Y: 3.5300, 3.5300
7Y: 3.7150, 3.7150
8Y: 3.8350, 3.8350
9Y: 3.9312, 3.9300
10Y: 4.0550, 4.0550
15Y: 4.3650, 4.3650
20Y: 4.4651, 4.4650


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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