Skip to content

Commit

Permalink
expose bond issuance assumption
Browse files Browse the repository at this point in the history
  • Loading branch information
yellowbean committed Jun 22, 2024
1 parent d2e91f6 commit 0d56a01
Show file tree
Hide file tree
Showing 19 changed files with 1,014 additions and 410 deletions.
7 changes: 4 additions & 3 deletions app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ instance ToSchema AB.AssetUnion
instance ToSchema PoolId
instance ToSchema DealStatus
instance ToSchema DateType
instance ToSchema DateDesp
instance ToSchema ActionOnDate
instance ToSchema DB.DateDesp
instance ToSchema DB.ActionOnDate
instance ToSchema DealStats
instance ToSchema Cmp
instance ToSchema PricingMethod
Expand Down Expand Up @@ -181,7 +181,7 @@ instance ToSchema CE.LiqDrawType
instance ToSchema CustomDataType
instance ToSchema TRG.Trigger
instance ToSchema TRG.TriggerEffect
instance ToSchema OverrideType
instance ToSchema DB.OverrideType
instance ToSchema Types.BalanceSheetReport
instance ToSchema Types.CashflowReport
instance ToSchema Types.BookItem
Expand All @@ -191,6 +191,7 @@ instance ToSchema AB.AssociateExp
instance ToSchema AB.AssociateIncome
instance ToSchema RV.RevolvingPool
instance ToSchema (TsPoint [AB.AssetUnion])
instance ToSchema (TsPoint AP.IssueBondEvent)
instance ToSchema AP.NonPerfAssumption
instance ToSchema AP.BondPricingInput
instance ToSchema AP.RevolvingAssumption
Expand Down
2 changes: 1 addition & 1 deletion src/Asset.hs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class (Show a,IR.UseRate a) => Asset a where
splitWith :: a -> [Rate] -> [a]
-- | ! Change the origination date of an asset
updateOriginDate :: a -> Date -> a
-- | get Last Interest Payment date
-- | Get Last Interest Payment date
getLastInterestPaymentDate :: a -> Maybe Date
-- | Calculate Accrued Interest
calcAccruedInterest :: a -> Date -> Balance
Expand Down
5 changes: 5 additions & 0 deletions src/AssetClass/AssetBase.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module AssetClass.AssetBase
,LeaseStepUp(..),AccrualPeriod(..),PrepayPenaltyType(..)
,AmortPlan(..),Loan(..),Mortgage(..),AssetUnion(..),MixedAsset(..),FixedAsset(..)
,AmortRule(..),Capacity(..),AssociateExp(..),AssociateIncome(..),ReceivableFeeType(..),Receivable(..)
,ProjectedCashflow(..)
,calcAssetPrinInt, calcPmt
)
where
Expand Down Expand Up @@ -171,6 +172,10 @@ data Mortgage = Mortgage OriginalInfo Balance IRate RemainTerms (Maybe BorrowerN
| ScheduleMortgageFlow Date [CF.TsRow] DatePattern
deriving (Show,Generic,Eq,Ord)

data ProjectedCashflow = ProjectedFlowFixed Date [CF.TsRow] DatePattern
| ProjectedFlowMixFloater Date [CF.TsRow] DatePattern (Rate, IRate) [(Rate, Spread, Index)]
deriving (Show,Generic,Eq,Ord)

data Receivable = Invoice OriginalInfo Status
| DUMMY4
deriving (Show,Generic,Eq,Ord)
Expand Down
240 changes: 240 additions & 0 deletions src/AssetClass/ProjectedCashFlow.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveGeneric #-}

module AssetClass.ProjectedCashFlow
()
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 qualified Cashflow as CF

import AssetClass.AssetBase
import AssetClass.AssetCashflow

import qualified Assumptions as A

import Cashflow (extendTxns,TsRow(..),mflowBalance)

import Debug.Trace

projectScheduleFlow :: [CF.TsRow] -> Rate -> Balance -> [CF.TsRow] -> [DefaultRate] -> [PrepaymentRate] -> [Amount] -> [Amount] -> (Int, Rate) -> [CF.TsRow]
projectScheduleFlow trs _ last_bal [] _ _ [] [] (_,_) = trs
projectScheduleFlow trs bal_factor last_bal (flow:flows) (defRate:defRates) (ppyRate:ppyRates) recV lossV (recoveryLag,recoveryRate)
= projectScheduleFlow (trs++[tr]) surviveRate endBal flows defRates ppyRates (tail recVector) (tail lossVector) (recoveryLag,recoveryRate) -- `debug` ("===>C")
where
startBal = last_bal
defAmt = mulBR startBal defRate
ppyAmt = mulBR (startBal - defAmt) ppyRate
afterBal = startBal - defAmt - ppyAmt

surviveRate = (1 - defRate) * (1 - ppyRate) * bal_factor
schedulePrin = mulBR (CF.mflowPrincipal flow) surviveRate --TODO round trip -- `debug` ("Schedule Principal"++(printf "%.2f" (CF.mflowPrincipal flow))++" Rate"++show(_schedule_rate))
scheduleInt = mulBR (CF.mflowInterest flow) surviveRate

newRec = mulBR defAmt recoveryRate
newLoss = mulBR defAmt (1 - recoveryRate)

recVector = replace recV recoveryLag newRec
lossVector = replace lossV recoveryLag newLoss

endBal = max 0 $ afterBal - schedulePrin

tr = CF.MortgageFlow (CF.getDate flow) endBal schedulePrin scheduleInt ppyAmt defAmt (head recVector) (head lossVector) 0.0 Nothing Nothing Nothing--TODO missing ppy-penalty here

projectScheduleFlow trs b_factor lastBal [] _ _ (r:rs) (l:ls) (recovery_lag,recovery_rate)
= projectScheduleFlow (trs++[tr]) b_factor lastBal [] [] [] rs ls (recovery_lag - 1,recovery_rate)
where
remain_length = length rs
lastDate = CF.getDate (last trs)
flowDate = nextDate lastDate Lib.Monthly
tr = CF.MortgageFlow flowDate lastBal 0 0 0 0 r l 0.0 Nothing Nothing Nothing



projCfwithAssumption :: (CF.CashFlowFrame, DatePattern) -> A.AssetPerfAssumption -> Date -> Maybe [RateAssumption] -> CF.CashFlowFrame
projCfwithAssumption (cf@(CF.CashFlowFrame (begBal, begDate, accInt) flows), dp)
pAssump@(A.MortgageAssump mDefault mPrepay mRecovery mEs)
asOfDay
mRates
= CF.CashFlowFrame (cb,asOfDay,Nothing) futureTxns
where
curveDatesLength = recoveryLag + length flows
endDate = CF.getDate (last flows)
(ppyRates,defRates,recoveryRate,recoveryLag) = buildAssumptionPpyDefRecRate (begDate:cfDates) pAssump
extraDates = genSerialDates dp Exc endDate recoveryLag
cfDates = (CF.getDate <$> flows) ++ extraDates

txns = projectScheduleFlow [] 1.0 begBal flows defRates ppyRates
(replicate curveDatesLength 0.0)
(replicate curveDatesLength 0.0)
(recoveryLag,recoveryRate)

(futureTxns,historyM) = CF.cutoffTrs asOfDay txns

cb = (CF.mflowBegBalance . head) futureTxns

projIndexCashflows :: ([Date],[Balance],[Principal],Index,Spread) -> Maybe [RateAssumption] -> CF.CashFlowFrame
projIndexCashflows (ds,bals,principals,index,spd) (Just ras) =
let
mIndexToApply = A.getRateAssumption ras index
indexRates = A.lookupRate0 ras index <$> ds

rates = (spd +) <$> indexRates
interestFlow = zipWith (flip mulBIR) rates bals
flowSize = length bals
in
CF.CashFlowFrame (head bals, head ds, Nothing) $
zipWith12 MortgageFlow
ds
bals
principals
interestFlow
(replicate flowSize 0 )
(replicate flowSize 0 )
(replicate flowSize 0 )
(replicate flowSize 0 )
rates
(replicate flowSize Nothing)
(replicate flowSize Nothing)
(replicate flowSize Nothing)




-- projMortgageFlow :: (Date,[Balance]) -> A.AssetPerfAssumption -> Maybe [RateAssumption] -> ([Balance],[Balance],[Balance])
-- projMortgageFlow (begDate,bals) pAssump mRates =
-- let
-- curveDatesLength = recoveryLag + length bals
-- extraPeriods = recoveryLag
-- endDate = last ds
-- extraDates = genSerialDates dp Exc endDate recoveryLag
-- cfDates = ds ++ extraDates
-- begBal = head bals
-- (ppyRates,defRates,recoveryRate,recoveryLag) = buildAssumptionPpyDefRecRate (begDate:cfDates) pAssump
--
-- txns = projectScheduleFlow [] 1.0 begBal flows defRates ppyRates
-- (replicate curveDatesLength 0.0)
-- (replicate curveDatesLength 0.0)
-- (recoveryLag,recoveryRate)
--
-- (futureTxns,historyM) = CF.cutoffTrs asOfDay txns
--
-- cb = (CF.mflowBegBalance . head) futureTxns
-- in
-- (cb, futureTxns, historyM)


seperateCashflows :: ProjectedCashflow -> Maybe A.AssetPerfAssumption -> Maybe [RateAssumption] -> (CF.CashFlowFrame, [CF.CashFlowFrame])
seperateCashflows (ProjectedFlowMixFloater begDate flows _ (fixPct,fixRate) floaterList)
(Just pAssump)
mRates
= let
begBal = CF.mflowBegBalance $ head flows
totalBals = begBal: (CF.mflowBalance <$> flows)
ds = CF.mflowDate <$> flows

(ppyRates,defRates,recoveryRate,recoveryLag) = buildAssumptionPpyDefRecRate (begDate:ds) pAssump


flowSize = length ds
fixedBals = (flip mulBR) fixPct <$> totalBals
fixedPrincipalFlow = replicate flowSize 0 -- flip mulBR fixPct <$> CF.mflowPrincipal <$> flows
fixedInterestFlow = replicate flowSize 0 -- flip mulBIR fixRate <$> fixedBals
fixedCashFlow = CF.CashFlowFrame (head fixedBals, begDate, Nothing) []
-- zipWith12 CF.MortgageFlow $
-- ds $
-- init fixedBals $
-- fixedPrincipalFlow $
-- fixedInterestFlow $
-- replicate flowSize 0 $
-- replicate flowSize 0 $
-- replicate flowSize 0 $
-- replicate flowSize 0 $
-- replicate flowSize fixRate $
-- replicate flowSize Nothing $
-- replicate flowSize Nothing $
-- replicate flowSize Nothing

floatBals = zipWith (-) totalBals fixedBals
floatPrincipalFlow = zipWith (-) (CF.mflowPrincipal <$> flows) fixedPrincipalFlow

-- rs = (head <$> floaterList)
-- floaterSize = length rs
-- indexes = last <$> floaterList
-- spds = (\(a,b,c) -> b) <$> floaterList
-- floatBalsBreakDown = zipWith mulBR floatBals rs
-- floatPrincipalFlowBreakDown = zipWith mulBR floatPrincipalFlow rs
-- floatedCashFlow = \x -> projIndexCashflows x mRates <$> zip5
-- (repeat ds)
-- floatBalsBreakDown
-- floatPrincipalFlowBreakDown
-- indexes
-- spds
in
-- (fixedCashFlow, floatedCashFlow)
(fixedCashFlow, [CF.CashFlowFrame (0, begDate, Nothing) []])



instance Ast.Asset ProjectedCashflow where

getCurrentBal (ProjectedFlowFixed _ cf _ ) = CF.mflowBalance (head cf)
getCurrentBal (ProjectedFlowMixFloater _ cf _ _ _ ) = CF.mflowBegBalance (head cf)

getOriginBal (ProjectedFlowFixed _ cf _ ) = CF.mflowBegBalance (head cf)
getOriginBal (ProjectedFlowMixFloater _ cf _ _ _ ) = CF.mflowBegBalance (head cf)

isDefaulted f = error ""
getOriginDate f = error ""
getOriginInfo f = error ""

calcCashflow f@(ProjectedFlowFixed begDate flows _) d _
= CF.CashFlowFrame ( (CF.mflowBalance . head) flows, begDate, Nothing ) flows

calcCashflow f@(ProjectedFlowMixFloater {}) d mRate
= let
(fixedCashFlow, floatedCashFlow) = seperateCashflows f Nothing mRate
in
-- Map.foldl CF.mergePoolCf fixedCashFlow floatedCashFlow
fixedCashFlow

projCashflow f asOfDay _ mRates = (calcCashflow f asOfDay mRates, Map.empty)


projCashflow f asOfDay (_, _, _) mRates
= let
(fixedCashFlow, floatedCashFlow) = seperateCashflows f Nothing mRates
in
-- Map.foldl CF.mergePoolCf fixedCashFlow floatedCashFlow
(fixedCashFlow, Map.empty)

getBorrowerNum f = 0

splitWith f rs = [f]

instance IR.UseRate ProjectedCashflow where
isAdjustbleRate _ = False

getIndex _ = Nothing
getIndexes _ = Nothing
getResetDates _ = []
28 changes: 17 additions & 11 deletions src/Assumptions.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ module Assumptions (BondPricingInput(..)
,NonPerfAssumption(..),AssetPerf
,AssetDelinquencyAssumption(..)
,AssetDelinqPerfAssumption(..),AssetDefaultedPerfAssumption(..)
,getCDR,calcResetDates,AssumpReceipes)
,getCDR,calcResetDates,AssumpReceipes,IssueBondEvent)
where

