Showing posts with label Market Data. Show all posts
Showing posts with label Market Data. Show all posts

Sunday, July 14, 2019

QuantLib-Python: flexible construction scheme for piecewise yield term structures

I consider QuantLib to be a fundamental pricing library, which can effectively handle valuations for pretty much any given type of security. If there is no existing implementation for an instrument available, one can create a new implementation for it. What then makes the use of QuantLib library sometimes difficult? It's the amount of work to be done, before anything will happen. Outside of that promised functionality to value security, one has to take full responsibility of all involved janitor work. The code is (usually always) containing endless sections for different variable definitions and creation of different types of helper objects. Even creating realistic pricing scheme for a simple interest rate swap seems to require an army of different variables and objects. A lot of cooking anyway, before the beef will be served.

In this post, one possible scheme for flexible construction of QuantLib piecewise yield term structures will be presented. The program and all involved files can be downloaded from my GitHub repository.

Assume we would like to construct piecewise yield term structure for EUR and USD. Assume also, that we have the following market data for EUR and USD currencies in a specific CSV file.

Ticker,Value
USD.DEPOSIT.1D,0.02359
USD.DEPOSIT.1W,0.0237475
USD.DEPOSIT.1M,0.02325
USD.DEPOSIT.2M,0.0232475
USD.DEPOSIT.3M,0.0230338
USD.FUTURE.2M,97.92
USD.FUTURE.5M,98.005
USD.FUTURE.8M,98.185
USD.FUTURE.11M,98.27
USD.FUTURE.14M,98.33
USD.SWAP.2Y,0.01879
USD.SWAP.3Y,0.01835
USD.SWAP.5Y,0.01862
USD.SWAP.7Y,0.0194
USD.SWAP.10Y,0.02065
USD.SWAP.15Y,0.02204
USD.SWAP.30Y,0.02306
EUR.DEPOSIT.1D,-0.00366
EUR.DEPOSIT.1W,-0.00399
EUR.DEPOSIT.1M,-0.00393
EUR.DEPOSIT.3M,-0.00363
EUR.DEPOSIT.6M,-0.00342
EUR.FUTURE.5M,100.48
EUR.FUTURE.8M,100.505
EUR.FUTURE.11M,100.505
EUR.FUTURE.14M,100.495
EUR.FUTURE.17M,100.47
EUR.SWAP.1Y,-0.0038
EUR.SWAP.2Y,-0.0039
EUR.SWAP.5Y,-0.0019
EUR.SWAP.7Y,-0.0002
EUR.SWAP.10Y,0.0024
EUR.SWAP.15Y,0.0056
EUR.SWAP.30Y,0.008

In essence, we have key-value pairs in this CSV file, where the key is ticker (instrument, such as deposit, future or swap) and the value is rate (or price for a futures contract). Now, all instruments in that file are following some specific market conventions. All these conventions are then stored in a specific JSON file. The content of this file can be easily understood by using some available JSON editor.






















In essence, we are actually storing all required "constant" parameters for all QuantLib instruments we would like to use for constructing piecewise yield curves, into this file. At the moment, there are required conventions available for constructing EUR and USD curves.

Next, we have builder class PiecewiseCurveBuilder for assembling QuantLib piecewise yield term structures, as shown below. In the first stage, we store all conventions and market data into this builder class in its constructor. After this, builder is ready for constructing curves. As client is requesting specific curve by using Build method, the class will then create all bootstrap helpers based on a given market data (for requested currency) and instrument conventions (for instruments in requested currency).

# create piecewise yield term structure
class PiecewiseCurveBuilder(object):
    
    # in constructor, we store all possible instrument conventions and market data
    def __init__(self, settlementDate, conventions, marketData):        
        self.helpers = [] # list containing bootstrap helpers
        self.settlementDate = settlementDate
        self.conventions = conventions
        self.market = marketData
    
    # for a given currency, first assemble bootstrap helpers, 
    # then construct yield term structure handle
    def Build(self, currency, enableExtrapolation = True):

        # clear all existing bootstrap helpers from list
        self.helpers.clear()
        # filter out correct market data set for a given currency
        data = self.market.loc[self.market['Ticker'].str.contains(currency), :]
        
        # loop through market data set
        for i in range(data.shape[0]):            
            # extract ticker and value
            ticker = data.iloc[i]['Ticker']
            value = data.iloc[i]['Value'] 
            
            # add deposit rate helper
            # ticker prototype: 'CCY.DEPOSIT.3M'
            if('DEPOSIT' in ticker):
                # extract correct instrument convention
                convention = self.conventions[currency]['DEPOSIT']
                rate = value
                period = ql.Period(ticker.split('.')[2])
                # extract parameters from instrument convention
                fixingDays = convention['FIXINGDAYS']
                calendar = Convert.to_calendar(convention['CALENDAR'])
                businessDayConvention = Convert.to_businessDayConvention(convention['BUSINESSDAYCONVENTION'])
                endOfMonth = convention['ENDOFMONTH']
                dayCounter = Convert.to_dayCounter(convention['DAYCOUNTER'])
                # create and append deposit helper into helper list
                self.helpers.append(ql.DepositRateHelper(rate, period, fixingDays, 
                    calendar, businessDayConvention, endOfMonth, dayCounter))
        
            # add futures rate helper
            # ticker prototype: 'CCY.FUTURE.10M'
            # note: third ticker field ('10M') is defining starting date
            # for future to be 10 months after defined settlement date
            if('FUTURE' in ticker):
                # extract correct instrument convention
                convention = self.conventions[currency]['FUTURE']
                price = value
                iborStartDate = ql.IMM.nextDate(self.settlementDate + ql.Period(ticker.split('.')[2]))
                # extract parameters from instrument convention
                lengthInMonths = convention['LENGTHINMONTHS']
                calendar = Convert.to_calendar(convention['CALENDAR'])
                businessDayConvention = Convert.to_businessDayConvention(convention['BUSINESSDAYCONVENTION']) 
                endOfMonth = convention['ENDOFMONTH']
                dayCounter = Convert.to_dayCounter(convention['DAYCOUNTER'])
                # create and append futures helper into helper list
                self.helpers.append(ql.FuturesRateHelper(price, iborStartDate, lengthInMonths,
                    calendar, businessDayConvention, endOfMonth, dayCounter))                
            
            # add swap rate helper
            # ticker prototype: 'CCY.SWAP.2Y'
            if('SWAP' in ticker):
                # extract correct instrument convention
                convention = self.conventions[currency]['SWAP']
                rate = value
                periodLength = ql.Period(ticker.split('.')[2])
                # extract parameters from instrument convention
                fixedCalendar = Convert.to_calendar(convention['FIXEDCALENDAR'])
                fixedFrequency = Convert.to_frequency(convention['FIXEDFREQUENCY']) 
                fixedConvention = Convert.to_businessDayConvention(convention['FIXEDCONVENTION'])
                fixedDayCount = Convert.to_dayCounter(convention['FIXEDDAYCOUNTER'])
                floatIndex = Convert.to_iborIndex(convention['FLOATINDEX']) 
                # create and append swap helper into helper list
                self.helpers.append(ql.SwapRateHelper(rate, periodLength, fixedCalendar,
                    fixedFrequency, fixedConvention, fixedDayCount, floatIndex))
        
        # extract day counter for curve from configurations
        dayCounter = Convert.to_dayCounter(self.conventions[currency]['CONFIGURATIONS']['DAYCOUNTER'])
        # construct yield term structure handle
        yieldTermStructure = ql.PiecewiseLinearZero(self.settlementDate, self.helpers, dayCounter)
        if(enableExtrapolation == True): yieldTermStructure.enableExtrapolation()
        return ql.RelinkableYieldTermStructureHandle(yieldTermStructure)

