Logo

SQL Server CMTCurve Function

Updated 2024-02-14 19:56:24.683000

Description

Use the table-valued function CMTCurve to generate a yield curve using Constant Maturity Treasury (CMT) rates or other similar rate types.

CMTCurve expects the rates to be supplied using dynamic SQL in which the resultant table consists of the time (in years) and the rate (where 10% = 0.10).

CMTCurve supports annual (1) and semi-annual (2) compounding. It assumes that all supplied rates which have a time value less than or equal to 1 / compounding frequency are cash rates which can be directly converted into discount factors. For all other rates, a bootstrapping processing is used to calculate the discount factors. Bootstrapped rates are identified as such in the table returned by the function. CMTCurve supports linear and spline interpolation for all the coupons. If we were to generate a lower triangular matrix containing the (calculated) cash flows for each point on the yield curve, then the calculation of the discount factors is straightforward forward substitution for each of the interpolated points.

For example, given the following annual rates:

TR
1.035
2.042
3.047
4.052

We are able to directly calculate the discount factors for each point by setting up the following equation:

\begin{pmatrix}100\\100\\100\\100\end{pmatrix}=\begin{pmatrix}103.5&&&\\4.2&104.2&&\\4.7&4.7&104.7&\\5.2&5.2&5.2&105.2\end{pmatrix}\times\begin{pmatrix}P(0,1)\\P(0,2)\\P(0,3)\\P(0,4)\end{pmatrix}

Which can be solved directly in SQL Server using the existing XLeratorDB math functions MATRIX2STRING_q, MUPDATE, EYE, FWDSUB, MTRIL and Matrix.

--Put the par rates into matrix format
DECLARE @cf as varchar(max) = wct.Matrix2String_q('
   SELECT r
   FROM (VALUES (1,.035),(2,.042),(3,.047),(4,.052))n(T,r)
   ORDER BY T'
   );
 
--Generate the cash flows in matrix format
SET @cf = wct.MUPDATE(100,NULL,NULL,NULL,NULL,'*',wct.MUPDATE(@cf,NULL,NULL,NULL,NULL,'+',wct.EYE(4,4),NULL,NULL,NULL,NULL),NULL,NULL,NULL,NULL);
 
--Solve by forward substitution
SELECT
    rownum + 1 as T
   ,ItemValue as df
FROM
   wct.Matrix(wct.FWDSUB(wct.MTRIL(@cf),'100;100;100;100'));

This produces the following result.

 T           df
 ----------- ----------------------
 1           0.966183574879227
 2           0.920748838632507
 3           0.870405135210075
 4           0.814276090747591

CMTCURVE simplifies the SQL into a single, easy-to-use function call.

Syntax

SELECT * FROM [westclintech].[wct].[CMTCurve](
  <@Curve, nvarchar(max),>
 ,<@InterpMethod, nvarchar(4000),>
 ,<@Freq, int,>)

Arguments

@Curve

A SELECT statement which return a resultant consisting of the time (in years) and the associated rates (where 10% = 0.10). @Curve should return 2 columns.

@InterpMethod

Identifies the interpolation method used in the bootstrapping process. Use 'S' for spline interpolation, 'L0' for linear interpolation where bounds are used for any values that would otherwise be out-of-bonds, or 'L1' for linear interpolation with extrapolation.

@Freq

Use 1 for annual or 2 for semi-annual. @Freq is an expression of type int or of a type that can be implicitly converted to int.

Return Type

table

