packages feed

monad-par-0.1: examples/blackscholes.hs

{-# LANGUAGE RecordWildCards, CPP, ScopedTypeVariables, FlexibleInstances  #-}

-- Ported from CnC/C++ program by Ryan Newton
-- Then ported again from the Haskell-CnC interface to monad-par. [2011.02.16]

-- Description
-- ===========

-- The Black-Scholes equation is a differential equation that describes how,
-- under a certain set of assumptions, the value of an option changes as the 
-- price of the underlying asset changes.

-- The formula for a put option is similar. The cumulative normal distribution 
-- function, CND(x), gives the probability that normally distributed random
-- variable will have a value less than x. There is no closed form expression for
-- this function, and as such it must be evaluated numerically. The other 
-- parameters are as follows: S underlying asset's current price, 
-- X the strike price, T time to the expiration date, r risk-less rate of return,
-- and v stock's volatility.

-- Usage
-- =====

-- The command line is:

-- blackscholes b n
--     b  : positive integer for the size of blocks
--     n  : positive integer for the number of options    
-- e.g.
-- blackscholes 100000 100 4

import Control.Seq
import Control.Monad
import Control.DeepSeq
import Control.Exception

import Control.Monad.Par
import Control.Monad.Par.AList

import Data.Array
import Data.List
import qualified Data.Array.Unboxed as U
import System.Environment

--------------------------------------------------------------------------------

type FpType = Float

-- This tuple contains the inputs for one invocation of our kernel
data ParameterSet =  ParameterSet {
	sptprice   :: FpType,
	strike     :: FpType,
	rate       :: FpType,
	volatility :: FpType ,
	otime      :: FpType,
	otype      :: Bool
} deriving Show

data_init :: Array Int ParameterSet

-- This defines some hard coded data as a big constant array:
#include "blackscholes_data.hs"
size_init = let (s,e) = bounds data_init in e - s + 1

inv_sqrt_2xPI = 0.39894228040143270286

--------------------------------------------------------------------------------
-- Scalar code follows:

cndf :: FpType -> FpType
cndf inputX = if sign then 1.0 - xLocal else xLocal
  where 
    sign = inputX < 0.0
    inputX' = if sign then -inputX else inputX
    
    -- Compute NPrimeX term common to both four & six decimal accuracy calcs
    xNPrimeofX = inv_sqrt_2xPI * exp(-0.5 * inputX * inputX);

    xK2 = 1.0 / (0.2316419 * inputX + 1.0);    
    xK2_2 = xK2   * xK2; -- Need all powers of xK2 from ^1 to ^5:
    xK2_3 = xK2_2 * xK2;
    xK2_4 = xK2_3 * xK2;
    xK2_5 = xK2_4 * xK2;
    
    xLocal   = 1.0 - xLocal_1 * xNPrimeofX;
    xLocal_1 = xK2   *   0.319381530  + xLocal_2;
    xLocal_2 = xK2_2 * (-0.356563782) + xLocal_3 + xLocal_3' + xLocal_3'';
    xLocal_3   = xK2_3 * 1.781477937;
    xLocal_3'  = xK2_4 * (-1.821255978);
    xLocal_3'' = xK2_5 * 1.330274429;


blkSchlsEqEuroNoDiv :: FpType -> FpType -> FpType -> FpType -> FpType -> Bool -> Float -> FpType
blkSchlsEqEuroNoDiv sptprice strike rate volatility time otype timet =
   if not otype
   then (sptprice * nofXd1) - (futureValueX * nofXd2)
   else let negNofXd1 = 1.0 - nofXd1
	    negNofXd2 = 1.0 - nofXd2
	in (futureValueX * negNofXd2) - (sptprice * negNofXd1)
 where 
   logValues  = log( sptprice / strike )                
   xPowerTerm = 0.5 * volatility * volatility
   xDen = volatility * sqrt(time)
   xD1  = (((rate + xPowerTerm) * time) + logValues) / xDen
   xD2  = xD1 -  xDen

   nofXd1 = cndf xD1 
   nofXd2 = cndf xD1    
   futureValueX = strike *  exp ( -(rate) * (time) )

--------------------------------------------------------------------------------


computeSegment :: Int -> Int -> U.UArray Int FpType
computeSegment granularity t = arr
 where 
  arr = U.listArray (0, granularity-1) $
        Prelude.map fn [0 .. granularity-1]
  fn i = let ParameterSet { .. } = data_init U.! ((t+i) `mod` size_init)
	 in blkSchlsEqEuroNoDiv sptprice strike rate volatility otime otype 0

--------------------------------------------------------------------------------

-- No need to go deeper here because its unboxed, right?
instance NFData (U.UArray Int FpType) where

main = do args <- getArgs 
          let (numOptions, granularity) =
               case args of 
  	         []      -> (10000, 1000)
  	         [b]     -> (10, read b)
	         [b,n] -> (read n, read b)

          if granularity > numOptions
	   then error "Granularity must be bigger than numOptions!!"
	   else return ()

	  putStrLn$ "Running blackscholes, numOptions "++ show numOptions ++ " and block size " ++ show granularity

          let numChunks = numOptions `quot` granularity
--	      results = runPar$ parMap (computeSegment granularity . (* granularity)) [0..numChunks-1]

#if 1
	      results = runPar$ parMap (computeSegment granularity) [0, granularity .. numOptions-1]
#else
-- Not working right yet [2011.02.18]
              results = toList$ runPar$ 
			parBuild 1 0 (numChunks-1) 
			  (computeSegment granularity . (* granularity))
#endif
	      sum = foldl1' (+) $ map (U.! 0) results

	  putStrLn$ "Final checksum: "++ show sum