The final component in this scheme is Convert class, which performs conversions from string presentation to specific QuantLib data types. This class is heavily used in builder class, where convention string information is transformed into correct QuantLib data types.

# utility class for different QuantLib type conversions 
class Convert:
    
    # convert date string ('yyyy-mm-dd') to QuantLib Date object
    def to_date(s):
        monthDictionary = {
            '01': ql.January, '02': ql.February, '03': ql.March,
            '04': ql.April, '05': ql.May, '06': ql.June,
            '07': ql.July, '08': ql.August, '09': ql.September,
            '10': ql.October, '11': ql.November, '12': ql.December
        }
        s = s.split('-')
        return ql.Date(int(s[2]), monthDictionary[s[1]], int(s[0]))
    
    # convert string to QuantLib businessdayconvention enumerator
    def to_businessDayConvention(s):
        if (s.upper() == 'FOLLOWING'): return ql.Following
        if (s.upper() == 'MODIFIEDFOLLOWING'): return ql.ModifiedFollowing
        if (s.upper() == 'PRECEDING'): return ql.Preceding
        if (s.upper() == 'MODIFIEDPRECEDING'): return ql.ModifiedPreceding
        if (s.upper() == 'UNADJUSTED'): return ql.Unadjusted
        
    # convert string to QuantLib calendar object
    def to_calendar(s):
        if (s.upper() == 'TARGET'): return ql.TARGET()
        if (s.upper() == 'UNITEDSTATES'): return ql.UnitedStates()
        if (s.upper() == 'UNITEDKINGDOM'): return ql.UnitedKingdom()
        # TODO: add new calendar here
        
    # convert string to QuantLib swap type enumerator
    def to_swapType(s):
        if (s.upper() == 'PAYER'): return ql.VanillaSwap.Payer
        if (s.upper() == 'RECEIVER'): return ql.VanillaSwap.Receiver
        
    # convert string to QuantLib frequency enumerator
    def to_frequency(s):
        if (s.upper() == 'DAILY'): return ql.Daily
        if (s.upper() == 'WEEKLY'): return ql.Weekly
        if (s.upper() == 'MONTHLY'): return ql.Monthly
        if (s.upper() == 'QUARTERLY'): return ql.Quarterly
        if (s.upper() == 'SEMIANNUAL'): return ql.Semiannual
        if (s.upper() == 'ANNUAL'): return ql.Annual

    # convert string to QuantLib date generation rule enumerator
    def to_dateGenerationRule(s):
        if (s.upper() == 'BACKWARD'): return ql.DateGeneration.Backward
        if (s.upper() == 'FORWARD'): return ql.DateGeneration.Forward
        # TODO: add new date generation rule here

    # convert string to QuantLib day counter object
    def to_dayCounter(s):
        if (s.upper() == 'ACTUAL360'): return ql.Actual360()
        if (s.upper() == 'ACTUAL365FIXED'): return ql.Actual365Fixed()
        if (s.upper() == 'ACTUALACTUAL'): return ql.ActualActual()
        if (s.upper() == 'ACTUAL365NOLEAP'): return ql.Actual365NoLeap()
        if (s.upper() == 'BUSINESS252'): return ql.Business252()
        if (s.upper() == 'ONEDAYCOUNTER'): return ql.OneDayCounter()
        if (s.upper() == 'SIMPLEDAYCOUNTER'): return ql.SimpleDayCounter()
        if (s.upper() == 'THIRTY360'): return ql.Thirty360()

    # convert string (ex.'USD.3M') to QuantLib ibor index object
    def to_iborIndex(s):
        s = s.split('.')
        if(s[0].upper() == 'USD'): return ql.USDLibor(ql.Period(s[1]))
        if(s[0].upper() == 'EUR'): return ql.Euribor(ql.Period(s[1]))        

Finally, let us take a look, how easily we can actually construct QuantLib piecewise yield term structures for our two currencies. After this point, these constructed curves can then be used as arguments for pricing engines.

# create instrument conventions and market data
rootDirectory = sys.argv[1] # command line argument: '/home/mikejuniperhill/QuantLib/'
evaluationDate = Convert.to_date(datetime.today().strftime('%Y-%m-%d'))
ql.Settings.instance().evaluationDate = evaluationDate
conventions = Configurations(rootDirectory + 'conventions.json')
marketData = pd.read_csv(rootDirectory + 'marketdata.csv')

# initialize builder, store all conventions and market data
builder = PiecewiseCurveBuilder(evaluationDate, conventions, marketData)
currencies = sys.argv[2] # command line argument: 'USD,EUR'
currencies = currencies.split(',')

# construct curves based on instrument conventions, given market data and currencies
for currency in currencies:    
    curve = builder.Build(currency)
    # print discount factors semiannually up to 30 years
    times = np.linspace(0.0, 30.0, 61)
    df = [round(curve.discount(t), 4) for t in times]
    print('discount factors for', currency)
    print(df)

Address to root directory (containing market data and instrument conventions files) and requested currencies are given as command line arguments. Conventions object and market data (pandas data frame) are being created and fed to curve builder object in its constructor. Finally, curves are being requested for each currency.

It can be seen, that we can actually create piecewise yield term structures for any currency (as long as market data and conventions are available) with a very few lines of code by using this kind of construction scheme. In essence, all the complexity involved is still there, but we have effectively moved all "janitor work" into specific classes (builder, conversions) and files (conventions, market data).

Configuration for a new currency should be straightforward: add instruments into market data file (ticker-value-pairs). Then, add new section for this new currency to conventions file (including configuration sub-sections for all instruments in this new currency). Also, some minor additions might be needed in conversion class.

Program execution is shown below.








Thanks for reading my blog.
-Mike

Tuesday, January 8, 2019

Python: Market Scenario Files Generator for Third-party Analytics Software

Third-party analytics software usually requires specific set of market data for performing its calculations. In this post, I am publishing one of my utility Python programs for creating different types of stress scenario markets, based on given base market and set of prepared XML configurations. The complete program can be found in my GitHub repository.

Configurations


The following screenshot shows configurations for this program. SourceFilePath attribute captures the source market data CSV file and TargetFolderPath captures the folder, into which all market scenario files will be created. Finally, ScenarioConfigurationsPath captures the folder, which contains all XML scenario configuration files. This configuration XML file should be stored in a chosen directory.

<Configurations>
    <!-- attributes for scenario generator settings -->
    <ScenarioConfigurationsPath>\\Temp\ScenarioConfigurations\</ScenarioConfigurationsPath>
    <SourceFilePath>\\Temp\baseMarket.csv</SourceFilePath>
    <TargetFolderPath>\\Temp\Scenarios\</TargetFolderPath>
</Configurations>

Market data


The following screenshot shows given base market data. Due to brevity reasons, only EUR swap curve has been used here as an example. All market data points are defined here as key-value pairs (ticker, value).

IR.EUR-EURIBOR.CASH-1BD.MID,-0.00365
IR.EUR-EURIBOR.CASH-1W.MID,-0.00373
IR.EUR-EURIBOR.CASH-1M.MID,-0.00363
IR.EUR-EURIBOR.CASH-2M.MID,-0.00336
IR.EUR-EURIBOR.CASH-3M.MID,-0.00309
IR.EUR-EURIBOR.CASH-6M.MID,-0.00237
IR.EUR-EURIBOR-6M.FRA-1M-7M.MID,-0.00231
IR.EUR-EURIBOR-6M.FRA-2M-8M.MID,-0.00227
IR.EUR-EURIBOR-6M.FRA-3M-9M.MID,-0.002255
IR.EUR-EURIBOR-6M.FRA-4M-10M.MID,-0.00218
IR.EUR-EURIBOR-6M.FRA-5M-11M.MID,-0.00214
IR.EUR-EURIBOR-6M.FRA-6M-12M.MID,-0.002075
IR.EUR-EURIBOR-6M.FRA-7M-13M.MID,-0.00198
IR.EUR-EURIBOR-6M.FRA-8M-14M.MID,-0.00186
IR.EUR-EURIBOR-6M.FRA-9M-15M.MID,-0.001775
IR.EUR-EURIBOR-6M.FRA-10M-16M.MID,-0.00169
IR.EUR-EURIBOR-6M.FRA-11M-17M.MID,-0.00159
IR.EUR-EURIBOR-6M.FRA-12M-18M.MID,-0.00141
IR.EUR-EURIBOR-6M.SWAP-2Y.MID,-0.001603
IR.EUR-EURIBOR-6M.SWAP-3Y.MID,-0.000505
IR.EUR-EURIBOR-6M.SWAP-4Y.MID,0.00067
IR.EUR-EURIBOR-6M.SWAP-5Y.MID,0.00199
IR.EUR-EURIBOR-6M.SWAP-6Y.MID,0.003315
IR.EUR-EURIBOR-6M.SWAP-7Y.MID,0.0046
IR.EUR-EURIBOR-6M.SWAP-8Y.MID,0.00584
IR.EUR-EURIBOR-6M.SWAP-9Y.MID,0.00698
IR.EUR-EURIBOR-6M.SWAP-10Y.MID,0.00798
IR.EUR-EURIBOR-6M.SWAP-11Y.MID,0.00895
IR.EUR-EURIBOR-6M.SWAP-12Y.MID,0.009775
IR.EUR-EURIBOR-6M.SWAP-15Y.MID,0.01165
IR.EUR-EURIBOR-6M.SWAP-20Y.MID,0.013245
IR.EUR-EURIBOR-6M.SWAP-25Y.MID,0.013725
IR.EUR-EURIBOR-6M.SWAP-30Y.MID,0.01378

We can clearly see, that the system used for constructing market data tickers leads to scheme, in which every market data point will have one and only one unique ticker. This will then guarantee, that we can drill down and stress individual market data points with regex expressions, if so desired. This data should be copied into CSV file (directory has been defined in previous configuration file).

Scenario configurations


The following screenshot shows XML configurations for one market scenario. One such scenario can have several different scenario items (Say, stress these rates up, stress those rates down, apply these changes to all FX rates against EUR and set hard-coded values for all CDS curves). From these configurations, ID and description are self-explainable. Attribute regExpression captures all regex expressions (scenario items), which will be searched from risk factor tickers. As soon as regex match is found, the program will use corresponding operationType attribute to identify desired stress operation (addition, multiplication or hard-coded value). Finally, the amount of change which will be applied in risk factor value is defined within stressValue attribute. This XML configuration should be stored (directory has been defined in program configuration file).

<!-- operation types : 0 = ADDITION, 1 = MULTIPLICATION, 2 = HARD-CODED VALUE -->
<Scenario>
  <ID>CURVE.STRESS</ID>
  <description>custom stress scenario for EUR swap curve</description>
  <regExpression>^IR.EUR-EURIBOR.CASH,^IR.EUR-EURIBOR-6M.FRA,^IR.EUR-EURIBOR-6M.SWAP</regExpression>
  <operationType>1,0,2</operationType>
  <stressValue>1.25,0.015,0.05</stressValue>
</Scenario>

Finally, the following screenshot shows resulting market data, when all configured scenario items have been applied. This is the content of output CSV file, created by this Python program.

IR.EUR-EURIBOR.CASH-1BD.MID,-0.0045625
IR.EUR-EURIBOR.CASH-1W.MID,-0.0046625
IR.EUR-EURIBOR.CASH-1M.MID,-0.0045375
IR.EUR-EURIBOR.CASH-2M.MID,-0.004200000000000001
IR.EUR-EURIBOR.CASH-3M.MID,-0.0038624999999999996
IR.EUR-EURIBOR.CASH-6M.MID,-0.0029625000000000003
IR.EUR-EURIBOR-6M.FRA-1M-7M.MID,0.01269
IR.EUR-EURIBOR-6M.FRA-2M-8M.MID,0.01273
IR.EUR-EURIBOR-6M.FRA-3M-9M.MID,0.012745
IR.EUR-EURIBOR-6M.FRA-4M-10M.MID,0.01282
IR.EUR-EURIBOR-6M.FRA-5M-11M.MID,0.01286
IR.EUR-EURIBOR-6M.FRA-6M-12M.MID,0.012924999999999999
IR.EUR-EURIBOR-6M.FRA-7M-13M.MID,0.01302
IR.EUR-EURIBOR-6M.FRA-8M-14M.MID,0.013139999999999999
IR.EUR-EURIBOR-6M.FRA-9M-15M.MID,0.013224999999999999
IR.EUR-EURIBOR-6M.FRA-10M-16M.MID,0.013309999999999999
IR.EUR-EURIBOR-6M.FRA-11M-17M.MID,0.01341
IR.EUR-EURIBOR-6M.FRA-12M-18M.MID,0.01359
IR.EUR-EURIBOR-6M.SWAP-2Y.MID,0.05
IR.EUR-EURIBOR-6M.SWAP-3Y.MID,0.05
IR.EUR-EURIBOR-6M.SWAP-4Y.MID,0.05
IR.EUR-EURIBOR-6M.SWAP-5Y.MID,0.05
IR.EUR-EURIBOR-6M.SWAP-6Y.MID,0.05
IR.EUR-EURIBOR-6M.SWAP-7Y.MID,0.05
IR.EUR-EURIBOR-6M.SWAP-8Y.MID,0.05
IR.EUR-EURIBOR-6M.SWAP-9Y.MID,0.05
IR.EUR-EURIBOR-6M.SWAP-10Y.MID,0.05
IR.EUR-EURIBOR-6M.SWAP-11Y.MID,0.05
IR.EUR-EURIBOR-6M.SWAP-12Y.MID,0.05
IR.EUR-EURIBOR-6M.SWAP-15Y.MID,0.05
IR.EUR-EURIBOR-6M.SWAP-20Y.MID,0.05
IR.EUR-EURIBOR-6M.SWAP-25Y.MID,0.05
IR.EUR-EURIBOR-6M.SWAP-30Y.MID,0.05

Handy way to create and test regex expressions is to use any online tool available. As an example, the first scenario item (^IR.EUR-EURIBOR.CASH) has been applied to a given base market data. The last screenshot below shows all regex matches.






















Have a great start for the year 2019 and thanks a lot again for reading my blog.
-Mike

Friday, November 23, 2018

Synthetic Basis Spread Calculation for Short-term Cross-currency Swaps

For cross-currency basis spread curves, there are usually no quotes available below 1Y time point (for 3M and 6M) and there might be a need to implement the appropriate calculation methodology for the missing data. This post will explain one possible calculation methodology based on Covered Interest Parity and presents the most relevant test results for this applied methodology.

Methodology


We will use adjusted version of traditional Covered Interest Rate Parity (CIP) for calculating synthetic quotes for short-term cross-currency basis spreads. First, in the absence of any arbitrage opportunities, CIP is assuming that the following parity will hold:



Assuming EUR/USD currency pair, we may consider the following two separate investment strategies:

1. Converting EUR amount to USD by using FX spot rate, then investing received USD amount by using USD interest rate for a given period and finally, converting resulting USD amount back to EUR by using FX forward.

2. Investing directly into EUR currency by using EUR interest rate for a given period.

As empirical evidence is strongly suggesting, CIP does not hold, since there is always some basis spread for a given currency pair for a given period. In order to take this basis spread properly into account, adjusted CIP is assuming the following parity to hold:



Where S is cross-currency basis spread for a given period. Finally, we can solve this cross-currency basis spread S for a given period by using the following analytical formula:




Test I: snapshot


As a first test for evaluating this methodology, we used formula for calculating short-term basis spread for a few liquid/major currency pairs. We used comparison data for this specific date from Bloomberg and/or ICE. We present the results in the following tables. Note, that even the curves are up to 5Y, only 3M and 6M points have been estimated by using Adjusted CIP.
























For these specific liquid/major currency pairs, applied calculation methodology is able to re-produce snapshot market quotes for the short-end relatively accurately. General hypothesis is, that the more liquid/major the currency pair, the more accurate results will be produced by Adjusted CIP.

Test II: time-series


At this point in our analysis, we are interested about the consistency of the proposed methodology. We want to know, whether this methodology produces consistent results as market changes over time. As a second test, we calculated short-term basis spread time-series for EUR/USD pair since the beginning of the year of 2014. We have used Bloomberg market quotes for this period as benchmark. The following table shows time-series data for 3M cross-currency basis spread.






















While there are clearly some periods with larger difference between market quote and estimated spread, applied calculation methodology is still able to re-produce market quotes consistently and relatively accurately over time.

Sensitivity


Generally, calculated basis spread estimate is highly sensitive for the interaction of specific variables (∆t, FX spot and FX forward). More specifically, as tenor ∆t will approach to zero, the relative change in spread estimate will generally increase. The following analysis was made for EUR/USD currency pair. All the other parameters were held constant, while FX spot was stressed to both directions in order to see the effect. In practice this means, that basis spread estimate might be heavily biased if, for example, market variables used in parity formula are not captured exactly at the same time.























Practical implementation notes


Assume the following parameters and market data. As we estimate spread for 3M tenor, we can use adjusted CIP formula as follows:






























Note, that when estimating spread for 6M tenor, the formula above can still be used, but domestic and foreign interest rate quotes must be adjusted accordingly by using 3v6 tenor basis spreads. Since there is no explicit basis spread quotes available for 3M and 6M, 1Y quote can be used as relatively close proxy.

































Finally, the following Excel/VBA function implements the calculation presented above. Thanks for reading my blog.
-Mike

Public Function GetXCCYBasisSpread( _
    ByVal FX_spot As Double, _
    ByVal FX_forward As Double, _
    ByVal BaseDepositRate As Double, _
    ByVal TermDepositRate As Double, _
    ByVal Tenor As String, _
    ByVal BasisSign As Integer, _
    ByVal ScaleRates As Boolean, _
    ByVal UseForwardPoints As Boolean, _
    ByVal ConvertToBasisPoints As Boolean) As Double
    
    ' tenor conversion from string ("3M") to double (0.25)
    ' handling for FX spot and forward, rate scaling
    Dim tenorInYears As Double
    If (VBA.UCase(Tenor) = "3M") Then tenorInYears = (3 / 12)
    If (VBA.UCase(Tenor) = "6M") Then tenorInYears = (6 / 12)
    If (UseForwardPoints) Then FX_forward = FX_spot + FX_forward
    If (ScaleRates) Then
        BaseDepositRate = BaseDepositRate / VBA.CDbl(100)
        TermDepositRate = TermDepositRate / VBA.CDbl(100)
    End If

    ' calculate cross-currency basis spread from given market data
    ' by using modified FX-IR-parity :
    ' (FX_spot / FX_forward) * (1 + R_domestic * dt) = (1 + [R_foreign + spread] * dt)
    
    ' solve the previous parity formula for spread
    Dim spread As Double
    spread = (((FX_spot / FX_forward) * (1 + TermDepositRate * tenorInYears) - 1) * _
            (1 / tenorInYears)) - BaseDepositRate
    
    If (ConvertToBasisPoints) Then spread = spread * 10000
    GetXCCYBasisSpread = spread * VBA.CDbl(BasisSign)
End Function

Wednesday, November 21, 2018

QuantLib-Python: Builder for Piecewise Term Structure

This post is presenting one possible implementation for Python builder class for constructing QuantLib piecewise yield term structure. The purpose is simple: one can assemble piecewise yield curve by adding arbitrary amount of different quote types and finally request handle for the curve. This curve can then be used in other part of the program. Effectively, this class is wrapping some of the required tedious administrative code work away from a client and also offering a nice compact package for all corresponding purposes. Now, it is not my intention to be object-oriented in Python world just for the sake of being object-oriented, but .. this is just a perfect place for Python class implementation. Corresponding C++ version has been presented in here.

Thanks for reading my blog.
-Mike

from QuantLib import *
import numpy as Numpy

# create piecewise yield term structure
class PiecewiseCurveBuilder:
    def __init__(self, settlementDate, dayCounter):
        self.helpers = []
        self.settlementDate = settlementDate
        self.dayCounter = dayCounter

    # 4th constructor: DepositRateHelper(Rate rate, const shared_ptr<IborIndex> &iborIndex)
    def AddDeposit(self, rate, iborIndex):
        helper = DepositRateHelper(rate, iborIndex)
        self.helpers.append(helper)

    # 4th constructor: FraRateHelper(Rate rate, Natural monthsToStart, const shared_ptr<IborIndex> &iborIndex)
    def AddFRA(self, rate, monthsToStart, iborIndex):
        helper = FraRateHelper(rate, monthsToStart, iborIndex)
        self.helpers.append(helper)
    
    # 6th constructor (Real price, const Date &iborStartDate, const ext::shared_ptr<IborIndex> &iborIndex) 
    def AddFuture(self, price, iborStartDate, iborIndex):
        helper = FuturesRateHelper(price, iborStartDate, iborIndex)
        self.helpers.append(helper)
    
    # 4th constructor: SwapRateHelper(Rate rate, const Period &tenor, const Calendar &calendar, 
    # Frequency fixedFrequency, BusinessDayConvention fixedConvention, const DayCounter &fixedDayCount, 
    # const shared_ptr<IborIndex> &iborIndex)
    def AddSwap(self, rate, periodLength, fixedCalendar, fixedFrequency, fixedConvention, fixedDayCount, floatIndex):
        helper = SwapRateHelper(rate, periodLength, fixedCalendar, fixedFrequency, 
            fixedConvention, fixedDayCount, floatIndex)
        self.helpers.append(helper)
    
    # PiecewiseYieldCurve <ZeroYield, Linear>
    def GetCurveHandle(self):  
        yieldTermStructure = PiecewiseLinearZero(self.settlementDate, self.helpers, self.dayCounter)
        return RelinkableYieldTermStructureHandle(yieldTermStructure)


# general parameters    
tradeDate = Date(4, February, 2008)
calendar = TARGET()
dayCounter = Actual360()
convention = ModifiedFollowing
settlementDate = calendar.advance(tradeDate, Period(2, Days), convention)  
swapIndex = USDLibor(Period(3, Months))
frequency = Annual

# create curve builder object
Settings.instance().evaluationDate = tradeDate
builder = PiecewiseCurveBuilder(settlementDate, dayCounter)

# cash deposit
depos = []
depos.append((0.032175, USDLibor(Period(1, Weeks))))
depos.append((0.0318125, USDLibor(Period(1, Months))))
depos.append((0.03145, USDLibor(Period(3, Months))))
[builder.AddDeposit(d[0], d[1]) for d in depos]

# futures
futures = []
futures.append((97.41, IMM.nextDate(settlementDate + Period(3, Months)), swapIndex))
futures.append((97.52, IMM.nextDate(settlementDate + Period(6, Months)), swapIndex))
futures.append((97.495, IMM.nextDate(settlementDate + Period(9, Months)), swapIndex))
futures.append((97.395, IMM.nextDate(settlementDate + Period(12, Months)), swapIndex))
[builder.AddFuture(f[0], f[1], f[2]) for f in futures]