import Call as C
import Lib (Ts(..),TsPoint(..),toDate,mkRateTs)
import Liability (Bond)
import Util
import DateUtil
import qualified Data.Map as Map
Expand Down Expand Up @@ -64,18 +65,21 @@ data ApplyAssumptionType = PoolLevel AssetPerf
-- ^ assumption for a named deal
deriving (Show, Generic)

type IssueBondEvent = (String,AccName,Bond) -- bond group name, account name, bond

data NonPerfAssumption = NonPerfAssumption {
stopRunBy :: Maybe Date -- ^ optional stop day,which will stop cashflow projection
,projectedExpense :: Maybe [(FeeName,Ts)] -- ^ optional expense projection
,callWhen :: Maybe [C.CallOption] -- ^ optional call options set, once any of these were satisfied, then clean up waterfall is triggered
,revolving :: Maybe RevolvingAssumption -- ^ optional revolving assumption with revoving assets
,interest :: Maybe [RateAssumption] -- ^ optional interest rates assumptions
,inspectOn :: Maybe [(DatePattern,DealStats)] -- ^ optional tuple list to inspect variables during waterfall run
,buildFinancialReport :: Maybe DatePattern -- ^ optional dates to build financial reports
,pricing :: Maybe BondPricingInput -- ^ optional bond pricing input( discount curve etc)
,fireTrigger :: Maybe [(Date,DealCycle,String)] -- ^ optional fire a trigger
stopRunBy :: Maybe Date -- ^ optional stop day,which will stop cashflow projection
,projectedExpense :: Maybe [(FeeName,Ts)] -- ^ optional expense projection
,callWhen :: Maybe [C.CallOption] -- ^ optional call options set, once any of these were satisfied, then clean up waterfall is triggered
,revolving :: Maybe RevolvingAssumption -- ^ optional revolving assumption with revoving assets
,interest :: Maybe [RateAssumption] -- ^ optional interest rates assumptions
,inspectOn :: Maybe [(DatePattern,DealStats)] -- ^ optional tuple list to inspect variables during waterfall run
,buildFinancialReport :: Maybe DatePattern -- ^ optional dates to build financial reports
,pricing :: Maybe BondPricingInput -- ^ optional bond pricing input( discount curve etc)
,fireTrigger :: Maybe [(Date,DealCycle,String)] -- ^ optional fire a trigger
,makeWholeWhen :: Maybe (Date,Spread,Table Float Spread)
} deriving (Show,Generic)
,issueBondSchedule :: Maybe [TsPoint IssueBondEvent]
} deriving (Show, Generic)

