packages feed

Hastructure-0.45.0: src/InterestRate.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveGeneric #-}

module InterestRate
  (ARM(..),RateType(..),runInterestRate2,runInterestRate,UseRate(..)
  ,getRateResetDates,getDayCount,calcInt, calcIntRate,calcIntRateCurve
  ,getSpread,_getSpread)
  
  where

import Language.Haskell.TH
import Data.Aeson       hiding (json)
import Data.Aeson.TH
import Data.Maybe
import Data.Fixed
import GHC.Generics
import DateUtil
import Data.Decimal

import Types
import Util
import Lib

import Debug.Trace
debug = flip trace

type InitPeriod = Int 
type PeriodicCap = Maybe Spread
type LifetimeCap = Maybe IRate
type PaymentCap = Maybe Balance
type RateFloor = Maybe IRate
type RateCap = Maybe IRate
type InitCap = Maybe IRate
type ResetDates = [Date]
type StartRate = IRate

data RateType = Fix DayCount IRate
              | Floater DayCount Index Spread IRate DatePattern RateFloor RateCap (Maybe (RoundingBy IRate))
              deriving (Show,Generic,Eq,Ord)

getDayCount :: RateType -> DayCount
getDayCount (Fix dc _) = dc
getDayCount (Floater dc _ _ _ _ _ _ _ ) = dc

_getSpread :: RateType -> Maybe Spread
_getSpread (Fix _ _) = Nothing
_getSpread (Floater _ _ spd _ _ _ _ _) = Just spd

data ARM = ARM InitPeriod InitCap PeriodicCap LifetimeCap RateFloor
         | OtherARM
         deriving (Show,Generic,Eq,Ord)

getRateResetDates :: Date -> Date -> Maybe RateType -> Dates
getRateResetDates _ _ Nothing = []
getRateResetDates _ _ (Just (Fix _ _)) = []
getRateResetDates sd ed (Just (Floater _ _ _ _ dp _ _ _)) = genSerialDatesTill2 NO_IE sd dp ed 

runInterestRate :: ARM -> StartRate -> RateType -> ResetDates -> Ts -> [IRate]
runInterestRate (ARM ip icap pc lifeCap floor) sr (Floater _ _ spd _ _ _ _ mRoundBy) resetDates rc
  = sr:cappedRates
    where 
      fr:rrs = (spd +) . fromRational <$> getValByDates rc Inc resetDates
      firstRate 
        | isNothing icap = fr
        | (sr + fromMaybe 0 icap) <= fr = sr + fromMaybe 0 icap
        | otherwise = fr
      rounder = roundingByM mRoundBy
      restRates = tail $
                    scanl 
                      (\lastRate idxRate -> 
                          if isNothing pc then -- periodic cap
                            rounder idxRate
                          else
                            if lastRate + (fromMaybe 0 pc) <= idxRate then 
                              rounder $ lastRate + (fromMaybe 0 pc)
                            else 
                              rounder idxRate)
                      firstRate
                      rrs
      flooredRates = max (fromMaybe 0 floor) <$> (firstRate:restRates) -- `debug` ("reset rates" ++ show (firstRate:restRates))
      cappedRates = min (fromMaybe 1 lifeCap) <$> flooredRates 

runInterestRate2 :: ARM -> (Date,StartRate) -> RateType -> ResetDates -> Ts -> Ts
runInterestRate2 arm (d,sr) floater resetDates rc
  = mkRateTs $ zip (d:resetDates) resultRates -- `debug` ("Result Rate"++show resultRates)
    where 
     resultRates = runInterestRate arm sr floater resetDates rc 
     
calcIntRate :: Date -> Date -> IRate -> DayCount -> IRate
calcIntRate startDate endDate intRate dayCount =
  let 
    yf = yearCountFraction dayCount startDate endDate
  in 
    intRate * fromRational yf

calcIntRateCurve :: DayCount -> IRate -> [Date] -> [IRate]
calcIntRateCurve dc r ds 
  = [ calcIntRate sd ed r dc |  (sd,ed) <- zip (init ds) (tail ds) ]

calcInt :: Balance -> Date -> Date -> IRate -> DayCount -> Amount
calcInt bal startDate endDate intRate dayCount =
  let 
    yfactor = yearCountFraction dayCount startDate endDate
  in 
    mulBR bal (yfactor * toRational intRate)

class UseRate x where 
  isAdjustbleRate :: x -> Bool
  -- get first index available,if not found return Nothing
  getIndex :: x -> Maybe Index
  getIndexes :: x -> Maybe [Index]
  getResetDates :: x -> Dates
  getSpread :: x -> Maybe Spread


$(deriveJSON defaultOptions ''ARM)
$(deriveJSON defaultOptions ''RateType)