# swaps
swaps = []
swaps.append((0.02795, Period(2, Years), calendar, frequency, convention, dayCounter, swapIndex))
swaps.append((0.03035, Period(3, Years), calendar, frequency, convention, dayCounter, swapIndex))
swaps.append((0.03275, Period(4, Years), calendar, frequency, convention, dayCounter, swapIndex))
swaps.append((0.03505, Period(5, Years), calendar, frequency, convention, dayCounter, swapIndex))
swaps.append((0.03715, Period(6, Years), calendar, frequency, convention, dayCounter, swapIndex))
swaps.append((0.03885, Period(7, Years), calendar, frequency, convention, dayCounter, swapIndex))
swaps.append((0.04025, Period(8, Years), calendar, frequency, convention, dayCounter, swapIndex))
swaps.append((0.04155, Period(9, Years), calendar, frequency, convention, dayCounter, swapIndex))
swaps.append((0.04265, Period(10, Years), calendar, frequency, convention, dayCounter, swapIndex))
swaps.append((0.04435, Period(12, Years), calendar, frequency, convention, dayCounter, swapIndex))
[builder.AddSwap(s[0], s[1], s[2], s[3], s[4], s[5], s[6]) for s in swaps]

# get relinkable curve handle from builder
curve = builder.GetCurveHandle()
curve.enableExtrapolation()

# create and print array of discount factors for every 3M up to 15Y
times = Numpy.linspace(0.0, 15.0, 61)
dfs = Numpy.array([curve.discount(t) for t in times])
print(dfs)

Sunday, June 3, 2018

QuantLib : Bootstrapping OIS curve

As we all are aware, all collateralized derivative contracts must be valued by using dual-curve discounting, by using separate curves for cash flow projection and cash flow discounting. While this post does not yet explain how to implement such dual-curve disconting scheme in QuantLib, it shows how to use QuantLib for bootstrapping required OIS discount curve from available market rates. Some further reading material, which nicely explains the justification for the use of dual-curve discounting, is available in here.

The program


First, for the sake of brevity, required market data (Eonia rates) has been hard-coded into map container. Then, container will be iterated through for constructing all required rate helpers. After this, piecewise yield term structure will be created by using constructed rate helpers. Finally, an equally-spaced time grid (monthly) will be created and discount factors are requested from bootstrapped Eonia curve for each grid time point (screenshot below). The curve itself (eurOisCurve) could now be used as a discounting curve in a dual-curve valuation scheme in QuantLib.

















Thanks for reading this blog.
-Mike

#include <iostream>
#include <ql\quantlib.hpp>
#include <algorithm>
#include <map>
using namespace QuantLib;

int main() {

 try {
  // create common data 
  Date today(7, Jul, 2017);
  DayCounter dayCounter = Actual360();
  Calendar calendar = TARGET();
  Date settlementDate = calendar.advance(today, Period(2, Days));
  Natural settlementDays = settlementDate - today;
  Settings::instance().evaluationDate() = today;

  // create overnight index for euro currency
  auto eoniaIndex = boost::make_shared<Eonia>();

  // create container for rate helpers
  std::vector<boost::shared_ptr<RateHelper>> rateHelpers;

  // create first 1d cash instrument for eonia curve - using deposit rate helper
  auto Q1D = boost::make_shared<SimpleQuote>(-0.0036);
  rateHelpers.push_back(boost::make_shared<DepositRateHelper>
   (Handle<Quote>(Q1D), Period(1, Days), eoniaIndex->fixingDays(),
   eoniaIndex->fixingCalendar(), eoniaIndex->businessDayConvention(), 
   eoniaIndex->endOfMonth(), eoniaIndex->dayCounter()));

  // create source data for eonia curve (period, rate)
  std::map<Period, Real> eoniaCurveData;
  eoniaCurveData.insert(std::make_pair(Period(1, Weeks), -0.00358));
  eoniaCurveData.insert(std::make_pair(Period(2, Weeks), -0.00357));
  eoniaCurveData.insert(std::make_pair(Period(3, Weeks), -0.00356));
  eoniaCurveData.insert(std::make_pair(Period(1, Months), -0.00358));
  eoniaCurveData.insert(std::make_pair(Period(2, Months), -0.00358));
  eoniaCurveData.insert(std::make_pair(Period(3, Months), -0.00358));
  eoniaCurveData.insert(std::make_pair(Period(4, Months), -0.00357));
  eoniaCurveData.insert(std::make_pair(Period(5, Months), -0.00356));
  eoniaCurveData.insert(std::make_pair(Period(6, Months), -0.00353));
  eoniaCurveData.insert(std::make_pair(Period(7, Months), -0.00351));
  eoniaCurveData.insert(std::make_pair(Period(8, Months), -0.00349));
  eoniaCurveData.insert(std::make_pair(Period(9, Months), -0.00344));
  eoniaCurveData.insert(std::make_pair(Period(10, Months), -0.0034));
  eoniaCurveData.insert(std::make_pair(Period(11, Months), -0.00336));
  eoniaCurveData.insert(std::make_pair(Period(1, Years), -0.00331));
  eoniaCurveData.insert(std::make_pair(Period(15, Months), -0.00314));
  eoniaCurveData.insert(std::make_pair(Period(18, Months), -0.00295));
  eoniaCurveData.insert(std::make_pair(Period(21, Months), -0.00273));
  eoniaCurveData.insert(std::make_pair(Period(2, Years), -0.00248));
  eoniaCurveData.insert(std::make_pair(Period(3, Years), -0.00138));
  eoniaCurveData.insert(std::make_pair(Period(4, Years), -0.0001245));
  eoniaCurveData.insert(std::make_pair(Period(5, Years), 0.0011945));
  eoniaCurveData.insert(std::make_pair(Period(6, Years), 0.00254));
  eoniaCurveData.insert(std::make_pair(Period(7, Years), 0.00387));
  eoniaCurveData.insert(std::make_pair(Period(8, Years), 0.0052));
  eoniaCurveData.insert(std::make_pair(Period(9, Years), 0.006474));
  eoniaCurveData.insert(std::make_pair(Period(10, Years), 0.007634));
  eoniaCurveData.insert(std::make_pair(Period(11, Years), 0.00868));
  eoniaCurveData.insert(std::make_pair(Period(12, Years), 0.0096045));
  eoniaCurveData.insert(std::make_pair(Period(15, Years), 0.0117245));
  eoniaCurveData.insert(std::make_pair(Period(17, Years), 0.0126797));
  eoniaCurveData.insert(std::make_pair(Period(20, Years), 0.0136245));
  eoniaCurveData.insert(std::make_pair(Period(25, Years), 0.01441));
  eoniaCurveData.insert(std::make_pair(Period(30, Years), 0.01479));

  // create other instruments for eonia curve - using ois rate helper
  std::for_each(eoniaCurveData.begin(), eoniaCurveData.end(),
   [settlementDays, &rateHelpers, &eoniaIndex](std::pair<Period, Real> p) -> void 
   { rateHelpers.push_back(boost::make_shared<OISRateHelper>(settlementDays,
   p.first, Handle<Quote>(boost::make_shared<SimpleQuote>(p.second)), eoniaIndex)); });

  // create piecewise term structure
  Handle<YieldTermStructure> eurOisCurve(boost::make_shared<PiecewiseYieldCurve<Discount, LogLinear>>
   (settlementDays, eoniaIndex->fixingCalendar(), rateHelpers, eoniaIndex->dayCounter()));

  // create equally-spaced (monthly) time grid
  Time maturity = 30.0;
  Size steps = 360;
  TimeGrid grid(maturity, steps);

  // print monthly discount factors
  std::for_each(grid.begin(), grid.end(), [&eurOisCurve](Time t) -> void
   { std::cout << eurOisCurve->discount(t) << std::endl; });

 }
 catch (std::exception& e) {
  std::cout << e.what() << std::endl;
 }
 return 0;
}