data AssumptionInput = Single ApplyAssumptionType NonPerfAssumption -- ^ one assumption request
| Multiple (Map.Map String ApplyAssumptionType) NonPerfAssumption -- ^ multiple assumption request in a single request
Expand Down Expand Up @@ -225,6 +229,8 @@ calcResetDates (r:rs) bs

$(deriveJSON defaultOptions ''BondPricingInput)

-- $(deriveJSON defaultOptions ''IssueBondEvent)

$(concat <$> traverse (deriveJSON defaultOptions) [''ApplyAssumptionType, ''AssetPerfAssumption
, ''AssetDefaultedPerfAssumption, ''AssetDelinqPerfAssumption, ''NonPerfAssumption, ''AssetDefaultAssumption
, ''AssetPrepayAssumption, ''RecoveryAssumption, ''ExtraStress
Expand Down
6 changes: 5 additions & 1 deletion src/Cashflow.hs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import Lib (weightedBy,toDate,getIntervalFactors,daysBetween,paySeqLiabilitiesAm
import Util (mulBR,mulBInt,mulIR,lastOf)
import DateUtil ( splitByDate )
import Types
--import Deal.DealType
import qualified Data.Map as Map
import qualified Data.Time as T
import qualified Data.List as L
Expand Down Expand Up @@ -146,7 +147,7 @@ scaleTsRow r (MortgageDelinqFlow d b p i prep delinq def rec los rat mbn pp st)
rat
mbn
pp
((splitStats r) <$> st)
(splitStats r <$> st)
scaleTsRow r (LoanFlow d b p i prep def rec los rat st)
= LoanFlow d (fromRational r * b) (fromRational r * p) (fromRational r * i) (fromRational r * prep) (fromRational r * def) (fromRational r * rec) (fromRational r * los) rat ((splitStats r) <$> st)
scaleTsRow r (LeaseFlow d b rental) = LeaseFlow d (fromRational r * b) (fromRational r * rental)
Expand All @@ -161,6 +162,7 @@ type BeginStatus = (BeginBalance, BeginDate, AccuredInterest)

data CashFlowFrame = CashFlowFrame BeginStatus [TsRow]
| MultiCashFlowFrame (Map.Map String [CashFlowFrame])
-- | CashFlowFrameIndex BeginStatus [TsRow] IR.Index
deriving (Eq,Generic,Ord)

instance Show CashFlowFrame where
Expand Down Expand Up @@ -844,6 +846,8 @@ setPrepaymentPenalty _ _ = error "prepay pental only applies to MortgageFlow"
setPrepaymentPenaltyFlow :: [Balance] -> [TsRow] -> [TsRow]
setPrepaymentPenaltyFlow bals trs = [ setPrepaymentPenalty bal tr | (bal,tr) <- zip bals trs]


-- ^ split single cashflow record by a rate
splitTs :: Rate -> TsRow -> TsRow
splitTs r (MortgageDelinqFlow d bal p i ppy delinq def recovery loss rate mB mPPN mStat)
= MortgageDelinqFlow d (mulBR bal r) (mulBR p r) (mulBR i r) (mulBR ppy r)
Expand Down
Loading

0 comments on commit 0d56a01

Please sign in to comment.