hasquant-0.7.0.0: test/example/QuantLib/Example/FittedBondCurve.hs
{-# LANGUAGE OverloadedLists #-}
module QuantLib.Example.FittedBondCurve
(
Result(..)
, Rate(..)
, run
) where
import Prelude hiding(init, head, tail, last)
import Control.Monad(forM)
import Data.Time.Calendar
import Data.List.NonEmpty(NonEmpty(..), init, head, tail, last, fromList)
import qualified QuantLib.CashFlow as CF
import qualified QuantLib.InterestRate as IR
import QuantLib.Instrument.Bond
import QuantLib.Math
import QuantLib.Quote
import QuantLib.Context
import QuantLib.TermStructure
import qualified QuantLib.TermStructure.Yield as TS
import QuantLib.Time.Calendar
import QuantLib.Time.Date
import QuantLib.Time.Schedule
data Result = Result { bondSettleR :: Day
, rates1R :: Rate
, rates2R :: Rate
, rates3R :: Rate
, rates4R :: Rate
} deriving Show
data Rate = Rate{refDateR :: Day, numIterR :: [Int], tenorsR :: [Double], ratesR :: [[Double]]}
deriving Show
run :: IO Result
run = do
cal <- calendar Null
tod1 <- today
evalDate <- adjust cal tod1 Following
setEvaluationDate $ Just evalDate
dc <- dayCounter Simple
bondSettle <- advance cal evalDate (bondSettleDays, Days) Following False
cleanQuotes <- mapM simpleQuote cleanPrices
(rates1, ts0, instrA, instrB, curves) <- step1 evalDate dc cal bondSettle cleanQuotes
rates2 <- step2 evalDate dc cal ts0 instrA instrB curves
let (iA, iB) = (drop 1 instrA, drop 1 instrB)
newtod <- advance cal evalDate (24, Months) ModifiedFollowing False
setEvaluationDate $ Just newtod
newBondSettle <- advance cal newtod (bondSettleDays, Days) Following False
(rates3, ts00, curves3) <- step3 newtod dc cal newBondSettle iA iB
mapM_ (\(price, q, i) -> do
b <- TS.helperInstrument i
ytm <- yieldFromPriceWithGuess b (price, Clean) dc IR.Compounded Annual newtod 1e-10 100 0.05
ytmRate <- IR.interestRate ytm dc IR.Compounded Annual
dur <- duration b ytmRate CF.Modified newtod
let dp = -dur * price * 5 / 10000
setValue q (price + dp)) $
zip3 (drop 1 cleanPrices) (drop 1 cleanQuotes) iA
rates4 <- rates ts00 dc newBondSettle newtod curves3 iA
return Result{bondSettleR = bondSettle, rates1R = rates1, rates2R = rates2, rates3R = rates3, rates4R = rates4}
where
bondSettleDays = 0
curveSettleDays = 0
cleanPrices = replicate 15 100.0
lengths = [2, 4, 6, 8, 10, 12, 14, 16,
18, 20, 22, 24, 26, 28, 30]
coupons = [0.0200, 0.0225, 0.0250, 0.0275, 0.0300,
0.0325, 0.0350, 0.0375, 0.0400, 0.0425,
0.0450, 0.0475, 0.0500, 0.0525, 0.0550]
tolerance = 1e-10
maxEvals = 5000
parRate :: TS.GenYieldTermStructure y -> NonEmpty Day -> DayCounter -> IO Double
parRate ts ds dc = do
dfs <- mapM (\(d1, d2) -> do
dt <- yearFraction dc d1 d2 Nothing Nothing
df <- TS.discount ts (TS.DatePoint d2) False
return $ df * dt) $
zip (init ds) (tail ds)
df1 <- TS.discount ts (TS.DatePoint (head ds)) False
df2 <- TS.discount ts (TS.DatePoint (last ds)) False
return $ 100.0 * (df1 - df2) / sum dfs
rates :: TS.YieldTermStructure -> DayCounter -> Day -> Day -> [TS.FittedBondDiscountCurve] -> [TS.BondHelper] -> IO Rate
rates ts0 dc bondSettle evalDate curves instrA = do
refDate <- referenceDate ts0
numIter <- forM curves TS.numberOfIterations
r <- forM instrA $
\h -> do
b <- TS.helperInstrument h
leg <- cashFlows b
cfs <- CF.cashFlows leg (Just False) (Just bondSettle)
let (ds, _, _) = unzip3 $ filter (\(_, _, oc) -> not oc) cfs
-- `ds` comes from a filter and can be empty; taking the maximum over the
-- NonEmpty that already includes bondSettle keeps this total, and shares
-- the one value the two parRate calls below both need
cfDates = bondSettle :| ds
m <- yearFraction dc evalDate (maximum cfDates) Nothing Nothing
r1 <- parRate ts0 cfDates dc
r2 <- forM curves $ \curve -> parRate curve cfDates dc
return (m, r1:r2)
let (tenors, rs) = unzip r
return Rate {refDateR = refDate, numIterR = numIter, tenorsR = tenors, ratesR = rs}
step1 :: Day -> DayCounter -> Calendar -> Day -> [SimpleQuote] -> IO (Rate, TS.YieldTermStructure, [TS.BondHelper], [TS.RateHelper], [TS.FittedBondDiscountCurve])
step1 evalDate dc cal bondSettle cleanQuotes = do
helpers <- mapM (\(q, l, c) -> do
mat <- advance cal bondSettle (l, Years) Following False
s <- schedule (Just bondSettle) mat (1, Years) cal
ModifiedFollowing ModifiedFollowing Backward False Nothing Nothing
hA <- TS.fixedRateBondHelper q (fromIntegral bondSettleDays) 100.0 s [c] dc ModifiedFollowing 100.0 Nothing
hB <- TS.fixedRateBondHelper q (fromIntegral bondSettleDays) 100.0 s [c] dc ModifiedFollowing 100.0 Nothing
>>= TS.asRateHelper
return (hA, hB)) $
zip3 cleanQuotes lengths coupons
let (instrA, instrB) = unzip helpers
ts0 <- TS.piecewiseYieldCurve (TS.SettlementDays curveSettleDays cal) (fromList instrB) dc []
(TS.Iterative TS.Discount LogLinear TS.defaultIterativeBootstrapOpts) False
curves <- fitCurves cal dc instrA
rs <- rates ts0 dc bondSettle evalDate curves instrA
return (rs, ts0, instrA, instrB, curves)
step2 :: Day -> DayCounter -> Calendar -> TS.YieldTermStructure -> [TS.BondHelper]
-> [TS.RateHelper] -> [TS.FittedBondDiscountCurve] -> IO Rate
step2 evalDate dc cal ts0 instrA _ curves = do
newtoday <- advance cal evalDate (23, Months) ModifiedFollowing False
setEvaluationDate $ Just newtoday
bondSettle <- advance cal newtoday (bondSettleDays, Days) Following False
rates ts0 dc bondSettle newtoday curves instrA
step3 :: Day -> DayCounter -> Calendar -> Day -> [TS.BondHelper] -> [TS.RateHelper]
-> IO (Rate, TS.YieldTermStructure, [TS.FittedBondDiscountCurve])
step3 evalDate dc cal bondSettle iA iB = do
ts00 <- TS.piecewiseYieldCurve (TS.SettlementDays curveSettleDays cal) (fromList iB) dc []
(TS.Iterative TS.Discount LogLinear TS.defaultIterativeBootstrapOpts) False
curves <- fitCurves cal dc iA
rs <- rates ts00 dc bondSettle evalDate curves iA
return (rs, ts00, curves)
-- The five fitting methods and their fitted curves.
-- Keep the fixture in one place. Results depend on QLC's optimization options.
fitCurves :: Calendar -> DayCounter -> [TS.BondHelper] -> IO [TS.FittedBondDiscountCurve]
fitCurves cal dc instr = mapM
(\f -> TS.fittedBondDiscountCurve (TS.SettlementDays curveSettleDays cal) (fromList instr) dc f tolerance maxEvals [] 1.0 False)
fittings
where
noCutoff = 1.0e6 :: Double -- stands in for QuantLib's QL_MAX_REAL default (effectively "no cutoff")
fittings = [TS.ExponentialSplines True [] [] 0.0 noCutoff 9 Nothing Nothing Nothing,
TS.SimplePolynomial 3 True [] [] 0.0 noCutoff Nothing Nothing,
TS.NelsonSiegel [] [] 0.0 noCutoff Nothing Nothing,
TS.CubicBSplines [-30.0, -20.0, 0.0, 5.0, 10.0, 15.0, 20.0, 25.0, 30.0, 40.0, 50.0] True [] [] 0.0 noCutoff Nothing Nothing,
TS.Svensson [] [] 0.0 noCutoff Nothing Nothing]
-- vim: set ft=haskell ff=unix ts=8 sts=2 sw=2 et: