packages feed

hasquant-0.6.0.0: test/example/QuantLib/Example/ForwardOption.hs

{-# LANGUAGE TemplateHaskell #-}
module QuantLib.Example.ForwardOption
  (
    Result(..)
  , run
  ) where

import Data.Time.Calendar

import QuantLib.Instrument
import QuantLib.InterestRate
import QuantLib.Instrument.Option
import QuantLib.Math
import QuantLib.PricingEngine
import QuantLib.Process
import QuantLib.Quote
import QuantLib.Settings
import QuantLib.Time.Calendar
import QuantLib.Time.Date hiding(today)
import QuantLib.Time.Schedule
import QuantLib.TermStructure.Volatility
import QuantLib.TermStructure.Yield
import QuantLib.Syntax

-- | Forward-starting (\"cliquet-style\") vanilla options, reproducing
-- QuantLib's own @forwardoption.cpp@ @testValues@ golden case (Haug,
-- \"Option pricing formulas\", p.37\/VBA code): moneyness 1.1, spot 60,
-- dividend yield 4%, risk-free rate 8%, reset in 0.25y, maturity in 1y,
-- 30% vol; expected NPV 4.4064 (call) \/ 8.2971 (put) under
-- 'forwardEuropeanEngine' (the @AnalyticEuropeanEngine@ instantiation of
-- @ForwardVanillaEngine\<Engine\>@). The other 5 forward-starting engines
-- added alongside it are exercised as self-consistency checks against this
-- reference: the finite-differences and Monte Carlo European forward
-- engines should reproduce the same (European-exercise) price; the two
-- American approximation engines, priced on an otherwise identical
-- American-exercise instrument, should be at least as valuable as the
-- European price (the right to exercise early is never a disadvantage);
-- and the Heston forward engine, with its process parameters chosen so
-- Heston degenerates to (near-)deterministic Black-Scholes volatility,
-- should reproduce the same price as the BS analytic engine.
data Result = Result
  { europeanCallR :: Double
  , europeanPutR :: Double
  , fdEuropeanR :: Double
  , mcEuropeanR :: Double
  , hestonEuropeanR :: Double
  , bawAmericanR :: Double
  , bjsAmericanR :: Double
  }

run :: IO Result
run = do
  setEvaluationDate $ Just today
  dc <- dayCounter (Actual360 False)
  spotQ <- simpleQuote spot
  qQ <- simpleQuote divYield
  rQ <- simpleQuote riskFreeRate
  qTS <- flatForward today qQ dc Continuous Annual
  rTS <- flatForward today rQ dc Continuous Annual
  volQ <- simpleQuote vol
  volTS <- calendar TARGET >>= $(free2nd 'blackConstantVol) today volQ dc
  bsmProc <- blackScholesMertonProcess spotQ qTS rTS volTS EulerDiscretization False

  let payoff t = PlainVanilla $ PlainVanillaPayoff t 0.0
      europeanExercise = European $ EuropeanExercise maturity
      americanExercise = American Nothing maturity False

  callOpt <- forwardVanillaOption moneyness resetDate (payoff Call) europeanExercise
  putOpt <- forwardVanillaOption moneyness resetDate (payoff Put) europeanExercise

  europeanEng <- forwardEuropeanEngine bsmProc
  QuantLib.Instrument.setPricingEngine callOpt europeanEng
  europeanCall <- npv callOpt
  QuantLib.Instrument.setPricingEngine putOpt europeanEng
  europeanPut <- npv putOpt

  fdEng <- forwardFdBlackScholesVanillaEngine bsmProc
  QuantLib.Instrument.setPricingEngine callOpt fdEng
  fdEuropean <- npv callOpt

  mcEng <- mcForwardEuropeanBSEngine LowDiscrepancy bsmProc (Just 1) Nothing False False (Just 32768) Nothing Nothing 0
  QuantLib.Instrument.setPricingEngine callOpt mcEng
  mcEuropean <- npv callOpt

  hestonProc <- hestonProcess rTS (Just qTS) spotQ (vol*vol) 1.0 (vol*vol) 0.3 0.0 QuadraticExponentialMartingale
  hestonEng <- analyticHestonForwardEuropeanEngine hestonProc 144
  QuantLib.Instrument.setPricingEngine callOpt hestonEng
  hestonEuropean <- npv callOpt

  americanOpt <- forwardVanillaOption moneyness resetDate (payoff Call) americanExercise
  bawEng <- forwardBaroneAdesiWhaleyEngine bsmProc
  QuantLib.Instrument.setPricingEngine americanOpt bawEng
  bawAmerican <- npv americanOpt

  bjsEng <- forwardBjerksundStenslandEngine bsmProc
  QuantLib.Instrument.setPricingEngine americanOpt bjsEng
  bjsAmerican <- npv americanOpt

  return Result
    { europeanCallR = europeanCall
    , europeanPutR = europeanPut
    , fdEuropeanR = fdEuropean
    , mcEuropeanR = mcEuropean
    , hestonEuropeanR = hestonEuropean
    , bawAmericanR = bawAmerican
    , bjsAmericanR = bjsAmerican
    }
  where
    today = 1 `january` 2020
    moneyness = 1.1
    spot = 60
    divYield = 0.04
    riskFreeRate = 0.08
    vol = 0.30
    -- matches upstream's `timeToDays(t, 360) = lround(t * 360)`
    timeToDays t = round (t * 360.0)
    resetDate = addDays (timeToDays (0.25 :: Double)) today
    maturity = addDays (timeToDays (1.0 :: Double)) today

-- vim: set ft=haskell ff=unix ts=8 sts=2 sw=2 et: