packages feed

Hastructure-0.45.0: src/AssetClass/Receivable.hs

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

module AssetClass.Receivable
  ()
  where

import qualified Data.Time as T
import qualified Cashflow as CF 
import qualified Assumptions as A
import Asset as Ast
import Types
import Lib
import Util
import DateUtil
import InterestRate as IR

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

import AssetClass.AssetBase
import AssetClass.AssetCashflow
import Debug.Trace
import Assumptions (AssetPerfAssumption(ReceivableAssump))
import GHC.Float.RealFracMethods (truncateFloatInteger)
import Cashflow (extendTxns)
import qualified Asset as A

debug = flip trace

-- project recovery cashflow from recovery assumption and defaulted balance
buildRecoveryCfs :: StartDate -> Balance -> Maybe A.RecoveryAssumption -> Either String [CF.TsRow]
buildRecoveryCfs _ _ Nothing = Right []
buildRecoveryCfs sd defaultedBal (Just (A.RecoveryByDays r dists))
  = let 
      totalRecoveryAmt = mulBR defaultedBal r
      recoveryDistribution = snd <$>  dists
    in 
      case sum recoveryDistribution of
        1 -> let
               recoveryAmts =  mulBR totalRecoveryAmt <$> recoveryDistribution
               recoveryDates = (\x -> T.addDays (toInteger x)) <$> (fst <$> dists) <*> [sd]
               lossAmts = replicate (pred (length recoveryDates)) 0  ++ [defaultedBal - totalRecoveryAmt]
             in
               Right $ [ CF.ReceivableFlow d 0 0 0 0 0 amt lossAmt Nothing  | (amt,d,lossAmt) <- zip3 recoveryAmts recoveryDates lossAmts]
        _ -> Left $ "Recovery distribution does not sum up to 1, got " ++ show (sum recoveryDistribution) ++ " for " ++ show dists


calcDueFactorFee :: Receivable -> Date -> Balance
calcDueFactorFee r@(Invoice (ReceivableInfo sd ob oa dd ft obr) st) asOfDay
  = case ft of
      Nothing -> 0
      Just (FixedFee b) -> b 
      Just (FixedRateFee r) -> mulBR ob r
      Just (FactorFee r daysInPeriod rnd) -> 
        let 
          periods = case rnd of 
                      Up ->  ceiling ((fromIntegral (daysBetween sd dd)) / (fromIntegral daysInPeriod)) :: Int 
                      Down -> floor ((fromIntegral (daysBetween sd dd)) / (fromIntegral daysInPeriod)) :: Int  
        in 
          fromRational $ (toRational periods) * toRational (mulBR ob r) 
      Just (AdvanceFee r) -> mulBR oa (r  * (yearCountFraction DC_ACT_365F sd dd))
      Just (CompoundFee fs) -> 
        let 
          newReceivables = [ Invoice (ReceivableInfo sd ob oa dd (Just newFeeType) obr) st  | newFeeType <- fs] 
        in 
          sum $ (`calcDueFactorFee` asOfDay) <$> newReceivables