Thursday, March 1, 2018

PowerShell : scenario generator

There are always some game-changing moments in life. Earlier this year, I finally discovered the greatness of PowerShell. Love at first sight. As a decent "honeymoon project", I re-implemented one of my C# programs to PowerShell. This post is opening this particular program, which takes base market data as input and creates stress market scenarios (for a third-party analytics software), based on given XML configuration files.

It should be noted, that this particular program uses CSV file input for constructing base market data. However, since PowerShell does enjoy the benefits of accessing .NET framework classes, one may request source data directly from database - if so desired.

Program configurations


The following screenshot shows general configurations for this program. SourceFilePath attribute captures the source market data CSV file and TargetFolderPath captures the folder, into which all market scenario files will be created. Finally, ScenarioConfigurationsPath captures the folder, which contains all XML scenario configuration files. This configuration XML file should be stored in a chosen directory.

<Configurations>
    <!-- attributes for scenario generator settings -->
    <ScenarioConfigurationsPath>\\Temp\ScenarioConfigurations\</ScenarioConfigurationsPath>
    <SourceFilePath>\\Temp\baseMarket.csv</SourceFilePath>
    <TargetFolderPath>\\Temp\Scenarios\</TargetFolderPath>
</Configurations>

Market data


The following screenshot shows base market data. Due to brevity reasons, only USD swap curve has been used here as an example. All risk factors (market data points) are effectively given as key-value pairs (ticker, value). As an example, USD.IBOR.SWAP.3M.10Y.MID is a 10-year mid-market quote for USD-nominated swap, where floating rate index (USD Libor) is fixed and paid on a quarterly basis.

It should be noted, that the system for risk factor tickers construction should always lead to a scheme, in which every risk factor will have one and only one unique ticker. This will then guarantee, that we can drill down and stress individual risk factor with regex expressions, if so desired. This data should be copied to CSV file (directory has been defined in previous configuration file).

"USD.IBOR.CASH.3M.1BD.MID,0.01445"
"USD.IBOR.CASH.3M.1W.MID,0.0147313"
"USD.IBOR.CASH.3M.1M.MID,0.0159563"
"USD.IBOR.CASH.3M.2M.MID,0.0173311"
"USD.IBOR.CASH.3M.3M.MID,0.0189213"
"USD.IBOR.FRA.3M.3M.6M.MID,0.02283"
"USD.IBOR.FRA.3M.6M.9M.MID,0.02363"
"USD.IBOR.FRA.3M.9M.12M.MID,0.02503"
"USD.IBOR.SWAP.3M.2Y.MID,0.025387"
"USD.IBOR.SWAP.3M.3Y.MID,0.026844"
"USD.IBOR.SWAP.3M.4Y.MID,0.027649"
"USD.IBOR.SWAP.3M.5Y.MID,0.028163"
"USD.IBOR.SWAP.3M.6Y.MID,0.028579"
"USD.IBOR.SWAP.3M.7Y.MID,0.028935"
"USD.IBOR.SWAP.3M.8Y.MID,0.029236"
"USD.IBOR.SWAP.3M.9Y.MID,0.029516"
"USD.IBOR.SWAP.3M.10Y.MID,0.029758"
"USD.IBOR.SWAP.3M.11Y.MID,0.030049"
"USD.IBOR.SWAP.3M.12Y.MID,0.030192"
"USD.IBOR.SWAP.3M.15Y.MID,0.030526"
"USD.IBOR.SWAP.3M.20Y.MID,0.030775"

Risk factor class


A simple C# class is used to host one risk factor (ticker, value). It should be noted at this point, that this particular custom C# class will be used within our PowerShell script.

using System;
public class RiskFactor {
    public string key;
    public double value;
    // instance constructor
    public RiskFactor(string stream) {
        this.key = stream.Split(',')[0];
        this.value = Convert.ToDouble(stream.Split(',')[1]);
    }
    // deep copy constructor
    public RiskFactor(RiskFactor riskFactor) {
        key = riskFactor.key;
        value = riskFactor.value;
    }
    // object to string
    public override string ToString() {
        return String.Concat(key, ',', Convert.ToString(value));
    }
}

Scenario configurations


The following screenshot shows XML configurations for one market scenario. One such scenario can have several different scenario items (Say, stress these rates up, stress those rates down, apply these changes to all FX rates against EUR and set hard-coded values for all CDS curves). From these configurations, ID and description are self-explainable. Attribute regExpression captures all regex expressions (scenario items), which will be searched from risk factor tickers. As soon as regex match is found, the program will use corresponding operationType attribute to identify desired stress operation (addition, multiplication or hard-coded value). Finally, the amount of change which will be applied in risk factor value is defined within stressValue attribute. This XML configuration should be stored (directory has been defined in program configuration file).

<!-- operation types : 0 = ADDITION, 1 = MULTIPLICATION, 2 = HARD-CODED VALUE -->
<Scenario>
  <ID>CURVE.STRESS.CUSTOM.1</ID>
  <description>custom stress scenario for USD swap curve</description>
  <regExpression>^USD\.*.*CASH,^USD\.*.*FRA,^USD\.*.*SWAP</regExpression>
  <operationType>1,0,2</operationType>
  <stressValue>1.25,0.015,0.05</stressValue>
</Scenario>

Finally, the following screenshot shows resulting market data, when all configured scenario items have been applied. This is the content of output CSV file, created by PowerShell program.

"USD.IBOR.CASH.3M.1BD.MID,0.0180625"
"USD.IBOR.CASH.3M.1W.MID,0.018414125"
"USD.IBOR.CASH.3M.1M.MID,0.019945375"
"USD.IBOR.CASH.3M.2M.MID,0.021663875"
"USD.IBOR.CASH.3M.3M.MID,0.023651625"
"USD.IBOR.FRA.3M.3M.6M.MID,0.03783"
"USD.IBOR.FRA.3M.6M.9M.MID,0.03863"
"USD.IBOR.FRA.3M.9M.12M.MID,0.04003"
"USD.IBOR.SWAP.3M.2Y.MID,0.05"
"USD.IBOR.SWAP.3M.3Y.MID,0.05"
"USD.IBOR.SWAP.3M.4Y.MID,0.05"
"USD.IBOR.SWAP.3M.5Y.MID,0.05"
"USD.IBOR.SWAP.3M.6Y.MID,0.05"
"USD.IBOR.SWAP.3M.7Y.MID,0.05"
"USD.IBOR.SWAP.3M.8Y.MID,0.05"
"USD.IBOR.SWAP.3M.9Y.MID,0.05"
"USD.IBOR.SWAP.3M.10Y.MID,0.05"
"USD.IBOR.SWAP.3M.11Y.MID,0.05"
"USD.IBOR.SWAP.3M.12Y.MID,0.05"
"USD.IBOR.SWAP.3M.15Y.MID,0.05"
"USD.IBOR.SWAP.3M.20Y.MID,0.05"

Program


# risk factor class
Add-Type @"
using System;
public class RiskFactor {
    public string key;
    public double value;
    // instance constructor
    public RiskFactor(string stream) {
        this.key = stream.Split(',')[0];
        this.value = Convert.ToDouble(stream.Split(',')[1]);
    }
    // deep copy constructor
    public RiskFactor(RiskFactor riskFactor) {
        key = riskFactor.key;
        value = riskFactor.value;
    }
    // object to string
    public override string ToString() {
        return String.Concat(key, ',', Convert.ToString(value));
    }
}
"@


function ProcessScenario([System.Xml.XmlDocument]$scenario, [System.Collections.Generic.List[RiskFactor]]$riskFactors) {
    
    # extract scenario items
    $regExpressions = $scenario.SelectSingleNode("Scenario/regExpression").InnerText.Split(',')
    $operationTypes = $scenario.SelectSingleNode("Scenario/operationType").InnerText.Split(',')
    $stressValues = $scenario.SelectSingleNode("Scenario/stressValue").InnerText.Split(',')
    
    # loop through all scenario regex expression items
    for($i = 0; $i -lt $regExpressions.Count; $i++) {
        # loop through all risk factors
        $riskFactors | ForEach-Object { 
            $match = $_.key -match $regExpressions[$i]
            # conditionally, apply regex expression to risk factor value
            if($match -eq $true) {
                $stressedValue = $_.value
                switch($operationTypes[$i]) {
                    # addition
                    0 { $stressedValue += [double]$stressValues[$i] }
                    # multiplication
                    1 { $stressedValue *= [double]$stressValues[$i] }
                    # hard-coded value
                    2 { $stressedValue = [double]$stressValues[$i] }
                }
                $_.value = $stressedValue
            }
        }
    }
}


function Main() {

    # create program configurations from xml file
    $configurationFilePath = "\\Temp\Configurations.xml"
    $configurations = New-Object System.Xml.XmlDocument
    $configurations.Load($configurationFilePath)

    # extract risk factor objects to master list
    $riskFactors = New-Object System.Collections.Generic.List[RiskFactor]
    Import-Csv -Path $configurations.SelectSingleNode("Configurations/SourceFilePath").InnerText -Header stream | 
        ForEach-Object { $riskFactors.Add((New-Object RiskFactor($_.stream))) }

    # extract all scenario xml configurations to list
    $scenarios = New-Object System.Collections.Generic.List[System.Xml.XmlDocument]
    Get-ChildItem -Path $configurations.SelectSingleNode("Configurations/ScenarioConfigurationsPath").InnerText | 
        ForEach-Object { 
            $scenario = New-Object System.Xml.XmlDocument
            $scenario.Load($_.FullName)
            $scenarios.Add($scenario) 
        }

    # loop through all scenarios xml configurations
    $scenarios | ForEach-Object { 
        # create risk factor list deep copy
        $riskFactorsDeepCopy = New-Object System.Collections.Generic.List[RiskFactor]
        $riskFactors | ForEach-Object { 
            $riskFactorsDeepCopy.Add((New-Object RiskFactor($_))) 
        }
        # apply scenario changes to copied risk factor values
        ProcessScenario $_ $riskFactorsDeepCopy

        # create output directory and remove existing file if exists
        $scenarioFileName = [System.String]::Concat($_.SelectSingleNode("Scenario/ID").InnerText, ".csv")
        $targetFolderPath = $configurations.SelectSingleNode("Configurations/TargetFolderPath").InnerText
        $scenarioFilePathName = [System.IO.Path]::Combine($targetFolderPath, $scenarioFileName)
        Remove-Item -Path $scenarioFilePathName -ErrorAction SilentlyContinue
        
        # create csv output file for processed risk factors
        $riskFactorsDeepCopy | ForEach-Object { New-Object psobject -Property @{string = $_.ToString() } } |
            ConvertTo-Csv -NoTypeInformation | select -Skip 1 | Out-File $scenarioFilePathName
    }
}


# required tag
. Main

Handy way to create and test regex expressions is to use any online tool available. As an example, the first scenario item (^USD\.*.*CASH) has been applied to a given base market data. The last screenshot below shows all regex matches.

Thanks again for reading this blog.
-Mike


Sunday, September 3, 2017

QuantLib : another implementation for piecewise yield curve builder class


Last year I published one possible implementation using Quantlib library for constructing piecewise yield curves. Within this second implementation, I have done a couple of changes in order to increase configurability. For allowing more flexible curve construction algorithm, Traits and Interpolations are now defined as template parameters. Previously, these were hard-coded inside the class. Secondly, all types of quotes for class methods are now wrapped inside shared pointers. Previously, there was an option to give quotes as rates. After some reconsideration, I have decided to give up this option completely. Finally, I have added a class method for allowing futures prices to be used in curve construction process.


Source data and results


The following source data from the book by Richard Flavell has been used for validation.



















Resulting zero-yield curve and discount factors are presented in the graph below. For this data used, the absolute maximum difference between Flavell-constructed and Quantlib-constructed zero yield curves is around one basis point.

















The program


