hasquant-0.5.0.2: test/example/QuantLib/Example/Bond.hs
module QuantLib.Example.Bond
(
Result(..)
, run
) where
import Control.Monad((>=>))
import Data.List(zip4)
import Data.Time.Calendar(fromGregorian)
import qualified QuantLib.CashFlow as CF
import QuantLib.InterestRate
import QuantLib.Index
import qualified QuantLib.Index.InterestRate as I
import QuantLib.Instrument
import QuantLib.Instrument.Bond
import QuantLib.Math
import QuantLib.PricingEngine
import QuantLib.Quote
import QuantLib.Settings
import QuantLib.TermStructure.Volatility
import QuantLib.TermStructure.Yield
import QuantLib.Time.Calendar
import QuantLib.Time.Date
import QuantLib.Time.Schedule
data Result = Result
{ npvR :: (Double, Double, Double)
, cleanPriceR :: (Double, Double, Double)
, dirtyPriceR :: (Double, Double, Double)
, accruedAmountR :: (Double, Double, Double)
, previousCoupon :: (Double, Double)
, nextCoupon :: (Double, Double)
, yieldR :: (Double, Double, Double)
, cleanPriceFromYieldR :: Double
, yieldFromCleanPriceR :: Double
, nextCouponDate :: (Day, Day, Day)
, tradable :: (Bool, Bool, Bool)
, cfnpvR :: Double
, cfnpvbpsR :: (Double, Double)
, bpsR :: Double
}
-- This example works with a fixed set of three bonds (and a two-bond subset) all the
-- way through, and every Result field is correspondingly a tuple. Keeping them as
-- tuples rather than lists carries that arity in the types, so there is no list
-- length to re-check: these replace a pair of `error`-throwing listToTuple/listToTriple
-- converters, and with them the `!!` and `fromJust` uses further down.
mapPair :: Applicative f => (a -> f b) -> (a, a) -> f (b, b)
mapPair f (x, y) = (,) <$> f x <*> f y
mapTriple :: Applicative f => (a -> f b) -> (a, a, a) -> f (b, b, b)
mapTriple f (x, y, z) = (,,) <$> f x <*> f y <*> f z
zipTriple :: (a -> b -> c) -> (a, a, a) -> (b, b, b) -> (c, c, c)
zipTriple f (x, y, z) (x', y', z') = (f x x', f y y', f z z')
sequenceTriple :: Applicative f => (f a, f a, f a) -> f (a, a, a)
sequenceTriple (x, y, z) = (,,) <$> x <*> y <*> z
-- |Calendars, day counters, settlement dates and the discount curve, built once
-- and threaded through bond construction and pricing.
data MarketData = MarketData
{ targetCal :: Calendar
, nyseCal :: Calendar
, usGovBondCal :: Calendar
, actual365Fixeddc :: DayCounter
, actActBond :: DayCounter
, actActISDA :: DayCounter
, actual360dc :: DayCounter
, thirty360Europeandc :: DayCounter
, settlDate :: Day
, todaysDate :: Day
, discountCurve :: YieldTermStructure
}
buildMarketData :: IO MarketData
buildMarketData = do
actual365Fixeddc' <- dayCounter Actual365FixedStandard
actActBond' <- dayCounter ActualActualBond
actActISDA' <- dayCounter ActualActualISDA
actual360dc' <- dayCounter (Actual360 False)
thirty360Europeandc' <- dayCounter Thirty360European
targetCal' <- calendar TARGET
nyseCal' <- calendar UnitedStatesNYSE
usGovBondCal' <- calendar UnitedStatesGovernmentBond
settlDate' <- adjust targetCal' (18 `september` 2008) Following
todaysDate' <- advance targetCal'
settlDate'
(-(fromIntegral fixDays), Days)
Following
False
setEvaluationDate (Just todaysDate')
discDepoHelpers <- mapM
(\(q, p) -> do
r <- simpleQuote q
depositRateHelper
r
(p, Months)
fixDays
targetCal'
ModifiedFollowing
True
actual365Fixeddc')
$ zip zcQuotes zcTenors
quotes <- mapM simpleQuote marketQuotes
discBondHelpers <- mapM
(\(q, c, i, m) -> do
s <- schedule i m (6, Months) usGovBondCal' Unadjusted
Unadjusted Backward False Nothing Nothing
fixedRateBondHelper q settlementDays 100.0 s [c]
actActBond' Unadjusted redemption i >>= asRateHelper)
$ zip4 quotes couponRates issueDates maturities
ts <- piecewiseYieldCurve
settlDate'
(discDepoHelpers ++ discBondHelpers)
actActISDA'
[]
Discount
LogLinear
--(Cubic $ NaturalSpline True)
--(LogCubic $ Parabolic False)
--(LogCubic Kruger)
--(Cubic FritschButland)
--Abcd
--df <- discount ts (fromGregorian 2011 08 03) True
return MarketData
{ targetCal = targetCal'
, nyseCal = nyseCal'
, usGovBondCal = usGovBondCal'
, actual365Fixeddc = actual365Fixeddc'
, actActBond = actActBond'
, actActISDA = actActISDA'
, actual360dc = actual360dc'
, thirty360Europeandc = thirty360Europeandc'
, settlDate = settlDate'
, todaysDate = todaysDate'
, discountCurve = ts
}
buildBonds :: MarketData -> IO (Bond, Bond, Bond, PricingEngine)
buildBonds md = do
pricing <- discountingBondEngine (discountCurve md) Nothing
-- Fixed 4.5% US Treasury Note
fixedSchedule <- schedule (Just (15 `may` 2007))
(15 `may` 2017)
(6, Months)
(usGovBondCal md)
Unadjusted
Unadjusted
Backward
False
Nothing
Nothing
fixedBond <- fixedRateBond settlementDays
faceAmount
fixedSchedule
[0.045]
(actActBond md)
ModifiedFollowing
100.0
(Just $ 15 `may` 2007)
(usGovBondCal md)
(0, Days) (usGovBondCal md) Unadjusted False (actActBond md) >>= asBond
zcBond <- zeroCouponBond settlementDays
(usGovBondCal md)
faceAmount
(15 `august` 2013)
Following
116.92
(Just $ 15 `august` 2003)
depoLiborHelpers <-
mapM (\(q, p) ->
do
quote <- simpleQuote q
depositRateHelper quote p fixDays (targetCal md)
ModifiedFollowing
True (actual360dc md)) $
zip liborDepoQuotes liborDepoTerms
eur6M <- I.iborIndex I.Euribor6M Nothing
swapLiborHelpers <-
mapM (\(q, n) ->
do
quote <- simpleQuote q
swapRateHelper' quote (n, Years) (targetCal md) Annual Unadjusted
(thirty360Europeandc md) eur6M Nothing (1, Days) Nothing
Nothing LastRelevantDate Nothing False Nothing Nothing Nothing >>= asRateHelper) $
zip liborSwapQuotes liborSwapTerms
fwdCurve <- piecewiseYieldCurve
(settlDate md)
(depoLiborHelpers ++ swapLiborHelpers)
(actActISDA md)
[]
Discount
LogLinear
usd3m <- I.iborIndex (I.UsdLibor (3, Months)) (Just fwdCurve)
I.asInterestRateIndex usd3m >>= asIndex >>= (\i -> addFixing i (fromGregorian 2008 07 17) 0.0278625 False)
floatSchedule <- schedule (Just $ fromGregorian 2005 10 21)
(fromGregorian 2010 10 21)
(3, Months)
(nyseCal md)
Unadjusted
Unadjusted
Backward
True
Nothing
Nothing
floater <- floatingRateBond settlementDays
faceAmount
floatSchedule
usd3m
(actual360dc md)
ModifiedFollowing
2
[1.0]
[0.001]
[]
[]
True
100.0
(Just $ fromGregorian 2005 10 21)
(0, Days) (nyseCal md) Unadjusted False ModifiedFollowing
volval <- simpleQuote 0
vol <- constantOptionletVolatility'
settlementDays (targetCal md) ModifiedFollowing volval (actual365Fixeddc md) ShiftedLognormal 0.0
cf <- cashFlows floater
CF.blackIborCouponPricer vol CF.Black76 Nothing Nothing >>= CF.setCouponPricer cf
return (fixedBond, zcBond, floater, pricing)
priceBonds :: MarketData -> PricingEngine -> (Bond, Bond, Bond) -> IO Result
priceBonds md pricing allBonds@(fixedBond, _, floater) = do
let twoBonds = (fixedBond, floater)
-- some cash flows smoke check
cfs <- cashFlows fixedBond
cfnpv <- CF.npv cfs (discountCurve md) True (Just $ 1 `may` 2012) (Just $ 3 `may` 2012)
cfnpvbps <- CF.npvbps cfs (discountCurve md) True (1 `may` 2012) (3 `may` 2012)
bbps <- bps fixedBond (discountCurve md) (3 `may` 2012)
bNpv <-
mapTriple (asInstrument >=>
(\y -> setPricingEngine y pricing >> npv y))
allBonds
bCleanPrice <- mapTriple (\b -> cleanPrice b (discountCurve md) (settlDate md)) allBonds
bYield <- mapTriple (\b -> yield b (actual360dc md) Compounded Annual 1e-8 100 (0.05, Clean)) allBonds
bAccruedAmount <- mapTriple (`accruedAmount` settlDate md) allBonds
bPreviousCoupon <- mapPair (`previousCouponRate` todaysDate md) twoBonds
bNextCoupon <- mapPair (`nextCouponRate` todaysDate md) twoBonds
let (_, _, floaterYield) = bYield
(_, _, floaterCleanPrice) = bCleanPrice
fCleanFromYield <- cleanPriceFromYield floater floaterYield (actual360dc md) Compounded Annual (settlDate md)
fYieldFromClean <- yieldFromPrice floater (floaterCleanPrice, Clean) (actual360dc md) Compounded Annual (settlDate md) 1e-8 100
let bDirtyPrice = zipTriple (+) bCleanPrice bAccruedAmount
bNextCouponDate <- mapTriple (`nextCashFlowDate` todaysDate md) allBonds
>>= maybe (fail "a bond has no next cash flow date") pure . sequenceTriple
bTradable <- mapTriple (`isTradable` (10 `february` 2013)) allBonds
return Result {
npvR = bNpv
, cleanPriceR = bCleanPrice
, dirtyPriceR = bDirtyPrice
, accruedAmountR = bAccruedAmount
, previousCoupon = bPreviousCoupon
, nextCoupon = bNextCoupon
, yieldR = bYield
, nextCouponDate = bNextCouponDate
, cleanPriceFromYieldR = fCleanFromYield
, yieldFromCleanPriceR = fYieldFromClean
, tradable = bTradable
, cfnpvR = cfnpv
, cfnpvbpsR = cfnpvbps
, bpsR = bbps
}
run :: IO Result
run = do
md <- buildMarketData
(fixedBond, zcBond, floater, pricing) <- buildBonds md
priceBonds md pricing (fixedBond, zcBond, floater)
zcQuotes :: [Double]
zcQuotes = [0.0096, 0.0145, 0.0194]
zcTenors :: [Int]
zcTenors = [3, 6, 12]
fixDays :: Word
fixDays = 3
settlementDays :: Word
settlementDays = 3
redemption :: Double
redemption = 100.0
faceAmount :: Double
faceAmount = 100.0
issueDates :: [Maybe Day]
issueDates = map Just [
fromGregorian 2005 03 15,
fromGregorian 2005 06 15,
fromGregorian 2006 06 30,
fromGregorian 2002 11 15,
fromGregorian 1987 05 15]
maturities :: [Day]
maturities = [
fromGregorian 2010 08 31,
fromGregorian 2011 08 31,
fromGregorian 2013 08 31,
fromGregorian 2018 08 15,
fromGregorian 2038 05 15]
couponRates :: [Double]
couponRates = [0.02375, 0.04625, 0.03125, 0.04000, 0.04500]
marketQuotes :: [Double]
marketQuotes = [100.390625, 106.21875, 100.59375, 101.6875, 102.140625]
liborDepoQuotes :: [Double]
liborDepoQuotes = [0.043375, 0.031875, 0.0320375,
0.03385, 0.0338125, 0.0335125]
liborDepoTerms :: [(Int, TimeUnit)]
liborDepoTerms = [(1, Weeks), (1, Months), (3, Months),
(6, Months), (9, Months), (1, Years)]
liborSwapQuotes :: [Double]
liborSwapQuotes = [0.0295, 0.0323, 0.0359, 0.0412, 0.0433]
liborSwapTerms :: [Int]
liborSwapTerms = [2, 3, 5, 10, 15]
-- vim: set ft=haskell ff=unix ts=8 sts=2 sw=2 et: