Search code examples
pythonpandasquantitative-financequantlib

Runtime error - Forward Rates Calculation


I am trying to build an forward annual EONIA forward curve with inputs of tenors from 1 week to 50 years.

I have managed to code thus far:

data
   maturity  spot rate
0         1     -0.529
1         2     -0.529
2         3     -0.529
3         1     -0.504
4         2     -0.505
5         3     -0.506
6         4     -0.508
7         5     -0.509
8         6     -0.510
9         7     -0.512
10        8     -0.514
11        9     -0.515
12       10     -0.517
13       11     -0.518
14        1     -0.520
15       15     -0.524
16       18     -0.526
17       21     -0.527
18        2     -0.528
19        3     -0.519
20        4     -0.501
21        5     -0.476
22        6     -0.441
23        7     -0.402
24        8     -0.358
25        9     -0.313
26       10     -0.265
27       11     -0.219
28       12     -0.174
29       15     -0.062
30       20      0.034
31       25      0.054
32       30      0.039
33       40     -0.001
34       50     -0.037

terms= data["maturity"].tolist()
rates= data['spot rate'].tolist()


calendar = ql.TARGET() 
business_convention = ql.ModifiedFollowing
day_count = ql.Actual360()
settlement_days_EONIA = 2
EONIA = ql.OvernightIndex("EONIA", settlement_days_EONIA, ql.EURCurrency(), calendar, day_count)

# Deposit Helper
depo_facility = -0.50
depo_helper = [ql.DepositRateHelper(ql.QuoteHandle(ql.SimpleQuote(depo_facility/100)), ql.Period(1,ql.Days), 1, calendar, ql.Unadjusted, False, day_count)]

# OIS Helper
OIS_helpers = []
for i in range(len(terms)):
    if i < 3:
        tenor = ql.Period(ql.Weeks)
        eon_rate = rates[i]
        OIS_helpers.append(ql.OISRateHelper(settlement_days_EONIA, tenor, ql.QuoteHandle(ql.SimpleQuote(eon_rate/100)), EONIA))    
    elif i < 12: 
        tenor = ql.Period(ql.Months)
        eon_rate = rates[i]
        OIS_helpers.append(ql.OISRateHelper(settlement_days_EONIA, tenor, ql.QuoteHandle(ql.SimpleQuote(eon_rate/100)), EONIA)) 
    else:
        tenor = ql.Period(ql.Years)
        eon_rate = rates[i]
        OIS_helpers.append(ql.OISRateHelper(settlement_days_EONIA, tenor, ql.QuoteHandle(ql.SimpleQuote(eon_rate/100)), EONIA))
    
rate_helpers = depo_helper +  OIS_helpers

eonia_curve_c = ql.PiecewiseLogCubicDiscount(0, ql.TARGET(), rate_helpers, day_count)
#This doesn't give me a daily grid of rates, but only the rates at the tenors of my input

eonia_curve_c.enableExtrapolation()


days = ql.MakeSchedule(eonia_curve_c.referenceDate(), eonia_curve_c.maxDate(), ql.Period('1Y'))

rates_fwd = [
    eonia_curve_c.forwardRate(d, calendar.advance(d,365,ql.Days), day_count, ql.Simple).rate()*100
    for d in days
]

The problem is that when I run the code, I get the following error:

RuntimeError: more than one instrument with pillar June 18th, 2021

There is probably an error somewhere in the code for the OIS helper, where there is an overlap but I am not sure what I have done wrong. Anyone know what the problem is?


Solution

  • First off, apologies for any inelegant Python, as I am coming from C++:

    The main issue with the original question was that ql.Period() takes two parameters when used with an integer number of periods: eg ql.Period(3,ql.Years). If instead you construct the input array with string representations of the tenors eg '3y' you can just pass this string to ql.Period(). So ql.Period(3,ql.Years) and ql.Period('3y') give the same result.

    import QuantLib as ql
    
    import numpy as np
    import pandas as pd
    
    curve = [ ['1w',     -0.529],
            ['2w',     -0.529],
            ['3w',     -0.529],
            ['1m',     -0.504],
            ['2m',     -0.505],
            ['3m',     -0.506],
            ['4m',     -0.508],
            ['5m',     -0.509],
            ['6m',     -0.510],
            ['7m',     -0.512],
            ['8m',     -0.514],
            ['9m',     -0.515],
            ['10m',     -0.517],
            ['11m',    -0.518],
            ['1y',     -0.520],
            ['15m',     -0.524],
            ['18m',     -0.526],
            ['21m',    -0.527],
            ['2y',     -0.528],
            ['3y',     -0.519],
            ['4y',     -0.501],
            ['5y',     -0.476],
            ['6y',     -0.441],
            ['7y',     -0.402],
            ['8y',     -0.358],
            ['9y',     -0.313],
            ['10y',     -0.265],
            ['11y',     -0.219],
            ['12y',     -0.174],
            ['15y',     -0.062],
            ['20y',      0.034],
            ['25y',      0.054],
            ['30y',      0.039],
            ['40y',     -0.001],
            ['50y',     -0.037] ]
    
    data = pd.DataFrame(curve, columns = ['maturity','spot rate'])
    
    print('Input curve\n',data)
    
    terms= data["maturity"].tolist()
    rates= data['spot rate'].tolist()
    
    calendar = ql.TARGET() 
    day_count = ql.Actual360()
    settlement_days_EONIA = 2
    EONIA = ql.OvernightIndex("EONIA", settlement_days_EONIA, ql.EURCurrency(), calendar, day_count)
    
    # Deposit Helper
    depo_facility = -0.50
    depo_helper = [ql.DepositRateHelper(ql.QuoteHandle(ql.SimpleQuote(depo_facility/100)), ql.Period(1,ql.Days), 1, calendar, ql.Unadjusted, False, day_count)]
    
    # OIS Helper
    OIS_helpers = []
    for i in range(len(terms)):
            tenor = ql.Period(terms[i])
            eon_rate = rates[i]
            OIS_helpers.append(ql.OISRateHelper(settlement_days_EONIA, tenor, ql.QuoteHandle(ql.SimpleQuote(eon_rate/100)), EONIA))    
        
    rate_helpers = depo_helper +  OIS_helpers
    
    eonia_curve_c = ql.PiecewiseLogCubicDiscount(0, ql.TARGET(), rate_helpers, day_count)
    #This doesn't give me a daily grid of rates, but only the rates at the tenors of my input
    
    eonia_curve_c.enableExtrapolation()
    
    
    days = ql.MakeSchedule(eonia_curve_c.referenceDate(), eonia_curve_c.maxDate(), ql.Period('1Y'))
    
    rates_fwd = [
        eonia_curve_c.forwardRate(d, calendar.advance(d,365,ql.Days), day_count, ql.Simple).rate()*100
        for d in days
    ]
    
    print('Output\n',pd.DataFrame(rates_fwd,columns=['Fwd rate']))