{-# LANGUAGE BangPatterns        #-}
{-# LANGUAGE ScopedTypeVariables #-}
module QuantLib.Stochastic.Process
        ( module QuantLib.Stochastic.Process )
        where

import           Data.List                  (foldl')
import           QuantLib.Stochastic.Random (NormalGenerator (..))

-- | Discretization of stochastic process over given interval
class Discretize b where
        dDrift :: StochasticProcess a => a->b->Dot->Double
        dDiff  :: StochasticProcess a => a->b->Dot->Double
        dDt    :: StochasticProcess a => a->b->Dot->Double

-- | 1D Stochastic process
class StochasticProcess a where
        drift  :: a -> Dot -> Double
        diff   :: a -> Dot -> Double
        evolve :: Discretize b=> b -> a -> Dot -> Double -> Dot
        evolve discr p dot dw = Dot newT newX
                where   !newT = getT dot + dDt p discr dot
                        !newX = getX dot + dDrift p discr dot + dDiff p discr dot * dw

-- | Dot. t and x pair
data Dot = Dot { getT :: {-# UNPACK #-} !Double, getX :: {-# UNPACK #-} !Double }
        deriving (Show, Eq)

-- | Path as list of Dots
type Path = [Dot]

-- | Generates sample path for given stochastic process under discretization and normal generator for given amount of steps, starting from x0
generatePath :: (StochasticProcess a, NormalGenerator b, Discretize c) => b->c->a->Int->Dot->Path
generatePath rnd discr sp steps x0 = reverse path
  where
        (!list, _) = foldl' generator ([], rnd) [1..steps]
        !path = foldl' evolver [x0] list
        evolver p dw = evolve discr sp (head p) dw : p
        generator (l, r) _ = (p:l, newRnd)
          where
                (!p, newRnd) = ngGetNext r

-- | Geometric Brownian motion
data GeometricBrownian = GeometricBrownian {
        gbDrift :: Double,
        gbDiff  :: Double
        } deriving (Show)

instance StochasticProcess GeometricBrownian where
        drift p (Dot _ x) = gbDrift p * x
        diff  p (Dot _ x) = gbDiff p  * x

-- | Ito process
data ItoProcess = ItoProcess {
        ipDrift :: Dot->Double,
        ipDiff  :: Dot->Double
        }

instance StochasticProcess ItoProcess where
        drift   = ipDrift
        diff    = ipDiff

-- | Square-root process
data SquareRootProcess = SquareRootProcess {
        srpSpeed :: Double,
        srpMean  :: Double,
        srpSigma :: Double
        } deriving (Show)

instance StochasticProcess SquareRootProcess where
       drift p (Dot _ x) = srpSpeed p * (srpMean p - x)
       diff  p (Dot _ x) = srpSigma p * sqrt x

-- | Ornstein-Uhlenbeck process
data OrnsteinUhlenbeckProcess = OrnsteinUhlenbeckProcess {
        oupSpeed :: Double,
        oupLevel :: Double,
        oupSigma :: Double
        } deriving (Show)

instance StochasticProcess OrnsteinUhlenbeckProcess where
        drift p (Dot _ x) = oupSpeed p * (oupLevel p - x)
        diff  p _         = oupSigma p

-- | Generalized Black-Scholes process
data BlackScholesProcess = BlackScholesProcess {
        bspRiskFree :: Double->Double,
        bspDividend :: Double->Double,
        bspBlackVol :: Dot->Double
        }

instance StochasticProcess BlackScholesProcess where
        drift (BlackScholesProcess r q v) dot = r (getT dot) - q ( getT dot) - 0.5 * v dot ** 2
        diff = bspBlackVol