colNamecolDatatypecolDesc
TfloatTime (in years) associated with the returned rate
rfloatPar rate
dffloatDiscount factor
spotfloatSpot rate; @Freq*(POWER(1/df,1/(@Freq*T))-1
cczerofloatContinuously compounded zero rate; -LOG(df)/T
bootstrapfloatidentifies T and r as having come from @Curve (1) or having been interpolated (0)

Remarks

If @Curve returns less than 2 columns an error is generated.

NULL values returned by @Curve are discarded.

Only 1 r value should be returned for each T in @Curve.

If @InterpMethod is NULL then @InterpMethod = 'S'.

If @Freq is NULL then @Freq = 2.

Examples

Example #1

Using the data from the introduction.

SELECT *
  FROM wct.CMTCurve(
           '
       SELECT T, r
       FROM (VALUES (1, .035), (2, .042), (3, .047), (4, .052))n(T, r)
       ORDER BY T'                                                                                                                   ,
           --@Curve
           'L',--@InterpMethod
           1 --@Freq
    );

This produces the following result.

Trdfspotcczerobootstrap
10.0350.9661835748792270.0350.03440142671733230
20.0420.9207488386325070.04214802573956370.0412839924927360
30.0470.8704051352100750.04735244719241050.04626550102337040
40.0520.8142760907475910.05270595397335340.05136394816619940

Example #2

In this example we put the curve into a temp table, #cmt, and have the dynamic SQL select from #cmt.

SELECT T,
       r / 100 as R
INTO   #cmt
  FROM (   VALUES (0.0833333333333333, 0.44),
                  (0.25, 0.51),
                  (0.5, 0.62),
                  (1, 0.85),
                  (2, 1.2),
                  (3, 1.47),
                  (5, 1.93),
                  (7, 2.25),
                  (10, 2.45),
                  (20, 2.79),
                  (30, 3.06)) n (T, r);
SELECT *
  FROM wct.CMTCurve('
       SELECT T,R FROM #cmt,',  --@Curve,
        NULL,                   --@InterpMethod
        NULL                    --@Freq
    );

This produces the following result.

Trdfspotcczerobootstrap
0.08333333333333330.00440.99963346772850.00440.004399193530463810
0.250.00510.9987266235549680.00510.005096751510921990
0.50.00620.9969095803010670.00620.006190409814605070
10.00850.9915490508177450.0085048931211360.008486860921953070
1.50.01032384352482820.9846530302132830.01033725866987820.01031063563766241
20.0120.9763035089781390.0120269015690320.01199088429862310
2.50.01342846942551530.9669901010649480.01347197895248440.01342680813158611
30.01470.9568317082001220.01476347553871850.01470925216453930
3.50.01592669199121660.9456984577905410.01601564657583010.01595186163477471
40.01711530574484720.9336560135017560.01723564353328210.01716180062137091
4.50.01824626662605430.9208708471694930.01840314951013110.01831899636587891
50.01930.9075432960173740.01949722324136440.0194028009742240
5.50.02025925676737140.8938931681818170.02049870226748850.02039436538676651
60.02111608997095460.8801132721305190.021397766401650.02128411024387381
6.50.02186487818906050.8663902637263450.02218674817487590.02206458784279041
70.02250.8529139422956260.02285772135931640.02272808926597440
7.50.0230199592751370.839848250041370.02340712166989390.02327120775699471
80.02343976105804740.8272325874167360.02384975486190340.02370867264472231
8.50.023778535685360.8150452900093420.02420549310345480.0240601878496061
90.02405541349370370.8032306504612920.02449471421684810.02434593003754311
9.50.02428952481970740.7917006418941070.02473826499566020.02458651958164291
100.02450.7803365499258160.02495743273223330.02480299780928860
10.50.02470280604228340.7690197475036370.02517027208756280.02501320289025141
110.02490125663855220.757729948048720.0253804398905650.02522074780760721
11.50.0250955021518740.7464735248082330.02558796647593380.02542566333864331
120.02528569294531640.7352563949288090.02579290252979670.02562800033006831
12.50.02547197938194720.7240840314958050.02599531557459710.02582782621836571
130.02565451182483380.7129614757751080.02619528731238670.02602522239988721
13.50.02583344063704410.7018933496153810.02639291160495820.02622028223131131
140.02600891618164550.6908838679714920.02658829293277790.02641310950398891
14.50.02618108882170590.6799368515126660.02678154521850470.02660381727908941
150.02635010892029270.669055739281590.02697279093158220.0267925270008541
15.50.02651612684047370.6582436013733480.02716216041220590.02697936782685921
160.02667929294531640.6475031516055630.02734979136866980.02716447612973691
16.50.02683975759788860.6368367601535970.0275358285135430.02734799513613411
170.02699767116125790.6262464661269890.02772042331258050.02753007467705761
17.50.02715318399849190.6157339900655850.02790373382658550.02771087103000111
180.02730644647265820.6053007463359450.02808592463121640.02789054683797391
18.50.02745760894682460.5949478554106870.02826716680338090.02806927109417141
190.02760682178405850.5846761560154010.02844763796571390.02824721918383231
19.50.02775423534742780.5744862171296070.02862752238285090.02842457297704091
200.02790.5643783498299980.02880701110502450.02860152096800340
20.50.02804425353222920.5543528159927670.02898628456722550.02877824112031761
210.02818708344411550.5444104550221910.02916545873947920.02895484779616771
21.50.02832856466304530.5345521380665690.0293446363046970.02913144222233681
220.02846877211640510.5247785814455060.02952392418946290.02930812977179591
22.50.02860778073158130.5150903527568350.02970343366862550.02948502006490271
230.02874566543596050.5054878769015160.02988328049542370.02966222709564631
23.50.0288825011569290.4959714420223160.03006358505646970.02983986938224321
240.02901836282187340.4865412053525420.03024447255136260.03001807014183691
24.50.02915332535818020.477197198971560.03042607319710640.03019695748945011
250.02928746369323580.4679393354643220.03060852245789380.03037666466171121
25.50.02942085275442660.4587674134824820.0307919613011660.03055733026622921
260.02955356746913920.449681123205140.03097653648122780.03073909855783881
26.50.02968568276476010.4406800516975860.03116240085203790.03092211974328561
270.02981727356867560.4317636881667880.03134971371117330.03110655031627651
27.50.02994841480827240.4229314291126840.0315386411773440.03129255342520231
280.03007918141093670.4141825833746670.03172935660425890.03148029927623581
28.50.03020964830405520.405516377072910.0319220410340790.03166996557495041
290.03033989041501420.3969319584444670.03211688369419720.03186173801008341
29.50.03046998267120040.3884284025743230.03231408254164060.03205581078360231
300.03060.3800047160217990.03251384485999240.03225238719183450

Example #3

Using the same data from Example #2, but this time only selecting the rows where bootstrap is false.

SELECT T,
       r / 100 as R
INTO   #cmt
  FROM (   VALUES (0.0833333333333333, 0.44),
                  (0.25, 0.51),
                  (0.5, 0.62),
                  (1, 0.85),
                  (2, 1.2),
                  (3, 1.47),
                  (5, 1.93),
                  (7, 2.25),
                  (10, 2.45),
                  (20, 2.79),
                  (30, 3.06)) n (T, r);
SELECT *
  FROM wct.CMTCurve('
       SELECT T,R FROM #cmt',   --@Curve
        NULL,                   --@InterpMethod
        NULL                    --@Freq
    )
 WHERE bootstrap = 'False';

This produces the following result.

Trdfspotcczerobootstrap
0.08333333333333330.00440.99963346772850.00440.004399193530463810
0.250.00510.9987266235549680.00510.005096751510921990
0.50.00620.9969095803010670.00620.006190409814605070
10.00850.9915490508177450.0085048931211360.008486860921953070
20.0120.9763035089781390.0120269015690320.01199088429862310
30.01470.9568317082001220.01476347553871850.01470925216453930
50.01930.9075432960173740.01949722324136440.0194028009742240
70.02250.8529139422956260.02285772135931640.02272808926597440
100.02450.7803365499258160.02495743273223330.02480299780928860
200.02790.5643783498299980.02880701110502450.02860152096800340
300.03060.3800047160217990.03251384485999240.03225238719183450

Example #4

In this example we use the same data as in the previous 2 example, but no we are going to shift the par curve up and down by 25 basis points and then return the continuously compounded zeroes for par rates and the shifted rates.

SELECT T,
       r / 100 as R
INTO   #cmt
  FROM (   VALUES (0.0833333333333333, 0.44),
                  (0.25, 0.51),
                  (0.5, 0.62),
                  (1, 0.85),
                  (2, 1.2),
                  (3, 1.47),
                  (5, 1.93),
                  (7, 2.25),
                  (10, 2.45),
                  (20, 2.79),
                  (30, 3.06)) n (T, r);

SELECT x.Ztype,
       k.T,
       k.cczero
INTO   #z
  FROM (   VALUES ('Z', 'SELECT T,r FROM #cmt'),
                  ('Zplus', 'SELECT T,r+.0025 FROM #cmt'),
                  ('Zminus', 'SELECT T,r-.0025 FROM #cmt')) x (Ztype, ZSQL)
 CROSS APPLY wct.CMTCURVE(x.ZSQL, 'S', 2) k
 WHERE bootstrap = 'False';

SELECT T,
       Z,
       zPlus,
       Zminus
  FROM (SELECT * FROM #z) pvt
  PIVOT (   MAX(cczero)
            for Ztype in (Z, Zplus, Zminus)) d;

This produces the following result.

TZzPlusZminus
0.08333333333333330.004399193530463810.006898017010109110.00189984959920848
0.250.005096751510921990.007592789132321080.00259915536598834
0.50.006190409814605070.008681132196840380.00369658171523514
10.008486860921953070.01097615808638580.00599446159481204
20.01199088429862310.01447998137434820.00949869226116942
30.01470925216453930.01720018503649850.0122152386862146
50.0194028009742240.02190294429063360.0168996544427597
70.02272808926597440.02523898548092670.0202143283294154
100.02480299780928860.02731915637106720.0222841402210191
200.02860152096800340.03115594075355930.0260459053777959
300.03225238719183450.03490875417358210.0296005214805165

See Also

BONDPRICEFROMZEROES - Bond pricing from the zero coupon curve

LOGNORMALIRLATTICE - LogNormal Interest Rate Lattice

OAC - Option Adjusted Convexity

OAD - Calculate the option-adjusted duration on a bond.

OAS - Option Adjusted Spread

PRICEFROMIRLATTICE - Bond Pricing using Option Adjusted Spread

PRICEFROMZEROESTVF - Zero Volatility spread details

ZSPREAD - Calculate the zero-volatility or static spread on a bond.