instance Asset Receivable where 

  getPaymentDates r@(Invoice (ReceivableInfo sd ob oa dd ft _) st) _ = [dd]

  calcCashflow r@(Invoice (ReceivableInfo sd ob oa dd ft _) st) asOfDay _ 
    = Right $ CF.CashFlowFrame (ob,asOfDay,Nothing) $ cutBy Inc Future asOfDay txns
    where
      payDate = dd
      feeDue = calcDueFactorFee r payDate
      initTxn = CF.ReceivableFlow sd ob 0 0 0 0 0 0 Nothing

      feePaid = min ob feeDue
      principal = max 0 $ ob - feeDue

      txns = [initTxn,CF.ReceivableFlow payDate 0 0 principal feePaid 0 0 0 Nothing]

  getCurrentBal r@(Invoice (ReceivableInfo sd ob oa dd ft _) st) = ob

  isDefaulted r@(Invoice (ReceivableInfo sd ob oa dd ft _) Current) = False
  isDefaulted r@(Invoice (ReceivableInfo sd ob oa dd ft _) _) = True

  getOriginDate r@(Invoice (ReceivableInfo sd ob oa dd ft _) st) = sd

  resetToOrig r@(Invoice (ReceivableInfo sd ob oa dd ft _) st) = r

  getRemainTerms r@(Invoice (ReceivableInfo sd ob oa dd ft _) st) = 1

  getOriginRate _ = 0
  getCurrentRate _ = 0

  updateOriginDate r@(Invoice (ReceivableInfo sd ob oa dd ft obr) st) newDate 
    = let 
        gaps = daysBetween sd dd
      in 
        Invoice (ReceivableInfo newDate ob oa (T.addDays gaps newDate) ft obr) st
    
  splitWith r@(Invoice (ReceivableInfo sd ob oa dd ft obr) st) rs 
    = [ Invoice (ReceivableInfo sd (mulBR ob ratio) (mulBR oa ratio) dd ft obr) st | ratio <- rs ]

  -- Defaulted Invoice
  projCashflow r@(Invoice (ReceivableInfo sd ob oa dd ft _) (Defaulted _))
               asOfDay
               massump@(A.ReceivableAssump _ amr ams, _ , _)
               mRates
    = Right $ (CF.CashFlowFrame (ob,asOfDay,Nothing) futureTxns, historyM)
    where
      payDate = dd
      initTxn = CF.ReceivableFlow sd ob 0 0 0 0 0 0 Nothing
      txns = [initTxn, CF.ReceivableFlow asOfDay 0 0 0 0 ob 0 ob Nothing]
      (futureTxns,historyM)= CF.cutoffTrs asOfDay (patchLossRecovery txns amr)


  -- Performing Invoice : default all balance at end of due date
  projCashflow r@(Invoice (ReceivableInfo sd ob oa dd ft _) Current) 
               asOfDay
               massump@(A.ReceivableAssump (Just A.DefaultAtEnd) amr ams, _ , _)
               mRates
    = let 
        payDate = dd
        feeDue = calcDueFactorFee r payDate
        -- initTxn = [CF.ReceivableFlow sd ob 0 0 0 0 0 0 Nothing]

        realizedLoss = case amr of
                        Nothing -> ob
                        Just _ -> 0
        txns = [CF.ReceivableFlow payDate 0 0 0 0 ob 0 realizedLoss Nothing]
      in
        do 
          recoveryFlow <- buildRecoveryCfs payDate ob amr
          let (futureTxns,historyM) = CF.cutoffTrs asOfDay $ txns++recoveryFlow
          return $ (CF.CashFlowFrame (ob,asOfDay,Nothing) futureTxns, historyM)

  -- Performing Invoice : 
  projCashflow r@(Invoice (ReceivableInfo sd ob oa dd ft _) Current) 
               asOfDay
               massump@(A.ReceivableAssump amd amr ams, _ , _)
               mRates
    = let
        payDate = dd
        feeDue = calcDueFactorFee r payDate
        initTxn = CF.ReceivableFlow sd ob 0 0 0 0 0 0 Nothing
      in 
        do 
          defaultRates <- A.buildDefaultRates r (sd:[dd]) amd
          let defaultAmt = mulBR ob (head defaultRates)
          let afterDefaultBal =  ob - defaultAmt
          let afterDefaultFee =  mulBR feeDue (1 - head defaultRates)

          let feePaid = min afterDefaultBal afterDefaultFee
          let principal = max 0 $ afterDefaultBal - feePaid
      
          let realizedLoss = case amr of
                          Nothing -> defaultAmt
                          Just _ -> 0
      
          let txns = [initTxn, CF.ReceivableFlow payDate 0 0 principal feePaid defaultAmt 0 realizedLoss Nothing]
          recoveryFlow <- buildRecoveryCfs payDate defaultAmt amr
          let (futureTxns,historyM) = CF.cutoffTrs asOfDay $ txns++recoveryFlow
          return $ (CF.CashFlowFrame (ob,asOfDay,Nothing) futureTxns, historyM)

  projCashflow a b c d = Left $ "Failed to match when proj receivable with assumption >>" ++ show a ++ show b ++ show c ++ show d