hasquant-0.5.0.2: test/example/QuantLib/Example/BermudanSwaption.hs
{-# LANGUAGE TupleSections #-}
module QuantLib.Example.BermudanSwaption
(
Result(..)
, run
) where
import Control.Monad((>=>), forM_, mapAndUnzipM)
import Data.List.NonEmpty(fromList)
import qualified QuantLib.CashFlow as CF
import qualified QuantLib.Index.InterestRate as IRI
import QuantLib.InterestRate
import QuantLib.Instrument
import QuantLib.Instrument.Swap
import QuantLib.Instrument.Option
import QuantLib.Math
import QuantLib.PricingEngine
import QuantLib.Quote
import QuantLib.Settings
import QuantLib.Time.Calendar
import QuantLib.Time.Date
import QuantLib.Time.Schedule
import QuantLib.TermStructure.Yield
import qualified QuantLib.Model as Model
data Result = Result
{ g2Vols :: [Double]
, g2Params :: [Double]
, hwVols :: [Double]
, hwParams :: [Double]
, hw2Vols :: [Double]
, hw2Params :: [Double]
, bkVols :: [Double]
, bkParams :: [Double]
, npvAtm :: [Double]
, npvOtm :: [Double]
, npvItm :: [Double]
}
calibrateModel :: Model.CalibratedModel -> [Model.BlackCalibrationHelper] -> IO [Double]
calibrateModel m hs = do
hsh <- mapM Model.asCalibrationHelper hs
Model.calibrate m (map (, 1.0) hsh) (LevenbergMarquardt 1.0e-8 1.0e-8 1.0e-8 False) (EndCriteria 400 100 1.0e-8 1.0e-8 1.0e-8) Nothing []
mapM calibrate hs
calibrate :: Model.BlackCalibrationHelper -> IO Double
calibrate h = do
nPV <- Model.modelValue h
vol <- Model.impliedVolatility h nPV 1.0e-4 1000 0.05 0.50
return $ 100.0 * vol
-- |Builds a short-rate model, attaches a pricing engine to the calibration
-- swaptions, calibrates it and returns the (still-untouched) model alongside
-- its calibrated vols/params. The model itself -- not just its calibration
-- output -- is what the pricing phase needs next, since 'priceSwaption'
-- re-derives its own engines from it.
calibrateShortRateModel
:: IO m -- ^model constructor
-> (m -> [Model.BlackCalibrationHelper] -> IO ()) -- ^attach a pricing engine for calibration
-> (m -> IO Model.CalibratedModel) -- ^narrow to the calibratable interface
-> [Model.BlackCalibrationHelper]
-> IO (m, [Double], [Double])
calibrateShortRateModel buildModel attachEngine toCalibratable swaptions = do
m <- buildModel
attachEngine m swaptions
calibratable <- toCalibratable m
vols <- calibrateModel calibratable swaptions
ps <- Model.params calibratable
return (m, vols, ps)
run :: IO Result
run = do
cal <- calendar TARGET
setEvaluationDate $ Just tod
flatRate <- simpleQuote 0.04875825 >>= asQuote -- just to test that explicit casting works
dc365 <- dayCounter Actual365FixedStandard
ts <- flatForward settl flatRate dc365 Continuous Annual
fixedDC <- dayCounter Thirty360European
index6m <- IRI.iborIndex IRI.Euribor6M (Just ts)
start <- advance cal settl (1, Years) floatConv False
maturity <- advance cal start (5, Years) floatConv False
fixedSchedule <- schedule (Just start) maturity (1, Years) cal fixedConv fixedConv Forward False Nothing Nothing
floatSchedule <- schedule (Just start) maturity (6, Months) cal floatConv floatConv Forward False Nothing Nothing
floatDC <- IRI.dayCounter index6m
swp <- vanillaSwap swapType 1000.0 fixedSchedule dummyFixRate fixedDC floatSchedule index6m 0.0
floatDC (Just floatConv) Nothing
engine <- discountingSwapEngine ts Nothing Nothing Nothing
asSwap swp >>= asInstrument >>= (`setPricingEngine` engine)
fixedATMRate <- fairRate swp
(swaptions, tms) <- mapAndUnzipM (createHelpers index6m ts) calibrationGrid
grid <- timeGridFromList' (fromList (concat tms)) 30
(modelG2, g2v, g2p) <- calibrateShortRateModel
(Model.g2 ts 0.1 0.01 0.1 0.01 (-0.75))
(\m ss -> forM_ ss (\s -> g2SwaptionEngine m 6.0 16 >>= Model.setPricingEngine s))
(Model.asShortRateModel >=> Model.asCalibratedModel)
swaptions
(modelHW, hwv, hwp) <- calibrateShortRateModel
(Model.hullWhite ts 0.1 0.01)
(\m ss -> do
modelHWo <- Model.asOneFactorAffineModel m
forM_ ss (\s -> jamshidianSwaptionEngine modelHWo Nothing >>= Model.setPricingEngine s))
(\m -> Model.asOneFactorAffineModel m >>= Model.asShortRateModel >>= Model.asCalibratedModel)
swaptions
(modelHW2, hw2v, hw2p) <- calibrateShortRateModel
(Model.hullWhite ts 0.1 0.01)
(\m ss -> do
modelHW2s <- Model.asOneFactorAffineModel m >>= Model.asShortRateModel
forM_ ss (\s -> treeSwaptionEngine' modelHW2s grid Nothing >>= Model.setPricingEngine s))
(\m -> Model.asOneFactorAffineModel m >>= Model.asShortRateModel >>= Model.asCalibratedModel)
swaptions
(modelBK, bkv, bkp) <- calibrateShortRateModel
(Model.blackKarasinski ts 0.1 0.1)
(\m ss -> forM_ ss (\s -> treeSwaptionEngine' m grid Nothing >>= Model.setPricingEngine s))
Model.asCalibratedModel
swaptions
atmSwap <- vanillaSwap swapType 1000.0 fixedSchedule fixedATMRate fixedDC floatSchedule index6m 0.0
floatDC (Just floatConv) Nothing
bermudanDates <- fixedLeg swp >>= CF.toCouponLeg >>= CF.couponAccrualStartDates
let ex = Bermudan (BermudanExercise bermudanDates False)
atmSwaption <- swaption atmSwap ex Physical PhysicalOTC
npvA <- priceSwaption atmSwaption modelG2 50 modelHW modelHW2 modelBK
let fixedOTMRate = fixedATMRate * 1.2
fixedITMRate = fixedATMRate * 0.8
otmSwap <- vanillaSwap swapType 1000.0 fixedSchedule fixedOTMRate fixedDC floatSchedule index6m 0.0
floatDC (Just floatConv) Nothing
otmSwaption <- swaption otmSwap ex Physical PhysicalOTC
itmSwap <- vanillaSwap swapType 1000.0 fixedSchedule fixedITMRate fixedDC floatSchedule index6m 0.0
floatDC (Just floatConv) Nothing
itmSwaption <- swaption itmSwap ex Physical PhysicalOTC
npvO <- priceSwaption otmSwaption modelG2 300 modelHW modelHW2 modelBK
npvI <- priceSwaption itmSwaption modelG2 50 modelHW modelHW2 modelBK
return Result {
g2Vols = g2v
, g2Params = g2p
, hwVols = hwv
, hwParams = hwp
, hw2Vols = hw2v
, hw2Params = hw2p
, bkVols = bkv
, bkParams = bkp
, npvAtm = npvA
, npvOtm = npvO
, npvItm = npvI
}
where tod = 15 `february` 2002
settl = 19 `february` 2002
swapLengths :: [Word]
swapLengths = [1, 2, 3, 4, 5]
-- Market swaption vols. Rows are option expiries (1y..5y), columns are swap
-- lengths (1y..5y) as given by swapLengths. This used to be a flat 25-element
-- list indexed with hand-rolled `i * numCols + j` arithmetic, which silently
-- reads the wrong cell if the literal is ever re-wrapped.
swaptionVolTable = [
[0.1490, 0.1340, 0.1228, 0.1189, 0.1148],
[0.1290, 0.1201, 0.1146, 0.1108, 0.1040],
[0.1149, 0.1112, 0.1070, 0.1010, 0.0957],
[0.1047, 0.1021, 0.0980, 0.0951, 0.1270],
[0.1000, 0.0950, 0.0900, 0.1230, 0.1160]]
-- The example calibrates against the anti-diagonal of that table: expiry i+1
-- years against the swap length in column (numCols - 1 - i), giving 1x5, 2x4,
-- 3x3, 4x2, 5x1. Built by pattern-matching a `drop` rather than indexing, so
-- there is no partial `!!` and a mis-sized row drops out instead of crashing.
calibrationGrid :: [(Word, Word, Double)] -- (expiry years, swap length years, vol)
calibrationGrid =
[ (fromIntegral i + 1, len, vol)
| (i, row) <- zip [0 :: Int ..] swaptionVolTable
, ((len, vol) : _) <- [drop (length row - 1 - i) (zip swapLengths row)]
]
fixedConv = Unadjusted
floatConv = ModifiedFollowing
dummyFixRate = 0.03
swapType = Payer
createHelpers index6m ts (expiry, len, volValue) = do
vol <- simpleQuote volValue
dc <- IRI.dayCounter index6m
tenr <- IRI.tenor index6m
h <- Model.swaptionHelper (expiry, Years) (len, Years) vol index6m tenr dc dc ts Model.RelativePriceError Nothing 1.0 ShiftedLognormal 0.0 Nothing CF.AveragingCompound
tms <- Model.times h
return (h, tms)
priceSwaption swption modelG2 g2n modelHW modelHW2 modelBK = do
modelG2s <- Model.asShortRateModel modelG2
treeSwaptionEngine modelG2s g2n Nothing >>= setPricingEngine swption
npvG2tree <- npv swption
fdG2SwaptionEngine modelG2 100 50 50 0 1.0e-5 Hundsdorfer >>= setPricingEngine swption
npvG2fd <- npv swption
modelHWs <- Model.asOneFactorAffineModel modelHW >>= Model.asShortRateModel
treeSwaptionEngine modelHWs 50 Nothing >>= setPricingEngine swption
npvHWtree <- npv swption
fdHullWhiteSwaptionEngine modelHW 100 100 0 1.0e-5 Douglas >>= setPricingEngine swption
npvHWfd <- npv swption
modelHW2s <- Model.asOneFactorAffineModel modelHW2 >>= Model.asShortRateModel
treeSwaptionEngine modelHW2s 50 Nothing >>= setPricingEngine swption
npvHW2numtree <- npv swption
fdHullWhiteSwaptionEngine modelHW2 100 100 0 1.0e-5 Douglas >>= setPricingEngine swption
npvHW2numfd <- npv swption
treeSwaptionEngine modelBK 50 Nothing >>= setPricingEngine swption
npvBK <- npv swption
return [npvG2tree, npvG2fd, npvHWtree, npvHWfd, npvHW2numtree, npvHW2numfd, npvBK]
-- vim: set ft=haskell ff=unix ts=8 sts=2 sw=2 et: