packages feed

Hastructure-0.45.0: src/AssetClass/Installment.hs

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

module AssetClass.Installment 
  (projectInstallmentFlow, updateOriginDate)
  where

import qualified Data.Time as T
import Data.Ratio

import Data.Aeson hiding (json)
import Language.Haskell.TH
import Data.Maybe
import Data.List
import qualified Data.DList as DL
import Data.Aeson.TH
import qualified Data.Map as Map
import Data.Aeson.Types
import GHC.Generics

import Asset
import InterestRate
import qualified Assumptions as A
import Types 
import Lib
import Util
import DateUtil
import qualified Cashflow as CF

import AssetClass.AssetBase

import Debug.Trace
import AssetClass.AssetCashflow
import qualified Asset as Ast
import Control.Lens hiding (element)
import Control.Lens.TH
debug = flip trace


projectInstallmentFlow :: (Balance,Date,(Balance,Balance),IRate,Rational,AmortPlan,Int) -> (Dates, [DefaultRate], [PrepaymentRate], [Int]) -> (DL.DList CF.TsRow, Balance ,Rational)
projectInstallmentFlow (startBal, lastPaidDate, (originRepay,originInt), startRate,begFactor,pt,ot) (cfDates, defRates, ppyRates, remainTerms)
  = let 
      initRow = CF.LoanFlow lastPaidDate startBal 0.0 0.0 0.0 0.0 0.0 0.0 startRate Nothing
      calcPrin _rt _bal _opmt _factor = case _rt of
                                          1 -> _bal
                                          0 -> 0
                                          _ -> mulBR _opmt _factor
    in
      foldl
        (\(acc,begBal,factor) (pDate, ppyRate, defRate, rt) -> 
          let 
            -- begBal = view CF.tsRowBalance (last acc)
            newDefault = mulBR begBal defRate
            newPrepay = mulBR (begBal - newDefault) ppyRate
            intBal = begBal - newDefault - newPrepay
            newFactor = factor * (1-defRate) * (1- ppyRate)
            newInt = case pt of 
                      F_P -> if rt > 0 then 
                               mulBR originInt newFactor
                             else
                               0
                      PO_FirstN n -> if (ot-rt) >= n then
                                          mulBR originInt newFactor
                                        else
                                          0 
            newPrin = calcPrin rt intBal originRepay newFactor
            endBal = intBal - newPrin
          in 
            (DL.snoc acc (CF.LoanFlow pDate endBal newPrin newInt newPrepay newDefault 0.0 0.0 startRate Nothing)
            ,endBal
            ,newFactor))
        (DL.singleton initRow, startBal, begFactor)
        (zip4 cfDates ppyRates defRates remainTerms)


instance Asset Installment where
  calcCashflow inst@(Installment (LoanOriginalInfo ob or ot p sd ptype _) cb rt st) asOfDay _
    = Right $ CF.CashFlowFrame (begBal,asOfDay,Nothing) flows 
     where 
        lastPayDate:cf_dates = lastN (rt+1) $ sd:getPaymentDates inst 0
        opmt = divideBI ob ot  
        schedule_balances = scanl (-) ob (replicate ot opmt) 
        current_schedule_bal =  schedule_balances !! (ot - rt)   
        ofee = mulBIR ob (getOriginRate inst)

        factor =  cb / current_schedule_bal 
        cpmt = opmt * factor 
        cfee = ofee * factor 
        orate = getOriginRate inst

        stressed_bal_flow = map (* factor)  $ lastN rt schedule_balances
        prin_flow = replicate rt cpmt 
        int_flow =  case ptype of 
                      F_P -> replicate rt cfee
                      PO_FirstN n -> lastN rt $ replicate n 0.0 ++ replicate (ot-n) cfee 
        -- initRow = CF.LoanFlow lastPayDate cb 0.0 0.0 0.0 0.0 0.0 0.0 0.0 Nothing
        _flows = let 
                  _rt = succ rt 
                 in 
                  zipWith10 CF.LoanFlow (lastPayDate:cf_dates) (cb:stressed_bal_flow) (0:prin_flow) (0:int_flow) 
                                        (replicate _rt 0.0) (replicate _rt 0.0) (replicate _rt 0.0) (replicate _rt 0.0) 
                                        (replicate _rt orate) (replicate _rt Nothing)
                                
        flows = cutBy Inc Future asOfDay _flows
        begBal = CF.buildBegBal flows


  getCurrentBal (Installment _ b _ _ ) = b
  getOriginInfo (Installment oi _ _ _) = oi
  getOriginBal (Installment (LoanOriginalInfo ob _ _ _ _ _ _) _ _ _) = ob

  getOriginRate (Installment (LoanOriginalInfo _ or _ _ _ _ _) _ _ _) 
    = case or of
       Fix _ _r -> _r
       Floater _ _ _ _r _ _ _ _ -> _r

  isDefaulted (Installment _ _ _ (Defaulted _)) = True
  isDefaulted (Installment {}) = False

  getPaymentDates (Installment (LoanOriginalInfo _ _ ot p sd _ _) _ _ _) extra 
    = genDates sd p (ot+extra)

  getOriginDate (Installment (LoanOriginalInfo _ _ ot p sd _ _) _ _ _) = sd
  
  getRemainTerms (Installment (LoanOriginalInfo _ _ ot p sd _ _) _ rt _) = rt

  updateOriginDate (Installment (LoanOriginalInfo ob or ot p sd _type _obligor) cb rt st) nd
    = Installment (LoanOriginalInfo ob or ot p nd _type _obligor) cb rt st

  resetToOrig (Installment (LoanOriginalInfo ob or ot p sd _type _obligor) cb rt st)
    = Installment (LoanOriginalInfo ob or ot p sd _type _obligor) ob ot st

  projCashflow inst@(Installment (LoanOriginalInfo ob or ot p sd pt _) cb rt Current) 
               asOfDay 
               pAssump@(A.InstallmentAssump defaultAssump prepayAssump recoveryAssump ams,_,_)
               mRates
      = let
          recoveryLag = maybe 0 getRecoveryLag recoveryAssump
          lastPayDate:cfDates = lastN (rt + recoveryLag +1) $ sd:getPaymentDates inst recoveryLag
          
          opmt = divideBI ob ot
          orate = getOriginRate inst
          ofee = mulBIR ob orate
          
          remainTerms = reverse $ replicate recoveryLag 0 ++ [0..rt]

          scheduleBalances = scanl (-) ob (replicate ot opmt)
          currentScheduleBal = scheduleBalances !! (ot - rt) -- `debug` ("RT->"++show rt)
          currentFactor = divideBB cb currentScheduleBal
        in  
          do 
            ppyRates <- Ast.buildPrepayRates inst (lastPayDate:cfDates) prepayAssump
            defRates <- Ast.buildDefaultRates inst (lastPayDate:cfDates) defaultAssump
            let (txns,_,_) = projectInstallmentFlow (cb,lastPayDate,(opmt,ofee),orate,currentFactor,pt,ot) (cfDates,defRates,ppyRates,remainTerms) 
            let (futureTxns,historyM) = CF.cutoffTrs asOfDay (patchLossRecovery (DL.toList txns) recoveryAssump)
            let begBal = CF.buildBegBal futureTxns
            return $ (applyHaircut ams (CF.CashFlowFrame (begBal,asOfDay,Nothing) futureTxns), historyM)

  -- ^ project with defaulted at a date
  projCashflow inst@(Installment (LoanOriginalInfo ob or ot p sd ptype _) cb rt (Defaulted (Just defaultedDate))) 
               asOfDay 
               (_,_,(A.DefaultedRecovery rr lag timing))
               mRates
    = let 
         (cf_dates1,cf_dates2) = splitAt lag $ genDates defaultedDate p (lag+length timing)
         beforeRecoveryTxn = [  CF.LoanFlow d cb 0 0 0 0 0 0 cr Nothing | d <- cf_dates1 ]
         recoveries = calcRecoveriesFromDefault cb rr timing
         bals = scanl (-) cb recoveries
         _txns = [  CF.LoanFlow d b 0 0 0 0 r 0 cr Nothing | (b,d,r) <- zip3 bals cf_dates2 recoveries ]
         futureTxns = cutBy Inc Future asOfDay $ beforeRecoveryTxn++_txns
         begBal = CF.buildBegBal futureTxns
      in 
         Right $ (CF.CashFlowFrame (begBal,asOfDay,Nothing) futureTxns ,Map.empty)
      where 
        cr = getOriginRate inst
  
  -- ^ project cashflow with defaulted status
  projCashflow inst@(Installment _ cb rt (Defaulted Nothing)) asOfDay assumps _
    = Right $ (CF.CashFlowFrame (cb, asOfDay, Nothing) $ [CF.LoanFlow asOfDay cb 0 0 0 0 0 0 (getOriginRate inst) Nothing],Map.empty)
        
  projCashflow a b c d = Left $ "Failed to match when proj mortgage with assumption >>" ++ show a ++ show b ++ show c ++ show d
  
  splitWith (Installment (LoanOriginalInfo ob or ot p sd _type _obligor) cb rt st) rs
    = [ Installment (LoanOriginalInfo (mulBR ob ratio) or ot p sd _type _obligor) (mulBR cb ratio) rt st | ratio <- rs ]