quantfin 0.1.0.1 → 0.1.0.2
raw patch · 18 files changed
+587/−374 lines, 18 filesdep +quantfindep +randomdep +random-sourcedep ~basenew-component:exe:examplePVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: quantfin, random, random-source
Dependency ranges changed: base
API changes (from Hackage documentation)
- Quant.ContingentClaim: ContingentClaim' :: Double -> ([Vector Double] -> Vector Double) -> [(Double, Observables -> Vector Double, Double -> Double)] -> ContingentClaim'
- Quant.ContingentClaim: ContingentClaimBasket :: ContingentClaim -> [Double] -> ContingentClaimBasket
- Quant.ContingentClaim: ccBasket :: ContingentClaim -> ContingentClaimBasket
- Quant.ContingentClaim: changeObservableFct :: ContingentClaim -> (Observables -> Vector Double) -> ContingentClaim
- Quant.ContingentClaim: collector :: ContingentClaim' -> [Vector Double] -> Vector Double
- Quant.ContingentClaim: data ContingentClaim'
- Quant.ContingentClaim: data ContingentClaimBasket
- Quant.ContingentClaim: fixed :: Double -> Double -> ContingentClaim
- Quant.ContingentClaim: instance Eq Observables
- Quant.ContingentClaim: instance Eq OptionType
- Quant.ContingentClaim: instance Show Observables
- Quant.ContingentClaim: instance Show OptionType
- Quant.ContingentClaim: obsHead :: Observables -> Vector Double
- Quant.ContingentClaim: obsNum :: ContingentClaim -> Int -> ContingentClaim
- Quant.ContingentClaim: observations :: ContingentClaim' -> [(Double, Observables -> Vector Double, Double -> Double)]
- Quant.ContingentClaim: payoutTime :: ContingentClaim' -> Double
- Quant.ContingentClaim: type ContingentClaim = [ContingentClaim']
- Quant.Models: charFunc :: (CharFunc a, CharFunc a) => a -> Double -> Complex Double -> Complex Double
- Quant.Models: charFuncMart :: (CharFunc a, CharFunc a, YieldCurve b) => a -> b -> Double -> Complex Double -> Complex Double
- Quant.Models: charFuncOption :: (CharFunc a, CharFunc a, YieldCurve b, YieldCurve c) => a -> b -> c -> ((Double -> Double) -> Double) -> Double -> Double -> Double -> Double
- Quant.Models: class CharFunc a where charFuncMart model fg t k = exp (i * r * k) * baseCF k where i = 0 :+ 1 baseCF = charFunc model t r = forward fg 0 t :+ 0 charFuncOption model fg yc intF strike tmat damp = intF f where f v' = realPart $ exp (i * v * k) * leftTerm * rightTerm where v = v' :+ 0 damp' = damp :+ 0 k = log strike :+ 0 i = 0 :+ 1 leftTerm = d / (damp' + i * v) / (damp' + i * v + (1 :+ 0)) rightTerm = cf $ v - i * (damp' + 1) d = disc yc tmat :+ 0 cf x = charFuncMart model fg tmat x
- Quant.MonteCarlo: discounter :: (Discretize a, Discretize a) => a -> Double -> MonteCarlo (Observables, Double) (Vector Double)
- Quant.MonteCarlo: getTrials :: MonteCarlo (Observables, Double) Int
- Quant.Test: baseYC :: FlatCurve
- Quant.Test: black :: Black
- Quant.Test: heston :: Heston
- Quant.Test: opt :: ContingentClaim
- Quant.Test: opt' :: ContingentClaim
- Quant.Test: opt'' :: ContingentClaim
- Quant.Test: val :: Double
- Quant.Test: val' :: Double
- Quant.Test: val'' :: Double
- Quant.Test: val''' :: Double
- Quant.Test: val'''' :: Double
+ Quant.ContingentClaim: CCProcessor :: Time -> Maybe [PayoffFunc CashFlow] -> CCProcessor
+ Quant.ContingentClaim: CashFlow :: Time -> Double -> CashFlow
+ Quant.ContingentClaim: ContingentClaim :: [CCProcessor] -> ContingentClaim
+ Quant.ContingentClaim: cfAmount :: CashFlow -> Double
+ Quant.ContingentClaim: cfTime :: CashFlow -> Time
+ Quant.ContingentClaim: data CCProcessor
+ Quant.ContingentClaim: data CashFlow
+ Quant.ContingentClaim: fixedBond :: Double -> Double -> Double -> Int -> ContingentClaim
+ Quant.ContingentClaim: instance Monoid ContingentClaim
+ Quant.ContingentClaim: monitor :: Time -> CCBuilder ContingentClaim MCMap Double
+ Quant.ContingentClaim: monitorByNum :: Int -> Time -> CCBuilder ContingentClaim MCMap Double
+ Quant.ContingentClaim: monitorTime :: CCProcessor -> Time
+ Quant.ContingentClaim: newtype ContingentClaim
+ Quant.ContingentClaim: obsGet :: Observables a -> [a]
+ Quant.ContingentClaim: payoutFunc :: CCProcessor -> Maybe [PayoffFunc CashFlow]
+ Quant.ContingentClaim: specify :: CCBuilder ContingentClaim MCMap CashFlow -> ContingentClaim
+ Quant.ContingentClaim: type CCBuilder w r a = WriterT w (Reader r) a
+ Quant.ContingentClaim: type MCObservables = Observables Double
+ Quant.ContingentClaim: unCC :: ContingentClaim -> [CCProcessor]
+ Quant.ContingentClaim: zcb :: Time -> Double -> ContingentClaim
+ Quant.Math.Interpolation: cSplineInterpolator :: Interpolator1d
+ Quant.Math.Interpolation: linearInterpolator :: Interpolator1d
+ Quant.Math.Interpolation: linearVarianceInterpolator :: Interpolator1d
+ Quant.Math.Interpolation: logLinearInterpolator :: Interpolator1d
+ Quant.Math.Interpolation: type Interpolator1d = [Double] -> [Double] -> Double -> Double
+ Quant.Math.Utilities: tdmaSolver :: (Fractional a, Ord a) => [a] -> [a] -> [a] -> [a] -> [a]
+ Quant.Models.Processes: ProcessSpec :: Double -> Double -> Double -> ProcessSpec
+ Quant.Models.Processes: data ProcessSpec
+ Quant.Models.Processes: lognormal :: ProcessSpec -> Double -> Double -> Double
+ Quant.Models.Processes: procElapsed :: ProcessSpec -> Double
+ Quant.Models.Processes: procGrowth :: ProcessSpec -> Double
+ Quant.Models.Processes: procInit :: ProcessSpec -> Double
+ Quant.MonteCarlo: discount :: (Discretize a, Discretize a) => a -> Time -> Double
+ Quant.MonteCarlo: discountState :: (Discretize a, Discretize a) => a -> Time -> MonteCarlo (MCObservables, Time) Double
+ Quant.Time: Time :: Double -> Time
+ Quant.Time: data Time
+ Quant.Time: instance Eq Time
+ Quant.Time: instance Ord Time
+ Quant.Time: instance Show Time
+ Quant.Time: timeDiff :: Time -> Time -> Double
+ Quant.Time: timeFromZero :: Time -> Double
+ Quant.Time: timeOffset :: Time -> Double -> Time
+ Quant.Types: Call :: OptionType
+ Quant.Types: CashFlow :: Time -> Double -> CashFlow
+ Quant.Types: Observables :: [a] -> Observables a
+ Quant.Types: Put :: OptionType
+ Quant.Types: cfAmount :: CashFlow -> Double
+ Quant.Types: cfTime :: CashFlow -> Time
+ Quant.Types: data CashFlow
+ Quant.Types: data Observables a
+ Quant.Types: data OptionType
+ Quant.Types: instance Eq OptionType
+ Quant.Types: instance Show OptionType
+ Quant.Types: instance Show a => Show (Observables a)
+ Quant.Types: obsGet :: Observables a -> [a]
+ Quant.Types: type MCObservables = Observables Double
+ Quant.VolSurf: GridSurf :: [Double] -> [Time] -> Map (Double, Time) Double -> Interpolator1d -> Interpolator1d -> GridSurf
+ Quant.VolSurf: data GridSurf
+ Quant.VolSurf: gridMaturities :: GridSurf -> [Time]
+ Quant.VolSurf: gridQuotes :: GridSurf -> Map (Double, Time) Double
+ Quant.VolSurf: gridStrikeInterpolator :: GridSurf -> Interpolator1d
+ Quant.VolSurf: gridStrikes :: GridSurf -> [Double]
+ Quant.VolSurf: gridTimeInterpolator :: GridSurf -> Interpolator1d
+ Quant.VolSurf: instance VolSurf GridSurf
- Quant.ContingentClaim: Observables :: [Vector Double] -> Observables
+ Quant.ContingentClaim: Observables :: [a] -> Observables a
- Quant.ContingentClaim: arithmeticAsianOption :: OptionType -> Double -> [Double] -> Double -> ContingentClaim
+ Quant.ContingentClaim: arithmeticAsianOption :: OptionType -> Double -> [Time] -> Time -> ContingentClaim
- Quant.ContingentClaim: binaryOption :: OptionType -> Double -> Double -> Double -> ContingentClaim
+ Quant.ContingentClaim: binaryOption :: OptionType -> Double -> Double -> Time -> ContingentClaim
- Quant.ContingentClaim: callSpread :: Double -> Double -> Double -> ContingentClaim
+ Quant.ContingentClaim: callSpread :: Double -> Double -> Time -> ContingentClaim
- Quant.ContingentClaim: data Observables
+ Quant.ContingentClaim: data Observables a
- Quant.ContingentClaim: forwardContract :: Double -> ContingentClaim
+ Quant.ContingentClaim: forwardContract :: Time -> ContingentClaim
- Quant.ContingentClaim: geometricAsianOption :: OptionType -> Double -> [Double] -> Double -> ContingentClaim
+ Quant.ContingentClaim: geometricAsianOption :: OptionType -> Double -> [Time] -> Time -> ContingentClaim
- Quant.ContingentClaim: putSpread :: Double -> Double -> Double -> ContingentClaim
+ Quant.ContingentClaim: putSpread :: Double -> Double -> Time -> ContingentClaim
- Quant.ContingentClaim: straddle :: Double -> Double -> ContingentClaim
+ Quant.ContingentClaim: straddle :: Double -> Time -> ContingentClaim
- Quant.ContingentClaim: terminalOnly :: Double -> (Double -> Double) -> ContingentClaim
+ Quant.ContingentClaim: terminalOnly :: Time -> (Double -> Double) -> ContingentClaim
- Quant.ContingentClaim: vanillaOption :: OptionType -> Double -> Double -> ContingentClaim
+ Quant.ContingentClaim: vanillaOption :: OptionType -> Double -> Time -> ContingentClaim
- Quant.Models.Dupire: Dupire :: Double -> (Double -> Double -> Double) -> a -> b -> Dupire
+ Quant.Models.Dupire: Dupire :: Double -> (Time -> Double -> Double) -> a -> b -> Dupire
- Quant.Models.Dupire: dupireFunc :: Dupire -> Double -> Double -> Double
+ Quant.Models.Dupire: dupireFunc :: Dupire -> Time -> Double -> Double
- Quant.MonteCarlo: class Discretize a where evolve mdl t2 anti = do { (_, t1) <- get; let ms = maxStep mdl; if (t2 - t1) < ms then evolve' mdl t2 anti else do { evolve' mdl (t1 + ms) anti; evolve mdl t2 anti } } maxStep _ = 1 / 250 simulateState modl (ContingentClaimBasket cs ts) trials anti = do { initialize modl trials; avg <$> process empty (replicate trials 0) cs ts } where process m cfs ccs@(c@(ContingentClaim' t _ _) : cs') (obsT : ts') = if t >= obsT then do { evolve modl obsT anti; obs <- gets fst; let m' = insert obsT obs m; process m' cfs ccs ts' } else do { evolve modl t anti; let cfs' = processClaimWithMap c m; d <- discounter modl obsT; let cfs'' = cfs' |*| d; process m (cfs |+| cfs'') cs' (obsT : ts') } process m cfs (c : cs') [] = do { d <- discounter modl (payoutTime c); let cfs' = d |*| processClaimWithMap c m; process m (cfs |+| cfs') cs' [] } process _ cfs _ _ = return cfs v1 |+| v2 = zipWith (+) v1 v2 v1 |*| v2 = zipWith (*) v1 v2 avg v = sum v / fromIntegral (length v) runSimulation modl ccs seed trials anti = runMC run seed (Observables [], 0) where run = simulateState modl (ccBasket ccs) trials anti runSimulationAnti modl ccs seed trials = (runSim True + runSim False) / 2 where runSim = runSimulation modl ccs seed (trials `div` 2) quickSim mdl opts trials = runSimulation mdl opts (pureMT 500) trials False quickSimAnti mdl opts trials = runSimulationAnti mdl opts (pureMT 500) trials
+ Quant.MonteCarlo: class Discretize a where evolve mdl t2 anti = do { (_, t1) <- get; let ms = maxStep mdl; unless (t2 == t1) $ if timeDiff t1 t2 < ms then evolve' mdl t2 anti else do { evolve' mdl (timeOffset t1 ms) anti; evolve mdl t2 anti } } discountState m t = return $ discount m t maxStep _ = 1 / 250 simulateState modl (ContingentClaim ccb) trials anti = avg <$> replicateM trials singleTrial where singleTrial = initialize modl >> process (0 :: Double) empty ccb [] process discCFs obsMap c@(CCProcessor t mf : ccs) allcfs@(CashFlow cft amt : cfs) = if t > cft then do { evolve modl cft anti; d <- discountState modl cft; process (discCFs + d * amt) obsMap c cfs } else do { evolve modl t anti; obs <- gets fst; let obsMap' = insert t obs obsMap; case mf of { Nothing -> process discCFs obsMap' ccs allcfs Just f -> let newCFs = map ($ obsMap') f insertCFList xs cfList = foldl' (flip insertCF) cfList xs in process discCFs obsMap' ccs (insertCFList newCFs allcfs) } } process discCFs obsMap (CCProcessor t mf : ccs) [] = do { evolve modl t anti; obs <- gets fst; let obsMap' = insert t obs obsMap; case mf of { Nothing -> process discCFs obsMap' ccs [] Just f -> let newCFs = map ($ obsMap') f insertCFList xs cfList = foldl' (flip insertCF) cfList xs in process discCFs obsMap' ccs (insertCFList newCFs []) } } process discCFs obsMap [] (cf : cfs) = do { evolve modl (cfTime cf) anti; d <- discountState modl $ cfTime cf; process (discCFs + d * cfAmount cf) obsMap [] cfs } process discCFs _ _ _ = return $! discCFs insertCF (CashFlow t amt) (CashFlow t' amt' : cfs) | t > t' = CashFlow t' amt' : insertCF (CashFlow t amt) cfs | otherwise = CashFlow t amt : CashFlow t' amt' : cfs insertCF cf [] = [cf] avg v = sum v / fromIntegral trials runSimulation modl ccs seed trials anti = runMC run seed (Observables [], Time 0) where run = simulateState modl ccs trials anti runSimulationAnti modl ccs seed trials = (runSim True + runSim False) / 2 where runSim = runSimulation modl ccs seed (trials `div` 2) quickSim mdl opts trials = runSimulation mdl opts (pureMT 500) trials False quickSimAnti mdl opts trials = runSimulationAnti mdl opts (pureMT 500) trials
- Quant.MonteCarlo: evolve :: (Discretize a, Discretize a) => a -> Double -> Bool -> MonteCarlo (Observables, Double) ()
+ Quant.MonteCarlo: evolve :: (Discretize a, Discretize a) => a -> Time -> Bool -> MonteCarlo (MCObservables, Time) ()
- Quant.MonteCarlo: evolve' :: (Discretize a, Discretize a) => a -> Double -> Bool -> MonteCarlo (Observables, Double) ()
+ Quant.MonteCarlo: evolve' :: (Discretize a, Discretize a) => a -> Time -> Bool -> MonteCarlo (MCObservables, Time) ()
- Quant.MonteCarlo: forwardGen :: (Discretize a, Discretize a) => a -> Double -> MonteCarlo (Observables, Double) (Vector Double)
+ Quant.MonteCarlo: forwardGen :: (Discretize a, Discretize a) => a -> Time -> MonteCarlo (MCObservables, Time) Double
- Quant.MonteCarlo: initialize :: (Discretize a, Discretize a) => a -> Int -> MonteCarlo (Observables, Double) ()
+ Quant.MonteCarlo: initialize :: (Discretize a, Discretize a) => a -> MonteCarlo (MCObservables, Time) ()
- Quant.MonteCarlo: simulateState :: (Discretize a, Discretize a) => a -> ContingentClaimBasket -> Int -> Bool -> MonteCarlo (Observables, Double) Double
+ Quant.MonteCarlo: simulateState :: (Discretize a, Discretize a) => a -> ContingentClaim -> Int -> Bool -> MonteCarlo (MCObservables, Time) Double
- Quant.VolSurf: class VolSurf a where var vs s t = v * v * t where v = vol vs s t localVol v s0 rcurve k t | w == 0.0 || solution < 0.0 = sqrt dwdt | otherwise = sqrt solution where dr = disc rcurve t f = s0 / dr y = log $ k / f dy = 1.0E-6 [kp, km] = [k * exp dy, k / exp dy] [w, wp, wm] = map (\ x -> var v (x / s0) t) [k, kp, km] dwdy = (wp - wm) / 2.0 / dy d2wdy2 = (wp - 2.0 * w + wm) / dy / dy dt = min 0.0001 (t / 2.0) dwdt = let strikept = k * dr / drpt strikemt = k * dr / drmt drpt = disc rcurve $ t + dt drmt = disc rcurve $ t - dt in case t of { 0 -> (var v (strikept / s0) (t + dt) - w) / dt _ -> (var v (strikept / s0) (t + dt) - var v (strikemt / s0) (t - dt)) / 2.0 / dt } solution = dwdt / (1.0 - y / w * dwdy + 0.25 * (- 0.25 - 1.0 / w + y * y / w / w) * dwdy * dwdy + 0.5 * d2wdy2)
+ Quant.VolSurf: class VolSurf a where var vs s t = v * v * t' where v = vol vs s t t' = timeFromZero t localVol v s0 rcurve k t | w == 0.0 || solution < 0.0 = sqrt dwdt | otherwise = sqrt solution where dr = disc rcurve t f = s0 / dr y = log $ k / f dy = 1.0E-6 kp = k * exp dy km = k / exp dy [w, wp, wm] = map (\ x -> var v (x / s0) t) [k, kp, km] dwdy = (wp - wm) / 2.0 / dy d2wdy2 = (wp - 2.0 * w + wm) / dy / dy dt = min 0.0001 (timeFromZero t / 2.0) dwdt = let strikept = k * dr / drpt strikemt = k * dr / drmt drpt = disc rcurve $ timeOffset t dt drmt = disc rcurve $ timeOffset t (- dt) in case timeFromZero t of { 0 -> (var v (strikept / s0) (timeOffset t dt) - w) / dt _ -> (var v (strikept / s0) (timeOffset t dt) - var v (strikemt / s0) (timeOffset t (- dt))) / 2.0 / dt } solution = dwdt / (1.0 - y / w * dwdy + 0.25 * (- 0.25 - 1.0 / w + y * y / w / w) * dwdy * dwdy + 0.5 * d2wdy2)
- Quant.VolSurf: localVol :: (VolSurf a, VolSurf a, YieldCurve b) => a -> Double -> b -> Double -> Double -> Double
+ Quant.VolSurf: localVol :: (VolSurf a, VolSurf a, YieldCurve b) => a -> Double -> b -> Double -> Time -> Double
- Quant.VolSurf: var :: (VolSurf a, VolSurf a) => a -> Double -> Double -> Double
+ Quant.VolSurf: var :: (VolSurf a, VolSurf a) => a -> Double -> Time -> Double
- Quant.VolSurf: vol :: (VolSurf a, VolSurf a) => a -> Double -> Double -> Double
+ Quant.VolSurf: vol :: (VolSurf a, VolSurf a) => a -> Double -> Time -> Double
- Quant.YieldCurve: class YieldCurve a where forward yc t1 t2 = (/ (t2 - t1)) $ log $ disc yc t1 / disc yc t2 spot yc t = forward yc 0 t
+ Quant.YieldCurve: class YieldCurve a where forward yc t1 t2 = (/ (timeFromZero t2 - timeFromZero t1)) $ log $ disc yc t1 / disc yc t2 spot yc t = forward yc (Time 0) t
- Quant.YieldCurve: disc :: (YieldCurve a, YieldCurve a) => a -> Double -> Double
+ Quant.YieldCurve: disc :: (YieldCurve a, YieldCurve a) => a -> Time -> Double
- Quant.YieldCurve: forward :: (YieldCurve a, YieldCurve a) => a -> Double -> Double -> Double
+ Quant.YieldCurve: forward :: (YieldCurve a, YieldCurve a) => a -> Time -> Time -> Double
- Quant.YieldCurve: spot :: (YieldCurve a, YieldCurve a) => a -> Double -> Double
+ Quant.YieldCurve: spot :: (YieldCurve a, YieldCurve a) => a -> Time -> Double
Files
- Test.hs +96/−0
- quantfin.cabal +22/−6
- src/Quant/ContingentClaim.hs +106/−89
- src/Quant/Math/Integration.hs +4/−1
- src/Quant/Math/Interpolation.hs +59/−0
- src/Quant/Math/Utilities.hs +41/−0
- src/Quant/Models.hs +0/−43
- src/Quant/Models/Black.hs +14/−14
- src/Quant/Models/Dupire.hs +16/−19
- src/Quant/Models/Heston.hs +19/−25
- src/Quant/Models/Merton.hs +19/−21
- src/Quant/Models/Processes.hs +20/−0
- src/Quant/MonteCarlo.hs +77/−68
- src/Quant/Test.hs +0/−63
- src/Quant/Time.hs +21/−0
- src/Quant/Types.hs +24/−0
- src/Quant/VolSurf.hs +40/−19
- src/Quant/YieldCurve.hs +9/−6
+ Test.hs view
@@ -0,0 +1,96 @@+ +import Quant.Time +import Data.Monoid +import Quant.MonteCarlo +import Quant.YieldCurve +import Quant.ContingentClaim +import Quant.Models.Black +import Quant.Models.Heston + +--create a flat yield curve with a 5% rate +baseYC :: FlatCurve +baseYC = FlatCurve 0.05 + +black :: Black +black = Black + 100 --initial stock price + 0.2 --volatility + baseYC --forward generator + baseYC --discount function + +--make a vanilla put, struck at 100, maturing at time 1 +vanopt :: ContingentClaim +vanopt = vanillaOption Call 100 (Time 1) --built in function + +vanopt' :: ContingentClaim +vanopt' = specify $ do + x <- monitor (Time 1) + return $ CashFlow (Time 1) (max (x - 100) 0) --roll your own + +--Run a Monte Carlo on opt in a a black model with 10000 trials +vanoptPrice :: Double +vanoptPrice = quickSim black vanopt 100000 + +--Make a call spread with a 100 unit notional, using some handy combinators. +cs :: ContingentClaim +cs = multiplier 100 + $ vanillaOption Call 100 (Time 1) + <> short (vanillaOption Call 120 (Time 1)) + +--Run a Monte Carlo on the call spread; use antithetic variates +csPrice :: Double +csPrice = quickSim black' cs 100000 + +black' :: Black +black' = Black + 100 --initial stock price + 0.2 --volatility + (NetYC (FlatCurve 0.05) (FlatCurve 0.02)) --forward generator, now with a 2% dividend yield + baseYC --discount rate + +callSpreadAnti :: Double +callSpreadAnti = quickSimAnti black' cs 100000 + +--Let's try it with a Heston model +heston :: Heston +heston = Heston + 100 + 0.04 --initial variance + 0.04 --final variance + 0.2 --volvol + (-0.7) --correlation between processes + 1.0 --mean reversion speed + baseYC --forward generator + baseYC --discount function + +--price the call spread in the Heston model +csHeston :: Double +csHeston = quickSimAnti heston cs 100000 + +--create an option that pays off based on the square of its underlying +squareOpt :: ContingentClaim +squareOpt = terminalOnly (Time 1) $ \x -> x*x --using the built in function +squareOpt' = specify $ do --roll your own + x <- monitor (Time 1) + return $ CashFlow (Time 1) $ x*x +squareOptPrice :: Double +squareOptPrice = quickSimAnti black squareOpt 100000 + +--create an option with a bizarre payoff +bizarre :: ContingentClaim +bizarre = specify $ do + x <- monitor (Time 1) --check the price of asset 0 @ time 1 + y <- monitor (Time 2) --check the price of asset 0 @ time 2 + z <- monitor (Time 3) --check the price of asset 0 @ time 3 + return $ CashFlow (Time 4) $ x ^ 3 / y ^ 2 - 3 * z --payoff @ time 4 +bizarrePrice :: Double +bizarrePrice = quickSimAnti black bizarre 100000 + +main :: IO () +main = do + print vanoptPrice + print csPrice + print callSpreadAnti + print csHeston + print squareOptPrice + print bizarrePrice
quantfin.cabal view
@@ -10,7 +10,7 @@ -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change -version: 0.1.0.1 +version: 0.1.0.2 -- A short (one-line) description of the package. synopsis: Quant finance library in pure Haskell. @@ -37,7 +37,7 @@ -- A copyright notice. -- copyright: -category: Quant +category: Finance build-type: Simple @@ -54,14 +54,19 @@ exposed-modules: Quant.YieldCurve Quant.VolSurf Quant.Math.Integration - Quant.Models + Quant.Math.Interpolation + Quant.Math.Utilities + --Quant.Models Quant.Models.Black Quant.Models.Merton Quant.Models.Dupire Quant.Models.Heston + Quant.Models.Processes + --Quant.RNG.MWC64X Quant.MonteCarlo Quant.ContingentClaim - Quant.Test + Quant.Types + Quant.Time -- Modules included in this library but not exported. -- other-modules: @@ -77,11 +82,22 @@ random-fu, containers, rvar, - mersenne-random-pure64 + mersenne-random-pure64, + random-source, + random -- Directories containing source files. hs-source-dirs: src -- Base language which the package is written in. default-language: Haskell2010 - + + ghc-options: -Wall -fexcess-precision + +executable "example" + build-depends: + base, + quantfin + main-is: Test.hs + ghc-options: -Wall -fexcess-precision -rtsopts + default-language: Haskell2010
src/Quant/ContingentClaim.hs view
@@ -1,13 +1,17 @@ module Quant.ContingentClaim ( -- * Types for modeling contingent claims. - ContingentClaim - , ContingentClaim' (..) + ContingentClaim (..) + , CCProcessor (..) , Observables (..) - , ContingentClaimBasket (..) + , MCObservables , OptionType (..) - , ccBasket + , CashFlow (..) + , CCBuilder -- * Options and option combinators + , specify + , monitor + , monitorByNum , vanillaOption , binaryOption , straddle @@ -16,46 +20,59 @@ , callSpread , putSpread , forwardContract - , fixed + , zcb + , fixedBond , multiplier , short , combine , terminalOnly - , changeObservableFct - -- * Utility functions - , obsNum - , obsHead - ) where +) where -import Data.List -import Data.Ord -import qualified Data.Vector.Unboxed as U +import Control.Monad.Reader +import Control.Monad.Writer.Strict +import Quant.Types +import Quant.Time +import qualified Data.Map as M +type MCMap = M.Map Time MCObservables +type PayoffFunc a = MCMap -> a --- | 'ContingentClaim'' is the underlying type of contingent claims. -data ContingentClaim' = ContingentClaim' { - payoutTime :: Double -- ^ Payout time for cash flow - , collector :: [U.Vector Double] -> U.Vector Double - , observations :: [( Double - , Observables -> U.Vector Double - , Double -> Double) ] {- ^ List containing: - -- Time of observation, - -- Function to access specific observable, - -- Function to collect observations and transform them into a cash flow. -} + +data CCProcessor = CCProcessor { + monitorTime :: Time + , payoutFunc :: Maybe [PayoffFunc CashFlow] } --- | 'ContingentClaim' is just a list of the underlying 'ContingentClaim''s. -type ContingentClaim = [ContingentClaim'] --- | Observables are the observables available in a Monte Carlo simulation. ---Most basic MCs will have one observables (Black-Scholes) whereas more ---complex ones will have multiple (i.e. Heston-Hull-White). -data Observables = Observables [U.Vector Double] deriving (Eq, Show) +type CCBuilder w r a = WriterT w (Reader r) a --- | ADT for Put or Calls -data OptionType = Put | Call deriving (Eq,Show) +monitor :: Time -> CCBuilder ContingentClaim MCMap Double +monitor = monitorByNum 0 +monitorByNum :: Int -> Time -> CCBuilder ContingentClaim MCMap Double +monitorByNum idx t = do + tell $ ContingentClaim [CCProcessor t Nothing] + m <- lift ask + return $ obsGet (m M.! t) !! idx --I know, I know. + +specify :: CCBuilder ContingentClaim MCMap CashFlow -> ContingentClaim +specify x = w `mappend` ContingentClaim [CCProcessor (last0 w') (Just [f])] + where + w = runReader (execWriterT x) M.empty + f = runReader . liftM fst $ runWriterT x + w' = map monitorTime $ unCC w + -- Equivalent to Prelude's last, but with a default of zero + last0 [] = Time 0 + last0 [y] = y + last0 (_:ys) = last0 ys + +newtype ContingentClaim = ContingentClaim { unCC :: [CCProcessor] } + +instance Monoid ContingentClaim where + mempty = ContingentClaim [] + mappend = combine + -- | Function to generate a vanilla put/call style payout. vanillaPayout :: OptionType -- ^ Put or Call -> Double -- ^ Strike @@ -77,94 +94,94 @@ -- | Takes a maturity time and a function and generates a ContingentClaim --dependent only on the terminal value of the observable. -terminalOnly :: Double -> (Double -> Double) -> ContingentClaim -terminalOnly t f = [ContingentClaim' t head [(t, obsHead, f)]] +terminalOnly :: Time -> (Double -> Double) -> ContingentClaim +terminalOnly t g = specify $ do + x <- monitor t + return $ CashFlow t $ g x -- | Takes an OptionType, a strike, and a time to maturity and generates a vanilla option. -vanillaOption :: OptionType -> Double -> Double -> ContingentClaim +vanillaOption :: OptionType -> Double -> Time -> ContingentClaim vanillaOption pc strike t = terminalOnly t $ vanillaPayout pc strike -- | Takes an OptionType, a strike, a payout amount and a time to --maturity and generates a vanilla option. -binaryOption :: OptionType -> Double -> Double -> Double -> ContingentClaim +binaryOption :: OptionType -> Double -> Double -> Time -> ContingentClaim binaryOption pc strike amount t = terminalOnly t $ binaryPayout pc strike amount -- | Takes an OptionType, a strike, observation times, time to --maturity and generates an arithmetic Asian option. -arithmeticAsianOption :: OptionType -> Double -> [Double] -> Double -> ContingentClaim -arithmeticAsianOption pc strike obsTimes t = [ContingentClaim' t f obs] - where obs = map (\x -> (x, obsHead, id)) obsTimes - f k = U.map (vanillaPayout pc strike . (/fromIntegral l)) - $ foldl1' (U.zipWith (+)) k - where l = length k +arithmeticAsianOption :: OptionType -> Double -> [Time] -> Time -> ContingentClaim +arithmeticAsianOption pc strike obsTimes t = specify $ do + x <- mapM monitor obsTimes + let avg = sum x / fromIntegral (length obsTimes) + return $ CashFlow t $ vanillaPayout pc strike avg -- | Takes an OptionType, a strike, observation times, time to ---maturity and generates a geometric Asian option. -geometricAsianOption :: OptionType -> Double -> [Double] -> Double -> ContingentClaim -geometricAsianOption pc strike obsTimes t = [ContingentClaim' t f obs] - where obs = map (\x -> (x, obsHead, id)) obsTimes - f k = U.map (vanillaPayout pc strike . (** (1/fromIntegral l))) - $ foldl1' (U.zipWith (*)) k - where l = length k +--maturity and generates an arithmetic Asian option. +geometricAsianOption :: OptionType -> Double -> [Time] -> Time -> ContingentClaim +geometricAsianOption pc strike obsTimes t = specify $ do + x <- mapM monitor obsTimes + let avg = product x ** (1 / fromIntegral (length obsTimes)) + return $ CashFlow t $ vanillaPayout pc strike avg -- | Scales up a contingent claim by a multiplier. multiplier :: Double -> ContingentClaim -> ContingentClaim -multiplier notional cs = map f cs - where f c@(ContingentClaim' _ collFct _) = c { collector = U.map (*notional) . collFct } +multiplier notional cs = ContingentClaim $ map f (unCC cs) + where f (CCProcessor t g) = CCProcessor t $ fmap (fmap (scale.)) g + scale (CashFlow dt amt) = CashFlow dt (amt*notional) -- | Flips the signs in a contingent claim to make it a short position. short :: ContingentClaim -> ContingentClaim short = multiplier (-1) -- | Takes an amount and a time and generates a fixed cash flow. -fixed :: Double -> Double -> ContingentClaim -fixed amount t = terminalOnly t $ const amount +zcb :: Time -> Double -> ContingentClaim +zcb t amt = specify $ return $ CashFlow t amt +-- | Takes a face value, an interest rate, a payment frequency and makes a fixed bond +fixedBond :: Double -> Double -> Double -> Int -> ContingentClaim +fixedBond faceVal intRate freq pmts = zcb (Time $ fromIntegral pmts * freq) faceVal + <> mconcat (map f [1..pmts]) + where + f x = zcb (Time $ fromIntegral x * freq) (faceVal * intRate * freq) + -- | Takes a time to maturity and generates a forward contract. -forwardContract :: Double -> ContingentClaim -forwardContract t = terminalOnly t id +forwardContract :: Time -> ContingentClaim +forwardContract t = specify $ do + x <- monitor t + return $ CashFlow t x -- | A call spread is a long position in a low-strike call --and a short position in a high strike call. -callSpread :: Double -> Double -> Double -> ContingentClaim -callSpread lowStrike highStrike t = combine (vanillaOption Call lowStrike t) (short $ vanillaOption Call highStrike t) +callSpread :: Double -> Double -> Time -> ContingentClaim +callSpread lowStrike highStrike t = mappend (vanillaOption Call lowStrike t) + (short $ vanillaOption Call highStrike t) -- | A put spread is a long position in a high strike put --and a short position in a low strike put. -putSpread :: Double -> Double -> Double -> ContingentClaim -putSpread lowStrike highStrike t = combine (vanillaOption Put highStrike t) (short $ vanillaOption Put lowStrike t) +putSpread :: Double -> Double -> Time -> ContingentClaim +putSpread lowStrike highStrike t = mappend (vanillaOption Put highStrike t) + (short $ vanillaOption Put lowStrike t) -- | A straddle is a put and a call with the same time to maturity / strike. -straddle :: Double -> Double -> ContingentClaim -straddle strike t = vanillaOption Put strike t ++ vanillaOption Call strike t +straddle :: Double -> Time -> ContingentClaim +straddle strike t = vanillaOption Put strike t <> vanillaOption Call strike t --- | Just combines two contingent claims into one. +-- | Combines two contingent claims into one. combine :: ContingentClaim -> ContingentClaim -> ContingentClaim -combine = (++) - --- | Used to compile claims for the Monte Carlo engine. -data ContingentClaimBasket = ContingentClaimBasket ContingentClaim [Double] - --- | Converts a 'ContingentClaim' into a 'ContingentClaimBasket' for use by the MC engine. -ccBasket :: ContingentClaim -> ContingentClaimBasket -ccBasket ccs = ContingentClaimBasket (sortBy (comparing payoutTime) ccs) monitorTimes - where monitorTimes = sort . nub $ concatMap (map fst3 . observations) ccs - --- | Utility function to pull the head of a basket of observables. -obsHead :: Observables -> U.Vector Double -obsHead (Observables (x:_)) = x - -changeObservableFct' :: ContingentClaim' -> (Observables -> U.Vector Double) -> ContingentClaim' -changeObservableFct' c@(ContingentClaim' _ _ calcs) f = c { observations = map (\(t, _, g) -> (t, f, g)) calcs } - --- | Offers the ability to change the function on the observable an option is based on. ---All options default to being based on the first observable. -changeObservableFct :: ContingentClaim -> (Observables -> U.Vector Double) -> ContingentClaim -changeObservableFct ccs f = map (`changeObservableFct'` f) ccs - -fst3 :: (a,b,c) -> a -fst3 (x, _, _) = x - --- | Utility function for when the observable function is just '!!' -obsNum :: ContingentClaim -> Int -> ContingentClaim -obsNum ccs k = changeObservableFct ccs $ \(Observables x)-> x !! k+combine (ContingentClaim x) (ContingentClaim y) = ContingentClaim $ combine' x y + where + combine' (cc1:ccs1) (cc2:ccs2) + | monitorTime cc1 == monitorTime cc2 = let + (CCProcessor t mf) = cc1 + (CCProcessor _ mf') = cc2 in + case mf of + Nothing -> cc2 : combine' ccs1 ccs2 + Just a -> case mf' of + Nothing -> cc1 : combine' ccs1 ccs2 + Just b -> CCProcessor t (Just (a ++ b)) : combine' ccs1 ccs2 + | monitorTime cc1 > monitorTime cc2 = cc2 : combine' (cc1:ccs1) ccs2 + | otherwise = cc1 : combine' ccs1 (cc2:ccs2) + combine' [] [] = [] + combine' cs [] = cs + combine' [] cs = cs
src/Quant/Math/Integration.hs view
@@ -14,6 +14,7 @@ where dx = (uBound - lBound) / fromIntegral intervals points = take intervals $ iterate (+dx) (lBound+dx/2) +{-# INLINE midpoint #-} -- | Trapezoidal integration. trapezoid :: Int -> Integrator @@ -21,7 +22,9 @@ where dx = (uBound - lBound) / fromIntegral intervals points = take (intervals-1) $ iterate (+dx) (lBound+dx) +{-# INLINE trapezoid #-} -- | Integration using Simpson's rule. simpson :: Int -> Integrator -simpson intervals f l u =( 2 * midpoint intervals f l u + trapezoid intervals f l u ) / 3+simpson intervals f l u =( 2 * midpoint intervals f l u + trapezoid intervals f l u ) / 3 +{-# INLINE simpson #-}
+ src/Quant/Math/Interpolation.hs view
@@ -0,0 +1,59 @@+module Quant.Math.Interpolation ( + linearInterpolator + , logLinearInterpolator + , linearVarianceInterpolator + , cSplineInterpolator + , Interpolator1d + --, cubicSpline +) where + +import Quant.Math.Utilities (tdmaSolver) +import Data.List (zipWith5) + +type Interpolator1d = [Double] -> [Double] -> Double -> Double + +linearInterpolator :: Interpolator1d +linearInterpolator (x1:x2:xs) (y1:y2:ys) x + | x >= x2 = linearInterpolator (x2:xs) (y2:ys) x + | x <= x1 = y1 + | otherwise = wt1 * y1 + wt2 * y2 + where + wt1 = (x2-x) / (x2-x1) + wt2 = (x-x1) / (x2-x1) +linearInterpolator _ [y] _ = y +{-# INLINE linearInterpolator #-} + +logLinearInterpolator :: Interpolator1d +logLinearInterpolator x1 x2 x = exp $ linearInterpolator x1 (map log x2) x +{-# INLINE logLinearInterpolator #-} + +linearVarianceInterpolator :: Interpolator1d +linearVarianceInterpolator xs ys = linearInterpolator xs + . map (\(x, y) -> y*y*x) + $ zip xs ys +{-# INLINE linearVarianceInterpolator #-} + +cSplineInterpolator :: Interpolator1d +cSplineInterpolator xs ys x = evalSpline xs ys moments + where + h = zipWith (-) (tail xs) (init xs) + lambda = 0.0 : zipWith (\a b->a/(a+b)) (tail h) (init h) + mu = map (\a->1.0-a) lambda++[0.0] + dj hj hj1 yj1 yj yjm1= 6.0/(hj+hj1)*((yj1-yj)/hj1-(yj-yjm1)/hj) + d = 0.0 : zipWith5 dj (init h) (tail h) + (tail $ tail ys) (tail $ init ys) + (init $ init ys) ++ [0.0] + moments = tdmaSolver mu (replicate (length d) 2) lambda d + evalSpline (x1:x2:xs') (y1:y2:ys') (m1:m2:ms) + | x <= x1 = y1 + | x >= x2 = evalSpline (x2:xs') (y2:ys') (m2:ms) + | otherwise = y1+beta*term+gamma*term*term+ + delta*term*term*term + where + gamma = m1/2.0 + beta = (y2-y1)/h'-(2*m1+m2)*h'/6.0 + delta = (m2-m1)/6.0/h' + term = x-x1 + h' = x2-x1 + evalSpline _ (y':_) _ = y' +{-# INLINE cSplineInterpolator #-}
+ src/Quant/Math/Utilities.hs view
@@ -0,0 +1,41 @@+module Quant.Math.Utilities ( + tdmaSolver +) where + +import Control.Monad +import Control.Monad.ST +import qualified Data.Vector.Mutable as M +import qualified Data.Vector as V + +tdmaSolver :: (Fractional a, Ord a) => [a] -> [a] -> [a] -> [a] -> [a] +tdmaSolver aL bL cL dL = V.toList $ + let [a,b,c,d] = map V.fromList [aL,bL,cL,dL] in + runST $ do + c' <- V.thaw c + M.write c' 0 (V.head c / V.head b) + forM_ [1..V.length c-1] $ \x -> do + let ai = a V.! x + bi = b V.! x + ci = c V.! x + ci1' <- M.read c' (x-1) + M.write c' x $ ci / (bi-ai*ci1') + cf <- V.unsafeFreeze c' + d' <- V.thaw d + M.write d' 0 (V.head d / V.head b) + forM_ [1..V.length d-1] $ \x -> do + let ai = a V.! x + bi = b V.! x + di = d V.! x + ci1 = cf V.! (x-1) + di1' <- M.read d' (x-1) + M.write d' x $ (di-ai*di1') / (bi-ai*ci1) + df <- V.unsafeFreeze d' + xn <- M.new $ V.length d + M.write xn (V.length d-1) $ V.last df + forM_ (reverse [0..V.length df-2]) $ \ x-> do + let ci = cf V.! x + di = df V.! x + xi1 <- M.read xn $ x+1 + M.write xn x $ di - ci*xi1 + V.unsafeFreeze xn +{-# INLINE tdmaSolver #-}
− src/Quant/Models.hs
@@ -1,43 +0,0 @@-module Quant.Models ( - CharFunc(..) -) where - -import Data.Complex -import Quant.YieldCurve - - -{- | The 'CharFunc' class defines those -models which have closed-form characteristic -functions. - -Minimal complete definition: 'charFunc'. - -Still under construction. --} -class CharFunc a where - -- | Creates a characteristic function for a model, without martingale adjustment. - charFunc :: CharFunc a => a -> Double -> Complex Double -> Complex Double - - -- | Calculates characteristic function given a forward generator and yield curve. - charFuncMart :: (CharFunc a, YieldCurve b) => a -> b -> Double -> Complex Double -> Complex Double - charFuncMart model fg t k = exp (i * r * k) * baseCF k - where - i = 0 :+ 1 - baseCF = charFunc model t - r = forward fg 0 t :+ 0 - - charFuncOption :: (CharFunc a, YieldCurve b, YieldCurve c) => - a -> b -> c -> ( (Double -> Double) -> Double) -> Double - -> Double -> Double -> Double - charFuncOption model fg yc intF strike tmat damp = intF f - where - f v' = realPart $ exp (i*v*k) * leftTerm * rightTerm - where - v = v' :+ 0 - damp' = damp :+ 0 - k = log strike :+ 0 - i = 0 :+ 1 - leftTerm = d / (damp' + i * v) / (damp'+i*v+(1:+0)) - rightTerm = cf $ v - i * (damp' + 1) - d = disc yc tmat :+ 0 - cf x = charFuncMart model fg tmat x
src/Quant/Models/Black.hs view
@@ -1,17 +1,16 @@ {-# LANGUAGE ExistentialQuantification #-} -{-# LANGUAGE FlexibleInstances #-} module Quant.Models.Black ( Black (..) ) where +import Quant.Time import Quant.YieldCurve import Data.Random import Control.Monad.State import Quant.MonteCarlo import Quant.ContingentClaim -import qualified Data.Vector.Unboxed as U -- | 'Black' represents a Black-Scholes model. data Black = forall a b . (YieldCurve a, YieldCurve b) => Black { @@ -30,27 +29,28 @@ --logs = log s :+ 0 instance Discretize Black where - initialize (Black s _ _ _) trials = put (Observables [U.replicate trials s], 0) + initialize (Black s _ _ _) = put (Observables [s], Time 0) + {-# INLINE initialize #-} evolve' b@(Black _ vol _ _) t2 anti = do - (Observables (stateVec:_), t1) <- get + (Observables (stateVal:_), t1) <- get fwd <- forwardGen b t2 - let grwth = U.map (\x -> (x - vol*vol/2) * (t2-t1)) fwd - postVal <- U.forM (U.zip grwth stateVec) $ \ ( g , x ) -> do + let grwth = (fwd - vol*vol/2) * timeDiff t1 t2 + postVal <- do resid <- lift stdNormal if anti then - return $ x * exp (g - resid*vol) + return $ stateVal * exp (grwth - resid*vol) else - return $ x * exp (g + resid*vol) + return $ stateVal * exp (grwth + resid*vol) put (Observables [postVal], t2) + {-# INLINE evolve' #-} - discounter (Black _ _ _ dsc) t = do - size <- getTrials - return $ U.replicate size $ disc dsc t + discount (Black _ _ _ dsc) t = disc dsc t + {-# INLINE discount #-} forwardGen (Black _ _ fg _) t2 = do - size <- getTrials - t1 <- gets snd - return $ U.replicate size $ forward fg t1 t2 + (_, t1) <- get + return $ forward fg t1 t2 + {-# INLINE forwardGen #-} maxStep _ = 100
src/Quant/Models/Dupire.hs view
@@ -1,45 +1,42 @@-{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ExistentialQuantification #-} module Quant.Models.Dupire ( Dupire (..) ) where +import Quant.Time import Data.Random import Control.Monad.State import Quant.ContingentClaim import Quant.MonteCarlo import Quant.YieldCurve -import qualified Data.Vector.Unboxed as U -- | 'Dupire' represents a Dupire-style local vol model. data Dupire = forall a b . (YieldCurve a, YieldCurve b) => Dupire { dupireInitial :: Double -- ^ Initial asset level - , dupireFunc :: Double -> Double -> Double -- ^ Local vol function taking a time to maturity and a level + , dupireFunc :: Time -> Double -> Double -- ^ Local vol function taking a time to maturity and a level , mertonForwardGen :: a -- ^ 'YieldCurve' to generate forwards , mertonDiscounter :: b } -- ^ 'YieldCurve' to generate discount rates instance Discretize Dupire where - initialize (Dupire s _ _ _) trials = put (Observables [U.replicate trials s], 0) + initialize (Dupire s _ _ _) = put (Observables [s], Time 0) + {-# INLINE initialize #-} evolve' d@(Dupire _ f _ _) t2 anti = do - (Observables (stateVec:_), t1) <- get + (Observables (stateVal:_), t1) <- get fwd <- forwardGen d t2 - let vols = U.map (f t1) stateVec - grwth = U.map (\(fwdVal, v) -> (fwdVal - v * v / 2) / (t2-t1)) $ U.zip fwd vols - postVal <- U.forM (U.zip3 grwth stateVec vols) $ \ ( g,x,v ) -> do - normResid <- lift stdNormal - if anti then - return $ x * exp (g - normResid*v) - else - return $ x * exp (g + normResid*v) - put (Observables [postVal], t2) + let vol = f t1 stateVal + grwth = (fwd - vol * vol / 2) * timeDiff t1 t2 + normResid <- lift stdNormal + let s' | anti = stateVal * exp (grwth - normResid*vol) + | otherwise = stateVal * exp (grwth - normResid*vol) + put (Observables [s'], t2) + {-# INLINE evolve' #-} - discounter (Dupire _ _ _ dsc) t = do - size <- getTrials - return $ U.replicate size $ disc dsc t + discount (Dupire _ _ _ dsc) t = disc dsc t + {-# INLINE discount #-} forwardGen (Dupire _ _ fg _) t2 = do t1 <- gets snd - size <- getTrials - return $ U.replicate size $ forward fg t1 t2+ return $ forward fg t1 t2 + {-# INLINE forwardGen #-}
src/Quant/Models/Heston.hs view
@@ -1,18 +1,16 @@ {-# LANGUAGE ExistentialQuantification #-} -{-# LANGUAGE FlexibleInstances #-} module Quant.Models.Heston ( Heston (..) ) where +import Quant.Time import Quant.YieldCurve import Data.Random -import Quant.Models import Control.Monad.State import Quant.MonteCarlo import Quant.ContingentClaim -import qualified Data.Vector.Unboxed as U -- | 'Heston' represents a Heston model (i.e. stochastic volatility). data Heston = forall a b . (YieldCurve a, YieldCurve b) => Heston { @@ -26,34 +24,30 @@ , hestonDisc :: b } -- ^ 'YieldCurve' to generate discounts instance Discretize Heston where - initialize (Heston s v0 _ _ _ _ _ _) trials = put (Observables [U.replicate trials s, - U.replicate trials v0 ], 0) + initialize (Heston s v0 _ _ _ _ _ _) = put (Observables [s, v0], Time 0) + {-# INLINE initialize #-} evolve' h@(Heston _ _ vf l rho eta _ _) t2 anti = do (Observables (sState:vState:_), t1) <- get fwd <- forwardGen h t2 - let grwth = U.map (\(g, v) -> (g - v/2) * (t2-t1)) (U.zip fwd vState) - t = t2-t1 - states <- U.forM (U.zip3 grwth sState vState) $ \ ( g, x, v ) -> do - resid1 <- lift stdNormal - resid2' <- lift stdNormal - let - op = if anti then (-) else (+) - resid2 = rho * resid1 + sqrt (1-rho*rho) * resid2' - v' = (sqrt v `op` (eta/2.0*sqrt t* resid2))^(2 :: Int)-l*(v-vf)*t-eta*eta*t/4.0 - s' = x * exp (g `op` (resid1*sqrt (v*(t2-t1)))) - return (s', abs v') - let newS = U.map fst states - newV = U.map snd states - put (Observables [newS, newV], t2) + let grwth = (fwd - vState/2) * t + t = timeDiff t1 t2 + resid1 <- lift stdNormal + resid2' <- lift stdNormal + let + op = if anti then (-) else (+) + resid2 = rho * resid1 + sqrt (1-rho*rho) * resid2' + v' = (sqrt vState `op` (eta/2.0*sqrt t* resid2))^(2 :: Int)-l*(vState-vf)*t-eta*eta*t/4.0 + s' = sState * exp (grwth `op` (resid1*sqrt (vState*t))) + put (Observables [s', abs v'], t2) + {-# INLINE evolve' #-} - discounter (Heston _ _ _ _ _ _ _ d) t = do - size <- getTrials - return $ U.replicate size $ disc d t + discount (Heston _ _ _ _ _ _ _ d) t = disc d t + {-# INLINE discount #-} forwardGen (Heston _ _ _ _ _ _ fg _) t2 = do - size <- getTrials t1 <- gets snd - return $ U.replicate size $ forward fg t1 t2 + return $ forward fg t1 t2 + {-# INLINE forwardGen #-} - maxStep _ = 1/250+ maxStep _ = 1/12
src/Quant/Models/Merton.hs view
@@ -1,17 +1,16 @@-{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ExistentialQuantification #-} module Quant.Models.Merton ( Merton (..) ) where +import Quant.Time import Data.Random import Data.Random.Distribution.Poisson import Control.Monad.State import Quant.MonteCarlo import Quant.ContingentClaim import Quant.YieldCurve -import qualified Data.Vector.Unboxed as U -- | 'Merton' represents a Merton model (Black-Scholes w/ jumps). data Merton = forall a b . (YieldCurve a, YieldCurve b) => Merton { @@ -32,30 +31,29 @@ --addon = exp $ (intensity * t :+ 0) * (-i*k*(inner1 - 1) + inner2 - 1) instance Discretize Merton where - initialize (Merton s _ _ _ _ _ _) trials = put (Observables [U.replicate trials s], 0) + initialize (Merton s _ _ _ _ _ _) = put (Observables [s], Time 0) + {-# INLINE initialize #-} evolve' m@(Merton _ vol intensity mu sig _ _) t2 anti = do - (Observables (stateVec:_), t1) <- get + (Observables (stateVal:_), t1) <- get fwd <- forwardGen m t2 let correction = exp (mu + sig*sig /2.0) - 1 - grwth = U.map (\x -> (x - vol*vol/2 - intensity * correction) * (t2-t1)) fwd - postVal <- U.forM (U.zip grwth stateVec) $ \ ( g,x ) -> do - normResid1 <- lift stdNormal - normResid2 <- lift stdNormal - poissonResid <- lift $ integralPoisson (intensity * (t2-t1)) :: MonteCarlo (Observables, Double) Int - let poisson' = fromIntegral poissonResid - jumpterm = mu*poisson'+sig*sqrt poisson' * normResid2 - if anti then - return $ x * exp (g - normResid1*vol + jumpterm) - else - return $ x * exp (g + normResid1*vol + jumpterm) - put (Observables [postVal], t2) + grwth = (fwd - vol*vol/2 - intensity * correction) * t + t = timeDiff t1 t2 + normResid1 <- lift stdNormal + normResid2 <- lift stdNormal + poissonResid <- lift $ integralPoisson (intensity * t) :: MonteCarlo (MCObservables, Time) Int + let poisson' = fromIntegral poissonResid + jumpterm = mu*poisson'+sig*sqrt poisson' * normResid2 + s' | anti = stateVal * exp (grwth - normResid1*vol + jumpterm) + | otherwise = stateVal * exp (grwth + normResid1*vol + jumpterm) + put (Observables [s'], t2) + {-# INLINE evolve' #-} - discounter (Merton _ _ _ _ _ _ dsc) t = do - size <- getTrials - return $ U.replicate size $ disc dsc t + discount (Merton _ _ _ _ _ _ dsc) t = disc dsc t + {-# INLINE discount #-} forwardGen (Merton _ _ _ _ _ fg _) t2 = do - size <- getTrials t1 <- gets snd - return $ U.replicate size $ forward fg t1 t2+ return $ forward fg t1 t2 + {-# INLINE forwardGen #-}
+ src/Quant/Models/Processes.hs view
@@ -0,0 +1,20 @@+ +module Quant.Models.Processes ( + ProcessSpec (..) + , lognormal +) where + +data ProcessSpec = ProcessSpec { + procInit :: Double + , procGrowth :: Double + , procElapsed :: Double +} + +lognormal :: ProcessSpec -> Double -> Double -> Double +lognormal (ProcessSpec initVal r t) vol normRand = initVal * exp ( g + sig * normRand ) + where + g = (r - vol*vol/2) * t + sig = vol * sqrt t + +--cir :: ProcessSpec -> Double +--cir (ProcessSpec initVal r t) vol normRand
src/Quant/MonteCarlo.hs view
@@ -11,8 +11,6 @@ , Discretize(..) , OptionType(..) - , getTrials - ) where @@ -21,10 +19,11 @@ import Control.Applicative import Control.Monad.State import Data.Functor.Identity +import Quant.Time import Data.RVar +import Data.Foldable (foldl') import System.Random.Mersenne.Pure64 import qualified Data.Map as Map -import qualified Data.Vector.Unboxed as U -- | A monad transformer for Monte-Carlo calculations. type MonteCarloT m s = StateT s (RVarT m) @@ -50,87 +49,109 @@ -- | Initializes a Monte Carlo simulation for a given number of runs. initialize :: Discretize a => a -- ^ Model - -> Int -- ^ number of trials - -> MonteCarlo (Observables, Double) () + -> MonteCarlo (MCObservables, Time) () -- | Evolves the internal states of the MC variables between two times. - evolve :: Discretize a => a -- ^ Model - -> Double -- ^ time to evolve to - -> Bool -- whether or not to use flipped variates - -> MonteCarlo (Observables, Double) () + evolve :: Discretize a => a -- ^ Model + -> Time -- ^ time to evolve to + -> Bool -- whether or not to use flipped variates + -> MonteCarlo (MCObservables, Time) () evolve mdl t2 anti = do (_, t1) <- get let ms = maxStep mdl - if (t2-t1) < ms then - evolve' mdl t2 anti - else do - evolve' mdl (t1 + ms) anti - evolve mdl t2 anti + unless (t2==t1) $ + if timeDiff t1 t2 < ms then + evolve' mdl t2 anti + else do + evolve' mdl (timeOffset t1 ms) anti + evolve mdl t2 anti -- | Stateful discounting function, takes a model and a time, and returns a vector of results. - discounter :: Discretize a => a -> Double -> MonteCarlo (Observables, Double) (U.Vector Double) + discountState :: Discretize a => a -> Time -> MonteCarlo (MCObservables, Time) Double + discountState m t = return $ discount m t + {-# INLINE discountState #-} + -- | Non-stateful discounting function...might need to find a better place to put this. + discount :: Discretize a => a -> Time -> Double + -- | Stateful forward generator for a given model at a certain time. - forwardGen :: Discretize a => a -> Double -> MonteCarlo (Observables, Double) (U.Vector Double) + forwardGen :: Discretize a => a -> Time -> MonteCarlo (MCObservables, Time) Double -- | Internal function to evolve a model to a given time. - evolve' :: Discretize a => a -- ^ model - -> Double -- ^ time to evolve to - -> Bool -- ^ whether or not to use flipped variates - -> MonteCarlo (Observables, Double) () -- ^ computation result + evolve' :: Discretize a => a -- ^ model + -> Time -- ^ time to evolve to + -> Bool -- ^ whether or not to use flipped variates + -> MonteCarlo (MCObservables, Time) () -- ^ computation result -- | Determines the maximum size time-step for discretization purposes. Defaults to 1/250. maxStep :: Discretize a => a -> Double maxStep _ = 1/250 + {-# INLINE maxStep #-} -- | Perform a simulation of a compiled basket of contingent claims. simulateState :: Discretize a => - a -- ^ model - -> ContingentClaimBasket -- ^ compilied basket of claims + a -- ^ model + -> ContingentClaim -- ^ compilied basket of claims -> Int -- ^ number of trials -> Bool -- ^ antithetic? - -> MonteCarlo (Observables, Double) Double -- ^ computation result - simulateState modl (ContingentClaimBasket cs ts) trials anti = do - initialize modl trials - avg <$> process Map.empty (U.replicate trials 0) cs ts - where - process m cfs ccs@(c@(ContingentClaim' t _ _):cs') (obsT:ts') = - if t >= obsT then do - evolve modl obsT anti - obs <- gets fst - let m' = Map.insert obsT obs m - process m' cfs ccs ts' - else do - evolve modl t anti - let cfs' = processClaimWithMap c m - d <- discounter modl obsT - let cfs'' = cfs' |*| d - process m (cfs |+| cfs'') cs' (obsT:ts') + -> MonteCarlo (MCObservables, Time) Double -- ^ computation result + simulateState modl (ContingentClaim ccb) trials anti = avg <$> replicateM trials singleTrial + where + singleTrial = initialize modl >> + process (0 :: Double) Map.empty ccb [] - process m cfs (c:cs') [] = do - d <- discounter modl (payoutTime c) - let cfs' = d |*| processClaimWithMap c m - process m (cfs |+| cfs') cs' [] - process _ cfs _ _ = return cfs + process discCFs obsMap c@(CCProcessor t mf:ccs) allcfs@(CashFlow cft amt:cfs) = + if t > cft then do + evolve modl cft anti + d <- discountState modl cft + process (discCFs+d*amt) obsMap c cfs + else do + evolve modl t anti + obs <- gets fst + let obsMap' = Map.insert t obs obsMap + case mf of + Nothing -> process discCFs obsMap' ccs allcfs + Just f -> let newCFs = map ($obsMap') f + insertCFList xs cfList = foldl' (flip insertCF) cfList xs in + process discCFs obsMap' ccs (insertCFList newCFs allcfs) - v1 |+| v2 = U.zipWith (+) v1 v2 - v1 |*| v2 = U.zipWith (*) v1 v2 + process discCFs obsMap (CCProcessor t mf:ccs) [] = do + evolve modl t anti + obs <- gets fst + let obsMap' = Map.insert t obs obsMap + case mf of + Nothing -> process discCFs obsMap' ccs [] + Just f -> let newCFs = map ($obsMap') f + insertCFList xs cfList = foldl' (flip insertCF) cfList xs in + process discCFs obsMap' ccs (insertCFList newCFs []) - avg v = U.sum v / fromIntegral (U.length v) + process discCFs obsMap [] (cf:cfs) = do + evolve modl (cfTime cf) anti + d <- discountState modl $ cfTime cf + process (discCFs+d*cfAmount cf) obsMap [] cfs + process discCFs _ _ _ = return $! discCFs + + insertCF (CashFlow t amt) (CashFlow t' amt':cfs) + | t > t' = CashFlow t' amt' : insertCF (CashFlow t amt) cfs + | otherwise = CashFlow t amt : CashFlow t' amt' : cfs + insertCF cf [] = [cf] + + avg v = sum v / fromIntegral trials + -- | Runs a simulation for a 'ContingentClaim'. runSimulation :: (Discretize a, MonadRandom (StateT b Identity)) => - a -- ^ model - -> ContingentClaim -- ^ claims to value - -> b -- ^ initial random state - -> Int -- ^ trials - -> Bool -- ^ whether to use antithetic variables - -> Double -- ^ final value - runSimulation modl ccs seed trials anti = runMC run seed (Observables [], 0) + a -- ^ model + -> ContingentClaim -- ^ claims to value + -> b -- ^ initial random state + -> Int -- ^ trials + -> Bool -- ^ whether to use antithetic variables + -> Double -- ^ final value + runSimulation modl ccs seed trials anti = runMC run seed (Observables [], Time 0) where - run = simulateState modl (ccBasket ccs) trials anti + run = simulateState modl ccs trials anti -- | Like 'runSimulation', but splits the trials in two and does antithetic variates. runSimulationAnti :: (Discretize a, @@ -145,16 +166,4 @@ -- | 'runSimulationAnti' with a default random number generator. quickSimAnti :: Discretize a => a -> ContingentClaim -> Int -> Double - quickSimAnti mdl opts trials = runSimulationAnti mdl opts (pureMT 500) trials - --- | Utility function to get the number of trials. -getTrials :: MonteCarlo (Observables, Double) Int -getTrials = U.length <$> gets (obsHead . fst) - - -processClaimWithMap :: ContingentClaim' -> Map.Map Double Observables -> U.Vector Double -processClaimWithMap (ContingentClaim' _ c obs) m = c vals - where - vals = map (\(t , g , f) -> U.map f . g $ m Map.! t) obs - - + quickSimAnti mdl opts trials = runSimulationAnti mdl opts (pureMT 500) trials
− src/Quant/Test.hs
@@ -1,63 +0,0 @@-module Quant.Test ( - baseYC - , val - , black - , opt - , val' - , val'' - , val''' - , val'''' - , heston - , opt' - , opt'' - ) -where - -import Quant.MonteCarlo -import Quant.YieldCurve -import Quant.ContingentClaim -import Quant.Models.Black -import Quant.Models.Heston - -baseYC = FlatCurve 0.05 --create a flat yield curve with a 5% rate - -black = Black - 100 --initial stock price - 0.2 --volatility - baseYC --forward generator - baseYC --discount function - -opt = vanillaOption Put 100 1 --make a vanilla put, struck at 100, maturing at time 1 - -val = quickSim black opt 10000 --Run a Monte Carlo on opt in a a black model with 10000 trials - -opt' = multiplier 100 - $ vanillaOption Call 100 1 ++ short (vanillaOption Call 120 1) --Make a call spread with a 100 unit notional - -val' = quickSimAnti black opt' 10000 --Run a Monte Carlo on the call spread; use antithetic variates - --Returns - -black' = Black - 100 --initial stock price - 0.2 --volatility - (NetYC (FlatCurve 0.05) (FlatCurve 0.02)) --forward generator, now with a 2% dividend yield - baseYC --discount rate - -val'' = quickSimAnti black' opt' 10000 - ---Let's try it with a Heston model -heston = Heston - 100 - 0.04 --initial variance - 0.04 --final variance - 0.2 --volvol - (-0.7) --correlation between processes - 1.0 --mean reversion speed - baseYC --forward generator - baseYC --discount function - -val''' = quickSimAnti heston opt' 10000 --price the call spread in the Heston model - -opt'' = terminalOnly 1 $ \x -> x*x --create an option that pays off based on the square of its underlying - -val'''' = quickSimAnti heston opt'' 10000 --price it in the Heston model
+ src/Quant/Time.hs view
@@ -0,0 +1,21 @@+ +module Quant.Time ( + Time(..) + , timeDiff + , timeOffset + , timeFromZero + ) where + +data Time = Time Double deriving (Eq,Show,Ord) + +timeDiff :: Time -> Time -> Double +timeDiff (Time x) (Time y) = y - x +{-# INLINE timeDiff #-} + +timeOffset :: Time -> Double -> Time +timeOffset (Time x) y = Time (x+y) +{-# INLINE timeOffset #-} + +timeFromZero :: Time -> Double +timeFromZero (Time x) = x +{-# INLINE timeFromZero #-}
+ src/Quant/Types.hs view
@@ -0,0 +1,24 @@+ +module Quant.Types ( + CashFlow(..) + , Observables(..) + , MCObservables + , OptionType(..) + ) where + +import Quant.Time + +data CashFlow = CashFlow { + cfTime :: Time + , cfAmount :: Double +} + +-- | Observables are the observables available in a Monte Carlo simulation. +--Most basic MCs will have one observables (Black-Scholes) whereas more +--complex ones will have multiple (i.e. Heston-Hull-White). +data Observables a = Observables { obsGet :: [a] } deriving (Show) + +type MCObservables = Observables Double + +-- | Type for Put or Calls +data OptionType = Put | Call deriving (Eq,Show)
src/Quant/VolSurf.hs view
@@ -1,10 +1,13 @@ module Quant.VolSurf ( VolSurf (..) , FlatSurf (..) + , GridSurf (..) ) where - +import Quant.Math.Interpolation import Quant.YieldCurve +import Quant.Time +import qualified Data.Map as M {- | The 'VolSurf' class defines the basic operations of a volatility surface. @@ -13,20 +16,22 @@ -} class VolSurf a where -- | Calculate the implied vol for a given strike/maturity. - vol :: VolSurf a => a -> Double -> Double -> Double + vol :: VolSurf a => a -> Double -> Time -> Double -- | Calculate the variance at a given strike/maturity. - var :: VolSurf a => a -> Double -> Double -> Double - var vs s t = v*v*t - where v = vol vs s t + var :: VolSurf a => a -> Double -> Time -> Double + var vs s t = v*v*t' + where + v = vol vs s t + t' = timeFromZero t -- | Calculates Dupire local vol for a given strike/maturity/forward generating yield curve. - localVol :: (VolSurf a, YieldCurve b) => a -- ^ Volatility surface - -> Double -- ^ Initial stock price - -> b -- ^ 'YieldCurve' to generate forwards - -> Double -- ^ Current stock level - -> Double -- ^ Time - -> Double -- ^ Local volatility + localVol :: (VolSurf a, YieldCurve b) => a -- ^ Volatility surface + -> Double -- ^ Initial stock price + -> b -- ^ 'YieldCurve' to generate forwards + -> Double -- ^ Current stock level + -> Time -- ^ Time + -> Double -- ^ Local volatility localVol v s0 rcurve k t | w==0.0 || solution<0.0 = sqrt dwdt | otherwise = sqrt solution @@ -35,23 +40,39 @@ f = s0/dr y = log $ k/f dy = 1.0E-6 - [kp, km] = [k*exp dy, k/exp dy] + kp = k*exp dy + km = k/exp dy [w, wp, wm] = map (\x->var v (x/s0) t) [k, kp, km] dwdy = (wp-wm)/2.0/dy d2wdy2 = (wp-2.0*w+wm)/dy/dy - dt = min 0.0001 (t/2.0) + dt = min 0.0001 (timeFromZero t/2.0) dwdt = let strikept = k*dr/drpt strikemt = k*dr/drmt - drpt = disc rcurve $ t+dt - drmt = disc rcurve $ t-dt - in case t of - 0 -> (var v (strikept/s0) (t+dt) -w)/dt - _ -> (var v (strikept/s0) (t+dt)-var v (strikemt/s0) (t-dt))/2.0/dt + drpt = disc rcurve $ timeOffset t dt + drmt = disc rcurve $ timeOffset t (-dt) + in case timeFromZero t of + 0 -> (var v (strikept/s0) (timeOffset t dt) -w)/dt + _ -> (var v (strikept/s0) (timeOffset t dt)-var v (strikemt/s0) (timeOffset t (-dt)))/2.0/dt solution = dwdt/(1.0-y/w*dwdy+0.25*(-0.25-1.0/w+y*y/w/w)*dwdy*dwdy+0.5*d2wdy2) -- |A flat surface has one volatility at all times/maturities. data FlatSurf = FlatSurf Double instance VolSurf FlatSurf where - vol (FlatSurf x) _ _ = x+ vol (FlatSurf x) _ _ = x + +data GridSurf = GridSurf { + gridStrikes :: [Double] + , gridMaturities :: [Time] + , gridQuotes :: M.Map (Double, Time) Double + , gridStrikeInterpolator :: Interpolator1d + , gridTimeInterpolator :: Interpolator1d +} + +instance VolSurf GridSurf where + vol (GridSurf sts mats quotes vInterp tInterp) strike t = tInterp mats' interpolatedVs $ timeFromZero t + where + mats' = map timeFromZero mats + interpolatedVs = map ((\k -> vInterp sts k strike) . allForT) mats + allForT t' = map (\ x -> quotes M.! (x, t')) sts
src/Quant/YieldCurve.hs view
@@ -4,6 +4,7 @@ , NetYC (..) ) where +import Quant.Time {- | The 'YieldCurve' class defines the basic operations of a yield curve. @@ -12,22 +13,24 @@ -} class YieldCurve a where -- | Calculate the discount factor for a given maturity. - disc :: YieldCurve a => a -> Double -> Double + disc :: YieldCurve a => a -> Time -> Double -- | Calculate the forward rate between a t1 and t2 - forward :: YieldCurve a => a -> Double -> Double -> Double - forward yc t1 t2 = (/(t2-t1)) $ log $ disc yc t1 / disc yc t2 + forward :: YieldCurve a => a -> Time -> Time -> Double + forward yc t1 t2 = (/(timeFromZero t2-timeFromZero t1)) $ log $ disc yc t1 / disc yc t2 + {-# INLINE forward #-} -- | Calculate the spot rate for a given maturity. - spot :: YieldCurve a => a -> Double -> Double - spot yc t = forward yc 0 t + spot :: YieldCurve a => a -> Time -> Double + spot yc t = forward yc (Time 0) t + {-# INLINE spot #-} -- |A flat curve is just a flat curve with one continuously -- compounded rate at all points on the curve. data FlatCurve = FlatCurve Double instance YieldCurve FlatCurve where - disc (FlatCurve r) t = exp (-r*t) + disc (FlatCurve r) t = exp (-r*timeFromZero t) -- | 'YieldCurve' that represents the difference between two 'YieldCurve's. data NetYC a = NetYC a a