Hastructure-0.45.0: src/Analytics.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Analytics (calcConvexity,calcDuration,pv,calcWAL,pv2,pv3
,fv2,pv21,calcRequiredAmtForIrrAtDate,calcIRR
,calcSurvivorFactors)
where
import Types
import Lib
import Util
import DateUtil
import Data.Aeson hiding (json)
import Language.Haskell.TH
import Data.Aeson.TH
import Data.Aeson.Types
import GHC.Generics
import Data.Ratio
import Numeric.RootFinding
import Debug.Trace
debug = flip trace
calcSurvivorFactors :: Date -> [Date] -> Double -> [Double]
calcSurvivorFactors sd ds 0 = replicate (length ds) 1.0
calcSurvivorFactors sd ds survivalRate =
let
yearFractions::[Double] = [ realToFrac (daysBetween sd d) / 365.0 | d <- ds ]
factors = [ (1 - survivalRate) ** x | x <- yearFractions ]
in
factors
-- ^ calculate the Weighted Average Life of cashflow, with unit option to Monthly or Yearly
calcWAL :: TimeHorizion -> Balance -> Date -> [(Balance,Date)] -> Balance
calcWAL th bal d ps =
let
interval = case th of
ByYear -> 365
ByMonth -> 30
weightedAmts = [ mulBR futureAmt ((daysBetween d futureDate) % interval) | (futureAmt,futureDate) <- ps ]
in
sum weightedAmts / bal
calcDuration :: DayCount -> Date -> [(Date,Balance)] -> Ts -> Rate
calcDuration dc d ps pricingCurve
= (foldr (\(_d,_b) acc ->
(*)
(divideBB (pv pricingCurve d _d _b) presentValue)
(yearCountFraction dc d _d)
+ acc)
0.0000
ps)
where
presentValue = sum [ pv pricingCurve d _d _b | (_d,_b) <- ps ]
calcConvexity :: DayCount -> Date -> [(Date,Balance)] -> Ts -> Rate
calcConvexity dc d ps pricingCurve
= toRational $
(*)
presentValue' $
(foldr (\(_t,_c,_f) acc ->
(_t * (_t + 1) * fromRational _c) / ((1.000 + _f) ** (_t+2))
)
0.0000
(zip3 ts payments pvFactors)) -- `debug` ("'v"++show presentValue'++"others"++ show (zip3 ts payments pvFactors))
where
pvFactors::[Double] = fromRational <$> getValByDate pricingCurve Inc <$> fst <$> ps
presentValue'::Double = 1 / (fromRational . toRational) (sum [ pv pricingCurve d _d _b | (_d,_b) <- ps ])
payments = toRational . snd <$> ps
ts::[Double] = fromRational <$> yearCountFraction dc d <$> fst <$> ps
-- ^ calculate present value of input amount in future with given a curve and PV date
pv :: Ts -> Date -> Date -> Amount -> Amount
pv pc today d amt =
realToFrac $ (realToFrac amt) * (1 / factor) -- `debug` ("DF:"++show factor++" PV AMT"++show amt)
where
distance::Double = fromIntegral $ daysBetween today d
discount_rate = fromRational $ getValByDate pc Exc d -- `debug` ("Get val by ts"++show pc ++">>d"++ show d)
factor::Double = (1 + realToFrac discount_rate) ** (distance / 365) -- `debug` ("discount_rate"++show(discount_rate) ++" dist days=>"++show(distance))
-- ^ calculate present value in the future using constant rate
pv2 :: IRate -> Date -> Date -> Amount -> Amount
pv2 discount_rate today d amt
| today == d = amt
| otherwise
= realToFrac $ (realToFrac amt) * (1/denominator) -- `debug` ("pv: cash"++ show amt++" deno"++ show denominator++">> rate"++show discount_rate)
where
denominator::Double = (1 + realToFrac discount_rate) ** (distance / 365)
distance::Double = fromIntegral $ daysBetween today d -- `debug` ("days betwwen"++ show (daysBetween today d)++">>"++ show d ++ ">>today>>"++ show today)
-- ^ calculate present value to specific date given a series of amount with dates
pv21 :: IRate -> Date -> [Date] -> [Amount] -> Balance
pv21 r d ds vs = sum [ pv2 r d _d amt | (_d,amt) <- zip ds vs ]
-- ^ using double for ridder's method
pv2' :: Double -> Date -> Date -> Double -> Double
pv2' r today d amt
| amt == 0 = 0
| today == d = amt
| otherwise
= amt * (1/denominator) -- `debug` ("pv: cash"++ show amt++" deno"++ show denominator++">> rate"++show discount_rate)
where
denominator::Double = (1 + r) ** (distance / 365)
distance::Double = fromIntegral $ daysBetween today d -- `debug` ("days betwwen"++ show (daysBetween today d)++">>"++ show d ++ ">>today>>"++ show today)
pv22 :: Double -> Date -> [Date] -> [Double] -> Double
pv22 r d ds vs = sum [ pv2' r d _d amt | (_d,amt) <- zip ds vs ]
-- ^ calcualte present value given a series of amount with dates
pv3 :: Ts -> Date -> [Date] -> [Amount] -> Balance
pv3 pvCurve pricingDate ds vs
= let
rs = fromRational <$> getValByDates pvCurve Inc ds
pvs = [ pv2 r pricingDate d amt | (r,d,amt) <- zip3 rs ds vs ]
in
sum pvs
pv3' :: Ts -> Date -> [Date] -> [Amount] -> Balance
pv3' pvCurve pricingDate ds vs
= let
rs = fromRational <$> getValByDates pvCurve Inc ds
vs' = (fromRational . toRational) <$> vs
pvs = [ pv2' r pricingDate d amt | (r,d,amt) <- zip3 rs ds vs' ]
in
fromRational . toRational $ foldr (+) 0 pvs
fv2 :: IRate -> Date -> Date -> Amount -> Amount
fv2 discount_rate today futureDay amt
= realToFrac $ realToFrac amt * factor
where
factor::Double = (1 + realToFrac discount_rate) ** (distance / 365)
distance::Double = fromIntegral $ daysBetween today futureDay
calcPvFromIRR :: Double -> [Date] -> [Amount] -> Date -> Double -> Double
calcPvFromIRR irr [] _ d amt = 0
calcPvFromIRR irr ds vs d amt =
let
begDate = head ds
vs' = fromRational . toRational <$> vs
pv = pv22 irr begDate (ds++[d]) (vs'++[amt])
in
(fromRational . toRational) pv
-- ^ calculate IRR of a series of cashflow
calcRequiredAmtForIrrAtDate :: Double -> [Date] -> [Amount] -> Date -> Maybe Amount
calcRequiredAmtForIrrAtDate irr [] _ d = Nothing
calcRequiredAmtForIrrAtDate irr ds vs d =
let
itertimes = 500
def = RiddersParam { riddersMaxIter = itertimes, riddersTol = RelTol 0.00000001}
in
case ridders def (0.0001,100000000000000) (calcPvFromIRR irr ds vs d) of
Root finalAmt -> Just (fromRational (toRational finalAmt))
_ -> Nothing
-- ^ calc IRR from a cashflow
calcIRR :: [Date] -> [Amount] -> Either String Rate
calcIRR _ [] = Left "No cashflow amount"
calcIRR [] _ = Left "No cashflow date"
calcIRR ds vs
| all (>= 0) vs = Left $ "All cashflow can't be all positive:"++ show vs
| all (<= 0) vs = Left $ "All cashflow can't be all negative:"++ show vs
| all (== 0) vs = Left "All cashflow can't be all zeros"
| otherwise =
let
itertimes = 1000
def = RiddersParam { riddersMaxIter = itertimes, riddersTol = RelTol 0.000001}
beginDate = head ds
vs' = fromRational . toRational <$> vs
sumOfPv irr = pv22 irr beginDate ds vs'
in
case ridders def (-1,1000) sumOfPv of
Root irrRate -> Right $ toRational irrRate
NotBracketed -> Left $ "IRR: not bracketed" ++ show vs' ++ " and dates"++ show ds
SearchFailed -> Left $ "IRR: search failed: can't be calculated with input "++ show vs++" and dates"++ show ds