// PiecewiseCurveBuilder.h
#pragma once
#include <ql/quantlib.hpp>
//
namespace MJPiecewiseCurveBuilderNamespace
{
 using namespace QuantLib;
 //
 // type alias definitions
 using pQuote = boost::shared_ptr<Quote>;
 using pIndex = boost::shared_ptr<IborIndex>;
 using pHelper = boost::shared_ptr<RateHelper>;
 //
 // T = traits, I = interpolation
 template<typename T, typename I>
 class PiecewiseCurveBuilder
 {
 public:
  PiecewiseCurveBuilder();
  void AddDeposit(const pQuote& quote, const pIndex& index);
  void AddFRA(const pQuote& quote, const Period& periodLengthToStart, const pIndex& index);
  void AddSwap(const pQuote& quote, const Period& periodLength, const Calendar& fixedCalendar, Frequency fixedFrequency,
   BusinessDayConvention fixedConvention, const DayCounter& fixedDayCount, const pIndex& floatIndex);
  //
  void AddFuture(const pQuote& quote, const Date& IMMDate, int lengthInMonths, const Calendar& calendar,
   BusinessDayConvention convention, const DayCounter& dayCounter, bool endOfMonth = true);
  //
  RelinkableHandle<YieldTermStructure> GetCurveHandle(const Date& settlementDate, const DayCounter& dayCounter);
 private:
  std::vector<pHelper> rateHelpers;

 };
}
//
//
//
//
// PiecewiseCurveBuilder.cpp
#pragma once
#include "PiecewiseCurveBuilder.h"
//
namespace MJPiecewiseCurveBuilderNamespace
{
 template<typename T, typename I>
 PiecewiseCurveBuilder<T, I>::PiecewiseCurveBuilder() { }
 //
 template<typename T, typename I>
 void PiecewiseCurveBuilder<T, I>::AddDeposit(const pQuote& quote, const pIndex& index)
 {
  pHelper rateHelper(new DepositRateHelper(Handle<Quote>(quote), index));
  rateHelpers.push_back(rateHelper);
 }
 //
 template<typename T, typename I>
 void PiecewiseCurveBuilder<T, I>::AddFRA(const pQuote& quote, const Period& periodLengthToStart, const pIndex& index)
 {
  pHelper rateHelper(new FraRateHelper(Handle<Quote>(quote),
   periodLengthToStart, pIndex(index)));
  rateHelpers.push_back(rateHelper);
 }
 //
 template<typename T, typename I>
 void PiecewiseCurveBuilder<T, I>::AddSwap(const pQuote& quote, const Period& periodLength, const Calendar& fixedCalendar,
  Frequency fixedFrequency, BusinessDayConvention fixedConvention, const DayCounter& fixedDayCount,
  const pIndex& floatIndex)
 {
  pHelper rateHelper(new SwapRateHelper(Handle<Quote>(quote), periodLength, fixedCalendar, fixedFrequency,
   fixedConvention, fixedDayCount, floatIndex));
  rateHelpers.push_back(rateHelper);
 }
 //
 template<typename T, typename I>
 void PiecewiseCurveBuilder<T, I>::AddFuture(const pQuote& quote, const Date& IMMDate, int lengthInMonths, const Calendar& calendar,
  BusinessDayConvention convention, const DayCounter& dayCounter, bool endOfMonth)
 {
  pHelper rateHelper(new FuturesRateHelper(Handle<Quote>(quote), IMMDate, lengthInMonths, calendar, convention, endOfMonth, dayCounter));
  rateHelpers.push_back(rateHelper);
 }
 //
 template<typename T, typename I>
 RelinkableHandle<YieldTermStructure> PiecewiseCurveBuilder<T, I>::GetCurveHandle(const Date& settlementDate, const DayCounter& dayCounter)
 {
  // T = traits, I = interpolation
  boost::shared_ptr<YieldTermStructure> yieldTermStructure(new PiecewiseYieldCurve<T, I>(settlementDate, rateHelpers, dayCounter));
  return RelinkableHandle<YieldTermStructure>(yieldTermStructure);
 }
}
//
//
//
//
// Tester.cpp
#include "PiecewiseCurveBuilder.cpp"
#include <iostream>
using namespace MJPiecewiseCurveBuilderNamespace;
//
int main()
{
 try
 {
  Date tradeDate(4, February, 2008);
  Settings::instance().evaluationDate() = tradeDate;
  Calendar calendar = TARGET();
  Date settlementDate = calendar.advance(tradeDate, Period(2, Days), ModifiedFollowing);
  DayCounter curveDaycounter = Actual360();
  PiecewiseCurveBuilder<ZeroYield, Linear> builder;
  //
  //
  // cash part of the curve
  pQuote q_1W(new SimpleQuote(0.032175));
  pIndex i_1W(new USDLibor(Period(1, Weeks)));
  builder.AddDeposit(q_1W, i_1W);
  //
  pQuote q_1M(new SimpleQuote(0.0318125));
  pIndex i_1M(new USDLibor(Period(1, Months)));
  builder.AddDeposit(q_1M, i_1M);
  //
  pQuote q_3M(new SimpleQuote(0.03145));
  pIndex i_3M(new USDLibor(Period(3, Months)));
  builder.AddDeposit(q_3M, i_3M);
  //
  //
  // futures part of the curve
  Date IMMDate;
  pQuote q_JUN08(new SimpleQuote(97.41));
  IMMDate = IMM::nextDate(settlementDate + Period(4, Months));
  builder.AddFuture(q_JUN08, IMMDate, 3, calendar, ModifiedFollowing, Actual360());
  //
  pQuote q_SEP08(new SimpleQuote(97.52));
  IMMDate = IMM::nextDate(settlementDate + Period(7, Months));
  builder.AddFuture(q_SEP08, IMMDate, 3, calendar, ModifiedFollowing, Actual360());
  //
  pQuote q_DEC08(new SimpleQuote(97.495));
  IMMDate = IMM::nextDate(settlementDate + Period(10, Months));
  builder.AddFuture(q_DEC08, IMMDate, 3, calendar, ModifiedFollowing, Actual360());
  //
  pQuote q_MAR09(new SimpleQuote(97.395));
  IMMDate = IMM::nextDate(settlementDate + Period(13, Months));
  builder.AddFuture(q_MAR09, IMMDate, 3, calendar, ModifiedFollowing, Actual360());
  //
  //
  // swap part of the curve
  pIndex swapFloatIndex(new USDLibor(Period(3, Months)));
  pQuote q_2Y(new SimpleQuote(0.02795));
  builder.AddSwap(q_2Y, Period(2, Years), calendar, Annual,
   ModifiedFollowing, Actual360(), swapFloatIndex);
  //
  pQuote q_3Y(new SimpleQuote(0.03035));
  builder.AddSwap(q_3Y, Period(3, Years), calendar, Annual,
   ModifiedFollowing, Actual360(), swapFloatIndex);
  //
  pQuote q_4Y(new SimpleQuote(0.03275));
  builder.AddSwap(q_4Y, Period(4, Years), calendar, Annual,
   ModifiedFollowing, Actual360(), swapFloatIndex);
  //
  pQuote q_5Y(new SimpleQuote(0.03505));
  builder.AddSwap(q_5Y, Period(5, Years), calendar, Annual,
   ModifiedFollowing, Actual360(), swapFloatIndex);
  //
  pQuote q_6Y(new SimpleQuote(0.03715));
  builder.AddSwap(q_6Y, Period(6, Years), calendar, Annual,
   ModifiedFollowing, Actual360(), swapFloatIndex);
  //
  pQuote q_7Y(new SimpleQuote(0.03885));
  builder.AddSwap(q_7Y, Period(7, Years), calendar, Annual,
   ModifiedFollowing, Actual360(), swapFloatIndex);
  //
  pQuote q_8Y(new SimpleQuote(0.04025));
  builder.AddSwap(q_8Y, Period(8, Years), calendar, Annual,
   ModifiedFollowing, Actual360(), swapFloatIndex);
  //
  pQuote q_9Y(new SimpleQuote(0.04155));
  builder.AddSwap(q_9Y, Period(9, Years), calendar, Annual,
   ModifiedFollowing, Actual360(), swapFloatIndex);
  //
  pQuote q_10Y(new SimpleQuote(0.04265));
  builder.AddSwap(q_10Y, Period(10, Years), calendar, Annual,
   ModifiedFollowing, Actual360(), swapFloatIndex);
  //
  pQuote q_12Y(new SimpleQuote(0.04435));
  builder.AddSwap(q_12Y, Period(12, Years), calendar, Annual,
   ModifiedFollowing, Actual360(), swapFloatIndex);
  //
  //
  // get curve handle and print out discount factors
  RelinkableHandle<YieldTermStructure> curve = builder.GetCurveHandle(settlementDate, curveDaycounter);
  std::cout << curve->discount(Date(11, February, 2008)) << std::endl;
  std::cout << curve->discount(Date(4, March, 2008)) << std::endl;
  std::cout << curve->discount(Date(4, May, 2008)) << std::endl;
  std::cout << curve->discount(Date(6, August, 2008)) << std::endl;
  std::cout << curve->discount(Date(6, November, 2008)) << std::endl;
  std::cout << curve->discount(Date(6, February, 2009)) << std::endl;
  std::cout << curve->discount(Date(8, February, 2010)) << std::endl;
  std::cout << curve->discount(Date(7, February, 2011)) << std::endl;
  std::cout << curve->discount(Date(6, February, 2012)) << std::endl;
  std::cout << curve->discount(Date(6, February, 2013)) << std::endl;
  std::cout << curve->discount(Date(6, February, 2014)) << std::endl;
  std::cout << curve->discount(Date(6, February, 2015)) << std::endl;
  std::cout << curve->discount(Date(8, February, 2016)) << std::endl;
  std::cout << curve->discount(Date(6, February, 2017)) << std::endl;
  std::cout << curve->discount(Date(6, February, 2018)) << std::endl;
 }
 catch (std::exception& e)
 {
  std::cout << e.what() << std::endl;
 }
 return 0;
}


Data updating


Since all rates have been wrapped inside quotes (which have been wrapped inside handles), those can be accessed only by using dynamic pointer casting. Below is an example for updating 3M cash quote.

boost::dynamic_pointer_cast<SimpleQuote>(q_3M)->setValue(0.03395);

After this quote updating shown above, a new requested value (zero rate, forward rate or discount factor) from constructed curve object will be re-calculated by using updated quote.

For those readers who are completely unfamiliar with this stuff presented, there are three extremely well-written slides available in Quantlib documentation page, written by Dimitri Reiswich. These slides are offering excellent way to get very practical hands-on overview on QuantLib library. Also, Luigi Ballabio has finally been finishing his book on QuantLib implementation, which can be purchased from Leanpub. This book is offering deep diving experience into the abyss of Quantlib architechture.

Finally, as usual, thanks for spending your precious time here and reading this blog.
-Mike